|
1 | 1 | """Contextual view module.""" |
2 | | -from domain_models import models |
| 2 | +from . import models |
3 | 3 | import six |
4 | 4 |
|
5 | 5 |
|
6 | 6 | class ContextViewMetaClass(type): |
7 | 7 | """Context view meta class.""" |
8 | 8 |
|
| 9 | + attributes = dict() |
| 10 | + |
9 | 11 | def __new__(mcs, class_name, bases, attributes): |
10 | 12 | """Context view class factory.""" |
11 | | - if bases[0] is not object: |
12 | | - __model_cls__ = attributes.get('__model_cls__') |
13 | | - if __model_cls__ is None: |
14 | | - raise AttributeError("Attribute __model_cls__ is required.") |
15 | | - if not issubclass(__model_cls__, models.DomainModel): |
16 | | - raise TypeError("Attribute __model_cls__ must be subclass of " |
17 | | - "DomainModel.") |
| 13 | + mcs.attributes = attributes |
| 14 | + mcs.validate(bases) |
| 15 | + cls = type.__new__(mcs, class_name, bases, attributes) |
| 16 | + cls.__fields__ = mcs.get_properties() |
| 17 | + return cls |
| 18 | + |
| 19 | + @classmethod |
| 20 | + def validate(mcs, bases): |
| 21 | + """Check attributes.""" |
| 22 | + if bases[0] is object: |
| 23 | + return None |
| 24 | + mcs.chk_model_cls() |
| 25 | + mcs.chk_incl_excl() |
| 26 | + |
| 27 | + @classmethod |
| 28 | + def chk_model_cls(mcs): |
| 29 | + """Check __model_cls__ attribute.""" |
| 30 | + model_cls = mcs.attributes.get('__model_cls__') |
| 31 | + if model_cls is None: |
| 32 | + raise AttributeError("Attribute __model_cls__ is required.") |
| 33 | + |
| 34 | + if not issubclass(model_cls, models.DomainModel): |
| 35 | + raise TypeError("Attribute __model_cls__ must be subclass of " |
| 36 | + "DomainModel.") |
| 37 | + |
| 38 | + @classmethod |
| 39 | + def chk_incl_excl(mcs): |
| 40 | + """Check __include__ and __exclude__ attributes.""" |
| 41 | + include = mcs.attributes.get('__include__', tuple()) |
| 42 | + exclude = mcs.attributes.get('__exclude__', tuple()) |
| 43 | + |
| 44 | + if not isinstance(include, tuple): |
| 45 | + raise TypeError("Attribute __include__ must be a tuple.") |
| 46 | + |
| 47 | + if not isinstance(exclude, tuple): |
| 48 | + raise TypeError("Attribute __exclude__ must be a tuple.") |
18 | 49 |
|
19 | | - return type.__new__(mcs, class_name, bases, attributes) |
| 50 | + if all((not include, not exclude)): |
| 51 | + return None |
| 52 | + |
| 53 | + if all((include, exclude)): |
| 54 | + raise AttributeError("Usage of __include__ and __exclude__ " |
| 55 | + "at the same time is prohibited.") |
| 56 | + |
| 57 | + include_names = [item.name for item in include] |
| 58 | + exclude_names = [item.name for item in exclude] |
| 59 | + |
| 60 | + mcs.chk_intersections(include_names, exclude_names) |
| 61 | + mcs.match_unknown_attrs(include_names, exclude_names) |
| 62 | + |
| 63 | + @classmethod |
| 64 | + def chk_intersections(mcs, include, exclude): |
| 65 | + """Check whether intersections exist.""" |
| 66 | + intersections = mcs.get_intersections(include) |
| 67 | + attr, intersections = ('__include__', intersections) \ |
| 68 | + if intersections \ |
| 69 | + else ('__exclude__', mcs.get_intersections(exclude)) |
| 70 | + |
| 71 | + if intersections: |
| 72 | + raise AttributeError( |
| 73 | + "It is not allowed to mention already defined properties: " |
| 74 | + "{0} in {1} attributes.".format(", ".join(intersections), |
| 75 | + attr)) |
| 76 | + |
| 77 | + @classmethod |
| 78 | + def get_intersections(mcs, attr): |
| 79 | + """Return intersection with defined properties if exists. |
| 80 | +
|
| 81 | + :type attr: list |
| 82 | + :rtype: list |
| 83 | + """ |
| 84 | + if not attr: |
| 85 | + return [] |
| 86 | + return list(set(mcs.get_properties()).intersection(attr)) |
| 87 | + |
| 88 | + @classmethod |
| 89 | + def match_unknown_attrs(mcs, include, exclude): |
| 90 | + """Check about using nonexistent attributes.""" |
| 91 | + model_cls = mcs.attributes.get('__model_cls__') |
| 92 | + unknown_attr = [] |
| 93 | + for item in include: |
| 94 | + if not hasattr(model_cls, item): |
| 95 | + unknown_attr.append(item) |
| 96 | + |
| 97 | + for item in exclude: |
| 98 | + if not hasattr(model_cls, item): |
| 99 | + unknown_attr.append(item) |
| 100 | + |
| 101 | + if unknown_attr: |
| 102 | + raise AttributeError( |
| 103 | + "Nonexistent attributes: {0}.".format( |
| 104 | + ", ".join(unknown_attr))) |
| 105 | + |
| 106 | + @classmethod |
| 107 | + def get_properties(mcs): |
| 108 | + """Return list of names of defined properties. |
| 109 | +
|
| 110 | + :rtype: list |
| 111 | + """ |
| 112 | + return [key for key, value in six.iteritems(mcs.attributes) if |
| 113 | + isinstance(value, property)] |
20 | 114 |
|
21 | 115 |
|
22 | 116 | @six.add_metaclass(ContextViewMetaClass) |
23 | 117 | class ContextView(object): |
24 | 118 | """Contextual view class.""" |
25 | 119 |
|
26 | 120 | __model_cls__ = None |
| 121 | + __model__ = None |
| 122 | + __include__ = tuple() |
| 123 | + __exclude__ = tuple() |
| 124 | + __fields__ = list() |
27 | 125 |
|
28 | 126 | def __init__(self, model): |
29 | 127 | """Model validation. |
30 | 128 |
|
31 | | - :param model: DomainModel |
| 129 | + :type model: DomainModel |
32 | 130 | """ |
33 | 131 | if not isinstance(model, self.__model_cls__): |
34 | 132 | raise TypeError("\"{0}\" is not an instance of {1}".format( |
35 | 133 | model, self.__model_cls__)) |
36 | 134 |
|
37 | 135 | self.__model__ = model |
38 | 136 |
|
| 137 | + if self.__include__: |
| 138 | + self._include() |
| 139 | + elif self.__exclude__: |
| 140 | + self._exclude() |
| 141 | + else: |
| 142 | + self._fields() |
| 143 | + |
| 144 | + def _include(self): |
| 145 | + for field in self.__include__: |
| 146 | + value = getattr(self.__model__, field.name) |
| 147 | + setattr(self, field.name, value) |
| 148 | + self.__fields__.append(field.name) |
| 149 | + |
| 150 | + def _exclude(self): |
| 151 | + exclude = [field.name for field in self.__exclude__] |
| 152 | + for (field, value) in six.iteritems(self.__model__.get_data()): |
| 153 | + if field in exclude: |
| 154 | + continue |
| 155 | + setattr(self, field, value) |
| 156 | + self.__fields__.append(field) |
| 157 | + |
| 158 | + def _fields(self): |
| 159 | + for (field, value) in six.iteritems(self.__model__.get_data()): |
| 160 | + if field in self.__fields__: |
| 161 | + continue |
| 162 | + setattr(self, field, value) |
| 163 | + self.__fields__.append(field) |
| 164 | + |
39 | 165 | def get_data(self): |
40 | 166 | """Read only dictionary fields/values of model within current context. |
41 | 167 |
|
42 | 168 | :rtype: dict |
43 | 169 | """ |
44 | | - raise NotImplementedError("Method get_data is required.") |
| 170 | + return dict((field, getattr(self, field)) for field in self.__fields__) |
0 commit comments