summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-01-03 21:46:13 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2014-01-03 21:46:13 -0500
commit31821011271bf2333b69954d53c3c922e39bf225 (patch)
treedacd52e93319d120c421ad17a6e1628bc3861f63 /lib/sqlalchemy/ext
parent3e7a17174f9486e5462d19842615c1b1df57769d (diff)
downloadsqlalchemy-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.py27
-rw-r--r--lib/sqlalchemy/ext/declarative/base.py55
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.