Skip to content

Commit 35e0582

Browse files
author
Aljosha Friemann
authored
Merge pull request #5 from AFriemann/v2
V2
2 parents dfa0ece + 1dc871e commit 35e0582

File tree

5 files changed

+126
-159
lines changed

5 files changed

+126
-159
lines changed

README.v2.rst

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ to work:
2626
2727
>>> from pprint import pprint
2828
29+
*Note*: casting Data to a dict as it is done here is unnecessary as well. However pprint does not sort models in
30+
Python 2 without the cast for some reason.
31+
2932
Examples:
3033

3134
.. code:: python
@@ -184,10 +187,10 @@ To easily check against expected values you can use the helper function *one_of*
184187
>>> Data(foo='foo') # doctest: +ELLIPSIS +IGNORE_EXCEPTION_DETAIL
185188
Traceback (most recent call last):
186189
...
187-
ModelError: Data
188-
- attribute: {'name': 'foo', 'type': ..., 'default': None, 'optional': False, 'mutable': True, 'alias': None, 'help': None, 'value': 'bar'}
190+
simple_model.v2.ModelError: Data
191+
- attribute: foo
189192
value: "foo"
190-
exception: ('Invalid value for Attribute: foo', ValueError("must be one of ('bar', 'foobar') but was 'foo'",))
193+
exception: must be one of ('bar', 'foobar') but was 'foo'
191194
192195
If you want to disallow unknown values, set the *ignore_unknown* attribute to False
193196

@@ -206,7 +209,7 @@ If you want to disallow unknown values, set the *ignore_unknown* attribute to Fa
206209
value: "def"
207210
exception: Unknown attribute "other"
208211
209-
Models are immutable by default
212+
Models are mutable by default
210213

211214
.. code:: python
212215
@@ -216,16 +219,19 @@ Models are immutable by default
216219
... pass
217220
218221
>>> d = Data(point = 1)
219-
>>> d.point = 2 # doctest: +ELLIPSIS
220-
Traceback (most recent call last):
221-
...
222-
AttributeError: can't set attribute
222+
>>> d.point
223+
1
224+
>>> d.point = 2
225+
>>> d.point
226+
2
227+
228+
.. fix vim syntax issues: '
223229
224-
You can set Models to be mutable and change Attribute values after creation
230+
You can set Models to be immutable
225231

226232
.. code:: python
227233
228-
>>> @Model(mutable=True)
234+
>>> @Model(mutable=False)
229235
... @Attribute('point', type=int)
230236
... class Data(object):
231237
... pass
@@ -234,8 +240,11 @@ You can set Models to be mutable and change Attribute values after creation
234240
>>> d.point
235241
1
236242
>>> d.point = 2
237-
>>> d.point
238-
2
243+
Traceback (most recent call last):
244+
...
245+
AttributeError: can't set attribute
246+
247+
.. fix syntax: '
239248
240249
This can also be done on a per Attribute basis
241250

simple_model/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
.. moduleauthor:: Aljosha Friemann a.friemann@automate.wtf
88
"""
99

10-
__version__ = '1.2.1'
10+
__version__ = '1.2.2'
1111

1212
from simple_model.helpers import list_type, one_of
1313
from simple_model.v1 import Model, Attribute

simple_model/v2/__init__.py

Lines changed: 76 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
11
# -*- coding: utf-8 -*-
2-
"""
3-
.. module:: simple_model.v2
4-
:platform: Unix
5-
:synopsis: Simple decorator based models for easy data (de-)serialization and validation.
6-
7-
.. moduleauthor:: Aljosha Friemann a.friemann@automate.wtf
8-
9-
"""
102

113
import copy
124

@@ -17,98 +9,96 @@
179
class ModelError(RuntimeError):
1810
def __str__(self):
1911
def format_arg(arg):
20-
return '- attribute: {}\n value: "{}"\n exception: {}'.format(*arg).strip()
12+
return '- attribute: {}\n value: "{}"\n exception: {}'.format(
13+
arg[0].name if arg[0] else None, arg[1], arg[2]
14+
).strip()
2115

2216
return '{name}\n{errors}'.format(
2317
name=self.args[0],
2418
errors='\n'.join(
25-
format_arg(arg) for arg in self.args[1]
19+
format_arg(arg.args) for arg in self.args[1]
2620
)
2721
)
2822

2923

30-
class Model:
31-
def __init__(self, mutable=False, hide_unset=False, drop_unknown=False, ignore_unknown=True):
24+
class Model(object):
25+
def __init__(self, mutable=True, hide_unset=False, drop_unknown=False, ignore_unknown=True):
3226
self.mutable = mutable
3327
self.hide_unset = hide_unset
3428
self.drop_unknown = drop_unknown
3529
self.ignore_unknown = ignore_unknown
3630

3731
def __call__(self, model):
38-
custom_init = getattr(model, '__init__', None)
32+
old_init = getattr(model, '__init__', None)
3933

40-
def __init__(cls, *args, **kwargs):
41-
# make this instance memory independent from it's class.
42-
cls.__attributes__ = copy.deepcopy(cls.__attributes__)
43-
44-
errors = []
34+
def new_init(cls, *args, **kwargs):
35+
exceptions = []
4536

4637
for attribute in cls.__attributes__:
47-
if attribute.is_set():
48-
# skip attributes that have already been set.
49-
continue
38+
if hasattr(cls, attribute.name):
39+
if getattr(cls, attribute.name) is not Unset:
40+
continue
5041

51-
value = kwargs.pop(attribute.name, kwargs.pop(
52-
attribute.alias,
53-
Unset))
42+
value = kwargs.pop(attribute.name,
43+
kwargs.pop(attribute.alias, Unset)) # noqa: E128
5444

5545
try:
56-
attribute.set(value=value)
46+
attribute.fset(cls, value)
5747
except (AttributeError, ValueError) as e:
58-
errors.append((attribute, value, e))
59-
60-
mutable = attribute.mutable or (self.mutable and attribute.mutable is None)
48+
exception = AttributeError(attribute, value, e)
49+
exception.__cause__ = None
50+
exceptions.append(exception)
6151

62-
prop = property(
63-
fget=attribute.get,
64-
fset=attribute.set if mutable else None,
65-
fdel=attribute.unset if mutable else None,
66-
doc=attribute.help
67-
)
68-
69-
setattr(model, attribute.name, prop)
52+
if not self.mutable:
53+
setattr(model, attribute.name,
54+
property(fget=getattr(model, attribute.name).fget)
55+
)
7056

7157
if kwargs:
7258
if self.drop_unknown:
7359
kwargs = {}
7460
elif not self.ignore_unknown:
75-
errors.extend(
76-
(None, v, AttributeError('Unknown attribute "%s"' % k)) for k, v in kwargs.items()
61+
exceptions.extend(
62+
(AttributeError(None, v, 'Unknown attribute "%s"' % k) for k, v in kwargs.items())
7763
)
7864

79-
if errors:
80-
raise ModelError(cls.__class__.__name__, errors)
65+
if exceptions:
66+
raise ModelError(cls.__class__.__name__, exceptions)
8167

82-
if custom_init:
83-
custom_init(cls, *args, **kwargs)
68+
if old_init:
69+
old_init(cls, *args, **kwargs)
8470

85-
if custom_init is not None:
86-
__init__.__doc__ = custom_init.__doc__
71+
if old_init:
72+
new_init.__doc__ = old_init.__doc__
8773

88-
def __getitem__(cls, key):
89-
for attribute in cls.__attributes__:
90-
if attribute.name == key or attribute.alias == key:
74+
def getitem(cls, key):
75+
for a in cls.__attributes__:
76+
if a.name == key or a.alias == key:
9177
try:
92-
return dict(attribute.get())
78+
return dict(getattr(cls, a.name))
9379
except (ValueError, TypeError):
94-
return attribute.get()
80+
return getattr(cls, a.name)
9581

9682
raise KeyError(key)
9783

98-
model.__init__ = __init__
99-
model.__getitem__ = __getitem__
84+
model.__init__ = new_init
85+
model.__getitem__ = getitem
86+
10087
model.__str__ = lambda cls: str(dict(cls))
10188
model.__repr__ = lambda cls: str(dict(cls))
10289
model.__ne__ = lambda cls, o: not cls.__eq__(o)
103-
model.__eq__ = lambda cls, o: (isinstance(o, cls.__class__) and dict(cls) == dict(o))
104-
model.__contains__ = lambda cls, key: next((a for a in cls.__attributes__ if a.name == key and (a.is_set() or not self.hide_unset)), Unset) is not Unset
105-
model.keys = lambda cls: sorted([ a.alias or a.name for a in cls.__attributes__ if (a.is_set() or not self.hide_unset)])
90+
model.__eq__ = lambda cls, o: (issubclass(o.__class__, cls.__class__) and dict(cls) == dict(o))
91+
model.__contains__ = lambda cls, key: getattr(cls, key) not in [None, Unset]
92+
model.keys = lambda cls: sorted(
93+
[a.alias or a.name for a in cls.__attributes__ if (
94+
getattr(cls, a.name) not in [None, Unset] or not self.hide_unset)]
95+
)
10696

10797
return model
10898

10999

110-
class Attribute:
111-
def __init__(self, name, type, optional=False, nullable=False, default=None, fdefault=None, mutable=None, alias=None, help=None):
100+
class Attribute(object):
101+
def __init__(self, name, type, optional=False, nullable=False, mutable=True, default=None, fdefault=None, alias=None, help=None, value_by_reference=False):
112102
self.name = name
113103
self.type = type
114104
self.default = default
@@ -118,14 +108,10 @@ def __init__(self, name, type, optional=False, nullable=False, default=None, fde
118108
self.mutable = mutable
119109
self.alias = alias
120110
self.help = help
111+
self.value_by_reference = value_by_reference
121112

122-
try:
123-
if self.default is not None:
124-
self.parse(self.default)
125-
elif self.fdefault is not None:
126-
self.parse(self.fdefault())
127-
except Exception as e:
128-
raise ValueError("Invalid default value(s) (%s/%s) for type %s" % (self.default, self.fdefault, self.type), e)
113+
def __repr__(self):
114+
return str(vars(self))
129115

130116
def __call__(self, model):
131117
if not hasattr(model, '__attributes__'):
@@ -138,48 +124,52 @@ def __call__(self, model):
138124
finally:
139125
model.__attributes__.add(self)
140126

127+
setattr(model, self.value_name, Unset)
128+
129+
prop = property(
130+
fget=self.fget,
131+
fset=self.fset if self.mutable else None,
132+
fdel=self.fdel if self.mutable else None,
133+
doc=self.help
134+
)
135+
136+
setattr(model, self.name, prop)
137+
141138
return model
142139

143-
def __repr__(self):
144-
return str(vars(self))
140+
def fget(self, cls):
141+
return getattr(cls, self.value_name)
145142

146-
def set(self, model=None, value=Unset):
143+
def fset(self, cls, value):
147144
if value is Unset:
148-
try:
149-
value = self.get_default()
150-
except AttributeError as e:
151-
pass
145+
if self.default is not None:
146+
value = self.default
147+
elif self.fdefault is not None:
148+
value = self.fdefault()
152149

153-
try:
154-
self.value = self.parse(value)
155-
except NotImplementedError as e:
156-
raise e
157-
except Exception as e:
158-
raise ValueError("Invalid value for Attribute: %s" % value, e)
150+
setattr(cls, self.value_name, self.parse(value))
159151

160-
def get(self, model=None):
161-
try:
162-
return self.value
163-
except AttributeError as e:
164-
if not self.optional:
165-
raise e
152+
def fdel(self, cls):
153+
setattr(cls, self.value_name, Unset)
166154

167-
def unset(self, model=None):
168-
del self.value
155+
@property
156+
def value_name(self):
157+
return '_%s' % self.name
169158

170159
def parse(self, value):
171160
if value is Unset:
172161
if self.optional:
173162
return value
174163
else:
175-
raise ValueError("Non-optional attribute set with non-value.")
164+
raise AttributeError(self, value, "Attribute is not optional")
176165
elif value is None:
177166
if self.nullable:
178167
return None
179168
else:
180169
value = self.get_default()
181170

182-
value = copy.deepcopy(value)
171+
if not self.value_by_reference:
172+
value = copy.deepcopy(value)
183173

184174
if self.type is None:
185175
return value
@@ -189,18 +179,4 @@ def parse(self, value):
189179
except TypeError:
190180
return self.type(value)
191181

192-
def is_set(self):
193-
try:
194-
return self.value is not Unset
195-
except AttributeError:
196-
return False
197-
198-
def get_default(self):
199-
if self.default is not None:
200-
return self.default
201-
elif self.fdefault is not None:
202-
return self.fdefault()
203-
else:
204-
raise AttributeError("Attribute has no default")
205-
206182
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 fenc=utf-8

0 commit comments

Comments
 (0)