Many cleanups; use dict for many models
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/0f040a2b Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/0f040a2b Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/0f040a2b Branch: refs/heads/ARIA-105-integrate-modeling Commit: 0f040a2b9a72cd7ed66b067a69df63d0bb367f0d Parents: f6da64a Author: Tal Liron <[email protected]> Authored: Tue Mar 14 18:40:55 2017 -0500 Committer: Tal Liron <[email protected]> Committed: Tue Mar 14 18:40:55 2017 -0500 ---------------------------------------------------------------------- aria/cli/commands.py | 7 +- aria/cli/dry.py | 2 +- aria/modeling/mixins.py | 398 +++++++++------ aria/modeling/orchestration.py | 97 ++-- aria/modeling/relationship.py | 421 ---------------- aria/modeling/service_changes.py | 79 +-- aria/modeling/service_common.py | 30 +- aria/modeling/service_instance.py | 493 ++++++++++--------- aria/modeling/service_template.py | 460 ++++++++--------- aria/modeling/types.py | 30 +- aria/orchestrator/workflows/api/task.py | 162 +++--- .../simple_v1_0/modeling/__init__.py | 35 +- tests/end2end/test_orchestrator.py | 7 +- tests/mock/models.py | 8 +- tests/modeling/test_models.py | 10 +- tests/orchestrator/context/test_operation.py | 10 +- tests/orchestrator/context/test_serialize.py | 2 +- tests/orchestrator/context/test_toolbelt.py | 2 +- tests/orchestrator/workflows/api/test_task.py | 14 +- .../workflows/builtin/test_execute_operation.py | 2 +- tests/orchestrator/workflows/core/test_task.py | 3 +- .../executor/test_process_executor_extension.py | 4 +- .../test_process_executor_tracked_changes.py | 4 +- tests/storage/test_resource_storage.py | 58 +-- 24 files changed, 1029 insertions(+), 1309 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0f040a2b/aria/cli/commands.py ---------------------------------------------------------------------- diff --git a/aria/cli/commands.py b/aria/cli/commands.py index f9d3053..1eef61d 100644 --- a/aria/cli/commands.py +++ b/aria/cli/commands.py @@ -235,12 +235,7 @@ class WorkflowCommand(BaseCommand): workflow_name)) inputs = {} else: - workflow = None - for policy in context.modeling.instance.policies: - if policy.name == workflow_name: - workflow = policy - break - + workflow = context.modeling.instance.policies.get(workflow_name) if workflow is None: raise AttributeError('workflow policy does not exist: "{0}"'.format(workflow_name)) if workflow.type.role != 'workflow': http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0f040a2b/aria/cli/dry.py ---------------------------------------------------------------------- diff --git a/aria/cli/dry.py b/aria/cli/dry.py index 114ab7a..82faf42 100644 --- a/aria/cli/dry.py +++ b/aria/cli/dry.py @@ -34,7 +34,7 @@ def convert_to_dry(service): for workflow in service.workflows: convert_operation_to_dry(workflow) - for node in service.nodes: + for node in service.nodes.itervalues(): for interface in node.interfaces.itervalues(): for oper in interface.operations.itervalues(): convert_operation_to_dry(oper) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0f040a2b/aria/modeling/mixins.py ---------------------------------------------------------------------- diff --git a/aria/modeling/mixins.py b/aria/modeling/mixins.py index 9133a85..06f2497 100644 --- a/aria/modeling/mixins.py +++ b/aria/modeling/mixins.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# pylint: disable=invalid-name + """ ARIA's storage.structures module Path: aria.storage.structures @@ -35,13 +37,13 @@ from sqlalchemy import ( Table, ) -from . import utils +from .utils import classproperty from ..utils import formatting class ModelMixin(object): - @utils.classproperty + @classproperty def __modelname__(cls): # pylint: disable=no-self-argument return getattr(cls, '__mapiname__', cls.__tablename__) @@ -57,9 +59,9 @@ class ModelMixin(object): """ Return a dict representation of the model - :param suppress_error: If set to True, sets `None` to attributes that - it's unable to retrieve (e.g., if a relationship wasn't established - yet, and so it's impossible to access a property through it) + :param suppress_error: If set to True, sets `None` to attributes that it's unable to + retrieve (e.g., if a relationship wasn't established yet, and so it's + impossible to access a property through it) """ res = dict() @@ -95,22 +97,64 @@ class ModelMixin(object): return fields - set(getattr(cls, '__private_fields__', [])) @classmethod - def _create_foreign_key(cls, parent_table, nullable=False): + def _iter_association_proxies(cls): + for col, value in vars(cls).items(): + if isinstance(value, associationproxy.AssociationProxy): + yield col + + def __repr__(self): + return '<{cls} id=`{id}`>'.format( + cls=self.__class__.__name__, + id=getattr(self, self.name_column_name())) + + # Model property declaration helpers + + @classmethod + def _declare_fk(cls, + other_table, + nullable=False): """ - Return a ForeignKey object. + Declare a foreign key property, which will also create a foreign key column in the table + with the name of the property. By convention the property name should end in "_fk". - :param parent_table: Parent table name - :param nullable: Should the column be allowed to remain empty + You are required to explicitly create foreign keys in order to allow for one-to-one, + one-to-many, and many-to-one relationships (but not for many-to-many relationships). If you + do not do so, SqlAlchemy will fail to create the relationship property and raise an + exception with a clear error message. + + You should normally not have to access this property directly, but instead use the + associated relationship properties. + + *This utility method should only be used during class creation.* + + :param other_table: Other table name + :type other_table: basestring + :param nullable: True to allow null values (meaning that there is no relationship) + :type nullable: bool """ + return Column(Integer, - ForeignKey('{table}.id'.format(table=parent_table), - ondelete='CASCADE'), + ForeignKey('{table}.id'.format(table=other_table), ondelete='CASCADE'), nullable=nullable) @classmethod - def _create_relationship_to_self(cls, - column_name, - relationship_kwargs=None): + def _declare_one_to_one_self(cls, + fk, + relationship_kwargs=None): + """ + Declare a one-to-one relationship property. The property value would be an instance of + the same model. + + You will need an associated foreign key to our own table. + + *This utility method should only be used during class creation.* + + :param fk: Foreign key name + :type fk: basestring + :param relationship_kwargs: Extra kwargs for SqlAlchemy `relationship` + :type relationship_kwargs: {} + """ + relationship_kwargs = relationship_kwargs or {} remote_side = '{cls}.{remote_column}'.format( @@ -121,11 +165,11 @@ class ModelMixin(object): primaryjoin = '{remote_side} == {cls}.{column}'.format( remote_side=remote_side, cls=cls.__name__, - column=column_name + column=fk ) return relationship( - cls._get_cls_by_tablename(cls.__tablename__).__name__, + cls._get_class_for_table(cls.__tablename__).__name__, primaryjoin=primaryjoin, remote_side=remote_side, post_update=True, @@ -133,100 +177,197 @@ class ModelMixin(object): ) @classmethod - def _create_one_to_many_relationship_to_self(cls, - key, - dict_key=None, - relationship_kwargs=None): + def _declare_one_to_many_self(cls, + fk, + dict_key=None, + relationship_kwargs=None): + """ + Declare a one-to-many relationship property. The property value would be a list or dict + of instances of the same model. + + You will need an associated foreign key to our own table. + + *This utility method should only be used during class creation.* + + :param fk: Foreign key name + :type fk: basestring + :param dict_key: If set the value will be a dict with this key as the dict key; otherwise + will be a list + :type dict_key: basestring + :param relationship_kwargs: Extra kwargs for SqlAlchemy `relationship` + :type relationship_kwargs: {} + """ + relationship_kwargs = relationship_kwargs or {} relationship_kwargs.setdefault('remote_side', '{cls}.{remote_column}'.format( cls=cls.__name__, - remote_column=key + remote_column=fk )) - return cls._create_relationship(cls.__tablename__, None, relationship_kwargs, - backreference=False, dict_key=dict_key) + return cls._declare_relationship(cls.__tablename__, None, relationship_kwargs, + other_property=False, dict_key=dict_key) @classmethod - def _create_one_to_one_relationship(cls, - other_table, - key=None, - backreference=None, - backref_kwargs=None, - relationship_kwargs=None): + def _declare_one_to_one(cls, + other_table, + fk=None, + other_fk=None, + other_property=None, + relationship_kwargs=None, + backref_kwargs=None): + """ + Declare a one-to-one relationship property. The property value would be an instance of + the other table's model. + + You have two options for the foreign key. Either this table can have an associated key to + the other table (use the `fk` argument) or the other table can have an associated foreign + key to this our table (use the `other_fk` argument). + + *This utility method should only be used during class creation.* + + :param other_table: Other table name + :type other_table: basestring + :param fk: Foreign key name at our table (no need specify if there's no ambiguity) + :type fk: basestring + :param other_fk: Foreign key name at the other table (no need specify if there's no + ambiguity) + :type other_fk: basestring + :param relationship_kwargs: Extra kwargs for SqlAlchemy `relationship` + :type relationship_kwargs: {} + :param backref_kwargs: Extra kwargs for SqlAlchemy `backref` + :type backref_kwargs: {} + """ + backref_kwargs = backref_kwargs or {} backref_kwargs.setdefault('uselist', False) - return cls._create_relationship(other_table, backref_kwargs, relationship_kwargs, - backreference, key=key) + return cls._declare_relationship(other_table, backref_kwargs, relationship_kwargs, + other_property, fk=fk, other_fk=other_fk) @classmethod - def _create_one_to_many_relationship(cls, - child_table, - key=None, - foreign_key=None, - dict_key=None, - backreference=None, - backref_kwargs=None, - relationship_kwargs=None): + def _declare_one_to_many(cls, + child_table, + child_fk=None, + dict_key=None, + child_property=None, + relationship_kwargs=None, + backref_kwargs=None): + """ + Declare a one-to-many relationship property. The property value would be a list or dict + of instances of the child table's model. + + The child table will need an associated foreign key to our table. + + The declaration will automatically create a matching many-to-one property at the child + model, named after our table name. Use the `child_property` argument to override this name. + + *This utility method should only be used during class creation.* + + :param child_table: Child table name + :type child_table: basestring + :param child_fk: Foreign key name at the child table (no need specify if there's no + ambiguity) + :type child_fk: basestring + :param dict_key: If set the value will be a dict with this key as the dict key; otherwise + will be a list + :type dict_key: basestring + :param child_property: Override name of matching many-to-one property at child table; set to + false to disable + :type child_property: basestring|bool + :param relationship_kwargs: Extra kwargs for SqlAlchemy `relationship` + :type relationship_kwargs: {} + :param backref_kwargs: Extra kwargs for SqlAlchemy `backref` + :type backref_kwargs: {} + """ + backref_kwargs = backref_kwargs or {} backref_kwargs.setdefault('uselist', False) - return cls._create_relationship(child_table, backref_kwargs, relationship_kwargs, - backreference, key=key, foreign_key=foreign_key, - dict_key=dict_key) + return cls._declare_relationship(child_table, backref_kwargs, relationship_kwargs, + child_property, other_fk=child_fk, dict_key=dict_key) @classmethod - def _create_many_to_one_relationship(cls, - parent_table, - key=None, - foreign_key=None, - backreference=None, - backref_kwargs=None, - relationship_kwargs=None): + def _declare_many_to_one(cls, + parent_table, + fk=None, + parent_fk=None, + parent_property=None, + relationship_kwargs=None, + backref_kwargs=None): """ - Return a one-to-many SQL relationship object - Meant to be used from inside the *child* object + Declare a many-to-one relationship property. The property value would be an instance of + the parent table's model. + + You will need an associated foreign key to the parent table. + + The declaration will automatically create a matching one-to-many property at the child + model, named after the plural form of our table name. Use the `parent_property` argument to + override this name. Note: the automatic property will always be a SqlAlchemy query object; + if you need a Python collection then use :meth:`_declare_one_to_many` at that model. + + *This utility method should only be used during class creation.* - :param parent_table: Name of the parent table - :param foreign_key: The column of the foreign key (from the child table) - :param backreference: The name to give to the reference to the child (on the parent table) + :param parent_table: Parent table name + :type parent_table: basestring + :param fk: Foreign key name at our table (no need specify if there's no ambiguity) + :type fk: basestring + :param parent_property: Override name of matching one-to-many property at parent table; set + to false to disable + :type parent_property: basestring|bool + :param relationship_kwargs: Extra kwargs for SqlAlchemy `relationship` + :type relationship_kwargs: {} + :param backref_kwargs: Extra kwargs for SqlAlchemy `backref` + :type backref_kwargs: {} """ - if backreference is None: - backreference = formatting.pluralize(cls.__tablename__) + if parent_property is None: + parent_property = formatting.pluralize(cls.__tablename__) backref_kwargs = backref_kwargs or {} backref_kwargs.setdefault('uselist', True) backref_kwargs.setdefault('lazy', 'dynamic') - # The following line make sure that when the *parent* is deleted, all its connected children - # are deleted as well - backref_kwargs.setdefault('cascade', 'all') + backref_kwargs.setdefault('cascade', 'all') # delete children when parent is deleted - return cls._create_relationship(parent_table, backref_kwargs, relationship_kwargs, - backreference, key=key, foreign_key=foreign_key) + return cls._declare_relationship(parent_table, backref_kwargs, relationship_kwargs, + parent_property, fk=fk, other_fk=parent_fk) @classmethod - def _create_many_to_many_relationship(cls, - other_table, - table_prefix=None, - key=None, - dict_key=None, - backreference=None, - backref_kwargs=None, - relationship_kwargs=None): + def _declare_many_to_many(cls, + other_table, + prefix=None, + dict_key=None, + other_property=None, + relationship_kwargs=None, + backref_kwargs=None): """ - Return a many-to-many SQL relationship object + Declare a many-to-many relationship property. The property value would be a list or dict + of instances of the other table's model. + + You do not need associated foreign keys for this relationship. Instead, an extra table will + be created for you. - Notes: + The declaration will automatically create a matching many-to-many property at the other + model, named after the plural form of our table name. Use the `other_property` argument to + override this name. Note: the automatic property will always be a SqlAlchemy query object; + if you need a Python collection then use :meth:`_declare_many_to_many` again at that model. - 1. The backreference name is the current table's table name - 2. This method creates a new helper table in the DB + *This utility method should only be used during class creation.* - :param cls: The class of the table we're connecting from - :param other_table: The class of the table we're connecting to - :param table_prefix: Custom prefix for the helper table name and the backreference name - :param dict_key: If provided, will use a dict class with this column as the key + :param parent_table: Parent table name + :type parent_table: basestring + :param prefix: Optional prefix for extra table name as well as for `other_property` + :type prefix: basestring + :param dict_key: If set the value will be a dict with this key as the dict key; otherwise + will be a list + :type dict_key: basestring + :param other_property: Override name of matching many-to-many property at other table; set + to false to disable + :type other_property: basestring|bool + :param relationship_kwargs: Extra kwargs for SqlAlchemy `relationship` + :type relationship_kwargs: {} + :param backref_kwargs: Extra kwargs for SqlAlchemy `backref` + :type backref_kwargs: {} """ this_table = cls.__tablename__ @@ -236,87 +377,87 @@ class ModelMixin(object): other_column_name = '{0}_id'.format(other_table) other_foreign_key = '{0}.id'.format(other_table) - helper_table = '{0}_{1}'.format(this_table, other_table) + secondary_table = '{0}_{1}'.format(this_table, other_table) - if backreference is None: - backreference = formatting.pluralize(this_table) - if table_prefix: - helper_table = '{0}_{1}'.format(table_prefix, helper_table) - backreference = '{0}_{1}'.format(table_prefix, backreference) + if other_property is None: + other_property = formatting.pluralize(this_table) + if prefix is not None: + secondary_table = '{0}_{1}'.format(prefix, secondary_table) + other_property = '{0}_{1}'.format(prefix, other_property) backref_kwargs = backref_kwargs or {} backref_kwargs.setdefault('uselist', True) relationship_kwargs = relationship_kwargs or {} - relationship_kwargs.setdefault('secondary', cls._get_secondary_table( + relationship_kwargs.setdefault('secondary', ModelMixin._get_secondary_table( cls.metadata, - helper_table, + secondary_table, this_column_name, other_column_name, this_foreign_key, other_foreign_key )) - return cls._create_relationship(other_table, backref_kwargs, relationship_kwargs, - backreference, key=key, dict_key=dict_key) + return cls._declare_relationship(other_table, backref_kwargs, relationship_kwargs, + other_property, dict_key=dict_key) @classmethod - def _create_relationship(cls, table, backref_kwargs, relationship_kwargs, backreference, - key=None, foreign_key=None, dict_key=None): + def _declare_relationship(cls, other_table, backref_kwargs, relationship_kwargs, + other_property, fk=None, other_fk=None, dict_key=None): relationship_kwargs = relationship_kwargs or {} - if key: + if fk: relationship_kwargs.setdefault('foreign_keys', lambda: getattr( - cls._get_cls_by_tablename(cls.__tablename__), - key)) + cls._get_class_for_table(cls.__tablename__), + fk)) - elif foreign_key: + elif other_fk: relationship_kwargs.setdefault('foreign_keys', lambda: getattr( - cls._get_cls_by_tablename(table), - foreign_key)) + cls._get_class_for_table(other_table), + other_fk)) if dict_key: relationship_kwargs.setdefault('collection_class', attribute_mapped_collection(dict_key)) - if backreference is False: + if other_property is False: + # No backref return relationship( - lambda: cls._get_cls_by_tablename(table), + lambda: cls._get_class_for_table(other_table), **relationship_kwargs ) else: - if backreference is None: - backreference = cls.__tablename__ + if other_property is None: + other_property = cls.__tablename__ backref_kwargs = backref_kwargs or {} return relationship( - lambda: cls._get_cls_by_tablename(table), - backref=backref(backreference, **backref_kwargs), + lambda: cls._get_class_for_table(other_table), + backref=backref(other_property, **backref_kwargs), **relationship_kwargs ) + @classmethod + def _get_class_for_table(cls, tablename): + if tablename in (cls.__name__, cls.__tablename__): + return cls + + for table_cls in cls._decl_class_registry.values(): + if tablename == getattr(table_cls, '__tablename__', None): + return table_cls + + raise ValueError('unknown table: {0}'.format(tablename)) + @staticmethod def _get_secondary_table(metadata, - helper_table, + name, first_column, second_column, first_foreign_key, second_foreign_key): - """ - Create a helper table for a many-to-many relationship - - :param helper_table: The name of the table - :param first_column_name: The name of the first column in the table - :param second_column_name: The name of the second column in the table - :param first_foreign_key: The string representing the first foreign key, - for example `blueprint.storage_id`, or `tenants.id` - :param second_foreign_key: The string representing the second foreign key - :return: A Table object - """ - return Table( - helper_table, + name, metadata, Column( first_column, @@ -330,33 +471,6 @@ class ModelMixin(object): ) ) - @classmethod - def _iter_association_proxies(cls): - for col, value in vars(cls).items(): - if isinstance(value, associationproxy.AssociationProxy): - yield col - - @classmethod - def _get_cls_by_tablename(cls, tablename): - """ - Return class reference mapped to table. - - :param tablename: String with name of table. - :return: Class reference or None. - """ - - if tablename in (cls.__name__, cls.__tablename__): - return cls - - for table_cls in cls._decl_class_registry.values(): - if tablename == getattr(table_cls, '__tablename__', None): - return table_cls - - def __repr__(self): - return '<{cls} id=`{id}`>'.format( - cls=self.__class__.__name__, - id=getattr(self, self.name_column_name())) - class ModelIDMixin(object): id = Column(Integer, primary_key=True, autoincrement=True) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0f040a2b/aria/modeling/orchestration.py ---------------------------------------------------------------------- diff --git a/aria/modeling/orchestration.py b/aria/modeling/orchestration.py index d8bdd3c..683c526 100644 --- a/aria/modeling/orchestration.py +++ b/aria/modeling/orchestration.py @@ -100,28 +100,33 @@ class ExecutionBase(ModelMixin): workflow_name = Column(Text) @declared_attr - def service_template(cls): - return association_proxy('service', 'service_template') + def service(cls): + return cls._declare_many_to_one('service') + + # region foreign keys @declared_attr - def service(cls): - return cls._create_many_to_one_relationship('service') + def service_fk(cls): + return cls._declare_fk('service') + + # endregion + + # region association proxies @declared_attr def service_name(cls): + """Required for use by SqlAlchemy queries""" return association_proxy('service', cls.name_column_name()) @declared_attr - def service_template_name(cls): - return association_proxy('service', 'service_template_name') - - # region foreign keys - - __private_fields__ = ['service_fk'] + def service_template(cls): + """Required for use by SqlAlchemy queries""" + return association_proxy('service', 'service_template') @declared_attr - def service_fk(cls): - return cls._create_foreign_key('service') + def service_template_name(cls): + """Required for use by SqlAlchemy queries""" + return association_proxy('service', 'service_template_name') # endregion @@ -132,6 +137,11 @@ class ExecutionBase(ModelMixin): self.status ) + __private_fields__ = ['service_fk', + 'service_name', + 'service_template', + 'service_template_name'] + class PluginBase(ModelMixin): """ @@ -186,37 +196,24 @@ class TaskBase(ModelMixin): INFINITE_RETRIES = -1 @declared_attr - def node_name(cls): - return association_proxy('node', cls.name_column_name()) - - @declared_attr def node(cls): - return cls._create_many_to_one_relationship('node') - - @declared_attr - def relationship_name(cls): - return association_proxy('relationship', cls.name_column_name()) + return cls._declare_many_to_one('node') @declared_attr def relationship(cls): - return cls._create_many_to_one_relationship('relationship') + return cls._declare_many_to_one('relationship') @declared_attr def plugin(cls): - return cls._create_many_to_one_relationship('plugin') + return cls._declare_many_to_one('plugin') @declared_attr def execution(cls): - return cls._create_many_to_one_relationship('execution') - - @declared_attr - def execution_name(cls): - return association_proxy('execution', cls.name_column_name()) + return cls._declare_many_to_one('execution') @declared_attr def inputs(cls): - return cls._create_many_to_many_relationship('parameter', table_prefix='inputs', - dict_key='name') + return cls._declare_many_to_many('parameter', prefix='inputs', dict_key='name') status = Column(Enum(*STATES, name='status'), default=PENDING) @@ -260,26 +257,40 @@ class TaskBase(ModelMixin): # region foreign keys - __private_fields__ = ['node_fk', - 'relationship_fk', - 'plugin_fk', - 'execution_fk'] - @declared_attr def node_fk(cls): - return cls._create_foreign_key('node', nullable=True) + return cls._declare_fk('node', nullable=True) @declared_attr def relationship_fk(cls): - return cls._create_foreign_key('relationship', nullable=True) + return cls._declare_fk('relationship', nullable=True) @declared_attr def plugin_fk(cls): - return cls._create_foreign_key('plugin', nullable=True) + return cls._declare_fk('plugin', nullable=True) @declared_attr def execution_fk(cls): - return cls._create_foreign_key('execution', nullable=True) + return cls._declare_fk('execution', nullable=True) + + # endregion + + # region association proxies + + @declared_attr + def node_name(cls): + """Required for use by SqlAlchemy queries""" + return association_proxy('node', cls.name_column_name()) + + @declared_attr + def relationship_name(cls): + """Required for use by SqlAlchemy queries""" + return association_proxy('relationship', cls.name_column_name()) + + @declared_attr + def execution_name(cls): + """Required for use by SqlAlchemy queries""" + return association_proxy('execution', cls.name_column_name()) # endregion @@ -298,3 +309,11 @@ class TaskBase(ModelMixin): @staticmethod def retry(message=None, retry_interval=None): raise TaskRetryException(message, retry_interval=retry_interval) + + __private_fields__ = ['node_fk', + 'relationship_fk', + 'plugin_fk', + 'execution_fk', + 'node_name', + 'relationship_name', + 'execution_name'] http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0f040a2b/aria/modeling/relationship.py ---------------------------------------------------------------------- diff --git a/aria/modeling/relationship.py b/aria/modeling/relationship.py deleted file mode 100644 index 2fdbe0c..0000000 --- a/aria/modeling/relationship.py +++ /dev/null @@ -1,421 +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. - -# pylint: disable=invalid-name, redefined-outer-name, unused-argument - -from functools import wraps - -from sqlalchemy.orm import relationship, backref -from sqlalchemy.orm.collections import attribute_mapped_collection -from sqlalchemy.ext.declarative import declared_attr -from sqlalchemy import ( - Column, - ForeignKey, - Integer, - Table -) - -from ..utils import formatting - - -def fk(other_table, - nullable=False): - """ - Decorator for a foreign key property, which will also create a foreign key column in the table - with the name of the property. By convention the property name should end in "_fk". - - You are required to explicitly create foreign keys in order to allow for one-to-one, - one-to-many, and many-to-one relationships. If you do not do so, SqlAlchemy will fail to create - the relationship and raise an exception with a clear error message. - - You should normally not have to access this property directly, but instead use the - associated relationship properties. - - :param other_table: Other table name - :type other_table: basestring - :param nullable: True to allow null values (meaning that there is no relationship) - :type nullable: bool - """ - - def decorator(fn): - @declared_attr - @wraps(fn) - def attr(cls): - if not hasattr(cls, '__private_fields__'): - cls.__private_fields__ = [] - cls.__private_fields__.append(fn.__name__) - return Column(Integer, - ForeignKey('{table}.id'.format(table=other_table), ondelete='CASCADE'), - nullable=nullable) - return attr - return decorator - - -def one_to_one_self(fk, - relationship_kwargs=None): - """ - Decorator for a one-to-one relationship property. The property value would be an instance of - the same model. - - You will need an associated foreign key to our own table. - - :param fk: Foreign key name - :type fk: basestring - :param relationship_kwargs: Extra kwargs for SqlAlchemy `relationship` - :type relationship_kwargs: {} - """ - - scope = locals() - def decorator(fn): - @declared_attr - @wraps(fn) - def attr(cls): - relationship_kwargs = scope['relationship_kwargs'] or {} - - remote_side = '{cls}.{remote_column}'.format( - cls=cls.__name__, - remote_column=cls.id_column_name() - ) - - primaryjoin = '{remote_side} == {cls}.{column}'.format( - remote_side=remote_side, - cls=cls.__name__, - column=fk - ) - - return relationship( - _get_class_for_table(cls, cls.__tablename__).__name__, - primaryjoin=primaryjoin, - remote_side=remote_side, - post_update=True, - **relationship_kwargs - ) - return attr - return decorator - - -def one_to_many_self(fk, - dict_key=None, - relationship_kwargs=None): - """ - Decorator for a one-to-many relationship property. The property value would be a list or dict - of instances of the same model. - - You will need an associated foreign key to our own table. - - :param fk: Foreign key name - :type fk: basestring - :param dict_key: If set the value will be a dict with this key as the dict key; otherwise will - be a list - :type dict_key: basestring - :param relationship_kwargs: Extra kwargs for SqlAlchemy `relationship` - :type relationship_kwargs: {} - """ - - scope = locals() - def decorator(fn): - @declared_attr - @wraps(fn) - def attr(cls): - relationship_kwargs = scope['relationship_kwargs'] or {} - - relationship_kwargs.setdefault('remote_side', '{cls}.{remote_column}'.format( - cls=cls.__name__, - remote_column=fk - )) - - return _create_relationship(cls, cls.__tablename__, None, relationship_kwargs, - other_property=False, dict_key=dict_key) - return attr - return decorator - - -def one_to_one(other_table, - fk=None, - other_fk=None, - other_property=None, - relationship_kwargs=None, - backref_kwargs=None): - """ - Decorator for a one-to-one relationship property. The property value would be an instance of - the other table's model. - - You have two options for the foreign key. Either this table can have an associated key to the - other table (use the `fk` argument) or the other table can have an associated foreign key to - this our table (use the `other_fk` argument). - - :param other_table: Other table name - :type other_table: basestring - :param fk: Foreign key name at our table (no need specify if there's no ambiguity) - :type fk: basestring - :param other_fk: Foreign key name at the other table (no need specify if there's no ambiguity) - :type other_fk: basestring - :param relationship_kwargs: Extra kwargs for SqlAlchemy `relationship` - :type relationship_kwargs: {} - :param backref_kwargs: Extra kwargs for SqlAlchemy `backref` - :type backref_kwargs: {} - """ - - scope = locals() - def decorator(fn): - @declared_attr - @wraps(fn) - def attr(cls): - backref_kwargs = scope['backref_kwargs'] or {} - backref_kwargs.setdefault('uselist', False) - - return _create_relationship(cls, other_table, backref_kwargs, relationship_kwargs, - other_property, fk=fk, other_fk=other_fk) - return attr - return decorator - - -def one_to_many(child_table, - child_fk=None, - dict_key=None, - child_property=None, - relationship_kwargs=None, - backref_kwargs=None): - """ - Decorator for a one-to-many relationship property. The property value would be a list or dict - of instances of the child table's model. - - The child table will need an associated foreign key to our table. - - The decorator will automatically create a matching many-to-one property at the child model, - named after our table name. Use the `child_property` argument to override this name. - - :param child_table: Child table name - :type child_table: basestring - :param child_fk: Foreign key name at the child table (no need specify if there's no ambiguity) - :type child_fk: basestring - :param dict_key: If set the value will be a dict with this key as the dict key; otherwise will - be a list - :type dict_key: basestring - :param child_property: Override name of matching many-to-one property at child table; set to - false to disable - :type child_property: basestring|bool - :param relationship_kwargs: Extra kwargs for SqlAlchemy `relationship` - :type relationship_kwargs: {} - :param backref_kwargs: Extra kwargs for SqlAlchemy `backref` - :type backref_kwargs: {} - """ - - scope = locals() - def decorator(fn): - @declared_attr - @wraps(fn) - def attr(cls): - backref_kwargs = scope['backref_kwargs'] or {} - backref_kwargs.setdefault('uselist', False) - - return _create_relationship(cls, child_table, backref_kwargs, relationship_kwargs, - child_property, other_fk=child_fk, dict_key=dict_key) - return attr - return decorator - - -def many_to_one(parent_table, - fk=None, - parent_fk=None, - parent_property=None, - relationship_kwargs=None, - backref_kwargs=None): - """ - Decorator for a many-to-one relationship property. The property value would be an instance of - the parent table's model. - - You will need an associated foreign key to the parent table. - - The decorator will automatically create a matching one-to-many property at the child model, - named after the plural form of our table name. Use the `parent_property` argument to override - this name. Note: the automatic property will always be a list; if you need it to be a dict, use - the :func:`one_to_many` decorator on that model instead. - - :param parent_table: Parent table name - :type parent_table: basestring - :param fk: Foreign key name at our table (no need specify if there's no ambiguity) - :type fk: basestring - :param parent_property: Override name of matching one-to-many property at parent table; set to - false to disable - :type parent_property: basestring|bool - :param relationship_kwargs: Extra kwargs for SqlAlchemy `relationship` - :type relationship_kwargs: {} - :param backref_kwargs: Extra kwargs for SqlAlchemy `backref` - :type backref_kwargs: {} - """ - - scope = locals() - def decorator(fn): - @declared_attr - @wraps(fn) - def attr(cls): - parent_property = scope['parent_property'] - if parent_property is None: - parent_property = formatting.pluralize(cls.__tablename__) - - backref_kwargs = scope['backref_kwargs'] or {} - backref_kwargs.setdefault('uselist', True) - backref_kwargs.setdefault('lazy', 'dynamic') - backref_kwargs.setdefault('cascade', 'all') # delete children when parent is deleted - - return _create_relationship(cls, parent_table, backref_kwargs, relationship_kwargs, - parent_property, fk=fk, other_fk=parent_fk) - return attr - return decorator - - -def many_to_many(other_table, - prefix=None, - dict_key=None, - other_property=None, - relationship_kwargs=None, - backref_kwargs=None): - """ - Decorator for a many-to-many relationship property. The property value would be a list or dict - of instances of the other table's model. - - You do not need associated foreign keys for this relationship. Instead, an extra table will be - created for you. - - The decorator will automatically create a matching many-to-many property at the other model, - named after the plural form of our table name. Use the `other_property` argument to override - this name. Note: the automatic property will always be a list; if you need it to be a dict, use - the `many_to_many` decorator again on that model. - - :param parent_table: Parent table name - :type parent_table: basestring - :param prefix: Optional prefix for extra table name as well as for `other_property` - :type prefix: basestring - :param dict_key: If set the value will be a dict with this key as the dict key; otherwise will - be a list - :type dict_key: basestring - :param other_property: Override name of matching many-to-many property at other table; set to - false to disable - :type other_property: basestring|bool - :param relationship_kwargs: Extra kwargs for SqlAlchemy `relationship` - :type relationship_kwargs: {} - :param backref_kwargs: Extra kwargs for SqlAlchemy `backref` - :type backref_kwargs: {} - """ - - scope = locals() - def decorator(fn): - @declared_attr - @wraps(fn) - def attr(cls): - this_table = cls.__tablename__ - this_column_name = '{0}_id'.format(this_table) - this_foreign_key = '{0}.id'.format(this_table) - - other_column_name = '{0}_id'.format(other_table) - other_foreign_key = '{0}.id'.format(other_table) - - secondary_table = '{0}_{1}'.format(this_table, other_table) - - other_property = scope['other_property'] - if other_property is None: - other_property = formatting.pluralize(this_table) - if prefix is not None: - secondary_table = '{0}_{1}'.format(prefix, secondary_table) - other_property = '{0}_{1}'.format(prefix, other_property) - - backref_kwargs = scope['backref_kwargs'] or {} - backref_kwargs.setdefault('uselist', True) - - relationship_kwargs = scope['relationship_kwargs'] or {} - relationship_kwargs.setdefault('secondary', _get_secondary_table( - cls.metadata, - secondary_table, - this_column_name, - other_column_name, - this_foreign_key, - other_foreign_key - )) - - return _create_relationship(cls, other_table, backref_kwargs, relationship_kwargs, - other_property, dict_key=dict_key) - return attr - return decorator - - -def _create_relationship(cls, other_table, backref_kwargs, relationship_kwargs, other_property, - fk=None, other_fk=None, dict_key=None): - relationship_kwargs = relationship_kwargs or {} - - if fk: - relationship_kwargs.setdefault('foreign_keys', - lambda: getattr( - _get_class_for_table(cls, cls.__tablename__), - fk)) - - elif other_fk: - relationship_kwargs.setdefault('foreign_keys', - lambda: getattr( - _get_class_for_table(cls, other_table), - other_fk)) - - if dict_key: - relationship_kwargs.setdefault('collection_class', - attribute_mapped_collection(dict_key)) - - if other_property is False: - # No backref - return relationship( - lambda: _get_class_for_table(cls, other_table), - **relationship_kwargs - ) - else: - if other_property is None: - other_property = cls.__tablename__ - backref_kwargs = backref_kwargs or {} - return relationship( - lambda: _get_class_for_table(cls, other_table), - backref=backref(other_property, **backref_kwargs), - **relationship_kwargs - ) - - -def _get_secondary_table(metadata, - name, - first_column, - second_column, - first_foreign_key, - second_foreign_key): - return Table( - name, - metadata, - Column( - first_column, - Integer, - ForeignKey(first_foreign_key) - ), - Column( - second_column, - Integer, - ForeignKey(second_foreign_key) - ) - ) - - -def _get_class_for_table(cls, tablename): - if tablename in (cls.__name__, cls.__tablename__): - return cls - - for table_cls in cls._decl_class_registry.values(): - if tablename == getattr(table_cls, '__tablename__', None): - return table_cls - - raise ValueError('unknown table: {0}'.format(tablename)) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0f040a2b/aria/modeling/service_changes.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_changes.py b/aria/modeling/service_changes.py index b83a376..eb7bb27 100644 --- a/aria/modeling/service_changes.py +++ b/aria/modeling/service_changes.py @@ -65,33 +65,35 @@ class ServiceUpdateBase(ModelMixin): @declared_attr def execution(cls): - return cls._create_many_to_one_relationship('execution') + return cls._declare_many_to_one('execution') @declared_attr - def execution_name(cls): - return association_proxy('execution', cls.name_column_name()) + def service(cls): + return cls._declare_many_to_one('service', parent_property='updates') + + # region foreign keys @declared_attr - def service(cls): - return cls._create_many_to_one_relationship('service', - backreference='updates') + def execution_fk(cls): + return cls._declare_fk('execution', nullable=True) @declared_attr - def service_name(cls): - return association_proxy('service', cls.name_column_name()) + def service_fk(cls): + return cls._declare_fk('service') - # region foreign keys + # endregion - __private_fields__ = ['service_fk', - 'execution_fk'] + # region association proxies @declared_attr - def execution_fk(cls): - return cls._create_foreign_key('execution', nullable=True) + def execution_name(cls): + """Required for use by SqlAlchemy queries""" + return association_proxy('execution', cls.name_column_name()) @declared_attr - def service_fk(cls): - return cls._create_foreign_key('service') + def service_name(cls): + """Required for use by SqlAlchemy queries""" + return association_proxy('service', cls.name_column_name()) # endregion @@ -101,6 +103,11 @@ class ServiceUpdateBase(ModelMixin): dep_update_dict['steps'] = [step.to_dict() for step in self.steps] return dep_update_dict + __private_fields__ = ['service_fk', + 'execution_fk', + 'execution_name', + 'service_name'] + class ServiceUpdateStepBase(ModelMixin): """ @@ -133,20 +140,22 @@ class ServiceUpdateStepBase(ModelMixin): @declared_attr def service_update(cls): - return cls._create_many_to_one_relationship('service_update', - backreference='steps') + return cls._declare_many_to_one('service_update', parent_property='steps') + + # region foreign keys @declared_attr - def service_update_name(cls): - return association_proxy('service_update', cls.name_column_name()) + def service_update_fk(cls): + return cls._declare_fk('service_update') - # region foreign keys + # endregion - __private_fields__ = ['service_update_fk'] + # region association proxies @declared_attr - def service_update_fk(cls): - return cls._create_foreign_key('service_update') + def service_update_name(cls): + """Required for use by SqlAlchemy queries""" + return association_proxy('service_update', cls.name_column_name()) # endregion @@ -177,6 +186,9 @@ class ServiceUpdateStepBase(ModelMixin): return self.entity_type == 'relationship' and other.entity_type == 'node' return False + __private_fields__ = ['service_update_fk', + 'service_update_name'] + class ServiceModificationBase(ModelMixin): """ @@ -201,19 +213,24 @@ class ServiceModificationBase(ModelMixin): @declared_attr def service(cls): - return cls._create_many_to_one_relationship('service', - backreference='modifications') + return cls._declare_many_to_one('service', parent_property='modifications') + + # region foreign keys @declared_attr - def service_name(cls): - return association_proxy('service', cls.name_column_name()) + def service_fk(cls): + return cls._declare_fk('service') - # region foreign keys + # endregion - __private_fields__ = ['service_fk'] + # region association proxies @declared_attr - def service_fk(cls): - return cls._create_foreign_key('service') + def service_name(cls): + """Required for use by SqlAlchemy queries""" + return association_proxy('service', cls.name_column_name()) # endregion + + __private_fields__ = ['service_fk', + 'service_name'] http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0f040a2b/aria/modeling/service_common.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_common.py b/aria/modeling/service_common.py index 7422211..6214bb2 100644 --- a/aria/modeling/service_common.py +++ b/aria/modeling/service_common.py @@ -16,7 +16,6 @@ # pylint: disable=no-self-argument, no-member, abstract-method import cPickle as pickle -import logging from sqlalchemy import ( Column, @@ -68,9 +67,9 @@ class ParameterBase(TemplateModelMixin): return None try: return pickle.loads(self._value) - except BaseException: + except BaseException as e: raise exceptions.ValueFormatException('bad format for parameter of type "{0}": {1}' - .format(self.type_name, self._value)) + .format(self.type_name, self._value), cause=e) @value.setter def value(self, value): @@ -79,9 +78,9 @@ class ParameterBase(TemplateModelMixin): else: try: self._value = pickle.dumps(value) - except (pickle.PicklingError, TypeError): - logging.getLogger('aria').warn('Could not pickle parameter of type "{0}": {1}' - .format(self.type_name, value)) + except (pickle.PicklingError, TypeError) as e: + #raise exceptions.ValueFormatException('bad format for parameter of type "{0}": {1}' + # .format(self.type_name, self.value), cause=e) self._value = pickle.dumps(str(value)) def instantiate(self, container): @@ -124,20 +123,18 @@ class TypeBase(InstanceModelMixin): @declared_attr def parent(cls): - return cls._create_relationship_to_self('parent_type_fk') + return cls._declare_one_to_one_self('parent_type_fk') @declared_attr def children(cls): - return cls._create_one_to_many_relationship_to_self('parent_type_fk') + return cls._declare_one_to_many_self('parent_type_fk') # region foreign keys - __private_fields__ = ['parent_type_fk'] - - # Type one-to-many to Type @declared_attr def parent_type_fk(cls): - return cls._create_foreign_key('type', nullable=True) + """For Type one-to-many to Type""" + return cls._declare_fk('type', nullable=True) # endregion @@ -206,6 +203,8 @@ class TypeBase(InstanceModelMixin): types.append(raw_child) child._append_raw_children(types) + __private_fields__ = ['parent_type_fk'] + class MetadataBase(TemplateModelMixin): """ @@ -258,11 +257,10 @@ class PluginSpecificationBase(InstanceModelMixin): # region foreign keys - __private_fields__ = ['service_template_fk'] - @declared_attr def service_template_fk(cls): - return cls._create_foreign_key('service_template', nullable=True) + """For ServiceTemplate one-to-many to PluginSpecification""" + return cls._declare_fk('service_template', nullable=True) # endregion @@ -272,3 +270,5 @@ class PluginSpecificationBase(InstanceModelMixin): if plugin.name == self.name: return plugin return None + + __private_fields__ = ['service_template_fk']
