Are calculated/derived fields fully supported in Pony ORM? - python

Suppose I have the following schema in Pony ORM:
from pony.orm import *
db = Database("postgres", database='foo')
class Job(db.Entity):
job_id = PrimaryKey(int, auto=True)
job_name = Required(str)
base_salary = Required(int)
multiplier = Required(int, default=1000)
people = Set(lambda: Person)
class Person(db.Entity):
person_id = PrimaryKey(int, auto=True)
name = Required(str)
job = Required(lambda: Job)
experience = Required(int)
I would like the Person entity to have a salary attribute that's equal to:
Job.base_salary + (Person.experience * Job.multiplier)
My first thought was to add a property to the Person entity like so:
#property
def salary(self):
return self.job.base_salary + (self.experience * self.job.multiplier)
This works for a simple query:
j1 = Job(job_name = "Astronaut", base_salary = 80000, multiplier = 5000)
j2 = Job(job_name = "Butcher", base_salary = 40000, multiplier = 2000)
j3 = Job(job_name = "Chef", base_salary = 30000)
for i, name in enumerate(["Alice", "Bob", "Carol"]):
p = Person(name = name, job=j1, experience = i)
for i, name in enumerate(["Dave", "Erin"]):
p = Person(name = name, job=j2, experience = i)
for i, name in enumerate(["Frank", "Gwen"]):
p = Person(name = name, job=j3, experience = i)
for p in select(p for p in Person):
print p.name, p.experience, p.salary
Prints:
Alice 2 90000
Bob 4 100000
Carol 6 110000
Dave 2 44000
Erin 4 48000
Frank 2 32000
Gwen 4 34000
But if I try something like this:
for j in select((j.job_name, avg(j.people.salary)) for j in Job):
print j
Perhaps unsurprisingly, I get:
AttributeError: j.people.salary
since salary isn't a "real" attribute. Is there a way to do this kind of thing that would allow calculated fields to be treated as first-class entities that can have normal aggregation / calculations done on them?

Thanks for the suggestion!
Currently (Feb 2015) Pony ORM does not support calculated fields, but this feature can be implemented relatively easily, because Pony already have the ability to translate lambdas to SQL. I hope we can add calculated fields soon.

Related

Django queryset .iterate(): not working. Only first item is taken into account, second is ignored

I have the following code. I know the queryset contains 2 items. However, Django only creates one entry. What am I overlooking?
I know "OrderItem.objects.filter(order=order)" returns two objects. However, in the order_lines.iterate() it only creates one entry. Do I overlook something?
order_lines = OrderItem.objects.filter(order=order)
inv_ref = (str(order.order_date.year) + "-" + str(10000+order.id))
inv = Invoice.objects.create(invoice_ref=inv_ref,date=order.order_date, order=order, ship_to=order.order_ship_to, bill_to=order.order_bill_to)
value = 0
vat = 0
total = 0
commission = 0
## Looping not working properly: fix all items to be added to invoice.
for item in order_lines.iterator():
exvat = (item.price/((100+item.vat)/100))
val = item.quantity * exvat
ttl = (item.price*item.quantity)
comm = item.commission
vat_val = ttl-val
InvoiceItems.objects.create(invoice=inv, order_item=item, quantity=item.quantity, price=exvat, value=val, vat=item.vat,vat_val=vat_val,total=ttl)
value = value + val
vat = vat + vat_val
total = total + ttl
commission = commission + comm
print(item)
Do not use .iterator() as django.models.Model.objects.filter() already returns an QuerySet which implements iterator protocol (Iterable[]).
...
for item in order_lines:
...
...
Try simplified loop:
for item in order_lines:
it should work like a charm because it's already QuerySet object.

Why using bulk_create on Django to insert data with foreign keys returns "property object is not callable"?

I'm using Django 2.17 with a SQLite 3.26 database and trying to insert data from a csv file. I was using the get_or_create method, but it was too slow. So I start to try to insert using bulk_create.
I have the following fields of Models being used:
class SENSOR_TEMPERATURA(models.Model):
ID_Sensor_Temperatura = models.AutoField(primary_key = True)
class VALOR_TEMPERATURA(models.Model):
ID_Valor_Temperatura = models.AutoField(primary_key = True)
ID_Sensor_Temperatura = models.ForeignKey(SENSOR_TEMPERATURA, on_delete = models.PROTECT, null = False, db_column = 'VATE_CD_ID_Sensor_Temperatura')
Data_De_Medição = models.DateTimeField(default = datetime.now(), null = False)
Valor = models.DecimalField(default = 0, null = False, max_digits = 30, decimal_places = 15)
The code that I'm trying to run is:
print (datetime.datetime.now())
reader = csv.reader(f)
insert_CSV = []
count = 1
for row in reader:
insert_CSV.append([
VALOR_TEMPERATURA.pk(ID_Valor_Temperatura = count),
VALOR_TEMPERATURA(Data_De_Medição = datetime.datetime.strptime(row[0] + " UTC-0300",'%d/%m/%Y %H:%M:%S %Z%z')),
VALOR_TEMPERATURA(Valor = float(row[1])),
VALOR_TEMPERATURA(ID_Sensor_Temperatura = SENSOR_TEMPERATURA.objects.get(ID_Sensor_Temperatura = 4))
])
count = count + 1
print (datetime.datetime.now())
VALOR_TEMPERATURA.objects.bulk_create(insert_CSV)
print (datetime.datetime.now())
The part that I think is put me in trouble is "ID_Sensor_Temperatura = SENSOR_TEMPERATURA.objects.get(ID_Sensor_Temperatura = 4))", but it is exactly how I defined the Foreign Key when using get_or_create, so I can't figure out what is the problem.
I'm getting the following error:
6 for row in reader:
7 insert_CSV.append([
8 VALOR_TEMPERATURA.pk(VATE_CD_ID_Valor_Temperatura = count),
9 VALOR_TEMPERATURA(VATE_DF_Data_De_Medição = datetime.datetime.strptime(row[0] + " UTC-0300",'%d/%m/%Y %H:%M:%S %Z%z')),
10 VALOR_TEMPERATURA(VATE_VL_Valor = float(row[1])),
TypeError: 'property' object is not callable
What may be the problem?
This isn't how you write Python. You need to create an instance of the object, passing it the values.
insert_CSV.append(
VALOR_TEMPERATURA(
ID_Valor_Temperatura=count,
Data_De_Medição=datetime.datetime.strptime(row[0] + " UTC-0300",'%d/%m/%Y %H:%M:%S %Z%z')),
Valor=float(row[1]),
ID_Sensor_Temperatura=SENSOR_TEMPERATURA.objects.get(ID_Sensor_Temperatura=4)
)
)
Note also, your models should not be defined in ALL_CAPS, as they are not constants. They shoudl be called ValorTemperatura and SensorTemperatura.

Trying to iterate over a list attached to an object under a variable class(?)

Full code is at the end.
I've written a program that reads in data from a csv file. It creates a class of variable called "Facility". Each facility can have multiple water sources, so there is another class called "WaterSource" which appends a list of attributes for an individual water source to each Facility. If I call :
data['00312']
I get output:
Facility 00312 US Aggregates Inc IN
If I ask for data['00312'].records:
[ WaterSource 00312 WELL Willshire 80 683175 4511625,
WaterSource 00312 WELL Willshire 80 682550 4511750,
WaterSource 00312 INTAKE Willshire 1200 Unnamed Quarry 683225 4512075,
WaterSource 00312 INTAKE Willshire 1200 Unnamed Quarry 683225 4512050]
I need to create a report that iterates over every variable in the class and returns a list of Facilities that have multiple water sources. Thus the final output would a list of [RegNo, Facility Name, No. of WaterSources] such as:
[Facility 00312 US Aggregates Inc 4]
The issue I'm having is understanding how to iterate over the Facilities to count the records of the water sources appended to each Facilities object. I think I could add a method into the class somewhere, but I can't quite figure out where. I'm a python beginner, so please forgive me if this isn't quite the right vocabulary. I'm not even sure where to start, so any suggestions you could offer would be helpful.
class Facilities:
def __init__(self, regno, name, mwu): ##creates facility attributes
self.regno = regno
self.name = name
self.mwu = mwu
self.records = []
def add_record(self,record):
self.records.append(record)
def __repr__(self):
'''Makes a string representation'''
return 'Facility {0} {1} {2}'.format(self.regno, self.name, self.mwu)
class WaterSource(Facility):
'''holds info about the water source'''
def __init__(self, regno, source, quad, cap, body, utmE, utmN): ##creates water source attributes
self.regno = regno
self.source = source
self.quad = quad
self.cap = cap
self.body = body
self.utmE = utmE
self.utmN = utmN
self.records = []
def source_data(self):
regnos = []
sources = []
quads = []
caps = []
bodies = []
utmEs = []
utmNs = []
for record in self.records:
regnos.append(record.regno)
sources.append(record.source)
quads.append(record.quad)
caps.append(record.cap)
bodies.append(record.body)
utmEs.append(record.utmE)
utmNs.append(record.utmN)
return (regnos,sources,quads,caps,bodies,utmEs,utmNs)
def __repr__(self):
return ' WaterSource {0} {1} {2} {3} {4} {5} {6}'.format(self.regno, \
self.source, self.quad, self.cap, self.body, self.utmE, self.utmN)
def read_data(filename):
rv = {}
for r in csv.DictReader(open(filename, 'r', encoding='UTF-8')):
regno = r['RegNo']
if r['RegNo'] not in rv:
rv[regno] = Facilities(r['RegNo'],r['Facility'], r['MWU Code'])
rv[regno].add_record(WaterSource(regno, r['Source Code'], r['Quadrangle'], \
r['Capacity (GPM)'], r['Water Body Name'], r['UTM East'], r['UTM North']))
return rv
data = read_data('Fac-2013-2016.csv')
[Facility 00312 US Aggregates Inc 4]
The issue I'm having is understanding how to iterate over the
Facilities to count the records of the water sources appended to each
Facilities object.
From my understanding, simply add a method and return a count of the objects or straight up count the records using len unless there is something more to what you are asking for?
class Facilities:
def __init__(self, regno, name, mwu): ##creates facility attributes
self.regno = regno
self.name = name
self.mwu = mwu
self.records = []
def add_record(self,record):
self.records.append(record)
def __repr__(self):
'''Makes a string representation'''
return 'Facility {0} {1} {2} {3}'.format(self.regno, self.name, self.mwu , len(self.records))
All of your Facilities are stored as values in the dictionary data using the facility's RegNo for the keys. You can iterate over all the data using the dictionary items method. The length of each facility's records attribute is the number of water sources. You can build a format string to use the information you need.
for reg_no, facility in data.items():
no_of_sources = len(facility.records)
print(f'Facility {facility.regno} {facility.name} {no_of_sources}') #Python v3.6+
#print('Facility {} {} {}'.format(facility.regno, facility.name, no_of_sources)) #Python versions <3.6

How to compare "peewee.DateField" with "datatime.date"?

I wrote the below program to fetch some rows of my database that contain information about the users whom born after 22-Jan-1963:
import datetime as dt
import peewee as pw
db = pw.SqliteDatabase('people.db')
class Person(pw.Model):
name = pw.CharField()
birthday = pw.DateField(formats=['%d-%b-%Y'])
class Meta:
database = db # This model uses the "people.db" database.
db.create_tables([Person])
bob = Person(name = 'Bob', birthday = '21-Jan-1960')
james = Person(name = 'James', birthday = '22-Jan-1965')
steve = Person(name = 'Steve', birthday = '20-Jan-1970')
alex = Person(name = 'Alex', birthday = '18-Jan-1975')
bob.save()
james.save()
steve.save()
alex.save()
for item in Person.select().where(Person.birthday > dt.date(1963,1,22)):
print item.name,item.birthday, item.birthday > dt.date(1963,1,22)
But when I run this, output is not what I have expected (I expect James, Steve and Alex in the output):
>>> ================================ RESTART ================================
>>>
Bob 1960-01-21 False
James 1965-01-22 True
Steve 1970-01-20 True
>>>
Well, I replaced dt.date(1963,1,22) with "22-Jan-1963" in the where() method, and now the result is:
>>> ================================ RESTART ================================
>>>
James 1965-01-22 True
>>>
As you see above, it is not correct still.
What shall I do?
Following the documentation, I’d suggest
other = dt.date(1963, 1, 22)
Person.select().where(
Person.birthday.year >= other.year
).where(
Person.birthday.year > other.year
| Person.birthday.month >= other.month
).where(
Person.birthday.year > other.year
| Person.birthday.month > other.month
| Person.birthday.day > other.day
)
Yeah, it’s quite verbose...
I absolutely do not know PeeWee, but given that Sqlite does not have a native date-time format (it mimics it as a string), you may want to try and change the date format to "%Y-%m-%d"; this will automatically be sorted correctly as a string, which may then work for Sqlite.

ponyORM: trouble with query

I have query with dynamic conditions,i.e.
select (lambda obj:obj.A = 'a' and obj.B = 'b' and ...)
So i write code for this:
def search(self,**kwargs):
q = unicode('lambda obj:', 'utf-8')
for field,value in kwargs.iteritems():
value = unicode(value, 'utf-8')
field = unicode(field, 'utf-8')
q+=u" obj.%s == '%s' and" % (field,value
q = q[0:q.rfind('and')]
res = select(q.encode('utf-8'))[:]
But i have this error during execution of function:
tasks.search(title='Задача 1',url='test.com')
res = select(q.encode('utf-8'))[:]
File "<string>", line 2, in select
File ".../local/lib/python2.7/site-packages/pony/utils.py", line 96, in cut_traceback
return func(*args, **kwargs)
File ".../local/lib/python2.7/site-packages/pony/orm/core.py", line 3844, in select
if not isinstance(tree, ast.GenExpr): throw(TypeError)
File "...local/lib/python2.7/site-packages/pony/utils.py", line 123, in throw
raise exc
TypeError
While it is possible to use strings in order to apply conditions to a query, it can be unsafe, because of the risk of SQL injection. The better way for applying conditions to a query is using the filter() method. You can take the latest version of Pony ORM from https://github.com/ponyorm/pony repository and try a couple of examples provided below.
First we define entities and create a couple of objects:
from decimal import Decimal
from pony.orm import *
db = Database('sqlite', ':memory:')
class Product(db.Entity):
name = Required(unicode)
description = Required(unicode)
price = Required(Decimal)
quantity = Required(int, default=0)
db.generate_mapping(create_tables=True)
with db_session:
Product(name='iPad', description='Air, 16GB', price=Decimal('478.99'), quantity=10)
Product(name='iPad', description='Mini, 16GB', price=Decimal('284.95'), quantity=15)
Product(name='iPad', description='16GB', price=Decimal('299.00'), quantity=10)
Now we'll apply filters passing them as keyword arguments:
def find_by_kwargs(**kwargs):
q = select(p for p in Product)
q = q.filter(**kwargs)
return list(q)
with db_session:
products = find_by_kwargs(name='iPad', quantity=10)
for p in products:
print p.name, p.description, p.price, p.quantity
Another option is to use lambdas in order to specify the conditions:
def find_by_params(name=None, min_price=None, max_price=None):
q = select(p for p in Product)
if name is not None:
q = q.filter(lambda p: p.name.startswith(name))
if min_price is not None:
q = q.filter(lambda p: p.price >= min_price)
if max_price is not None:
q = q.filter(lambda p: p.price <= max_price)
return list(q)
with db_session:
products = find_by_params(name='iPad', max_price=400)
for p in products:
print p.name, p.description, p.price, p.quantity
As you can see filters can be applied dynamically. You can find more information about using filters following by this link: http://doc.ponyorm.com/queries.html#Query.filter
If you still want to filter using strings, you have to apply new filter for each key/value pair.
Something like this:
def search(self,**kwargs):
q = select(m for m in Product)
for field,value in kwargs.iteritems():
value = unicode(value, 'utf-8')
field = unicode(field, 'utf-8')
flt = u"m.{0} == {1}".format(value, field)
q = q.filter(flt)
# return q # return Query which can be further modified (for ex. paging, ordering, etc.)
return q[:] # or return found products
HTH, Tom

Categories

Resources