Repository: incubator-ariatosca Updated Branches: refs/heads/ARIA-44-Merge-parser-and-storage-models 0e8bac195 -> 555567930 (forced update)
ARIA-66-Convert-custom-parser-fields-into-sqla-based-fields Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/9e62fcac Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/9e62fcac Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/9e62fcac Branch: refs/heads/ARIA-44-Merge-parser-and-storage-models Commit: 9e62fcacbff3313b840beaf961c15d96c2881488 Parents: 4447829 Author: mxmrlv <[email protected]> Authored: Sun Jan 15 19:07:29 2017 +0200 Committer: mxmrlv <[email protected]> Committed: Tue Jan 31 14:11:27 2017 +0200 ---------------------------------------------------------------------- aria/storage/type.py | 185 ++++++++++++++++++- tests/storage/__init__.py | 30 +++- tests/storage/test_instrumentation.py | 53 +++++- tests/storage/test_model_storage.py | 195 +------------------- tests/storage/test_structures.py | 275 +++++++++++++++++++++++++++++ 5 files changed, 533 insertions(+), 205 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/9e62fcac/aria/storage/type.py ---------------------------------------------------------------------- diff --git a/aria/storage/type.py b/aria/storage/type.py index dd2a5ce..ac695b1 100644 --- a/aria/storage/type.py +++ b/aria/storage/type.py @@ -12,14 +12,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import json +from collections import namedtuple from sqlalchemy import ( TypeDecorator, VARCHAR, event ) - from sqlalchemy.ext import mutable from . import exceptions @@ -61,10 +62,60 @@ class List(_MutableType): return list +class _StrictDictMixin(object): + + @classmethod + def coerce(cls, key, value): + "Convert plain dictionaries to MutableDict." + try: + if not isinstance(value, cls): + if isinstance(value, dict): + for k, v in value.items(): + cls._assert_strict_key(k) + cls._assert_strict_value(v) + return cls(value) + return mutable.MutableDict.coerce(key, value) + else: + return value + except ValueError as e: + raise exceptions.StorageError('SQL Storage error: {0}'.format(str(e))) + + def __setitem__(self, key, value): + self._assert_strict_key(key) + self._assert_strict_value(value) + super(_StrictDictMixin, self).__setitem__(key, value) + + def setdefault(self, key, value): + self._assert_strict_key(key) + self._assert_strict_value(value) + super(_StrictDictMixin, self).setdefault(key, value) + + def update(self, *args, **kwargs): + for k, v in kwargs.items(): + self._assert_strict_key(k) + self._assert_strict_value(v) + super(_StrictDictMixin, self).update(*args, **kwargs) + + @classmethod + def _assert_strict_key(cls, key): + if cls._key_cls is not None and not isinstance(key, cls._key_cls): + raise exceptions.StorageError("Key type was set strictly to {0}, but was {1}".format( + cls._key_cls, type(key) + )) + + @classmethod + def _assert_strict_value(cls, value): + if cls._value_cls is not None and not isinstance(value, cls._value_cls): + raise exceptions.StorageError("Value type was set strictly to {0}, but was {1}".format( + cls._value_cls, type(value) + )) + + class _MutableDict(mutable.MutableDict): """ Enables tracking for dict values. """ + @classmethod def coerce(cls, key, value): "Convert plain dictionaries to MutableDict." @@ -74,6 +125,48 @@ class _MutableDict(mutable.MutableDict): raise exceptions.StorageError('SQL Storage error: {0}'.format(str(e))) +class _StrictListMixin(object): + + @classmethod + def coerce(cls, key, value): + "Convert plain dictionaries to MutableDict." + try: + if not isinstance(value, cls): + if isinstance(value, list): + for item in value: + cls._assert_item(item) + return cls(value) + return mutable.MutableList.coerce(key, value) + else: + return value + except ValueError as e: + raise exceptions.StorageError('SQL Storage error: {0}'.format(str(e))) + + def __setitem__(self, index, value): + """Detect list set events and emit change events.""" + self._assert_item(value) + super(_StrictListMixin, self).__setitem__(index, value) + + def append(self, item): + self._assert_item(item) + super(_StrictListMixin, self).append(item) + + def extend(self, item): + self._assert_item(item) + super(_StrictListMixin, self).extend(item) + + def insert(self, index, item): + self._assert_item(item) + super(_StrictListMixin, self).insert(index, item) + + @classmethod + def _assert_item(cls, item): + if cls._item_cls is not None and not isinstance(item, cls._item_cls): + raise exceptions.StorageError("Key type was set strictly to {0}, but was {1}".format( + cls._item_cls, type(item) + )) + + class _MutableList(mutable.MutableList): @classmethod @@ -84,13 +177,99 @@ class _MutableList(mutable.MutableList): except ValueError as e: raise exceptions.StorageError('SQL Storage error: {0}'.format(str(e))) +_StrictDictID = namedtuple('_StrictDictID', 'key_cls, value_cls') +_StrictValue = namedtuple('_StrictValue', 'type_cls, listener_cls') + + +class _StrictDict(object): + """ + This entire class functions as a factory for strict dicts and their listeners. + No type class, and no listener type class is created more than once. If a relevant type class + exists it is returned. + """ + _strict_map = {} + + def __call__(self, key_cls=None, value_cls=None): + strict_dict_map_key = _StrictDictID(key_cls=key_cls, value_cls=value_cls) + if strict_dict_map_key not in self._strict_map: + # Creating the type class itself. this class would be returned (used by the sqlalchemy + # Column). + strict_dict_cls = type( + 'StrictDict_{0}_{1}'.format(key_cls.__name__, value_cls.__name__), + (Dict, ), + {} + ) + # Creating the type listening class. + # The new class inherits from both the _MutableDict class and the _StrictDictMixin, + # while setting the necessary _key_cls and _value_cls as class attributes. + listener_cls = type( + 'StrictMutableDict_{0}_{1}'.format(key_cls.__name__, value_cls.__name__), + (_StrictDictMixin, _MutableDict), + {'_key_cls': key_cls, '_value_cls': value_cls} + ) + self._strict_map[strict_dict_map_key] = _StrictValue(type_cls=strict_dict_cls, + listener_cls=listener_cls) + + return self._strict_map[strict_dict_map_key].type_cls + +StrictDict = _StrictDict() + + +class _StrictList(object): + """ + This entire class functions as a factory for strict lists and their listeners. + No type class, and no listener type class is created more than once. If a relevant type class + exists it is returned. + """ + _strict_map = {} + + def __call__(self, item_cls=None): + + if item_cls not in self._strict_map: + # Creating the type class itself. this class would be returned (used by the sqlalchemy + # Column). + strict_list_cls = type( + 'StrictList_{0}'.format(item_cls.__name__), + (List, ), + {} + ) + # Creating the type listening class. + # The new class inherits from both the _MutableList class and the _StrictListMixin, + # while setting the necessary _item_cls as class attribute. + listener_cls = type( + 'StrictMutableList_{0}'.format(item_cls.__name__), + (_StrictListMixin, _MutableList), + {'_item_cls': item_cls} + ) + self._strict_map[item_cls] = _StrictValue(type_cls=strict_list_cls, + listener_cls=listener_cls) + + return self._strict_map[item_cls].type_cls + +StrictList = _StrictList() + def _mutable_association_listener(mapper, cls): + strict_dict_type_to_listener = \ + dict((v.type_cls, v.listener_cls) for v in _StrictDict._strict_map.values()) + + strict_list_type_to_listener = \ + dict((v.type_cls, v.listener_cls) for v in _StrictList._strict_map.values()) + for prop in mapper.column_attrs: column_type = prop.columns[0].type - if isinstance(column_type, Dict): + # Dict Listeners + if type(column_type) in strict_dict_type_to_listener: # pylint: disable=unidiomatic-typecheck + strict_dict_type_to_listener[type(column_type)].associate_with_attribute( + getattr(cls, prop.key)) + elif isinstance(column_type, Dict): _MutableDict.associate_with_attribute(getattr(cls, prop.key)) - if isinstance(column_type, List): + + # List Listeners + if type(column_type) in strict_list_type_to_listener: # pylint: disable=unidiomatic-typecheck + strict_list_type_to_listener[type(column_type)].associate_with_attribute( + getattr(cls, prop.key)) + elif isinstance(column_type, List): _MutableList.associate_with_attribute(getattr(cls, prop.key)) _LISTENER_ARGS = (mutable.mapper, 'mapper_configured', _mutable_association_listener) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/9e62fcac/tests/storage/__init__.py ---------------------------------------------------------------------- diff --git a/tests/storage/__init__.py b/tests/storage/__init__.py index 9101fd0..3b3715e 100644 --- a/tests/storage/__init__.py +++ b/tests/storage/__init__.py @@ -17,12 +17,30 @@ import platform from tempfile import mkdtemp from shutil import rmtree -from aria.storage import model from sqlalchemy import ( create_engine, - orm) -from sqlalchemy.orm import scoped_session -from sqlalchemy.pool import StaticPool + orm, + Column, + Text, + Integer, + pool +) + + +from aria.storage import ( + model, + structure, + type as aria_type, +) + + +class MockModel(model.DeclarativeBase, structure.ModelMixin): #pylint: disable=abstract-method + __tablename__ = 'mock_models' + model_dict = Column(aria_type.Dict) + model_list = Column(aria_type.List) + value = Column(Integer) + name = Column(Text) + class TestFileSystem(object): @@ -53,11 +71,11 @@ def get_sqlite_api_kwargs(base_dir=None, filename='db.sqlite'): else: uri = 'sqlite:///:memory:' engine_kwargs = dict(connect_args={'check_same_thread': False}, - poolclass=StaticPool) + poolclass=pool.StaticPool) engine = create_engine(uri, **engine_kwargs) session_factory = orm.sessionmaker(bind=engine) - session = scoped_session(session_factory=session_factory) if base_dir else session_factory() + session = orm.scoped_session(session_factory=session_factory) if base_dir else session_factory() model.DeclarativeBase.metadata.create_all(bind=engine) return dict(engine=engine, session=session) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/9e62fcac/tests/storage/test_instrumentation.py ---------------------------------------------------------------------- diff --git a/tests/storage/test_instrumentation.py b/tests/storage/test_instrumentation.py index 8b826e9..9b4da4f 100644 --- a/tests/storage/test_instrumentation.py +++ b/tests/storage/test_instrumentation.py @@ -22,7 +22,8 @@ from aria.storage import ( type as aria_type, ModelStorage, sql_mapi, - instrumentation + instrumentation, + exceptions ) from ..storage import get_sqlite_api_kwargs, release_sqlite_storage @@ -275,6 +276,47 @@ class TestInstrumentation(object): instruments_holder.append(instrument) return instrument + def test_track_changes_to_strict_dict(self, storage): + model_kwargs = dict(strict_dict={'key': 'value'}, + strict_list=['item']) + mode_instance = StrictMockModel(**model_kwargs) + storage.strict_mock_model.put(mode_instance) + + instrument = self._track_changes({ + StrictMockModel.strict_dict: dict, + StrictMockModel.strict_list: list, + }) + + assert not instrument.tracked_changes + + storage_model_instance = storage.strict_mock_model.get(mode_instance.id) + + with pytest.raises(exceptions.StorageError): + storage_model_instance.strict_dict = {1: 1} + + with pytest.raises(exceptions.StorageError): + storage_model_instance.strict_dict = {'hello': 1} + + with pytest.raises(exceptions.StorageError): + storage_model_instance.strict_dict = {1: 'hello'} + + storage_model_instance.strict_dict = {'hello': 'world'} + assert storage_model_instance.strict_dict == {'hello': 'world'} + + with pytest.raises(exceptions.StorageError): + storage_model_instance.strict_list = [1] + storage_model_instance.strict_list = ['hello'] + assert storage_model_instance.strict_list == ['hello'] + + assert instrument.tracked_changes == { + 'strict_mock_model': { + mode_instance.id: { + 'strict_dict': Value(STUB, {'hello': 'world'}), + 'strict_list': Value(STUB, ['hello']), + } + }, + } + @pytest.fixture(autouse=True) def restore_instrumentation(): @@ -289,7 +331,7 @@ def storage(): result = ModelStorage( api_cls=sql_mapi.SQLAlchemyModelAPI, api_kwargs=get_sqlite_api_kwargs(), - items=(MockModel1, MockModel2)) + items=(MockModel1, MockModel2, StrictMockModel)) yield result release_sqlite_storage(result) @@ -311,3 +353,10 @@ class MockModel1(model.DeclarativeBase, _MockModel): class MockModel2(model.DeclarativeBase, _MockModel): __tablename__ = 'mock_model2' + + +class StrictMockModel(model.DeclarativeBase): + __tablename__ = 'strict_mock_model' + + strict_dict = Column(aria_type.StrictDict(basestring, basestring)) + strict_list = Column(aria_type.StrictList(basestring)) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/9e62fcac/tests/storage/test_model_storage.py ---------------------------------------------------------------------- diff --git a/tests/storage/test_model_storage.py b/tests/storage/test_model_storage.py index 0e8d1a0..d1596e3 100644 --- a/tests/storage/test_model_storage.py +++ b/tests/storage/test_model_storage.py @@ -15,31 +15,16 @@ import pytest -from sqlalchemy import Column, Text, Integer - from aria.storage import ( ModelStorage, model, exceptions, sql_mapi, - structure, - type as aria_type, ) from aria import application_model_storage from ..storage import get_sqlite_api_kwargs, release_sqlite_storage -from ..mock import ( - context as mock_context, - models, - operations -) - -class MockModel(model.DeclarativeBase, structure.ModelMixin): #pylint: disable=abstract-method - __tablename__ = 'mock_models' - model_dict = Column(aria_type.Dict) - model_list = Column(aria_type.List) - value = Column(Integer) - name = Column(Text) +from . import MockModel @pytest.fixture @@ -50,11 +35,6 @@ def storage(): release_sqlite_storage(base_storage) [email protected] -def context(): - return mock_context.simple(get_sqlite_api_kwargs()) - - @pytest.fixture(scope='module', autouse=True) def module_cleanup(): model.DeclarativeBase.metadata.remove(MockModel.__table__) #pylint: disable=no-member @@ -79,65 +59,6 @@ def test_model_storage(storage): storage.mock_model.get(mock_model.id) -def test_inner_dict_update(storage): - inner_dict = {'inner_value': 1} - - mock_model = MockModel(model_dict={'inner_dict': inner_dict, 'value': 0}) - storage.mock_model.put(mock_model) - - storage_mm = storage.mock_model.get(mock_model.id) - assert storage_mm == mock_model - - storage_mm.model_dict['inner_dict']['inner_value'] = 2 - storage_mm.model_dict['value'] = -1 - storage.mock_model.update(storage_mm) - storage_mm = storage.mock_model.get(storage_mm.id) - - assert storage_mm.model_dict['inner_dict']['inner_value'] == 2 - assert storage_mm.model_dict['value'] == -1 - - -def test_inner_list_update(storage): - mock_model = MockModel(model_list=[0, [1]]) - storage.mock_model.put(mock_model) - - storage_mm = storage.mock_model.get(mock_model.id) - assert storage_mm == mock_model - - storage_mm.model_list[1][0] = 'new_inner_value' - storage_mm.model_list[0] = 'new_value' - storage.mock_model.update(storage_mm) - storage_mm = storage.mock_model.get(storage_mm.id) - - assert storage_mm.model_list[1][0] == 'new_inner_value' - assert storage_mm.model_list[0] == 'new_value' - - -def test_model_to_dict(context): - deployment = context.deployment - deployment_dict = deployment.to_dict() - - expected_keys = [ - 'created_at', - 'description', - 'inputs', - 'groups', - 'permalink', - 'policy_triggers', - 'policy_types', - 'outputs', - 'scaling_groups', - 'updated_at', - 'workflows', - 'blueprint_name', - ] - - for expected_key in expected_keys: - assert expected_key in deployment_dict - - assert 'blueprint_fk' not in deployment_dict - - def test_application_storage_factory(): storage = application_model_storage(sql_mapi.SQLAlchemyModelAPI, api_kwargs=get_sqlite_api_kwargs()) @@ -152,117 +73,3 @@ def test_application_storage_factory(): assert storage.execution release_sqlite_storage(storage) - - -def test_relationship_model_ordering(context): - deployment = context.model.deployment.get_by_name(models.DEPLOYMENT_NAME) - source_node = context.model.node.get_by_name(models.DEPENDENT_NODE_NAME) - source_node_instance = context.model.node_instance.get_by_name( - models.DEPENDENT_NODE_INSTANCE_NAME) - target_node = context.model.node.get_by_name(models.DEPENDENCY_NODE_NAME) - target_node_instance = context.model.node_instance.get_by_name( - models.DEPENDENCY_NODE_INSTANCE_NAME) - new_node = model.Node( - name='new_node', - type='test_node_type', - type_hierarchy=[], - number_of_instances=1, - planned_number_of_instances=1, - deploy_number_of_instances=1, - properties={}, - operations=dict((key, {}) for key in operations.NODE_OPERATIONS), - min_number_of_instances=1, - max_number_of_instances=1, - deployment=deployment - ) - source_to_new_relationship = model.Relationship( - source_node=source_node, - target_node=new_node, - source_interfaces={}, - source_operations=dict((key, {}) for key in operations.RELATIONSHIP_OPERATIONS), - target_interfaces={}, - target_operations=dict((key, {}) for key in operations.RELATIONSHIP_OPERATIONS), - type='rel_type', - type_hierarchy=[], - properties={}, - ) - new_node_instance = model.NodeInstance( - name='new_node_instance', - runtime_properties={}, - version=None, - node=new_node, - state='', - scaling_groups=[] - ) - source_to_new_relationship_instance = model.RelationshipInstance( - relationship=source_to_new_relationship, - source_node_instance=source_node_instance, - target_node_instance=new_node_instance, - ) - - new_to_target_relationship = model.Relationship( - source_node=new_node, - target_node=target_node, - source_interfaces={}, - source_operations=dict((key, {}) for key in operations.RELATIONSHIP_OPERATIONS), - target_interfaces={}, - target_operations=dict((key, {}) for key in operations.RELATIONSHIP_OPERATIONS), - type='rel_type', - type_hierarchy=[], - properties={}, - ) - new_to_target_relationship_instance = model.RelationshipInstance( - relationship=new_to_target_relationship, - source_node_instance=new_node_instance, - target_node_instance=target_node_instance, - ) - - - context.model.node.put(new_node) - context.model.node_instance.put(new_node_instance) - context.model.relationship.put(source_to_new_relationship) - context.model.relationship.put(new_to_target_relationship) - context.model.relationship_instance.put(source_to_new_relationship_instance) - context.model.relationship_instance.put(new_to_target_relationship_instance) - - def flip_and_assert(node_instance, direction): - """ - Reversed the order of relationships and assert effects took place. - :param node_instance: the node instance to operatate on - :param direction: the type of relationships to flip (inbound/outbount) - :return: - """ - assert direction in ('inbound', 'outbound') - - relationships = getattr(node_instance.node, direction + '_relationships') - relationship_instances = getattr(node_instance, direction + '_relationship_instances') - assert len(relationships) == 2 - assert len(relationship_instances) == 2 - - first_rel, second_rel = relationships - first_rel_instance, second_rel_instance = relationship_instances - assert getattr(first_rel, relationships.ordering_attr) == 0 - assert getattr(second_rel, relationships.ordering_attr) == 1 - assert getattr(first_rel_instance, relationship_instances.ordering_attr) == 0 - assert getattr(second_rel_instance, relationship_instances.ordering_attr) == 1 - - reversed_relationships = list(reversed(relationships)) - reversed_relationship_instances = list(reversed(relationship_instances)) - - assert relationships != reversed_relationships - assert relationship_instances != reversed_relationship_instances - - relationships[:] = reversed_relationships - relationship_instances[:] = reversed_relationship_instances - context.model.node_instance.update(node_instance) - - assert relationships == reversed_relationships - assert relationship_instances == reversed_relationship_instances - - assert getattr(first_rel, relationships.ordering_attr) == 1 - assert getattr(second_rel, relationships.ordering_attr) == 0 - assert getattr(first_rel_instance, relationship_instances.ordering_attr) == 1 - assert getattr(second_rel_instance, relationship_instances.ordering_attr) == 0 - - flip_and_assert(source_node_instance, 'outbound') - flip_and_assert(target_node_instance, 'inbound') http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/9e62fcac/tests/storage/test_structures.py ---------------------------------------------------------------------- diff --git a/tests/storage/test_structures.py b/tests/storage/test_structures.py new file mode 100644 index 0000000..0223a98 --- /dev/null +++ b/tests/storage/test_structures.py @@ -0,0 +1,275 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +import sqlalchemy + +from aria.storage import ( + ModelStorage, + sql_mapi, + model, + type, + exceptions +) + +from ..storage import get_sqlite_api_kwargs, release_sqlite_storage, structure +from . import MockModel +from ..mock import ( + models, + operations, + context as mock_context +) + + [email protected] +def storage(): + base_storage = ModelStorage(sql_mapi.SQLAlchemyModelAPI, api_kwargs=get_sqlite_api_kwargs()) + base_storage.register(MockModel) + yield base_storage + release_sqlite_storage(base_storage) + + [email protected](scope='module', autouse=True) +def module_cleanup(): + model.DeclarativeBase.metadata.remove(MockModel.__table__) #pylint: disable=no-member + + [email protected] +def context(): + return mock_context.simple(get_sqlite_api_kwargs()) + + +def test_inner_dict_update(storage): + inner_dict = {'inner_value': 1} + + mock_model = MockModel(model_dict={'inner_dict': inner_dict, 'value': 0}) + storage.mock_model.put(mock_model) + + storage_mm = storage.mock_model.get(mock_model.id) + assert storage_mm == mock_model + + storage_mm.model_dict['inner_dict']['inner_value'] = 2 + storage_mm.model_dict['value'] = -1 + storage.mock_model.update(storage_mm) + storage_mm = storage.mock_model.get(storage_mm.id) + + assert storage_mm.model_dict['inner_dict']['inner_value'] == 2 + assert storage_mm.model_dict['value'] == -1 + + +def test_inner_list_update(storage): + mock_model = MockModel(model_list=[0, [1]]) + storage.mock_model.put(mock_model) + + storage_mm = storage.mock_model.get(mock_model.id) + assert storage_mm == mock_model + + storage_mm.model_list[1][0] = 'new_inner_value' + storage_mm.model_list[0] = 'new_value' + storage.mock_model.update(storage_mm) + storage_mm = storage.mock_model.get(storage_mm.id) + + assert storage_mm.model_list[1][0] == 'new_inner_value' + assert storage_mm.model_list[0] == 'new_value' + + +def test_model_to_dict(context): + deployment = context.deployment + deployment_dict = deployment.to_dict() + + expected_keys = [ + 'created_at', + 'description', + 'inputs', + 'groups', + 'permalink', + 'policy_triggers', + 'policy_types', + 'outputs', + 'scaling_groups', + 'updated_at', + 'workflows', + 'blueprint_name', + ] + + for expected_key in expected_keys: + assert expected_key in deployment_dict + + assert 'blueprint_fk' not in deployment_dict + + +def test_relationship_model_ordering(context): + deployment = context.model.deployment.get_by_name(models.DEPLOYMENT_NAME) + source_node = context.model.node.get_by_name(models.DEPENDENT_NODE_NAME) + source_node_instance = context.model.node_instance.get_by_name( + models.DEPENDENT_NODE_INSTANCE_NAME) + target_node = context.model.node.get_by_name(models.DEPENDENCY_NODE_NAME) + target_node_instance = context.model.node_instance.get_by_name( + models.DEPENDENCY_NODE_INSTANCE_NAME) + new_node = model.Node( + name='new_node', + type='test_node_type', + type_hierarchy=[], + number_of_instances=1, + planned_number_of_instances=1, + deploy_number_of_instances=1, + properties={}, + operations=dict((key, {}) for key in operations.NODE_OPERATIONS), + min_number_of_instances=1, + max_number_of_instances=1, + deployment=deployment + ) + source_to_new_relationship = model.Relationship( + source_node=source_node, + target_node=new_node, + source_interfaces={}, + source_operations=dict((key, {}) for key in operations.RELATIONSHIP_OPERATIONS), + target_interfaces={}, + target_operations=dict((key, {}) for key in operations.RELATIONSHIP_OPERATIONS), + type='rel_type', + type_hierarchy=[], + properties={}, + ) + new_node_instance = model.NodeInstance( + name='new_node_instance', + runtime_properties={}, + version=None, + node=new_node, + state='', + scaling_groups=[] + ) + source_to_new_relationship_instance = model.RelationshipInstance( + relationship=source_to_new_relationship, + source_node_instance=source_node_instance, + target_node_instance=new_node_instance, + ) + + new_to_target_relationship = model.Relationship( + source_node=new_node, + target_node=target_node, + source_interfaces={}, + source_operations=dict((key, {}) for key in operations.RELATIONSHIP_OPERATIONS), + target_interfaces={}, + target_operations=dict((key, {}) for key in operations.RELATIONSHIP_OPERATIONS), + type='rel_type', + type_hierarchy=[], + properties={}, + ) + new_to_target_relationship_instance = model.RelationshipInstance( + relationship=new_to_target_relationship, + source_node_instance=new_node_instance, + target_node_instance=target_node_instance, + ) + + + context.model.node.put(new_node) + context.model.node_instance.put(new_node_instance) + context.model.relationship.put(source_to_new_relationship) + context.model.relationship.put(new_to_target_relationship) + context.model.relationship_instance.put(source_to_new_relationship_instance) + context.model.relationship_instance.put(new_to_target_relationship_instance) + + def flip_and_assert(node_instance, direction): + """ + Reversed the order of relationships and assert effects took place. + :param node_instance: the node instance to operatate on + :param direction: the type of relationships to flip (inbound/outbount) + :return: + """ + assert direction in ('inbound', 'outbound') + + relationships = getattr(node_instance.node, direction + '_relationships') + relationship_instances = getattr(node_instance, direction + '_relationship_instances') + assert len(relationships) == 2 + assert len(relationship_instances) == 2 + + first_rel, second_rel = relationships + first_rel_instance, second_rel_instance = relationship_instances + assert getattr(first_rel, relationships.ordering_attr) == 0 + assert getattr(second_rel, relationships.ordering_attr) == 1 + assert getattr(first_rel_instance, relationship_instances.ordering_attr) == 0 + assert getattr(second_rel_instance, relationship_instances.ordering_attr) == 1 + + reversed_relationships = list(reversed(relationships)) + reversed_relationship_instances = list(reversed(relationship_instances)) + + assert relationships != reversed_relationships + assert relationship_instances != reversed_relationship_instances + + relationships[:] = reversed_relationships + relationship_instances[:] = reversed_relationship_instances + context.model.node_instance.update(node_instance) + + assert relationships == reversed_relationships + assert relationship_instances == reversed_relationship_instances + + assert getattr(first_rel, relationships.ordering_attr) == 1 + assert getattr(second_rel, relationships.ordering_attr) == 0 + assert getattr(first_rel_instance, relationship_instances.ordering_attr) == 1 + assert getattr(second_rel_instance, relationship_instances.ordering_attr) == 0 + + flip_and_assert(source_node_instance, 'outbound') + flip_and_assert(target_node_instance, 'inbound') + + +class StrictClass(model.DeclarativeBase, structure.ModelMixin): + __tablename__ = 'strict_class' + + strict_dict = sqlalchemy.Column(type.StrictDict(basestring, basestring)) + strict_list = sqlalchemy.Column(type.StrictList(basestring)) + + +def test_strict_dict(): + + strict_class = StrictClass() + + def assert_strict(sc): + with pytest.raises(exceptions.StorageError): + sc.strict_dict = {'key': 1} + + with pytest.raises(exceptions.StorageError): + sc.strict_dict = {1: 'value'} + + with pytest.raises(exceptions.StorageError): + sc.strict_dict = {1: 1} + + assert_strict(strict_class) + strict_class.strict_dict = {'key': 'value'} + assert strict_class.strict_dict == {'key': 'value'} + + assert_strict(strict_class) + with pytest.raises(exceptions.StorageError): + strict_class.strict_dict['key'] = 1 + with pytest.raises(exceptions.StorageError): + strict_class.strict_dict[1] = 'value' + with pytest.raises(exceptions.StorageError): + strict_class.strict_dict[1] = 1 + + +def test_strict_list(): + strict_class = StrictClass() + + def assert_strict(sc): + with pytest.raises(exceptions.StorageError): + sc.strict_list = [1] + + assert_strict(strict_class) + strict_class.strict_list = ['item'] + assert strict_class.strict_list == ['item'] + + assert_strict(strict_class) + with pytest.raises(exceptions.StorageError): + strict_class.strict_list[0] = 1
