SQLAlchemy one-to-many relationship (Single table with join table) - python

I have db that I cannot modify, it has two tables 'people' and 'relation'. The table 'people' has names, ids and the column parent (yes/no). The table 'relation' contains a foreign key 'people.id' for parent and a 'people.id' for its child. I want to join columns in the people table so I can
People.query.filter_by(id='id of the parent')
to get the name of the parent and it's childs. This is my code:
class People(db.model):
__tablename__ = 'people'
id = db.Column(db.integer(), primary_key=True
name = db.Column(db.String())
parent = db.Column(db.Integer()) ##0 for no 1 for yes
parent_id=db.relationship('Link',backref=db.backref('Link.parent_id')
class Link(db.Model):
_tablename__ = 'link'
parent_id=db.Column(db.Integer(),db.ForeignKey('people.id'),primary_key=True)
id = db.Column(db.Integer(), db.ForeignKey('people.id'), primary_key=True)
dateofbirth = db.Column(db.Integer())
SQLAlchemy tells me:
ArgumentError: relationship 'parent_id' expects a class or a mapper argument (received: <class 'sqlalchemy.sql.schema.Table'>)
Excuse me if I messed up, but it's my first question here (and also the first steps with SQLAlchemy)

Typically you would want to set up the foreign key and backref in the same table, like this:
class Link(db.Model):
_tablename__ = 'link'
parent_id = db.Column(db.Integer(),db.ForeignKey('people.id'),primary_key=True)
parent = db.relationship('People', backref='links')
Now you can access each Link entries parent via Link.parent, and you can get a list of each People entries links via People.links (assuming this is a one-to-many relationship).
Also, if People.parent is supposed to represent a boolean value then:
1.) you should follow the standard naming convention and call it something like is_parent
2.) you should declare People.parent as a db.Boolean type, not a db.Integer. In most (probably all) database implementations, using booleans instead of integers (when appropriate) is more memory efficient.
I hope this helped.

Related

SqlAlchemy doubly linked tables [duplicate]

I'm trying to model the following situation: A program has many versions, and one of the versions is the current one (not necessarily the latest).
This is how I'm doing it now:
class Program(Base):
__tablename__ = 'programs'
id = Column(Integer, primary_key=True)
name = Column(String)
current_version_id = Column(Integer, ForeignKey('program_versions.id'))
current_version = relationship('ProgramVersion', foreign_keys=[current_version_id])
versions = relationship('ProgramVersion', order_by='ProgramVersion.id', back_populates='program')
class ProgramVersion(Base):
__tablename__ = 'program_versions'
id = Column(Integer, primary_key=True)
program_id = Column(Integer, ForeignKey('programs.id'))
timestamp = Column(DateTime, default=datetime.datetime.utcnow)
program = relationship('Filter', foreign_keys=[program_id], back_populates='versions')
But then I get the error: Could not determine join condition between parent/child tables on relationship Program.versions - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
But what foreign key should I provide for the 'Program.versions' relationship? Is there a better way to model this situation?
Circular dependency like that is a perfectly valid solution to this problem.
To fix your foreign keys problem, you need to explicitly provide the foreign_keys argument.
class Program(Base):
...
current_version = relationship('ProgramVersion', foreign_keys=current_version_id, ...)
versions = relationship('ProgramVersion', foreign_keys="ProgramVersion.program_id", ...)
class ProgramVersion(Base):
...
program = relationship('Filter', foreign_keys=program_id, ...)
You'll find that when you do a create_all(), SQLAlchemy has trouble creating the tables because each table has a foreign key that depends on a column in the other. SQLAlchemy provides a way to break this circular dependency by using an ALTER statement for one of the tables:
class Program(Base):
...
current_version_id = Column(Integer, ForeignKey('program_versions.id', use_alter=True, name="fk_program_current_version_id"))
...
Finally, you'll find that when you add a complete object graph to the session, SQLAlchemy has trouble issuing INSERT statements because each row has a value that depends on the yet-unknown primary key of the other. SQLAlchemy provides a way to break this circular dependency by issuing an UPDATE for one of the columns:
class Program(Base):
...
current_version = relationship('ProgramVersion', foreign_keys=current_version_id, post_update=True, ...)
...
This design is not ideal; by having two tables refer to one another, you cannot effectively insert into either table, because the foreign key required in the other will not exist. One possible solution in outlined in the selected answer of
this question related to microsoft sqlserver, but I will summarize/elaborate on it here.
A better way to model this might be to introduce a third table, VersionHistory, and eliminate your foreign key constraints on the other two tables.
class VersionHistory(Base):
__tablename__ = 'version_history'
program_id = Column(Integer, ForeignKey('programs.id'), primary_key=True)
version_id = Column(Integer, ForeignKey('program_version.id'), primary_key=True)
current = Column(Boolean, default=False)
# I'm not too familiar with SQLAlchemy, but I suspect that relationship
# information goes here somewhere
This eliminates the circular relationship you have created in your current implementation. You could then query this table by program, and receive all existing versions for the program, etc. Because of the composite primary key in this table, you could access any specific program/version combination. The addition of the current field to this table takes the burden of tracking currency off of the other two tables, although maintaining a single current version per program could require some trigger gymnastics.
HTH!

slqlalchemy UniqueConstraint VS Index(unique=True)

I am using MySQL (running InnoDB), and wrapped the entire thing using sqlalchemy. Now, I would like to generate changes in my database by using (see docs)
sqlalchemy_utils.functions.create_database(...)
Generally the above function does what it is supposed to. The only exception being the generation of unique indexes.
Say, I define a table like this:
## ...
# DeclBase = declarative_base()
## ...
class MyTable(DeclBase):
__tablename__ = 'my_table'
id = Column(Integer, primary_key=True)
attr_1 = Column(String(32))
attr_2 = Column(Integer, nullable=False)
attr_3 = Column(DateTime)
attr_4 = Column(
Integer,
ForeignKey('other_table.id', onupdate='CASCADE', ondelete='CASCADE'),
nullable=False
)
u_idx = UniqueConstraint(attr_2, attr_3, 'my_table_uidx')
when I call create_database I will get sqlalchemy to create the table 'my_table' with all columns as specified. The foreign key is also setup fine, but no unique index can be found on the database side. I then tried using a Index(unique=True) instead. So instead of
u_idx = UniqueConstraint(attr_2, attr_3, 'my_table_uidx')
I put
u_idx_1 = Index('my_table_uidx', attr_2, attr_3, unique=True)
My impression was this logically produces a similar result. This time sqlalchemy indeed created the unique index on the db.
Maybe I am miserably misunderstanding something about the difference between UniqueConstraint and Index(unique=True), or the way sqlalchemy uses them to automate generation of databases.
Can anyone shed some light on this?
The main difference is that while the Index API allows defining an index outside of a table definition as long as it can reference the table through the passed SQL constructs, a UniqueConstraint and constraints in general must be defined inline in the table definition:
To apply table-level constraint objects such as ForeignKeyConstraint to a table defined using Declarative, use the __table_args__ attribute, described at Table Configuration.
The thing to understand is that during construction of a declarative class a new Table is constructed, if not passed an explicit __table__. In your example model class the UniqueConstraint instance is bound to a class attribute, but the declarative base does not include constraints in the created Table instance from attributes. You must pass it in the table arguments:
class MyTable(DeclBase):
__tablename__ = 'my_table'
...
# A positional argument tuple, passed to Table constructor
__table_args__ = (
UniqueConstraint(attr_2, attr_3, name='my_table_uidx'),
)
Note that you must pass the constraint name as a keyword argument. You could also pass the constraint using Table.append_constraint(), if called before any attempts to create the table:
class MyTable(DeclBase):
...
MyTable.__table__.append_constraint(
UniqueConstraint('attr_2', 'attr_3', name='my_table_uidx'))

SqlAlchemy table inheritance and primary keys

I have an inherited table in SqlAlchemy which complains about not having a primary key. Oddly, the parent table has a primary key. Here is the situation:
Parent(Base)
__tablename__= 'parents'
id = Column(INT, primary_key=True, autoincrement=True)
Child(Parent)
__tablename__= 'children'
birthday = Column(TIMESTAMP)
parentId = Column(INT, ForeignKey('parents.uid', onupdate="CASCADE", ondelete="CASCADE"))
parent = relationship("User", backref=backref('CommandsQueued'))
Note that the parent table has a primary key, and that the child table is inheriting that. Despite this setup, I'm getting the following error:
SAWarning: Could not assemble any primary keys for locally mapped table 'children' - no rows will be persisted in this Table.
self._configure_pks()
I don't understand why SA doesn't recognize that the table does have a primary key. Does anyone know what is going on here? Am I misunderstanding the inheritance behaviour of SA?
I guess it's just a minimal example, it doesn't make much sense for Child to be a subclass of Parent. I'd expect both to be subclasses of Person, or something like that. In that case, you might want to take a look at polymorphic identity too.
Anyway, SQLAlchemy inheritance doesn't work like that. In the way you declared, It expects that your Child class and table declares its own primary key because it's a separate table, but if you try to do that with the same attribute name, you'll get a conflict. Try declaring the base id column using sqlalchemy.orm.column_property and it should do what you expect.
So, do something like this on the Child:
id = sqlalchemy.orm.column_property(Column(INT, primary_key=True), Parent.id)
And it should work as you expect.

Database Relationship - Syntax

A question on the syntax involved in SQLAlchemy.
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
child_id = Column(Integer, ForeignKey('child.id'))
child = relationship("Child")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
Why is it ForeignKey('child.id') and not ForeignKey("Child.id")?
Why is it relationship("Child") and not relationship("child")? Is there something fundamental about how databases and SQLAlchemy work that I don't understand which is why I have to ask this question? Thanks!
relationship(Child) is also valid. By capitalising inside string, sqlalchemy will look for respective model.
Relationship isn't sql standard so SQLAlchemy is using its own convention, whereas ForeignKey is SQL Standard so tablename.column is used.
In general: A relationship is defined on orm level while ForeignKey represents a database model. Now, it well might be the case that sqlalchemy is smart enough to figure from from the other, but if you keep this separation in mind, you are safe.
Specifically to your question: just read the documentation. Extract below (verbatim)
From relationship:
argument – a mapped class, or actual Mapper instance, representing the
target of the relationship.
argument may also be passed as a callable function which is evaluated
at mapper initialization time, and may be passed as a Python-evaluable
string when using Declarative.
From ForeignKey
column – A single target column for the key relationship. A Column
object or a column name as a string: tablename.columnkey or
schema.tablename.columnkey. columnkey is the key which has been
assigned to the column (defaults to the column name itself), unless
link_to_name is True in which case the rendered name of the column is
used.

SQLAlchemy configuring many-to-many relationship to self using Association

I am having problems configuring a many to many relationship to a model itself. I can configure a self-many-to-many relation when I use anormal relationship configuration i.e. one not using an Association object.
In this scenario I have to record some extra information in the many-to-many table itself so I'm trying to implement relationship using an Association object (PageLink).
Here are the models.
class PageLink(Base):
'''
Association table.
'''
__tablename__ = 'page_links'
id = Column(Integer,primary_key=True)
page_from = Column(Integer,ForeignKey('page.id'),primary_key=True)
page_to = Column(Integer,ForeignKey('page.id'),primary_key=True)
extra_col1 = Column(String(256),nullable=False)
class Page(Base):
'''
main table
'''
__tablename__ = 'page'
id = Column(Integer,primary_key=True)
name = Column(String(56),nullable=False)
linked = relationship('PageLinks',backref='parent_page',
primaryjoin=id==PageLink.page_from,
secondaryjoin=id==PageLink.page_to)
This approach does not work. I have tried removing 'secondaryjoin' keyword but it wouldn't work.
Would greatly appreciate any help or advice on this matter.
Thank you for reading.
The association object pattern is not a sepecialization of the many-to-many relationship, but rather a special case of one-to-many relationships where you have a left_table - Many-To-One - association_table - One-To-Many - right_table set up. In short, you need two relationships, neither of which should have a secondary/secondaryjoin.
class PageLink(Base):
'''
Association table.
'''
__tablename__ = 'page_links'
id = Column(Integer,primary_key=True)
page_from = Column(Integer,ForeignKey('page.id'),primary_key=True)
page_to = Column(Integer,ForeignKey('page.id'),primary_key=True)
extra_col1 = Column(String(256),nullable=False)
class Page(Base):
'''
main table
'''
__tablename__ = 'page'
id = Column(Integer,primary_key=True)
name = Column(String(56),nullable=False)
linked_to = relationship('PageLinks',backref='parent_page',
primaryjoin=id==PageLink.page_from)
linked_from = relationship('PageLinks',backref='child_page',
primaryjoin=id==PageLink.page_to)
which means, to access the extra column for the 'to' links from some page p, you have to do: p.linked_to[0].extra_col1, or to get the actual linked page, p.linked_to[0].page_to
As an aside, it's often a great idea to use either an autoincrement primary key or (left/right) foreign key pair as the primary key in associations, but almost never useful to have both in the primary key. An alternative that combines both ideas would be to use an autoincrement integer as the only column in the primary key, and have an additional unique constraint on the left/right foreign key columns.

Categories

Resources