core.orm.abstract.associable
Generic associations are an interesting SQLAlchemy design pattern that allow for collections of records to be attached to other models by mere inheritance.
Generic associations are useful in situations where a model should be attachable to a number of third-party models. For example, we might say that a comment model should be attacheable to any other model (say a page or a ticket). In such an instance generic associations offer the benefit of being simple to integrate on the third-party model.
Additionally we can define generic methods that work on all third-party models that inherit from the associated model (in our example imagine a “Commentable” class that leads to the automatic attachment of comments to Page/Ticket models).
See also: https://github.com/zzzeek/sqlalchemy/blob/master/examples/ generic_associations/table_per_association.py
Note that you do not want to use these associations in lieue of simple relationships between two models. The standard way of doing this leads to a strong relationship on the database and is easier to change and reason about.
Generic associations are meant for generic usecases. These currently include payments (any model may be payable) and files (any model may have files attached). Other generic associations should be introduced along these lines.
A single model may be associated to any number of other models. For example:
┌─────────────┐
┌─────│ Reservation │
┌────────────┐ │ └─────────────┘
│ Payment │◀───┤
└────────────┘ │ ┌─────────────┐
└─────│ Form │
└─────────────┘
Here, Payment
is associable (through the Payable
mixin).
Reservation
and Form
in turn inherit from Payable
.
This all is probably best understood in an example:
class Payment(Base, Associable):
__tablename__ == 'payments'
class Payable:
payment = associated(Payment, 'payment', 'one-to-one')
class Product(Base, Payable):
__tablename__ == 'products'
This results in a product model which has a payment attribute attached to it.
The payments are stored in the payments
table, products in the products
table. The link between the two is established in the automatically created
payments_for_products
table.
Attributes
Classes
Mixin to enable associations on a model. Only models which are |
Functions
|
Creates an associated attribute. This attribute is supposed to be |
Module Contents
- core.orm.abstract.associable.associated(associated_cls: type[_M], attribute_name: str, cardinality: Literal['one-to-many', 'many-to-many'] = ..., *, uselist: Literal['auto'] = ..., backref_suffix: str = ..., onupdate: str | None = ...) rel[list[_M]] [source]
- core.orm.abstract.associable.associated(associated_cls: type[_M], attribute_name: str, cardinality: Literal['one-to-one'], *, uselist: Literal['auto'] = ..., backref_suffix: str = ..., onupdate: str | None = ...) rel[_M | None]
- core.orm.abstract.associable.associated(associated_cls: type[_M], attribute_name: str, cardinality: Cardinality = ..., *, uselist: Literal[True], backref_suffix: str = ..., onupdate: str | None = ...) rel[list[_M]]
- core.orm.abstract.associable.associated(associated_cls: type[_M], attribute_name: str, cardinality: Cardinality = ..., *, uselist: Literal[False], backref_suffix: str = ..., onupdate: str | None = ...) rel[_M | None]
Creates an associated attribute. This attribute is supposed to be defined on the mixin class that will establish the generic association if inherited by a model.
- Parameters:
associated_cls – The class which the model will be associated with.
attribute_name – The name of the attribute used on the mixin class.
cardinality – May be ‘one-to-one’, ‘one-to-many’ or ‘many-to-many’. Cascades are used automatically, unless ‘many-to-many’ is used, in which case cascades are disabled.
uselist – True if the attribute on the inheriting model is a list. Use ‘auto’ if this should be automatically decided depending on the cardinality.
backref_suffix – Individual suffix used for the backref.
onupdate – The ‘onupdate’ constraint of the foreign key column.
Example:
class Adress(Base, Associable): pass class Addressable: address = associated(Address, 'address', 'one-to-one') class Company(Base, Addressable): pass
- class core.orm.abstract.associable.Associable[source]
Mixin to enable associations on a model. Only models which are associable may be targeted by
associated()
.- registered_links: dict[str, RegisteredLink] | None = None[source]
- classmethod association_base() type[Associable] [source]
Returns the model which directly inherits from Associable.
- classmethod register_link(link_name: str, linked_class: type[core.orm.Base], table: sqlalchemy.Table, key: str, attribute: str, cardinality: Cardinality) None [source]
All associated classes are registered through this method. This yields the following benefits:
We gain the ability to query all the linked records in one query. This is hard otherwise as each
Payable
class leads to its own association table which needs to be queried separately.We are able to reset all created backreferences. This is necessary during tests. SQLAlchemy keeps these references around and won’t let us re-register the same model multiple times (which outside of tests is completely reasonable).
- property links: QueryChain[Base][source]
Returns a query chain with all records of all models which attach to the associable model.