Skip to content

Commit 9bf2837

Browse files
committed
Issue #25: Reviewers' suggestions.
1 parent 14238bf commit 9bf2837

File tree

2 files changed

+101
-70
lines changed

2 files changed

+101
-70
lines changed

domain_models/views.py

Lines changed: 94 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,59 @@
77
class ContextViewMetaClass(type):
88
"""Context view meta class."""
99

10-
attributes = dict()
11-
1210
def __new__(mcs, class_name, bases, attributes):
1311
"""Context view class factory."""
14-
mcs.attributes = attributes
15-
mcs.validate(bases)
12+
mcs.validate(bases, attributes)
1613
cls = type.__new__(mcs, class_name, bases, attributes)
17-
cls.__fields__ = mcs.get_properties()
14+
cls.__fields__ = mcs.get_properties(attributes)
1815
return cls
1916

2017
@classmethod
21-
def validate(mcs, bases):
18+
def validate(mcs, bases, attributes):
2219
"""Check attributes."""
2320
if bases[0] is object:
2421
return None
25-
mcs.check_model_cls()
26-
mcs.check_include_exclude()
2722

28-
@classmethod
29-
def check_model_cls(mcs):
30-
"""Check __model_cls__ attribute."""
31-
model_cls = mcs.attributes.get('__model_cls__')
23+
mcs.check_model_cls(attributes)
24+
mcs.check_include_exclude(attributes)
25+
mcs.check_properties(attributes)
26+
mcs.match_unknown_attrs(attributes)
27+
28+
@staticmethod
29+
def check_model_cls(attributes):
30+
"""Check __model_cls__ attribute.
31+
32+
:type attributes: dict
33+
"""
34+
model_cls = attributes.get('__model_cls__')
3235
if model_cls is None:
3336
raise AttributeError("Attribute __model_cls__ is required.")
3437

3538
if not issubclass(model_cls, models.DomainModel):
3639
raise TypeError("Attribute __model_cls__ must be subclass of "
3740
"DomainModel.")
3841

39-
@classmethod
40-
def check_include_exclude(mcs):
41-
"""Check __include__ and __exclude__ attributes."""
42-
include = mcs.attributes.get('__include__', tuple())
43-
exclude = mcs.attributes.get('__exclude__', tuple())
42+
@staticmethod
43+
def get_prepared_include_exclude(attributes):
44+
"""Return tuple with prepared __include__ and __exclude__ attributes.
45+
46+
:type attributes: dict
47+
:rtype: tuple
48+
"""
49+
attrs = dict()
50+
for attr in ('__include__', '__exclude__'):
51+
attrs[attr] = tuple([item.name for item in
52+
attributes.get(attr, tuple())])
53+
return attrs['__include__'], attrs['__exclude__']
54+
55+
@staticmethod
56+
def check_include_exclude(attributes):
57+
"""Check __include__ and __exclude__ attributes.
58+
59+
:type attributes: dict
60+
"""
61+
include = attributes.get('__include__', tuple())
62+
exclude = attributes.get('__exclude__', tuple())
4463

4564
if not isinstance(include, tuple):
4665
raise TypeError("Attribute __include__ must be a tuple.")
@@ -55,71 +74,80 @@ def check_include_exclude(mcs):
5574
raise AttributeError("Usage of __include__ and __exclude__ "
5675
"at the same time is prohibited.")
5776

58-
include_names = [item.name for item in include]
59-
exclude_names = [item.name for item in exclude]
77+
@staticmethod
78+
def get_properties(attributes):
79+
"""Return tuple of names of defined properties.
6080
61-
mcs.chk_intersections(include_names, exclude_names)
62-
mcs.match_unknown_attrs(include_names, exclude_names)
81+
:type attributes: dict
82+
:rtype: list
83+
"""
84+
return [key for key, value in six.iteritems(attributes)
85+
if isinstance(value, property)]
6386

6487
@classmethod
65-
def chk_intersections(mcs, include, exclude):
66-
"""Check whether intersections exist."""
67-
intersections = mcs.get_intersections(include)
68-
attr, intersections = ('__include__', intersections) \
69-
if intersections \
70-
else ('__exclude__', mcs.get_intersections(exclude))
71-
72-
if intersections:
73-
raise AttributeError(
74-
"It is not allowed to mention already defined properties: "
75-
"{0} in {1} attributes.".format(", ".join(intersections),
76-
attr))
88+
def check_properties(mcs, attributes):
89+
"""Check whether intersections exist.
90+
91+
:type attributes: dict
92+
"""
93+
include, exclude = mcs.get_prepared_include_exclude(attributes)
94+
95+
if include:
96+
intersections = mcs.get_intersections(attributes, include)
97+
attr = '__include__'
98+
elif exclude:
99+
intersections = mcs.get_intersections(attributes, exclude)
100+
attr = '__exclude__'
101+
else:
102+
return None
103+
104+
if not intersections:
105+
return None
106+
107+
raise AttributeError(
108+
"It is not allowed to mention already defined properties: "
109+
"{0} in {1} attributes.".format(", ".join(intersections), attr))
77110

78111
@classmethod
79-
def get_intersections(mcs, attr):
112+
def get_intersections(mcs, attributes, attr):
80113
"""Return intersection with defined properties if exists.
81114
115+
:type attributes: dict
82116
:type attr: list
83117
:rtype: list
84118
"""
85119
if not attr:
86120
return []
87-
return list(set(mcs.get_properties()).intersection(attr))
88-
89-
@classmethod
90-
def match_unknown_attrs(mcs, include, exclude):
91-
"""Check about using nonexistent attributes."""
92-
model_cls = mcs.attributes.get('__model_cls__')
93-
unknown_attr = []
94-
for item in include:
95-
if not hasattr(model_cls, item):
96-
unknown_attr.append(item)
97-
98-
for item in exclude:
99-
if not hasattr(model_cls, item):
100-
unknown_attr.append(item)
101-
102-
if unknown_attr:
103-
raise AttributeError(
104-
"Nonexistent attributes: {0}.".format(
105-
", ".join(unknown_attr)))
121+
properties = mcs.get_properties(attributes)
122+
return list(set(properties).intersection(attr))
106123

107124
@classmethod
108-
def get_properties(mcs):
109-
"""Return list of names of defined properties.
125+
def match_unknown_attrs(mcs, attributes):
126+
"""Check about using nonexistent attributes.
110127
111-
:rtype: list
128+
:type attributes: dict
112129
"""
113-
return [key for key, value in six.iteritems(mcs.attributes)
114-
if isinstance(value, property)]
130+
model_cls = attributes.get('__model_cls__')
131+
include, exclude = mcs.get_prepared_include_exclude(attributes)
132+
attrs = include if include else exclude
133+
unknown_attr = list()
134+
135+
for attr in attrs:
136+
if not hasattr(model_cls, attr):
137+
unknown_attr.append(attr)
138+
139+
if not unknown_attr:
140+
return None
141+
142+
raise AttributeError(
143+
"Nonexistent attributes: {0}.".format(", ".join(unknown_attr)))
115144

116145

117146
@six.add_metaclass(ContextViewMetaClass)
118147
class ContextView(object):
119148
"""Contextual view class."""
120149

121150
__model_cls__ = None
122-
__model__ = None
123151
__include__ = tuple()
124152
__exclude__ = tuple()
125153
__fields__ = list()
@@ -133,7 +161,7 @@ def __init__(self, model):
133161
raise TypeError("\"{0}\" is not an instance of {1}".format(
134162
model, self.__model_cls__))
135163

136-
self.__model__ = model
164+
self._model = model
137165

138166
if self.__include__:
139167
self._include()
@@ -143,21 +171,24 @@ def __init__(self, model):
143171
self._fields()
144172

145173
def _include(self):
174+
"""Fill __fields__ out based on __include__."""
146175
for field in self.__include__:
147-
value = getattr(self.__model__, field.name)
176+
value = getattr(self._model, field.name)
148177
setattr(self, field.name, value)
149178
self.__fields__.append(field.name)
150179

151180
def _exclude(self):
181+
"""Fill __fields__ out based on __exclude__."""
152182
exclude = [field.name for field in self.__exclude__]
153-
for (field, value) in six.iteritems(self.__model__.get_data()):
183+
for (field, value) in six.iteritems(self._model.get_data()):
154184
if field in exclude:
155185
continue
156186
setattr(self, field, value)
157187
self.__fields__.append(field)
158188

159189
def _fields(self):
160-
for (field, value) in six.iteritems(self.__model__.get_data()):
190+
"""Fill __fields__ out based on full model data."""
191+
for (field, value) in six.iteritems(self._model.get_data()):
161192
if field in self.__fields__:
162193
continue
163194
setattr(self, field, value)

tests/test_context_view.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def oid(self):
3939
4040
:rtype: int
4141
"""
42-
return self.__model__.id << 8
42+
return self._model.id << 8
4343

4444
@property
4545
def photos(self):
@@ -48,7 +48,7 @@ def photos(self):
4848
:rtype: list
4949
"""
5050
photos = []
51-
for photo in self.__model__.photos:
51+
for photo in self._model.photos:
5252
if not photo.public:
5353
continue
5454
photo_data = PhotoPublicContext(photo).get_data()
@@ -62,7 +62,7 @@ def main_photo(self):
6262
6363
:rtype: dict
6464
"""
65-
return PhotoPublicContext(self.__model__.main_photo).get_data()
65+
return PhotoPublicContext(self._model.main_photo).get_data()
6666

6767

6868
class ProfilePrivateContext(views.ContextView):
@@ -76,7 +76,7 @@ def photos(self):
7676
:rtype: list
7777
"""
7878
photos = []
79-
for photo in self.__model__.photos:
79+
for photo in self._model.photos:
8080
photo_data = PhotoPrivateContext(photo).get_data()
8181
if photo_data:
8282
photos.append(photo_data)
@@ -88,7 +88,7 @@ def main_photo(self):
8888
8989
:rtype: dict
9090
"""
91-
return PhotoPrivateContext(self.__model__.main_photo).get_data()
91+
return PhotoPrivateContext(self._model.main_photo).get_data()
9292

9393

9494
class PhotoPublicContext(views.ContextView):
@@ -102,7 +102,7 @@ def oid(self):
102102
103103
:rtype: int
104104
"""
105-
return self.__model__.id << 8
105+
return self._model.id << 8
106106

107107

108108
class PhotoPrivateContext(views.ContextView):
@@ -273,7 +273,7 @@ class SomeContext(views.ContextView):
273273

274274
@property
275275
def name(self):
276-
return " + ".join((self.__model__.name, "postfix"))
276+
return " + ".join((self._model.name, "postfix"))
277277

278278
profile_within_context = SomeContext(self.profile)
279279
self.assertEqual(profile_within_context.name, 'John + postfix')

0 commit comments

Comments
 (0)