Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Adding model to metadata and snapshot #6119

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependencies = [
"tornado>=6.3.3",
"ipython>=8.10.0",
"pillow>=9.0.0",
"pydantic>=2.0.0",
]

dynamic = ["version"]
Expand Down
50 changes: 46 additions & 4 deletions src/qcodes/metadatable/metadatable_base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from abc import abstractmethod
from typing import TYPE_CHECKING, Any, Optional, final
from typing import TYPE_CHECKING, Any, Generic, Optional, final

from pydantic import BaseModel
from typing_extensions import TypeVar

from qcodes.utils import deep_update

Expand All @@ -18,9 +21,29 @@
Snapshot = dict[str, Any]


class Metadatable:
def __init__(self, metadata: Optional["Mapping[str, Any]"] = None):
class EmptyMetadataModel(BaseModel):
pass

MetadataType = TypeVar("MetadataType", bound=EmptyMetadataModel)


class EmptySnapshotModel(BaseModel):
pass


SnapshotType = TypeVar("SnapshotType", bound=EmptySnapshotModel)


class Metadatable(Generic[SnapshotType, MetadataType]):
def __init__(
self,
metadata: Optional["Mapping[str, Any]"] = None,
snapshot_model: type[SnapshotType] = EmptySnapshotModel,
metadata_model: type[MetadataType] = EmptyMetadataModel,
):
self.metadata: dict[str, Any] = {}
self._snapshot_model = snapshot_model or EmptySnapshotModel
self._metadata_model = metadata_model or EmptyMetadataModel
self.load_metadata(metadata or {})

def load_metadata(self, metadata: "Mapping[str, Any]") -> None:
Expand Down Expand Up @@ -53,6 +76,16 @@ def snapshot(self, update: Optional[bool] = False) -> Snapshot:

return snap

@final
def typed_snapshot(self) -> SnapshotType:
snapshot_dict = self.snapshot() # probably want to filter metadata here
snapshot = self._snapshot_model(**snapshot_dict)
return snapshot

@final
def typed_metadata(self) -> MetadataType:
return self._metadata_model(**self.metadata)

def snapshot_base(
self,
update: Optional[bool] = False,
Expand All @@ -63,8 +96,17 @@ def snapshot_base(
"""
return {}

# @property
# def metadata_model(self) -> type[BaseModel] | None:
# return self._metadata_model


# @metadata_model.setter
# def metadata_model(self, model: type[BaseModel] | None) -> None:
# self._metadata_model = model


class MetadatableWithName(Metadatable):
class MetadatableWithName(Metadatable[SnapshotType, MetadataType]):
"""Add short_name and full_name properties to Metadatable.
This is used as a base class for all components in QCoDeS that
are members of a station to ensure that they have a name and
Expand Down
26 changes: 25 additions & 1 deletion src/qcodes/parameters/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
from types import MethodType
from typing import TYPE_CHECKING, Any, Callable, Literal

from qcodes.metadatable.metadatable_base import (
EmptyMetadataModel,
EmptySnapshotModel,
MetadataType,
SnapshotType,
)

from .command import Command
from .parameter_base import ParamDataType, ParameterBase, ParamRawDataType
from .sweep_values import SweepFixedValues
Expand All @@ -21,7 +28,7 @@
log = logging.getLogger(__name__)


class Parameter(ParameterBase):
class Parameter(ParameterBase[SnapshotType, MetadataType]):
"""
A parameter represents a single degree of freedom. Most often,
this is the standard parameter for Instruments, though it can also be
Expand Down Expand Up @@ -179,6 +186,8 @@ def __init__(
docstring: str | None = None,
initial_cache_value: float | str | None = None,
bind_to_instrument: bool = True,
snapshot_model: type[SnapshotType] = EmptySnapshotModel,
metadata_model: type[MetadataType] = EmptyMetadataModel,
**kwargs: Any,
) -> None:
def _get_manual_parameter(self: Parameter) -> ParamRawDataType:
Expand Down Expand Up @@ -240,6 +249,8 @@ def _set_manual_parameter(
vals=vals,
max_val_age=max_val_age,
bind_to_instrument=bind_to_instrument,
snapshot_model=snapshot_model,
metadata_model=metadata_model,
**kwargs,
)

Expand Down Expand Up @@ -441,6 +452,19 @@ def sweep(
return SweepFixedValues(self, start=start, stop=stop, step=step, num=num)


class ParameterSnapshot(EmptySnapshotModel):
# need to handle replacing __class__ with a different name that is compatible
value: Any # use paramdatatype
raw_value: Any # useparamdatatype
ts: str | None
inter_delay: float
name: str
post_delay: float
validators: list[str]
label: str
unit: str


class ManualParameter(Parameter):
def __init__(
self,
Expand Down
18 changes: 15 additions & 3 deletions src/qcodes/parameters/parameter_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@
from functools import cached_property, wraps
from typing import TYPE_CHECKING, Any, ClassVar, overload

from qcodes.metadatable import Metadatable, MetadatableWithName
from qcodes.metadatable import (
Metadatable,
MetadatableWithName,
)
from qcodes.metadatable.metadatable_base import (
EmptyMetadataModel,
EmptySnapshotModel,
MetadataType,
SnapshotType,
)
from qcodes.utils import DelegateAttributes, full_class, qcodes_abstractmethod
from qcodes.validators import Enum, Ints, Validator

Expand Down Expand Up @@ -85,7 +94,7 @@ def invert_val_mapping(val_mapping: Mapping[Any, Any]) -> dict[Any, Any]:
return {v: k for k, v in val_mapping.items()}


class ParameterBase(MetadatableWithName):
class ParameterBase(MetadatableWithName[SnapshotType, MetadataType]):
"""
Shared behavior for all parameters. Not intended to be used
directly, normally you should use ``Parameter``, ``ArrayParameter``,
Expand Down Expand Up @@ -201,8 +210,11 @@ def __init__(
abstract: bool | None = False,
bind_to_instrument: bool = True,
register_name: str | None = None,
snapshot_model: type[SnapshotType] = EmptySnapshotModel,
metadata_model: type[MetadataType] = EmptyMetadataModel,
**kwargs: Any,
) -> None:
super().__init__(metadata)
super().__init__(metadata, snapshot_model=snapshot_model, metadata_model=metadata_model, **kwargs)
if not str(name).isidentifier():
raise ValueError(
f"Parameter name must be a valid identifier "
Expand Down
43 changes: 43 additions & 0 deletions tests/parameter/test_parameter_typed_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from enum import StrEnum

Check failure on line 1 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.10, false)

"StrEnum" is unknown import symbol (reportAttributeAccessIssue)

Check failure on line 1 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (windows-latest, 3.9, false)

"StrEnum" is unknown import symbol (reportAttributeAccessIssue)

Check failure on line 1 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (windows-latest, 3.10, false)

"StrEnum" is unknown import symbol (reportAttributeAccessIssue)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need 3.10 support


from typing_extensions import assert_type

from qcodes.metadatable.metadatable_base import EmptyMetaDataModel

Check failure on line 5 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.10, false)

"EmptyMetaDataModel" is unknown import symbol (reportAttributeAccessIssue)

Check failure on line 5 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.11, false)

"EmptyMetaDataModel" is unknown import symbol (reportAttributeAccessIssue)

Check failure on line 5 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.12, false)

"EmptyMetaDataModel" is unknown import symbol (reportAttributeAccessIssue)

Check failure on line 5 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (windows-latest, 3.9, false)

"EmptyMetaDataModel" is unknown import symbol (reportAttributeAccessIssue)

Check failure on line 5 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (windows-latest, 3.10, false)

"EmptyMetaDataModel" is unknown import symbol (reportAttributeAccessIssue)
from qcodes.parameters import Parameter
from qcodes.parameters.parameter import ParameterSnapshot


def test_parameter_typed_metadata_basic():

class ParamType(StrEnum):

voltage = "voltage"
current = "current"

class MyParameterMetadata(EmptyMetaDataModel):
param_type: ParamType

a = Parameter(
name="myparam",
set_cmd=None,
get_cmd=None,
model=ParameterSnapshot,
metadata_model=MyParameterMetadata,
)
a.metadata["param_type"] = (
ParamType.voltage
) # TODO setting metadata should validate against the model
value = 123
a.set(value)

assert isinstance(a.typed_snapshot(), ParameterSnapshot)
assert isinstance(a.typed_metadata(), MyParameterMetadata)

assert a.typed_metadata().param_type == ParamType.voltage
assert a.typed_snapshot().value == value

Check failure on line 37 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.10, false)

Cannot access attribute "value" for class "EmptySnapshotModel"   Attribute "value" is unknown (reportAttributeAccessIssue)

Check failure on line 37 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.11, false)

Cannot access attribute "value" for class "EmptySnapshotModel"   Attribute "value" is unknown (reportAttributeAccessIssue)

Check failure on line 37 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.12, false)

Cannot access attribute "value" for class "EmptySnapshotModel"   Attribute "value" is unknown (reportAttributeAccessIssue)

Check failure on line 37 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (windows-latest, 3.9, false)

Cannot access attribute "value" for class "EmptySnapshotModel"   Attribute "value" is unknown (reportAttributeAccessIssue)

Check failure on line 37 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (windows-latest, 3.10, false)

Cannot access attribute "value" for class "EmptySnapshotModel"   Attribute "value" is unknown (reportAttributeAccessIssue)
assert a.typed_snapshot().name == "myparam"

Check failure on line 38 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.10, false)

Cannot access attribute "name" for class "EmptySnapshotModel"   Attribute "name" is unknown (reportAttributeAccessIssue)

Check failure on line 38 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.11, false)

Cannot access attribute "name" for class "EmptySnapshotModel"   Attribute "name" is unknown (reportAttributeAccessIssue)

Check failure on line 38 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.12, false)

Cannot access attribute "name" for class "EmptySnapshotModel"   Attribute "name" is unknown (reportAttributeAccessIssue)

Check failure on line 38 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (windows-latest, 3.9, false)

Cannot access attribute "name" for class "EmptySnapshotModel"   Attribute "name" is unknown (reportAttributeAccessIssue)

Check failure on line 38 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (windows-latest, 3.10, false)

Cannot access attribute "name" for class "EmptySnapshotModel"   Attribute "name" is unknown (reportAttributeAccessIssue)

assert_type(
a.typed_metadata(), MyParameterMetadata
) # TODO this is only checked if the type checker runs agains the test
assert_type(a.typed_snapshot(), ParameterSnapshot)

Check failure on line 43 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.10, false)

"assert_type" mismatch: expected "ParameterSnapshot" but received "EmptySnapshotModel" (reportAssertTypeFailure)

Check failure on line 43 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.11, false)

"assert_type" mismatch: expected "ParameterSnapshot" but received "EmptySnapshotModel" (reportAssertTypeFailure)

Check failure on line 43 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.12, false)

"assert_type" mismatch: expected "ParameterSnapshot" but received "EmptySnapshotModel" (reportAssertTypeFailure)

Check failure on line 43 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (windows-latest, 3.9, false)

"assert_type" mismatch: expected "ParameterSnapshot" but received "EmptySnapshotModel" (reportAssertTypeFailure)

Check failure on line 43 in tests/parameter/test_parameter_typed_metadata.py

View workflow job for this annotation

GitHub Actions / pytestmypy (windows-latest, 3.10, false)

"assert_type" mismatch: expected "ParameterSnapshot" but received "EmptySnapshotModel" (reportAssertTypeFailure)
Loading