Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor some code for understandability #130

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions jsonmodels/collections.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


class ModelCollection(list):

"""`ModelCollection` is list which validates stored values.
Expand Down
88 changes: 49 additions & 39 deletions jsonmodels/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from weakref import WeakKeyDictionary

import six
from dateutil.parser import parse
import dateutil.parser

from .errors import ValidationError
from .collections import ModelCollection
Expand All @@ -14,6 +14,9 @@
NotSet = object()


_FIELD_NAME_REGEX = re.compile(r'^[A-Za-z_](([\w\-]*)?\w+)?$')


class BaseField(object):

"""Base class for all fields."""
Expand All @@ -39,6 +42,8 @@ def __init__(
self.validate(default)
self._default = default

self._initialized = False

@property
def has_default(self):
return self._default is not NotSet
Expand All @@ -49,25 +54,30 @@ def _assign_validators(self, validators):
self.validators = validators or []

def __set__(self, instance, value):
self._finish_initialization(type(instance))
if not self._initialized:
self._finish_initialization(type(instance))
self._initialized = True
value = self.parse_value(value)
self.validate(value)
self.memory[instance._cache_key] = value

def __get__(self, instance, owner=None):
if not self._initialized:
if instance is None:
self._finish_initialization(owner)
else:
self._finish_initialization(type(instance))
self._initialized = True
if instance is None:
self._finish_initialization(owner)
return self

self._finish_initialization(type(instance))

self._check_value(instance)
return self.memory[instance._cache_key]
else:
self._maybe_assign_default_value(instance)
return self.memory[instance._cache_key]

def _finish_initialization(self, owner):
pass

def _check_value(self, obj):
def _maybe_assign_default_value(self, obj):
if obj._cache_key not in self.memory:
self.__set__(obj, self.get_default_value())

Expand All @@ -88,10 +98,9 @@ def _check_against_required(self, value):
def _validate_against_types(self, value):
if value is not None and not isinstance(value, self.types):
raise ValidationError(
'Value is wrong, expected type "{types}"'.format(
types=', '.join([t.__name__ for t in self.types])
),
value,
'Value {value!r} is wrong, expected type {types!r}'
.format(value=value,
types=', '.join(t.__name__ for t in self.types))
)

def _check_types(self):
Expand Down Expand Up @@ -132,14 +141,15 @@ def get_default_value(self):
return self._default if self.has_default else None

def _validate_name(self):
if self.name is None:
return
if not re.match('^[A-Za-z_](([\w\-]*)?\w+)?$', self.name):
if self.name is not None \
and not re.match(_FIELD_NAME_REGEX, self.name):
raise ValueError('Wrong name', self.name)

def structue_name(self, default):
def structure_name(self, default):
return self.name if self.name is not None else default

structue_name = structure_name


class StringField(BaseField):

Expand All @@ -159,7 +169,8 @@ def parse_value(self, value):
parsed = super(IntField, self).parse_value(value)
if parsed is None:
return parsed
return int(parsed)
else:
return int(parsed)


class FloatField(BaseField):
Expand Down Expand Up @@ -209,7 +220,7 @@ def _assign_types(self, items_types):
try:
self.items_types = tuple(items_types)
except TypeError:
self.items_types = items_types,
self.items_types = (items_types, )
else:
self.items_types = tuple()

Expand Down Expand Up @@ -244,15 +255,12 @@ def validate_single_value(self, item):

def parse_value(self, values):
"""Cast value to proper collection."""
result = self.get_default_value()

if not values:
return result

if not isinstance(values, list):
return self.get_default_value()
elif not isinstance(values, list):
return values

return [self._cast_value(value) for value in values]
else:
return [self._cast_value(value) for value in values]

def _cast_value(self, value):
if isinstance(value, self.items_types):
Expand All @@ -271,11 +279,11 @@ def _finish_initialization(self, owner):
super(ListField, self)._finish_initialization(owner)

types = []
for type in self.items_types:
if isinstance(type, _LazyType):
types.append(type.evaluate(owner))
for type_ in self.items_types:
if isinstance(type_, _LazyType):
types.append(type_.evaluate(owner))
else:
types.append(type)
types.append(type_)
self.items_types = tuple(types)

def _elem_to_struct(self, value):
Expand Down Expand Up @@ -330,9 +338,9 @@ def parse_value(self, value):
"""Parse value to proper model type."""
if not isinstance(value, dict):
return value

embed_type = self._get_embed_type()
return embed_type(**value)
else:
embed_type = self._get_embed_type()
return embed_type(**value)

def _get_embed_type(self):
if len(self.types) != 1:
Expand Down Expand Up @@ -419,9 +427,10 @@ def parse_value(self, value):
"""Parse string into instance of `time`."""
if value is None:
return value
if isinstance(value, datetime.time):
elif isinstance(value, datetime.time):
return value
return parse(value).timetz()
else:
return dateutil.parser.parse(value).timetz()


class DateField(StringField):
Expand Down Expand Up @@ -451,9 +460,10 @@ def parse_value(self, value):
"""Parse string into instance of `date`."""
if value is None:
return value
if isinstance(value, datetime.date):
elif isinstance(value, datetime.date):
return value
return parse(value).date()
else:
return dateutil.parser.parse(value).date()


class DateTimeField(StringField):
Expand Down Expand Up @@ -482,7 +492,7 @@ def parse_value(self, value):
"""Parse string into instance of `datetime`."""
if isinstance(value, datetime.datetime):
return value
if value:
return parse(value)
elif value:
return dateutil.parser.parse(value)
else:
return None
70 changes: 38 additions & 32 deletions jsonmodels/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ def validate_fields(attributes):
}
taken_names = set()
for name, field in fields.items():
structue_name = field.structue_name(name)
if structue_name in taken_names:
raise ValueError('Name taken', structue_name, name)
taken_names.add(structue_name)
structure_name = field.structure_name(name)
if structure_name in taken_names:
raise ValueError('Name taken', structure_name, name)
taken_names.add(structure_name)


class Base(six.with_metaclass(JsonmodelMeta, object)):
Expand All @@ -31,61 +31,66 @@ class Base(six.with_metaclass(JsonmodelMeta, object)):

def __init__(self, **kwargs):
self._cache_key = _CacheKey()
self.initialize_fields()
self.populate(**kwargs)

def initialize_fields(self):
for _, _, _ in self.iterate_with_name():
pass

def populate(self, **values):
"""Populate values to fields. Skip non-existing."""
values = values.copy()
fields = list(self.iterate_with_name())
for _, structure_name, field in fields:
for attr_name, structure_name, field in self.iterate_with_name():
# set field by structure name
if structure_name in values:
field.__set__(self, values.pop(structure_name))
for name, _, field in fields:
if name in values:
field.__set__(self, values.pop(name))
elif attr_name in values:
field.__set__(self, values.pop(attr_name))

def get_field(self, field_name):
@classmethod
def get_field(cls, field_name):
"""Get field associated with given attribute."""
for attr_name, field in self:
if field_name == attr_name:
return field

raise errors.FieldNotFound('Field not found', field_name)
field = getattr(cls, field_name, None)
if isinstance(field, BaseField):
return field
else:
raise errors.FieldNotFound('Field not found', field_name)

def __iter__(self):
"""Iterate through fields and values."""
for name, field in self.iterate_over_fields():
yield name, field
for item in self.iterate_over_fields():
yield item

def validate(self):
"""Explicitly validate all the fields."""
for name, field in self:
for attr_name, field in self:
try:
field.validate_for_object(self)
except ValidationError as error:
raise ValidationError(
"Error for field '{name}'.".format(name=name),
error,
'Error for field {attr_name!r}: {error}'
.format(attr_name=attr_name, error=error)
)

@classmethod
def iterate_over_fields(cls):
"""Iterate through fields as `(attribute_name, field_instance)`."""
for attr in dir(cls):
clsattr = getattr(cls, attr)
if isinstance(clsattr, BaseField):
yield attr, clsattr
for attr_name in dir(cls):
field = getattr(cls, attr_name)
if isinstance(field, BaseField):
yield attr_name, field

@classmethod
def iterate_with_name(cls):
"""Iterate over fields, but also give `structure_name`.

Format is `(attribute_name, structue_name, field_instance)`.
Format is `(attribute_name, structure_name, field_instance)`.
Structure name is name under which value is seen in structure and
schema (in primitives) and only there.
"""
for attr_name, field in cls.iterate_over_fields():
structure_name = field.structue_name(attr_name)
structure_name = field.structure_name(attr_name)
yield attr_name, structure_name, field

def to_struct(self):
Expand All @@ -99,11 +104,11 @@ def to_json_schema(cls):

def __repr__(self):
attrs = {}
for name, _ in self:
for attr_name, field in self:
try:
attr = getattr(self, name)
attr = getattr(self, attr_name)
if attr is not None:
attrs[name] = repr(attr)
attrs[attr_name] = repr(attr)
except ValidationError:
pass

Expand All @@ -130,14 +135,14 @@ def __eq__(self, other):
if type(other) is not type(self):
return False

for name, _ in self.iterate_over_fields():
for attr_name, _ in self.iterate_over_fields():
try:
our = getattr(self, name)
our = getattr(self, attr_name)
except errors.ValidationError:
our = None

try:
their = getattr(other, name)
their = getattr(other, attr_name)
except errors.ValidationError:
their = None

Expand All @@ -152,3 +157,4 @@ def __ne__(self, other):

class _CacheKey(object):
"""Object to identify model in memory."""
pass
Loading