diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-01-03 21:46:13 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-01-03 21:46:13 -0500 |
| commit | 31821011271bf2333b69954d53c3c922e39bf225 (patch) | |
| tree | dacd52e93319d120c421ad17a6e1628bc3861f63 /lib/sqlalchemy/ext | |
| parent | 3e7a17174f9486e5462d19842615c1b1df57769d (diff) | |
| download | sqlalchemy-31821011271bf2333b69954d53c3c922e39bf225.tar.gz | |
- 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.
Diffstat (limited to 'lib/sqlalchemy/ext')
| -rw-r--r-- | lib/sqlalchemy/ext/declarative/api.py | 27 | ||||
| -rw-r--r-- | lib/sqlalchemy/ext/declarative/base.py | 55 |
2 files changed, 63 insertions, 19 deletions
diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py index 64bf7fd9f..c9b5a9195 100644 --- a/lib/sqlalchemy/ext/declarative/api.py +++ b/lib/sqlalchemy/ext/declarative/api.py @@ -18,7 +18,7 @@ import weakref from .base import _as_declarative, \ _declarative_constructor,\ - _MapperConfig, _add_attribute + _DeferredMapperConfig, _add_attribute from .clsregistry import _class_resolver @@ -468,8 +468,7 @@ class DeferredReflection(object): """Reflect all :class:`.Table` objects for all current :class:`.DeferredReflection` subclasses""" - to_map = [m for m in _MapperConfig.configs.values() - if issubclass(m.cls, cls)] + to_map = _DeferredMapperConfig.classes_for_base(cls) for thingy in to_map: cls._sa_decl_prepare(thingy.local_table, engine) thingy.map() @@ -479,7 +478,7 @@ class DeferredReflection(object): if isinstance(rel, properties.RelationshipProperty) and \ rel.secondary is not None: if isinstance(rel.secondary, Table): - cls._sa_decl_prepare(rel.secondary, engine) + cls._reflect_table(rel.secondary, engine) elif isinstance(rel.secondary, _class_resolver): rel.secondary._resolvers += ( cls._sa_deferred_table_resolver(engine, metadata), @@ -489,7 +488,7 @@ class DeferredReflection(object): def _sa_deferred_table_resolver(cls, engine, metadata): def _resolve(key): t1 = Table(key, metadata) - cls._sa_decl_prepare(t1, engine) + cls._reflect_table(t1, engine) return t1 return _resolve @@ -500,10 +499,14 @@ class DeferredReflection(object): # will fill in db-loaded columns # into the existing Table object. if local_table is not None: - Table(local_table.name, - local_table.metadata, - extend_existing=True, - autoload_replace=False, - autoload=True, - autoload_with=engine, - schema=local_table.schema) + cls._reflect_table(local_table, engine) + + @classmethod + def _reflect_table(cls, table, engine): + Table(table.name, + table.metadata, + extend_existing=True, + autoload_replace=False, + autoload=True, + autoload_with=engine, + schema=table.schema) 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. |
