Pony ORM Release 0.6.1

With Pony release 0.6.1 we take into account our users feedback and introduce some new cool features.

Using queries for collections (relationship attributes)

Now you can apply Entity.select, Query.order_by, Query.page, Query.limit, Query.random methods to the relationships to-many.

Below you can find several examples of using these methods. We’ll use the University schema for showing these queries, here are python entity definitions and Entity-Relationship diagram

The example below selects all students with the gpa greater than 3 within the group 101:

    g = Group[101]
    g.students.select(lambda student: student.gpa > 3)[:]

This query can be used for displaying the second page of group 101 student’s list ordered by the name attribute:

    g.students.order_by(Student.name).page(2, pagesize=3)

The same query can be also written in the following way:

    g.students.order_by(lambda s: s.name).limit(3, offset=3)

The following query returns two random students from the group 101:

    g.students.random(2)

And one more example. This query returns the first page of courses which were taken by Student[1] in the second semester, ordered by the course name:

    s = Student[1]
    s.courses.select(lambda c: c.semester == 2).order_by(Course.name).page(1)

Now you can pass globals and locals to queries

It allows you to create a context for executing your queries. Example:

    Student.select(lambda s: s.gpa > x, {'x' : 3})
    # or
    select("s for s in Student if s.gpa > x", {'x' : 3})

Even if the variable x has another value within the outer scope, the value from the dictionary that we pass to the query overrides it.

It behaves the same way as for the standard Python eval expression.

Here is the list of methods and functions that can receive globals and locals starting with this release:
select, left_join, get, exists, Entity.select, Entity.exists, Entity.get, Entity.get_for_update, Query.order_by, Query.filter

Improved inheritance support in queries

Now you can iterate over the base entity and use attributes of subclasses in query conditions:

    select(x for x in BaseClass if x.subclass_attr == y)

As an example, let’s say you want to extract all employee and manager objects which have their salary or bonus greater then some amount x. We have classes Employee and Manager inherited from the class Person:

    class Person(db.Entity):
        name = Required(str)

    class Employee(Person):
        salary = Required(Decimal)
        ...

    class Manager(Employee):
        bonus = Required(Decimal)
        ...

Here is the query:

    select(p for p in Person if p.salary > x or p.bonus > x)

The entity Person doesn’t have the attributes salary and bonus, but the fact that the descendant classes have it, allows us to use this attribute in the query conditions section.

db.insert() can receive both table name or entity name

Object of the Database class can be used for direct access to the database. Now you can specify either the table name or the entity name when usingh the insert() method:

    db.insert(SomeEntity, column1=x, column2=y)
    # is equal to
    db.insert(SomeEntity._table_, column1=x, column2=y)

When you specify an entity, Pony will use the table which is mapped to this entity.

Discriminator attribute can be part of the composite index

    class Person(db.Entity):
        cls_id = Discriminator(int)
        _discriminator_ = 1
        name = Required(str)
        ...
        composite_key(cls_id, name)

Now it is possible to specify the attribute name instead of the attribute itself in composite index

It can be used if you want to use an automatically generated attribute (e.g. classtype) as a part of a composite key.
Let’s use the same example again. Now, in composite_key instead of the name attribute, you can use the string representation of it:

    class Person(db.Entity):
        cls_id = Discriminator(int)
        _discriminator_ = 1
        name = Required(str)
        ...
        composite_key(cls_id, 'name')

Query statistics: global_stats_lock is deprecated, just use global_stats property without any locking

Pony gathers statistics on executed queries. Previously you had to acqire lock on global_stats_lock attribute before accessing the global_stats property. Now Pony does it itself and you don’t need to bother about it yourself.

New load() method for entity instances

The new load() method loads all lazy and non-lazy attributes, but not collection attributes, which were not retrieved from the database yet. If an attribute was already loaded, it won’t be loaded again. You can specify the list of the attributes which need to be loaded, or it’s names. In this case Pony will load only them:

    p = Person[123]
    p.load(Person.biography, Person.some_other_field)

You can use the string attribute names too:

    p = Person[123]
    p.load('biography', 'some_other_field')

Usually, when Pony loads lazy attributes only when you access them. But if your object has a lot of lazy attributes and you know that you’re going to need all of them, it is more optimal to load all of them at once using the load() method.

New load() method for collections

This method loads all related objects from the database. It is useful when you intend to work with a collection and want to load all objects from the database at once. Example:

    customer = Customer[123]
    customer.orders.load()

Enhanced error message when descendant classes declare attributes with the same name

Now if you try to declare the following entities:

    class Person(db.Entity):
        name = Required(str)

    class Employee(Person):
        dob = Required(date)
        ...

    class Manager(Person):
        dob = Required(date)
        ...

    db.generate_mapping(create_tables=True) # raises exception

When Pony will try to generate mapping for the entities above, it will raise the ERDiagramError exception with the following text:

    Attribute Manager.dob conflicts with attribute Employee.dob because both entities inherit from Person. To fix this, move attribute definition to base class

To fix this you need to declare the dob attribute in the entity Person:

    class Person(db.Entity):
        name = Required(str)
        dob = Required(date)

    class Employee(Person):
       ...

    class Manager(Person):
        ...

Other changes and bug fixes

  • Fixed #98: Composite index can include attributes of base entity
  • Fixed #106: incorrect loading of object which consists of primary key only
  • Fixed pony.converting.check_email()
  • Prefetching bug fixed: if collection is already fully loaded it shouldn’t be loaded again
  • Deprecated Entity.order_by(..) method was removed. Use Entity.select().order_by(…) instead
  • Various performance enhancements
  • Multiple bugs were fixed

As always, we appreciate your feedback which you can send us to our email list or by email at team [at] ponyorm.com