diff --git a/CHANGELOG.md b/CHANGELOG.md index de84dfdb6..4ff08a751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed usage of deprecated pydantic validation methods - Fixed qelectron_db retrieval in result object - Fixed editability of Qelectron on settings page - UI changes +- Certain pydantic v2 related updates +- Fixed lattice's metadata propagation to electron's metadata in case no metadata was provided to the electron ### Operations @@ -45,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Significant Changes] Migrated core server-side code to new data access layer. - Changed the way UI was accessing the qelectron database to access it directly from the mdb file in object store - Update version of browserverify-sign +- Limiting cloudpickle version to less than 3.0 for now ### Added diff --git a/covalent/_serialize/transport_graph.py b/covalent/_serialize/transport_graph.py index a7ce04eec..217c2e61c 100644 --- a/covalent/_serialize/transport_graph.py +++ b/covalent/_serialize/transport_graph.py @@ -71,7 +71,7 @@ def _deserialize_edge(e: EdgeSchema) -> dict: return { "source": e.source, "target": e.target, - "attrs": e.metadata.dict(), + "attrs": e.metadata.model_dump(), } diff --git a/covalent/_shared_files/qinfo.py b/covalent/_shared_files/qinfo.py index 92056173b..4c93bfc2f 100644 --- a/covalent/_shared_files/qinfo.py +++ b/covalent/_shared_files/qinfo.py @@ -36,11 +36,11 @@ class QNodeSpecs(BaseModel): num_trainable_params: int = None num_device_wires: int device_name: str - diff_method: Optional[str] + diff_method: Optional[str] = None expansion_strategy: str gradient_options: Dict[str, int] - interface: Optional[str] - gradient_fn: Any # can be string or `qml.gradients.gradient_transform` + interface: Optional[str] = None + gradient_fn: Any = None # can be string or `qml.gradients.gradient_transform` num_gradient_executions: Any = 0 num_parameter_shift_executions: int = None @@ -56,7 +56,7 @@ class QElectronInfo(BaseModel): device_import_path: str # used to inherit type converters and other methods device_shots: Union[ None, int, Sequence[int], Sequence[Union[int, Sequence[int]]] - ] # optional default for execution devices + ] = None # optional default for execution devices device_shots_type: Any = None device_wires: int # this can not be reliably inferred from tapes alone pennylane_active_return: bool # client-side status of `pennylane.active_return()` diff --git a/covalent/_shared_files/schemas/edge.py b/covalent/_shared_files/schemas/edge.py index 2df0b1cd7..5b7ac82b2 100644 --- a/covalent/_shared_files/schemas/edge.py +++ b/covalent/_shared_files/schemas/edge.py @@ -29,8 +29,8 @@ class EdgeMetadata(BaseModel): edge_name: str - param_type: Optional[str] - arg_index: Optional[int] + param_type: Optional[str] = None + arg_index: Optional[int] = None class EdgeSchema(BaseModel): diff --git a/covalent/_shared_files/schemas/electron.py b/covalent/_shared_files/schemas/electron.py index 3635724e8..d75500d82 100644 --- a/covalent/_shared_files/schemas/electron.py +++ b/covalent/_shared_files/schemas/electron.py @@ -19,7 +19,7 @@ from datetime import datetime from typing import Dict, Optional -from pydantic import BaseModel, validator +from pydantic import BaseModel, field_validator from .asset import AssetSchema from .common import StatusEnum @@ -122,7 +122,7 @@ class ElectronSchema(BaseModel): assets: ElectronAssets custom_assets: Optional[Dict[str, AssetSchema]] = None - @validator("custom_assets") + @field_validator("custom_assets") def check_custom_asset_keys(cls, v): if v is not None: for key in v: diff --git a/covalent/_shared_files/schemas/lattice.py b/covalent/_shared_files/schemas/lattice.py index 1bc7f4da2..80a8c62aa 100644 --- a/covalent/_shared_files/schemas/lattice.py +++ b/covalent/_shared_files/schemas/lattice.py @@ -18,7 +18,7 @@ from typing import Dict, Optional -from pydantic import BaseModel, validator +from pydantic import BaseModel, field_validator from .asset import AssetSchema from .transport_graph import TransportGraphSchema @@ -115,7 +115,7 @@ class LatticeSchema(BaseModel): transport_graph: TransportGraphSchema - @validator("custom_assets") + @field_validator("custom_assets") def check_custom_asset_keys(cls, v): if v is not None: for key in v: diff --git a/covalent/_workflow/electron.py b/covalent/_workflow/electron.py index 67f3044ba..ebe313675 100644 --- a/covalent/_workflow/electron.py +++ b/covalent/_workflow/electron.py @@ -16,6 +16,7 @@ """Class corresponding to computation nodes.""" + import inspect import json import operator diff --git a/covalent/_workflow/lattice.py b/covalent/_workflow/lattice.py index aa09ac033..2c4159d99 100644 --- a/covalent/_workflow/lattice.py +++ b/covalent/_workflow/lattice.py @@ -203,7 +203,7 @@ def build_graph(self, *args, **kwargs) -> None: named_args, named_kwargs = get_named_params(workflow_function, args, kwargs) new_args = [v for _, v in named_args.items()] - new_kwargs = {k: v for k, v in named_kwargs.items()} + new_kwargs = dict(named_kwargs.items()) self.inputs = TransportableObject({"args": args, "kwargs": kwargs}) self.named_args = TransportableObject(named_args) @@ -215,7 +215,7 @@ def build_graph(self, *args, **kwargs) -> None: new_metadata = { name: DEFAULT_METADATA_VALUES[name] for name in constraint_names - if not self.metadata[name] + if self.metadata[name] is None } new_metadata = encode_metadata(new_metadata) @@ -330,8 +330,8 @@ def lattice( # Add custom metadata fields here deps_bash: Union[DepsBash, list, str] = None, deps_pip: Union[DepsPip, list] = None, - call_before: Union[List[DepsCall], DepsCall] = [], - call_after: Union[List[DepsCall], DepsCall] = [], + call_before: Union[List[DepsCall], DepsCall] = None, + call_after: Union[List[DepsCall], DepsCall] = None, triggers: Union["BaseTrigger", List["BaseTrigger"]] = None, # e.g. schedule: True, whether to use a custom scheduling logic or not ) -> Lattice: diff --git a/covalent/_workflow/transport.py b/covalent/_workflow/transport.py index 191789580..c1c7dd31d 100644 --- a/covalent/_workflow/transport.py +++ b/covalent/_workflow/transport.py @@ -37,7 +37,7 @@ def encode_metadata(metadata: dict) -> dict: encoded_metadata = deepcopy(metadata) if "executor" in metadata: if "executor_data" not in metadata: - encoded_metadata["executor_data"] = {} + encoded_metadata["executor_data"] = None if metadata["executor"] is None else {} if metadata["executor"] is not None and not isinstance(metadata["executor"], str): encoded_executor = metadata["executor"].to_dict() encoded_metadata["executor"] = encoded_executor["short_name"] @@ -45,7 +45,9 @@ def encode_metadata(metadata: dict) -> dict: if "workflow_executor" in metadata: if "workflow_executor_data" not in metadata: - encoded_metadata["workflow_executor_data"] = {} + encoded_metadata["workflow_executor_data"] = ( + None if metadata["workflow_executor"] is None else {} + ) if metadata["workflow_executor"] is not None and not isinstance( metadata["workflow_executor"], str ): diff --git a/covalent/executor/qbase.py b/covalent/executor/qbase.py index ca04a1c53..cc3e55144 100644 --- a/covalent/executor/qbase.py +++ b/covalent/executor/qbase.py @@ -25,7 +25,12 @@ import orjson import pennylane as qml from mpire import WorkerPool -from pydantic import BaseModel, Extra, Field, root_validator # pylint: disable=no-name-in-module +from pydantic import ( # pylint: disable=no-name-in-module + BaseModel, + ConfigDict, + Field, + model_validator, +) from .._shared_files.qinfo import QElectronInfo, QNodeSpecs @@ -109,10 +114,10 @@ def override_shots(self) -> Union[int, None]: # User has specified `shots` as an int. return self.shots - class Config: - extra = Extra.allow + model_config = ConfigDict(extra="allow") - @root_validator(pre=True) + @model_validator(mode="before") + @classmethod def set_name(cls, values): # pylint: disable=no-self-argument # Set the `name` attribute to the class name @@ -138,7 +143,7 @@ def run_circuit(self, qscript, device, result_obj: "QCResult") -> "QCResult": return result_obj def dict(self, *args, **kwargs): - dict_ = super().dict(*args, **kwargs) + dict_ = super().model_dump(*args, **kwargs) # Ensure shots is a hashable value. shots = dict_.get("shots") diff --git a/covalent/executor/schemas.py b/covalent/executor/schemas.py index ef99c9c71..2e9b16204 100644 --- a/covalent/executor/schemas.py +++ b/covalent/executor/schemas.py @@ -20,7 +20,7 @@ from typing import Dict, List -from pydantic import BaseModel, validator +from pydantic import BaseModel, field_validator from covalent._shared_files.schemas.asset import AssetUpdate from covalent._shared_files.util_classes import RESULT_STATUS, Status @@ -41,7 +41,7 @@ class TaskUpdate(BaseModel): status: Status assets: Dict[str, AssetUpdate] - @validator("status") + @field_validator("status") def validate_status(cls, v): if RESULT_STATUS.is_terminal(v): return v diff --git a/covalent/quantum/qcluster/base.py b/covalent/quantum/qcluster/base.py index cef232754..9f2be2427 100644 --- a/covalent/quantum/qcluster/base.py +++ b/covalent/quantum/qcluster/base.py @@ -20,7 +20,7 @@ from typing import Callable, List, Sequence, Union from mpire.async_result import AsyncResult -from pydantic import BaseModel, Extra +from pydantic import BaseModel, ConfigDict from ...executor.qbase import AsyncBaseQExecutor, BaseQExecutor, QCResult @@ -96,6 +96,4 @@ def selector_function(self, qscript, executors): """ raise NotImplementedError - class Config: - # Allows defining extra state fields in subclasses. - extra = Extra.allow + model_config = ConfigDict(extra="allow") diff --git a/covalent/quantum/qcluster/simulator.py b/covalent/quantum/qcluster/simulator.py index f6cab7c0d..6ef5ef19c 100644 --- a/covalent/quantum/qcluster/simulator.py +++ b/covalent/quantum/qcluster/simulator.py @@ -16,7 +16,7 @@ from typing import Union -from pydantic import validator +from pydantic import field_validator from ...executor.qbase import ( BaseProcessPoolQExecutor, @@ -61,7 +61,7 @@ class Simulator(BaseQExecutor): parallel: Union[bool, str] = "thread" workers: int = 10 - @validator("device") + @field_validator("device") def validate_device(cls, device): # pylint: disable=no-self-argument """ Check that the `device` attribute is NOT a provider or hardware device. diff --git a/covalent_dispatcher/_service/models.py b/covalent_dispatcher/_service/models.py index de9a6db35..43ac3410a 100644 --- a/covalent_dispatcher/_service/models.py +++ b/covalent_dispatcher/_service/models.py @@ -106,7 +106,7 @@ class ElectronAssetKey(str, Enum): class ExportResponseSchema(BaseModel): id: str status: str - result_export: Optional[ResultSchema] + result_export: Optional[ResultSchema] = None class AssetRepresentation(str, Enum): diff --git a/covalent_ui/api/v1/models/dispatch_model.py b/covalent_ui/api/v1/models/dispatch_model.py index 1084ab01b..27b7c91f5 100644 --- a/covalent_ui/api/v1/models/dispatch_model.py +++ b/covalent_ui/api/v1/models/dispatch_model.py @@ -21,7 +21,8 @@ from typing import List, Optional, Union from uuid import UUID -from pydantic import BaseModel, conint +from pydantic import BaseModel, ConfigDict, Field +from typing_extensions import Annotated from covalent_ui.api.v1.utils.models_helper import SortBy, SortDirection from covalent_ui.api.v1.utils.status import Status @@ -30,8 +31,8 @@ class DispatchSummaryRequest(BaseModel): """Dispatch Summary Request model""" - count: conint(gt=0, lt=100) - offset: Optional[conint(gt=-1)] = 0 + count: Annotated[int, Field(gt=0, lt=100)] + offset: Optional[Annotated[int, Field(gt=-1)]] = 0 sort_by: Optional[SortBy] = SortBy.STARTED search: Optional[str] = "" direction: Optional[SortDirection] = SortDirection.DESCENDING @@ -43,16 +44,14 @@ class DispatchModule(BaseModel): dispatch_id: str lattice_name: str - runtime: Optional[Union[int, float, None]] - total_electrons: Optional[Union[int, None]] - total_electrons_completed: Optional[Union[int, None]] - started_at: Optional[Union[datetime, None]] - ended_at: Optional[Union[datetime, None]] + runtime: Optional[Union[int, float, None]] = None + total_electrons: Optional[Union[int, None]] = None + total_electrons_completed: Optional[Union[int, None]] = None + started_at: Optional[Union[datetime, None]] = None + ended_at: Optional[Union[datetime, None]] = None status: Status - updated_at: Optional[Union[datetime, None]] - - class Config: - from_attributes = True + updated_at: Optional[Union[datetime, None]] = None + model_config = ConfigDict(from_attributes=True) class DispatchResponse(BaseModel): @@ -60,11 +59,8 @@ class DispatchResponse(BaseModel): items: List[DispatchModule] total_count: int - - class Config: - """Configure example for openAPI""" - - json_schema_extra = { + model_config = ConfigDict( + json_schema_extra={ "example": { "dispatches": [ { @@ -79,6 +75,7 @@ class Config: "total_count": 10, } } + ) class DeleteDispatchesRequest(BaseModel): @@ -113,11 +110,8 @@ class DispatchDashBoardResponse(BaseModel): total_jobs_new_object: Union[int, None] = None latest_running_task_status: Union[Status, None] = None total_dispatcher_duration: Union[int, None] = None - - class Config: - """Configure example for openAPI""" - - json_schema_extra = { + model_config = ConfigDict( + json_schema_extra={ "example": { "total_jobs": 5, "total_jobs_running": 5, @@ -129,3 +123,4 @@ class Config: "total_dispatcher_duration": 90, } } + ) diff --git a/covalent_ui/api/v1/models/electrons_model.py b/covalent_ui/api/v1/models/electrons_model.py index c9692e512..4080a30ae 100644 --- a/covalent_ui/api/v1/models/electrons_model.py +++ b/covalent_ui/api/v1/models/electrons_model.py @@ -26,10 +26,10 @@ class Job(BaseModel): - job_id: Union[str, None] - start_time: Union[datetime, None] - executor: Union[str, None] - status: Union[str, None] + job_id: Union[str, None] = None + start_time: Union[datetime, None] = None + executor: Union[str, None] = None + status: Union[str, None] = None class JobsResponse(BaseModel): @@ -38,9 +38,9 @@ class JobsResponse(BaseModel): class JobDetails(BaseModel): - overview: Union[dict, None] - circuit: Union[dict, None] - executor: Union[dict, None] + overview: Union[dict, None] = None + circuit: Union[dict, None] = None + executor: Union[dict, None] = None class JobDetailsResponse(BaseModel): diff --git a/covalent_ui/api/v1/models/logs_model.py b/covalent_ui/api/v1/models/logs_model.py index abc24d575..a8a356100 100644 --- a/covalent_ui/api/v1/models/logs_model.py +++ b/covalent_ui/api/v1/models/logs_model.py @@ -19,7 +19,8 @@ from typing import List, Optional, Union -from pydantic import BaseModel, conint +from pydantic import BaseModel, ConfigDict, Field +from typing_extensions import Annotated from covalent_ui.api.v1.utils.models_helper import CaseInsensitiveEnum, SortDirection @@ -34,8 +35,8 @@ class SortBy(CaseInsensitiveEnum): class LogsRequest(BaseModel): """Logs request model""" - count: conint(gt=0, lt=100) - offset: Optional[conint(gt=-1)] = 0 + count: Annotated[int, Field(gt=0, lt=100)] + offset: Optional[Annotated[int, Field(gt=-1)]] = 0 sort_by: Optional[SortBy] = SortBy.LOG_DATE search: Optional[str] = "" direction: Optional[SortDirection] = SortDirection.DESCENDING @@ -46,7 +47,7 @@ class LogsModule(BaseModel): log_date: Union[str, None] = None status: str = "INFO" - message: Optional[Union[str, None]] + message: Optional[Union[str, None]] = None class LogsResponse(BaseModel): @@ -54,11 +55,8 @@ class LogsResponse(BaseModel): items: List[LogsModule] total_count: Union[int, None] = None - - class Config: - """Configure example for openAPI""" - - json_schema_extra = { + model_config = ConfigDict( + json_schema_extra={ "example": { "data": [ { @@ -70,3 +68,4 @@ class Config: "total_count": 1, } } + ) diff --git a/covalent_ui/api/v1/routes/end_points/summary_routes.py b/covalent_ui/api/v1/routes/end_points/summary_routes.py index f9ecc7120..68ae33dfb 100644 --- a/covalent_ui/api/v1/routes/end_points/summary_routes.py +++ b/covalent_ui/api/v1/routes/end_points/summary_routes.py @@ -19,8 +19,9 @@ from typing import Optional from fastapi import APIRouter, Query -from pydantic import conint +from pydantic import Field from sqlalchemy.orm import Session +from typing_extensions import Annotated import covalent_ui.api.v1.database.config.db as db from covalent_ui.api.v1.data_layer.summary_dal import Summary @@ -39,8 +40,8 @@ @routes.get("/list") def get_all_dispatches( - count: Optional[conint(gt=0, lt=100)] = Query(10), - offset: Optional[conint(gt=-1)] = 0, + count: Optional[Annotated[int, Field(gt=0, lt=100)]] = Query(10), + offset: Optional[Annotated[int, Field(gt=-1)]] = 0, sort_by: Optional[SortBy] = SortBy.RUNTIME, search: Optional[str] = "", sort_direction: Optional[SortDirection] = SortDirection.DESCENDING, diff --git a/requirements-client.txt b/requirements-client.txt index 2ad354644..9f7e806aa 100644 --- a/requirements-client.txt +++ b/requirements-client.txt @@ -1,6 +1,6 @@ aiofiles>=0.8.0 aiohttp>=3.8.1 -cloudpickle>=2.0.0 +cloudpickle>=2.0.0,<3 dask[distributed]>=2022.6.0 filelock>=3.12.2 furl>=2.1.3 diff --git a/requirements.txt b/requirements.txt index 0e098d5ae..453d71ebb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,9 +2,9 @@ aiofiles>=0.8.0 aiohttp>=3.8.1 alembic>=1.8.0 click>=8.1.3 -cloudpickle>=2.0.0 +cloudpickle>=2.0.0,<3 dask[distributed]>=2022.6.0 -fastapi>=0.93.0 +fastapi>=0.100.0 filelock>=3.12.2 furl>=2.1.3 lmdbm>=0.0.5 @@ -23,6 +23,7 @@ simplejson>=3.17.6 sqlalchemy>=1.4.37,<2.0.0 sqlalchemy_utils>=0.38.3 toml>=0.10.2 +typing-extensions>=4.8.0 uvicorn[standard]==0.18.3 watchdog>=2.2.1 werkzeug>=2.0.3 diff --git a/tests/covalent_ui_backend_tests/end_points/electrons_test.py b/tests/covalent_ui_backend_tests/end_points/electrons_test.py index 1c55d0e79..5b4b2ec90 100644 --- a/tests/covalent_ui_backend_tests/end_points/electrons_test.py +++ b/tests/covalent_ui_backend_tests/end_points/electrons_test.py @@ -395,7 +395,7 @@ def test_electrons_inputs_bad_request(): def qelectron_mocked_data_for_jobs(mocker): from covalent.quantum.qserver.database import Database - return mocker.patch.object(Database, "get_db", return_value=mock_input_data_jobs) + return mocker.patch.object(Database, "get_db_dict", return_value=mock_input_data_jobs) def test_get_qelectrons_jobs(qelectron_mocked_data_for_jobs):