In Sqlalchemy, what's different in defining table using Base and Table - sqlalchemy

I know i can define a table using Table:
user = Table('user', metadata,
Column('user_id', Integer, primary_key = True),
)
and using Base:
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
user_id= Column(Integer, primary_key = True)
but what's the different???

Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column('id', Integer, primary_key=True)
name = Column('name', Unicode(64))
is just syntactic sugar for
metadata = MetaData()
user = Table('user', metadata,
Column('id', Integer, primary_key=True),
Column('name', Unicode(64)),
)
class User(object):
pass
mapper(User, user) # this will modify the User class!

Related

How to show different keyboards to different users in pyTelegramBotAPI using SQLAlchemy

I'm building telegram bot with SQLAlchemy and have the issue. I have class User (table users), that have relationship with class Role (table roles), accordingly, user can have different roles.
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
telegram_id = Column(String)
role_id = Column(Integer, ForeignKey("roles.id"))
role = relationship("Role", back_populates="users")
class Role(Base):
__tablename__ = 'roles'
id = Column(Integer, primary_key=True, index=True)
name = Column(String)
users = relationship("User", back_populates="role")
I want, to User with role "user" and with role "admin" have different keyboards when you call certain method. If there is the way, to do it with inheritance - it would be more prefeable

Generic Association with generic fk: Can't get rid of overlap warning

In a database that I have no control over I have what I believe is a Generic Association using generic foreign keys. In short: A CommunicationInterface belongs either to a Router or a Server, never both. There's no discriminator because ids are unique over the entire database.
Code shortened for brevity:
class Server(...):
__tablename__ = 'server'
id = Column('_oid', Integer, primary_key=True)
interfaces = relationship(
'CommunicationInterface', backref='server',
primaryjoin='Server.id == foreign(CommunicationInterface.parent_id)'
)
class Router(...):
__tablename__ = 'router'
id = Column('_oid', Integer, primary_key=True)
interfaces = relationship(
'CommunicationInterface', backref='router',
primaryjoin='Router.id == foreign(CommunicationInterface.parent_id)'
)
class CommunicationInterface(...):
__tablename__ = 'communicationinterface'
id = Column('_oid', Integer, primary_key=True)
parent_id = Column('_parent_oid', Integer)
overlaps = 'router,server'
router = relationship(
'Router', back_populates='interfaces', overlaps=overlaps,
primaryjoin='Router.id == foreign(CommunicationInterface.parent_id)'
)
server = relationship(
'Server', back_populates='interfaces', overlaps=overlaps,
primaryjoin='Server.id == foreign(CommunicationInterface.parent_id)'
)
Even with the overlap parameter this still gives me warnings like the following:
SAWarning: relationship 'Router.interfaces' will copy column router._oid to column communicationinterface._parent_oid, which conflicts with relationship(s): 'CommunicationInterface.server' (copies elb_server._oid to elb_communicationinterface._parent_oid). If this is not the intention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards. The 'overlaps' parameter may be used to remove this warning.
Is there a better way to declare these relations? And if not, am I using the overlaps parameter wrong?
The issue that you faced has a few reasons:
Mixing of backref and back_populates
backref also creates a relationship on the other side
back_populates expects a relationship on the other side
overlaps incorrect missing on the relationship on the other side
This should solve your problem:
class Server(Base):
__tablename__ = 'server'
id = Column('_oid', Integer, primary_key=True)
class Router(Base):
__tablename__ = 'router'
id = Column('_oid', Integer, primary_key=True)
class CommunicationInterface(Base):
__tablename__ = 'communicationinterface'
id = Column('_oid', Integer, primary_key=True)
parent_id = Column('_parent_oid', Integer)
router = relationship(
'Router', backref=backref('interface', lazy='subquery', overlaps='server, interface'),
primaryjoin='Router.id == foreign(CommunicationInterface.parent_id)'
)
server = relationship(
'Server', backref=backref('interface', lazy='subquery', overlaps='router'), overlaps='router',
primaryjoin='Server.id == foreign(CommunicationInterface.parent_id)'
)
Or if you want to do it with back_populates:
class Server(Base):
__tablename__ = 'server'
id = Column('_oid', Integer, primary_key=True)
interface = relationship(
'CommunicationInterface', back_populates='server', overlaps='router',
primaryjoin='Server.id == foreign(CommunicationInterface.parent_id)'
)
class Router(Base):
__tablename__ = 'router'
id = Column('_oid', Integer, primary_key=True)
interface = relationship(
'CommunicationInterface', back_populates='router', overlaps='server, interface',
primaryjoin='Router.id == foreign(CommunicationInterface.parent_id)'
)
class CommunicationInterface(Base):
__tablename__ = 'communicationinterface'
id = Column('_oid', Integer, primary_key=True)
parent_id = Column('_parent_oid', Integer)
router = relationship(
'Router', back_populates='interface',
primaryjoin='Router.id == foreign(CommunicationInterface.parent_id)'
)
server = relationship(
'Server', back_populates='interface', overlaps='router',
primaryjoin='Server.id == foreign(CommunicationInterface.parent_id)'
)

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 conditional many to many relationship with an additional column

This is the way that I usually use for m2m relationship implementation.
(Brought from docs.sqlalchemy.org)
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=association_table,
backref="parents")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
Is there any way for using additional columns at the association_table table?
So it should be like
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id')),
Column('is_valid', Boolean, default=True) # Add the validation column
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=association_table,
backref="parents")
# How can I do implement this??
valid_children = relationship("Child",
secondary="and_(association_table.left_id == Parent.id, association_table.right_id == Child.id)"
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
I want to do query depends on is_valid column. How can I modify "secondary" attr in Parent table? Or should I fix the other part?
In this question, time_create column has the same value for all children. But in this case, I need a flag that makes able to retrieve whether this connection is still alive or not.
For example, if you implement a one-on-one chatting, there will be a chatting room consist of two-person, right?
And the table should be like as below:
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id')),
Column('is_left', Boolean, default=False) # Whether the user left or not
)
class Match(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
user = relationship("User",
secondary=association_table,
backref="matches")
# How can I do implement this??
exist_user = relationship("User",
secondary="and_(association_table.left_id == Parent.id, association_table.right_id == Child.id)"
class User(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
nickname = Column(String, unique=True)
How can I do for this?

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()