alembic
alembic copied to clipboard
The Enum's `name` property is lost within an autogenerated migration
Describe the bug
A migration doesn't provide the name
property of Enum if sqlalchemy.Enum
is augmented with sqlalchemy.types.Decorator
. Even if the name
property is explicitly given.
Expected behavior The property has to be provided in Enum's definition inside a migration.
To Reproduce
sql_types.py
from typing import Any
from sqlalchemy import Enum, TypeDecorator
class Enum1(Enum):
def __init__(self, *enums: object, **kw: Any):
validate_strings = kw.pop("validate_strings", True)
super().__init__(*enums, **kw, validate_strings=validate_strings)
class Enum2(TypeDecorator):
impl = Enum
cache_ok = True
def __init__(self, *enums: object, **kw: Any):
validate_strings = kw.pop("validate_strings", True)
super().__init__(*enums, **kw, validate_strings=validate_strings)
tables.py
import enum
from sqlalchemy import Column, MetaData, Table
from sql_types import Enum1, Enum2
class TestEnum(str, enum.Enum):
FIRST = enum.auto()
SECOND = enum.auto()
metadata = MetaData()
test_table = Table(
"test",
metadata,
Column("col1", Enum1(TestEnum), nullable=False),
Column("col2", Enum2(TestEnum, name="enum2"), nullable=False),
)
alembic revision --autogenerate -m "Enums"
provides (shortened):
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('test',
sa.Column('col1', db.sql_types.Enum1('FIRST', 'SECOND', name='testenum'), nullable=False),
sa.Column('col2', db.sql_types.Enum2('FIRST', 'SECOND'), nullable=False)
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('test')
# ### end Alembic commands ###
Error
As you can see from the migration enums are defined differently - the second one misses its name
property.
If I try to run it on PostgreSQL I get the error:
Stacktrace
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Generating static SQL
INFO [alembic.runtime.migration] Will assume transactional DDL.
BEGIN;
CREATE TABLE alembic_version (
version_num VARCHAR(32) NOT NULL,
CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
);
INFO [alembic.runtime.migration] Running upgrade -> 264b6f57ae76, Enums
-- Running upgrade -> 264b6f57ae76
CREATE TYPE testenum AS ENUM ('FIRST', 'SECOND');
Traceback (most recent call last):
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.3056.0_x64__qbz5n2kfra8p0\lib\runpy.py", line 196, in _run_module_as_main
return _run_code(code, main_globals, None,
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.3056.0_x64__qbz5n2kfra8p0\lib\runpy.py", line 86, in _run_code
exec(code, run_globals)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\__main__.py", line 4, in <module>
main(prog="alembic")
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\config.py", line 636, in main
CommandLine(prog=prog).main(argv=argv)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\config.py", line 626, in main
self.run_cmd(cfg, options)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\config.py", line 603, in run_cmd
fn(
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\command.py", line 406, in upgrade
script.run_env()
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\script\base.py", line 586, in run_env
util.load_python_file(self.dir, "env.py")
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\util\pyfiles.py", line 95, in load_python_file
module = load_module_py(module_id, path)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\util\pyfiles.py", line 113, in load_module_py
spec.loader.exec_module(module) # type: ignore
File "<frozen importlib._bootstrap_external>", line 883, in exec_module
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "c:\Users\Михаил\PycharmProjects\Example Project\db\alembic\env.py", line 78, in <module>
run_migrations_offline()
File "c:\Users\Михаил\PycharmProjects\Example Project\db\alembic\env.py", line 52, in run_migrations_offline
context.run_migrations()
File "<string>", line 8, in run_migrations
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\runtime\environment.py", line 946, in run_migrations
self.get_context().run_migrations(**kw)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\runtime\migration.py", line 628, in run_migrations
step.migration_fn(**kw)
File "C:\Users\Михаил\PycharmProjects\Example Project\db\alembic\versions\264b6f57ae76_enums.py", line 24, in upgrade
op.create_table('test',
File "<string>", line 8, in create_table
File "<string>", line 3, in create_table
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\operations\ops.py", line 1318, in create_table
return operations.invoke(op)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\operations\base.py", line 442, in invoke
return fn(self, operation)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\operations\toimpl.py", line 143, in create_table
operations.impl.create_table(table, **kw)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\ddl\impl.py", line 366, in create_table
table.dispatch.before_create(
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\event\attr.py", line 497, in __call__
fn(*args, **kw)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\util\langhelpers.py", line 852, in __call__
return getattr(self.target, self.name)(*arg, **kw)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\sql\sqltypes.py", line 1130, in _on_table_create
t._on_table_create(target, bind, **kw)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\dialects\postgresql\named_types.py", line 98, in _on_table_create
self.create(bind=bind, checkfirst=checkfirst)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\dialects\postgresql\named_types.py", line 338, in create
super().create(bind, checkfirst=checkfirst)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\dialects\postgresql\named_types.py", line 51, in create
bind._run_ddl_visitor(self.DDLGenerator, self, checkfirst=checkfirst)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\engine\mock.py", line 61, in _run_ddl_visitor
visitorcallable(self.dialect, self, **kwargs).traverse_single(element)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\sql\visitors.py", line 664, in traverse_single
return meth(obj, **kw)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\dialects\postgresql\named_types.py", line 153, in visit_enum
self.connection.execute(CreateEnumType(enum))
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\engine\mock.py", line 69, in execute
return self._execute_impl(obj, parameters)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\runtime\migration.py", line 675, in dump
self.impl._exec(construct)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\alembic\ddl\impl.py", line 190, in _exec
compiled = construct.compile(dialect=self.dialect, **compile_kw)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\sql\elements.py", line 308, in compile
return self._compiler(dialect, **kw)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\sql\ddl.py", line 69, in _compiler
return dialect.ddl_compiler(dialect, self, **kw)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\sql\compiler.py", line 870, in __init__
self.string = self.process(self.statement, **compile_kwargs)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\sql\compiler.py", line 915, in process
return obj._compiler_dispatch(self, **kwargs)
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\sql\visitors.py", line 141, in _compiler_dispatch
return meth(self, **kw) # type: ignore # noqa: E501
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\dialects\postgresql\base.py", line 2236, in visit_create_enum_type
self.preparer.format_type(type_),
File "C:\Users\Михаил\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\sqlalchemy\dialects\postgresql\base.py", line 2728, in format_type
raise exc.CompileError(
sqlalchemy.exc.CompileError: PostgreSQL ENUM type requires a name.
Versions.
- OS: Windows 11 23H2
- Python: 3.10.11
- Alembic: 1.13.3
- SQLAlchemy: 2.0.35
- Database: PostgreSQL 17.0.1
- DBAPI: psycopg2 2.9.9
Additional context
The docs state that subclassing from TypeDecorator
should be preferred:
This method is preferred to direct subclassing of SQLAlchemy’s built-in types as it ensures that all required functionality of the underlying type is kept in place.
Have a nice day!