♻️ Move the schema-wide `many` warning to `__setattr__` instead

This commit is contained in:
Daniil Fajnberg 2023-03-13 15:48:05 +01:00
parent 2a5e35b334
commit 4878d550fe
Signed by: daniil-berg
GPG Key ID: BE187C50903BEE97
2 changed files with 56 additions and 40 deletions

View File

@ -57,7 +57,7 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
many: bool = False, # usage discouraged
) -> None:
"""
Emits a warning, if the `many` argument is not `None`.
Emits a warning, if the `many` argument is not `False`.
Otherwise the same as in [`marshmallow.Schema`][marshmallow.Schema].
@ -72,19 +72,20 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
it is not used. Nested fields can be represented with dot
delimiters.
context:
Optional context passed to [`Method`]
[marshmallow.fields.Method] and [`Function`]
[marshmallow.fields.Function] fields.
Optional context passed to
[`Method`][marshmallow.fields.Method] and
[`Function`][marshmallow.fields.Function] fields.
load_only:
Fields to skip during serialization (write-only fields)
dump_only:
Fields to skip during deserialization (read-only fields)
partial:
Whether to ignore missing fields and not require any fields
declared. Propagates down to [`Nested`]
[marshmallow.fields.Nested] fields as well. If its value is an
iterable, only missing fields listed in that iterable will be
ignored. Use dot delimiters to specify nested fields.
declared. Propagates down to
[`Nested`][marshmallow.fields.Nested] fields as well. If its
value is an iterable, only missing fields listed in that
iterable will be ignored. Use dot delimiters to specify nested
fields.
unknown:
Whether to exclude, include, or raise an error for unknown
fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
@ -99,11 +100,6 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
[`load`][marshmallow_generic.GenericSchema.load]/
[`loads`][marshmallow_generic.GenericSchema.loads].
"""
if many:
warn(
"Setting `many` schema-wide breaks type safety. Use the the "
"`many` parameter of specific methods (like `load`) instead."
)
super().__init__(
only=only,
exclude=exclude,
@ -115,13 +111,28 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
unknown=unknown,
)
def __setattr__(self, name: str, value: Any) -> None:
"""
Warns, when trying to set `many` to anything other than `False`.
Otherwise the same the normal
[`object.__setattr__`](https://docs.python.org/3/reference/datamodel.html#object.__setattr__).
"""
if name == "many" and value is not False:
warn(
"Changing `many` schema-wide breaks type safety. Use the the "
"`many` parameter of specific methods (like `load`) instead."
)
super().__setattr__(name, value)
@post_load
def instantiate(self, data: dict[str, Any], **_kwargs: Any) -> Model:
"""
Unpacks `data` into the constructor of the specified **`Model`**.
Registered as a [`@post_load`]
[marshmallow_generic.decorators.post_load] hook for the schema.
Registered as a
[`@post_load`][marshmallow_generic.decorators.post_load]
hook for the schema.
!!! warning
You should probably not use this method directly. No parsing,
@ -167,8 +178,9 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
"""
Serializes **`Model`** objects to native Python data types.
Same as [`marshmallow.Schema.dump`]
[marshmallow.schema.Schema.dump] at runtime.
Same as
[`marshmallow.Schema.dump`][marshmallow.schema.Schema.dump]
at runtime.
Annotations ensure that type checkers will infer the return type
correctly based on the `many` argument, and also enforce the `obj`
@ -252,10 +264,10 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
"""
Deserializes data to objects of the specified **`Model`** class.
Same as [`marshmallow.Schema.load`]
[marshmallow.schema.Schema.load] at runtime, but data will always
pass through the [`instantiate`]
[marshmallow_generic.schema.GenericSchema.instantiate]
Same as
[`marshmallow.Schema.load`][marshmallow.schema.Schema.load] at
runtime, but data will always pass through the
[`instantiate`][marshmallow_generic.schema.GenericSchema.instantiate]
hook after deserialization.
Annotations ensure that type checkers will infer the return type
@ -269,11 +281,11 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
the value for `self.many` is used.
partial:
Whether to ignore missing fields and not require any
fields declared. Propagates down to [`Nested`]
[marshmallow.fields.Nested] fields as well. If its value
is an iterable, only missing fields listed in that
iterable will be ignored. Use dot delimiters to specify
nested fields.
fields declared. Propagates down to
[`Nested`][marshmallow.fields.Nested] fields as well. If
its value is an iterable, only missing fields listed in
that iterable will be ignored. Use dot delimiters to
specify nested fields.
unknown:
Whether to exclude, include, or raise an error for unknown
fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.
@ -321,10 +333,10 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
"""
Deserializes data to objects of the specified **`Model`** class.
Same as [`marshmallow.Schema.loads`]
[marshmallow.schema.Schema.loads] at runtime, but data will always
pass through the [`instantiate`]
[marshmallow_generic.schema.GenericSchema.instantiate]
Same as
[`marshmallow.Schema.loads`][marshmallow.schema.Schema.loads] at
runtime, but data will always pass through the
[`instantiate`][marshmallow_generic.schema.GenericSchema.instantiate]
hook after deserialization.
Annotations ensure that type checkers will infer the return type
@ -338,11 +350,11 @@ class GenericSchema(GenericInsightMixin1[Model], Schema):
the value for `self.many` is used.
partial:
Whether to ignore missing fields and not require any
fields declared. Propagates down to [`Nested`]
[marshmallow.fields.Nested] fields as well. If its value
is an iterable, only missing fields listed in that
iterable will be ignored. Use dot delimiters to specify
nested fields.
fields declared. Propagates down to
[`Nested`][marshmallow.fields.Nested] fields as well. If
its value is an iterable, only missing fields listed in
that iterable will be ignored. Use dot delimiters to
specify nested fields.
unknown:
Whether to exclude, include, or raise an error for unknown
fields in the data. Use `EXCLUDE`, `INCLUDE` or `RAISE`.

View File

@ -19,15 +19,19 @@ class GenericSchemaTestCase(TestCase):
"dump_only": object(),
"partial": object(),
"unknown": object(),
"many": False,
"many": object(),
}
schema.GenericSchema[Foo](**kwargs)
mock_super_init.assert_called_once_with(**kwargs)
mock_super_init.reset_mock()
kwargs["many"] = True
def test___setattr__(self) -> None:
class Foo:
pass
obj = schema.GenericSchema[Foo]()
with self.assertWarns(UserWarning):
schema.GenericSchema[Foo](**kwargs)
mock_super_init.assert_called_once_with(**kwargs)
obj.many = new = MagicMock()
self.assertIs(new, obj.many)
@patch.object(_util.GenericInsightMixin, "_get_type_arg")
def test_instantiate(self, mock__get_type_arg: MagicMock) -> None: