You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ariatosca.apache.org by mx...@apache.org on 2017/01/30 16:32:54 UTC
incubator-ariatosca git commit:
ARIA-66-Convert-custom-parser-fields-into-sqla-based-fields [Forced Update!]
Repository: incubator-ariatosca
Updated Branches:
refs/heads/ARIA-66-Convert-custom-parser-fields-into-sqla-based-fields 49f21eaf8 -> 2165d7582 (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/2165d758
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/2165d758
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/2165d758
Branch: refs/heads/ARIA-66-Convert-custom-parser-fields-into-sqla-based-fields
Commit: 2165d75821d6353243a3f4cdb520d6a7b7183aab
Parents: 4447829
Author: mxmrlv <mx...@gmail.com>
Authored: Sun Jan 15 19:07:29 2017 +0200
Committer: mxmrlv <mx...@gmail.com>
Committed: Mon Jan 30 18:32:39 2017 +0200
----------------------------------------------------------------------
aria/storage/type.py | 156 ++++++++++++++++-
tests/storage/__init__.py | 30 +++-
tests/storage/test_model_storage.py | 195 +---------------------
tests/storage/test_structures.py | 277 +++++++++++++++++++++++++++++++
4 files changed, 455 insertions(+), 203 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/2165d758/aria/storage/type.py
----------------------------------------------------------------------
diff --git a/aria/storage/type.py b/aria/storage/type.py
index dd2a5ce..8fb02c6 100644
--- a/aria/storage/type.py
+++ b/aria/storage/type.py
@@ -12,17 +12,18 @@
# 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
+from aria.storage import exceptions
class _MutableType(TypeDecorator):
@@ -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,16 +125,115 @@ class _MutableDict(mutable.MutableDict):
raise exceptions.StorageError('SQL Storage error: {0}'.format(str(e)))
+class _StrictListMixin(object):
+ ANY_TYPE = 'any_type'
+
+ _item_cls = ANY_TYPE
+
+ @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
def coerce(cls, key, value):
"Convert plain dictionaries to MutableDict."
+
try:
- return mutable.MutableList.coerce(key, value)
+ if not isinstance(value, cls):
+ if isinstance(value, list):
+ return cls(value)
+
+ return mutable.Mutable.coerce(key, value)
+ else:
+ return value
+
except ValueError as e:
raise exceptions.StorageError('SQL Storage error: {0}'.format(str(e)))
+StrictDictID = namedtuple('strict_dict_id', 'key_cls, value_cls')
+
+
+class _StrictDict(object):
+ _strict_map = {}
+
+ def __call__(self, key_cls=None, value_cls=None, *args, **kwargs):
+ strict_dict_map_key = StrictDictID(key_cls=key_cls, value_cls=value_cls)
+ if strict_dict_map_key not in self._strict_map:
+ strict_dict_cls = type(
+ 'StrictDict_{0}_{1}'.format(key_cls.__name__, value_cls.__name__),
+ (Dict, ),
+ {}
+ )
+ type(
+ 'StrictMutableDict_{0}_{1}'.format(key_cls.__name__, value_cls.__name__),
+ (_StrictDictMixin, _MutableDict),
+ {'_key_cls': key_cls, '_value_cls': value_cls}
+ ).associate_with(strict_dict_cls)
+ self._strict_map[strict_dict_map_key] = strict_dict_cls
+
+ return self._strict_map[strict_dict_map_key]
+
+StrictDict = _StrictDict()
+
+
+class _StrictList(object):
+ _strict_map = {}
+
+ def __call__(self, item_cls=None):
+ if item_cls not in self._strict_map:
+ strict_list_cls = type(
+ 'StrictList_{0}'.format(item_cls.__name__),
+ (List, ),
+ {}
+ )
+ type(
+ 'StrictMutableList_{0}'.format(item_cls.__name__),
+ (_StrictListMixin, _MutableList),
+ {'_item_cls': item_cls}
+ ).associate_with(strict_list_cls)
+ self._strict_map[item_cls] = strict_list_cls
+
+ return self._strict_map[item_cls]
+
+StrictList = _StrictList()
def _mutable_association_listener(mapper, cls):
for prop in mapper.column_attrs:
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/2165d758/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/2165d758/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/2165d758/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..7e34b17
--- /dev/null
+++ b/tests/storage/test_structures.py
@@ -0,0 +1,277 @@
+# 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(sd, after_assignment=False):
+ if after_assignment:
+ with pytest.raises(exceptions.StorageError):
+ sd.strict_dict['key'] = None
+ with pytest.raises(exceptions.StorageError):
+ sd.strict_dict[None] = 'value'
+ with pytest.raises(exceptions.StorageError):
+ sd.strict_dict[None] = None
+
+ with pytest.raises(exceptions.StorageError):
+ sd.strict_dict = {'key': None}
+
+ with pytest.raises(exceptions.StorageError):
+ sd.strict_dict = {None: 'value'}
+
+ with pytest.raises(exceptions.StorageError):
+ sd.strict_dict = {None: None}
+
+ assert_strict(strict_class)
+ strict_class.strict_dict = {'key': 'value'}
+ assert strict_class.strict_dict == {'key': 'value'}
+ assert_strict(strict_class, after_assignment=True)
+
+
+def test_strict_list():
+ strict_class = StrictClass()
+
+ def assert_strict(sd, after_assignment=False):
+ if after_assignment:
+ with pytest.raises(exceptions.StorageError):
+ sd.strict_list[0] = None
+
+ with pytest.raises(exceptions.StorageError):
+ sd.strict_list = [None]
+
+ assert_strict(strict_class)
+ strict_class.strict_list = ['item']
+ assert strict_class.strict_list == ['item']
+ assert_strict(strict_class, after_assignment=True)