- edited description
AssociationProxy needs to act more like a descriptor; "owning_class" is misleading
This is using sqlalchemy 0.9.8
Assuming a declarative class called "MyClass" with an association proxy field call "my_field"
>>> ap = MyClass.__mapper__.all_orm_descriptors['my_field']
>>> ap.owning_class
None
>>> ap = MyClass.my_field
>>> ap.owning_class
<class 'MyClass'>
>>> ap = MyClass.__mapper__.all_orm_descriptors['my_field']
>>> ap.owning_class
<class 'MyClass'>
Without the owning_class set, most of the other properties will raise exceptions when trying to access them. This can be problematic when using the mapper class to procedurally access fields via the mapper.
Comments (16)
-
reporter -
repo owner can you make this a little easier for me and show me a script that raises an exception? thanks.
-
repo owner I say this partially because the assocationproxy is a Python descriptor. It should not be called outside of the context of being bound to a class (e.g. from all_orm_descriptors). the all_orm_descriptors use case is strictly so you can get at the .info dictionary.
-
reporter - attached aptest.py
Example script reproducing error.
-
repo owner yeah not sure what can be done here. It's a Python descriptor. It has no idea what the class is until it is called. There is no internal system to support objects that are not mapped attributes such that they get "blessed" as part of a mapping system with a full lifecycle.
-
reporter Just attached a short script that reproduces the error. It's basically the same example used in the Association Proxy docs.
My current use case is for a REST framework that automatically unserializes JSON data that corresponds to a table field. It's currently doing this by getting the column/relationship/association_proxy from the mapper and using the data type to decide how to process the JSON data. I'm using the all_orm_descriptors because that was the only place in the mapper (AFAIK) that listed the association proxies.
-
repo owner for example, if you initialized it as Python expects, then it's fine:
User.__mapper__.all_orm_descriptors['keywords'].__get__(None, User).target_class
-
repo owner here's one reason it can't work completely - because mapping has no metaclass requirement, and without that we have no way to know when an attribute is added to a class. So even if we did some kind of "sweep" for attributes that are on the class at the time that we map it, that would fail when the attribute were added after the fact and therefore be inconsistent in its behavior.
-
repo owner though that case indicates that all_orm_descriptors is then broken, because it memoizes on first access and wouldn't pick up such attributes anyway...
-
repo owner a related issue also came up recently regarding hybrid properties. The object that we get back from all_orm_descriptors is again the descriptor itself; it doesn't represent any SQL expression by itself, only when its invoked. There is a path for that to work also, if it knew its owning class up front.
-
repo owner - attached rework_attribute_proxy_owner.patch
the association proxy mis-implements Python descriptors in any case because it attempts to refer to a single class as state, and this is incorrect. this patch begins to split it out into two objects. the per-owner class needs to be put into a weakref dictionary of some kind so that it can be cached.
-
repo owner There still needs to be something done with all_orm_descriptors such that differentiating between a "descriptor" and the "actual thing the descriptor returns" is more clear, for assocaition proxies, hybrids, and other things; the latter always requires a parent object in order to be correct, and it cannot be assumed to be the same class every time. When you call an AP from an AliasedClass like aliased(User) for example, that is different than calling the original. the AP needs to be refactored to work in this way cleanly, more like a hybrid does.
-
repo owner -
repo owner - changed milestone to 1.2
this should be done but for now trying to trim down 1.1 to essentials
-
repo owner - changed milestone to 1.3
-
Why this was pushed to 1.2 and then to 1.3? Isn't this a major (priority) bug (type)?
I hit a bug that may be related to this, which is a AssociationProxy applied to a AliasedClass generated by with_polymorphic.
It raises the exception at https://bitbucket.org/zzzeek/sqlalchemy/src/a1de76c42f6b64808448aed6e821fbb3b988f99b/lib/sqlalchemy/orm/base.py?at=master&fileviewer=file-view-default#base.py-424
- Log in to comment