-
Notifications
You must be signed in to change notification settings - Fork 4
Add backwards-compatible marshmallow 4.x support #107
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
base: v1.x.x
Are you sure you want to change the base?
Conversation
bb481ba to
8245824
Compare
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.
Pull request overview
This PR adds backwards-compatible support for marshmallow 4.x by migrating from schema context-based serialization configuration to Python's ContextVar. The changes maintain full API compatibility while deprecating the old constructor-based approach.
Key changes:
- Introduces
serialize_as_string_defaultas aContextVarfor controlling serialization format globally - Deprecates the
serialize_as_string_defaultconstructor parameter with aDeprecationWarning - Adds backwards compatibility handling in
_QuantityField.__init__to check metadata for per-field configuration
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/frequenz/quantities/experimental/marshmallow.py | Adds QuantitySchema.__init__ with deprecation logic and backwards compatibility for metadata-based field configuration |
| tests/experimental/test_marshmallow.py | Adds two new tests to verify the deprecated constructor API works correctly with both True and False values |
| RELEASE_NOTES.md | Documents the API changes, migration path, and rationale for the marshmallow 4.x compatibility update |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| """Test that the deprecated constructor API still works but emits a warning.""" | ||
|
|
||
| @dataclass | ||
| class SimpleConfig: | ||
| pct: Percentage = field(default_factory=lambda: Percentage.from_percent(75.0)) | ||
|
|
||
| Schema = class_schema(SimpleConfig, base_schema=QuantitySchema) | ||
|
|
||
| with pytest.warns( | ||
| DeprecationWarning, | ||
| match="Passing 'serialize_as_string_default' to QuantitySchema constructor is deprecated", | ||
| ): | ||
| schema = Schema(serialize_as_string_default=True) # type: ignore[call-arg] | ||
|
|
||
| # Verify it still works | ||
| result = schema.dump(SimpleConfig()) | ||
| assert result["pct"] == "75 %" # type: ignore[index] | ||
|
|
Copilot
AI
Dec 23, 2025
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 test sets the global serialize_as_string_default ContextVar but doesn't reset it afterwards. This could affect subsequent tests if they rely on the default value. Consider adding cleanup to reset the ContextVar after the test, either explicitly with serialize_as_string_default.set(False) or using a pytest fixture/context manager to ensure proper cleanup.
| TYPE_MAPPING: dict[type, type[Field[Any]]] = QUANTITY_FIELD_CLASSES | ||
|
|
||
| def __init__( | ||
| self, *args: Any, serialize_as_string_default: bool | None = None, **kwargs: Any |
Copilot
AI
Dec 23, 2025
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 parameter name serialize_as_string_default shadows the module-level context variable of the same name (defined at line 35). This creates ambiguity and requires the awkward self-import pattern to access the module-level variable. Consider renaming the parameter to something like serialize_as_string_default_value or _serialize_as_string_default to avoid the name collision and eliminate the need for the self-import.
| # Import the module-level context variable | ||
| from . import marshmallow as _module | ||
|
|
||
| _module.serialize_as_string_default.set(serialize_as_string_default) |
Copilot
AI
Dec 23, 2025
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 module 'quantities.experimental.marshmallow' imports itself.
| # Import the module-level context variable | |
| from . import marshmallow as _module | |
| _module.serialize_as_string_default.set(serialize_as_string_default) | |
| # Set the module-level context variable directly | |
| serialize_as_string_default.set(serialize_as_string_default) | |
| # The above keeps backwards compatibility without importing this module. |
8245824 to
9c30b12
Compare
c81617b to
6b536ab
Compare
- Update marshmallow dependency to >= 4.0.0, < 5 - Use ContextVar for serialize_as_string_default instead of constructor param - Add backwards-compat __init__ that accepts old kwarg with DeprecationWarning - Add backwards-compat for serialize_as_string in field metadata Signed-off-by: Mathias L. Baumann <[email protected]>
6b536ab to
8cca58a
Compare
llucax
left a comment
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.
OK, this is a tricky one, and a reason why we might want to split marshmallow support into a separate package at some point.
For people using marshmallow, this is in fact a breaking change, as marshmallow 4.0 is required, not just optional, but for users not using marshmallow is not a breaking change.
Normally, if any part of the code is backwards incompatible, even if unused, should be a breaking release, I think this should be a breaking release too. The only reason we could try to sell this as a non-breaking release would be because it is an experimental package. But still, we are supposed to be committed to not break even those.
What we could do to keep this backwards compatible, is getting the installed marshmallow version and import a different module that is compatible with one or the other version conditionally based on that.
But I think in this case it makes sense to just bite the bullet and release it as a breaking change. For users not using marshmallow, it will be just changing the dependency and changing no code. For users using marshmallow, they are going through a marshmallow upgrade anyway, so fixing one more piece of code should not be too bad.
I'm not sure if keeping the deprecated ctor makes sense if we ship this in a breaking release anyway. It might ease the upgrading a bit for users if they don't need to make any other marshmallow-related updates, so we could keep it if we want to be extra nice, and only remove the deprecated ctor in v3, but I'm not sure it is worth the trouble.
|
I think I would be in favor of just breaking it then without a compatible c'tor :) |
Adds support for marshmallow 4.x, dropping support for marshmallow 3.x. The old API is maintained with deprecation warnings for backwards compatibility.
Changes:
QuantitySchema(serialize_as_string_default=...)constructor parameter now emits aDeprecationWarning- users should migrate to using theserialize_as_string_defaultcontext variable insteadserialize_as_string_defaultas aContextVarfor controlling serialization formatserialize_as_stringmetadata continues to work unchangedThis change was necessary because marshmallow 4.0.0 made
Fieldgeneric (Field[T]) and changed how schema context is accessed from fields, breaking the previous implementation that relied onself.parent.context.The old API continues to work with deprecation warnings, so this can be released as v1.1.0.