sqlalchemy setting a relationship - python

I am trying to create a relationship between the two tables.
When instantiating a model I get the following error:
from core.models import Activation
a = Activation()
ArgumentError: Class object expected, got 'Table(u'activations', MetaData(bind=Engine(postgresql+psycopg2://localhost:5432/mydb)), Column('id', Integer(), table=<activations>, primary_key=True, nullable=False), Column('valid_until', DateTime(), table=<activations>), Column('code', Unicode(length=30), table=<activations>, nullable=False), Column('created_by', Unicode(length=16), table=<activations>, nullable=False), schema=None)'.
core/models.py
class ActivationMixin(Base):
#declared_attr
def code(self):
return Column(Unicode(30), nullable=False, unique=True)
#declared_attr
def valid_until(self):
return Column(DateTime, nullable=True)
#declared_attr
def created_by(self):
return Column(Unicode(16), nullable=False)
#classmethod
def get_by_code(cls, request, code):
session = get_session(request)
return session.query(cls).filter(cls.code == code).first()
def __init__(self, created_by='web', valid_until=None):
"""Create a new activation. *valid_until* is a datetime.
It defaults to 3 days from current day.
"""
self.code = generate_random_string(24)
self.created_by = created_by
if valid_until:
self.valid_until = valid_until
else:
self.valid_until = datetime.utcnow() + timedelta(days=3)
class Activation(ActivationMixin):
pass
user/models.py
class User(Base):
email = Column(Unicode(256), nullable=False, unique=True)
status = Column(Boolean, default=False)
salt = Column(Unicode(32), nullable=False)
_password = Column('password', Unicode(256), nullable=False)
logins = Column(Integer, default=0)
last_login = Column(
TIMESTAMP(timezone=False),
default=func.now(),
server_default=func.now(),
nullable=False
)
account_type = Column(AccountType.db_type())
#declared_attr
def activation_id(self):
return Column(
Integer,
ForeignKey('%s.id' % (Activation.__tablename__))
)
#property
def is_activated(self):
if self.activation_id is None:
return True
return False
#declared_attr
def activation(self):
return relationship(
Activation.__tablename__,
backref=self.__tablename__
)
The error is occures with the following declaration:
#declared_attr
def activation(self):
return relationship(
Activation.__tablename__,
backref=self.__tablename__
)

There seems to be some code that you haven't posted since running just this code would give you other errors as well (e.g., related to the __tablename__ attributes not being properly set). However, the actual error you are getting is probably related to this code...
#declared_attr
def activation(self):
return relationship(
Activation.__tablename__,
backref=self.__tablename__
)
According to the documentation for relationship function, the first argument should be...
a mapped class, or actual Mapper instance, representing the target of
the relationship.
However, you are using the __tablename__ attribute, which should be just by the name of the table (a string).
So, try changing this to...
#declared_attr
def activation(self):
return relationship(
Activation,
backref=self.__tablename__
)

Related

Difference between abstract class Model and Mixin in SQLAlchemy

Mixin in SQLAlchemy can be used to share functionalities and attributes between Models. But as I undersand, it also achieve using inheritance over abstract class. So my question is what is the difference between the following two implementations:
Using abstract Model:
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.declarative import declared_attr,
db = SQLAlchemy()
class BaseModel(db.Model):
__abstract__ = True
#declared_attr
def iid(cls):
return db.Column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
unique=True,
nullable=False,
)
#declared_attr
def created_on(cls):
return db.Column(db.DateTime, server_default=db.func.now())
#declared_attr
def updated_on(cls):
return db.Column(
db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now()
)
def save(self):
db.session.add(self)
db.session.commit()
def delete(self):
db.session.delete(self)
db.session.commit()
class SomeEntity(BaseModel):
some_field = db.Column(db.String)
Using Mixin:
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.declarative import declared_attr,
db = SQLAlchemy()
class Mixin(object):
#declared_attr
def iid(cls):
return db.Column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
unique=True,
nullable=False,
)
#declared_attr
def created_on(cls):
return db.Column(db.DateTime, server_default=db.func.now())
#declared_attr
def updated_on(cls):
return db.Column(
db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now()
)
def save(self):
db.session.add(self)
db.session.commit()
def delete(self):
db.session.delete(self)
db.session.commit()
class SomeEntity(db.Model, Mixin):
some_field = db.Column(db.String)
Are those implementations equivalent ? In which case should I use a Mixin instead of an abstract Model class ?

Postgres / SQLAlchemy: no matching unique constraint

I am working on a system which requires a composite foreign key in SQLalchemy with Postgres, and have come up with the following example TestCase as a proof of concept. The idea is that every Thing must reference a unique combination of ThingFeatureType, through two columns tt_id (thing type id) and feature (a string). ThingType and Feature also have their own tables.
When I run the code below using pytest, I get the following error complaining that there is no UniqueConstraint on (thing_feature_type.tt_id, feature). However, there definitely is!
Any help on this much appreciated!
Error:
self = <sqlalchemy.dialects.postgresql.psycopg2.PGDialect_psycopg2 object at 0x7f4f61ee4320>, cursor = <cursor object at 0x7f4f61ee1808; closed: -1>
statement = '\nCREATE TABLE thing (\n\tt_id SERIAL NOT NULL, \n\ttt_id INTEGER, \n\tfeature VARCHAR(64), \n\tname VARCHAR(128) NOT...RY KEY (t_id), \n\tFOREIGN KEY(tt_id, feature) REFERENCES thing_feature_type (tt_id, feature) ON DELETE CASCADE\n)\n\n'
parameters = {}, context = <sqlalchemy.dialects.postgresql.psycopg2.PGExecutionContext_psycopg2 object at 0x7f4f5f4a91d0>
def do_execute(self, cursor, statement, parameters, context=None):
> cursor.execute(statement, parameters)
E sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) there is no unique constraint matching given keys for referenced table "thing_feature_type"
E [SQL: '\nCREATE TABLE thing (\n\tt_id SERIAL NOT NULL, \n\ttt_id INTEGER, \n\tfeature VARCHAR(64), \n\tname VARCHAR(128) NOT NULL, \n\tPRIMARY KEY (t_id), \n\tFOREIGN KEY(tt_id, feature) REFERENCES thing_feature_type (tt_id, feature) ON DELETE CASCADE\n)\n\n'] (Background on this error at: http://sqlalche.me/e/f405)
venv/lib/python3.5/site-packages/SQLAlchemy-1.2.7-py3.5-linux-x86_64.egg/sqlalchemy/engine/default.py:507: ProgrammingError
Code:
from unittest import TestCase
from sqlalchemy import (case,
Column,
Float,
ForeignKey,
Integer,
String,
Table,
Text, )
from sqlalchemy.orm import relationship
from sqlalchemy.schema import ForeignKeyConstraint, UniqueConstraint
from concept_back_end.run import app
from concept_back_end.database import db
def define_feature(model):
class Feature(model):
feature = Column(String(64), primary_key=True)
#classmethod
def _define_relationships(cls):
cls.feature_types = relationship('FeatureType',
back_populates='the_feature',
cascade='save-update, delete',
lazy='select')
return Feature
def define_thing_type(model):
class ThingType(model):
tt_id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(128), nullable=False)
#classmethod
def _define_relationships(cls):
cls.things = relationship('Thing',
back_populates='thing_type',
cascade='save-update, delete',
lazy='select')
cls.thing_feature_types = relationship(
'ThingFeatureType',
back_populates='thing_type',
cascade='save-update, delete',
lazy='select'
)
return ThingType
def define_thing_feature_type(model):
class ThingFeatureType(model):
__tablename__ = 'thing_feature_type'
ft_id = Column(Integer, primary_key=True, autoincrement=True)
feature = Column(String(64),
ForeignKey('feature.feature'))
tt_id = Column(Integer, ForeignKey('thing_type.tt_id'))
__table_args__ = (
UniqueConstraint('tt_id', 'feature'),
)
#classmethod
def _define_relationships(cls):
cls.the_feature = relationship('Feature',
back_populates='feature_types',
lazy='select')
cls.thing_type = relationship('ThingType',
back_populates='feature_types',
lazy='select')
cls.things = relationship('Thing',
back_populates='feature_type',
lazy='select')
return ThingFeatureType
def define_thing(model):
class Thing(model):
t_id = Column(Integer, primary_key=True, autoincrement=True)
tt_id = Column(Integer)
feature = Column(String(64))
name = Column(String(128), nullable=False)
__table_args__ = (
ForeignKeyConstraint(
('tt_id', 'feature'),
('thing_feature_type.tt_id', 'thing_feature_type.feature'),
ondelete='CASCADE'
),
{},
)
#classmethod
def _define_relationships(cls):
cls.thing_type = relationship('ThingType',
back_populates='things',
lazy='select')
cls.feature_type = relationship('ThingFeatureType',
back_populates='things',
lazy='select')
return Thing
model_factories = [
define_feature,
define_thing_type,
define_thing_feature_type,
define_thing,
]
"""List of factory functions"""
class ForeignKeyExampleTestCase(TestCase):
def setUp(self):
with app.app_context():
models = [m(db.Model) for m in model_factories]
for m in models:
m._define_relationships()
db.create_all()
db.session.commit()
def test_can_connect_to_db(self):
with app.app_context():
db.session.execute('SELECT * FROM thing;')
def tearDown(self):
"""And then tear them down again"""
with app.app_context():
db.session.close()
db.drop_all()
This appeared to be an issue with stale data in the db. The final working code is below.
from unittest import TestCase
from sqlalchemy import (case,
Column,
Float,
ForeignKey,
ForeignKeyConstraint,
Integer,
String,
Table,
Text,
UniqueConstraint, )
from sqlalchemy.orm import relationship
from sqlalchemy.schema import CreateTable
from concept_back_end.run import app
from concept_back_end.database import db
def define_feature(model):
class Feature(model):
feature = Column(String(64), primary_key=True, unique=True)
#classmethod
def _define_relationships(cls):
cls.feature_types = relationship('FeatureType',
back_populates='the_feature',
cascade='save-update, delete',
lazy='select')
return Feature
def define_thing_type(model):
class ThingType(model):
tt_id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(128), nullable=False)
#classmethod
def _define_relationships(cls):
cls.things = relationship('Thing',
back_populates='thing_type',
cascade='save-update, delete',
lazy='select')
cls.thing_feature_types = relationship(
'ThingFeatureType',
back_populates='thing_type',
cascade='save-update, delete',
lazy='select'
)
return ThingType
def define_thing_feature_type(model):
class ThingFeatureType(model):
__tablename__ = 'thing_feature_type'
__table_args__ = (
UniqueConstraint('tt_id', 'feature'),
{},
)
ft_id = Column(Integer, primary_key=True, autoincrement=True)
feature = Column(String(64),
ForeignKey('feature.feature'))
tt_id = Column(Integer, ForeignKey('thing_type.tt_id'))
#classmethod
def _define_relationships(cls):
cls.the_feature = relationship('Feature',
back_populates='feature_types',
lazy='select')
cls.thing_type = relationship('ThingType',
back_populates='feature_types',
lazy='select')
cls.things = relationship('Thing',
back_populates='feature_type',
lazy='select')
return ThingFeatureType
def define_thing(model):
class Thing(model):
t_id = Column(Integer, primary_key=True, autoincrement=True)
tt_id = Column(Integer)
feature = Column(String(64))
name = Column(String(128), nullable=False)
__table_args__ = (
ForeignKeyConstraint(
['tt_id', 'feature'],
['thing_feature_type.tt_id', 'thing_feature_type.feature'],
ondelete='CASCADE'
),
{},
)
#classmethod
def _define_relationships(cls):
cls.thing_type = relationship('ThingType',
back_populates='things',
lazy='select')
cls.feature_type = relationship('ThingFeatureType',
back_populates='things',
lazy='select')
return Thing
model_factories = [
define_feature,
define_thing_type,
define_thing_feature_type,
define_thing,
]
"""List of factory functions"""
class ForeignKeyExampleTestCase(TestCase):
def setUp(self):
with app.app_context():
models = [m(db.Model) for m in model_factories]
for i, m in enumerate(models):
m._define_relationships()
models[i] = m
for m in models:
print(CreateTable(m.__table__))
db.create_all()
db.session.commit()
def test_can_connect_to_db(self):
with app.app_context():
db.session.execute('SELECT * FROM thing;')
def tearDown(self):
"""And then tear them down again"""
with app.app_context():
db.session.close()
db.drop_all()

cannot get one to many relationship working in (Flask-) SQLAlchemy

I have several classes:
import uuid
from app import db, create_app
from sqlalchemy.sql import func
from sqlalchemy.dialects.postgresql import UUID, ARRAY, JSONB
class Ticket(db.Model):
__tablename__ = 'tickets'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
time = db.Column(db.DateTime, server_default=func.now(), index=True)
proposed_names = db.Column(ARRAY(db.String))
measurements = db.relationship('TempMeasurement', back_populates='ticket')
class BaseMeasurement(object):
id = db.Column(db.Integer, primary_key=True)
#declared_attr
def type_id(self):
return db.Column(db.Integer, db.ForeignKey('optical_data_types.id'))
#declared_attr
def type(self):
return db.relationship('OpticalDataType')
#declared_attr
def operator_id(self):
return db.Column(db.Integer, db.ForeignKey('operators.id'))
#declared_attr
def operator(self):
return db.relationship('Operator')
#declared_attr
def item_id(self):
return db.Column(db.String, db.ForeignKey('items.serial'))
#declared_attr
def item(self):
return db.relationship('Item')
time = db.Column(db.DateTime, index=True)
instrument = db.Column(db.String)
instrument_sn = db.Column(db.String)
data = db.Column(JSONB)
class TempMeasurement(db.Model, BaseMeasurement):
__tablename__ = 'ticket_data'
id = db.Column(db.Integer, primary_key=True)
ticket_id = db.Column(UUID(as_uuid=True), db.ForeignKey('tickets.id'), index=True)
ticket = db.relationship('Ticket', back_populates='measurements')
original_paths = db.Column(ARRAY(db.String))
What I want/expect is that I can create a Ticket with several child TempMeasurements and commit this to the database. Something like:
app = create_app()
with app.app_context():
ticket = Ticket()
ticket.measurements = [TempMeasurement(...)]
db.session.add(ticket) # <-- error on this line
db.session.commit()
However, I get an obscure error deep in SQLAlchemy:
AttributeError: 'str' object has no attribute '_sa_instance_state'
with a full trace here.
I thought that it might be because the UUID ticket_id column has as_uuid, so I made it simply UUID (implicitly a str), but this did not solve my issue.
The error is too deep in SQLAlchemy for me to understand -- can anyone help?

Python - SqlAlchemy foreign key issues

So, I have been getting a lot of errors with SQLAlchemy from Flask and so far nothing I have found online has fixed it, only caused further errors to appear. My current model code is here:
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
nickname = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), unique=True, nullable=False)
posts = db.relationship('Post', back_populates='author', lazy='dynamic',
primaryjoin='User.id == Post.user_id')
created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow())
#property
def is_authenticated(self):
return True
#property
def is_active(self):
return True
#property
def is_anonymous(self):
return False
def get_id(self):
return str(self.id)
def __repr__(self):
return "<User %r>" % (self.nickname)
class Post(db.Model):
__tablename__ = 'post'
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Post %r>' % (self.body)
My current error with this code is:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'post.user_id' could not find table 'user' with which to generate a foreign key to target column 'id'
I am at a complete loss as to what I'm doing wrong and why it is that no other solution has worked for me.
user.id needs to be users.id in your Post model:
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))

How to add a custom function/method in sqlalchemy model to do CRUD operations?

Below I have a Flask-SQLAlchemy model for the table User.
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self):
#self.name = name
self.name = None
def add_user(self, name):
self.name = name
Here add_user is a custom method. So if I call the add_user method it should add the name to the User table.
Likewise how do I write custom methods for CRUD operations in that model itself?
You'll probably want to use a classmethod to accomplish this.
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self, name):
self.name = name
#classmethod
def create(cls, **kw):
obj = cls(**kw)
db.session.add(obj)
db.session.commit()
This way you can use User.create(name="kumaran") to create a new user that will be committed to the database.
Better yet, it is a great idea to create a mixin for this method and others like it so that the functionality can be easily reused in your other models:
class BaseMixin(object):
#classmethod
def create(cls, **kw):
obj = cls(**kw)
db.session.add(obj)
db.session.commit()
You can then reuse this functionality in your models by using multiple inheritance, like so:
class User(BaseMixin, db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self, name):
self.name = name
Not sure this is relevant to Flask-SQLAlchemy, but basic SQLAlchemy has examples of creating Mixin classes or augmenting the Base class.
https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/mixins.html
e.g.
from sqlalchemy.ext.declarative import declared_attr
class MyMixin(object):
#declared_attr
def __tablename__(cls):
return cls.__name__.lower()
__table_args__ = {'mysql_engine': 'InnoDB'}
__mapper_args__= {'always_refresh': True}
id = Column(Integer, primary_key=True)
class MyModel(MyMixin, Base):
name = Column(String(1000))
I would accomplish what you're after like this:
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self):
#self.name = name
self.name = None
#classmethod
def add_user(cls, session, name):
user = User(name)
session.add(user)
return User
Then in whatever context you're using it in, create a session, call your method, and commit it.
from .user import User
session = Session()
# or if using Flask SQLAlchemy
# session = db.session
User.add_user(session, 'Foo')
session.commit()
From the sqlalchemy docs:
Keep the lifecycle of the session (and usually the transaction) separate and external.
In contrast to one of the other answers, which assumes you are using this model in a Flask app with FlaskSQLAlchemy's global db.session object, parametrizing the session object like this keeps your model code separate from your session management code. This allows it to be used flexibly in many different contexts.

Categories

Resources