Entities can relate to each other. A relationship between two entities is defined by using two attributes which specify both ends of a relationship:
class Customer(db.Entity):
orders = Set('Order')
class Order(db.Entity):
customer = Required(Customer)
In the example above we have two relationship attributes: orders
and customer
. When we define the entity Customer
, the entity Order
is not defined yet. That is why we have to put quotes around Order
in the orders
attribute. Another option is to use lambda:
class Customer(db.Entity):
orders = Set(lambda: Order)
This can be useful if you want your IDE to check the names of declared entities and highlight typos.
Some mappers (e.g. Django) require defining relationships on one side only. Pony requires defining relationships on both sides explicitly (as The Zen of Python reads: Explicit is better than implicit), which allows the user to see all relationships from the perspective of each entity.
All relationships are bidirectional. If you update one side of a relationship, the other side will be updated automatically. For example, if we create an instance of Order
entity, the customer’s set of orders will be updated to include this new order.
There are three types of relationships: one-to-one, one-to-many and many-to-many. A one-to-one relationship is rarely used, most relations between entities are one-to-many and many-to-many. If two entities have one-to-one relationship it often means that they can be combined into a single entity. If your data diagram has a lot of one-to-one relationships, then it may signal that you need to reconsider entity definitions.
Here is an example of one-to-many relationship:
class Order(db.Entity):
items = Set("OrderItem")
class OrderItem(db.Entity):
order = Required(Order)
In the example above the instance of OrderItem
cannot exist without an order. If we want to allow an instance of OrderItem
to exist without being assigned to an order, we can define the order
attribute as Optional
:
class Order(db.Entity):
items = Set("OrderItem")
class OrderItem(db.Entity):
order = Optional(Order)
In order to create many-to-many relationship you need to define both ends of the relationship as Set
attributes:
class Product(db.Entity):
tags = Set("Tag")
class Tag(db.Entity):
products = Set(Product)
In order to implement this relationship in the database, Pony will create an intermediate table. This is a well known solution which allows you to have many-to-many relationships in relational databases.
In order to create a one-to-one relationship, the relationship attributes should be defined as Optional
-Required
or as Optional
-Optional
:
class Person(db.Entity):
passport = Optional("Passport")
class Passport(db.Entity):
person = Required("Person")
Defining both attributes as Required
is not allowed because it doesn’t make sense.
An entity can relate to itself using a self-reference relationship. Such relationships can be of two types: symmetric and non-symmetric. A non-symmetric relationship is defined by two attributes which belong to the same entity.
The specifics of the symmetrical relationship is that the entity has just one relationship attribute specified, and this attribute defines both sides of the relationship. Such relationship can be either one-to-one or many-to-many. Here are examples of self-reference relationships:
class Person(db.Entity):
name = Required(str)
spouse = Optional("Person", reverse="spouse") # symmetric one-to-one
friends = Set("Person", reverse="friends") # symmetric many-to-many
manager = Optional("Person", reverse="employees") # one side of non-symmetric
employees = Set("Person", reverse="manager") # another side of non-symmetric
When two entities have more than one relationship between them, Pony requires the reverse attributes to be specified. This is needed in order to let Pony know which pair of attributes are related to each other. Let’s consider the data diagram where a user can write tweets and also can favorite them:
class User(db.Entity):
tweets = Set("Tweet", reverse="author")
favorites = Set("Tweet", reverse="favorited")
class Tweet(db.Entity):
author = Required(User, reverse="tweets")
favorited = Set(User, reverse="favorites")
In the example above we have to specify the option reverse
. If you will try to generate mapping for the entities definition without the reverse
specified, you will get the exception pony.orm.core.ERDiagramError
: "Ambiguous reverse attribute for Tweet.author"
. That happens because in this case the attribute author
can technically relate either to the attribute tweets
or to favorites
and Pony has no information on which one to use.