sqlalchemy: Inheriting from DeclarativeMeta overriding __getattr__ doesn't work with aliased
This is using SQLAlchemy-1.3.3.
I am implementing custom fields on entities inheriting from DeclarativeMeta and overriding __getattr__, please see the code below:
from sqlalchemy import Column, Integer, JSON
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from sqlalchemy.orm import aliased, configure_mappers
class MyDeclarativeMeta(DeclarativeMeta):
FIELDS = frozenset()
def __getattr__(cls, key):
if key in cls.FIELDS:
return cls.fields[key]
return getattr(super(), key)
Base = declarative_base(metaclass=MyDeclarativeMeta)
class A(Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
fields = Column(JSON)
def __getattr__(self, key):
...
def __setattr__(self, key, value):
...
configure_mappers()
A.FIELDS |= {'name'}
print(A.name)
# a.fields[:fields_1]
print(aliased(A).name)
# Traceback (most recent call last):
# File "main.py", line 38, in <module>
# print(aliased(A).name)
# File "/usr/local/lib/python3.5/site-packages/sqlalchemy/orm/util.py", line 506, in __getattr__
# raise AttributeError(key)
# AttributeError: name
So A.name works, but aliased(A).name fails.
Am I doing it wrong?
I see AliasedClass.__getattr__ uses object.__getattribute__ which doesn’t call my method…
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 31 (29 by maintainers)
Thanks a lot @zzzeek for assistance and quick fix!
once that passes I will merge both
yes, sorry this was not a simple change but you did write the tests which I am grateful for
basically we need to go through every test and document every kind of object that comes through this method and what the usage contract should be. then we can try changing it to call getattr() and unwrap what it gets, rather than trying to bypass the attribute mechanics. the change above is one example of getting the full object and unwrapping it, for a very specific case of “unwrap”.
that is, the code should look like:
I’d really have to fully reopen all the use cases for this and fully re-understand the whole thing to write this such that it can expect sub-callables the way it’s doing. The quick hack to make test two work is:
but this is too hacky, it’s very specific to that one test. There also seems to be some uncovered code in here as well, the “func_code” path, there’s no “util.types.MethodType()” so that code is not even used, coverage at https://jenkins.sqlalchemy.org/job/sqlalchemy_coverage/758/cobertura/lib_sqlalchemy_orm/util_py/ confirms it. needs cleanup
I can get test_meta_getattr_two to work. test_meta_getattr_three looks crazy and that doesn’t work at all. You can’t need it to do that.
perfect! that’s exactly right
I’m done releasing 1.2 releases we are well into 1.3 at this point, the upgrade path should be mostly effortless. This is already a bit of a destabilizing change depending on how it goes.
always work in master, we always backport
yes so you can just work with a normal github PR against master here, and I have an elaborate integration with a code review system called Gerrit that you don’t have to deal with at all, you will see code review comments and whatnot on your pull request.
That stuff is extremely sensitive because there are tons of use cases packed into the attribute mechanics at this point, all of which should be tested and all of which have to continue working. None of these attribute systems started out this complicated, it’s new behavioral requests like this one over the years that has made everything so packed such that if you change anything, tests break.
sure I thought the same thing.
an overview of how to run the test suite is at https://github.com/sqlalchemy/sqlalchemy/blob/master/README.unittests.rst
here’s how to run the tests en masse, assuming you have tox:
if you want to run the tests more surgically, here’s more of that, assuming you have nothing else set up but virtualenv:
basically for us to make this change means we are officially supporting aliased() of an ORM class that defines its own
__getattr__()so there needs to be tests that ensure this, otherwise it will just change back someday