Alembic TypeError: Additional arguments should be named <dialectname>_<argument>, got 'nullable' - sqlalchemy

Hi I added a new model to SQLAlchemy models.
But this change trigers this error on all alembic commands
Alembic TypeError: Additional arguments should be named <dialectname>_<argument>, got 'nullable'
My table looks like this:
class XClass(Base):
__tablename__ = "x_table_name"
X1 = Column(
String, ForeignKey("y_table.COLUMN", deferrable=True, initially="DEFERRED"),
primary_key=True,
nullable=False
)
X2 = Column(String, nullable=False)
PROBLEMATIC_COLUMN = Column(
String, ForeignKey("table_a.Y3", deferrable=True, initially="DEFERRED",
nullable=True,
unique=True)
)
X3 = Column(String, nullable=False)
X4 = Column(String, nullable=False)
X% = Column(String, nullable=False)
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
Model from table_a in the PROBLEMATIC_COLUMN looks like this:
class TableA(Base):
__tablename__ = "table_a"
Y1 = Column(
String,
ForeignKey("table_b.Z1", deferrable=True, initially="DEFERRED"),
primary_key=True,
)
Y2 = Column(String, primary_key=True)
Y3 = Column(String, primary_key=True)
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
When change PROBLEMATIC_COLUMNT in XClass to:
PROBLEMATIC_COLUMN = Column(String, nullable=False)
Alembic works corectly.
Any one knows what im doin wrong?

Related

Pydantic how to get relationship value

I need to get one value from relationship Many-to-one model in pydantic BaseModel.
How can I do this?
My children class
class Picnic(Base):
__tablename__ = 'picnic'
id = Column(Integer, primary_key=True, autoincrement=True)
city_id = Column(Integer, ForeignKey('city.id'), nullable=False)
city = relationship('City', backref='picnics')
class City(Base):
__tablename__ = 'city'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, unique=True, nullable=False, index=True)
I need to get city name value :
class Picnics(BaseModel):
id: int
# city: str[CityBaseInDB.name] not working
# city: str = Field(source='city.name') not working
# city_name: str not working
class Config:
orm_mode: bool = True
from sqlalchemy import Column, ForeignKey, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, relationship, sessionmaker, Query
from pydantic import BaseModel
Base = declarative_base()
engine = create_engine("sqlite://", echo=True)
class Picnic(Base):
__tablename__ = 'picnic'
id = Column(Integer, primary_key=True, autoincrement=True)
city_id = Column(Integer, ForeignKey('city.id'), nullable=False)
city = relationship('City', backref='picnics')
class City(Base):
__tablename__ = 'city'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, unique=True, nullable=False, index=True)
class PicnicModel(BaseModel):
id: int
# city: str[CityBaseInDB.name] not working
# city: str = Field(source='city.name') not working
city_name: str
class Config:
orm_mode: bool = True
picnic = Picnic(city=City(name='Shenzhen'))
Base.metadata.create_all(engine)
LocalSession = sessionmaker(bind=engine)
db: Session = LocalSession()
db.add(picnic)
db.commit()
q: Query = db.query(Picnic.id, City.name.label('city_name'))
q = q.select_from(Picnic).join(City)
row = q.one_or_none()
model = PicnicModel.from_orm(row)
print(model)
You need to have a pydantic model for your parent model too and put it as the type in the relationship field.
from pydantic import BaseModel
class City(BaseModel):
id: int
name: str
class Config:
orm_mode: bool = True
class Picnics(BaseModel):
id: int
city: City
class Config:
orm_mode: bool = True

SQLAlchemy - how to make column unique per ForeignKey relationship?

In the Todo model below there is a unique constraint put on text column.
How can I narrow this constraint to validate uniqueness per "foreign-keyed" user only, not per all users as it is now?
I use SQLite.
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
from sqlalchemy.orm import relationship
from database import Base
class Todo(Base):
__tablename__ = 'todos'
id = Column(Integer, primary_key=True, index=True)
text = Column(String, unique=True)
user_id = Column(Integer, ForeignKey('users.id'))
user = relationship("User", back_populates="todos")
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
todos = relationship("Todo", back_populates="user")
If you use postgresql, you can use the Partial Index to implement this:
class Todo(Base):
__tablename__ = 'todos'
id = Column(Integer, primary_key=True, index=True)
text = Column(String) # ! removed the unique from here
user_id = Column(Integer, ForeignKey('users.id'))
user = relationship("User", back_populates="todos")
# ! added unique index with
__table_args__ = (
Index(
"todo_text_uc",
"text",
unique=True,
postgresql_where=(user_id != None),
# postgresql_where=(~user_id.is_(None)), # equivalent to the row above, but no linting warnings
),
)
For sqlite just replace postgresql_where with sqlite_where.

SQLAlchemy Circular dependency detected

I created two models for my application with SQLAlchemy, but while using it i receive this error message:
sqlalchemy.exc.CircularDependencyError: Circular dependency detected. (ProcessState(ManyToOneDP(PlayerModel.clan), <PlayerModel at 0x7f9503070ac8>, delete=False), ProcessState(ManyToOneDP(ClanModel.commander), <ClanModel at 0x7f95030b8da0>, delete=False), ProcessState(OneToManyDP(ClanModel.players), <ClanModel at 0x7f95030b8da0>, delete=False), SaveUpdateState(<PlayerModel at 0x7f9503070ac8>), SaveUpdateState(<ClanModel at 0x7f95030b8da0>), ProcessState(ManyToOneDP(ClanModel.owner), <ClanModel at 0x7f95030b8da0>, delete=False))
How can I fix this error?
class PlayerModel(Base):
__tablename__ = 'player'
chat_id = Column(Integer, primary_key=True)
clan_id = Column(Integer, ForeignKey('clan.id'))
clan = relationship("ClanModel", foreign_keys='PlayerModel.clan_id', back_populates="players", primaryjoin="PlayerModel.clan_id==ClanModel.id")
def __repr__(self):
return "<User(name='{}',level={})>".format(self.name, self.level)
class ClanModel(Base):
__tablename__ = 'clan'
id = Column(Integer, primary_key=True)
war_with_id = Column(Integer, ForeignKey('clan.id'))
war_with_from = relationship(
'ClanModel',
uselist=False,
remote_side=[id],
backref=backref('war_with_to', uselist=False),
)
owner_id = Column(Integer, ForeignKey('player.chat_id'))
owner = relationship("PlayerModel", uselist=False, foreign_keys=owner_id)
commander_id = Column(Integer, ForeignKey('player.chat_id'))
commander = relationship("PlayerModel", uselist=False, foreign_keys=commander_id)
players = relationship("PlayerModel", back_populates="clan", foreign_keys=PlayerModel.clan_id)

Abstract Table Concrete Inheritance. Could not determine join condition between parent/child tables

I've got following example code:
models.py
class CadastralObject(Base):
__tablename__ = 'cadastral_object'
def __init__(self, cadastral_region, cadastral_district, cadastral_block, cadastral_object):
self.cadastral_region = cadastral_region
self.cadastral_district = cadastral_district
self.cadastral_block = cadastral_block
self.cadastral_object = cadastral_object
# this is a combined PK
cadastral_region = Column(Integer, primary_key=True, index=True)
cadastral_district = Column(Integer, primary_key=True, index=True)
cadastral_block = Column(Integer, primary_key=True, index=True)
cadastral_object = Column(Integer, primary_key=True, index=True)
encumbrances = relationship("Encumbrance")
class Encumbrance(Base):
__tablename__ = 'encumbrance'
id = Column(Integer, primary_key=True, index=True)
def __init__(self, cadastral_object):
self.parent_cadastral_region = cadastral_object.cadastral_region
self.parent_cadastral_district = cadastral_object.cadastral_district
self.parent_cadastral_block = cadastral_object.cadastral_block
self.parent_cadastral_object = cadastral_object.cadastral_object
# FK fields
parent_cadastral_region = Column(Integer, nullable=False)
parent_cadastral_district = Column(Integer, nullable=False)
parent_cadastral_block = Column(Integer, nullable=False)
parent_cadastral_object = Column(Integer, nullable=False)
parent_object = relationship(CadastralObject)
__table_args__ = (ForeignKeyConstraint(
[
parent_cadastral_region,
parent_cadastral_district,
parent_cadastral_block,
parent_cadastral_object],
[
CadastralObject.cadastral_region,
CadastralObject.cadastral_district,
CadastralObject.cadastral_block,
CadastralObject.cadastral_object]),
{}
)
this code works as intended:
main.py
c = CadastralObject(1, 2, 3, 4)
session.add(c)
e = Encumbrance(c)
session.add(e)
session.commit()
print(c.encumbrances)
print(e.parent_object)
results:
[<app.models.Encumbrance object at 0x000001C9B820BCC0>]
<app.models.CadastralObject object at 0x000001C9B820BB00>
however, when I'm trying convert my code to Concrete Inheritance:
imodels.py
class iCadastralObject(AbstractConcreteBase, Base):
def __init__(self, cadastral_region, cadastral_district, cadastral_block, cadastral_object):
self.cadastral_region = cadastral_region
self.cadastral_district = cadastral_district
self.cadastral_block = cadastral_block
self.cadastral_object = cadastral_object
# this is a combined PK
cadastral_region = Column(Integer, primary_key=True, index=True)
cadastral_district = Column(Integer, primary_key=True, index=True)
cadastral_block = Column(Integer, primary_key=True, index=True)
cadastral_object = Column(Integer, primary_key=True, index=True)
#declared_attr
def encumbrances(self):
return relationship("iEncumbrance")
class Building(iCadastralObject):
__tablename__ = 'building'
__mapper_args__ = {
'polymorphic_identity': 'building',
'concrete': True
}
#declared_attr
def encumbrances(self):
return relationship("iEncumbrance")
class Flat(iCadastralObject):
__tablename__ = 'flat'
__mapper_args__ = {
'polymorphic_identity': 'flat',
'concrete': True
}
#declared_attr
def encumbrances(self):
return relationship("iEncumbrance")
class Construction(iCadastralObject):
__tablename__ = 'construction'
__mapper_args__ = {
'polymorphic_identity': 'construction',
'concrete': True
}
class iEncumbrance(Base):
__tablename__ = 'iencumbrance'
id = Column(Integer, primary_key=True, index=True)
def __init__(self, cadastral_object):
self.parent_cadastral_region = cadastral_object.cadastral_region
self.parent_cadastral_district = cadastral_object.cadastral_district
self.parent_cadastral_block = cadastral_object.cadastral_block
self.parent_cadastral_object = cadastral_object.cadastral_object
# FK fields
parent_cadastral_region = Column(Integer, nullable=False)
parent_cadastral_district = Column(Integer, nullable=False)
parent_cadastral_block = Column(Integer, nullable=False)
parent_cadastral_object = Column(Integer, nullable=False)
parent_object = relationship(iCadastralObject)
__table_args__ = (ForeignKeyConstraint(
[
parent_cadastral_region,
parent_cadastral_district,
parent_cadastral_block,
parent_cadastral_object],
[
iCadastralObject.cadastral_region,
iCadastralObject.cadastral_district,
iCadastralObject.cadastral_block,
iCadastralObject.cadastral_object]),
{}
)
I'm getting an error on "from app.imodels import Building"
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship iCadastralObject.encumbrances - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.

Star schema in SQLAlchemy

I have a star-schema architectured database that I want to represent in SQLAlchemy. Now I have the problem on how this can be done in the best possible way. Right now I have a lot of properties with custom join conditions, because the data is stored in different tables.
It would be nice if it would be possible to re-use the dimensions for different fact tablesw but I haven't figured out how that can be done nicely.
A typical fact table in a star schema contains foreign key references to all dimension tables, so usually there wouldn't be any need for custom join conditions - they are determined automatically from foreign key references.
For example a star schema with two fact tables would look like:
Base = declarative_meta()
class Store(Base):
__tablename__ = 'store'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(50), nullable=False)
class Product(Base):
__tablename__ = 'product'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(50), nullable=False)
class FactOne(Base):
__tablename__ = 'sales_fact_one'
store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
product_id = Column('product_id', Integer, ForeignKey('product.id'), primary_key=True)
units_sold = Column('units_sold', Integer, nullable=False)
store = relation(Store)
product = relation(Product)
class FactTwo(Base):
__tablename__ = 'sales_fact_two'
store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
product_id = Column('product_id', Integer, ForeignKey('product.id'), primary_key=True)
units_sold = Column('units_sold', Integer, nullable=False)
store = relation(Store)
product = relation(Product)
But suppose you want to reduce the boilerplate in any case. I'd create generators local to the dimension classes which configure themselves on a fact table:
class Store(Base):
__tablename__ = 'store'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(50), nullable=False)
#classmethod
def add_dimension(cls, target):
target.store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
target.store = relation(cls)
in which case usage would be like:
class FactOne(Base):
...
Store.add_dimension(FactOne)
But, there's a problem with that. Assuming the dimension columns you're adding are primary key columns, the mapper configuration is going to fail since a class needs to have its primary keys set up before the mapping is set up. So assuming we're using declarative (which you'll see below has a nice effect), to make this approach work we'd have to use the instrument_declarative() function instead of the standard metaclass:
meta = MetaData()
registry = {}
def register_cls(*cls):
for c in cls:
instrument_declarative(c, registry, meta)
So then we'd do something along the lines of:
class Store(object):
# ...
class FactOne(object):
__tablename__ = 'sales_fact_one'
Store.add_dimension(FactOne)
register_cls(Store, FactOne)
If you actually have a good reason for custom join conditions, as long as there's some pattern to how those conditions are created, you can generate that with your add_dimension():
class Store(object):
...
#classmethod
def add_dimension(cls, target):
target.store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
target.store = relation(cls, primaryjoin=target.store_id==cls.id)
But the final cool thing if you're on 2.6, is to turn add_dimension into a class decorator. Here's an example with everything cleaned up:
from sqlalchemy import *
from sqlalchemy.ext.declarative import instrument_declarative
from sqlalchemy.orm import *
class BaseMeta(type):
classes = set()
def __init__(cls, classname, bases, dict_):
klass = type.__init__(cls, classname, bases, dict_)
if 'metadata' not in dict_:
BaseMeta.classes.add(cls)
return klass
class Base(object):
__metaclass__ = BaseMeta
metadata = MetaData()
def __init__(self, **kw):
for k in kw:
setattr(self, k, kw[k])
#classmethod
def configure(cls, *klasses):
registry = {}
for c in BaseMeta.classes:
instrument_declarative(c, registry, cls.metadata)
class Store(Base):
__tablename__ = 'store'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(50), nullable=False)
#classmethod
def dimension(cls, target):
target.store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
target.store = relation(cls)
return target
class Product(Base):
__tablename__ = 'product'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(50), nullable=False)
#classmethod
def dimension(cls, target):
target.product_id = Column('product_id', Integer, ForeignKey('product.id'), primary_key=True)
target.product = relation(cls)
return target
#Store.dimension
#Product.dimension
class FactOne(Base):
__tablename__ = 'sales_fact_one'
units_sold = Column('units_sold', Integer, nullable=False)
#Store.dimension
#Product.dimension
class FactTwo(Base):
__tablename__ = 'sales_fact_two'
units_sold = Column('units_sold', Integer, nullable=False)
Base.configure()
if __name__ == '__main__':
engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)
sess = sessionmaker(engine)()
sess.add(FactOne(store=Store(name='s1'), product=Product(name='p1'), units_sold=27))
sess.commit()