Generic query method based of an object from its model class - python

In my model class, I want to create a generic method say get_list(obj) which accept an argument of its object which contains values of their corresponding attribute, and returns all appropriate records that match with corresponding column.
Suppose that I have a users class in my model, and to use the get_list(obj) method. I just need to pass an object of users with its values. Obviously, this will save a lot of time instead of creating repetitive filter_by().
class Users(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
username = Column(String(200), nullable=False)
password = Column(String(200))
email = Column(String(200), nullable=False, unique=True)
def as_query(self):
query = []
for c in self.__table__.columns:
if getattr(self, c.name) is not None:
query.append(c.name+'='+str(getattr(self, c.name)))
return ' and '.join(query)
#classmethod
def get_list(cls, statement):
return cls.query.filter_by(statement).all()
To use the method, we can expect something more like this
user = Users(username='admin')
results = Users.get_list(user.as_query()) # result as a list
I'm aware that we can achieve the same thing with just write the query into filter_by instead of creating a meaningless object. However, in one of my APIs the object will be created automatically-meaning it will be automatic on the fly.
However, this solution is only a hack to just flatten the attribute and its value into filter_by() statement and obviously not working?
Do you have a better solution for this?

Not sure, If this is what you were looking for, but I believe with Python's dict unpacking, it is already possible to unpack all the object attributes to use as filter in the filter_by function.
results = session.query(models.Users).filter_by(**user.dict()).all()
You can also use the or_ method to match any of the attribute values like below:
session.query(Users).filter(or_(**user.dict()))

Related

SQLAlchemy hybrid_property order_by using text

Given the following Model
class Person(db.Model):
__tablename__ = 'persons'
name = db.Column(db.String(10), nullable=False)
age = db.Column(db.Integer(), nullable=False)
def __init__(self):
self.name = ''
self.age = 0
#hybrid_property
def is_configured(self):
return self.name != '' and self.age > 0
Is it possible to construct a query using order_by on is_configured hybrid_property using sqlalchemy.text?
If we use the ORM to order_by it works
result = Person.query.filter(or_(*some_filters)).order_by(Person.is_configured).all()
Using sqlalchemy.text results in SQL error stating no column persons.is_configured
result = Person.query.filter(or_(*some_filters)).order_by(text('persons.is_configured asc')).all()
UPDATE 2021-07-16
Some background on why this question was opened: We have a template that renders a table for user accounts where some of the columns are fields on related tables. Clicking the header of a column will sort by the column, sending request to the server with table_name.column to order_by. We have one case where the column is a property. We'd like to make this a hybrid_property so we can query and order by it. We could make this work by mapping the text to the ORM, but if there is a way to make it work with the text that the view provides that would be preferred.
No, this is not possible. The TextClause is pure SQL and executed against the database. As the hybrid property is an ORM object the database doesn’t know anything about this.
P.s.: There are a few ways to achieve what you want but maybe elaborate a bit more about what you are trying to achieve

Regenerate SQLalchemy Sequence on update

Imagine I have a class in my models like this:
class Sample(Base):
__tablename__ = 'sample'
id = Column(Integer, primary_key=True)
firstname = Column(String(50))
lastname = Column(String(50))
auto_generated_code = Column(
Integer,
Sequence('sample_auto_generated_code_sequence'),
unique=True
)
When I add an instance to Sample class, after flushing the session, my instance get an integer number automatically. so far so good.
What I also want is, when I update any of other columns of the mentioned instance, it should get a new auto_generated_code automatically.
In simple word I want my Sequence to generate another code on update too. How can I achieve this?
I found an answer that we can get the Sequence object in Sqlalchemy like this:
from sqlalchemy import Sequence
seq = Sequence('sample_auto_generated_code_sequence')
Then we can get the next Sequence by executing it on our connection or session
instance.auto_generated_code = session.execute(seq) # or conn.execute(seq)
Then by adding it to the session it will go just fine.

SQLAlchemy: Map a column value before storage

Perhaps I am approaching this wrongly.
Say I have an enum like so:
class MyEnum(Enum):
type0 = 'type0'
type1 = 'type1'
And a SQLAlchemy ORM Schema like so:
class MyEntity(Base):
__tablename__ = 'my_entity'
id = Column(BigInteger, primary_key=True)
name = Column(String, nullable=False)
I don't want to store MyEnum in the database as an entity type. I just want it to be as a string, with no validation. However, whenever I access MyEntity.name, I want it to have been cast to an enum.
Is there any way to do a simple mapping so that, from the outside, it looks as though MyEntity.name is of type MyEnum, but is persisted as a raw string?
The closest thing I have found was to use a hybrid_property, but this doesn't seem to be applied for my exact use-case.
Any ideas?

How do I set the attributes of a SQLAlchemy ORM class before query?

For example, using Flask-SQLAlchemy and jsontools to serialize to JSON like shown -here-, and given a model like this:
class Engine(db.Model):
__tablename__ = "engines"
id = db.Column(db.Integer, primary_key=True)
this = db.Column(db.String(10))
that = db.Column(db.String(10))
parts = db.relationship("Part")
schema = ["id"
, "this"
, "that"
, "parts"
]
def __json__(self):
return self.schema
class Part(db.Model):
__tablename__ = "parts"
id = db.Column(db.Integer, primary_key=True)
engine_id = db.Column(db.Integer, db.ForeignKey("engines.id"))
code = db.Column(db.String(10))
def __json__(self):
return ["id", "code"]
How do I change the schema attribute before query so that it takes effect on the return data?
enginelist = db.session.query(Engine).all()
return enginelist
So far, I have succeeded with subclassing and single-table inheritance like so:
class Engine_smallschema(Engine):
__mapper_args__ = {'polymorphic_identity': 'smallschema'}
schema = ["id"
, "this"
, "that"
]
and
enginelist = db.session.query(Engine_smallschema).all()
return enginelist
...but it seems there should be a better way without needing to subclass (I'm not sure if this is wise). I've tried various things such as setting an attribute or calling a method to set an internal variable. Problem is, when trying such things, the query doesn't like the instance object given it and I don't know SQLAlchemy well enough yet to know if queries can be executed on pre-made instances of these classes.
I can also loop through the returned objects, setting a new schema, and get the wanted JSON, but this isn't a solution for me because it launches new queries (I usually request the small dataset first).
Any other ideas?
The JSON serialization takes place in flask, not in SQLAlchemy. Thus, the __json__ function is not consulted until after you return from your view function. This has therefore nothing to do with SQLAlchemy, and instead it has to do with the custom encoding function, which presumably you can change.
I would actually suggest not attempting to do it this way if you have different sets of attributes you want to serialize for a model. Setting a magic attribute on an instance that affects how it's serialized violates the principle of least surprise. Instead, you can, for example, make a Serializer class that you can initialize with the list of fields you want to be serialized, then pass your Engine to it to produce a dict that can be readily converted to JSON.
If you insist on doing it your way, you can probably just do this:
for e in enginelist:
e.__json__ = lambda: ["id", "this", "that"]
Of course, you can change __json__ to be a property instead if you want to avoid the lambda.

Computing field values on save?

class Quote(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text)
votes = db.Column(db.Integer)
author_id = db.Column(db.Integer, db.ForeignKey('author.id'))
date_added = db.Column(db.DateTime,default=datetime.datetime.now())
last_letter = db.Column(db.String(1))
I have a Model that looks like the above. I want last_letter to be the last letter of whatever the content is. Where should I place this logic so that it will occur every time a model is saved? I'm reading about Hybrid Properties and stuff and I'm not sure which way is the correct one to go.
1.the Naive way: you can use sqlalchemy column default value to set something like:
last_letter = db.Column(db.char, default=content[len(content)-1:])
didn't check if that would actually work, guess not.
2.you can also do something like adding this init to the class:
def __init__(self,id,content,votes,auther_id,date_added):
self.id = id
self.content = content
#yadda yadda etc
self.last_letter = content[len(content)-1:] #or something similiar
or you could use "listen" to the "before insert" event and add this dynamically as explained here.
you can use sql computed column with an sql trigger (in the db) without sqlalchemy.
you can probably use a sqlalchemy mapper sql expression as a hybrid property, also I didn't try that myself, look simple enough and probably is the most elegant way to do this.
last_letter could be decorated with #property and defined
#property
def last_letter(self):
return self.content[-1]
Disclaimer: I just learned how to use decorators and am using them everywhere

Categories

Resources