Vitaliy
Hi all!
I have noticed that following expression doesn't work in 0.7.4:
User[1].servers.filter('s.expire is None')
But it works using lambda:
User[1].servers.filter(lambda s: s.expire is None)
I widely use such filtering in jinja templates, where lambdas are not supported. I found out that I can use such expression:
User[1].servers.filter('lambda s: s.expire is None')
So my problem is resolved :) But I need to know is this a bug or feature? Because I use mentioned above expression in my another project.
Alexander
Hi Vitaly! What do you mean by "doesn't work"? Does it throw an exception or returns an incorrect result?
Vitaliy
it throws s is not defined
Alexander
Ok, I was able to reproduce it
Vitaliy
Also I have q question about hybrid methods. Expression from your example
select((p, p.cars_by_color('yellow')) for p in Person if p.has_car)
throws an error:
pony.orm.sqlbuilding.AstError: An SQL AST list was expected. Got string: 'SELECT'
Vitaliy
it seems there might be count instead of select:
def cars_by_color(self, color):
return count(car for car in self.cars if car.color == color)
Alexander
Regarding your first question about lambdas, probably we can fix it, but I recommend to use explicit lambda s: with argument name. Lambda without argument looks strange, and probably should be deprecated
Alexander
You are right, the query text is incorrect. We should change example to something like
select(p for p in Person
if count(p.cars_by_color('red')) > 1)
Alexander
it throws s is not defined
Thank you Vitaliy! It is indeed a bug, which affects query.where method as well. We will release a fix soon
Alexander
We've released 0.7.5 version where the bug that Vitaliy discovered was fixed
Alexander
New release every day ☺️
Vitaliy
I found another bug.
This declaration doesn't work in some cases:
def cars_by_color(self, color):
return select(car for car in self.cars if car.color == color)
If we call it inside query as in your example select(p for p in Person if count(p.cars_by_color('yellow')) > 1) it works OK.
But if we call it on instance like Person[1].cars_by_color('yellow') it throws TypeError: Query can only iterate over entity or another query (not a list of objects)
However this expression return self.cars.select(lambda car: car.color == color) works well in both cases.
Alexander
Thanks for reporting! It is not as critical as previous issue, so we will fix it a bit later
Grigory
Hi again!
Is it possible to use Python Enum as a discriminator with Pony?
Something like
class PersonType(Enum):
regular_person = 0
student = 1
Professor = 2
class Person(db.Entity)
type = Discriminator(PersonType)
_discriminator_ = PersonType.regular_person
Alexander
Right now Pony does not support enums
Valery
There was some pretty workaround in google
Alexander
Regarding using enums?
Johannes
Micaiah
Hey, long time no see. I've always used ponyorm through something like heroku where there were pretty clear instructions on how to connect to postgres. Now I'm running it on my own VPS and I'm pretty lost
Valery
Regarding using enums?
Well, actually that was just custom converter, somethong like that
from pony.orm.dbapiprovider import StrConverter
class EnumConverter(StrConverter):
def validate(self, val, **kwargs):
if isinstance(val, str) and val in self.py_type:
return self.py_type(str)
if isinstance(val, self.py_type):
return val
raise ValueError('Instance or value of {} enum expected, got {}'.format(self.py_type, type(val)))
def py2sql(self, val):
return val.value
def sql2py(self, value):
return self.py_type(value)
def sql_type(self):
return 'ENUM ({})'.format(', '.join(repr(i.value) for i in self.py_type))
Vitaliy
Hello again! Hybrid properties does not lifted on Sets. Will it be implemented in future releases?
Alexander
Hi Vitaliy! I think we can add it
Vitaliy
Great! Also I want to ask: can you extend group_concat function to return a list of objects instead of string in case when group_concat's argument is entity or primary key. For example:
select((t, group_concat(t.messages)) for t in Ticket) return something like:
[(Ticket[1], [Message[1], Message[2], Message[3]]),
(Ticket[2], [Message[4], Message[5], Message[6]])]
?
Matthew
Can you explain group_concat please?
Vitaliy
SQL's GROUP_CONCAT function adds the contents of one field from different lines, inserting a delimiter between them (by default it is a comma).
Vitaliy
Something like simpified JOIN with aggregation of several records to one field
Vitaliy
More info here https://www.percona.com/blog/2013/10/22/the-power-of-mysql-group_concat/
Alexander
Great! Also I want to ask: can you extend group_concat function to return a list of objects instead of string in case when group_concat's argument is entity or primary key. For example:
select((t, group_concat(t.messages)) for t in Ticket) return something like:
[(Ticket[1], [Message[1], Message[2], Message[3]]),
(Ticket[2], [Message[4], Message[5], Message[6]])]
?
Hmm... classic group_concat returns string. In order to return list of object we need to do some additional magic. In principle it is possible, but with multiple limitations: the object should have int primary key column, and the function should be in expression part of a generator.
I think it is better not to use group_concat for this, but just translate queries like
select((t, t.messages) for t in Ticket)
this way (currently such queries are not supported)
But I think it is not trivial to implement and other tasks are more urgent now
Vitaliy
Ok, thank you for answer!
Grigory
Is it possible to get a list of objects of different types from Pony's select statement? Like, if we have some student`s and `person`s in the database, and want to do `select on some criteria, so the returned list will consist of both students and persons?
Alexander
It is possible if Student and Person have common inheritance root
Alexander
For example, if Student is inherited from Person
Alexander
The if you select Person, and some students are satisfy select criteria, you will select these students as well
Grigory
So, when I do orm.select, the returned objects could have different types, but if I call 'person.select', what to I get? Would I get a list of person' objects whose `_discriminator_ equals to those for a pure person? Or would I get a list of all objects that inherit from 'person', in the form of 'person' objects?
Alexander
The second. You will get Person and all its subclasses. There is no differense between Person.sellect(...) and select(x for x in Person)
Alexander
You can look at generated SQL and check what conditions pony write for discriminator column
Grigory
So, when Pony returns an object from any kind of query, the object will always be of the type corresponding to the _discriminator_ row.
Thanks, Alexander !
Alexander
Yes. And the same for attribute access. If you have Car.owner attribute of Person type, and the actual car1 owner is of Student type which is inherited from Person, then car1.owner will return the correct Student instance.
Grigory
That is how one properly does ORM!
Alexander
😎
Anonymous
Anonymous
I attached file too earlier. Hi group - the code in previously attached file worked in an older version of PONY but now the function defined within the class definition throws an error. Does anyone have any ideas to help me. Thanks very much, David
Alexander
Hi David!
Can you show the error traceback?
Anonymous
Yes and thanks for super quick reply. How do I embed "code" into Telegraph message or should I attach as file?
Artur Rakhmatulin
some code
Artur Rakhmatulin
use -> [```some code```]
Alexander
triple backquote
Anonymous
```Traceback (most recent call last):
File "C:/cis/main.py", line 246, in onCompButtonClick
self.showComponentFrame(name)
File "C:/cis/main.py", line 251, in showComponentFrame
self.activeFrames[name]=ComponentFrame(None, "<< " + name + " >>", name, self.frameSize)
File "C:\cis\componentframe.py", line 81, in init
self.loadComponents()
File "C:\cis\componentframe.py", line 96, in loadComponents
self.allComponents = self.componentFunctions.allComponents(stock=self.filterInStock)
File "C:\cis\FunctionsInductors.py", line 68, in allComponents
comp.dcr, comp.shielded, comp.numAMLS,
File "C:\cis\dbDefines.py", line 103, in numAMLS
return count(self.amls)
File "C:\python\lib\site-packages\pony\orm\core.py", line 5188, in aggrfunc
return std_func(*args)
File "C:\python\lib\site-packages\pony\utils\utils.py", line 331, in count
if hasattr(arg, 'count'): return arg.count()
File "<string>", line 2, in count
File "C:\python\lib\site-packages\pony\utils\utils.py", line 58, in cut_traceback
return func(*args, **kwargs)
File "C:\python\lib\site-packages\pony\orm\core.py", line 3112, in count
if cache is None or not cache.is_alive: throw_db_session_is_over('read value of', obj, attr)
File "C:\python\lib\site-packages\pony\orm\core.py", line 614, in throw_db_session_is_over
throw(DatabaseSessionIsOver, msg % (action, safe_repr(obj), '.%s' % attr.name if attr else ''))
File "C:\python\lib\site-packages\pony\utils\utils.py", line 98, in throw
raise exc
pony.orm.core.DatabaseSessionIsOver: Cannot read value of Inductors['IND00000',1].amls: the database session is over
Artur Rakhmatulin
before and after the code
Anonymous
The actual code (not pseudo) is in next attached file, see line 102
Anonymous
Alexander
According to traceback, you use PonyORM 0.7.3, not the newest release (which is PonyORM 0.7.5)
But if I understand correctly, the error is not caused by Pony. The reason for the error is that you call comp.numAMLS after the db_session is over.
comp.numAMLS calls count(obj.collection), which needs to send query to the database, but after the db_session is over it is impossible to sent new queries
Anonymous
I understand. So how could I write a method of a db class that returns the length of a Set?
Alexander
Do I understand correctly that your application is some GUI application and not web-based one?
Anonymous
Yes - but just solved it. I defined a function external to the class definition, with it's own database seesion. This works fine. I can share if you want?
Alexander
yes, you can show it to be sure that the code is correct
Anonymous
```
@db_session
def getCount(component):
return select(count(c.amls) for c in Component if c == component)[:][0]
Anonymous
and here is the call: ```
@property
def numAMLS(self):
return getCount(self)
Alexander
To me it looks like a hack
Alexander
In this code you create a new db_session to perform a query. But the original object is created in previous db_session. The correct solution is to extend previous db_session so it embrace the query
Alexander
In order to write correct code, it is necessary to understand what is the life cycle of ORM objects in application. How long they should exist in memory.
Classical PonyORM usage is for web application. In web application, each HTTP request to the site has separate db_session. During these db_session ORM objects are loaded from the database, then they are used for generating HTML page or JSON and then they are discarded from the memore by garbahe collector after the db_session is over. Typical db_session in a web application exists less then a second, and then all objects are garbage collected.
If you write GUI application, then it is not as clear what should be included in a single db_session.
What is the expected size of your SQLite database (rougly?)
Alexander
I think you can use a different approach: in the beginning of your application code you can write
pony.MODE == 'INTERACTIVE'
After that you will have implicit db_session just like in Python shell. Then it will be not necessary to wrap any function with db_session. But you need to call commit when the changes should be saved to the database.
Also, you need to perform all work with the database from a single thread
Anonymous
Wow - thanks for the info and explanation. I am using GUI not web. Using MySQL not sqlite (sqlite when I developed then changed to MySQL). Most tables maximum few thousand rows. Will think about your approach and how much effort to do manual commit. Thanks again.
Alexander
I think, the main question is how many concurrent users will be connected to the database at the same time. If it is the single-user application you may have pretty long db_sessions, but for concurrent users sessions should be short in order to avoid database locks and see fresh information from the database
Anonymous
Usually 1 person but there are times when >1 concurrently. I think using manual commit is still viable option , but need to think about time required to find all relevant pieces of code (and possible new bugs)
Grigory
Alexander , now there is a tricky question:
is it possible to have Pony-managed classes bound to some external object?
Like, we have our class Base, and sometimes (specifically, when we do nosetests) there simultaneously exist several instances of it. And we want each instance of the Base class to have methods/members that are actually Pony-managed classes bound to DBs which are different for each instance of Base.
Is it possible to do something like this?
Grigory
Something like:
class Base():
def __init__(self):
db_path = ":memory:"
db = orm.Database()
db.bind(provider='sqlite', filename=db_path, create_db=True)
class OrmManaged(db.Entity):
someProperty = orm.PrimaryKey(int, auto=True)
and then call
a = Base()
b = Base()
a.OrmManaged()
b.OrmManaged()
Grigory
?
Alexander
Maybe I don't fully understand your question, but you can access entity classes as attributes of the database object: db.Person. So, you can make several instances of the database and use different Person entities:
def define_entities(db):
class Person(db.Entity):
name = Required(str)
db1 = Database('sqlite', 'foo.sqlite')
define_entities(db1)
db1.generate_mapping(create_tables=True)
db2 = Database('sqlite', 'bar.sqlite')
define_entities(db2)
db2.generate_mapping(create_tables=True)
with db_session:
persons = select(p for p in db1.Person)[:]
Grigory
Thanks! I need to think about it...
Grigory
Alexander , you know, it would be very cool if all this dialogs from Telegram channel were available online at Pony website for Google to index...
Grigory
Like old-school subscription lists.
Grigory
It would really save you a lot of time ))
Jim
I already thougt to that too. maybe we coulld fill up the pony github wiki like FAQ ?
Grigory
too much work, I guess. It is easier to just set up some export routine.
Alexander
Actually we had (and still have) an old-school mailing list which I like for exactly this reason:
http://ponyorm-list.ponyorm.com
Some time ago we moved to Telegram, because it was more "cool", but maybe we need to revive the classic mailing list again. The ability to search archives using Google is very important thing, imo.
Also I probably need to develop a habit to write an answer at some wiki FAQ page instead of answering here, and then referring to this answer in the chat
Grigory
Alexander , just auto-exporting messages from this chat with a bot will be enough.
J J
Like the web irc logs
Grigory
yep
Alexander
Sounds like a good idea. Can someone suggest such a bot?