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?
Related
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.
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.
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)
)
when do we generally get the following error in SQLAlchemy?
sqlalchemy.exc.OperationalError: (OperationalError) (1050, "Table 'foobar' already exists")
The foobar table does already exists, but why SQLAlchemy is trying to create table when already present. I'm assuming it shouldn't create table if already exists.
I'm using following syntax to create table:
t = Table('foobar', metadata,
Column('col1', String(50), primary_key=True),
Column('col2', String(100)),
mysql_engine='InnoDB',
mysql_charset='utf8')
(I'm calling the same program in parallel 10 times)
Just use schema object's (Table,Index and Sequence) create and drop methods with checkfirst=True keyword and table will automatically add an "IF NOT EXISTS or IF EXISTS CLAUSE" whichever is appropriate to SQL.
FOR EXAMPLE:
t = Table('foobar', metadata,
Column('col1', String(50), primary_key=True),
Column('col2', String(100)),
mysql_engine='InnoDB',
mysql_charset='utf8')
t.create(checkfirst=True)
If the foobar table already existed, you could instead have done:
users = Table('foobar', metadata, autoload=True)
and SQLAlchemy would have automatically figured out the table's structure from the database.
Check first using autoload, table is there or not, if it is not there then create the table.
Here is my guess with some troubleshooting ideas. My guess is that the client thinks the table does not exist, because it can't see it, but when trying to create it, cannot because it in fact does exist.
Troubleshooting ideas:
Check to see if maybe some other part of the code is writing to the same log file or whatever and is trying to create these tables.
Manually login with the same ID and password as the client, and see if you can see the
table.
Pass echo=True to create_engine to learn the exact queries the client performs, then repeat all the queries in your own SQL shell to see what the client is seeing. Hopefully that will lead you to a conclusion.
Another option is to use metadata:
database_engine = create_engine(
'mssql+pyodbc://username:password=#server:1433/databaseName?driver=ODBC+Driver+17+for+SQL+Server?trusted_connection=no')
with database_engine.connect() as database_connection:
metaData = MetaData(bind=database_engine, reflect=True)
metaData.reflect()
try:
Table("tbl_stuff",
metaData,
Column("id", Integer, primary_key=True, autoincrement=False),
Column("Name", String(255)),
extend_existing=True)
metaData.create_all(database_connection, checkfirst=True)
metaData.reflect()
except Exception as an_exc:
logging.exception("Exception creating tbl_stuff: " + str(an_exc))
For details please see https://docs.sqlalchemy.org/en/13/core/metadata.html
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)