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

Fix: models v2 deserialization #584

Merged
merged 2 commits into from
May 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .style.yapf
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[style]
based_on_style = pep8
spaces_before_comment = 4
split_before_logical_operator = true
indent_width = 4
column_limit = 140
based_on_style=pep8
spaces_before_comment=4
split_before_logical_operator=True
indent_width=4
column_limit=140
split_arguments_when_comma_terminated=True
3 changes: 0 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
"${workspaceRoot}/node_scripts"
],
"python.formatting.provider": "yapf",
"python.formatting.yapfArgs": [
"--style=.style.yapf"
],
"python.venvPath": "${workspaceFolder}/.venv/",
"python.pythonPath": "${workspaceFolder}/.venv/Scripts/python.exe",
"python.unitTest.pyTestEnabled": true
Expand Down
10 changes: 7 additions & 3 deletions aztk/core/models/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def __setstate__(self, state):
"""
For pickle serialization. This update the current model with the given state
"""
self._update(state)
self._update(state, ignore_missing=True)

def validate(self):
"""
Expand Down Expand Up @@ -110,9 +110,13 @@ def to_dict(self):
def __str__(self):
return yaml.dump(self.to_dict(), default_flow_style=False)

def _update(self, values):
def _update(self, values, ignore_missing=False):
for k, v in values.items():
self[k] = v
try:
self[k] = v
except AztkAttributeError as e:
if not ignore_missing:
raise e

def _process_field_error(self, e: InvalidModelFieldError, field: str):
if not e.field:
Expand Down
17 changes: 9 additions & 8 deletions aztk/models/plugins/plugin_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,17 @@ class PluginPort(Model):
public = fields.Field(default=None)
name = fields.Integer()

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.expose_publicly = bool(self.public)
self.public_port = None
@property
def expose_publicly(self):
return bool(self.public)

@property
def public_port(self):
if self.expose_publicly:
if self.public is True:
self.public_port = self.internal
else:
self.public_port = self.public

return self.internal
return self.public
return None

class PluginConfiguration(Model):
"""
Expand Down
65 changes: 38 additions & 27 deletions tests/core/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@

# pylint: disable=C1801


class UserState(Enum):
Creating = "creating"
Ready = "ready"
Deleting = "deleting"


class UserInfo(Model):
name = fields.String()
age = fields.Integer()


class User(Model):
info = fields.Model(UserInfo)
enabled = fields.Boolean(default=True)
Expand All @@ -38,6 +41,7 @@ def test_models():
assert user.enabled is False
assert user.state == UserState.Creating


def test_inherited_models():
class ServiceUser(User):
service = fields.String()
Expand All @@ -58,25 +62,27 @@ class ServiceUser(User):
assert user.state == UserState.Ready
assert user.service == "bus"


def test_raise_error_if_extra_parameters():
class SimpleNameModel(Model):
name = fields.String()

with pytest.raises(AztkAttributeError, match="SimpleNameModel doesn't have an attribute called abc"):
SimpleNameModel(name="foo", abc="123")


def test_enum_invalid_type_raise_error():
class SimpleStateModel(Model):
state = fields.Enum(UserState)


with pytest.raises(
InvalidModelFieldError,
match="SimpleStateModel state unknown is not a valid option. Use one of \\['creating', 'ready', 'deleting'\\]"):
InvalidModelFieldError,
match="SimpleStateModel state unknown is not a valid option. Use one of \\['creating', 'ready', 'deleting'\\]"):

obj = SimpleStateModel(state="unknown")
obj.validate()


def test_enum_parse_string():
class SimpleStateModel(Model):
state = fields.Enum(UserState)
Expand All @@ -87,7 +93,6 @@ class SimpleStateModel(Model):
assert obj.state == UserState.Creating



def test_convert_nested_dict_to_model():
user = User(
info=dict(
Expand All @@ -103,6 +108,7 @@ def test_convert_nested_dict_to_model():
assert user.enabled is False
assert user.state == UserState.Deleting


def test_raise_error_if_missing_required_field():
class SimpleRequiredModel(Model):
name = fields.String()
Expand All @@ -112,6 +118,7 @@ class SimpleRequiredModel(Model):
with pytest.raises(InvalidModelFieldError, match="SimpleRequiredModel name is required"):
missing.validate()


def test_raise_error_if_string_field_invalid_type():
class SimpleStringModel(Model):
name = fields.String()
Expand All @@ -121,6 +128,7 @@ class SimpleStringModel(Model):
with pytest.raises(InvalidModelFieldError, match="SimpleStringModel name 123 should be a string"):
missing.validate()


def test_raise_error_if_int_field_invalid_type():
class SimpleIntegerModel(Model):
age = fields.Integer()
Expand All @@ -130,6 +138,7 @@ class SimpleIntegerModel(Model):
with pytest.raises(InvalidModelFieldError, match="SimpleIntegerModel age 123 should be an integer"):
missing.validate()


def test_raise_error_if_bool_field_invalid_type():
class SimpleBoolModel(Model):
enabled = fields.Boolean()
Expand All @@ -139,6 +148,7 @@ class SimpleBoolModel(Model):
with pytest.raises(InvalidModelFieldError, match="SimpleBoolModel enabled false should be a boolean"):
missing.validate()


def test_merge_with_default_value():
class SimpleMergeModel(Model):
name = fields.String()
Expand All @@ -164,13 +174,9 @@ class ComplexModel(Model):
info=dict(
name="John",
age=29,
)
)
obj2 = ComplexModel(
info=dict(
age=38,
)
),
)
obj2 = ComplexModel(info=dict(age=38))

assert obj1.info.age == 29
assert obj2.info.age == 38
Expand All @@ -179,6 +185,7 @@ class ComplexModel(Model):
assert obj1.info.name == "John"
assert obj1.info.age == 38


def test_merge_nested_model_override_strategy():
class ComplexModel(Model):
model_id = fields.String()
Expand All @@ -188,13 +195,9 @@ class ComplexModel(Model):
info=dict(
name="John",
age=29,
)
)
obj2 = ComplexModel(
info=dict(
age=38,
)
),
)
obj2 = ComplexModel(info=dict(age=38))

assert obj1.info.age == 29
assert obj2.info.age == 38
Expand All @@ -203,6 +206,7 @@ class ComplexModel(Model):
assert obj1.info.name is None
assert obj1.info.age == 38


def test_list_field_convert_model_correctly():
class UserList(Model):
infos = fields.List(UserInfo)
Expand All @@ -212,8 +216,8 @@ class UserList(Model):
dict(
name="John",
age=29,
)
]
),
],
)
obj.validate()

Expand All @@ -222,34 +226,37 @@ class UserList(Model):
assert obj.infos[0].name == "John"
assert obj.infos[0].age == 29


def test_list_field_is_never_required():
class UserList(Model):
infos = fields.List(UserInfo)

obj = UserList()
obj.validate()

assert isinstance(obj.infos, (list,))
assert isinstance(obj.infos, (list, ))
assert len(obj.infos) == 0

infos = obj.infos
infos.append(UserInfo())
assert len(obj.infos) == 1

obj2 = UserList(infos=None)
assert isinstance(obj2.infos, (list,))
assert isinstance(obj2.infos, (list, ))
assert len(obj2.infos) == 0


def test_list_field_ignore_none_entries():
class UserList(Model):
infos = fields.List(UserInfo)

obj = UserList(infos=[None, None])
obj.validate()

assert isinstance(obj.infos, (list,))
assert isinstance(obj.infos, (list, ))
assert len(obj.infos) == 0


def test_merge_nested_model_append_strategy():
class UserList(Model):
infos = fields.List(UserInfo, merge_strategy=ListMergeStrategy.Append)
Expand All @@ -259,14 +266,17 @@ class UserList(Model):
dict(
name="John",
age=29,
)
]
),
],
)

obj2 = UserList(
infos=[dict(
name="Frank",
age=38,
)]
infos=[
dict(
name="Frank",
age=38,
),
],
)

assert len(obj1.infos) == 1
Expand Down Expand Up @@ -296,6 +306,7 @@ def test_serialize_simple_model_to_yaml():
assert info_parsed.name == "John"
assert info_parsed.age == 29


def test_serialize_nested_model_to_yaml():
user = User(
info=dict(name="John", age=29),
Expand Down