Skip to content
This repository has been archived by the owner on Apr 10, 2023. It is now read-only.

Rework validators #102

Merged
merged 1 commit into from
Sep 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
314 changes: 158 additions & 156 deletions README.rst

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Tags
Validators
----------

.. automodule:: serde.validate
.. automodule:: serde.validators
:members:

Exceptions
Expand Down
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ max-line-length = 100
per-file-ignores =
docs/*: D
src/serde/__init__.py: D205
src/serde/validators.py: D102,D107
tests/*: D
tests/test_exceptions.py: B011,D

[isort]
balanced_wrapping = true
line_length = 100
line_length = 80
lines_after_imports = 2
multi_line_output = 3
not_skip=__init__.py
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def get_metadata():
about_text = f.read()

metadata = {
key: re.search(r'__' + key + r'__ = ["\'](.*?)["\']', about_text).group(1)
key: re.search(r'__' + key + r"__ = '(.*?)'", about_text).group(1)
for key in ('title', 'version', 'url', 'author', 'author_email', 'license', 'description')
}
metadata['name'] = metadata.pop('title')
Expand Down
6 changes: 2 additions & 4 deletions src/serde/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@
from serde.model import Model


__all__ = ['Model', 'exceptions', 'fields', 'tags', 'validate']
__all__ = ['Model', 'exceptions', 'fields', 'tags', 'validators']
__title__ = 'serde'
__version__ = '0.6.2'
__url__ = 'https://github.com/rossmacarthur/serde'
__author__ = 'Ross MacArthur'
__author_email__ = 'ross@macarthur.io'
__license__ = 'MIT'
__description__ = (
'Define, serialize, deserialize, and validate Python data structures.'
)
__description__ = 'Define, serialize, deserialize, and validate Python data structures.'
98 changes: 48 additions & 50 deletions src/serde/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import isodate
from six import integer_types

from serde import validate
from serde import validators
from serde.exceptions import (
ContextError,
DeserializationError,
Expand All @@ -19,7 +19,7 @@
ValidationError,
map_errors
)
from serde.utils import applied, chained, try_import_all, zip_equal
from serde.utils import applied, chained, is_subclass, try_import_all, zip_equal


def _resolve_to_field_instance(thing, none_allowed=True):
Expand Down Expand Up @@ -122,8 +122,7 @@ def __model__(self):

def _attrs(self):
"""
Return all attributes of base field except `id` and some private
attributes.
Returns a dictionary of all public attributes on this base field.
"""
return {
name: value for name, value in vars(self).items()
Expand Down Expand Up @@ -242,8 +241,7 @@ def __init__(

def _attrs(self):
"""
Return all attributes of the field except `id` and some private
attributes.
Returns a dictionary of all public attributes on this field.
"""
return {
name: value for name, value in vars(self).items()
Expand Down Expand Up @@ -650,7 +648,7 @@ def __init__(self, type, **kwargs):
"""
super(Instance, self).__init__(**kwargs)
self.type = type
self._validate_instance = validate.instance(type)
self._validate_instance = validators.Instance(type)

def validate(self, value):
"""
Expand Down Expand Up @@ -683,32 +681,6 @@ def deserialize(self, d):
return self.type.from_dict(d)


class Constant(Field):
"""
A constant field.

A `Constant` is a field that always has to be the specified value.

Args:
value: the constant value that this `Constant` wraps.
**kwargs: keyword arguments for the `Field` constructor.
"""

def __init__(self, value, **kwargs):
"""
Create a new `Constant`.
"""
super(Constant, self).__init__(**kwargs)
self.value = value
self._validate_equal = validate.equal(value)

def validate(self, value):
"""
Validate that the given value is equal to the constant value.
"""
self._validate_equal(value)


class Dict(Instance):
"""
This field represents the built-in `dict` type.
Expand Down Expand Up @@ -959,34 +931,30 @@ def validate(self, value):
pass


class Regex(Str):
class Constant(Field):
"""
A regex field.
A constant field.

A `Regex` is a string field that validates that data matches a specified
regex expression.
A `Constant` is a field that always has to be the specified value.

Args:
pattern (str): the regex pattern that the value must match.
flags (int): the regex flags passed directly to `re.compile`.
value: the constant value that this `Constant` wraps.
**kwargs: keyword arguments for the `Field` constructor.
"""

def __init__(self, pattern, flags=0, **kwargs):
def __init__(self, value, **kwargs):
"""
Create a new `Regex`.
Create a new `Constant`.
"""
super(Regex, self).__init__(**kwargs)
self.pattern = pattern
self.flags = flags
self._validate_regex = validate.regex(self.pattern, flags=self.flags)
super(Constant, self).__init__(**kwargs)
self.value = value
self._validate_equal = validators.Equal(value)

def validate(self, value):
"""
Validate the given string matches the specified regex.
Validate that the given value is equal to the constant value.
"""
super(Regex, self).validate(value)
self._validate_regex(value)
self._validate_equal(value)


class Choice(Field):
Expand All @@ -1007,7 +975,7 @@ def __init__(self, choices, **kwargs):
"""
super(Choice, self).__init__(**kwargs)
self.choices = choices
self._validate_contains = validate.contains(choices)
self._validate_contains = validators.Contains(choices)

def validate(self, value):
"""
Expand Down Expand Up @@ -1099,6 +1067,36 @@ def deserialize(self, value):
return datetime.datetime.strptime(value, self.format).time()


class Regex(Str):
"""
A regex field.

A `Regex` is a string field that validates that data matches a specified
regex expression.

Args:
pattern (str): the regex pattern that the value must match.
flags (int): the regex flags passed directly to `re.compile`.
**kwargs: keyword arguments for the `Field` constructor.
"""

def __init__(self, pattern, flags=0, **kwargs):
"""
Create a new `Regex`.
"""
super(Regex, self).__init__(**kwargs)
self.pattern = pattern
self.flags = flags
self._validate_regex = validators.Regex(self.pattern, flags=self.flags)

def validate(self, value):
"""
Validate the given string matches the specified regex.
"""
super(Regex, self).validate(value)
self._validate_regex(value)


class Uuid(Instance):
"""
A `~uuid.UUID` field.
Expand Down Expand Up @@ -1170,5 +1168,5 @@ def normalize(self, value):

try_import_all('serde_ext.fields', globals())

__all__ = [name for name, obj in globals().items() if isinstance(obj, Field)]
__all__ = [name for name, obj in globals().items() if is_subclass(obj, Field)]
__all__.append('create')
7 changes: 6 additions & 1 deletion src/serde/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ class Internal(Tag):
"""
A tag to internally tag `~serde.Model` data.


Args:
tag: the key to use when serializing the model variant's tag.
"""
Expand Down Expand Up @@ -264,3 +263,9 @@ def _deserialize_with(self, model, d):
model.__class__ = self._deserialize(tag)

return model, content


__all__ = [
name for name, obj in globals().items()
if utils.is_subclass(obj, Tag)
]
12 changes: 12 additions & 0 deletions src/serde/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ def dict_partition(d, keyfunc, dict=OrderedDict):
return left, right


def is_subclass(cls, class_or_tuple):
"""
Return whether 'cls' is a derived from another class or is the same class.

Does not raise `TypeError` if the given `cls` is not a class.
"""
try:
return issubclass(cls, class_or_tuple)
except TypeError:
return False


def subclasses(cls):
"""
Returns the recursed subclasses.
Expand Down