I couldn't find how to setup relationships in sqlalchemy - python

I'm coding an application connected with a PostgreSQL database and sqlalchemy as the ORM.
I have two classes in the simplified schema (camera and room) that I can use but without any relationship.
So for example if I select all the room I will not be able to list their cameras.
The error:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'mapped class Room->room'. Original exception was: When initializing mapper mapped class Room->room, expression 'Camera' failed to locate a name ('Camera'). If this is a class name, consider adding this relationship() to the <class 'models.room.Room'> class after both dependent classes have been defined.
# room.py
from sqlalchemy import Integer, Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Room(Base):
__tablename__ = 'room'
id_room = Column(Integer, primary_key=True)
cameras = relationship("Camera", back_populates="room")
# camera.py
from sqlalchemy import Integer, Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Camera(Base):
__tablename__ = 'camera'
id_camera = Column(Integer, primary_key=True)
id_room = Column(Integer, ForeignKey('room.id_room'))
room = relationship("Room", back_populates="cameras")

Just define both the classes in a single models.py file -
# models.py
from sqlalchemy import Integer, Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Room(Base):
__tablename__ = 'room'
id_room = Column(Integer, primary_key=True)
cameras = relationship("Camera", back_populates="room")
class Camera(Base):
__tablename__ = 'camera'
id_camera = Column(Integer, primary_key=True)
id_room = Column(Integer, ForeignKey('room.id_room'))
room = relationship("Room", back_populates="cameras")

Related

SQLAlchemy: Why importing using relative or absolute paths returns different instance of object <Base class>

I have the following issue in a project that uses SQLAlchemy. I've put all the code after the question.
If I use from base import Base in create_tables.py (as seen in the code below), the tables are not created.
If I use from persistence.base import Base in create_tables.py, the tables ARE created.
Why does this happen? Why does using the absolute import path fix everything?
I suspect that when I use from base import Base a new, clean Base class instance is used, so SQLAlchemy does not know of the child classes that inherit from Base and make up the tables. However, I don't understand why using the absolute import path (persistence.Base) actually solves the issue.
Another point to make is that I use absolute import paths in the *_api.py files as you can see below. How would using relative path imports and absolute path imports affect the outcome of this situation?
Any ideas? Thanks.
Project's structure:
src
--persistence
----create_tables.py
----base.py
----user
------user_api.py
----expenditure
------expenditure.py
----statement
------statement.py
Relevant code:
base.py
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
expenditure_api.py
from sqlalchemy import Column, Float, ForeignKey, String, TIMESTAMP
from sqlalchemy.dialects.mysql import INTEGER
from sqlalchemy.orm import relationship
from persistence.base import Base
class Expenditure(Base):
__tablename__ = 'expenditure'
expenditure_id = Column(INTEGER, primary_key=True, unique=True)
expenditure_user_id = Column(ForeignKey('user.user_id'), nullable=False, index=True)
expenditure_issue_date = Column(TIMESTAMP)
expenditure_payment_date = Column(TIMESTAMP)
expenditure_receipt_id = Column(String(45))
expenditure_statement_id = Column(ForeignKey('statement.statement_id'), index=True)
expenditure_transaction_details = Column(String(255))
expenditure_payment_num = Column(INTEGER)
expenditure_total_payments = Column(INTEGER)
expenditure_amount = Column(Float(asdecimal=True), nullable=False)
expenditure_currency = Column(String(45), nullable=False)
expenditure_statement = relationship('Statement')
expenditure_user = relationship('User')
statement_api.py
# coding=utf-8
from sqlalchemy import Column, Float, ForeignKey, String, TIMESTAMP
from sqlalchemy.dialects.mysql import INTEGER
from persistence.base import Base
class Statement(Base):
__tablename__ = 'statement'
statement_id = Column(String(255), primary_key=True, unique=True)
statement_from_date = Column(TIMESTAMP)
statement_to_date = Column(TIMESTAMP)
user_api.py
from sqlalchemy import Column, Float, ForeignKey, String, TIMESTAMP
from sqlalchemy.dialects.mysql import INTEGER
from persistence.base import Base
class User(Base):
__tablename__ = 'user'
user_id = Column(INTEGER, primary_key=True, unique=True)
user_username = Column(String(45), nullable=False)
user_full_name = Column(String(255))
create_tables.py
from sqlalchemy import exc
from sqlalchemy.orm import sessionmaker
from persistence_config import sql_server_socket, database_name
from user.user_api import User
from expenditure.expenditure_api import Expenditure
from statement.statement_api import Statement
from base import Base
from create_logger import create_logger
logger = create_logger(__name__)
def create_tables(engine):
# try:
Base.metadata.create_all(engine)
logger.info(f"Tables created.")
# except exc.DatabaseError as ex:
# logger.error(f"create_tables error: {ex}")
# except:
# logger.exception("create_tables error.")
if __name__ == "__main__":
from persistence_config import sql_server_socket, database_name
from sqlalchemy import create_engine
engine = create_engine(sql_server_socket + "/" + database_name, echo=True)
create_tables(engine)

SQLAlchemy 'no such table' when trying to reference session from imported module

I have an app I am using where all of my models and my session are stored within a single models.py file:
import datetime
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy import Column, Date, Integer, String, DateTime, Float
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
engine = create_engine('sqlite:///bidbot.db', echo=True)
Base = declarative_base()
Session = sessionmaker(bind=engine)
session = Session()
class AgentLog(Base):
__tablename__ = 'agentlog'
id = Column(Integer, primary_key=True)
created_at = Column(DateTime,default=datetime.datetime.utcnow)
ingroups = Column(String)
total_calls = Column(Integer)
calls_waiting = Column(Integer)
agents_logged_in = Column(Integer)
agents_in_calls = Column(Integer)
agents_waiting = Column(Integer)
agents_paused = Column(Integer)
agents_in_dispo = Column(Integer)
agents_in_dial = Column(Integer)
def create_new():
session.add(AgentLog())
session.commit()
As you can see, I have a custom create_new method which uses the session to create a new object.
If I run this models.py file on its own:
python -i models.py
Then I can call the AgentLog.create_new() function and it creates a new record.
However, I am importing models.py into my higher level package:
from models.py import *
And when I run the same function on the model:
AgentLog.create_new()
I now get a no such table exists error... Which is confusing because I believe the method should have access to the exact same session object.
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: agentlog
Figured it out! When I imported the package, it was defining the engine with the relative path based on the importing directory, not the models.py module.
I needed to update the path to use relative directory based on the models.py file:
import os
DATABASE_DIR = os.path.dirname(os.path.abspath(__file__)) + '\\bidbot.db'
engine = create_engine('sqlite:///'+DATABASE_DIR, echo=True)

SQLAlchemy Can't Find Column when adding ForeignKey to another table

I have been fighting this error for a few days now in a variety of configurations.
I tried adding Relationship lines under each of the ForeignKeys with no luck.
I also swapped my queries from using the declarative_base style calls to SQL queries.
The error only appears once I add the second ForeignKey to Task. (I originally had several foreign keys in Task and Project but pared things down until it worked and tried slowly adding things back. Adding the second ForeignKey did it no matter which table it was added to.)
My current Flask method is:
#app.route('/project/<int:project_id>/')
def showProject(project_id):
project = session.query(Project).from_statement(text("SELECT * FROM project WHERE project.id = project_id ORDER BY project.projnum DESC"))
tasks = session.query(Task).filter_by(project_id = project_id).all()
return render_template('project.html', project = project, tasks = tasks)
And I get a "no such column" error:
OperationalError: (sqlite3.OperationalError) no such column: task.project_id [SQL: u'SELECT task.id AS task_id, task.name AS task_name, task.description AS task_description, task.assigned_id AS task_assigned_id, task.project_id AS task_project_id, task.due AS task_due \nFROM task \nWHERE task.project_id = ?'] [parameters: (2,)]
This is the setup file for my database:
import os
import sys
import time
from sqlalchemy import Column, ForeignKey, Integer, String, Float
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine
Base = declarative_base()
engine = create_engine('sqlite:///gmnpm.db')
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(250), nullable=False)
email = Column(String(250), nullable=False)
picture = Column(String(250))
class Task(Base):
__tablename__ = 'task'
id = Column(Integer, primary_key=True)
name = Column(String(250), nullable=False)
description = Column(String(2000))
assigned_id = Column(Integer, ForeignKey('user.id'))
project_id = Column(Integer, ForeignKey('project.id'))
due = Column(Integer, default=int(time.time()))
class Project(Base):
__tablename__ = 'project'
id = Column(Integer, primary_key=True)
name = Column(String(250), nullable=False)
description = Column(String(1000))
projnum = Column(Integer)
Base.metadata.create_all(engine)
After reading a bunch of documentation and going crazy trying random things on the off chance they work, I'm grateful for any suggestions!

how to use .query attribute in sqlacodegen models

When I use sqlacodegen to create models.py I can't use User.query, getting the warning "[AttributeError: type object 'User' has no attribute 'query']".
I think db.query is a very usable attribute, it's used in SQLAlchemy():
db = SQLAlchemy()
But I need to use sqlacodegen to create models for existing table in our system. Creation code below:
models.py
# coding: utf-8
from sqlalchemy import Column, Date, Float, Integer, String, Table, text
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
metadata = Base.metadata
class User(Base):
__tablename__ = u'users'
id = Column(Integer, primary_key=True)
email = Column(String(64))
username = Column(String(64))
role_id = Column(Integer)
password_hash = Column(String(128))
Now I import models.py:
from models import *
#main.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.name.data).first()
...
When I run this, the warning given is:
AttributeError: type object 'User' has no attribute 'query'
I want to know how I can trans [class User(Base)] to a type like [class User(db.Model)] so it can use .query attribute? Or is there some other usable method to do that with [class User(Base) ]type?
It sounds like you're used to using Flask-SQLAlchemy, which includes the secret sauce to allow the functionality you mention.
The Flask Docs on SQLAlchemy indicate how to get that same functionality:
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
It'll enable the shorthand of User.query.... that you expect. The important line is the last one. You could alternatively use the db.Model class from Flask-SQLAlchemy instead of the 'pure' SQLAlchemy Base class.

Can only define secondary relationship using association table object but not name

Trying to create an association relationship between the classes Note and Document. The problem I'm facing is that my secondary relationship only works when I use the association table object and not that table name. What I mean is that the relationship:
notes = relationship(u'Note', secondary=t_Documented, backref='documents')
works but the following does NOT work:
notes = relationship(u'Note', secondary='Documented', backref='documents')
When querying, I get the error:
sqlalchemy.exc.InvalidRequestError: When initializing mapper
Mapper|Document|Document, expression 'Documented' failed to locate a
name ("name 'Documented' is not defined"). If this is a class name,
consider adding this relationship() to the
class after both dependent classes have been defined.
I would rather use the name as my model is generated using sqlacodegen.
Moreover, SQLAlchemy docs say I can use the name (http://docs.sqlalchemy.org/en/rel_0_9/orm/relationships.html#many-to-many) with caveta "with the declarative extension in use". I Googled the term which led me to this. Question is how in my case can I augment the Base.
# model.py
# coding: utf-8
from sqlalchemy import Column, Date, DateTime, ForeignKey, ForeignKeyConstraint, Index, Integer, Numeric, String, Table, Text, text
from sqlalchemy.orm import backref, relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
metadata = Base.metadata
t_Documented = Table(
'Documented', metadata,
Column('id', Integer, primary_key=True),
Column('note_id', ForeignKey(u'MySchema.Note.id'), nullable=False),
Column('document_id', ForeignKey(u'MySchema.Document.id'), nullable=False, index=True),
Column('inserted', DateTime, nullable=False, server_default=text("'0000-00-00 00:00:00'")),
Column('updated', DateTime, nullable=False, server_default=text("'0000-00-00 00:00:00'")),
Index('Documented_AK1', 'note_id', 'document_id'),
schema='MySchema'
)
class Note(Base):
__tablename__ = 'Note'
__table_args__ = {u'schema': 'MySchema'}
id = Column(Integer, primary_key=True)
class Document(Note):
__tablename__ = 'Document'
__table_args__ = {u'schema': 'MySchema'}
id = Column(ForeignKey(u'MySchema.Note.id'), primary_key=True)
title = Column(String(100), nullable=False)
author = Column(String(100), nullable=False)
notes = relationship(u'Note', secondary='Documented', backref='documents')
Using SQLAlchemy 0.9.4 and Python 2.6.6. Connector is MySQLDB and I'm using MySQL database.
your table has a schema of "MySchema" so that has to be part of it:
notes = relationship(u'Note', secondary='MySchema.Documented', backref='documents')

Categories