Skip to content

Commit b93b769

Browse files
authored
Merge pull request #33 from alexei/feature/fields
Implement various fields
2 parents 0a28c52 + 95e8cc4 commit b93b769

17 files changed

Lines changed: 429 additions & 268 deletions

adapter/__init__.py

Lines changed: 0 additions & 3 deletions
This file was deleted.

adapter/adapter.py

Lines changed: 0 additions & 173 deletions
This file was deleted.

adapters/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from __future__ import absolute_import
4+
5+
from .adapters import * # noqa
6+
from .fields import * # noqa

adapters/adapters.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from __future__ import unicode_literals
4+
from __future__ import absolute_import
5+
6+
import collections
7+
import copy
8+
import six
9+
10+
from .base import BaseField
11+
from .meta import AdapterMetaClass
12+
from .utils import BindingDict
13+
from .utils import undefined
14+
15+
16+
__all__ = ['Adapter']
17+
18+
19+
@six.add_metaclass(AdapterMetaClass)
20+
class Adapter(BaseField):
21+
def __init__(self, data=None, instance=None, *args, **kwargs):
22+
self.data = data
23+
self.instance = instance
24+
25+
super(Adapter, self).__init__(*args, **kwargs)
26+
27+
@property
28+
def fields(self):
29+
if not hasattr(self, '_fields'):
30+
self._fields = BindingDict(self)
31+
for key, value in self.get_fields().iteritems():
32+
self._fields[key] = value
33+
return self._fields
34+
35+
def get_fields(self):
36+
return copy.deepcopy(self.declared_fields)
37+
38+
def adapt(self, data=None):
39+
instance = self.get_instance()
40+
for field_name, field in self.fields.iteritems():
41+
value = field.get_attribute(data or self.data)
42+
if value is undefined:
43+
continue
44+
adapted_value = field.adapt(value)
45+
if isinstance(instance, collections.Mapping):
46+
instance[field_name] = adapted_value
47+
else:
48+
setattr(instance, field_name, adapted_value)
49+
return instance
50+
51+
def get_instance(self):
52+
if self.instance:
53+
return self.instance
54+
else:
55+
meta = getattr(self, 'Meta', None)
56+
model_cls = getattr(meta, 'model', dict)
57+
return model_cls()

adapters/base.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from __future__ import unicode_literals
4+
from __future__ import absolute_import
5+
6+
from .helpers import get_attribute
7+
from .utils import undefined
8+
9+
10+
class BaseField(object):
11+
def __init__(self, source=None, default=undefined, required=True):
12+
self.source = source
13+
self.default = default
14+
self.required = required
15+
16+
def bind(self, field_name, adapter):
17+
if field_name == self.source:
18+
raise ValueError((
19+
"The `source='{field_name}'` kwarg is redundant on "
20+
"field `{adapter_name}.{field_name}`. "
21+
"Remove the `source` kwarg."
22+
).format(
23+
field_name=field_name,
24+
adapter_name=adapter.__class__.__name__,
25+
))
26+
27+
self.field_name = field_name
28+
self.adapter = adapter
29+
30+
if self.source is None:
31+
self.source = self.field_name
32+
33+
if self.source == '*':
34+
self.lookup_attrs = []
35+
else:
36+
self.lookup_attrs = self.source.split('.')
37+
38+
def get_attribute(self, obj):
39+
value = get_attribute(obj, self.lookup_attrs)
40+
if value is undefined:
41+
if self.default is not undefined:
42+
return self.default
43+
elif self.required:
44+
raise ValueError((
45+
"Required value not found for field "
46+
"`{adapter_name}.{field_name}`. Provide a default value."
47+
).format(
48+
adapter_name=self.adapter.__class__.__name__,
49+
field_name=self.field_name,
50+
))
51+
else:
52+
return undefined
53+
else:
54+
return value
55+
56+
def adapt(self, data):
57+
return data

adapters/fields.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from __future__ import unicode_literals
4+
5+
import datetime
6+
import dateutil.parser
7+
from decimal import Decimal
8+
9+
from .base import BaseField
10+
11+
12+
__all__ = [
13+
'BooleanField', 'CharField', 'DecimalField', 'Field', 'FloatField',
14+
'IntField', 'TimeField']
15+
16+
17+
class BooleanField(BaseField):
18+
def adapt(self, data):
19+
return bool(data)
20+
21+
22+
class CharField(BaseField):
23+
def adapt(self, data):
24+
return unicode(data)
25+
26+
27+
class DecimalField(BaseField):
28+
def adapt(self, data):
29+
return Decimal(data)
30+
31+
32+
class Field(BaseField):
33+
pass
34+
35+
36+
class FloatField(BaseField):
37+
def adapt(self, data):
38+
return float(data)
39+
40+
41+
class IntField(BaseField):
42+
def adapt(self, data):
43+
return int(data)
44+
45+
46+
class TimeField(BaseField):
47+
def adapt(self, data):
48+
if isinstance(data, datetime.time):
49+
return data
50+
elif isinstance(data, (str, unicode)):
51+
return dateutil.parser.parse(data).timetz()
52+
else:
53+
raise ValueError("Invalid time argument")

adapters/helpers.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from __future__ import unicode_literals
4+
from __future__ import absolute_import
5+
6+
import collections
7+
8+
from .utils import undefined
9+
10+
11+
__all__ = ['get_attribute']
12+
13+
14+
def get_attribute(obj, attrs):
15+
for attr in attrs:
16+
if obj is None:
17+
return undefined
18+
19+
try:
20+
if isinstance(obj, collections.Mapping):
21+
obj = obj[attr]
22+
elif isinstance(obj, collections.Iterable):
23+
obj = obj[int(attr)]
24+
else:
25+
obj = getattr(obj, attr)
26+
except Exception:
27+
return undefined
28+
29+
if callable(obj):
30+
obj = obj()
31+
32+
return obj

0 commit comments

Comments
 (0)