diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2018-07-09 18:24:12 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2018-07-09 18:24:12 -0400 |
| commit | f34f634824da18a71f3c898a2e19e19e5f26bede (patch) | |
| tree | 43c07ea44f5e05c2b17f139258c9cd135ff4a38d /lib | |
| parent | 941143858ba949c3a4a2dfcc5cd710ae6d4146e1 (diff) | |
| download | sqlalchemy-f34f634824da18a71f3c898a2e19e19e5f26bede.tar.gz | |
Expire memoizations on setattr/delattr, check in delattr
Fixed bug where declarative would not update the state of the
:class:`.Mapper` as far as what attributes were present, when additional
attributes were added or removed after the mapper attribute collections had
already been called and memoized. Addtionally, a ``NotImplementedError``
is now raised if a fully mapped attribute (e.g. column, relationship, etc.)
is deleted from a class that is currently mapped, since the mapper will not
function correctly if the attribute has been removed.
Change-Id: Idaca8e0237b31aa1d6564d94c3a179d7dc6b5df9
Fixes: #4133
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/ext/declarative/api.py | 4 | ||||
| -rw-r--r-- | lib/sqlalchemy/ext/declarative/base.py | 19 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 2 |
3 files changed, 24 insertions, 1 deletions
diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py index b08d3ce30..865cd16f0 100644 --- a/lib/sqlalchemy/ext/declarative/api.py +++ b/lib/sqlalchemy/ext/declarative/api.py @@ -21,7 +21,7 @@ import re from .base import _as_declarative, \ _declarative_constructor,\ - _DeferredMapperConfig, _add_attribute + _DeferredMapperConfig, _add_attribute, _del_attribute from .clsregistry import _class_resolver @@ -68,6 +68,8 @@ class DeclarativeMeta(type): def __setattr__(cls, key, value): _add_attribute(cls, key, value) + def __delattr__(cls, key): + _del_attribute(cls, key) def synonym_for(name, map_column=False): """Decorator that produces an :func:`.orm.synonym` attribute in conjunction diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py index 5d0eab34e..544bb2497 100644 --- a/lib/sqlalchemy/ext/declarative/base.py +++ b/lib/sqlalchemy/ext/declarative/base.py @@ -677,10 +677,29 @@ def _add_attribute(cls, key, value): ) else: type.__setattr__(cls, key, value) + cls.__mapper__._expire_memoizations() else: type.__setattr__(cls, key, value) +def _del_attribute(cls, key): + + if '__mapper__' in cls.__dict__ and \ + key in cls.__dict__ and not cls.__mapper__._dispose_called: + value = cls.__dict__[key] + if isinstance( + value, + (Column, ColumnProperty, MapperProperty, QueryableAttribute) + ): + raise NotImplementedError( + "Can't un-map individual mapped attributes on a mapped class.") + else: + type.__delattr__(cls, key) + cls.__mapper__._expire_memoizations() + else: + type.__delattr__(cls, key) + + def _declarative_constructor(self, **kwargs): """A simple constructor that allows initialization from kwargs. diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index a30a8c243..e856c6c79 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -86,6 +86,7 @@ class Mapper(InspectionAttr): """ _new_mappers = False + _dispose_called = False def __init__(self, class_, @@ -1274,6 +1275,7 @@ class Mapper(InspectionAttr): def dispose(self): # Disable any attribute-based compilation. self.configured = True + self._dispose_called = True if hasattr(self, '_configure_failed'): del self._configure_failed |
