How do I prevent sql alchemy from inserting the None value to field? - postgresql

The Alembic migration script :
def upgrade():
uuid_gen = saexp.text("UUID GENERATE V1MC()")
op.create_table(
'foo',
sa.Column('uuid', UUID, primary_key=True, server_default=uuid_gen),
sa.Column(
'inserted',
sa.DateTime(timezone=True),
server_default=sa.text("not null now()"))
sa.Column('data', sa.Text)
)
This is my Base class for SQL Alchemy:
Class Foo(Base):
__tablename__ = 'foo'
inserted = Column(TIMESTAMP)
uuid = Column(UUID, primary_key=True)
data = Column(TEXT)
It has a static mehtod for insert :
#staticmethod
def insert(session, jsondata):
foo = Foo()
foo.data = jsondata['data']
if 'inserted' in jsondata:
foo.inserted = jsondata['inserted']
if 'uuid' in jsondata:
foo.uuid = jsondata['uuid']
session.add(foo)
return foo
the purpose of the 2 if's are to simplify testing. this way i can "inject" a uuid and inserted date, to get predictible data for my tests
When trying to insert data
foo = Foo()
foo.insert(session, {"data": "foo bar baz"})
session.commit()
I get an IntegrityError :
[SQL: 'INSERT INTO foo (inserted, data) VALUES (%(inserted)s, %(data)s) RETURNING foo.uuid'] [parameters: {'data': 'foo bar baz', 'inserted': None}]
wich seem normal to me because the insert violates the "not-null" constraint in the postgres database.
How do I prevent sql alchemy from inserting the None value to the inserted field ?
While playing and testing around, I found that if the "inserted" column is defined as primary key , sql alchemy does not include the field in the insert statement.
def upgrade():
uuid_gen = saexp.text("UUID GENERATE V1MC()")
op.create_table(
'foo',
sa.Column('uuid', UUID, primary_key=True, server_default=uuid_gen),
sa.Column(
'inserted',
primary_key=True,
sa.DateTime(timezone=True),
server_default=sa.text("not null now()"))
sa.Column('data', sa.Text)
)
But this is not what I want.

The primary problem is the server_default which is missing in the inserted member in class Foo. It's only present in the alembic script. Note that the alembic definitions are only used when running the migrations. They do not affect the application. For this reason, it's a good idea to copy the exact same definitions from the alembic script to your application (or vice-versa).
Because no value is defined in the model definition, sqlalchemy seems to set this to None when the class is instantiated. This will then be sent to the DB which will complain. To fix this, either set default or server_default on the model definition (the class inheriting from Base).
Some additional notes/questions:
Where does UUID GENERATE V1MC() come from? The official docs look different. I replaced it with func.uuid_generate_v1mc().
The server_default value in your case contains not null which is incorrect. You should set nullable=False on you column attribute (see below).
alembic script
# revision identifiers, used by Alembic.
revision = THIS_IS_DIFFERENT_ON_EACH_INSTANCE! # '1b7e145f2138'
down_revision = None
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import UUID
def upgrade():
op.create_table(
'foo',
sa.Column('uuid', UUID, primary_key=True,
server_default=sa.func.uuid_generate_v1mc()),
sa.Column(
'inserted',
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text("now()")),
sa.Column('data', sa.Text)
)
def downgrade():
op.drop_table('foo')
tester.py
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, create_engine, func
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.dialects.postgresql import (
TEXT,
TIMESTAMP,
UUID,
)
engine = create_engine('postgresql://michel#/michel')
Session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
class Foo(Base):
__tablename__ = 'foo'
inserted = Column(TIMESTAMP, nullable=False,
server_default=func.now())
uuid = Column(UUID, primary_key=True,
server_default=func.uuid_generate_v1mc()),
data = Column(TEXT)
#staticmethod
def insert(session, jsondata):
foo = Foo()
foo.data = jsondata['data']
if 'inserted' in jsondata:
foo.inserted = jsondata['inserted']
if 'uuid' in jsondata:
foo.uuid = jsondata['uuid']
session.add(foo)
return foo
if __name__ == '__main__':
session = Session()
Foo.insert(session, {"data": "foo bar baz"})
session.commit()
session.close()
output after execution
[9:43:54] michel#BBS-nexus [1 background job(s)]
/home/users/michel/tmp› psql -c "select * from foo"
uuid | inserted | data
--------------------------------------+-------------------------------+-------------
71f5fd32-0602-11e6-aebb-27be4bbac26e | 2016-04-19 09:43:45.297191+02 | foo bar baz
(1 row)

Related

Postgres connection string with schema, alembic sqlalchemy

I am working on Alembic with SQLAlchemy on my Alembic.ini I am setting my postgres connection string but it goes to my public schema, i need to choose my schema.
How I can use an specific schema:
alembic.ini file
sqlalchemy.url = postgresql://myuser:mypassword#server:host/database
models.py
import datetime
from sqlalchemy import Column,Integer,String,DateTime,create_engine
from sqlalchemy.orm import declarative_base,sessionmaker
Base = declarative_base()
class UserModel(Base):
__tablename__='myschema.person'
id = Column(Integer, primary_key=True)
first_name = Column(String, nullable=False)
last_name = Column(String, nullable = False)
birth = Column(DateTime)
This connection string works with public, I need to connect to my "sales" schema.
On my models.py I am just creating a table person, I tried adding there Core schema but didn't works
Regards
Here you can find a complete working example
Define schema on your model
class A(Base):
__tablename__ = 'a'
__table_args__ = {
"schema": "alphabet"
}
edit env.py to work with schemas
with connectable.connect() as connection:
"""
Configure migration context
1. Pass our models metadata
2. Set schema for alembic_version table
3. Load all available schemas
"""
context.configure(
connection=connection,
target_metadata=target_metadata,
"""
here you are passing schema
"""
version_table_schema=target_metadata.schema,
include_schemas=True
)
Run alembic revision and upgrade and it will work

SQLAlchemy Joined Table Inheritance: Problem adding objects

I'm having problems trying to add inherited objects with SQLAlchemy following the instructions in https://docs.sqlalchemy.org/en/14/orm/inheritance.html to implement Joined Table Inheritance.
I'm using a PostgreSQL version 14 as a database engine.
Here is my Base configuration:
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.automap import automap_base
db_string = os.environ['DB_STRING']
engine = create_engine(db_string)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = automap_base()
Base.query = db_session.query_property()
Base.prepare(engine, reflect=True)
Here is Instrument (parent class) definition:
from sqlalchemy import Column, String
from context_model.database import Base
class Instrument(Base):
__tablename__ = "instrument"
_id = Column(String, primary_key=True)
discriminator = Column(String)
__mapper_args__ = {
'polymorphic_identity': 'instrument',
'polymorphic_on': discriminator
Here is Bond (child class) definition:
import datetime as dt
from sqlalchemy import Column, Integer, Float, String, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from context_model.instrument.instrument import Instrument
class Bond(Instrument):
__tablename__ = "bond"
_id = Column(String, ForeignKey("instrument._id"), primary_key=True)
_provider_bbg_id = Column(String)
__mapper_args__ = {
'polymorphic_identity': 'bond'
}
When I try to add a Bond instance and persist it to the database:
Bond = Base.classes.bond
bond = Bond()
bond._id = "0"
bond._provider_bbg_id = "XXX"
db_session.add(bond)
db_session.commit()
appears the following error message:
sqlalchemy.exc.IntegrityError: (psycopg2.errors.ForeignKeyViolation) insert or update on table "bond" violates foreign key constraint "bond__id_fkey"
DETAIL: Key (_id)=(0) is not present in table "instrument"."
It seems to me that the inheritance is not working for some reason, did I define well the Instrument (parent) and Bond (child) classes ?, maybe do I need to use another type of Base ? (I'm using automap_base )
Thanks in advance for your help!
I don't know the exact reasons but I changed from declarative_base() to automap_base() as #snakecharmerb suggested in the commentaries and now it works :)

Race condition when manually setting the id in sqlalchemy

I want that all my object have a unique id that is set by PostgreSQL with a (serial) and another id that depends to the first one.
When creating an object if I set the second id after saving it, I'll have an INSERT and an UPDATE on the table, what is not really the best.
So to have only one INSERT I fetch the id from the PostgreSQL sequence and set the id with it instead of letting PostgreSQL do it at INSERT stage.
I'm pretty new on SQLAlchemy and want to be sure that this way of doing is race condition proof.
Thanks for you thoughts on this idea
class MyModel:
def __init__(self, session, **data):
"""
Base constructor for almost all model classes, performing common tasks
"""
cls = type(self)
if session:
"""To avoid having an UPDATE right after the INSERT we manually fetch
the next available id using a postgresl internal
SELECT nextval(pg_get_serial_sequence('events', 'id'));
To do that we need the table's name and the sequence
column's name, by chance we use the same name in all our
model
"""
table_name = cls.__tablename__
qry = f"SELECT nextval(pg_get_serial_sequence('{table_name}', 'id'))"
rs = session.execute(qry)
# TODO : find a non ugly way to to that
for row in rs:
next_id = row[0]
# manually set the object id
self.id = next_id
# set the external_id before saving the object in the database
self.ex_id = cls.ex_id_prefix + self.id
session.add(self)
session.flush([self])
If you are targetting Postgresql 12 or later, you can use a generated column. SQLAlchemy's Computed column type will create such a column, and we can pass an SQL expression to compute the value.
The model would look like this:
class MyModel(Base):
__tablename__ = 't68225046'
ex_id_prefix = 'prefix_'
id = sa.Column(sa.Integer, primary_key=True)
ex_id = sa.Column(sa.String,
sa.Computed(sa.text(":p || id::varchar").bindparams(p=ex_id_prefix)))
producing this DDL
CREATE TABLE t68225046 (
id SERIAL NOT NULL,
ex_id VARCHAR GENERATED ALWAYS AS ('prefix_' || id::varchar) STORED,
PRIMARY KEY (id)
)
and a single insert statement
2021-09-19 ... INFO sqlalchemy.engine.Engine BEGIN (implicit)
2021-09-19 ... INFO sqlalchemy.engine.Engine INSERT INTO t68225046 DEFAULT VALUES RETURNING t68225046.id
2021-09-19 ... INFO sqlalchemy.engine.Engine [generated in 0.00014s] {}
2021-09-19 ... INFO sqlalchemy.engine.Engine COMMIT
For earlier releases of Postgresql, or if you don't need to store the value in the database, you could simulate it with a hybrid property.
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.sql import cast
Base = orm.declarative_base()
class MyModel(Base):
__tablename__ = 't68225046'
ex_id_prefix = 'prefix_'
id = sa.Column(sa.Integer, primary_key=True)
#hybrid_property
def ex_id(self):
return self.ex_id_prefix + str(self.id)
#ex_id.expression
def ex_id(cls):
# See https://stackoverflow.com/a/54487891/5320906
return cls.ex_id_prefix + cast(cls.id, sa.String)

Default value doesn't work in SQLAlchemy + PostgreSQL + aiopg + psycopg2

I've found an unexpected behavior in SQLAlchemy. I'm using the following versions:
SQLAlchemy (0.9.8)
PostgreSQL (9.3.5)
psycopg2 (2.5.4)
aiopg (0.5.1)
This is the table definition for the example:
import asyncio
from aiopg.sa import create_engine
from sqlalchemy import (
MetaData,
Column,
Integer,
Table,
String,
)
metadata = MetaData()
users = Table('users', metadata,
Column('id_user', Integer, primary_key=True, nullable=False),
Column('name', String(20), unique=True),
Column('age', Integer, nullable=False, default=0),
)
Now if I try to execute a simple insert to the table just populating the id_user and name, the column age should be auto-generated right? Lets see...
#asyncio.coroutine
def go():
engine = yield from create_engine('postgresql://USER#localhost/DB')
data = {'id_user':1, 'name':'Jimmy' }
stmt = users.insert(values=data, inline=False)
with (yield from engine) as conn:
result = yield from conn.execute(stmt)
loop = asyncio.get_event_loop()
loop.run_until_complete(go())
This is the resulting statement with the corresponding error:
INSERT INTO users (id_user, name, age) VALUES (1, 'Jimmy', null);
psycopg2.IntegrityError: null value in column "age" violates not-null constraint
I didn't provide the age column, so where is that age = null value coming from? I was expecting something like this:
INSERT INTO users (id_user, name) VALUES (1, 'Jimmy');
Or if the default flag actually works should be:
INSERT INTO users (id_user, name, Age) VALUES (1, 'Jimmy', 0);
Could you put some light on this?
This issue has been confirmed has an aiopg bug. Seems like at the moment it's ignoring the default argument on data manipulation.
I've fixed the issue using server_default instead:
users = Table('users', metadata,
Column('id_user', Integer, primary_key=True, nullable=False),
Column('name', String(20), unique=True),
Column('age', Integer, nullable=False, server_default='0'))
I think you need to use inline=True in your insert. This turns off 'pre-execution'.
Docs are a bit cryptic on what exactly this 'pre-execution' entails, but they mentions default parameters:
:param inline:
if True, SQL defaults present on :class:`.Column` objects via
the ``default`` keyword will be compiled 'inline' into the statement
and not pre-executed. This means that their values will not
be available in the dictionary returned from
:meth:`.ResultProxy.last_updated_params`.
This piece of docstring is from Update class, but they have a shared behavior with Insert.
Besides, that's the only way they test it:
https://github.com/zzzeek/sqlalchemy/blob/rel_0_9/test/sql/test_insert.py#L385

ENUM type in SQLAlchemy with PostgreSQL

I'm using SQLAlchemy core with a postgresql database and I would like to add the ENUM type to my table definition. According to the postgresql documentation, the ENUM type must be defined prior to the table being created:
CREATE TYPE gender_enum AS ENUM ('female', 'male');
CREATE TABLE person (
name VARCHAR(20),
gender gender_enum
);
The problem is when I'm creating the table definition. After reading the SQLAlchemy documentation I couldn't find any implementation examples. I've tried something like this but it didn't work:
from sqlalchemy.dialects.postgresql import ENUM
person = Table('user_profile', metadata,
Column('name', String(20)),
Column('gender', ENUM('female', 'male'))
);
How it must be done?
You need to import Enum from sqlalchemy and add a name to it. It should work like this:
from sqlalchemy import Enum
person = Table("user_profile", metadata,
Column("name", String(20)),
Column("gender", Enum("female", "male", name="gender_enum", create_type=False))
);
#Tim's answer is definitely correct but I wanted to offer the way I setup my ENUMs.
In my models.py I will create the values as tuples
skill_levels = ('Zero', 'A little', 'Some', 'A lot')
Then I will create a skill_level_enum variable and assign it an ENUM class with the skill level values as args.
skill_level_enum = ENUM(*skill_levels, name="skill_level")
In my table model then I pass in the skill_level_enum
class User(db.Model):
id = db.Column(db.Integer(), primary_key=True)
skill_level = db.Column(skill_level_enum)
I have found this makes my code a lot cleaner and I'm able to make updates to the skill_levels at the top of my file rather than scanning my models for the right thing to update.
You can use Python's native enums as the column type as well:
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.dialects.postgresql import ENUM as pgEnum
from enum import Enum, unique
#unique
class errorTypeEnum(Enum):
videoValidation = 'videoValidation'
audioValidation = 'audioValidation'
subtitleValidation = 'subtitleValidation'
db = SQLAlchemy()
class Error(db.Model):
serviceID = db.Column(db.String(20), primary_key=True)
timestamp = db.Column(db.DateTime, unique=False, nullable=False)
category = db.Column(pgEnum(errorTypeEnum), unique=False, nullable=False)
The code below worked for me on SQLAlchemy 1.3.11 and Postgres 12.0.
You must first create the Enum type in postgres before creating the user table. This can be done directly through sql statement.
CREATE TYPE permission AS ENUM ('READ_ONLY', 'READ_WRITE', 'ADMIN', 'OWNER');
Then set up the project model
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(200), nullable=False)
access = db.Column(db.Enum('READ_ONLY', 'READ_WRITE', 'ADMIN', 'OWNER', name="permission"))