AssociationProxy needs to act more like a descriptor; "owning_class" is misleading

Issue #3423 new
Brendan Abel created an issue

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)

  1. Mike Bayer repo owner

    can you make this a little easier for me and show me a script that raises an exception? thanks.

  2. Mike Bayer 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.

  3. Mike Bayer 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.

  4. Brendan Abel 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.

  5. Mike Bayer 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
    
  6. Mike Bayer 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.

  7. Mike Bayer 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...

  8. Mike Bayer 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.

  9. Mike Bayer repo owner

    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.

  10. Mike Bayer 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.

  11. Log in to comment