From 9c9fd31bcea3beaed6d14fde639e65f6b43bea09 Mon Sep 17 00:00:00 2001 From: Federico Caselli Date: Sun, 27 Nov 2022 18:11:34 +0100 Subject: Improve support for enum in mapped classes Add a new system by which TypeEngine objects have some say in how the declarative type registry interprets them. The Enum datatype is the primary target for this but it is hoped the system may be useful for other types as well. Fixes: #8859 Change-Id: I15ac3daee770408b5795746f47c1bbd931b7d26d --- doc/build/changelog/unreleased_20/8859.rst | 16 +++++ doc/build/orm/declarative_tables.rst | 100 +++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 doc/build/changelog/unreleased_20/8859.rst (limited to 'doc') diff --git a/doc/build/changelog/unreleased_20/8859.rst b/doc/build/changelog/unreleased_20/8859.rst new file mode 100644 index 000000000..85e4be422 --- /dev/null +++ b/doc/build/changelog/unreleased_20/8859.rst @@ -0,0 +1,16 @@ +.. change:: + :tags: usecase, orm + :tickets: 8859 + + Added support custom user-defined types which extend the Python + ``enum.Enum`` base class to be resolved automatically + to SQLAlchemy :class:`.Enum` SQL types, when using the Annotated + Declarative Table feature. The feature is made possible through new + lookup features added to the ORM type map feature, and includes support + for changing the arguments of the :class:`.Enum` that's generated by + default as well as setting up specific ``enum.Enum`` types within + the map with specific arguments. + + .. seealso:: + + :ref:`orm_declarative_mapped_column_enums` diff --git a/doc/build/orm/declarative_tables.rst b/doc/build/orm/declarative_tables.rst index 475813f81..806a6897f 100644 --- a/doc/build/orm/declarative_tables.rst +++ b/doc/build/orm/declarative_tables.rst @@ -369,6 +369,106 @@ while still being able to use succinct annotation-only :func:`_orm.mapped_column configurations. There are two more levels of Python-type configurability available beyond this, described in the next two sections. +.. _orm_declarative_mapped_column_enums: + +Using Python ``Enum`` types in the type map +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.0.0b4 + +User-defined Python types which derive from the Python built-in ``enum.Enum`` +class are automatically linked to the SQLAlchemy :class:`.Enum` datatype +when used in an ORM declarative mapping:: + + import enum + + from sqlalchemy.orm import DeclarativeBase + from sqlalchemy.orm import Mapped + from sqlalchemy.orm import mapped_column + + + class Base(DeclarativeBase): + pass + + + class Status(enum.Enum): + PENDING = "pending" + RECEIVED = "received" + COMPLETED = "completed" + + + class SomeClass(Base): + __tablename__ = "some_table" + + id: Mapped[int] = mapped_column(primary_key=True) + status: Mapped[Status] + +In the above example, the mapped attribute ``SomeClass.status`` will be +linked to a :class:`.Column` with the datatype of ``Enum(Status)``. +We can see this for example in the CREATE TABLE output for the PostgreSQL +database: + +.. sourcecode:: sql + + CREATE TYPE status AS ENUM ('PENDING', 'RECEIVED', 'COMPLETED') + + CREATE TABLE some_table ( + id SERIAL NOT NULL, + status status NOT NULL, + PRIMARY KEY (id) + ) + +The entry used in :paramref:`_orm.registry.type_annotation_map` links the +base ``enum.Enum`` Python type to the SQLAlchemy :class:`.Enum` SQL +type, using a special form which indicates to the :class:`.Enum` datatype +that it should automatically configure itself against an arbitrary enumerated +type. This configuration, which is implicit by default, would be indicated +explicitly as:: + + import enum + import sqlalchemy + + + class Base(DeclarativeBase): + type_annotation_map = {enum.Enum: sqlalchemy.Enum(enum.Enum)} + +The resolution logic within Declarative is able to resolve subclasses +of ``enum.Enum``, in the above example the custom ``Status`` enumeration, +to match the ``enum.Enum`` entry in the +:paramref:`_orm.registry.type_annotation_map` dictionary. The :class:`.Enum` +SQL type then knows how to produce a configured version of itself with the +appropriate settings, including default string length. + +In order to modify the configuration of the :class:`.enum.Enum` datatype used +in this mapping, use the above form, indicating additional arguments. For +example, to use "non native enumerations" on all backends, the +:paramref:`.Enum.native_enum` parameter may be set to False for all types:: + + import enum + import sqlalchemy + + + class Base(DeclarativeBase): + type_annotation_map = {enum.Enum: sqlalchemy.Enum(enum.Enum, native_enum=False)} + +To use a specific configuration for a specific ``enum.Enum`` subtype, such +as setting the string length to 50 when using the example ``Status`` +datatype:: + + import enum + import sqlalchemy + + + class Status(enum.Enum): + PENDING = "pending" + RECEIVED = "received" + COMPLETED = "completed" + + + class Base(DeclarativeBase): + type_annotation_map = { + Status: sqlalchemy.Enum(Status, length=50, native_enum=False) + } .. _orm_declarative_mapped_column_type_map_pep593: -- cgit v1.2.1