Declaring Entities
Entities are Python classes which store an object’s state in the database. Each instance of an entity corresponds to a row in the database table. Often entities represent objects from the real world (e.g. Customer, Product).
Before creating entity instances you need to map entities to the database tables. Pony can map entities to existing tables or create new tables. After the mapping is generated you can query the database and create new instances of entities.
Pony provides an entity-relationship diagram editor which can be used for creating Python entity declarations.
Declaring an entity
Each entity belongs to a database. That is why before defining entities you need to create an object of the Database
class:
from pony.orm import *
db = Database()
class MyEntity(db.Entity):
attr1 = Required(str)
The Pony’s Database object has the Entity
attribute which is used as a base class for all the entities stored in this database. Each new entity that is defined must inherit from this Entity
class.
Entity attributes
Entity attributes are specified as class attributes inside the entity class using the syntax attr_name = kind(type, options)
:
class Customer(db.Entity):
name = Required(str)
email = Required(str, unique=True)
In the parentheses, after the attribute type, you can specify attribute options.
Each attribute can be one of the following kinds:
Required
Optional
PrimaryKey
Set
Required and Optional
Usually most entity attributes are of Required
or Optional
kind. If an attribute is defined as Required
then it must have a value at all times, while Optional
attributes can be empty.
If you need the value of an attribute to be unique then you can set the attribute option unique=True
.
PrimaryKey
PrimaryKey
defines an attribute which is used as a primary key in the database table. Each entity should always have a primary key. If the primary key is not specified explicitly, Pony will create it implicitly. Let’s consider the following example:
class Product(db.Entity):
name = Required(str, unique=True)
price = Required(Decimal)
description = Optional(str)
The entity definition above will be equal to the following:
class Product(db.Entity):
id = PrimaryKey(int, auto=True)
name = Required(str, unique=True)
price = Required(Decimal)
description = Optional(str)
The primary key attribute which Pony adds automatically always will have the name id
and int
type. The option auto=True
means that the value for this attribute will be assigned automatically using the database’s incremental counter or a sequence.
If you specify the primary key attribute yourself, it can have any name and type. For example, we can define the entity Customer
and have customer’s email as the primary key:
class Customer(db.Entity):
email = PrimaryKey(str)
name = Required(str)
Set
A Set
attribute represents a collection. Also we call it a relationship, because such attribute relates to an entity. You need to specify an entity as the type for the Set
attribute. This is the way to define one side for the to-many relationships. As of now, Pony doesn’t allow the use of Set
with primitive types. We plan to add this feature later.
We will talk in more detail about this attribute type in the Entity relationships chapter.
Composite keys
Pony fully supports composite keys. In order to declare a composite primary key you need to specify all the parts of the key as Required
and then combine them into a composite primary key:
class Example(db.Entity):
a = Required(int)
b = Required(str)
PrimaryKey(a, b)
Here PrimaryKey(a, b)
doesn’t create an attribute, but combines the attributes specified in the parenthesis into a composite primary key. Each entity can have only one primary key.
In order to declare a secondary composite key you need to declare attributes as usual and then combine them using the composite_key
directive:
class Example(db.Entity):
a = Required(str)
b = Optional(int)
composite_key(a, b)
In the database composite_key(a, b)
will be represented as the UNIQUE ("a", "b")
constraint.
If have just one attribute, which represents a unique key, you can create such a key by specifying unique=True
by an attribute:
class Product(db.Entity):
name = Required(str, unique=True)
Composite indexes
Using the composite_index()
directive you can create a composite index for speeding up data retrieval. It can combine two or more attributes:
class Example(db.Entity):
a = Required(str)
b = Optional(int)
composite_index(a, b)
You can use the attribute or the attribute name:
class Example(db.Entity):
a = Required(str)
b = Optional(int)
composite_index(a, 'b')
If you want to create a non-unique index for just one column, you can specify the index
option of an attribute.
The composite index can include a discriminator attribute used for inheritance.
Attribute data types
Pony supports the following attribute types:
str
unicode
int
float
Decimal
datetime
date
time
timedelta
bool
buffer - used for binary data in Python 2 and 3
bytes - used for binary data in Python 3
LongStr - used for large strings
LongUnicode - used for large strings
UUID
Json - used for mapping to native database JSON type
IntArray - array of integers
StrArray - array of strings
FloatArray - array of floats
See the Attribute types part of the API Reference for more information.
Attribute options
You can specify additional options during attribute definitions using positional and keyword arguments. See the Attribute options part of the API Reference for more information.
Entity inheritance
Entity inheritance in Pony is similar to inheritance for regular Python classes. Let’s consider an example of a data diagram where entities Student
and Professor
inherit from the entity Person
:
class Person(db.Entity):
name = Required(str)
class Student(Person):
gpa = Optional(Decimal)
mentor = Optional("Professor")
class Professor(Person):
degree = Required(str)
students = Set("Student")
All attributes and relationships of the base entity Person
are inherited by all descendants.
In some mappers (e.g. Django) a query on a base entity doesn’t return the right class: for derived entities the query returns just a base part of each instance. With Pony you always get the correct entity instances:
for p in Person.select():
if isinstance(p, Professor):
print p.name, p.degree
elif isinstance(p, Student):
print p.name, p.gpa
else: # somebody else
print p.name
Note
Since version 0.7.7 you can use isinstance() inside query
staff = select(p for p in Person if not isinstance(p, Student))
In order to create the correct entity instance Pony uses a discriminator column. By default this is a string column and Pony uses it to store the entity class name:
classtype = Discriminator(str)
By default Pony implicitly creates the classtype
attribute for each entity class which takes part in inheritance. You can use your own discriminator column name and type. If you change the type of the discriminator column, then you have to specify the _discriminator_
value for each entity.
Let’s consider the example above and use cls_id
as the name for our discriminator column of int
type:
class Person(db.Entity):
cls_id = Discriminator(int)
_discriminator_ = 1
...
class Student(Person):
_discriminator_ = 2
...
class Professor(Person):
_discriminator_ = 3
...
Multiple inheritance
Pony also supports multiple inheritance. If you use multiple inheritance then all the parent classes of the newly defined class should inherit from the same base class (a “diamond-like” hierarchy).
Let’s consider an example where a student can have a role of a teaching assistant. For this purpose we’ll introduce the entity Teacher
and derive Professor
and TeachingAssistant
from it. The entity TeachingAssistant
inherits from both the Student
class and the Teacher
class:
class Person(db.Entity):
name = Required(str)
class Student(Person):
...
class Teacher(Person):
...
class Professor(Teacher):
...
class TeachingAssistant(Student, Teacher):
...
The TeachingAssistant
objects are instances of both Teacher
and Student
entities and inherit all their attributes. Multiple inheritance is possible here because both Teacher
and Student
have the same base class Person
.
Inheritance is a very powerful tool, but it should be used wisely. Often the data diagram is much simpler if it has limited usage of inheritance.
Representing inheritance in the database
There are three ways to implement inheritance in the database:
Single Table Inheritance: all entities in the hierarchy are mapped to a single database table.
Class Table Inheritance: each entity in the hierarchy is mapped to a separate table, but each table stores only the attributes which the entity doesn’t inherit from its parents.
Concrete Table Inheritance: each entity in the hierarchy is mapped to a separate table and each table stores the attributes of the entity and all its ancestors.
The main problem of the third approach is that there is no single table where we can store the primary key and that is why this implementation is rarely used.
The second implementation is used often, this is how the inheritance is implemented in Django. The disadvantage of this approach is that the mapper has to join several tables together in order to retrieve data which can lead to the performance degradation.
Pony uses the first approach where all entities in the hierarchy are mapped to a single database table. This is the most efficient implementation because there is no need to join tables. This approach has its disadvantages too:
Each table row has columns which are not used because they belong to other entities in the hierarchy. It is not a big problem because the blank columns keep NULL
values and it doesn’t use much space.
The table can have large number of columns if there are a lot of entities in the hierarchy. Different databases have different limits for maximum columns per table, but usually that limit is pretty high.
Adding custom methods to entities
Besides data attributes, entities can have methods. The most straightforward way of adding methods to entities is defining those methods in the entity class. Let’s say we would like to have a method of the Product entity which returns concatenated name and price. It can be done the following way:
class Product(db.Entity):
name = Required(str, unique=True)
price = Required(Decimal)
def get_name_and_price(self):
return "%s (%s)" % (self.name, self.price)
Another approach is using mixin classes. Instead of putting custom methods directly to the entity definition, you can define them in a separate mixin class and inherit entity class from that mixin:
class ProductMixin(object):
def get_name_and_price(self):
return "%s (%s)" % (self.name, self.price)
class Product(db.Entity, ProductMixin):
name = Required(str, unique=True)
price = Required(Decimal)
This approach can be beneficial if you are using our online ER diagram editor. The editor automatically generates entity definitions in accordance with the diagram. In this case, if you add some custom methods to the entity definition, these methods will be overwritten once you change your diagram and save newly generated entity definitions. Using mixins would allow you to separate entity definitions and mixin classes with methods into two different files. This way you can overwrite your entity definitions without losing your custom methods.
For our example above the separation can be done in the following way.
File mixins.py:
class ProductMixin(object):
def get_name_and_price(self):
return "%s (%s)" % (self.name, self.price)
File models.py:
from decimal import Decimal
from pony.orm import *
from mixins import *
class Product(db.Entity, ProductMixin):
name = Required(str, unique=True)
price = Required(Decimal)
Mapping customization
When Pony creates tables from entity definitions, it uses the name of entity as the table name and attribute names as the column names, but you can override this behavior.
The name of the table is not always equal to the name of an entity: in MySQL, PostgreSQL and CockroachDB the default table name generated from the entity name will be converted to the lower case, in Oracle - to the upper case. You can always find the name of the entity table by reading the _table_
attribute of an entity class.
If you need to set your own table name use the _table_
class attribute:
class Person(db.Entity):
_table_ = "person_table"
name = Required(str)
Also you can set schema name:
class Person(db.Entity):
_table_ = ("my_schema", "person_table")
name = Required(str)
If you need to set your own column name, use the option column
:
class Person(db.Entity):
_table_ = "person_table"
name = Required(str, column="person_name")
Also you can specify the _table_options_
for the table. It can be used when you need to set options like ENGINE
or TABLESPACE
. See Entity options part of the API reference for more detail.
For composite attributes use the option columns
with the list of the column names specified:
class Course(db.Entity):
name = Required(str)
semester = Required(int)
lectures = Set("Lecture")
PrimaryKey(name, semester)
class Lecture(db.Entity):
date = Required(datetime)
course = Required(Course, columns=["name_of_course", "semester"])
In this example we override the column names for the composite attribute Lecture.course
. By default Pony will generate the following column names: "course_name"
and "course_semester"
. Pony combines the entity name and the attribute name in order to make the column names easy to understand to the developer.
If you need to set the column names for the intermediate table for many-to-many relationship, you should specify the option column
or columns
for the Set
attributes. Let’s consider the following example:
class Student(db.Entity):
name = Required(str)
courses = Set("Course")
class Course(db.Entity):
name = Required(str)
semester = Required(int)
students = Set(Student)
PrimaryKey(name, semester)
By default, for storing many-to-many relationships between Student
and Course
, Pony will create an intermediate table "Course_Student"
(it constructs the name of the intermediate table from the entity names in the alphabetical order). This table will have three columns: "course_name"
, "course_semester"
and "student"
- two columns for the Course
’s composite primary key and one column for the Student
. Now let’s say we want to name the intermediate table as "Study_Plans"
which have the following columns: "course"
, "semester"
and "student_id"
. This is how we can achieve this:
class Student(db.Entity):
name = Required(str)
courses = Set("Course", table="Study_Plans", columns=["course", "semester"]))
class Course(db.Entity):
name = Required(str)
semester = Required(int)
students = Set(Student, column="student_id")
PrimaryKey(name, semester)
You can find more examples of mapping customization in an example which comes with Pony ORM package
Hybrid methods and properties
(new in version 0.7.4)
You can declare methods and properties inside your entity that you can use in queries. Important that hybrids and properties should contain single line return statement.
class Person(db.Entity):
first_name = Required(str)
last_name = Required(str)
cars = Set(lambda: Car)
@property
def full_name(self):
return self.first_name + ' ' + self.last_name
@property
def has_car(self):
return not self.cars.is_empty()
def cars_by_color(self, color):
return select(car for car in self.cars if car.color == color)
# or return self.cars.select(lambda car: car.color == color)
@property
def cars_price(self):
return sum(c.price for c in self.cars)
class Car(db.Entity):
brand = Required(str)
model = Required(str)
owner = Optional(Person)
year = Required(int)
price = Required(int)
color = Required(str)
with db_session:
# persons' full name
select(p.full_name for p in Person)
# persons who have a car
select(p for p in Person if p.has_car)
# persons who have yellow cars
select(p for p in Person if count(p.cars_by_color('yellow')) > 1)
# sum of all cars that have owners
sum(p.cars_price for p in Person)