Skip to content
This repository was archived by the owner on Feb 3, 2021. It is now read-only.

Commit 02f336b

Browse files
Feature: New Models design with auto validation, default and merging (#543)
1 parent f6735cc commit 02f336b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1359
-733
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,6 @@ tmp/
4949

5050
# Built docs
5151
docs/_build/
52+
53+
# PyCharm
54+
.idea/

.style.yapf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ based_on_style = pep8
33
spaces_before_comment = 4
44
split_before_logical_operator = true
55
indent_width = 4
6-
column_limit = 120
6+
column_limit = 140

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@
1616
"--style=.style.yapf"
1717
],
1818
"python.venvPath": "${workspaceFolder}/.venv/",
19-
"python.pythonPath": "${workspaceFolder}/.venv/Scripts/python.exe"
19+
"python.pythonPath": "${workspaceFolder}/.venv/Scripts/python.exe",
20+
"python.unitTest.pyTestEnabled": true
2021
}

aztk/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def __create_pool_and_job(self, cluster_conf: models.ClusterConfiguration, softw
9090
network_conf = batch_models.NetworkConfiguration(
9191
subnet_id=cluster_conf.subnet_id)
9292
auto_scale_formula = "$TargetDedicatedNodes={0}; $TargetLowPriorityNodes={1}".format(
93-
cluster_conf.vm_count, cluster_conf.vm_low_pri_count)
93+
cluster_conf.size, cluster_conf.size_low_priority)
9494

9595
# Confiure the pool
9696
pool = batch_models.PoolAddParameter(
@@ -110,7 +110,7 @@ def __create_pool_and_job(self, cluster_conf: models.ClusterConfiguration, softw
110110
batch_models.MetadataItem(
111111
name=constants.AZTK_SOFTWARE_METADATA_KEY, value=software_metadata_key),
112112
batch_models.MetadataItem(
113-
name=constants.AZTK_MODE_METADATA_KEY, value=constants.AZTK_CLUSTER_MODE_METADATA)
113+
name=constants.AZTK_MODE_METADATA_KEY, value=constants.AZTK_CLUSTER_MODE_METADATA)
114114
])
115115

116116
# Create the pool + create user for the pool

aztk/core/__init__.py

Whitespace-only changes.

aztk/core/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .model import Model
2+
from .fields import String, Integer, Boolean, Float, List, ModelMergeStrategy, ListMergeStrategy

aztk/core/models/fields.py

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import collections
2+
import enum
3+
4+
from aztk.error import InvalidModelFieldError
5+
from . import validators as aztk_validators
6+
7+
class ModelMergeStrategy(enum.Enum):
8+
Override = 1
9+
"""
10+
Override the value with the other value
11+
"""
12+
Merge = 2
13+
"""
14+
Try to merge value nested
15+
"""
16+
17+
class ListMergeStrategy(enum.Enum):
18+
Replace = 1
19+
"""
20+
Override the value with the other value
21+
"""
22+
Append = 2
23+
"""
24+
Append all the values of the new list
25+
"""
26+
27+
# pylint: disable=W0212
28+
class Field:
29+
"""
30+
Base class for all model fields
31+
"""
32+
def __init__(self, *validators, **kwargs):
33+
self.default = kwargs.get('default')
34+
self.required = 'default' not in kwargs
35+
self.validators = []
36+
37+
if self.required:
38+
self.validators.append(aztk_validators.Required())
39+
40+
self.validators.extend(validators)
41+
42+
choices = kwargs.get('choices')
43+
if choices:
44+
self.validators.append(aztk_validators.In(choices))
45+
46+
def validate(self, value):
47+
for validator in self.validators:
48+
validator(value)
49+
50+
def __get__(self, instance, owner):
51+
if instance is not None:
52+
value = instance._data.get(self)
53+
if value is None:
54+
return instance._defaults.setdefault(self, self._default(instance))
55+
return value
56+
57+
return self
58+
59+
def __set__(self, instance, value):
60+
instance._data[self] = value
61+
62+
def merge(self, instance, value):
63+
"""
64+
Method called when merging 2 model together.
65+
This is overriden in some of the fields where merge can be handled differently
66+
"""
67+
if value is not None:
68+
instance._data[self] = value
69+
70+
def serialize(self, instance):
71+
return self.__get__(instance, None)
72+
73+
def _default(self, model):
74+
if callable(self.default):
75+
return self.__call_default(model)
76+
77+
return self.default
78+
79+
def __call_default(self, *args):
80+
try:
81+
return self.default()
82+
except TypeError as error:
83+
try:
84+
return self.default(*args)
85+
except TypeError:
86+
raise error
87+
88+
89+
class String(Field):
90+
"""
91+
Model String field
92+
"""
93+
94+
def __init__(self, *args, **kwargs):
95+
super().__init__(aztk_validators.String(), *args, **kwargs)
96+
97+
98+
class Integer(Field):
99+
"""
100+
Model Integer field
101+
"""
102+
def __init__(self, *args, **kwargs):
103+
super().__init__(aztk_validators.Integer(), *args, **kwargs)
104+
105+
106+
class Float(Field):
107+
"""
108+
Model Float field
109+
"""
110+
111+
def __init__(self, *args, **kwargs):
112+
super().__init__(aztk_validators.Float(), *args, **kwargs)
113+
114+
115+
class Boolean(Field):
116+
"""
117+
Model Boolean field
118+
"""
119+
120+
def __init__(self, *args, **kwargs):
121+
super().__init__(aztk_validators.Boolean(), *args, **kwargs)
122+
123+
124+
class List(Field):
125+
"""
126+
Field that should be a list
127+
"""
128+
129+
def __init__(self, model=None, **kwargs):
130+
self.model = model
131+
kwargs.setdefault('default', list)
132+
self.merge_strategy = kwargs.get('merge_strategy', ListMergeStrategy.Append)
133+
self.skip_none = kwargs.get('skip_none', True)
134+
135+
super().__init__(
136+
aztk_validators.List(*kwargs.get('inner_validators', [])), **kwargs)
137+
138+
def __set__(self, instance, value):
139+
if isinstance(value, collections.MutableSequence):
140+
value = self._resolve(value)
141+
if value is None:
142+
value = []
143+
super().__set__(instance, value)
144+
145+
def _resolve(self, value):
146+
result = []
147+
for item in value:
148+
if item is None and self.skip_none: # Skip none values
149+
continue
150+
151+
if self.model and isinstance(item, collections.MutableMapping):
152+
item = self.model(**item)
153+
result.append(item)
154+
return result
155+
156+
def merge(self, instance, value):
157+
if value is None:
158+
value = []
159+
160+
if self.merge_strategy == ListMergeStrategy.Append:
161+
current = instance._data.get(self)
162+
if current is None:
163+
current = []
164+
value = current + value
165+
166+
instance._data[self] = value
167+
168+
def serialize(self, instance):
169+
items = super().serialize(instance)
170+
output = []
171+
if items is not None:
172+
for item in items:
173+
if hasattr(item, 'to_dict'):
174+
output.append(item.to_dict())
175+
else:
176+
output.append(item)
177+
return output
178+
179+
class Model(Field):
180+
"""
181+
Field is another model
182+
183+
Args:
184+
model (aztk.core.models.Model): Model object that field should be
185+
merge_strategy (ModelMergeStrategy): When merging models how should the nested model be merged.
186+
Default: `ModelMergeStrategy.merge`
187+
"""
188+
189+
def __init__(self, model, *args, **kwargs):
190+
super().__init__(aztk_validators.Model(model), *args, **kwargs)
191+
192+
self.model = model
193+
self.merge_strategy = kwargs.get('merge_strategy', ModelMergeStrategy.Merge)
194+
195+
def __set__(self, instance, value):
196+
if isinstance(value, collections.MutableMapping):
197+
value = self.model(**value)
198+
199+
super().__set__(instance, value)
200+
201+
def merge(self, instance, value):
202+
if self.merge_strategy == ModelMergeStrategy.Merge:
203+
current = instance._data.get(self)
204+
if current is not None:
205+
current.merge(value)
206+
value = current
207+
208+
instance._data[self] = value
209+
210+
def serialize(self, instance):
211+
val = super().serialize(instance)
212+
if val is not None:
213+
return val.to_dict()
214+
else:
215+
return None
216+
217+
class Enum(Field):
218+
"""
219+
Field that should be an enum
220+
"""
221+
def __init__(self, model, *args, **kwargs):
222+
super().__init__(aztk_validators.InstanceOf(model), *args, **kwargs)
223+
224+
self.model = model
225+
226+
def __set__(self, instance, value):
227+
if value is not None and not isinstance(value, self.model):
228+
try:
229+
value = self.model(value)
230+
except ValueError:
231+
available = [e.value for e in self.model]
232+
raise InvalidModelFieldError("{0} is not a valid option. Use one of {1}".format(value, available))
233+
super().__set__(instance, value)
234+
235+
236+
def serialize(self, instance):
237+
val = super().serialize(instance)
238+
if val is not None:
239+
return val.value
240+
else:
241+
return None

0 commit comments

Comments
 (0)