Declarative Mapping Styles — SQLAlchemy 1.4 Documentation

Release: 1.4.32 | Release Date: March 6, 2022

# SQLAlchemy 1.4 Documentation

# Declarative Mapping Styles

As introduced at Declarative Mapping, the Declarative Mapping is the typical way that mappings are constructed in modern SQLAlchemy. This section will provide an overview of forms that may be used for Declarative mapper configuration.

## Using a Generated Base Class

The most common approach is to generate a “base” class using the `declarative_base()` function:

    from sqlalchemy.orm import declarative_base

    # declarative base class
    Base = declarative_base()

The declarative base class may also be created from an existing `registry`, by using the `registry.generate_base()` method:

    from sqlalchemy.orm import registry

    reg = registry()

    # declarative base class
    Base = reg.generate_base()

With the declarative base class, new mapped classes are declared as subclasses of the base:

    from sqlalchemy import Column, Integer, String, ForeignKey
    from sqlalchemy.orm import declarative_base

    # declarative base class
    Base = declarative_base()

    # an example mapping using the base
    class User(Base):
        __tablename__ = 'user'

        id = Column(Integer, primary_key=True)
        name = Column(String)
        fullname = Column(String)
        nickname = Column(String)

Above, the `declarative_base()` function returns a new base class from which new classes to be mapped may inherit from, as above a new mapped class `User` is constructed.

For each subclass constructed, the body of the class then follows the declarative mapping approach which defines both a `Table` as well as a `Mapper` object behind the scenes which comprise a full mapping.

See also

Table Configuration with Declarative

Mapper Configuration with Declarative

## Creating an Explicit Base Non-Dynamically (for use with mypy, similar)

SQLAlchemy includes a Mypy plugin that automatically accommodates for the dynamically generated `Base` class delivered by SQLAlchemy functions like `declarative_base()`. For the SQLAlchemy 1.4 series only, this plugin works along with a new set of typing stubs published at sqlalchemy2-stubs.

When this plugin is not in use, or when using other PEP 484 tools which may not know how to interpret this class, the declarative base class may be produced in a fully explicit fashion using the `DeclarativeMeta` directly as follows:

    from sqlalchemy.orm import registry
    from sqlalchemy.orm.decl_api import DeclarativeMeta

    mapper_registry = registry()

    class Base(metaclass=DeclarativeMeta):
        __abstract__ = True

        registry = mapper_registry
        metadata = mapper_registry.metadata

        __init__ = mapper_registry.constructor

The above `Base` is equivalent to one created using the `registry.generate_base()` method and will be fully understood by type analysis tools without the use of plugins.

See also

Mypy / Pep-484 Support for ORM Mappings - background on the Mypy plugin which applies the above structure automatically when running Mypy.

## Declarative Mapping using a Decorator (no declarative base)

As an alternative to using the “declarative base” class is to apply declarative mapping to a class explicitly, using either an imperative technique similar to that of a “classical” mapping, or more succinctly by using a decorator. The `registry.mapped()` function is a class decorator that can be applied to any Python class with no hierarchy in place. The Python class otherwise is configured in declarative style normally:

    from sqlalchemy import Column, Integer, String, Text, ForeignKey

    from sqlalchemy.orm import registry
    from sqlalchemy.orm import relationship

    mapper_registry = registry()

    @mapper_registry.mapped
    class User:
        __tablename__ = 'user'

        id = Column(Integer, primary_key=True)
        name = Column(String)

        addresses = relationship("Address", back_populates="user")

    @mapper_registry.mapped
    class Address:
        __tablename__ = 'address'

        id = Column(Integer, primary_key=True)
        user_id = Column(ForeignKey("user.id"))
        email_address = Column(String)

        user = relationship("User", back_populates="addresses")

Above, the same `registry` that we’d use to generate a declarative base class via its `registry.generate_base()` method may also apply a declarative-style mapping to a class without using a base. When using the above style, the mapping of a particular class will only proceed if the decorator is applied to that class directly. For inheritance mappings, the decorator should be applied to each subclass:

    from sqlalchemy.orm import registry
    mapper_registry = registry()

    @mapper_registry.mapped
    class Person:
        __tablename__ = "person"

        person_id = Column(Integer, primary_key=True)
        type = Column(String, nullable=False)

        __mapper_args__ = {

            "polymorphic_on": type,
            "polymorphic_identity": "person"
        }

    @mapper_registry.mapped
    class Employee(Person):
        __tablename__ = "employee"

        person_id = Column(ForeignKey("person.person_id"), primary_key=True)

        __mapper_args__ = {
            "polymorphic_identity": "employee"
        }

Both the “declarative table” and “imperative table” styles of declarative mapping may be used with the above mapping style.

The decorator form of mapping is particularly useful when combining a SQLAlchemy declarative mapping with other forms of class declaration, notably the Python `dataclasses` module. See the next section.

## Declarative Mapping with Dataclasses and Attrs

The dataclasses module, added in Python 3.7, provides a `@dataclass` class decorator to automatically generate boilerplate definitions of `__init__()`, `__eq__()`, `__repr()__`, etc. methods. Another very popular library that does the same, and much more, is attrs. Both libraries make use of class decorators in order to scan a class for attributes that define the class’ behavior, which are then used to generate methods, documentation, and annotations.

The `registry.mapped()` class decorator allows the declarative mapping of a class to occur after the class has been fully constructed, allowing the class to be processed by other class decorators first. The `@dataclass` and `@attr.s` decorators may therefore be applied first before the ORM mapping process proceeds via the `registry.mapped()` decorator or via the `registry.map_imperatively()` method discussed in a later section.

Mapping with `@dataclass` or `@attr.s` may be used in a straightforward way with Declarative with Imperative Table (a.k.a. Hybrid Declarative) style, where the the `Table`, which means that it is defined separately and associated with the class via the `__table__`. For dataclasses specifically, Declarative Table is also supported.

New in version 1.4.0b2: Added support for full declarative mapping when using dataclasses.

When attributes are defined using `dataclasses`, the `@dataclass` decorator consumes them but leaves them in place on the class. SQLAlchemy’s mapping process, when it encounters an attribute that normally is to be mapped to a `Column`, checks explicitly if the attribute is part of a Dataclasses setup, and if so will replace the class-bound dataclass attribute with its usual mapped properties. The `__init__` method created by `@dataclass` is left intact. In contrast, the `@attr.s` decorator actually removes its own class-bound attributes after the decorator runs, so that SQLAlchemy’s mapping process takes over these attributes without any issue.

New in version 1.4: Added support for direct mapping of Python dataclasses, where the `Mapper` will now detect attributes that are specific to the `@dataclasses` module and replace them at mapping time, rather than skipping them as is the default behavior for any class attribute that’s not part of the mapping.

### Example One - Dataclasses with Imperative Table

An example of a mapping using `@dataclass` using Declarative with Imperative Table (a.k.a. Hybrid Declarative) is as follows:

    from __future__ import annotations

    from dataclasses import dataclass
    from dataclasses import field
    from typing import List
    from typing import Optional

    from sqlalchemy import Column
    from sqlalchemy import ForeignKey
    from sqlalchemy import Integer
    from sqlalchemy import String
    from sqlalchemy import Table
    from sqlalchemy.orm import registry
    from sqlalchemy.orm import relationship

    mapper_registry = registry()

    @mapper_registry.mapped
    @dataclass
    class User:
        __table__ = Table(
            "user",
            mapper_registry.metadata,
            Column("id", Integer, primary_key=True),
            Column("name", String(50)),
            Column("fullname", String(50)),
            Column("nickname", String(12)),
        )
        id: int = field(init=False)
        name: Optional[str] = None
        fullname: Optional[str] = None
        nickname: Optional[str] = None
        addresses: List[Address] = field(default_factory=list)

        __mapper_args__ = {   # type: ignore
            "properties" : {
                "addresses": relationship("Address")
            }
        }

    @mapper_registry.mapped
    @dataclass
    class Address:
        __table__ = Table(
            "address",
            mapper_registry.metadata,
            Column("id", Integer, primary_key=True),
            Column("user_id", Integer, ForeignKey("user.id")),
            Column("email_address", String(50)),
        )
        id: int = field(init=False)
        user_id: int = field(init=False)
        email_address: Optional[str] = None

In the above example, the `User.id`, `Address.id`, and `Address.user_id` attributes are defined as `field(init=False)`. This means that parameters for these won’t be added to `__init__()` methods, but `Session` will still be able to set them after getting their values during flush from autoincrement or other default value generator. To allow them to be specified in the constructor explicitly, they would instead be given a default value of `None`.

For a `relationship()` to be declared separately, it needs to be specified directly within the `Mapper.properties` dictionary which itself is specified within the `__mapper_args__` dictionary, so that it is passed to the constructor for `Mapper`. An alternative to this approach is in the next example.

### Example Two - Dataclasses with Declarative Table

The fully declarative approach requires that `Column` objects are declared as class attributes, which when using dataclasses would conflict with the dataclass-level attributes. An approach to combine these together is to make use of the `metadata` attribute on the `dataclass.field` object, where SQLAlchemy-specific mapping information may be supplied. Declarative supports extraction of these parameters when the class specifies the attribute `__sa_dataclass_metadata_key__`. This also provides a more succinct method of indicating the `relationship()` association:

    from __future__ import annotations

    from dataclasses import dataclass
    from dataclasses import field
    from typing import List

    from sqlalchemy import Column
    from sqlalchemy import ForeignKey
    from sqlalchemy import Integer
    from sqlalchemy import String
    from sqlalchemy.orm import registry
    from sqlalchemy.orm import relationship

    mapper_registry = registry()

    @mapper_registry.mapped
    @dataclass
    class User:
        __tablename__ = "user"

        __sa_dataclass_metadata_key__ = "sa"
        id: int = field(
            init=False, metadata={"sa": Column(Integer, primary_key=True)}
        )
        name: str = field(default=None, metadata={"sa": Column(String(50))})
        fullname: str = field(default=None, metadata={"sa": Column(String(50))})
        nickname: str = field(default=None, metadata={"sa": Column(String(12))})
        addresses: List[Address] = field(
            default_factory=list, metadata={"sa": relationship("Address")}
        )

    @mapper_registry.mapped
    @dataclass
    class Address:
        __tablename__ = "address"
        __sa_dataclass_metadata_key__ = "sa"
        id: int = field(
            init=False, metadata={"sa": Column(Integer, primary_key=True)}
        )
        user_id: int = field(
            init=False, metadata={"sa": Column(ForeignKey("user.id"))}
        )
        email_address: str = field(
            default=None, metadata={"sa": Column(String(50))}
        )

#### Using Declarative Mixins with Dataclasses

In the section Composing Mapped Hierarchies with Mixins, Declarative Mixin classes are introduced. One requirement of declarative mixins is that certain constructs that can’t be easily duplicated must be given as callables, using the `declared_attr` decorator, such as in the example at Mixing in Relationships:

    class RefTargetMixin:
        @declared_attr
        def target_id(cls):
            return Column('target_id', ForeignKey('target.id'))

        @declared_attr
        def target(cls):
            return relationship("Target")

This form is supported within the Dataclasses `field()` object by using a lambda to indicate the SQLAlchemy construct inside the `field()`. Using `declared_attr()` to surround the lambda is optional. If we wanted to produce our `User` class above where the ORM fields came from a mixin that is itself a dataclass, the form would be:

    @dataclass
    class UserMixin:
        __tablename__ = "user"

        __sa_dataclass_metadata_key__ = "sa"

        id: int = field(
            init=False, metadata={"sa": Column(Integer, primary_key=True)}
        )

        addresses: List[Address] = field(
            default_factory=list, metadata={"sa": lambda: relationship("Address")}
        )

    @dataclass
    class AddressMixin:
        __tablename__ = "address"
        __sa_dataclass_metadata_key__ = "sa"
        id: int = field(
            init=False, metadata={"sa": Column(Integer, primary_key=True)}
        )
        user_id: int = field(
            init=False, metadata={"sa": lambda: Column(ForeignKey("user.id"))}
        )
        email_address: str = field(
            default=None, metadata={"sa": Column(String(50))}
        )

    @mapper_registry.mapped
    class User(UserMixin):
        pass

    @mapper_registry.mapped
    class Address(AddressMixin):
      pass

New in version 1.4.2: Added support for “declared attr” style mixin attributes, namely `relationship()` constructs as well as `Column` objects with foreign key declarations, to be used within “Dataclasses with Declarative Table” style mappings.

### Example Three - attrs with Imperative Table

A mapping using `@attr.s`, in conjunction with imperative table:

    import attr

    # other imports

    from sqlalchemy.orm import registry

    mapper_registry = registry()

    @mapper_registry.mapped
    @attr.s
    class User:
        __table__ = Table(
            "user",
            mapper_registry.metadata,
            Column("id", Integer, primary_key=True),
            Column("name", String(50)),
            Column("fullname", String(50)),
            Column("nickname", String(12)),
        )
        id = attr.ib()
        name = attr.ib()
        fullname = attr.ib()
        nickname = attr.ib()
        addresses = attr.ib()

    # other classes...

`@dataclass` and attrs mappings may also be used with classical mappings, i.e. with the `registry.map_imperatively()` function. See the section Imperative Mapping with Dataclasses and Attrs for a similar example.