This blog was last modified 580 days before.
We have enum
package in Python and we can use enum.Enum
as a super class to create an Enum
class like below:
from enum import Enum
class Status(Enum):
PENDING = "pending"
RECEIVED = "received"
COMPLETED = "completed"
In SQLAlchemy 2.0.0b4
, we can use Enum
type with ORM class, and in 2.0.1
SQLAlchemy also support Literal
type.
Today we just going to discuss Enum
type with SQLAlchemy.
Use Enum with ORM Class
2 Ways to create an Enum member field
Take the Status
class above as example, we can directly anno the type with Mapped[]
:
class SomeClass(Base):
__tablename__ = "some_table"
id: Mapped[int] = mapped_column(primary_key=True)
status: Mapped[Status]
Or we can also use mapped_column()
to declare it explicitly:
class SomeClass(Base):
__tablename__ = "some_table"
id: Mapped[int] = mapped_column(primary_key=True)
status: Mapped[Status] = mapped_coloum(sqlalchemy.Enum(Status, name='status_enum'))
str_enum: Mapped[Status] = mapped_column(sqlalchemy.Enum("pending", "received", "completed", name="status_enum"))
Notice in this second way, we are able to configure more detail of this enum field. Here are some params we can set in the sqlalchemy.Enum()
.
-
*args
The first positional params. Could be PythonEnum
type class (Actaully it could be any class which has__members__
special method), or severalstr
type values indicating different enums. -
name
Name of the type created in database. If*args
is anEnum
class, then the class name will be used as a default. -
native_enum
If use the backendENUM
type is possible, default toTrue
-
create_constraint
Only meaningful whennative_enum=False
. If this param isTrue
, then SQLAlchemy will build a CHECK constraint on the database against the valid values. However in most cases of usingEnum
type, is no need for more detailed configuration, and the method 1 is sufficient.
Check SQLAlchemy Docs - Enum for more params info.
SQLAlchemy will take use of the backend native ENUM
type if possible. Otherwise, SQLAlchemy will try to use VARCHAR
type to presist the enum type.
Input and Output
For an enum field created from Python enum.Enum
class type, both the input and output will be converted into the Enum
class.
For an enum field created by several literal str
objects, output will be the literal str
type object. Notice you can also add check constraint by settings create_constraint=True
when creating this field, check the guide above.
Dive into SQLAlchemy Enum
How SQLAlchemy stores enum in database
Maybe not same to what we thought, in default cases, SQLAlchemy persist enum in database by enum key but NOT enum value. Use above Status
enum class for example, SQLAlchemy will use the enum key PENDING
but NOT value pending
as presist data.
Why SQLAlchemy do this? This is because the value
of the enum may not be persistable in all cases, but key is always persistable using native backend ENUM
type or a VARCHAR
type. So by this you are free to set whatever things you like as the value of the enum while keep it working with SQLAlchemy Enum.
It's still possible to use enum value as the persist content. To achieve this, we should provide a Enum.values_callable
manually. It returns a list of str
object which would be used to be persisted in database. For a simple enum.Enum
class with all value is str
type, a simple lambda lambda x: [e.value for e in x]
is sufficient.
Configuring Default Enum type behaviour
This feature only available in SQLAlchemy version >= 2.0.1
If you have read other guide of this series, you may familiar with type_annotation_map
. We can also use it to alter the default settings of the Enum
type in ORM classes, check code example below:
class Base(DeclarativeBase):
type_annotation_map = {
enum.Enum: sqlalchemy.Enum(enum.Enum, native_enum=False),
Status: sqlalchemy.Enum(enum.Enum, native_enum=False),
}
With this code, we have changed the default settings of native_enum
to False
for all Enum
type in ORM classes. That means if you don't explicitly set, all enum will not use native backend ENUM
type now.
Also, you can change the dict key to control the scope, in the second line of the dict, we use Status
as key, which means we will change the default settings for enum class with Status
type anno.
Only one comment
[...]create_at: Mapped[Optional[timestamp]]For mapped_column()The mapped column in class declaration will takes precendence over the one in our own Annotated type, and if the params are not conflict, they [...]