Self-reference column in sqlalchemy core - python

https://docs.sqlalchemy.org/en/latest/orm/self_referential.html explains how a table's column can refer to the table in the ORM. How can a column refer to its containing table in SQLAlchemy Core?
For example, this won't work because person isn't defined yet at the time it needs to be referenced.
import sqlalchemy as sa
metadata = sa.MetaData()
person = sa.Table(
"person",
metadata,
sa.Column("person_id", sa.BigInteger, primary_key=True, autoincrement=True),
sa.Column("mother", None, sa.ForeignKey(person.c.person_id)),
) # ^ This is an errror

Use the string argument form of ForeignKey, i.e. 'tablename.columnkey':
person = sa.Table(
"person",
metadata,
sa.Column("person_id", sa.BigInteger, primary_key=True, autoincrement=True),
sa.Column("mother", sa.ForeignKey("person.person_id")), # Note: no `.c.`
)
The string argument is handled lazily, so that it can reference for example tables from other modules without having to import them, as long as they exist in the metadata. In this case it allows referencing the table itself.

Related

How set start of auto increment in flask-sqlalchemy [duplicate]

The autoincrement argument in SQLAlchemy seems to be only True and False, but I want to set the pre-defined value aid = 1001, the via autoincrement aid = 1002 when the next insert is done.
In SQL, can be changed like:
ALTER TABLE article AUTO_INCREMENT = 1001;
I'm using MySQL and I have tried following, but it doesn't work:
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Article(Base):
__tablename__ = 'article'
aid = Column(INTEGER(unsigned=True, zerofill=True),
autoincrement=1001, primary_key=True)
So, how can I get that? Thanks in advance!
You can achieve this by using DDLEvents. This will allow you to run additional SQL statements just after the CREATE TABLE ran. Look at the examples in the link, but I am guessing your code will look similar to below:
from sqlalchemy import event
from sqlalchemy import DDL
event.listen(
Article.__table__,
"after_create",
DDL("ALTER TABLE %(table)s AUTO_INCREMENT = 1001;")
)
According to the docs:
autoincrement –
This flag may be set to False to indicate an integer primary key column that should not be considered to be the “autoincrement” column, that is the integer primary key column which generates values implicitly upon INSERT and whose value is usually returned via the DBAPI cursor.lastrowid attribute. It defaults to True to satisfy the common use case of a table with a single integer primary key column.
So, autoincrement is only a flag to let SQLAlchemy know whether it's the primary key you want to increment.
What you're trying to do is to create a custom autoincrement sequence.
So, your example, I think, should look something like:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import Sequence
Base = declarative_base()
class Article(Base):
__tablename__ = 'article'
aid = Column(INTEGER(unsigned=True, zerofill=True),
Sequence('article_aid_seq', start=1001, increment=1),
primary_key=True)
Note, I don't know whether you're using PostgreSQL or not, so you should make note of the following if you are:
The Sequence object also implements special functionality to accommodate Postgresql’s SERIAL datatype. The SERIAL type in PG automatically generates a sequence that is used implicitly during inserts. This means that if a Table object defines a Sequence on its primary key column so that it works with Oracle and Firebird, the Sequence would get in the way of the “implicit” sequence that PG would normally use. For this use case, add the flag optional=True to the Sequence object - this indicates that the Sequence should only be used if the database provides no other option for generating primary key identifiers.
I couldn't get the other answers to work using mysql and flask-migrate so I did the following inside a migration file.
from app import db
db.engine.execute("ALTER TABLE myDB.myTable AUTO_INCREMENT = 2000;")
Be warned that if you regenerated your migration files this will get overwritten.
I know this is an old question but I recently had to figure this out and none of the available answer were quite what I needed. The solution I found relied on Sequence in SQLAlchemy. For whatever reason, I could not get it to work when I called the Sequence constructor within the Column constructor as has been referenced above. As a note, I am using PostgreSQL.
For your answer I would have put it as such:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Sequence, Column, Integer
import os
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Sequence, Integer, create_engine
Base = declarative_base()
def connection():
engine = create_engine(f"postgresql://postgres:{os.getenv('PGPASSWORD')}#localhost:{os.getenv('PGPORT')}/test")
return engine
engine = connection()
class Article(Base):
__tablename__ = 'article'
seq = Sequence('article_aid_seq', start=1001)
aid = Column('aid', Integer, seq, server_default=seq.next_value(), primary_key=True)
Base.metadata.create_all(engine)
This then can be called in PostgreSQL with:
insert into article (aid) values (DEFAULT);
select * from article;
aid
------
1001
(1 row)
Hope this helps someone as it took me a while
You can do it using the mysql_auto_increment table create option. There are mysql_engine and mysql_default_charset options too, which might be also handy:
article = Table(
'article', metadata,
Column('aid', INTEGER(unsigned=True, zerofill=True), primary_key=True),
mysql_engine='InnoDB',
mysql_default_charset='utf8',
mysql_auto_increment='1001',
)
The above will generate:
CREATE TABLE article (
aid INTEGER UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT,
PRIMARY KEY (aid)
)ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8
If your database supports Identity columns*, the starting value can be set like this:
import sqlalchemy as sa
tbl = sa.Table(
't10494033',
sa.MetaData(),
sa.Column('id', sa.Integer, sa.Identity(start=200, always=True), primary_key=True),
)
Resulting in this DDL output:
CREATE TABLE t10494033 (
id INTEGER GENERATED ALWAYS AS IDENTITY (START WITH 200),
PRIMARY KEY (id)
)
Identity(..) is ignored if the backend does not support it.
* PostgreSQL 10+, Oracle 12+ and MSSQL, according to the linked documentation above.

Using alembic to migrate pre-existing and populated tables

I have this tables I created models for using vanilla SQLAlchemy to operate the postgre DB.
class table:
def __init__(self):
self.engine = create_engine(DB_ADDRESS, poolclass=NullPool)
self.meta = MetaData()
self.table = Table(
"employeetable",
self.meta,
Column("id", Integer, primary_key=True),
Column("employeeId", String),
Column("name", String),
Column("email", String),
Column("place", String),
Column("sector", String),
Column("function", String),
Column("phone", String),
Column("status", String),
Column("type", String),
Column(
"lastChange",
DateTime(timezone=True),
server_default=func.now(),
),
Column("receptorId", String),
Column("syncFlag", Boolean),
)
self.meta.create_all(self.engine)
each class also have its methods for me to search and alter the the table and whatnot.
The table already is populated with data and takes care of the data sync between two systems.
The problem is now I need to add another column to check the sync of another field.
I know I have to migrate the table to do this, and also know Alembic is an option to this. I just can't figure out how I get Alembic to "import" this class/table so I can work with it inside Alembic to make this migration to alter the table.
Can someone give me some directions on how this works and how i can configure it to receive this?

How to add UniqueConstraint in SQLAlchemy

How does one add a UniqueConstraint which is Case Insensitive using SQLalchemy?
In some databases, string columns are case-insensitive by default (MySQL, SQL Server), so you wouldn't need to do anything extra.
In others, you can create a functional index that enforces the case-insensitive unique constraint:
Index('myIndex', func.lower(mytable.c.myColumn), unique=True)
You can also specify a case-insensitive collation for the column if the database supports it. For instance SQLite has a 'NOCASE' collation:
myColumn = Column(String(255), collation='NOCASE', nullable=False)
See http://docs.sqlalchemy.org/en/latest/core/type_basics.html?highlight=collation#sqlalchemy.types.String.params.collation
You may also specify a user-defined type for your column if your database provides a suitable one. PostgreSQL has a citext data type which is case-insensitive. See https://github.com/mahmoudimus/sqlalchemy-citext
Finally you can customize the DDL to create the constraint for your specific database.
To add upon #jspcal's answer, if the model is defined using a class, then one you would have to either instantiate it independently after declaring the model or use the text construct.
i.e.
from sqlalchemy.sql.expressions import func
class User(Base):
__tablename__ = 'user'
username = Column('username', String(24), nullable=False)
Index('user_username_index', func.lower(User.username), unique=True)
using the text construct:
from sqlalchemy.sql.expressions import text
class User(Base):
__tablename__ = 'user'
__table_args__ = (
Index('ix_user_name', text('LOWER(username)')),
)
username = Column('username', String(24), nullable=False)
NB: table_args needs to be a tuple or dict, hence the need for that trailing comma inside the parenthesis.
That will create an index on username column of table user in lowercase form. Therefore, data stored in this column is unique and case insensitive.

How to create "GENERATED BY DEFAULT AS IDENTITY" field in Firebird 3 via SQLAlchemy?

I want create column ID via SQLAlchemy.
SQL seems like:
CREATE TABLE OBJECT (
ID INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
NAME VARCHAR(15)
)
Or how to create table with autoincrement field?
Column(autoincrement='auto')
default for primary_key, but that not work for Firebird.
I add fdb to github.
In this string SQL-code what needed for that, but i don't know how setup Column in SQLAlchemy to use that.
Screenshot from IBExpert showing what create after use "GENERATED BY DEFAULT AS IDENTITY". That not same as a simple generator.
This postgresql work around might work:
https://docs.sqlalchemy.org/en/13/dialects/postgresql.html
I believe you would have to modify the example for firebird by changing the compile dialect like this:
from sqlalchemy import MetaData
from sqlalchemy.schema import CreateColumn
from sqlalchemy.ext.compiler import compiles
#compiles(CreateColumn, 'firebird')
def use_identity(element, compiler, **kw):
text = compiler.visit_create_column(element, **kw)
text = text.replace("SERIAL", "INT GENERATED BY DEFAULT AS IDENTITY")
return text
m = MetaData()
t = Table(
't', m,
Column('id', Integer, primary_key=True),
Column('data', String)
)

How do I delete a foreign key constraint in SQLAlchemy?

I'm using SQLAlchemy Migrate to keep track of database changes and I'm running into an issue with removing a foreign key. I have two tables, t_new is a new table, and t_exists is an existing table. I need to add t_new, then add a foreign key to t_exists. Then I need to be able to reverse the operation (which is where I'm having trouble).
t_new = sa.Table("new", meta.metadata,
sa.Column("new_id", sa.types.Integer, primary_key=True)
)
t_exists = sa.Table("exists", meta.metadata,
sa.Column("exists_id", sa.types.Integer, primary_key=True),
sa.Column(
"new_id",
sa.types.Integer,
sa.ForeignKey("new.new_id", onupdate="CASCADE", ondelete="CASCADE"),
nullable=False
)
)
This works fine:
t_new.create()
t_exists.c.new_id.create()
But this does not:
t_exists.c.new_id.drop()
t_new.drop()
Trying to drop the foreign key column gives an error: 1025, "Error on rename of '.\my_db_name\#sql-1b0_2e6' to '.\my_db_name\exists' (errno: 150)"
If I do this with raw SQL, i can remove the foreign key manually then remove the column, but I haven't been able to figure out how to remove the foreign key with SQLAlchemy? How can I remove the foreign key, and then the column?
You can do it with sqlalchemy.migrate.
In order to make it work, I have had to create the foreign key constraint explicitly rather than implicitely with Column('fk', ForeignKey('fk_table.field')):
Alas, instead of doing this:
p2 = Table('tablename',
metadata,
Column('id', Integer, primary_key=True),
Column('fk', ForeignKey('fk_table.field')),
mysql_engine='InnoDB',
)
do that:
p2 = Table('tablename',
metadata,
Column('id', Integer, primary_key=True),
Column('fk', Integer, index=True),
mysql_engine='InnoDB',
)
ForeignKeyConstraint(columns=[p2.c.fk], refcolumns=[p3.c.id]).create()
Then the deletion process looks like this:
def downgrade(migrate_engine):
# First drop the constraint
ForeignKeyConstraint(columns=[p2.c.fk], refcolumns=[p3.c.id]).drop()
# Then drop the table
p2.drop()
I was able to accomplish this by creating a separate metadata instance and using Session.execute() to run raw SQL. Ideally, there would be a solution that uses sqlalchemy exclusively, so I wouldn't have to use MySQL-specific solutions. But as of now, I am not aware of such a solution.
I believe you can achieve this with SQLAlchemy-Migrate. Note that a ForeignKey is on an isolated column. A ForeignKeyConstraint is at the table level and relates the columns together. If you look at the ForeignKey object on the column you will see that it references a ForeignKeyConstraint.
I would unable to test this idea because of the two databases I use MS SQL isn't supported by SqlAlchemy-Migrate and sqlite doesn't support "alter table" for constraints. I did get SQLAlchemy to try to remove a FK via a drop on the references constraint on a sqlite table so it was looking good. YMMV.
Well, you can achieve this in sqlalchemy: just drop() the all the constraints before you drop() the column (theoretically, you might have multiple constraints):
def drop_column(column):
for fk in column.table.foreign_keys:
if fk.column == column:
print 'deleting fk ', fk
fk.drop()
column.drop()
drop_column(t_exists.c.new_id)

Categories

Resources