Description
Python 3.7.4
SQLAlchemy 1.3.7
graphene-sqlalchemy 2.2.2
Flask 1.1.1
Flask-SQLAlchemy 2.4.0
psycopg2 2.8.3
Linux xxx 5.1.21-200.fc29.x86_64 #1 SMP Mon Jul 29 15:30:04 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
The following code snippets show just a simple One-to-Many Relationship. I tried it with and back_populates and with backref, Uni- and Bidirectional. I also tried Many-to-Many relationships with a association Table. But nothing helped.
I always get the error shown in the last Bash-snippet.
Whats wrong here?
I found out, that sqlalchemy.orm.relationships.RelationshipProperty give that exception when I try to call .entity on it.
I already opened an issue on the sqlalchemy github https://github.com/sqlalchemy/sqlalchemy/issues/4819 and got the answer above. Hopefully it helps you to help me to fix this issue ;)
that stack trace is not very easy to create as it involves an unusual attribute error being generated when mappings are being resolved, and it is masquerading as an attribute error for the "entity" attribute, which is in fact a function. in python 3, any ofher kind of exception will be displayed as is, so it's very strange for it to be an attribute error.
in case that doesn't make sense, it means there is another exception happening that we're not able to see.
I unfortunately cannot reproduce your error with your mappings. The condition may be due to whatever graphene-sqlalchemy is doing, The method call here:
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/graphene/utils/subclass_with_meta.py", line 52, in init_subclass
super_class.init_subclass_with_meta(**options)which is then calling down to class_mapper(), looks suspicious to me; I would first off not be using a metaclass for anything in conjunction wtih SQLAlchemy declarative because metaclasses are very unwieldy and declarative is already using one (they should use mixin classes or class decorators for whatever it is they need to add to SQLAlchemy models) and if I were, I'd not be trying to run class_mapper() inside of it as this could in theory lead to some difficult re-entrant conditions which is likely what's happening here.
in short I think you need to report this to graphene-sqlalchemy.
It is happen in sqlalchemy/orm/mapper.py(1947)_post_configure_properties() on line 1947
1932 def _post_configure_properties(self): │
1933 """Call the ``init()`` method on all ``MapperProperties`` │
1934 attached to this mapper. │
1935 │
1936 This is a deferred configuration step which is intended │
1937 to execute once all mappers have been constructed. │
1938 │
1939 """ │
1940 │
1941 self._log("_post_configure_properties() started") │
1942 l = [(key, prop) for key, prop in self._props.items()] │
1943 for key, prop in l: │
1944 self._log("initialize prop %s", key) │
1945 │
1946 if prop.parent is self and not prop._configure_started: │
1947 -> prop.init() │
1948 │
1949 if prop._configure_finished: │
1950 prop.post_instrument_class(self) │
1951
(Pdb) dir(prop)
['Comparator', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__','__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', '_add_reverse_property', '_all_strategies', '_cascade', '_check_cascade_settings', '_check_conflicts', '_columns_are_mapped', '_configure_finished', '_configure_started', '_create_joins', '_creation_order', '_default_path_loader_key', '_dependency_processor', '_fallback_getattr', '_generate_backref', '_get_attr_w_warn_on_none', '_get_cascade', '_get_context_loader', '_get_strategy', '_is_internal_proxy', '_is_self_referential', '_lazy_none_clause', '_memoized_attr__default_path_loader_key', '_memoized_attr_wildcard_token', '_memoized_attr_info', '_optimized_compare', '_persists_for', '_post_init', '_process_dependent_arguments', '_reverse_property', '_set_cascade', '_setup_join_conditions', '_should_log_debug', '_should_log_info', '_strategies', '_strategy_lookup', '_use_get', '_user_defined_foreign_keys', '_value_as_iterable', '_wildcard_token', '_with_parent', 'active_history', 'argument', 'back_populates', 'backref', 'bake_queries', 'cascade', 'cascade_backrefs', 'cascade_iterator', 'class_attribute', 'collection_class', 'comparator', 'comparator_factory', 'create_row_processor', 'direction', 'distinct_target_key', 'do_init', 'doc', 'enable_typechecks', 'entity', 'extension', 'extension_type', 'info', 'init', 'innerjoin', 'instrument_class', 'is_aliased_class', 'is_attribute', 'is_clause_element', 'is_instance', 'is_mapper', 'is_property', 'is_selectable', 'join_depth', 'key', 'lazy', 'load_on_pending', 'local_remote_pairs', 'logger', 'mapper', 'merge', 'omit_join', 'order_by', 'parent', 'passive_deletes', 'passive_updates', 'post_instrument_class', 'post_update', 'primaryjoin', 'query_class', 'remote_side', 'secondary', 'secondaryjoin', 'set_parent', 'setup', 'single_parent', 'strategy', 'strategy_for', 'strategy_key', 'strategy_wildcard_key', 'uselist', 'viewonly']
(Pdb) prop.entity
*** AttributeError: entity
(Pdb) prop.__class__
<class 'sqlalchemy.orm.relationships.RelationshipProperty'>
app/models/roles.py
from ..models import db, bcrypt
from sqlalchemy import Column, Integer, String, Boolean, Binary
class Role(db.Model):
__tablename__ = "roles"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(80), unique=True)
description = Column(String(255))
users = db.relationship("models.User", backref=db.backref('role', lazy='joined'), lazy=True)
def __repr__(self):
return '<Role %r>' % (self.name)
__all__ = [ Role ]
app/models/users.py
from ..models import db, bcrypt
from sqlalchemy import Column, Integer, String, Boolean, Binary, DateTime, Text, ForeignKey
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
class User(db.Model):
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
first_name = Column(String(255), nullable=False)
last_name = Column(String(255), nullable=False)
email = Column(String(255), unique=True, nullable=False)
public_key = Column(Text, unique=True)
_secret_access_key = Column(Binary(60), unique=True)
access_key_id = Column(String(255), unique=True)
active = Column(Boolean())
confirmed_at = Column(DateTime())
confirmation_token = Column(String(255), unique=True)
confirmation_sent_at = Column(DateTime())
role_id = Column(Integer, ForeignKey("roles.id"), nullable=False)
@hybrid_property
def secret_access_key(self):
return self._secret_access_key
@secret_access_key.setter
def secret_access_key(self, plaintext_key):
self._secret_access_key = bcrypt.generate_password_hash(plaintext_key, 15)
@hybrid_method
def is_correct_secret_access_key(self, plaintext_key):
return bcrypt.check_password_hash(self.secret_access_key, plaintext_key)
def __repr__(self):
return '<User %r %r>' % (self.first_name, self.last_name)
__all__ = [ User ]
app/models/init.py
from flask.cli import with_appcontext
import click
from flask.cli import AppGroup
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
import pdb
db = SQLAlchemy()
bcrypt = Bcrypt()
role_cli = AppGroup('role')
user_cli = AppGroup('user')
def init_app(app):
print("app.models.__init__.init_app()")
db.init_app(app)
bcrypt.init_app(app)
app.cli.add_command(role_cli)
app.cli.add_command(user_cli)
app.cli.add_command(init_db)
app.cli.add_command(seed)
@click.command('init-db')
@with_appcontext
def init_db():
pdb.set_trace()
db.create_all()
click.echo('Initialized the database.')
@role_cli.command('create')
@click.argument("name")
@click.argument("description")
@with_appcontext
def create_role(name, description):
from .roles import Role
role = Role(name=name, description=description)
db.session.add(role)
db.session.commit()
click.echo('Role created.')
@user_cli.command('create')
@click.argument("first_name")
@click.argument("last_name")
@click.argument("email")
@with_appcontext
def create_role(first_name, last_name, email):
from .users import User
from ..tasks.mailer import send_confirmation_mail
user = User(first_name=first_name, last_name=last_name, email=email)
db.session.add(user)
db.session.commit()
send_confirmation_mail.delay({'subject': 'Please complete your registration confirmation', 'to': user.email, 'from': 'xxx@yyy.zz'})
click.echo('User created.')
@click.command('seed')
@with_appcontext
def seed():
from .roles import Role
from .users import User
from flask import current_app
from datetime import datetime
import secrets
entities = []
entities.append(Role(name='Admin', description='Administrator'))
entities.append(Role(name='ClusterAdmin', description='Administrator of one Redis Cluster'))
secret_access_key=secrets.token_hex()
entities.append(User(
first_name=current_app.config["ADMIN"]["FIRST_NAME"],
last_name=current_app.config["ADMIN"]["LAST_NAME"],
email=current_app.config["ADMIN"]["EMAIL"],
confirmed_at=datetime.now(),
public_key=current_app.config["ADMIN"]["PUBLIC_KEY"],
access_key_id=secrets.token_hex(),
secret_access_key=secret_access_key
))
for entity in entities:
try:
db.session.add(entity)
db.session.commit()
click.echo("Add Entity " + str(entity) +" to Database.")
if isinstance(entity, User):
click.echo("SECRET_ACCESS_KEY: " + secret_access_key)
click.echo("ACCESS_KEY_ID: " + entity.access_key_id)
except Exception as err:
click.echo("Entity " + str(entity) + " already exist!")
click.echo('Database seeding Done.')
from .users import User
from .roles import Role
from .clusters import Cluster
from .groups import Group
from .workers import Worker
__all__ = [Role, User, Worker, Cluster, Group]
(Qubic) [david@doha Qubic]$ export FLASK_APP=app
(Qubic) [david@doha Qubic]$ export FLASK_ENV=development
(Qubic) [david@doha Qubic]$ flask init-db
app.schemas.user.UserObject
Traceback (most recent call last):
File "/home/david/projects/Qubic/bin/flask", line 11, in <module>
sys.exit(main())
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/flask/cli.py", line 966, in main
cli.main(prog_name="python -m flask" if as_module else None)
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/flask/cli.py", line 586, in main
return super(FlaskGroup, self).main(*args, **kwargs)
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/click/core.py", line 717, in main
rv = self.invoke(ctx)
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/click/core.py", line 1132, in invoke
cmd_name, cmd, args = self.resolve_command(ctx, args)
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/click/core.py", line 1171, in resolve_command
cmd = self.get_command(ctx, cmd_name)
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/flask/cli.py", line 542, in get_command
rv = info.load_app().cli.get_command(ctx, name)
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/flask/cli.py", line 388, in load_app
app = locate_app(self, import_name, name)
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/flask/cli.py", line 240, in locate_app
__import__(module_name)
File "/home/david/projects/Qubic/app/__init__.py", line 6, in <module>
from . import schemas
File "/home/david/projects/Qubic/app/schemas/__init__.py", line 14, in <module>
from .user import UserObject, UserObjectConnection, CreateUser, UpdateUser, DeleteUser, DeleteAllUser, ConfirmUser
File "/home/david/projects/Qubic/app/schemas/user.py", line 18, in <module>
class UserObject(SQLAlchemyObjectType):
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/graphene/utils/subclass_with_meta.py", line 52, in __init_subclass__
super_class.__init_subclass_with_meta__(**options)
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/graphene_sqlalchemy/types.py", line 224, in __init_subclass_with_meta__
assert is_mapped_class(model), (
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/graphene_sqlalchemy/utils.py", line 28, in is_mapped_class
class_mapper(cls)
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/sqlalchemy/orm/base.py", line 441, in class_mapper
mapper = _inspect_mapped_class(class_, configure=configure)
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/sqlalchemy/orm/base.py", line 420, in _inspect_mapped_class
mapper._configure_all()
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/sqlalchemy/orm/mapper.py", line 1337, in _configure_all
configure_mappers()
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/sqlalchemy/orm/mapper.py", line 3229, in configure_mappers
mapper._post_configure_properties()
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/sqlalchemy/orm/mapper.py", line 1947, in _post_configure_properties
prop.init()
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/sqlalchemy/orm/interfaces.py", line 196, in init
self.do_init()
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/sqlalchemy/orm/relationships.py", line 1860, in do_init
self._process_dependent_arguments()
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/sqlalchemy/orm/relationships.py", line 1922, in _process_dependent_arguments
self.target = self.entity.persist_selectable
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/sqlalchemy/util/langhelpers.py", line 949, in __getattr__
return self._fallback_getattr(key)
File "/home/david/projects/Qubic/lib64/python3.7/site-packages/sqlalchemy/util/langhelpers.py", line 923, in _fallback_getattr
raise AttributeError(key)
AttributeError: entity