From 59141d360e70d1a762719206e3cb0220b4c53fef Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 14 Aug 2013 19:58:34 -0400 Subject: - apply an import refactoring to the ORM as well - rework the event system so that event modules load after their targets, dependencies are reversed - create an improved strategy lookup system for the ORM - rework the ORM to have very few import cycles - move out "importlater" to just util.dependency - other tricks to cross-populate modules in as clear a way as possible --- lib/sqlalchemy/ext/declarative/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/sqlalchemy/ext/declarative/base.py') diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py index 5a2b88db4..820c0874b 100644 --- a/lib/sqlalchemy/ext/declarative/base.py +++ b/lib/sqlalchemy/ext/declarative/base.py @@ -9,7 +9,7 @@ from ...schema import Table, Column from ...orm import mapper, class_mapper from ...orm.interfaces import MapperProperty from ...orm.properties import ColumnProperty, CompositeProperty -from ...orm.util import _is_mapped_class +from ...orm.base import _is_mapped_class from ... import util, exc from ...sql import expression from ... import event -- cgit v1.2.1 From de786a4208e621229769a8fb1f876f358dc4e70e Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 27 Dec 2013 17:10:55 -0500 Subject: - Declarative does an extra check to detect if the same :class:`.Column` is mapped multiple times under different properties (which typically should be a :func:`.synonym` instead) or if two or more :class:`.Column` objects are given the same name, raising a warning if this condition is detected. [ticket:2828] --- lib/sqlalchemy/ext/declarative/base.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'lib/sqlalchemy/ext/declarative/base.py') diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py index 820c0874b..f7668a540 100644 --- a/lib/sqlalchemy/ext/declarative/base.py +++ b/lib/sqlalchemy/ext/declarative/base.py @@ -14,7 +14,7 @@ from ... import util, exc from ...sql import expression from ... import event from . import clsregistry - +import collections def _declared_mapping_info(cls): # deferred mapping @@ -173,15 +173,19 @@ def _as_declarative(cls, classname, dict_): # extract columns from the class dict declared_columns = set() + name_to_prop_key = collections.defaultdict(set) for key, c in list(our_stuff.items()): if isinstance(c, (ColumnProperty, CompositeProperty)): for col in c.columns: if isinstance(col, Column) and \ col.table is None: _undefer_column_name(key, col) + if not isinstance(c, CompositeProperty): + name_to_prop_key[col.name].add(key) declared_columns.add(col) elif isinstance(c, Column): _undefer_column_name(key, c) + name_to_prop_key[c.name].add(key) declared_columns.add(c) # if the column is the same name as the key, # remove it from the explicit properties dict. @@ -190,6 +194,15 @@ def _as_declarative(cls, classname, dict_): # in multi-column ColumnProperties. if key == c.key: del our_stuff[key] + + for name, keys in name_to_prop_key.items(): + if len(keys) > 1: + util.warn( + "On class %r, Column object %r named directly multiple times, " + "only one will be used: %s" % + (classname, name, (", ".join(sorted(keys)))) + ) + declared_columns = sorted( declared_columns, key=lambda c: c._creation_order) table = None -- cgit v1.2.1 From 8a7fdd4e5cf5e4d9ba71c66a06bcba6b1054cfef Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 2 Jan 2014 18:51:49 -0500 Subject: - A quasi-regression where apparently in 0.8 you can set a class-level attribute on declarative to simply refer directly to an :class:`.InstrumentedAttribute` on a superclass or on the class itself, and it acts more or less like a synonym; in 0.9, this fails to set up enough bookkeeping to keep up with the more liberalized backref logic from :ticket:`2789`. Even though this use case was never directly considered, it is now detected by declarative at the "setattr()" level as well as when setting up a subclass, and the mirrored/renamed attribute is now set up as a :func:`.synonym` instead. [ticket:2900] --- lib/sqlalchemy/ext/declarative/base.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'lib/sqlalchemy/ext/declarative/base.py') diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py index f7668a540..69e4b9eea 100644 --- a/lib/sqlalchemy/ext/declarative/base.py +++ b/lib/sqlalchemy/ext/declarative/base.py @@ -6,9 +6,10 @@ """Internal implementation for declarative.""" from ...schema import Table, Column -from ...orm import mapper, class_mapper +from ...orm import mapper, class_mapper, synonym from ...orm.interfaces import MapperProperty from ...orm.properties import ColumnProperty, CompositeProperty +from ...orm.attributes import QueryableAttribute from ...orm.base import _is_mapped_class from ... import util, exc from ...sql import expression @@ -148,6 +149,15 @@ def _as_declarative(cls, classname, dict_): if isinstance(value, declarative_props): value = getattr(cls, k) + elif isinstance(value, QueryableAttribute) and \ + value.class_ is not cls and \ + value.key != k: + # detect a QueryableAttribute that's already mapped being + # assigned elsewhere in userland, turn into a synonym() + value = synonym(value.key) + setattr(cls, k, value) + + if (isinstance(value, tuple) and len(value) == 1 and isinstance(value[0], (Column, MapperProperty))): util.warn("Ignoring declarative-like tuple value of attribute " @@ -397,6 +407,7 @@ def _add_attribute(cls, key, value): adds it to the Mapper, adds a column to the mapped Table, etc. """ + if '__mapper__' in cls.__dict__: if isinstance(value, Column): _undefer_column_name(key, value) @@ -413,6 +424,14 @@ def _add_attribute(cls, key, value): key, clsregistry._deferred_relationship(cls, value) ) + elif isinstance(value, QueryableAttribute) and value.key != key: + # detect a QueryableAttribute that's already mapped being + # assigned elsewhere in userland, turn into a synonym() + value = synonym(value.key) + cls.__mapper__.add_property( + key, + clsregistry._deferred_relationship(cls, value) + ) else: type.__setattr__(cls, key, value) else: -- cgit v1.2.1 From 31821011271bf2333b69954d53c3c922e39bf225 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 3 Jan 2014 21:46:13 -0500 Subject: - Fixed an extremely unlikely memory issue where when using :class:`.DeferredReflection` to define classes pending for reflection, if some subset of those classes were discarded before the :meth:`.DeferredReflection.prepare` method were called to reflect and map the class, a strong reference to the class would remain held within the declarative internals. This internal collection of "classes to map" now uses weak references against the classes themselves. --- lib/sqlalchemy/ext/declarative/base.py | 55 +++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 7 deletions(-) (limited to 'lib/sqlalchemy/ext/declarative/base.py') diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py index 69e4b9eea..3cd600980 100644 --- a/lib/sqlalchemy/ext/declarative/base.py +++ b/lib/sqlalchemy/ext/declarative/base.py @@ -16,11 +16,12 @@ from ...sql import expression from ... import event from . import clsregistry import collections +import weakref def _declared_mapping_info(cls): # deferred mapping - if cls in _MapperConfig.configs: - return _MapperConfig.configs[cls] + if _DeferredMapperConfig.has_cls(cls): + return _DeferredMapperConfig.config_for_cls(cls) # regular mapping elif _is_mapped_class(cls): return class_mapper(cls, configure=False) @@ -304,19 +305,24 @@ def _as_declarative(cls, classname, dict_): inherited_mapped_table is not inherited_table: inherited_mapped_table._refresh_for_new_column(c) - mt = _MapperConfig(mapper_cls, + defer_map = hasattr(cls, '_sa_decl_prepare') + if defer_map: + cfg_cls = _DeferredMapperConfig + else: + cfg_cls = _MapperConfig + mt = cfg_cls(mapper_cls, cls, table, inherits, declared_columns, column_copies, our_stuff, mapper_args_fn) - if not hasattr(cls, '_sa_decl_prepare'): + if not defer_map: mt.map() class _MapperConfig(object): - configs = util.OrderedDict() + mapped_table = None def __init__(self, mapper_cls, @@ -334,7 +340,7 @@ class _MapperConfig(object): self.mapper_args_fn = mapper_args_fn self.declared_columns = declared_columns self.column_copies = column_copies - self.configs[cls] = self + def _prepare_mapper_arguments(self): properties = self.properties @@ -391,7 +397,6 @@ class _MapperConfig(object): return result_mapper_args def map(self): - self.configs.pop(self.cls, None) mapper_args = self._prepare_mapper_arguments() self.cls.__mapper__ = self.mapper_cls( self.cls, @@ -399,6 +404,42 @@ class _MapperConfig(object): **mapper_args ) +class _DeferredMapperConfig(_MapperConfig): + _configs = util.OrderedDict() + + @property + def cls(self): + return self._cls() + + @cls.setter + def cls(self, class_): + self._cls = weakref.ref(class_, self._remove_config_cls) + self._configs[self._cls] = self + + @classmethod + def _remove_config_cls(cls, ref): + cls._configs.pop(ref, None) + + @classmethod + def has_cls(cls, class_): + # 2.6 fails on weakref if class_ is an old style class + return isinstance(class_, type) and \ + weakref.ref(class_) in cls._configs + + @classmethod + def config_for_cls(cls, class_): + return cls._configs[weakref.ref(class_)] + + + @classmethod + def classes_for_base(cls, base_cls): + return [m for m in cls._configs.values() + if issubclass(m.cls, base_cls)] + + def map(self): + self._configs.pop(self._cls, None) + super(_DeferredMapperConfig, self).map() + def _add_attribute(cls, key, value): """add an attribute to an existing declarative class. -- cgit v1.2.1 From f89d4d216bd7605c920b7b8a10ecde6bfea2238c Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 5 Jan 2014 16:57:05 -0500 Subject: - happy new year --- lib/sqlalchemy/ext/declarative/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/sqlalchemy/ext/declarative/base.py') diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py index 3cd600980..a764f126b 100644 --- a/lib/sqlalchemy/ext/declarative/base.py +++ b/lib/sqlalchemy/ext/declarative/base.py @@ -1,5 +1,5 @@ # ext/declarative/base.py -# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors +# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors # # This module is part of SQLAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -- cgit v1.2.1