summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-08-02 14:22:25 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2014-08-02 14:22:25 -0400
commitbeaefd799dcc8954cbbbc9c8df9ede55cbee1e1c (patch)
tree7ecc71f8cb5dd01b3ec206bcdf31a647fa64c0e4
parent32165f50209036a98959553e1c5e81537a091a15 (diff)
downloadsqlalchemy-beaefd799dcc8954cbbbc9c8df9ede55cbee1e1c.tar.gz
- poc fix for #3149
-rw-r--r--lib/sqlalchemy/ext/declarative/api.py41
-rw-r--r--lib/sqlalchemy/ext/declarative/base.py25
-rw-r--r--lib/sqlalchemy/sql/schema.py6
3 files changed, 65 insertions, 7 deletions
diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py
index daf8bffb5..251b06fed 100644
--- a/lib/sqlalchemy/ext/declarative/api.py
+++ b/lib/sqlalchemy/ext/declarative/api.py
@@ -13,7 +13,7 @@ from ...orm import synonym as _orm_synonym, mapper,\
interfaces, properties
from ...orm.util import polymorphic_union
from ...orm.base import _mapper_or_none
-from ...util import OrderedDict
+from ...util import OrderedDict, classproperty
from ... import exc
import weakref
@@ -164,6 +164,45 @@ class declared_attr(interfaces._MappedAttribute, property):
def __get__(desc, self, cls):
return desc.fget(cls)
+ @classproperty
+ def column(cls):
+ return _declared_column
+
+ @classproperty
+ def property(cls):
+ return _declared_property
+
+ defer_defer_defer = False
+
+
+class _memoized_declared_attr(declared_attr):
+ def __init__(self, fget, cascading=False):
+ super(_memoized_declared_attr, self).__init__(fget)
+ self.reg = weakref.WeakKeyDictionary()
+ self._cascading = cascading
+
+ def __get__(desc, self, cls):
+ if desc.defer_defer_defer:
+ return desc
+ elif cls in desc.reg:
+ return desc.reg[cls]
+ else:
+ desc.reg[cls] = obj = desc.fget(cls)
+ return obj
+
+ @classproperty
+ def cascading(cls):
+ return lambda decorated: cls(decorated, cascading=True)
+
+
+class _declared_column(_memoized_declared_attr):
+ pass
+
+
+class _declared_property(_memoized_declared_attr):
+ defer_defer_defer = True
+
+
def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
name='Base', constructor=_declarative_constructor,
diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py
index 94baeeb51..cee22637d 100644
--- a/lib/sqlalchemy/ext/declarative/base.py
+++ b/lib/sqlalchemy/ext/declarative/base.py
@@ -33,7 +33,7 @@ def _declared_mapping_info(cls):
def _as_declarative(cls, classname, dict_):
- from .api import declared_attr
+ from .api import declared_attr, _memoized_declared_attr
# dict_ will be a dictproxy, which we can't write to, and we need to!
dict_ = dict(dict_)
@@ -132,6 +132,14 @@ def _as_declarative(cls, classname, dict_):
"column_property(), relationship(), etc.) must "
"be declared as @declared_attr callables "
"on declarative mixin classes.")
+ elif isinstance(obj, _memoized_declared_attr): # and \
+ if obj._cascading:
+ dict_[name] = ret = obj.__get__(obj, cls)
+ else:
+ dict_[name] = ret = getattr(cls, name)
+ if isinstance(ret, (Column, MapperProperty)) and \
+ ret.doc is None:
+ ret.doc = obj.__doc__
elif isinstance(obj, declarative_props):
dict_[name] = ret = \
column_copies[obj] = getattr(cls, name)
@@ -148,6 +156,7 @@ def _as_declarative(cls, classname, dict_):
clsregistry.add_class(classname, cls)
our_stuff = util.OrderedDict()
+ add_later = util.OrderedDict()
for k in list(dict_):
@@ -157,7 +166,10 @@ def _as_declarative(cls, classname, dict_):
value = dict_[k]
if isinstance(value, declarative_props):
- value = getattr(cls, k)
+ if value.defer_defer_defer:
+ add_later[k] = value
+ else:
+ value = getattr(cls, k)
elif isinstance(value, QueryableAttribute) and \
value.class_ is not cls and \
@@ -324,7 +336,8 @@ def _as_declarative(cls, classname, dict_):
declared_columns,
column_copies,
our_stuff,
- mapper_args_fn)
+ mapper_args_fn,
+ add_later)
if not defer_map:
mt.map()
@@ -339,7 +352,8 @@ class _MapperConfig(object):
inherits,
declared_columns,
column_copies,
- properties, mapper_args_fn):
+ properties, mapper_args_fn,
+ add_later):
self.mapper_cls = mapper_cls
self.cls = cls
self.local_table = table
@@ -348,6 +362,7 @@ class _MapperConfig(object):
self.mapper_args_fn = mapper_args_fn
self.declared_columns = declared_columns
self.column_copies = column_copies
+ self.add_later = add_later
def _prepare_mapper_arguments(self):
properties = self.properties
@@ -410,6 +425,8 @@ class _MapperConfig(object):
self.local_table,
**mapper_args
)
+ for k, v in self.add_later.items():
+ setattr(self.cls, k, v.fget(self.cls))
class _DeferredMapperConfig(_MapperConfig):
diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py
index f3af46c40..ea8c32fd4 100644
--- a/lib/sqlalchemy/sql/schema.py
+++ b/lib/sqlalchemy/sql/schema.py
@@ -1161,8 +1161,10 @@ class Column(SchemaItem, ColumnClause):
existing = getattr(self, 'table', None)
if existing is not None and existing is not table:
raise exc.ArgumentError(
- "Column object already assigned to Table '%s'" %
- existing.description)
+ "Column object '%s' already assigned to Table '%s'" % (
+ self.key,
+ existing.description
+ ))
if self.key in table._columns:
col = table._columns.get(self.key)