SQLAlchemy order by function result - python

This is the code I have and it is working (returns all problems ordered by difficulty):
def get_noteworthy_problems(self):
ACategory = aliased(Category)
AProblem = aliased(Problem)
all_prob = DBSession.query(AProblem).filter(
AProblem.parent_id == ACategory.id,
ACategory.parent_id == self.id)
noteworthy_problems = \
sorted(all_prob, key=lambda x: x.difficulty(), reverse=True)
return noteworthy_problems
But I think I must optimize this code.
Is there a possibility to change the code having order_by and my function difficulty()? My function returns a number. I tried something like:
result = DBSession.query(AProblem).filter(
AProblem.parent_id == ACategory.id,
ACategory.parent_id == self.id).order_by(
AProblem.difficulty().desc())
but I receive the error TypeError: 'NoneType' object is not callable.

Hybrid attributes are special methods that act as both a Python property and a SQL expression. As long as your difficulty function can be expressed in SQL, it can be used to filter and order like a normal column.
For example, if you calculate difficulty as the number of parrots a problem has, times ten if the problem is older than 30 days, you would use:
from datetime import datetime, timedelta
from sqlalchemy import Column, Integer, DateTime, case
from sqlalchemy.ext.hybrid import hybrid_property
class Problem(Base):
parrots = Column(Integer, nullable=False, default=1)
created = Column(DateTime, nullable=False, default=datetime.utcnow)
#hybrid_property
def difficulty(self):
# this getter is used when accessing the property of an instance
if self.created <= (datetime.utcnow() - timedelta(30)):
return self.parrots * 10
return self.parrots
#difficulty.expression
def difficulty(cls):
# this expression is used when querying the model
return case(
[(cls.created <= (datetime.utcnow() - timedelta(30)), cls.parrots * 10)],
else_=cls.parrots
)
and query it with:
session.query(Problem).order_by(Problem.difficulty.desc())

Related

Flask admin - complex sort on hybrid_property

I have a fairly complex hybrid_property. This is a vendor model, which has multiple skuchannels (products). What it does is: Based on the target_stock_duration (e.g. we want to keep items in stock for 4 months) calculate how many units have to be ordered and how much this would cost. This gives us the potential.
class Vendor(db.Model):
__tablename__ = "vendor"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(150))
b2c_price_factor = db.Column(db.Float, nullable=False)
skuchannels = db.relationship("SKUChannel", back_populates="vendor")
#hybrid_property
def po_potential(self):
"""This is a "virtual" property that will that can be used in the admin view. It calculates
the potential value for a comming PO.
Returns:
_type_: _description_
"""
potential = 0
for item in self.skuchannels:
purchasing_price = item.purchasing_price if item.purchasing_price != None else 0
target_stock_duration = 4
try:
to_order = item.average_monthly_sales * target_stock_duration - item.stock_level #calculate how many units we have to order
if to_order < 0:
to_order = 0
except TypeError:
to_order = 0
potential = potential + purchasing_price * to_order #calculate how much everything costs
return potential
well this hybrid_property works just fine, but I would very much like to sort this property. with #po_potential.expression -> well I have no clue how to do this, because in my understanding it should return a select object. Is there any other way?
This should get you started:
class Vendor(Base):
...
...
#po_potential.expression
def po_potential(cls):
target_stock_duration = 4
return (
select(func.sum(
func.ISNULL(SKUChannel.purchasing_price, 0) *
func.GREATEST(0, SKUChannel.average_monthly_sales * target_stock_duration - SKUChannel.stock_level, 0)
))
.where(SKUChannel.vendor_id == cls.id)
.label('po_potential')
)

count the sales of a worker

I am trying to count the sales made by a worker but I get the following error when using the code mentioned below:
TypeError: object of type 'bool' has no len()
class Movement_type (models.Model):
_name = 'project_rc.movement_type'
_rec_name = 'movement_type'
type_movement = fields.Selection ([('purchase', 'Purchase'), ('sale', 'Sale'), ('merma', 'Merma')], string = "Movement type", required = True)
class Worker (models.Model):
_name = 'project_rc.worker'
_rec_name = 'name'
sales_counter = fields.Integer (string = "Sales made", compute = "get_sales_realized", store = True)
#api.depends('move_type_ids')
def get_sales_realized (self):
for rec in self:
rec.count_sale = len (rec.move_type_ids.mov_type == 'sale')
I'm not familiar with whatever framework you are using, but if you look at the error you are getting you can see that it is correct.
On line 3, you write rec.move_type_ids.mov_type == 'sale'. It doesn't matter what rec.move_type_ids.mov_type is, when you compare it to something with ==, the answer will either be True or False. It doesn't make sense to take the length of a boolean (t/f).
From the context, I'm guessing that rec.move_type_ids is a list of objects and you want to figure out how many of them have a mov_type property equal to 'sale'. If that's the case, then you could easily do that with a for loop:
sales = []
for thing in rec.move_type_ids:
if thing.type == 'sale':
sales.append(thing)
rec.count_sale = len(sales)
If you want to get a little fancier, you can do that with a filter function:
rec.count_sale = len(filter(lambda x: x.mov_type == 'sale', rec.move_type_ids))

SQLAlchemy: how to extend hybrid attributes?

I'm working with a MSSQL database with no control over the DB setup nor the (read-only) data in it. One table is represented in SQLAlchemy like this:
class pdAnlage(pdBase):
__tablename__ = "Anlage"
typ = Column(CHAR(4), primary_key=True)
nummer = Column(CHAR(4), primary_key=True)
In accessing the database, I need a property "name" that is just a concatenation of "typ" and "nummer" with a dot between them. So I did this:
#hybrid_property
def name(self):
return self.typ + '.' + self.nummer
Looks simple and works as expected. There are two caveats though, one general and one special. The general one: The table is quite big, and i'd like to make queries against Anlage.name, like this:
db.query(Anlage).filter(Anlage.name.like('A%.B'))
db.query(Anlage).filter(Anlage.name == 'X.Y')
This works but it is inefficient as the SQL server first has to concatenate all "typ" and "nummer" columns of the (large) table before doing the test. So I've defined a classmethods like this one:
#classmethod
def name_like(self, pattern):
p = pattern.split('.', 2)
if len(p) == 1 or not p[1]:
return self.typ.like(p[0])
else:
return and_(self.typ.like(p[0]), self.nummer.like(p[1]))
This isn't elegant, but it does the job just fine. It would be nicer to overload "==" and "like()", is there a way to do that?
Now to the special case: Both name and typ columns can contain trailing spaces in the DB. But the name property must not have spaces, especially not before the dot. So I tried to rewrite the name hybrid property like this:
#hybrid_property
def name(self):
return self.typ.rstrip() + '.' + self.nummer.rstrip()
This doesn't work because SQLAlchemy doesn't know how to translate the rstrip() python method to the MSSQL RTRIM() function. How can I accomplish that?
You could implement a custom comparator that handles string operands in a special way (and others as necessary):
from sqlalchemy.ext.hybrid import Comparator
_sep = '.'
def _partition(s):
typ, sep, nummer = s.partition(_sep)
return typ, nummer
class NameComparator(Comparator):
def __init__(self, typ, nummer):
self.typ = typ
self.nummer = nummer
super().__init__(func.rtrim(typ) + _sep + func.rtrim(nummer))
def operate(self, op, other, **kwgs):
if isinstance(other, str):
typ, nummer = _partition(other)
expr = op(self.typ, typ, **kwgs)
if nummer:
expr = and_(expr, op(self.nummer, nummer, **kwgs))
return expr
else:
# Default to using the "slow" method of concatenating first that
# hides the columns from the index created for the primary key.
return op(self.__clause_element__(), other, **kwgs)
and use it with your hybrid attribute:
class pdAnlage(Base):
__tablename__ = "Anlage"
typ = Column(CHAR(4), primary_key=True)
nummer = Column(CHAR(4), primary_key=True)
#hybrid_property
def name(self):
return self.typ.rstrip() + _sep + self.nummer.rstrip()
#name.comparator
def name(cls):
return NameComparator(cls.typ, cls.nummer)

Read and Write Operations involved with ComputedProperty in GAE NDB

I use GAE NDB Python 2.7
My two Models code:
class A(ndb.Model):
def X(self, value):
:: # some statements to return a value
return range
def Y(self, value):
:: # some statements to return a value
return range
def Z(self, value):
:: # some statements to return a value
return range
property_1 = ndb.IntegerProperty(default=0, indexed=False)
property_2 = ndb.IntegerProperty(default=0, indexed=False)
property_3 = ndb.IntegerProperty(default=0, indexed=False)
property_4 = ndb.IntegerProperty(indexed=False)
# Computed values
computed_property_1 = ndb.ComputedProperty(lambda e: e.X(e.property_1))
computed_property_2 = ndb.ComputedProperty(lambda e: e.Y(e.property_2))
computed_property_3 = ndb.ComputedProperty(lambda e: e.Z(e.property_3))
date_added = ndb.DateTimeProperty(auto_now_add=True, indexed=False)
date_modified = ndb.DateTimeProperty(auto_now=True, indexed=False)
class B(ndb.Model):
property_5 = ndb.IntegerProperty()
property_6 = ndb.StructuredProperty(A)
date_added = ndb.DateTimeProperty(auto_now_add=True, indexed=False)
date_modified = ndb.DateTimeProperty(auto_now=True, indexed=False)
My Query code:
qry_1 = B.query(B.property_5==input_value) # or B.query(B.property_6.computed_property_2==input_value)
record_list = qry_1.fetch()
When I perform the above query on entity of model B, would any write operation be performed? (especially for the ComputedProperty and DateTimeProperty(with "auto_now") properties)
If yes, would it be rate limited to 1 write per second (i think that is the limit for free apps)
If yes, and if i have 50 entities matching the query, would it first complete the write operation(mentioned above) before completing the query and returning the matched entity set (any estimate of the query completion time)
Any difference in the above answers if I replace the following line in class B
property_6 = ndb.StructuredProperty(A)
with
property_6 = ndb.StructuredProperty(A, repeated=True)
There are no write operations by performing queries. The same applies to the two variations with StructuredProperty. Also auto_now_add and auto_now are only set during write operations. I'm not 100% sure, but as far as I understand the docs, computed properties are also updated at write (I haven't used them yet).

Django random value for Model field

So I am able to generate a random id using uuid
So far so good
But when I try to database i get same value
def f():
d = uuid4()
str = d.hex
return str[0:16]
class Q(models.Model):
a = models.CharField(max_length=150)
b = models.IntegerField(max_length=25)
c = models.IntegerField(max_length=32 , default=0)
d = models.ManyToManyField(Ans , related_name='aa')
e = models.CharField(max_length=18 , default = f() ,unique=True )
class Ans(models.Model):
sub = models.CharField(max_length=150)
-----------------------------------------------------------------
And I'm inserting like this
def ins(request):
t =random.randint(0, 1000)
p = Q(a = t , b=0 , c=0)
p.save()
return HttpResponse('Saved')
Just curious what the hell is happening here
Side note: If I set e.unique = False I get 2-3 with the same e values before I get a new
UUID values
You should not call the function that you are passing to default:
e = models.CharField(max_length=18, default=f, unique=True)
FYI, according to docs, you should pass a value or a callable:
The default value for the field. This can be a value or a callable
object. If callable it will be called every time a new object is
created.

Categories

Resources