From 2a548865106b1cfc98f5dea8bf00084f62e6334e Mon Sep 17 00:00:00 2001 From: Ross MacArthur Date: Sat, 15 Feb 2020 10:56:06 +0200 Subject: [PATCH] Remove `fields.create()` method --- README.rst | 17 ----- src/serde/fields.py | 160 ++++--------------------------------------- tests/test_fields.py | 86 ++--------------------- 3 files changed, 22 insertions(+), 241 deletions(-) diff --git a/README.rst b/README.rst index c381800..6cfa6b4 100644 --- a/README.rst +++ b/README.rst @@ -442,23 +442,6 @@ to only accept dates after 15th April 1912. Note: the ``rename`` argument only applies to the serializing and deserializing of the data, the ``Album`` class would still be instantiated using ``Album(released=...)``. -The ``create()`` method can be used to generate a new ``Field`` class from -arbitrary functions without having to manually subclass a ``Field``. For example -if we wanted a ``Percent`` field we would do the following. - -.. code-block:: python - - >>> from serde import fields, validators - >>> - >>> Percent = fields.create( - ... 'Percent', - ... fields.Float, - ... validators=[validators.Between(0.0, 100.0)] - ... ) - >>> - >>> issubclass(Percent, fields.Float) - True - If these methods of creating custom ``Field`` classes are not satisfactory, you can always subclass a ``Field`` and override the relevant methods. diff --git a/src/serde/fields.py b/src/serde/fields.py index b7a19e7..236e9da 100644 --- a/src/serde/fields.py +++ b/src/serde/fields.py @@ -6,7 +6,6 @@ import datetime import re import uuid -from functools import wraps import isodate from six import binary_type, integer_types, text_type @@ -333,145 +332,6 @@ def validate(self, value): pass -def _create_serialize(cls, serializers): - """ - Create a new serialize method with extra serializer functions. - """ - - @wraps(serializers[0]) - def serialize(self, value): - for serializer in serializers: - value = serializer(value) - value = super(cls, self).serialize(value) - return value - - return serialize - - -def _create_deserialize(cls, deserializers): - """ - Create a new deserialize method with extra deserializer functions. - """ - - @wraps(deserializers[0]) - def deserialize(self, value): - value = super(cls, self).deserialize(value) - for deserializer in deserializers: - value = deserializer(value) - return value - - return deserialize - - -def _create_normalize(cls, normalizers): - """ - Create a new normalize method with extra normalizer functions. - """ - - @wraps(normalizers[0]) - def normalize(self, value): - value = super(cls, self).normalize(value) - for normalizer in normalizers: - value = normalizer(value) - return value - - return normalize - - -def _create_validate(cls, validators): - """ - Create a new validate method with extra validator functions. - """ - - @wraps(validators[0]) - def validate(self, value): - super(cls, self).validate(value) - for validator in validators: - validator(value) - - return validate - - -def create( - name, - base=None, - args=None, - description=None, - serializers=None, - deserializers=None, - normalizers=None, - validators=None, -): - """ - Create a new `Field` class. - - This is a convenience method for creating new Field classes from arbitrary - serializer, deserializer, normalizer, and/or validator functions. - - Args: - name (str): the name of the class. - base (Field): the base field class to subclass. - args (tuple): positional arguments for the base class's ``__init__`` - method. - serializers (list): a list of serializer functions taking the value to - serialize as an argument. The functions need to raise an `Exception` - if they fail. These serializer functions will be applied before the - primary serializer on this field. - deserializers (list): a list of deserializer functions taking the value - to deserialize as an argument. The functions need to raise an - `Exception` if they fail. These deserializer functions will be - applied after the primary deserializer on this field. - normalizers (list): a list of normalizer functions taking the value to - normalize as an argument. The functions need to raise an `Exception` - if they fail. These normalizer functions will be applied after the - primary normalizer on this field. - validators (list): a list of validator functions taking the value to - validate as an argument. The functions need to raise an `Exception` - if they fail. - - Returns: - class: a new `Field` class. - """ - if not base: - base = Field - - if not description: - description = name.lower() - - doc = """\ -{description} - -Args: - **kwargs: keyword arguments for the `Field` constructor. -""".format( - description=description - ) - - field_cls = type(name, (base,), {'__doc__': doc}) - - if args: - - def __init__(self, **kwargs): # noqa: N807 - super(field_cls, self).__init__(*args, **kwargs) - - __init__.__doc__ = 'Create a new `{name}`.'.format(name=name) - field_cls.__init__ = __init__ - - if serializers: - field_cls.serialize = _create_serialize(field_cls, serializers) - - if deserializers: - field_cls.deserialize = _create_deserialize(field_cls, deserializers) - - if normalizers: - field_cls.normalize = _create_normalize(field_cls, normalizers) - - if validators: - field_cls.validate = _create_validate(field_cls, validators) - - return field_cls - - class Optional(Field): """ An optional field. @@ -925,14 +785,25 @@ def _apply(self, stage, element): return getattr(f, stage)(v) -def create_primitive(name, type): +def create_primitive(name, type_): """ Create a primitive `Field` class. """ - description = 'This field represents the built-in `{type}` type.'.format( - type=type.__name__ + doc = """\ +This field represents the built-in `{type}` type. + +Args: + **kwargs: keyword arguments for the `Field` constructor. +""".format( + type=type_ ) - return create(name, base=Instance, args=(type,), description=description) + + def __init__(self, **kwargs): # noqa: N807 + Instance.__init__(self, type_, **kwargs) + + __init__.__doc__ = 'Create a new `{name}`.'.format(name=name) + + return type(name, (Instance,), {'__doc__': doc, '__init__': __init__}) Bool = create_primitive('Bool', bool) @@ -1343,4 +1214,3 @@ def validate(self, value): del create_from __all__ = [name for name, obj in globals().items() if is_subclass(obj, Field)] -__all__.append('create') diff --git a/tests/test_fields.py b/tests/test_fields.py index 52f22d3..af7ab38 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -40,16 +40,16 @@ _Mapping, _resolve_to_field_instance, _Sequence, - create, ) -Reversed = create( # noqa: N806 - 'Reversed', - base=Str, - serializers=[lambda x: x[::-1]], - deserializers=[lambda x: x[::-1]], -) +class Reversed(Str): + def __init__(self, **kwargs): + serializers = kwargs.setdefault('serializers', []) + deserializers = kwargs.setdefault('deserializers', []) + serializers.append(lambda x: x[::-1]) + deserializers.append(lambda x: x[::-1]) + super(Reversed, self).__init__(**kwargs) def test_overridden_methods(): @@ -359,78 +359,6 @@ def test_validate(self): field.validate(value) -def test_create_base(): - # By default the created Field should subclass Field. - Example = create('Example') # noqa: N806 - assert issubclass(Example, Field) - assert Example.__mro__[1] == Field - - -def test_create_str(): - # You should be able to specify a different base Field. - Example = create('Example', base=Str) # noqa: N806 - assert issubclass(Example, Str) - assert Example.__mro__[1] == Str - - -def test_create_args(): - # You should be able to create a new Field, subclassing a Field that - # requires positional arguments. - Example = create('Example', base=Instance, args=(str,)) # noqa: N806 - Example() - - # This is what should happen if you don't give it the arguments! - Example = create('Example', base=Instance) # noqa: N806 - with raises(TypeError): - Example() - - -def test_create_serializer_and_normalizer_and_deserializer(): - # You should be able to create a new Field with extra serializers, - # normalizers, and deserializers. - - def reverser(value): - return value[::-1] - - Reversed = create( # noqa: N806 - 'Reversed', - base=Str, - serializers=[reverser], - deserializers=[reverser], - normalizers=[reverser], - ) - - class Example(Model): - a = Reversed() - - field = Example(a='test') - assert field.a == 'tset' - - field = Example.from_dict({'a': 'test'}) - assert field.a == 'test' - assert field.to_dict() == {'a': 'tset'} - - -def test_create_validator(): - # You should be able to create a Field with an arbitrary validate method. - - def assert_is_not_derp(value): - if value == 'derp': - raise ValidationError("value is 'derp'") - assert value != 'derp' - - NotDerp = create('NotDerp', Str, validators=[assert_is_not_derp]) # noqa: N806 - - class Example(Model): - a = NotDerp() - - assert Example('notderp').a == 'notderp' - - with raises(ValidationError) as e: - Example(a='derp') - assert e.value.messages() == {'a': "value is 'derp'"} - - class TestOptional: def test___init___basic(self): # Construct a basic Optional and check values are set correctly.