Skip to content

AttributeError: entity #245

Closed
Closed
@david-freistrom

Description

@david-freistrom

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            """19401941            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)                                                                                                                            │
19451946                if prop.parent is self and not prop._configure_started:                                                                                                         │
1947 ->                 prop.init()                                                                                                                                                 │
19481949                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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions