How to define two parent classes for SqlAlchemy entities? - python

Until now I have a parent class Entity for all my orm classes:
class AbstractEntity():
id = Column(Integer, primary_key=True)
#declared_attr
def __tablename__(self):
return AbstractEntity.table_name_for_class(self)
...
Entity = declarative_base(cls=AbstractEntity)
class Drink(Entity):
name = Entity.stringColumn()
I want my classes only to inherit from a single class Entity, not from a class Base and a mixin Entity. That works fine.
However, now I would like to introduce another parent class EntityAssociation that I can use as parent for all my asssociation classes that are used for many to many relationships, e.g.
class DrinkIngretients(EntityAssociation):
drink_id = Entity.foreign_key(Drink)
ingredient_id = Entity.foreign_key(Ingredient)
...
The class EntityAssociation should inherit from Base = declarative_base() but not from AbstractEntity. (It should not include the column id that is defined in AbstractEntity.)
=> How can I implement that inheritance structure?
I tried
class AbstractEntity():
id = Column(Integer, primary_key=True)
#declared_attr
def __tablename__(self):
return AbstractEntity.table_name_for_class(self)
...
Base = declarative_base()
class Entity(Base, AbstractEntity):
pass
class EntityAssociation(Base):
pass
However, the behavior of
Entity = declarative_base(cls=AbstractEntity)
and
class Entity(Base, AbstractEntity):
pass
seems to be different.
Class does not have a table or tablename specified and does not inherit from an existing table-mapped class.
=> How can I specify that the classes Entity and EntityAssociation should not have extra table names?
=> Any other suggestions on how to get the wanted inheritance structure?

The __abstract__ flag did the trick:
class EntityRelation(Base):
__abstract__ = True
class Entity(Base, AbstractEntity):
__abstract__ = True

Related

How to create an abstract subclass of SQLAlchemy's Table(Base) class

I am using SQLAlchemy to create tables in my project. I have a requirement where all these tables should have some specific attributes and functions. I want to create a structure such that all tables inherit from an abstract class which includes these attributes and functions.
Here's an example of what I want to achieve:
Base = declarative_base()
# pseudo
class Table(ABC, Base):
# like #abstractattribute
some_attribtue = list()
#staticmethod
def some_func(self):
pass
class Users(Table):
__tablename__ = "users"
user_id = Column(Integer, primary_key=True)
username = Column(String, nullable=False)
some_attribute = list()
#staticmethod
def some_func():
do_something()
By doing this, I hope that I can use these classes in something like:
Base.metadata.create_all(engine)
while also being able to call:
Users.some_func()
I understand that this code wouldn't work as is, due to issues like having ABC and Base at the same time, not having #abstractattribute, and needing to add __tablename__ and a Primary-Key Column to the class Table.
I am thinking of using a decorator to achieve this, but I am not sure how to implement it correctly. This is the outline of my idea:
class Table(ABC):
some_attribute=None
#staticmethod
def some_func(self):
pass
# create decorator
def sql_table():
def decorator(abstract_class):
class SQLTable(Base): # How do I name the class correctly?
__tablename__ = abstract_class.__dict__["__tablename__"]
some_attribute = abstract_class.__dict__["some_attribute"]
for name, obj in abstract_class.__dict__.items():
if isinstance(obj, Column):
locals()[name] = obj
# How do I get the some_func function?
#sql_table
class Users(Table):
__tablename__ = "users"
user_id = Column(Integer, primary_key=True)
username = Column(String, nullable=False)
some_attribute = "some_val"
#staticmethod
def some_func():
do_something()
Any help or suggestions on how to implement this (not necessarily with decorators) would be greatly appreciated.
Thanks to #snakecharmerb and #ljmc I have found an solution that works for me, although there seem to be many ways one can achieve this.
The solution that works for me is:
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy import Column, Integer, String
Base = declarative_base()
class Table(Base):
__abstract__ = True
#declared_attr
def __tablename__(cls) -> str: # so i don't have to specify no more
return cls.__name__.lower()
some_attribute = set() # this is the default
#staticmethod
def some_func(): # define default behavior (or pass)
do_something()
class Users(Table):
# define columns as usual
user_id = Column(Integer, primary_key=True)
username = Column(String, nullable=False)
some_attribute = set(["a"]) # overwrite the default
def some_func(): # overwrite the default behavior
do_something_else()
Now, this should be improved upon by specifying a type to some_attribute (typing is awesome).

Represent a generic decision tree with SQL Alchemy

I want to create a generic decision tree using SQL Alchemy. That is, each node has zero or more children of any type, and the task is to evaluate some expression using the tree root, which will pass on the logic to the children using extending classes.
I have defined the following base class:
from flask_sqlalchemy import Model, SQLAlchemy, DefaultMeta
from abc import ABCMeta, abstractmethod
from sqlalchemy import Column, Integer, String, Date, Boolean, ForeignKey, Text, Float, Unicode
db = SQLAlchemy(model_class=BaseModel)
class BaseModel(Model):
pass
class ModelABCMeta(DefaultMeta, ABCMeta):
pass
class RuleBaseNode(db.Model, metaclass=ModelABCMeta):
"""Generic base class representing a node in a decision tree"""
id = Column(Integer, primary_key=True)
type = Column(String(50))
parent_node_type = Column(Unicode(255), nullable=True)
parent_node_id = Column(Integer, nullable=True)
parent_node = generic_relationship(parent_node_type, parent_node_id)
__mapper_args__ = {
'polymorphic_on': type,
'polymorphic_identity': 'node'
}
#abstractmethod
def eval(self, input) -> bool:
"""Evaluates an input to a boolean"""
pass
Now the question is how to add an attribute of the node children.
Usually, I would use relationship with backref, but I couldn't find anything in the documentation.
I would want such a property:
class RuleBaseNode(db.Model, metaclass=ModelABCMeta):
...
#property
def sub_nodes():
return ...
Now I guess I could implement some sort of the following, but I would guess it won't work with querying an abstract class
def get_sub_nodes(session, node):
session.query(RuleBaseNode).filter(RuleBaseNode.parent_node == node).all()

Why does the attribute 'name' of the sqlalchemy.Column instance disappear?

I'm writing the history of attribute changes for sqlalchemy models. Attributes list, which are important for history, I keep in the class attribute history_attributes.
from sqlalchemy import Integer, Column, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class HistoryAttribute:
def __init__(self, descriptor, display_name, use_message=False):
self.name = descriptor.name
self.display_name = display_name
self.use_message = use_message
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
first_name = Column(String(100))
email = Column(String(100))
history_attributes = (
HistoryAttribute(descriptor=first_name, display_name='User name'),
email
)
print(User.history_attributes[0].name)
>>> None
print(User.history_attributes[1].name)
>>> email
Why does the attribute "name" of the Column instance disappear, if I pass one to constructor of other class? Of course, I can write first_name = Column('first_name', String(100) and code will work fine,
but I don't want to add Column.name explicitly. I avoided the problem using namedtuple, which I then pass to the constructor of the HistoryAttribute class, but it's very similar to a crutch.
It's not that the name attribute disappears, but that it hasn't been initialized yet. Look at the value of first_name as passed to HistoryAttribute: Column(String(100)); it does not contain any mention of the string first_name. SQLAlchemy will fill the name in later after the class is defined.
As has been discussed, the issue is that first_name.name is set while the class is being constructed.
SQLAlchemy does have a mechanism for deferring an attribute definition until configuration time. Try something like
class User(Base):
# ...
#sqlalchemy.ext.declarative.api.declared_attr
def history_attributes(self):
return (HistoryAttribute(self.first_name #...))
That will defer the construction of history_attributes until a point where column names are populated

An alias of a sqlalchemy class?

This is my base class
class Product(Base):
__tablename__ = 'PRODUCT'
__table_args__ = {'quote':False}
...
id = Column(Integer, name='id_prod', primary_key=True)
type = Column(String(100),name='id_typ_prod')
__mapper_args__ = {'polymorphic_on': type}
So, naturally we have a number of classes that extends from this Product, e.g. Phone and Cable, each of them maps to its own table.
class Phone (Product):
__tablename__ = 'PHONE'
...
Now for some reasons now I want to create a 'alias' class, a class that does not have a corresponding table in database. Something like this:
class VapourWare(Product):
...
If I do
class VapourWare(Product):
__tablename__ = 'PRODUCT'
__mapper_args__ = {'polymorphic_identity':'VapourWare'}
It seems to work. But is it the right or recommended way? I am repeating __tablename__ = 'PRODUCT' here.
To some degree it depends on what you're trying to achieve, but from your example it seems that what you're trying to do is called Single-Table Inheritence in the SA Docs. The example listed on the linked page seems very much like your example, with Employee == Product and Manager == VapourWare (insert Dilbert joke here).

Sqlalchemy: avoiding multiple inheritance and having abstract base class

So I have a bunch of tables using SQLAlchemy that are modelled as objects which inherit from the result to a call to declarative_base(). Ie:
Base = declarative_base()
class Table1(Base):
# __tablename__ & such here
class Table2(Base):
# __tablename__ & such here
Etc. I then wanted to have some common functionality available to each of my DB table classes, the easiest way to do this according to the docs is to just do multiple inheritance:
Base = declarative_base()
class CommonRoutines(object):
#classmethod
def somecommonaction(cls):
# body here
class Table1(CommonRoutines, Base):
# __tablename__ & such here
class Table2(CommonRoutines, Base):
# __tablename__ & such here
The thing I don't like about this is A) multiple inheritance in general is a bit icky (gets tricky resolving things like super() calls, etc), B) if I add a new table I have to remember to inherit from both Base and CommonRoutines, and C) really that "CommonRoutines" class "is-a" type of table in a sense. Really what CommonBase is is an abstract base class which defines a set of fields & routines which are common to all tables. Put another way: "its-a" abstract table.
So, what I'd like is this:
Base = declarative_base()
class AbstractTable(Base):
__metaclass__ = ABCMeta # make into abstract base class
# define common attributes for all tables here, like maybe:
id = Column(Integer, primary_key=True)
#classmethod
def somecommonaction(cls):
# body here
class Table1(AbstractTable):
# __tablename__ & Table1 specific fields here
class Table2(AbstractTable):
# __tablename__ & Table2 specific fields here
But this of course doesn't work, as I then have to A) define a __tablename__ for AbstractTable, B) the ABC aspect of things causes all sorts of headaches, and C) have to indicate some sort of DB relationship between AbstractTable and each individual table.
So my question: is it possible to achieve this in a reasonable way? Ideally I'd like to enforce:
No multiple inheritance
CommonBase/AbstractTable be abstract (ie cannot be instantiated)
SQLAlchemy version 0.7.3 introduced the __abstract__ directive which is used for abstract classes that should not be mapped to a database table, even though they are subclasses of sqlalchemy.ext.declarative.api.Base. So now you create a base class like this:
Base = declarative_base()
class CommonRoutines(Base):
__abstract__ = True
id = Column(Integer, primary_key=True)
def __init__(self):
# ...
Notice how CommonRoutines doesn't have a __tablename__ attribute. Then create subclasses like this:
class Foo(CommonRoutines):
__tablename__ = 'foo'
name = Column(...)
def __init__(self, name):
super().__init__()
self.name = name
# ...
This will map to the table foo and inherit the id attribute from CommonRoutines.
Source and more information: http://docs.sqlalchemy.org/en/rel_0_7/orm/extensions/declarative.html#abstract
It is pretty straigh-forward, you just make declarative_base() to return a Base class which inherits from your CommonBase using cls= parameter. Also shown in Augmenting The Base docs. Your code might then look similar to below:
class CommonBase(object):
#classmethod
def somecommonaction(cls):
# body here
Base = declarative_base(cls=CommonBase)
class Table1(Base):
# __tablename__ & Table1 specific fields here
class Table2(Base):
# __tablename__ & Table2 specific fields here
You can use AbstractConcreteBase to make an absract base model:
from sqlalchemy.ext.declarative import AbstractConcreteBase
class AbstractTable(AbstractConcreteBase, Base):
id = db.Column(db.Integer, primary_key=True)
#classmethod
def somecommonaction(cls):
# body here
If you want to have several models with common columns, then you can use __abstract__ and #declared_attr to inherit shared table attributes. Example:
Base = declarative_base()
class CommonRoutines(Base):
__abstract__ = True
id = Column(Integer, primary_key=True)
modified_at = Column(DateTime)
#declared_attr
def modified_by(self):
# `user.id` is another table called `user` with an `id` field
return Column(Integer, ForeignKey('user.id', name='fk_modified_by_user_id'))
def __init__(self):
self.modified_by = None
super().__init__()
class Foo(CommonRoutines):
__tablename__ = 'foo'
name = Column(...)
With this solution you will have a Foo table with the fields of Foo class (name) and the ones in CommonRoutines (id, modified_at and modified_by)

Categories

Resources