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

Cardinality

_M

Classes

RegisteredLink

Associable

Mixin to enable associations on a model. Only models which are

Functions

associated(…)

Creates an associated attribute. This attribute is supposed to be

Module Contents

core.orm.abstract.associable.Cardinality[source]
core.orm.abstract.associable._M[source]

Bases: NamedTuple

cls: type[core.orm.Base][source]
table: sqlalchemy.Table[source]
key: str[source]
attribute: str[source]
cardinality: Cardinality[source]
property class_attribute: Any[source]
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().

classmethod association_base() type[Associable][source]

Returns the model which directly inherits from Associable.

All associated classes are registered through this method. This yields the following benefits:

  1. 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.

  2. 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).

Returns a query chain with all records of all models which attach to the associable model.