1

I'm building a DB, that matches my local product catalogue with foreign ones. When I had the only table 'Catalog' SQLAlchemy worked fine. After adding tables Competitors and Competitors_catalog all my attempts to add records to both new tables returned with error.

AttributeError: 'Table' object has no attribute 'id'

I change/remove/play with relationship between Competitors and Competitors_catalog several times, drop and create tables through flask-migrate back and forth, but keep receiving same error. Simple query like Competitors.query.all() returns the same. Removed db.relationship() - same result. Then I finally rolled back to a clear db and initiated it with flask-migrate from scratch. After that I begin to receive same error trying to add records to Catalog table, which worked fine before I rolled back. Which is odd since I made no changes to that part of the code.

class Catalog(db.Model):
    __tablename__ = 'catalog'

    id            = db.Column(db.Integer, primary_key=True)
    name          = db.Column(db.String(60), index=True, nullable=False)
    parent_id     = db.Column(db.Integer, db.ForeignKey('catalog.id'), default=None)
    url           = db.Column(db.String())
    created       = db.Column(db.DateTime, default=datetime.now())
    last_modified = db.Column(db.DateTime, index=True, onupdate=datetime.now())

    categories = db.relationship('Catalog', remote_side='catalog.id', backref='parent')

class Competitors(db.Model):
    __tablename__ = 'competitors'

    id            = db.Column(db.Integer, primary_key=True)
    name          = db.Column(db.String(40), index=True, nullable=False)
    base_url      = db.Column(db.String())
    cat_url       = db.Column(db.String())
    created       = db.Column(db.DateTime, default=datetime.now())
    last_modified = db.Column(db.DateTime, onupdate=datetime.now(), default=datetime.now())

    sells = db.relationship('Competitors_catalog', backref='competitor', cascade="all, delete-orphan", passive_deletes=True, lazy='dynamic')

class CompCatalog(db.Model):
    __tablename__ = 'compcatalog'

    id            = db.Column(db.Integer, primary_key=True)
    out_id        = db.Column(db.Integer)
    name          = db.Column(db.String(60), index=True, nullable=False)
    top_section   = db.Column(db.Integer, default=None)
    comp_id       = db.Column(db.Integer, db.ForeignKey('competitors.id', ondelete="CASCADE"))
    url           = db.Column(db.String())
    created       = db.Column(db.DateTime, default=datetime.now())
    last_modified = db.Column(db.DateTime, index=True, onupdate=datetime.now())

Simple creation of new instance returns error. But worked fine before roll back.

from .models import Catalog

name = 'Apple'
rec = Catalog(name=name)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File ".../app/helpers.py", line 31, in create_catalog
    rec = Catalog(name=name)
  File "<string>", line 2, in __init__
  File ".../.virtualenvs/.../lib/python3.6/site-packages/sqlalchemy/orm/instrumentation.py", line 376, in _new_state_if_none
    state = self._state_constructor(instance, self)
  File ".../.virtualenvs/.../lib/python3.6/site-packages/sqlalchemy/util/langhelpers.py", line 855, in __get__
    obj.__dict__[self.__name__] = result = self.fget(obj)
  File ".../.virtualenvs/.../lib/python3.6/site-packages/sqlalchemy/orm/instrumentation.py", line 202, in _state_constructor
    self.dispatch.first_init(self, self.class_)
  File ".../.virtualenvs/.../lib/python3.6/site-packages/sqlalchemy/event/attr.py", line 322, in __call__
    fn(*args, **kw)
  File ".../.virtualenvs/.../lib/python3.6/site-packages/sqlalchemy/orm/mapper.py", line 3360, in _event_on_first_init
    configure_mappers()
  File ".../.virtualenvs/.../lib/python3.6/site-packages/sqlalchemy/orm/mapper.py", line 3248, in configure_mappers
    mapper._post_configure_properties()
  File ".../.virtualenvs/.../lib/python3.6/site-packages/sqlalchemy/orm/mapper.py", line 1947, in _post_configure_properties
    prop.init()
  File ".../.virtualenvs/.../lib/python3.6/site-packages/sqlalchemy/orm/interfaces.py", line 196, in init
    self.do_init()
  File ".../.virtualenvs/.../lib/python3.6/site-packages/sqlalchemy/orm/relationships.py", line 1860, in do_init
    self._process_dependent_arguments()
  File ".../.virtualenvs/.../lib/python3.6/site-packages/sqlalchemy/orm/relationships.py", line 1889, in _process_dependent_arguments
    setattr(self, attr, attr_value())
  File ".../.virtualenvs/.../lib/python3.6/site-packages/sqlalchemy/ext/declarative/clsregistry.py", line 294, in __call__
    x = eval(self.arg, globals(), self._dict)
  File "<string>", line 1, in <module>
AttributeError: 'Table' object has no attribute 'id'

1 Answer 1

2

The remote_side argument can be passed as an evaluable string, which you've done:

remote_side='catalog.id'

The problem is that the namespace it is evaluated in contains the Table objects and the model classes, and you're referencing a Table instead of the model class. Just alter the remote side to

remote_side='Catalog.id'

or just pass the id column in the class body directly:

class Catalog(db.Model):
    __tablename__ = 'catalog'

    id            = db.Column(db.Integer, primary_key=True)
    ...
    categories = db.relationship('Catalog', remote_side=id, backref='parent')
Sign up to request clarification or add additional context in comments.

6 Comments

Do you have any ideas why this relationship brings the same error? comp_id = db.Column(db.Integer, db.ForeignKey('competitors.id', ondelete="CASCADE"))
Hmm, at a glance no. ForeignKey is different from a relationship in that it works at the DB level and so should use the <table_name>.<column_key> notation: docs.sqlalchemy.org/en/13/core/…
Yes, in pure SQLAlchemy it works that way. I use flask-sqlalchemy, they suggest to address through __tablename__. Thank you for the answer.
Flask-SQLA is basically SQLA — with Flask integrations, a few tweaks, and a customized Session. Where can the suggestion to use __tablename__ be found? It'd br interesting to see what's it like.
As I understand the issue, SQLA working with Postgre converts camelcase class name to a name divided with underscores, e.g. class CompetitorsCatalog to competitors_catalog, while I was addressing to competitorscatalog. That solves the issue for me.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.