--- /dev/null
+.. _orm_declarative_styles_toplevel:
+
+==========================
+Declarative Mapping Styles
+==========================
+
+As introduced at :ref:`orm_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.
+
+
+.. _orm_declarative_generated_base_class:
+
+Using a Generated Base Class
+----------------------------
+
+The most common approach is to generate a "base" class using the
+:func:`_orm.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
+:class:`_orm.registry`, by using the :meth:`_orm.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 :func:`_orm.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 :class:`_schema.Table`
+as well as a :class:`_orm.Mapper` object behind the scenes which comprise
+a full mapping.
+
+.. seealso::
+
+ :ref:`orm_declarative_table_config_toplevel`
+
+ :ref:`orm_declarative_mapper_config_toplevel`
+
+
+.. _orm_explicit_declarative_base:
+
+Creating an Explicit Base Non-Dynamically (for use with mypy, similar)
+----------------------------------------------------------------------
+
+SQLAlchemy includes a :ref:`Mypy plugin <mypy_toplevel>` that automatically
+accommodates for the dynamically generated ``Base`` class
+delivered by SQLAlchemy functions like :func:`_orm.declarative_base`.
+
+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
+:class:`_orm.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
+:meth:`_orm.registry.generate_base` method and will be fully understood by
+type analysis tools without the use of plugins.
+
+.. seealso::
+
+ :ref:`mypy_toplevel` - background on the Mypy plugin which applies the
+ above structure automatically when running Mypy.
+
+
+.. _orm_declarative_decorator:
+
+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 :meth:`_orm.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 :class:`_orm.registry` that we'd use to generate a declarative
+base class via its :meth:`_orm.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.
+
+.. _orm_declarative_dataclasses:
+
+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 :meth:`_orm.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 :meth:`_orm.registry.mapped` decorator
+or via the :meth:`_orm.registry.map_imperatively` method discussed in a
+later section.
+
+Mapping with ``@dataclass`` or ``@attr.s`` may be used in a straightforward
+way with :ref:`orm_imperative_table_configuration` style, where the
+the :class:`_schema.Table`, which means that it is defined separately and
+associated with the class via the ``__table__``. For dataclasses specifically,
+:ref:`orm_declarative_table` is also supported.
+
+.. versionadded:: 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 :class:`_schema.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.
+
+.. versionadded:: 1.4 Added support for direct mapping of Python dataclasses,
+ where the :class:`_orm.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.
+
+.. _orm_declarative_dataclasses_imperative_table:
+
+Example One - Dataclasses with Imperative Table
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+An example of a mapping using ``@dataclass`` using
+:ref:`orm_imperative_table_configuration` 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
+:class:`.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 :func:`_orm.relationship` to be declared separately, it needs to be
+specified directly within the :paramref:`_orm.Mapper.properties` dictionary
+which itself is specified within the ``__mapper_args__`` dictionary, so that it
+is passed to the constructor for :class:`_orm.Mapper`. An alternative to this
+approach is in the next example.
+
+.. _orm_declarative_dataclasses_declarative_table:
+
+Example Two - Dataclasses with Declarative Table
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The fully declarative approach requires that :class:`_schema.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 :func:`_orm.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))}
+ )
+
+.. _orm_declarative_dataclasses_mixin:
+
+Using Declarative Mixins with Dataclasses
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In the section :ref:`orm_mixins_toplevel`, 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 :class:`_orm.declared_attr` decorator, such as in the
+example at :ref:`orm_declarative_mixins_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 :func:`_orm.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
+
+.. versionadded:: 1.4.2 Added support for "declared attr" style mixin attributes,
+ namely :func:`_orm.relationship` constructs as well as :class:`_schema.Column`
+ objects with foreign key declarations, to be used within "Dataclasses
+ with Declarative Table" style mappings.
+
+.. _orm_declarative_attrs_imperative_table:
+
+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 :meth:`_orm.registry.map_imperatively` function. See the section
+:ref:`orm_imperative_dataclasses` for a similar example.
+
+.. _dataclasses: https://docs.python.org/3/library/dataclasses.html
+.. _attrs: https://pypi.org/project/attrs/
Base = mapper_registry.generate_base()
The :class:`_orm.registry` is used directly in order to access a variety
-of mapping styles to suit different use cases:
+of mapping styles to suit different use cases. The primary mapping styles
+offered by :class:`_orm.registry` are further detailed in the following
+sections:
+
+* :ref:`orm_declarative_generated_base_class` - declarative mapping using a
+ base class generated by the :class:`_orm.registry` object.
* :ref:`orm_declarative_decorator` - declarative mapping using a decorator,
rather than a base class.
.. seealso::
- :ref:`declarative_config_toplevel`
-
-.. _orm_explicit_declarative_base:
-
-Creating an Explicit Base Non-Dynamically (for use with mypy, similar)
-----------------------------------------------------------------------
-
-TODO: update for 2.0 - code here may not be accurate
-
-SQLAlchemy includes a :ref:`Mypy plugin <mypy_toplevel>` that automatically
-accommodates for the dynamically generated ``Base`` class
-delivered by SQLAlchemy functions like :func:`_orm.declarative_base`.
-
-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
-:class:`_orm.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
-:meth:`_orm.registry.generate_base` method and will be fully understood by
-type analysis tools without the use of plugins.
-
-.. seealso::
-
- :ref:`mypy_toplevel` - background on the Mypy plugin which applies the
- above structure automatically when running Mypy.
-
-
-.. _orm_declarative_decorator:
-
-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 :meth:`_orm.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 :class:`_orm.registry` that we'd use to generate a declarative
-base class via its :meth:`_orm.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.
-
-.. _orm_declarative_dataclasses:
-
-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 :meth:`_orm.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 :meth:`_orm.registry.mapped` decorator
-or via the :meth:`_orm.registry.map_imperatively` method discussed in a
-later section.
-
-Mapping with ``@dataclass`` or ``@attr.s`` may be used in a straightforward
-way with :ref:`orm_imperative_table_configuration` style, where the
-the :class:`_schema.Table`, which means that it is defined separately and
-associated with the class via the ``__table__``. For dataclasses specifically,
-:ref:`orm_declarative_table` is also supported.
-
-.. versionadded:: 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 :class:`_schema.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.
-
-.. versionadded:: 1.4 Added support for direct mapping of Python dataclasses,
- where the :class:`_orm.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.
-
-.. _orm_declarative_dataclasses_imperative_table:
-
-Example One - Dataclasses with Imperative Table
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-An example of a mapping using ``@dataclass`` using
-:ref:`orm_imperative_table_configuration` 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()
+ * :ref:`declarative_config_toplevel`
+ * :ref:`orm_declarative_styles_toplevel`
+ * :ref:`orm_declarative_table_config_toplevel`
+ * :ref:`orm_declarative_mapper_config_toplevel`
- @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
-:class:`.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 :func:`_orm.relationship` to be declared separately, it needs to be
-specified directly within the :paramref:`_orm.Mapper.properties` dictionary
-which itself is specified within the ``__mapper_args__`` dictionary, so that it
-is passed to the constructor for :class:`_orm.Mapper`. An alternative to this
-approach is in the next example.
-
-.. _orm_declarative_dataclasses_declarative_table:
-
-Example Two - Dataclasses with Declarative Table
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The fully declarative approach requires that :class:`_schema.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 :func:`_orm.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))}
- )
-
-.. _orm_declarative_dataclasses_mixin:
-
-Using Declarative Mixins with Dataclasses
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In the section :ref:`orm_mixins_toplevel`, 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 :class:`_orm.declared_attr` decorator, such as in the
-example at :ref:`orm_declarative_mixins_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 :func:`_orm.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
-
-.. versionadded:: 1.4.2 Added support for "declared attr" style mixin attributes,
- namely :func:`_orm.relationship` constructs as well as :class:`_schema.Column`
- objects with foreign key declarations, to be used within "Dataclasses
- with Declarative Table" style mappings.
-
-.. _orm_declarative_attrs_imperative_table:
-
-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 :meth:`_orm.registry.map_imperatively` function. See the section
-:ref:`orm_imperative_dataclasses` for a similar example.
-
-.. _dataclasses: https://docs.python.org/3/library/dataclasses.html
-.. _attrs: https://pypi.org/project/attrs/
.. _orm_imperative_mapping:
mapper_registry.map_imperatively(User, user_table)
-
Information about mapped attributes, such as relationships to other classes, are provided
via the ``properties`` dictionary. The example below illustrates a second :class:`_schema.Table`
object, mapped to a class called ``Address``, then linked to ``User`` via :func:`_orm.relationship`::