Flask SQLAlchemy query with concatenated columns - python

I have a models like this:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(64), index=True)
last_name = db.Column(db.String(64), index=True)
def full_name(self):
return '%s %s' % (self.first_name, self.last_name)
I want to get the full_name method in a query, I try it like it:
user = db.session.query(User.full_name()).all()
But I get this error message:
TypeError: full_name() missing 1 required positional argument: 'self'
Then I try to call the function without brackets:
user = db.session.query(User.full_name).all()
Then I got this error message:
sqlalchemy.exc.InvalidRequestError: SQL expression, column, or mapped entity expected - got '<function User.full_name at 0x7f265960aae8>'
So, what is the correct way to query full_name() method in the User model..?

There are a number of ways to accomplish this. Firstly, a hybrid attribute, a property that defines full_name on an instance and on the class (for querying).
This example is pure SQLAlchemy, but the hybrid attributes should be the same in Flask-SQLAlchemy.
import sqlalchemy as sa
from sqlalchemy.ext import hybrid
class User(Base):
__tablename__ = 'users'
id = sa.Column(sa.Integer, primary_key=True)
first_name = sa.Column(sa.String)
last_name = sa.Column(sa.String)
#hybrid.hybrid_property
def full_name(self):
return self.first_name + ' ' + self.last_name
users = session.query(User).filter_by(full_name='Joan Doe').all()
Credit to Ilja for pointing out that this can be done using a single method in this case. This works because SQLAlchemy maps the + operator to the database's CONCAT function. If this weren't the case, an additional property decorated with hybrid.expression would be necessary to implement the behaviour at the class level.
SQLAlchemy inserts the necessary expressions into SQL statements as required, for example:
SELECT users.id, users.first_name, users.last_name
FROM users
WHERE users.first_name || ? || users.last_name = ?
Another way to achieve this is by defining a column_property:
from sqlalchemy import orm
class User(Base):
...
full_name = orm.column_property(first_name + " " + last_name)
This inserts an expression into all queries on the model, so
query = sa.select(User)
will generate this SQL:
SELECT users.first_name || ? || users.last_name AS anon_1,
users.id,
users.first_name,
users.last_name
FROM users
Finally, there are computed columns. These are conceptually similar to column properties, but implemented on the database side (provided the database supports the required syntax). The database may or may not physically store the computed value; it may be possible to condition this behaviour by passing the persisted boolean keyword argument to Computed.
class User(Base):
...
full_name = sa.Column(sa.String, sa.Computed(first_name + ' ' + last_name))

You can use #classmethod.
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(64), index=True)
last_name = db.Column(db.String(64), index=True)
#classmethod
def full_name_filter(cls, fname, lname):
return (cls.first_name == fname, cls.last_name == lname)
Then
user = db.session.query(User).filter(*User.full_name_filter("first", "last")).all()

Related

SQLAlchemy: #property mapping?

I have following models:
class Details(db.Model):
details_id = db.Column(db.Integer, primary_key=True)
details_main = db.Column(db.String(50))
details_desc = db.Column(db.String(50))
class Data(db.Model):
data_id = db.Column(db.Integer, primary_key=True)
data_date = db.Column(db.Date)
details_main = db.Column(db.String(50))
#property
def details_desc(self):
result = object_session(self).\
scalar(
select([Details.details_desc]).
where(Details.details_main == self.details_main)
)
return result
Now, I would like to run query using filter which depends on defined property. I get empty results (of course proper data is in DB). It doesn't work because, probably, I have to map this property. The question is how to do this? (One limitation: FK are not allowed).
Data.query\
.filter(Data.details_desc == unicode('test'))\
.all()
You can implement this with a regular relationship and an association proxy:
class Data(db.Model):
data_id = db.Column(db.Integer, primary_key=True)
data_date = db.Column(db.Date)
details_main = db.Column(db.String(50))
details = relationship(
Details,
primaryjoin=remote(Details.details_main) == foreign(details_main))
details_desc = association_proxy('details', 'details_desc')
Since there are no foreign keys in the schema, you need to tell SQLAlchemy yourself what the join condition for the relationship should be. This is what the remote() and foreign() annotations do.
With that in place, you can use an association_proxy "across" the relationship to create a property on Data which will work the way you want.

dynamic __tablename__ in flask-sqlalchemy models

Is there a possibility to make the __tablename__ in flask-sqlalchemy models dynamic with the declarative base approach?
Usually you set it as this one:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True)
email = Column(String(120), unique=True)
def __init__(self, name=None, email=None):
self.name = name
self.email = email
def __repr__(self):
return '<User %r>' % (self.name)
I would like to change it through a parameter (maybe in the constructor?), so that I can have a table per user.
I found some other approaches in this guide here
Approaches
but I would like to use the session for that as I am already using it for the other models.
You can utilize python's type() function to dynamically build SQLAlchemy models.
Here's a example:
# define columns in an abstract model class
class Log(Base):
__abstract__ = True # this line is necessary
# the columns id, content and user_id are just examples, just ignore it.
id = Column(BIGINT(64), primary_key=True)
content = Column(VARCHAR(200), nullable=False)
user_id = Column(INTEGER(unsigned=True))
# build a model class with a specific table name
def get_log_model(year):
tablename = 'logs_%s' % year # dynamic table name
Model = type('Model', (Log,), {
'__tablename__': tablename
})
return Model
# Log2022 correspond to table "logs_2022"
Log2022 = get_step_model(2022)
# use the dynamically built model in the same way as regular models
print(session.query(Log2022).count()) # row count of table "logs_2022"
I also wrote an article about it on my website, it may help you too: https://easydevguide.com/posts/dynamic_table

Query One to Many Relationship SQLAlchemy

I am trying to query the users based upon their skills from these tables.
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
username = db.Column(db.String(64), unique=True, index=True)
skills = db.relationship('Skill', backref='author', lazy='dynamic')
class Skill(db.Model):
__tablename__ = 'skills'
id = db.Column(db.Integer, primary_key=True)
skill = db.Column(db.String(64), index=True)
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
i tried this in User table and got this error.
#classmethod
def users_by_skill(cls, skill):
return User.query.join(Skill).filter(skill.skill).all()
AttributeError: 'str' object has no attribute 'skill'
Where i am missing badly?
You define the following class method:
#classmethod
def users_by_skill(cls, skill):
return User.query.join(Skill).filter(skill.skill).all()
You are probably expecting to use this function like so:
users = Users.users_by_skill('nunchuk')
That means the skill argument in users_by_skill is a string. Then, you try to use skill.skill, which essentially is like doing 'nunchuk'.skill. Python does not have a skill attribute on the string class, hence the error.
The filter function actually takes a Criteria object. In other words, you don't pass it a value like "filter", you instead pass it a criterion that represents the notion of "the skill column on the Skill table must equal 'nunchuk'". You can do this using syntax like the following:
#classmethod
def users_by_skill(cls, skill_name):
return User.query.join(Skill).filter(Skill.skill == skill_name).all()

How to avoid inserting duplicate entries when adding values via a sqlalchemy relationship?

Let's assume we have two tables in a many to many relationship as shown below:
class User(db.Model):
__tablename__ = 'user'
uid = db.Column(db.String(80), primary_key=True)
languages = db.relationship('Language', lazy='dynamic',
secondary='user_language')
class UserLanguage(db.Model):
__tablename__ = 'user_language'
__tableargs__ = (db.UniqueConstraint('uid', 'lid', name='user_language_ff'),)
id = db.Column(db.Integer, primary_key=True)
uid = db.Column(db.String(80), db.ForeignKey('user.uid'))
lid = db.Column(db.String(80), db.ForeignKey('language.lid'))
class Language(db.Model):
lid = db.Column(db.String(80), primary_key=True)
language_name = db.Column(db.String(30))
Now in the python shell:
In [4]: user = User.query.all()[0]
In [11]: user.languages = [Language('1', 'English')]
In [12]: db.session.commit()
In [13]: user2 = User.query.all()[1]
In [14]: user2.languages = [Language('1', 'English')]
In [15]: db.session.commit()
IntegrityError: (IntegrityError) column lid is not unique u'INSERT INTO language (lid, language_name) VALUES (?, ?)' ('1', 'English')
How can I let the relationship know that it should ignore duplicates and not break the unique constraint for the Language table? Of course, I could insert each language separately and check if the entry already exists in the table beforehand, but then much of the benefit offered by sqlalchemy relationships is gone.
The SQLAlchemy wiki has a collection of examples, one of which is how you might check uniqueness of instances.
The examples are a bit convoluted though. Basically, create a classmethod get_unique as an alternate constructor, which will first check a session cache, then try a query for existing instances, then finally create a new instance. Then call Language.get_unique(id, name) instead of Language(id, name).
I've written a more detailed answer in response to OP's bounty on another question.
I would suggest to read Association Proxy: Simplifying Association Objects. In this case your code would translate into something like below:
# NEW: need this function to auto-generate the PK for newly created Language
# here using uuid, but could be any generator
def _newid():
import uuid
return str(uuid.uuid4())
def _language_find_or_create(language_name):
language = Language.query.filter_by(language_name=language_name).first()
return language or Language(language_name=language_name)
class User(Base):
__tablename__ = 'user'
uid = Column(String(80), primary_key=True)
languages = relationship('Language', lazy='dynamic',
secondary='user_language')
# proxy the 'language_name' attribute from the 'languages' relationship
langs = association_proxy('languages', 'language_name',
creator=_language_find_or_create,
)
class UserLanguage(Base):
__tablename__ = 'user_language'
__tableargs__ = (UniqueConstraint('uid', 'lid', name='user_language_ff'),)
id = Column(Integer, primary_key=True)
uid = Column(String(80), ForeignKey('user.uid'))
lid = Column(String(80), ForeignKey('language.lid'))
class Language(Base):
__tablename__ = 'language'
# NEW: added a *default* here; replace with your implementation
lid = Column(String(80), primary_key=True, default=_newid)
language_name = Column(String(30))
# test code
user = User(uid="user-1")
# NEW: add languages using association_proxy property
user.langs.append("English")
user.langs.append("Spanish")
session.add(user)
session.commit()
user2 = User(uid="user-2")
user2.langs.append("English") # this will not create a new Language row...
user2.langs.append("German")
session.add(user2)
session.commit()

Overriding the table name in Flask-Alchemy

I am creating a Flask application and accessing the MySQL database using Flask-Alchemy.
I have following Class to access a table:
class price_table(db.Model):
id = db.Column(db.Integer, primary_key = True)
trans_id = db.Column(db.Integer)
timestamp = db.Column(db.Integer)
order_type = db.Column(db.String(25))
price = db.Column(db.Numeric(15,8))
quantity = db.Column(db.Numeric(25,8))
def __repr__(self):
return 'id'
For the table 'price_table' this works brilliantly, but problem is I have a few tables with the same columns as 'price_table' from which I only know the name at runtime.
I want to reuse the class above so I thought I could change tablename to the name of the table I need to read, but that does not work, the program keeps reading the 'price-table'
How do I override the tablename at runtime?
You should use: __tablename__ :
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True)
email = Column(String(120), unique=True)
http://flask.pocoo.org/docs/0.12/patterns/sqlalchemy/
Based on the comment left by jbub I found the following solution that does the trick just as needed.
from app import db
def ClassFactory(name):
tabledict={'id':db.Column(db.Integer, primary_key = True),
'trans_id':db.Column(db.Integer),
'timestamp':db.Column(db.Integer),
'order_type':db.Column(db.String(25)),
'price':db.Column(db.Numeric(25,8)),
'quantity':db.Column(db.Numeric(25,8)),}
newclass = type(name, (db.Model,), tabledict)
return newclass
You can overwrite price_table.table.name attribute, yet keep in mind that it will affect your price_table model so, unless you want to use it to create a new specialized version of this table in the db and you are not interacting with price_table model - I wouldn't recommend that.

Categories