summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-09-24 20:53:29 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2014-09-24 20:53:29 -0400
commit3b79eafdbf3b8be84c6586fa699daceea7fa5fd7 (patch)
tree210b535d794ab1cf3bf1e483b4d32bfa466cfb97
parent4cfed27b451188240e2fb3379aff5be5bd2e33d8 (diff)
downloadsqlalchemy-3b79eafdbf3b8be84c6586fa699daceea7fa5fd7.tar.gz
- simplify. make the base declared_attr memoized.
-rw-r--r--lib/sqlalchemy/ext/declarative/__init__.py8
-rw-r--r--lib/sqlalchemy/ext/declarative/api.py69
-rw-r--r--lib/sqlalchemy/ext/declarative/base.py22
-rw-r--r--lib/sqlalchemy/util/__init__.py3
-rw-r--r--lib/sqlalchemy/util/langhelpers.py13
-rw-r--r--test/ext/declarative/test_inheritance.py2
-rw-r--r--test/ext/declarative/test_mixin.py49
7 files changed, 68 insertions, 98 deletions
diff --git a/lib/sqlalchemy/ext/declarative/__init__.py b/lib/sqlalchemy/ext/declarative/__init__.py
index dc9c55c09..9f3f36f0f 100644
--- a/lib/sqlalchemy/ext/declarative/__init__.py
+++ b/lib/sqlalchemy/ext/declarative/__init__.py
@@ -921,7 +921,7 @@ reference a common target class via many-to-one::
id = Column(Integer, primary_key=True)
When using a mixin to specify a relationship or other mapper property,
-the :meth:`.declared_attr.property` modifier is often helpful, as it
+the :meth:`.declared_attr.after_mapping` modifier is often helpful, as it
indicates that the callable should not be invoked at all until the
target class is fully mapped::
@@ -930,7 +930,7 @@ target class is fully mapped::
def target_id(cls):
return Column('target_id', ForeignKey('target.id'))
- @declared_attr.property
+ @declared_attr.after_mapping
def target(cls):
return relationship("Target")
@@ -1019,14 +1019,14 @@ requirement so that no reliance on copying is needed::
More advanced properties like that of a :func:`.column_property` which refers
to other columns that are mapped to the class should make use of
-:meth:`.declared_attr.property`, to ensure that the function is invoked
+:meth:`.declared_attr.after_mapping`, to ensure that the function is invoked
only after all columns are mapped on the target class::
class SomethingMixin(object):
x = Column(Integer)
y = Column(Integer)
- @declared_attr.property
+ @declared_attr.after_mapping
def x_plus_y(cls):
return column_property(cls.x + cls.y)
diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py
index 149cedbb6..99ec080f5 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, classproperty
+from ...util import OrderedDict, classproperty, hybridmethod, hybridproperty
from ... import exc
import weakref
@@ -157,59 +157,48 @@ class declared_attr(interfaces._MappedAttribute, property):
"""
- def __init__(self, fget, *arg, **kw):
- super(declared_attr, self).__init__(fget, *arg, **kw)
+ def __init__(self, fget, cascading=False, defer_until_mapping=False):
+ super(declared_attr, self).__init__(fget)
self.__doc__ = fget.__doc__
-
- def __get__(desc, self, cls):
- return desc.fget(cls)
-
- @classproperty
- def cascading(cls):
- return _memoized_declared_attr.cascading
-
- @classproperty
- def memoized(cls):
- return _memoized_declared_attr
-
- @classproperty
- def column(cls):
- return _declared_column
-
- @classproperty
- def property(cls):
- return _declared_property
-
- defer_until_mapping = 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._reg = weakref.WeakKeyDictionary()
self._cascading = cascading
+ self._defer_until_mapping = defer_until_mapping
def __get__(desc, self, cls):
- if desc.defer_until_mapping:
+ if desc._defer_until_mapping:
return desc
- elif cls in desc.reg:
- return desc.reg[cls]
+ elif cls in desc._reg:
+ return desc._reg[cls]
else:
- desc.reg[cls] = obj = desc.fget(cls)
+ desc._reg[cls] = obj = desc.fget(cls)
return obj
- @classproperty
+ @hybridmethod
+ def _stateful(cls, **kw):
+ return _stateful_declared_attr(**kw)
+
+ @hybridproperty
def cascading(cls):
- return lambda decorated: cls(decorated, cascading=True)
+ return cls._stateful(cascading=True)
+
+ @hybridproperty
+ def after_mapping(cls):
+ return cls._stateful(defer_until_mapping=True)
+ defer_until_mapping = False
-class _declared_column(_memoized_declared_attr):
- pass
+class _stateful_declared_attr(declared_attr):
+ def __init__(self, **kw):
+ self.kw = kw
-class _declared_property(_memoized_declared_attr):
- defer_until_mapping = True
+ def _stateful(self, **kw):
+ new_kw = self.kw.copy()
+ new_kw.update(kw)
+ return _stateful_declared_attr(**new_kw)
+ def __call__(self, fn):
+ return declared_attr(fn, **self.kw)
def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py
index 526af2823..4266da852 100644
--- a/lib/sqlalchemy/ext/declarative/base.py
+++ b/lib/sqlalchemy/ext/declarative/base.py
@@ -55,7 +55,7 @@ def _get_immediate_cls_attr(cls, attrname):
def _as_declarative(cls, classname, dict_):
- from .api import declared_attr, _memoized_declared_attr
+ from .api import declared_attr
# dict_ will be a dictproxy, which we can't write to, and we need to!
dict_ = dict(dict_)
@@ -159,17 +159,17 @@ 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):
+ elif isinstance(obj, declarative_props):
+ if isinstance(obj, util.classproperty):
+ util.warn_deprecated(
+ "Use of sqlalchemy.util.classproperty on "
+ "declarative classes is deprecated.")
if obj._cascading:
- dict_[name] = ret = obj.__get__(obj, cls)
+ dict_[name] = column_copies[obj] = \
+ 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)
+ dict_[name] = column_copies[obj] = \
+ ret = getattr(cls, name)
if isinstance(ret, (Column, MapperProperty)) and \
ret.doc is None:
ret.doc = obj.__doc__
@@ -193,7 +193,7 @@ def _as_declarative(cls, classname, dict_):
value = dict_[k]
if isinstance(value, declarative_props):
- if value.defer_until_mapping:
+ if value._defer_until_mapping:
add_later[k] = value
else:
value = getattr(cls, k)
diff --git a/lib/sqlalchemy/util/__init__.py b/lib/sqlalchemy/util/__init__.py
index c963b18c3..dfed5b90a 100644
--- a/lib/sqlalchemy/util/__init__.py
+++ b/lib/sqlalchemy/util/__init__.py
@@ -33,7 +33,8 @@ from .langhelpers import iterate_attributes, class_hierarchy, \
duck_type_collection, assert_arg_type, symbol, dictlike_iteritems,\
classproperty, set_creation_order, warn_exception, warn, NoneType,\
constructor_copy, methods_equivalent, chop_traceback, asint,\
- generic_repr, counter, PluginLoader, hybridmethod, safe_reraise,\
+ generic_repr, counter, PluginLoader, hybridproperty, hybridmethod, \
+ safe_reraise,\
get_callable_argspec, only_once, attrsetter, ellipses_string, \
warn_limited
diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py
index 76f85f605..35d3b7174 100644
--- a/lib/sqlalchemy/util/langhelpers.py
+++ b/lib/sqlalchemy/util/langhelpers.py
@@ -1090,10 +1090,21 @@ class classproperty(property):
return desc.fget(cls)
+class hybridproperty(object):
+ def __init__(self, func):
+ self.func = func
+
+ def __get__(self, instance, owner):
+ if instance is None:
+ return self.func(owner)
+ else:
+ return self.func(instance)
+
+
class hybridmethod(object):
"""Decorate a function as cls- or instance- level."""
- def __init__(self, func, expr=None):
+ def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
diff --git a/test/ext/declarative/test_inheritance.py b/test/ext/declarative/test_inheritance.py
index f9e0a9833..d8ee0c19f 100644
--- a/test/ext/declarative/test_inheritance.py
+++ b/test/ext/declarative/test_inheritance.py
@@ -1327,7 +1327,7 @@ class ConcreteExtensionConfigTest(
counter(cls, "something")
return relationship("Something")
- @declared_attr.property
+ @declared_attr.after_mapping
def something_else(cls):
counter(cls, "something_else")
return relationship("Something")
diff --git a/test/ext/declarative/test_mixin.py b/test/ext/declarative/test_mixin.py
index da1328d69..00adca834 100644
--- a/test/ext/declarative/test_mixin.py
+++ b/test/ext/declarative/test_mixin.py
@@ -1305,7 +1305,7 @@ class DeclarativeMixinPropertyTest(DeclarativeTestBase):
class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL):
__dialect__ = 'default'
- def test_plain_called_repeatedly(self):
+ def test_singleton_behavior(self):
counter = mock.Mock()
class Mixin(object):
@@ -1321,37 +1321,6 @@ class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL):
@declared_attr
def my_other_prop(cls):
return column_property(cls.my_prop + 5)
- eq_(counter.mock_calls, [mock.call(A), mock.call(A)])
-
- class B(Base, Mixin):
- __tablename__ = 'b'
- id = Column(Integer, primary_key=True)
-
- @declared_attr
- def my_other_prop(cls):
- return column_property(cls.my_prop + 5)
-
- eq_(
- counter.mock_calls,
- [mock.call(A), mock.call(A), mock.call(B), mock.call(B)]
- )
-
- def test_singleton_called_once(self):
- counter = mock.Mock()
-
- class Mixin(object):
- @declared_attr.memoized
- def my_prop(cls):
- counter(cls)
- return Column('x', Integer)
-
- class A(Base, Mixin):
- __tablename__ = 'a'
- id = Column(Integer, primary_key=True)
-
- @declared_attr
- def my_other_prop(cls):
- return column_property(cls.my_prop + 5)
eq_(counter.mock_calls, [mock.call(A)])
@@ -1369,7 +1338,7 @@ class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL):
counter = mock.Mock()
class Mixin(object):
- @declared_attr.memoized
+ @declared_attr
def my_prop(cls):
counter(cls.__name__)
return Column('x', Integer)
@@ -1386,13 +1355,13 @@ class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL):
del A
gc_collect()
assert "A" not in Base._decl_class_registry
- assert not Mixin.__dict__['my_prop'].reg
+ assert not Mixin.__dict__['my_prop']._reg
def test_property_noncascade(self):
counter = mock.Mock()
class Mixin(object):
- @declared_attr.property
+ @declared_attr.after_mapping
def my_prop(cls):
counter(cls)
return column_property(cls.x)
@@ -1412,7 +1381,7 @@ class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL):
counter = mock.Mock()
class Mixin(object):
- @declared_attr.property.cascading
+ @declared_attr.after_mapping.cascading
def my_prop(cls):
counter(cls)
return column_property(cls.x)
@@ -1432,7 +1401,7 @@ class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL):
counter = mock.Mock()
class Mixin(object):
- @declared_attr.column
+ @declared_attr
def my_col(cls):
counter(cls)
assert not orm_base._mapper_or_none(cls)
@@ -1449,7 +1418,7 @@ class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL):
counter = mock.Mock()
class Mixin(object):
- @declared_attr.property
+ @declared_attr.after_mapping
def my_prop(cls):
counter(cls)
assert orm_base._mapper_or_none(cls) is cls.__mapper__
@@ -1464,7 +1433,7 @@ class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL):
def test_column_prop(self):
- # this is the use case for .property.
+ # this is the use case for .after_mapping
# we don't want address_count() to run against
# the "id" column until we know we will get the
# one that's mapped.
@@ -1472,7 +1441,7 @@ class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL):
class HasAddressCount(object):
id = Column(Integer, primary_key=True)
- @declared_attr.property
+ @declared_attr.after_mapping
def address_count(cls):
return column_property(
select([func.count(Address.id)]).