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/05/16 18:38:24 UTC
incubator-ariatosca git commit: removed instumentation and fixed
linting
Repository: incubator-ariatosca
Updated Branches:
refs/heads/runtime_props_to_attr b47ad4295 -> 18d218542
removed instumentation and fixed linting
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/18d21854
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/18d21854
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/18d21854
Branch: refs/heads/runtime_props_to_attr
Commit: 18d2185422f38e395659fdb5b566ed215c756d8c
Parents: b47ad42
Author: max-orlov <ma...@gigaspaces.com>
Authored: Tue May 16 21:38:19 2017 +0300
Committer: max-orlov <ma...@gigaspaces.com>
Committed: Tue May 16 21:38:19 2017 +0300
----------------------------------------------------------------------
aria/modeling/types.py | 20 -
aria/orchestrator/context/common.py | 13 +-
aria/orchestrator/workflows/executor/process.py | 1 -
aria/storage/instrumentation.py | 321 ---------------
tests/orchestrator/context/test_operation.py | 28 +-
tests/storage/test_instrumentation.py | 396 -------------------
6 files changed, 26 insertions(+), 753 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/18d21854/aria/modeling/types.py
----------------------------------------------------------------------
diff --git a/aria/modeling/types.py b/aria/modeling/types.py
index 7460f47..920a0c2 100644
--- a/aria/modeling/types.py
+++ b/aria/modeling/types.py
@@ -286,24 +286,4 @@ _LISTENER_ARGS = (mutable.mapper, 'mapper_configured', _mutable_association_list
def _register_mutable_association_listener():
event.listen(*_LISTENER_ARGS)
-
-def remove_mutable_association_listener():
- """
- Remove the event listener that associates ``Dict`` and ``List`` column types with
- ``MutableDict`` and ``MutableList``, respectively.
-
- This call must happen before any model instance is instantiated.
- This is because once it does, that would trigger the listener we are trying to remove.
- Once it is triggered, many other listeners will then be registered.
- At that point, it is too late.
-
- The reason this function exists is that the association listener, interferes with ARIA change
- tracking instrumentation, so a way to disable it is required.
-
- Note that the event listener this call removes is registered by default.
- """
- if event.contains(*_LISTENER_ARGS):
- event.remove(*_LISTENER_ARGS)
-
-
_register_mutable_association_listener()
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/18d21854/aria/orchestrator/context/common.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/context/common.py b/aria/orchestrator/context/common.py
index 260ccea..9758bb5 100644
--- a/aria/orchestrator/context/common.py
+++ b/aria/orchestrator/context/common.py
@@ -333,6 +333,16 @@ class DecorateAttributes(dict):
def __init__(self, func):
super(DecorateAttributes, self).__init__()
self._func = func
+ self._attributes = None
+ self._actor = None
+
+ @property
+ def attributes(self):
+ return self._attributes
+
+ @property
+ def actor(self):
+ return self._actor
def __getattr__(self, item):
try:
@@ -343,6 +353,5 @@ class DecorateAttributes(dict):
def __call__(self, *args, **kwargs):
func_self = args[0]
self._actor = self._func(*args, **kwargs)
- self._model = func_self.model
- self.attributes = _Dict(self._actor, self._model)
+ self._attributes = _Dict(self._actor, func_self.model)
return self
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/18d21854/aria/orchestrator/workflows/executor/process.py
----------------------------------------------------------------------
diff --git a/aria/orchestrator/workflows/executor/process.py b/aria/orchestrator/workflows/executor/process.py
index f3a08ec..d15d878 100644
--- a/aria/orchestrator/workflows/executor/process.py
+++ b/aria/orchestrator/workflows/executor/process.py
@@ -49,7 +49,6 @@ from aria.utils import (
exceptions,
process as process_utils
)
-from aria.modeling import types as modeling_types
_INT_FMT = 'I'
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/18d21854/aria/storage/instrumentation.py
----------------------------------------------------------------------
diff --git a/aria/storage/instrumentation.py b/aria/storage/instrumentation.py
deleted file mode 100644
index eb5ff6c..0000000
--- a/aria/storage/instrumentation.py
+++ /dev/null
@@ -1,321 +0,0 @@
-# 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 copy
-import json
-import os
-
-import sqlalchemy.event
-
-from ..modeling import models as _models
-from ..storage.exceptions import StorageError
-
-
-_VERSION_ID_COL = 'version'
-_STUB = object()
-_Collection = type('_Collection', (object, ), {})
-
-collection = _Collection()
-_INSTRUMENTED = {
- 'modified': {
- _models.Node.state: str,
- _models.Task.status: str,
- _models.Node.attributes: collection,
- # TODO: add support for pickled type
- _models.Parameter._value: lambda x: x
- },
- 'new': (_models.Log, ),
-
-}
-
-_NEW_INSTANCE = 'NEW_INSTANCE'
-
-
-def track_changes(model=None, instrumented=None):
- """Track changes in the specified model columns
-
- This call will register event listeners using sqlalchemy's event mechanism. The listeners
- instrument all returned objects such that the attributes specified in ``instrumented``, will
- be replaced with a value that is stored in the returned instrumentation context
- ``tracked_changes`` property.
-
- Why should this be implemented when sqlalchemy already does a fantastic job at tracking changes
- you ask? Well, when sqlalchemy is used with sqlite, due to how sqlite works, only one process
- can hold a write lock to the database. This does not work well when ARIA runs tasks in
- subprocesses (by the process executor) and these tasks wish to change some state as well. These
- tasks certainly deserve a chance to do so!
-
- To enable this, the subprocess calls ``track_changes()`` before any state changes are made.
- At the end of the subprocess execution, it should return the ``tracked_changes`` attribute of
- the instrumentation context returned from this call, to the parent process. The parent process
- will then call ``apply_tracked_changes()`` that resides in this module as well.
- At that point, the changes will actually be written back to the database.
-
- :param model: the model storage. it should hold a mapi for each model. the session of each mapi
- is needed to setup events
- :param instrumented: A dict from model columns to their python native type
- :return: The instrumentation context
- """
- return _Instrumentation(model, instrumented or _INSTRUMENTED)
-
-
-class _Instrumentation(object):
-
- def __init__(self, model, instrumented):
- self.tracked_changes = {}
- self.new_instances_as_dict = {}
- self.listeners = []
- self._instances_to_expunge = []
- self._model = model
- self._track_changes(instrumented)
-
- @property
- def _new_instance_id(self):
- return '{prefix}_{index}'.format(prefix=_NEW_INSTANCE,
- index=len(self._instances_to_expunge))
-
- def expunge_session(self):
- for new_instance in self._instances_to_expunge:
- self._get_session_from_model(new_instance.__tablename__).expunge(new_instance)
-
- def _get_session_from_model(self, tablename):
- mapi = getattr(self._model, tablename, None)
- if mapi:
- return mapi._session
- raise StorageError("Could not retrieve session for {0}".format(tablename))
-
- def _track_changes(self, instrumented):
- instrumented_attribute_classes = {}
- # Track any newly created instances.
- for instrumented_class in instrumented.get('new', []):
- self._register_new_instance_listener(instrumented_class)
-
- # Track any newly-set attributes.
- for instrumented_attribute, attribute_type in instrumented.get('modified', {}).items():
- self._register_attribute_listener(instrumented_attribute=instrumented_attribute,
- attribute_type=attribute_type)
- if not isinstance(attribute_type, _Collection):
- instrumented_class = instrumented_attribute.parent.entity
- instrumented_class_attributes = instrumented_attribute_classes.setdefault(
- instrumented_class, {})
- instrumented_class_attributes[instrumented_attribute.key] = attribute_type
-
- # Track any global instance update such as 'refresh' or 'load'
- for instrumented_class, instrumented_attributes in instrumented_attribute_classes.items():
- self._register_instance_listeners(instrumented_class=instrumented_class,
- instrumented_attributes=instrumented_attributes)
-
- def _register_new_instance_listener(self, instrumented_class):
- if self._model is None:
- raise StorageError("In order to keep track of new instances, a ctx is needed")
-
- def listener(_, instance):
- if not isinstance(instance, instrumented_class):
- return
- self._instances_to_expunge.append(instance)
- tracked_instances = self.new_instances_as_dict.setdefault(instance.__modelname__, {})
- tracked_attributes = tracked_instances.setdefault(self._new_instance_id, {})
- instance_as_dict = instance.to_dict()
- instance_as_dict.update((k, getattr(instance, k))
- for k in getattr(instance, '__private_fields__', []))
- tracked_attributes.update(instance_as_dict)
- session = self._get_session_from_model(instrumented_class.__tablename__)
- listener_args = (session, 'after_attach', listener)
- sqlalchemy.event.listen(*listener_args)
- self.listeners.append(listener_args)
-
- def _register_attribute_listener(self, instrumented_attribute, attribute_type):
- # Track and newly created instances that are a part of a collection.
- if isinstance(attribute_type, _Collection):
- return self._register_append_to_attribute_listener(instrumented_attribute)
- else:
- return self._register_set_attribute_listener(instrumented_attribute, attribute_type)
-
- def _register_append_to_attribute_listener(self, collection_attr):
- def listener(target, value, initiator):
- import pydevd; pydevd.settrace('localhost', suspend=False)
- tracked_instances = self.tracked_changes.setdefault(target.__modelname__, {})
- tracked_attributes = tracked_instances.setdefault(target.id, {})
- collection_attr = tracked_attributes.setdefault(initiator.key, [])
- instance_as_dict = value.to_dict()
- instance_as_dict.update((k, getattr(value, k))
- for k in getattr(value, '__private_fields__', []))
- instance_as_dict['_MODEL_CLS'] = value.__modelname__
- collection_attr.append(instance_as_dict)
-
- listener_args = (collection_attr, 'append', listener)
- sqlalchemy.event.listen(*listener_args)
- self.listeners.append(listener_args)
-
- def _register_set_attribute_listener(self, instrumented_attribute, attribute_type):
- def listener(target, value, *_):
- import pydevd; pydevd.settrace('localhost', suspend=False)
- mapi_name = target.__modelname__
- tracked_instances = self.tracked_changes.setdefault(mapi_name, {})
- tracked_attributes = tracked_instances.setdefault(target.id, {})
- current = copy.deepcopy(attribute_type(value)) if value else None
- tracked_attributes[instrumented_attribute.key] = _Value(_STUB, current)
- return current
- listener_args = (instrumented_attribute, 'set', listener)
- sqlalchemy.event.listen(*listener_args, retval=True)
- self.listeners.append(listener_args)
-
- def _register_instance_listeners(self, instrumented_class, instrumented_attributes):
- def listener(target, *_):
- mapi_name = instrumented_class.__modelname__
- tracked_instances = self.tracked_changes.setdefault(mapi_name, {})
- tracked_attributes = tracked_instances.setdefault(target.id, {})
- if hasattr(target, _VERSION_ID_COL):
- # We want to keep track of the initial version id so it can be compared
- # with the committed version id when the tracked changes are applied
- tracked_attributes.setdefault(_VERSION_ID_COL,
- _Value(_STUB, getattr(target, _VERSION_ID_COL)))
- for attribute_name, attribute_type in instrumented_attributes.items():
- if attribute_name not in tracked_attributes:
- initial = getattr(target, attribute_name)
- if initial is None:
- current = None
- else:
- current = copy.deepcopy(attribute_type(initial))
- tracked_attributes[attribute_name] = _Value(initial, current)
- target.__dict__[attribute_name] = tracked_attributes[attribute_name].current
- for listener_args in ((instrumented_class, 'load', listener),
- (instrumented_class, 'refresh', listener),
- (instrumented_class, 'refresh_flush', listener)):
- sqlalchemy.event.listen(*listener_args)
- self.listeners.append(listener_args)
-
- def clear(self, target=None):
- if target:
- mapi_name = target.__modelname__
- tracked_instances = self.tracked_changes.setdefault(mapi_name, {})
- tracked_instances.pop(target.id, None)
- else:
- self.tracked_changes.clear()
-
- self.new_instances_as_dict.clear()
- self._instances_to_expunge = []
-
- def restore(self):
- """Remove all listeners registered by this instrumentation"""
- for listener_args in self.listeners:
- if sqlalchemy.event.contains(*listener_args):
- sqlalchemy.event.remove(*listener_args)
-
- def __enter__(self):
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- self.restore()
-
-
-class _Value(object):
- # You may wonder why is this a full blown class and not a named tuple. The reason is that
- # jsonpickle that is used to serialize the tracked_changes, does not handle named tuples very
- # well. At the very least, I could not get it to behave.
-
- def __init__(self, initial, current):
- self.initial = initial
- self.current = current
-
- def __eq__(self, other):
- if not isinstance(other, _Value):
- return False
- return self.initial == other.initial and self.current == other.current
-
- def __hash__(self):
- return hash((self.initial, self.current))
-
- @property
- def dict(self):
- return {'initial': self.initial, 'current': self.current}.copy()
-
-
-def apply_tracked_changes(tracked_changes, new_instances, model):
- """Write tracked changes back to the database using provided model storage
-
- :param tracked_changes: The ``tracked_changes`` attribute of the instrumentation context
- returned by calling ``track_changes()``
- :param model: The model storage used to actually apply the changes
- """
- successfully_updated_changes = dict()
- try:
-
- # Handle new instances
- for mapi_name, new_instance in new_instances.items():
- successfully_updated_changes[mapi_name] = dict()
- mapi = getattr(model, mapi_name)
- for tmp_id, new_instance_kwargs in new_instance.items():
- instance = mapi.model_cls(**new_instance_kwargs)
- mapi.put(instance)
- successfully_updated_changes[mapi_name][instance.id] = new_instance_kwargs
- new_instance[tmp_id] = instance
-
- # handle instance updates
- for mapi_name, tracked_instances in tracked_changes.items():
- successfully_updated_changes[mapi_name] = dict()
- mapi = getattr(model, mapi_name)
-
- for instance_id, tracked_attributes in tracked_instances.items():
- successfully_updated_changes[mapi_name][instance_id] = dict()
- instance = None
- for attribute_name, value in tracked_attributes.items():
- instance = instance or mapi.get(instance_id)
- if isinstance(value, list):
- # The changes are new item to a collection
- for item in value:
- model_name = item.pop('_MODEL_CLS')
- attr_model = getattr(model, model_name).model_cls
- new_attr = attr_model(**item)
- getattr(instance, attribute_name)[new_attr] = new_attr
- elif value.initial != value.current:
- # scalar attribute
- setattr(instance, attribute_name, value.current)
- if instance:
- _validate_version_id(instance, mapi)
- mapi.update(instance)
- # TODO: reinstate this
- # successfully_updated_changes[mapi_name][instance_id] = [
- # v.dict for v in tracked_attributes.values()]
-
- except BaseException:
- for key, value in successfully_updated_changes.items():
- if not value:
- del successfully_updated_changes[key]
- # TODO: if the successful has _STUB, the logging fails because it can't serialize the object
- model.logger.error(
- 'Registering all the changes to the storage has failed. {0}'
- 'The successful updates were: {0} '
- '{1}'.format(os.linesep, json.dumps(successfully_updated_changes, indent=4)))
-
- raise
-
-
-def _validate_version_id(instance, mapi):
- version_id = sqlalchemy.inspect(instance).committed_state.get(_VERSION_ID_COL)
- # There are two version conflict code paths:
- # 1. The instance committed state loaded already holds a newer version,
- # in this case, we manually raise the error
- # 2. The UPDATE statement is executed with version validation and sqlalchemy
- # will raise a StateDataError if there is a version mismatch.
- if version_id and getattr(instance, _VERSION_ID_COL) != version_id:
- object_version_id = getattr(instance, _VERSION_ID_COL)
- mapi._session.rollback()
- raise StorageError(
- 'Version conflict: committed and object {0} differ '
- '[committed {0}={1}, object {0}={2}]'
- .format(_VERSION_ID_COL,
- version_id,
- object_version_id))
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/18d21854/tests/orchestrator/context/test_operation.py
----------------------------------------------------------------------
diff --git a/tests/orchestrator/context/test_operation.py b/tests/orchestrator/context/test_operation.py
index ca5154c..5ce0b22 100644
--- a/tests/orchestrator/context/test_operation.py
+++ b/tests/orchestrator/context/test_operation.py
@@ -513,7 +513,7 @@ class MockModel(object):
'update': lambda *args, **kwargs: None})()
-class TestDict():
+class TestDict(object):
@pytest.fixture
def actor(self):
@@ -525,9 +525,11 @@ class TestDict():
def test_keys(self, model, actor):
dict_ = common._Dict(actor, model)
- actor.attributes.update({
- 'key1': Parameter.wrap('key1', 'value1'),
- 'key2': Parameter.wrap('key1', 'value2')}
+ actor.attributes.update(
+ {
+ 'key1': Parameter.wrap('key1', 'value1'),
+ 'key2': Parameter.wrap('key1', 'value2')
+ }
)
assert sorted(dict_.keys()) == sorted(['key1', 'key2'])
@@ -535,24 +537,24 @@ class TestDict():
dict_ = common._Dict(actor, model)
actor.attributes.update({
'key1': Parameter.wrap('key1', 'value1'),
- 'key2': Parameter.wrap('key1', 'value2')}
- )
+ 'key2': Parameter.wrap('key1', 'value2')
+ })
assert sorted(dict_.values()) == sorted(['value1', 'value2'])
def test_items(self, actor, model):
dict_ = common._Dict(actor, model)
actor.attributes.update({
'key1': Parameter.wrap('key1', 'value1'),
- 'key2': Parameter.wrap('key1', 'value2')}
- )
+ 'key2': Parameter.wrap('key1', 'value2')
+ })
assert sorted(dict_.items()) == sorted([('key1', 'value1'), ('key2', 'value2')])
def test_iter(self, actor, model):
dict_ = common._Dict(actor, model)
actor.attributes.update({
'key1': Parameter.wrap('key1', 'value1'),
- 'key2': Parameter.wrap('key1', 'value2')}
- )
+ 'key2': Parameter.wrap('key1', 'value2')
+ })
assert sorted(list(dict_)) == sorted(['key1', 'key2'])
def test_bool(self, actor, model):
@@ -560,8 +562,8 @@ class TestDict():
assert not dict_
actor.attributes.update({
'key1': Parameter.wrap('key1', 'value1'),
- 'key2': Parameter.wrap('key1', 'value2')}
- )
+ 'key2': Parameter.wrap('key1', 'value2')
+ })
assert dict_
def test_set_item(self, actor, model):
@@ -617,4 +619,4 @@ class TestDict():
dict_['key1'] = 'value1'
dict_.clear()
- assert len(dict_) == 0
\ No newline at end of file
+ assert len(dict_) == 0
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/18d21854/tests/storage/test_instrumentation.py
----------------------------------------------------------------------
diff --git a/tests/storage/test_instrumentation.py b/tests/storage/test_instrumentation.py
deleted file mode 100644
index bdbb17e..0000000
--- a/tests/storage/test_instrumentation.py
+++ /dev/null
@@ -1,396 +0,0 @@
-# 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
-from sqlalchemy import Column, Text, Integer, event
-
-from aria.modeling import (
- mixins,
- types as modeling_types,
- models
-)
-from aria.modeling.exceptions import ValueFormatException
-from aria.storage import (
- ModelStorage,
- sql_mapi,
- instrumentation
-)
-
-from . import release_sqlite_storage, init_inmemory_model_storage
-
-STUB = instrumentation._STUB
-Value = instrumentation._Value
-instruments_holder = []
-
-
-class TestInstrumentation(object):
-
- def test_track_changes(self, storage):
- model_kwargs = dict(
- name='name',
- dict1={'initial': 'value'},
- dict2={'initial': 'value'},
- list1=['initial'],
- list2=['initial'],
- int1=0,
- int2=0,
- string2='string')
- model1_instance = MockModel1(**model_kwargs)
- model2_instance = MockModel2(**model_kwargs)
- storage.mock_model_1.put(model1_instance)
- storage.mock_model_2.put(model2_instance)
-
- instrument = self._track_changes({
- MockModel1.dict1: dict,
- MockModel1.list1: list,
- MockModel1.int1: int,
- MockModel1.string2: str,
- MockModel2.dict2: dict,
- MockModel2.list2: list,
- MockModel2.int2: int,
- MockModel2.name: str
- })
-
- assert not instrument.tracked_changes
-
- storage_model1_instance = storage.mock_model_1.get(model1_instance.id)
- storage_model2_instance = storage.mock_model_2.get(model2_instance.id)
-
- storage_model1_instance.dict1 = {'hello': 'world'}
- storage_model1_instance.dict2 = {'should': 'not track'}
- storage_model1_instance.list1 = ['hello']
- storage_model1_instance.list2 = ['should not track']
- storage_model1_instance.int1 = 100
- storage_model1_instance.int2 = 20000
- storage_model1_instance.name = 'should not track'
- storage_model1_instance.string2 = 'new_string'
-
- storage_model2_instance.dict1.update({'should': 'not track'})
- storage_model2_instance.dict2.update({'hello': 'world'})
- storage_model2_instance.list1.append('should not track')
- storage_model2_instance.list2.append('hello')
- storage_model2_instance.int1 = 100
- storage_model2_instance.int2 = 20000
- storage_model2_instance.name = 'new_name'
- storage_model2_instance.string2 = 'should not track'
-
- assert instrument.tracked_changes == {
- 'mock_model_1': {
- model1_instance.id: {
- 'dict1': Value(STUB, {'hello': 'world'}),
- 'list1': Value(STUB, ['hello']),
- 'int1': Value(STUB, 100),
- 'string2': Value(STUB, 'new_string')
- }
- },
- 'mock_model_2': {
- model2_instance.id: {
- 'dict2': Value({'initial': 'value'}, {'hello': 'world', 'initial': 'value'}),
- 'list2': Value(['initial'], ['initial', 'hello']),
- 'int2': Value(STUB, 20000),
- 'name': Value(STUB, 'new_name'),
- }
- }
- }
-
- def test_attribute_initial_none_value(self, storage):
- instance1 = MockModel1(name='name1', dict1=None)
- instance2 = MockModel1(name='name2', dict1=None)
- storage.mock_model_1.put(instance1)
- storage.mock_model_1.put(instance2)
- instrument = self._track_changes({MockModel1.dict1: dict})
- instance1 = storage.mock_model_1.get(instance1.id)
- instance2 = storage.mock_model_1.get(instance2.id)
- instance1.dict1 = {'new': 'value'}
- assert instrument.tracked_changes == {
- 'mock_model_1': {
- instance1.id: {'dict1': Value(STUB, {'new': 'value'})},
- instance2.id: {'dict1': Value(None, None)},
- }
- }
-
- def test_attribute_set_none_value(self, storage):
- instance = MockModel1(name='name')
- storage.mock_model_1.put(instance)
- instrument = self._track_changes({
- MockModel1.dict1: dict,
- MockModel1.list1: list,
- MockModel1.string2: str,
- MockModel1.int1: int
- })
- instance = storage.mock_model_1.get(instance.id)
- instance.dict1 = None
- instance.list1 = None
- instance.string2 = None
- instance.int1 = None
- assert instrument.tracked_changes == {
- 'mock_model_1': {
- instance.id: {
- 'dict1': Value(STUB, None),
- 'list1': Value(STUB, None),
- 'string2': Value(STUB, None),
- 'int1': Value(STUB, None)
- }
- }
- }
-
- def test_restore(self):
- instrument = self._track_changes({MockModel1.dict1: dict})
- # set instance attribute, load instance, refresh instance and flush_refresh listeners
- assert len(instrument.listeners) == 4
- for listener_args in instrument.listeners:
- assert event.contains(*listener_args)
- instrument.restore()
- assert len(instrument.listeners) == 4
- for listener_args in instrument.listeners:
- assert not event.contains(*listener_args)
- return instrument
-
- def test_restore_twice(self):
- instrument = self.test_restore()
- instrument.restore()
-
- def test_instrumentation_context_manager(self, storage):
- instance = MockModel1(name='name')
- storage.mock_model_1.put(instance)
- with self._track_changes({MockModel1.dict1: dict}) as instrument:
- instance = storage.mock_model_1.get(instance.id)
- instance.dict1 = {'new': 'value'}
- assert instrument.tracked_changes == {
- 'mock_model_1': {instance.id: {'dict1': Value(STUB, {'new': 'value'})}}
- }
- assert len(instrument.listeners) == 4
- for listener_args in instrument.listeners:
- assert event.contains(*listener_args)
- for listener_args in instrument.listeners:
- assert not event.contains(*listener_args)
-
- def test_apply_tracked_changes(self, storage):
- initial_values = {'dict1': {'initial': 'value'}, 'list1': ['initial']}
- instance1_1 = MockModel1(name='instance1_1', **initial_values)
- instance1_2 = MockModel1(name='instance1_2', **initial_values)
- instance2_1 = MockModel2(name='instance2_1', **initial_values)
- instance2_2 = MockModel2(name='instance2_2', **initial_values)
- storage.mock_model_1.put(instance1_1)
- storage.mock_model_1.put(instance1_2)
- storage.mock_model_2.put(instance2_1)
- storage.mock_model_2.put(instance2_2)
-
- instrument = self._track_changes({
- MockModel1.dict1: dict,
- MockModel1.list1: list,
- MockModel2.dict1: dict,
- MockModel2.list1: list
- })
-
- def get_instances():
- return (storage.mock_model_1.get(instance1_1.id),
- storage.mock_model_1.get(instance1_2.id),
- storage.mock_model_2.get(instance2_1.id),
- storage.mock_model_2.get(instance2_2.id))
-
- instance1_1, instance1_2, instance2_1, instance2_2 = get_instances()
- instance1_1.dict1 = {'new': 'value'}
- instance1_2.list1 = ['new_value']
- instance2_1.dict1.update({'new': 'value'})
- instance2_2.list1.append('new_value')
-
- instrument.restore()
- storage.mock_model_1._session.expire_all()
-
- instance1_1, instance1_2, instance2_1, instance2_2 = get_instances()
- instance1_1.dict1 = {'overriding': 'value'}
- instance1_2.list1 = ['overriding_value']
- instance2_1.dict1 = {'overriding': 'value'}
- instance2_2.list1 = ['overriding_value']
- storage.mock_model_1.put(instance1_1)
- storage.mock_model_1.put(instance1_2)
- storage.mock_model_2.put(instance2_1)
- storage.mock_model_2.put(instance2_2)
- instance1_1, instance1_2, instance2_1, instance2_2 = get_instances()
- assert instance1_1.dict1 == {'overriding': 'value'}
- assert instance1_2.list1 == ['overriding_value']
- assert instance2_1.dict1 == {'overriding': 'value'}
- assert instance2_2.list1 == ['overriding_value']
-
- instrumentation.apply_tracked_changes(
- tracked_changes=instrument.tracked_changes,
- new_instances={},
- model=storage)
-
- instance1_1, instance1_2, instance2_1, instance2_2 = get_instances()
- assert instance1_1.dict1 == {'new': 'value'}
- assert instance1_2.list1 == ['new_value']
- assert instance2_1.dict1 == {'initial': 'value', 'new': 'value'}
- assert instance2_2.list1 == ['initial', 'new_value']
-
- def test_clear_instance(self, storage):
- instance1 = MockModel1(name='name1')
- instance2 = MockModel1(name='name2')
- for instance in [instance1, instance2]:
- storage.mock_model_1.put(instance)
- instrument = self._track_changes({MockModel1.dict1: dict})
- instance1.dict1 = {'new': 'value'}
- instance2.dict1 = {'new2': 'value2'}
- assert instrument.tracked_changes == {
- 'mock_model_1': {
- instance1.id: {'dict1': Value(STUB, {'new': 'value'})},
- instance2.id: {'dict1': Value(STUB, {'new2': 'value2'})}
- }
- }
- instrument.clear(instance1)
- assert instrument.tracked_changes == {
- 'mock_model_1': {
- instance2.id: {'dict1': Value(STUB, {'new2': 'value2'})}
- }
- }
-
- def test_clear_all(self, storage):
- instance1 = MockModel1(name='name1')
- instance2 = MockModel1(name='name2')
- for instance in [instance1, instance2]:
- storage.mock_model_1.put(instance)
- instrument = self._track_changes({MockModel1.dict1: dict})
- instance1.dict1 = {'new': 'value'}
- instance2.dict1 = {'new2': 'value2'}
- assert instrument.tracked_changes == {
- 'mock_model_1': {
- instance1.id: {'dict1': Value(STUB, {'new': 'value'})},
- instance2.id: {'dict1': Value(STUB, {'new2': 'value2'})}
- }
- }
- instrument.clear()
- assert instrument.tracked_changes == {}
-
- def test_new_instances(self, storage):
- model_kwargs = dict(
- name='name',
- dict1={'initial': 'value'},
- dict2={'initial': 'value'},
- list1=['initial'],
- list2=['initial'],
- int1=0,
- int2=0,
- string2='string')
- model_instance_1 = MockModel1(**model_kwargs)
- model_instance_2 = MockModel2(**model_kwargs)
-
- instrument = self._track_changes(model=storage, instrumented_new=(MockModel1,))
- assert not instrument.tracked_changes
-
- storage.mock_model_1.put(model_instance_1)
- storage.mock_model_2.put(model_instance_2)
- # Assert all models made it to storage
- assert len(storage.mock_model_1.list()) == len(storage.mock_model_2.list()) == 1
-
- # Assert only one model was tracked
- assert len(instrument.new_instances) == 1
-
- mock_model_1 = instrument.new_instances[MockModel1.__tablename__].values()[0]
- storage_model1_instance = storage.mock_model_1.get(model_instance_1.id)
-
- for key in model_kwargs:
- assert mock_model_1[key] == model_kwargs[key] == getattr(storage_model1_instance, key)
-
- def _track_changes(self, instrumented_modified=None, model=None, instrumented_new=None):
- instrument = instrumentation.track_changes(
- model=model,
- instrumented={'modified': instrumented_modified or {}, 'new': instrumented_new or {}})
- 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'])
- model_instance = StrictMockModel(**model_kwargs)
- storage.strict_mock_model.put(model_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(model_instance.id)
-
- with pytest.raises(ValueFormatException):
- storage_model_instance.strict_dict = {1: 1}
-
- with pytest.raises(ValueFormatException):
- storage_model_instance.strict_dict = {'hello': 1}
-
- with pytest.raises(ValueFormatException):
- 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(ValueFormatException):
- 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': {
- model_instance.id: {
- 'strict_dict': Value(STUB, {'hello': 'world'}),
- 'strict_list': Value(STUB, ['hello']),
- }
- },
- }
-
-
-@pytest.fixture(autouse=True)
-def restore_instrumentation():
- yield
- for instrument in instruments_holder:
- instrument.restore()
- del instruments_holder[:]
-
-
-@pytest.fixture
-def storage():
- result = ModelStorage(api_cls=sql_mapi.SQLAlchemyModelAPI,
- items=(MockModel1, MockModel2, StrictMockModel),
- initiator=init_inmemory_model_storage)
- yield result
- release_sqlite_storage(result)
-
-
-class _MockModel(mixins.ModelMixin):
- name = Column(Text)
- dict1 = Column(modeling_types.Dict)
- dict2 = Column(modeling_types.Dict)
- list1 = Column(modeling_types.List)
- list2 = Column(modeling_types.List)
- int1 = Column(Integer)
- int2 = Column(Integer)
- string2 = Column(Text)
-
-
-class MockModel1(_MockModel, models.aria_declarative_base):
- __tablename__ = 'mock_model_1'
-
-
-class MockModel2(_MockModel, models.aria_declarative_base):
- __tablename__ = 'mock_model_2'
-
-
-class StrictMockModel(mixins.ModelMixin, models.aria_declarative_base):
- __tablename__ = 'strict_mock_model'
-
- strict_dict = Column(modeling_types.StrictDict(basestring, basestring))
- strict_list = Column(modeling_types.StrictList(basestring))