-
Notifications
You must be signed in to change notification settings - Fork 4
Build(deps): Update marshmallow requirement from <4,>=3.0.0 to >=3.0.0,<5 #55
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,9 +14,11 @@ | |||||||||||
| even in minor or patch releases. | ||||||||||||
| """ | ||||||||||||
|
|
||||||||||||
| from contextvars import ContextVar | ||||||||||||
| from typing import Any, Type | ||||||||||||
|
|
||||||||||||
| from marshmallow import Schema, ValidationError, fields | ||||||||||||
| from marshmallow import Schema, ValidationError | ||||||||||||
| from marshmallow.fields import Field | ||||||||||||
|
|
||||||||||||
| from .._apparent_power import ApparentPower | ||||||||||||
| from .._current import Current | ||||||||||||
|
|
@@ -29,8 +31,20 @@ | |||||||||||
| from .._temperature import Temperature | ||||||||||||
| from .._voltage import Voltage | ||||||||||||
|
|
||||||||||||
| serialize_as_string_default: ContextVar[bool] = ContextVar( | ||||||||||||
| "serialize_as_string_default", default=False | ||||||||||||
| ) | ||||||||||||
| """Context variable to control the default serialization format for quantities. | ||||||||||||
|
|
||||||||||||
| class _QuantityField(fields.Field): | ||||||||||||
| If True, quantities are serialized as strings with units. | ||||||||||||
| If False, quantities are serialized as floats. | ||||||||||||
|
|
||||||||||||
| This can be overridden on a per-field basis using the `serialize_as_string` | ||||||||||||
| metadata attribute. | ||||||||||||
| """ | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| class _QuantityField(Field[Quantity]): | ||||||||||||
| """Custom field for Quantity objects supporting per-field serialization configuration. | ||||||||||||
|
|
||||||||||||
| This class handles serialization and deserialization of ALL Quantity | ||||||||||||
|
|
@@ -57,24 +71,34 @@ class _QuantityField(fields.Field): | |||||||||||
| field_type: Type[Quantity] | None = None | ||||||||||||
| """The specific Quantity subclass.""" | ||||||||||||
|
|
||||||||||||
| def __init__(self, *args: Any, **kwargs: Any) -> None: | ||||||||||||
| """Initialize the field.""" | ||||||||||||
| self.serialize_as_string_override = kwargs.pop("serialize_as_string", None) | ||||||||||||
| super().__init__(*args, **kwargs) | ||||||||||||
|
|
||||||||||||
| def _serialize( | ||||||||||||
| self, value: Quantity, attr: str | None, obj: Any, **kwargs: Any | ||||||||||||
| self, value: Quantity | None, attr: str | None, obj: Any, **kwargs: Any | ||||||||||||
| ) -> Any: | ||||||||||||
| """Serialize the Quantity object based on per-field configuration.""" | ||||||||||||
| if self.field_type is None or not issubclass(self.field_type, Quantity): | ||||||||||||
| raise TypeError( | ||||||||||||
| "field_type must be set to a Quantity subclass in the subclass." | ||||||||||||
| ) | ||||||||||||
| if value is None: | ||||||||||||
| return None | ||||||||||||
|
|
||||||||||||
| assert self.parent is not None | ||||||||||||
| if not isinstance(value, Quantity): | ||||||||||||
| raise TypeError( | ||||||||||||
| f"Expected a Quantity object, but got {type(value).__name__}." | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| # Determine the serialization format | ||||||||||||
| default = ( | ||||||||||||
| False | ||||||||||||
| if self.parent.context is None | ||||||||||||
| else self.parent.context.get("serialize_as_string_default", False) | ||||||||||||
| default = serialize_as_string_default.get() | ||||||||||||
| serialize_as_string = ( | ||||||||||||
| self.serialize_as_string_override | ||||||||||||
| if self.serialize_as_string_override is not None | ||||||||||||
| else default | ||||||||||||
| ) | ||||||||||||
| serialize_as_string = self.metadata.get("serialize_as_string", default) | ||||||||||||
|
|
||||||||||||
| if serialize_as_string: | ||||||||||||
| # Use the Quantity's native string representation (includes unit) | ||||||||||||
|
|
@@ -177,7 +201,7 @@ class VoltageField(_QuantityField): | |||||||||||
| field_type = Voltage | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| QUANTITY_FIELD_CLASSES: dict[type[Quantity], type[fields.Field]] = { | ||||||||||||
| QUANTITY_FIELD_CLASSES: dict[type[Quantity], type[Field[Any]]] = { | ||||||||||||
| ApparentPower: ApparentPowerField, | ||||||||||||
| Current: CurrentField, | ||||||||||||
| Energy: EnergyField, | ||||||||||||
|
|
@@ -208,8 +232,10 @@ class QuantitySchema(Schema): | |||||||||||
| from marshmallow_dataclass import class_schema | ||||||||||||
| from marshmallow.validate import Range | ||||||||||||
| from frequenz.quantities import Percentage | ||||||||||||
| from frequenz.quantities.experimental.marshmallow import QuantitySchema | ||||||||||||
| from typing import cast | ||||||||||||
| from frequenz.quantities.experimental.marshmallow import ( | ||||||||||||
| QuantitySchema, | ||||||||||||
| serialize_as_string_default, | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| @dataclass | ||||||||||||
| class Config: | ||||||||||||
|
|
@@ -245,29 +271,24 @@ class Config: | |||||||||||
| }, | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| @classmethod | ||||||||||||
| def load(cls, config: dict[str, Any]) -> "Config": | ||||||||||||
| schema = class_schema(cls, base_schema=QuantitySchema)( | ||||||||||||
| serialize_as_string_default=True # type: ignore[call-arg] | ||||||||||||
| ) | ||||||||||||
| return cast(Config, schema.load(config)) | ||||||||||||
| config_obj = Config() | ||||||||||||
| Schema = class_schema(Config, base_schema=QuantitySchema) | ||||||||||||
| schema = Schema() | ||||||||||||
|
|
||||||||||||
| # Default serialization (as float) | ||||||||||||
| result = schema.dump(config_obj) | ||||||||||||
| assert result["percentage_serialized_as_schema_default"] == 25.0 | ||||||||||||
|
|
||||||||||||
| # Override default serialization to string | ||||||||||||
| serialize_as_string_default.set(True) | ||||||||||||
| result = schema.dump(config_obj) | ||||||||||||
| assert result["percentage_serialized_as_schema_default"] == "25.0 %" | ||||||||||||
| serialize_as_string_default.set(False) # Reset context | ||||||||||||
|
||||||||||||
| serialize_as_string_default.set(False) # Reset context | |
| token = serialize_as_string_default.set(True) | |
| result = schema.dump(config_obj) | |
| assert result["percentage_serialized_as_schema_default"] == "25.0 %" | |
| serialize_as_string_default.reset(token) # Reset context |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,7 +18,10 @@ | |||||||||
| Temperature, | ||||||||||
| Voltage, | ||||||||||
| ) | ||||||||||
| from frequenz.quantities.experimental.marshmallow import QuantitySchema | ||||||||||
| from frequenz.quantities.experimental.marshmallow import ( | ||||||||||
| QuantitySchema, | ||||||||||
| serialize_as_string_default, | ||||||||||
| ) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| @dataclass | ||||||||||
|
|
@@ -74,19 +77,19 @@ class Config: | |||||||||
| default_factory=lambda: Voltage.from_kilovolts(200.0), | ||||||||||
| metadata={ | ||||||||||
| "metadata": { | ||||||||||
| "description": "A voltage field that is always serialized as a string", | ||||||||||
| "serialize_as_string": True, | ||||||||||
| "description": "A voltage field that is always serialized as a string" | ||||||||||
| }, | ||||||||||
| "serialize_as_string": True, | ||||||||||
|
||||||||||
| "serialize_as_string": True, | |
| "description": "A voltage field that is always serialized as a string", | |
| "serialize_as_string": True, | |
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
serialize_as_stringparameter is being popped from kwargs but this field doesn't appear to be documented or validated. Consider adding proper validation or documentation for this parameter to clarify its expected usage.