Skip to content

Commit

Permalink
More complete examples
Browse files Browse the repository at this point in the history
  • Loading branch information
sloria authored and lafrech committed Jan 5, 2025
1 parent c89c15a commit f1cbe27
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 64 deletions.
24 changes: 14 additions & 10 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Other changes:
As a consequence of this change:
- Time with time offsets are now supported.
- YYYY-MM-DD is now accepted as a datetime and deserialized as naive 00:00 AM.
- `from_iso_date`, `from_iso_time` and `from_iso_datetime` are removed from `marshmallow.utils`
- `from_iso_date`, `from_iso_time` and `from_iso_datetime` are removed from `marshmallow.utils`.

- *Backwards-incompatible*: Custom validators must raise a `ValidationError <marshmallow.exceptions.ValidationError>` for invalid values.
Returning `False` is no longer supported (:issue:`1775`).
Expand Down Expand Up @@ -48,28 +48,32 @@ As a consequence of this change:

Thanks :user:`ddelange` for the PR.

- *Backwards-incompatible*: Remove schema ``context`` property. Passing a context
should be done using a context variable. (issue:`1826`)
marshmallow 4.0 provides an experimental `Context <marshmallow.experimental.context.Context>`
manager class that can be used both to set and retrieve the context.
- *Backwards-incompatible*: Remove `Schema <marshmallow.schema.Schema>`'s ``context`` attribute. Passing a context
should be done using `contextvars.ContextVar` (:issue:`1826`).
marshmallow 4 provides an experimental `Context <marshmallow.experimental.context.Context>`
manager class that can be used to both set and retrieve context.

.. code-block:: python
import typing
from marshmallow import Schema, fields
from marshmallow.experimental.context import Context
def transform_name(obj):
return obj["name"].upper() + Context.get()["suffix"]
class UserContext(typing.TypedDict):
suffix: str
class UserSchema(Schema):
name = fields.Function(serialize=transform_name)
name_suffixed = fields.Function(
lambda obj: obj["name"] + Context[UserContext].get()["suffix"]
)
with Context({"suffix": "BAR"}):
with Context[UserContext]({"suffix": "bar"}):
UserSchema().dump({"name": "foo"})
# {'name': 'FOOBAR'}
# {'name_suffixed': 'foobar'}
Deprecations/Removals:

Expand Down
48 changes: 38 additions & 10 deletions docs/custom_fields.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ Both :class:`Function <marshmallow.fields.Function>` and :class:`Method <marshma
result = schema.load({"balance": "100.00"})
result["balance"] # => 100.0
.. _using_context:

Using context
-------------

Expand All @@ -102,37 +104,63 @@ A field may need information about its environment to know how to (de)serialize
You can use the experimental `Context <marshmallow.experimental.context.Context>` class
to set and retrieve context.

As an example, you might want your ``UserSchema`` to output whether or not a ``User`` is the author of a ``Blog`` or whether a certain word appears in a ``Blog's`` title.
Let's say your ``UserSchema`` needs to output
whether or not a ``User`` is the author of a ``Blog`` or
whether a certain word appears in a ``Blog's`` title.

.. code-block:: python
import typing
from dataclasses import dataclass
from marshmallow import Schema, fields
from marshmallow.experimental.context import Context
@dataclass
class User:
name: str
@dataclass
class Blog:
title: str
author: User
class ContextDict(typing.TypedDict):
blog: Blog
class UserSchema(Schema):
name = fields.String()
is_author = fields.Function(lambda user: user == Context.get()["blog"].author)
is_author = fields.Function(
lambda user: user == Context[ContextDict].get()["blog"].author
)
likes_bikes = fields.Method("writes_about_bikes")
def writes_about_bikes(self, user):
return "bicycle" in Context.get()["blog"].title.lower()
def writes_about_bikes(self, user: User) -> bool:
return "bicycle" in Context[ContextDict].get()["blog"].title.lower()
.. note::
You can use `Context.get <marshmallow.experimental.context.Context.get>`
within custom fields, pre-/post-processing methods, and validators.

When (de)serializing, set the context by using `Context <marshmallow.experimental.context.Context>` as a context manager.

.. code-block:: python
schema = UserSchema()
user = User("Freddie Mercury", "fred@queen.com")
blog = Blog("Bicycle Blog", author=user)
schema = UserSchema()
with Context({"blog": blog}):
result = schema.dump(user)
result["is_author"] # => True
result["likes_bikes"] # => True
print(result["is_author"]) # => True
print(result["likes_bikes"]) # => True
.. note::
You can use `Context.get <marshmallow.experimental.context.Context.get>`
within custom fields, pre-/post-processing methods, and validators.
Customizing error messages
--------------------------
Expand Down
91 changes: 49 additions & 42 deletions docs/upgrading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,55 @@ If you want to use anonymous functions, you can use this helper function.
class UserSchema(Schema):
password = fields.String(validate=predicate(lambda x: x == "password"))
New context API
***************

Passing context to `Schema <marshmallow.schema.Schema>` classes is no longer supported. Use `contextvars.ContextVar` for passing context to
fields, pre-/post-processing methods, and validators instead.

marshmallow 4 provides an experimental `Context <marshmallow.experimental.context.Context>`
manager class that can be used to both set and retrieve context.

.. code-block:: python
# 3.x
from marshmallow import Schema, fields
class UserSchema(Schema):
name_suffixed = fields.Function(
lambda obj, context: obj["name"] + context["suffix"]
)
user_schema = UserSchema()
user_schema.context = {"suffix": "bar"}
user_schema.dump({"name": "foo"})
# {'name_suffixed': 'foobar'}
# 4.x
import typing
from marshmallow import Schema, fields
from marshmallow.experimental.context import Context
class UserContext(typing.TypedDict):
suffix: str
class UserSchema(Schema):
name_suffixed = fields.Function(
lambda obj: obj["name"] + Context[UserContext].get()["suffix"]
)
with Context[UserContext]({"suffix": "bar"}):
UserSchema().dump({"name": "foo"})
# {'name_suffixed': 'foobar'}
See :ref:`using_context` for more information.

Implicit field creation is removed
**********************************

Expand Down Expand Up @@ -263,48 +312,6 @@ The ``missing`` and ``default`` parameters of fields are renamed to
``load_default`` and ``dump_default`` are passed to the field constructor as keyword arguments.

Schema context is removed
*************************

Passing context to the schema is no longer supported. Use `contextvars` for passing context to
fields and pre-/post-processing methods instead.

marshmallow 4.0 provides an experimental `Context <marshmallow.experimental.context.Context>`
manager class that can be used both to set and retrieve the context.

.. code-block:: python
# 3.x
from marshmallow import Schema, fields
class UserSchema(Schema):
name = fields.Function(
serialize=lambda obj, context: obj["name"].upper() + context["suffix"]
)
user_schema = UserSchema()
user_schema.context = {"suffix": "BAR"}
user_schema.dump({"name": "foo"})
# {'name': 'FOOBAR'}
# 4.x
from marshmallow import Schema, fields
from marshmallow.experimental.context import Context
def transform_name(obj):
return obj["name"].upper() + Context.get()["suffix"]
class UserSchema(Schema):
name = fields.Function(serialize=transform_name)
with Context({"suffix": "BAR"}):
UserSchema().dump({"name": "foo"})
# {'name': 'FOOBAR'}

Upgrading to 3.3
++++++++++++++++
Expand Down
4 changes: 2 additions & 2 deletions src/marshmallow/experimental/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ class UserSchema(Schema):
)
with Context({"suffix": "bar"}):
with Context[UserContext]({"suffix": "bar"}):
print(UserSchema().dump({"name": "foo"}))
# {'name': 'foobar'}
# {'name_suffixed': 'foobar'}
"""

import contextlib
Expand Down

0 comments on commit f1cbe27

Please sign in to comment.