ariatosca-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mxm...@apache.org
Subject [1/2] incubator-ariatosca git commit: ARIA-66-Convert-custom-parser-fields-into-sqla-based-fields [Forced Update!]
Date Tue, 31 Jan 2017 14:13:12 GMT
Repository: incubator-ariatosca
Updated Branches:
  refs/heads/ARIA-81-Install-execution-plugin-ctx-entry-point-by-default c90560cf2 -> 9b016543f
(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-81-Install-execution-plugin-ctx-entry-point-by-default
Commit: 9e62fcacbff3313b840beaf961c15d96c2881488
Parents: 4447829
Author: mxmrlv <mxmrlv@gmail.com>
Authored: Sun Jan 15 19:07:29 2017 +0200
Committer: mxmrlv <mxmrlv@gmail.com>
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)
 
 
-@pytest.fixture
-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
+)
+
+
+@pytest.fixture
+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)
+
+
+@pytest.fixture(scope='module', autouse=True)
+def module_cleanup():
+    model.DeclarativeBase.metadata.remove(MockModel.__table__)  #pylint: disable=no-member
+
+
+@pytest.fixture
+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


Mime
View raw message