Sqlacodegen generates mixed models and tables - python

Executing this command:
sqlacodegen <connection-url> --outfile db.py
The db.py contains generated tables:
t_table1 = Table(...)
and classes too:
Table2(Base):
__tablename__ = 'table2'
The problem is that a table is generated in one way only - either a table or a class.
I would like to make it generate models (classes) only but in the provided flags I couldn't find such an option. Any idea?

It looks like what you're describing is a feature itself. sqlacodegenwill not always generate class models.
It will only form model classes for tables that have a primary key and are not association tables, as you can see in the source code:
# Only form model classes for tables that have a primary key and are not association tables
if noclasses or not table.primary_key or table.name in association_tables:
model = self.table_model(table)
else:
model = self.class_model(table, links[table.name], self.inflect_engine, not nojoined)
classes[model.name] = model
Furthermore, in the documentation it is stated that
A table is considered an association table if it satisfies all of the
following conditions:
has exactly two foreign key constraints
all its columns are involved in said constraints
Although, you can try a quick and dirty hack. Locate those lines in the source code (something like /.../lib/python2.7/site-packages/sqlacodegen/codegen.py) and comment out the first three code lines (and fix indentation):
# Only form model classes for tables that have a primary key and are not association tables
# if noclasses or not table.primary_key or table.name in association_tables:
# model = self.table_model(table)
# else:
model = self.class_model(table, links[table.name], self.inflect_engine, not nojoined)
classes[model.name] = model
I have tried this for one specific table that was generated as a table model. It went from
t_Admin_op = Table(
'Admin_op', metadata,
Column('id_admin', Integer, nullable=False),
Column('id_op', Integer, nullable=False)
)
to
class AdminOp(Base):
__tablename__ = 'Admin_op'
id_admin = Column(Integer, nullable=False)
id_op = Column(Integer, nullable=False)
You can also open an issue about this as a feature request, in the official tracker.
Just in case, if you want the opposite (only table models), you could do so with the --noclasses flag.

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!

flask sqlalchemy UniqueConstraint with foreignkey attribute

I have an app I am building with Flask that contains models for Projects and Plates, where Plates have Project as a foreignkey.
Each project has a year, given as an integer (so 17 for 2017); and each plate has a number and a name, constructed from the plate.project.year and plate.number. For example, Plate 106 from a project done this year would have the name '17-0106'. I would like this name to be unique.
Here are my models:
class Project(Model):
__tablename__ = 'projects'
id = Column(Integer, primary_key=True)
name = Column(String(64),unique=True)
year = Column(Integer,default=datetime.now().year-2000)
class Plate(Model):
__tablename__ = 'plates'
id = Column(Integer, primary_key=True)
number = Column(Integer)
project_id = Column(Integer, ForeignKey('projects.id'))
project = relationship('Project',backref=backref('plates',cascade='all, delete-orphan'))
#property
def name(self):
return str(self.project.year) + '-' + str(self.number).zfill(4)
My first idea was to make the number unique amongst the plates that have the same project.year attribute, so I have tried variations on
__table_args__ = (UniqueConstraint('project.year', 'number', name='_year_number_uc'),), but this needs to access the other table.
Is there a way to do this in the database? Or, failing that, an __init__ method that checks for uniqueness of either the number/project.year combination, or the name property?
There are multiple solutions to your problem. For example, you can de-normalize project.year-number combination and store it as a separate Plate field. Then you can put a unique key on it. The question is how you're going to maintain that value. The two obvious options are triggers (assuming your DB supports triggers and you're ok to use them) or sqla Events, see http://docs.sqlalchemy.org/en/latest/orm/events.html#
Both solutions won't emit an extra SELECT query. Which I believe is important for you.
your question is somewhat similar to Can SQLAlchemy events be used to update a denormalized data cache?

Can I use an ORM class as association table for a many-to-many realtionship in SQLAlchemy?

I want to collect statistical information for songs (rating, play-count, last time played) from a couple of players over different devices and from different users. I use Python and SQL Alchemy.
I came up with the following table layout:
I can access all related Stats objects from my Commit ORM class as a list. I also want to have access to the related Song objects from the Commit class. Following the examples in the SQLALchemy Documentation I came up with an association table for the mtm relationship.
In code it looks like this (full source):
songcommits_table = Table(
'songcommits', Base.metadata,
Column('commit_id', Integer, ForeignKey('commits.commit_id')),
Column('song_id', Integer, ForeignKey('songs.song_id'))
)
class Commit(Base):
__tablename__ = 'commits'
commit_id = Column(Integer, primary_key=True)
# ...
songs = relationship(
"Song", secondary=songcommits_table, backref="commits"
)
stats = relationship('Stat', backref='commit')
def __repr__(self):
return "<Commit {0.commit_id}>".format(self)
It works. But I have the feeling it might work without the table, using the info already stored in the stats table, but I have no idea how to formulate this in the ORM.
So how can I use the information (commit_id and song_id) already present on the Stats ORM class instead of the helper table?

A django like unique together in turbogears/sqlalchemy

This question essentially two parts.
1. I have a situation where I require things to be unique together i.e the elements in db need to be unique together with each other.
Lets say we have a model Things ( Rough PseudoCode)
Class ShoppingList( object ):
thing1_id = Column(Integer)
thing2_id = Column(Integer)
Now I need thing1_id and thing2_id to be a unique together ie the set of thing1_id and thing2_id needs to be unique together. Coming from django world I know that you can do a meta declaration in django models of unique_together. But how can do this in turbogears .
Also how do I actually apply a unique_together on a legacy system.
You simply want to add a UniqueConstraint to your table definition (using a primary key would achive similar effects, but with different semantics nevertheless).
This is as simple as:
Class ShoppingList( object ):
thing1_id = Column(Integer)
thing2_id = Column(Integer)
__table_args__ = (
UniqueConstraint('thing1_id', 'thing2_id'),
)
See also https://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/table_config.html#table-configuration
For the first part of your question, if I understand your question correctly, I believe you are talking about the need for defining composite primary keys. As stated in http://docs.sqlalchemy.org/en/latest/core/schema.html#describing-databases-with-metadata:
Multiple columns may be assigned the primary_key=True flag which denotes a multi-column primary key, known as a composite primary key.
Defining such a relationship on a class using the declarative ORM way in SQLAlchemy, should be as simple as:
class ShoppingList(Base):
thing1_id = Column(Integer, primary_key=True)
thing2_id = Column(Integer, primary_key=True)
As for the second part of your question, I believe you mean how one would define the same SQLAlchemy mapping for an existing, legacy database. If so, you should be able to use the above approach, just don't create the database from the ORM definition. You may also use the classic mapping way, described in: http://docs.sqlalchemy.org/en/rel_0_8/orm/mapper_config.html?highlight=composite%20primary%20key#classical-mappings

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