Skip to content

Conversation

@Marenz
Copy link
Contributor

@Marenz Marenz commented Dec 23, 2025

Adds support for marshmallow 4.x, dropping support for marshmallow 3.x. The old API is maintained with deprecation warnings for backwards compatibility.

Changes:

  • Requires marshmallow >= 4.0.0 (marshmallow 3.x no longer supported)
  • The QuantitySchema(serialize_as_string_default=...) constructor parameter now emits a DeprecationWarning - users should migrate to using the serialize_as_string_default context variable instead
  • Added serialize_as_string_default as a ContextVar for controlling serialization format
  • Existing per-field serialize_as_string metadata continues to work unchanged

This change was necessary because marshmallow 4.0.0 made Field generic (Field[T]) and changed how schema context is accessed from fields, breaking the previous implementation that relied on self.parent.context.

The old API continues to work with deprecation warnings, so this can be released as v1.1.0.

@Marenz Marenz requested a review from a team as a code owner December 23, 2025 09:16
@github-actions github-actions bot added part:docs Affects the documentation part:tests Affects the unit, integration and performance (benchmarks) tests part:code Affects the code in general labels Dec 23, 2025
@Marenz Marenz force-pushed the marshmallow-4-compat branch from bb481ba to 8245824 Compare December 23, 2025 09:20
Copy link

Copilot AI left a 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_default as a ContextVar for controlling serialization format globally
  • Deprecates the serialize_as_string_default constructor parameter with a DeprecationWarning
  • 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.

Comment on lines 252 to 275
"""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]

Copy link

Copilot AI Dec 23, 2025

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.

Copilot uses AI. Check for mistakes.
TYPE_MAPPING: dict[type, type[Field[Any]]] = QUANTITY_FIELD_CLASSES

def __init__(
self, *args: Any, serialize_as_string_default: bool | None = None, **kwargs: Any
Copy link

Copilot AI Dec 23, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines 324 to 327
# Import the module-level context variable
from . import marshmallow as _module

_module.serialize_as_string_default.set(serialize_as_string_default)
Copy link

Copilot AI Dec 23, 2025

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.

Suggested change
# 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.

Copilot uses AI. Check for mistakes.
@Marenz Marenz force-pushed the marshmallow-4-compat branch from 8245824 to 9c30b12 Compare December 23, 2025 09:21
@github-actions github-actions bot added the part:tooling Affects the development tooling (CI, deployment, dependency management, etc.) label Dec 23, 2025
@Marenz Marenz force-pushed the marshmallow-4-compat branch 4 times, most recently from c81617b to 6b536ab Compare December 23, 2025 09:53
- 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]>
@Marenz Marenz force-pushed the marshmallow-4-compat branch from 6b536ab to 8cca58a Compare December 23, 2025 10:32
@llucax llucax added the scope:breaking-change Breaking change, users will need to update their code label Jan 5, 2026
@llucax llucax added this to the v2.0.0 milestone Jan 5, 2026
Copy link
Contributor

@llucax llucax left a 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.

@Marenz
Copy link
Contributor Author

Marenz commented Jan 12, 2026

I think I would be in favor of just breaking it then without a compatible c'tor :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

part:code Affects the code in general part:docs Affects the documentation part:tests Affects the unit, integration and performance (benchmarks) tests part:tooling Affects the development tooling (CI, deployment, dependency management, etc.) scope:breaking-change Breaking change, users will need to update their code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants