From: Mike Bayer Date: Thu, 15 Jun 2023 22:34:57 +0000 (-0400) Subject: default Enum name to None and don't remove given name X-Git-Tag: rel_2_0_17~11^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a06d1526216f651f415e9aff56f8d7a725501dc8;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git default Enum name to None and don't remove given name Fixed issue in support for the :class:`.Enum` datatype in the :paramref:`_orm.registry.type_annotation_map` first added as part of :ticket:`8859` where using a custom :class:`.Enum` with fixed configuration in the map would fail to transfer the :paramref:`.Enum.name` parameter, which among other issues would prevent PostgreSQL enums from working if the enum values were passed as individual values. Logic has been updated so that "name" is transferred over, but also that the default :class:`.Enum` which is against the plain Python `enum.Enum` class or other "empty" enum won't set a hardcoded name of ``"enum"`` either. Fixes: #9963 Change-Id: I36526dcb5443579fcd604b0c02bd1f02ca85a977 --- diff --git a/doc/build/changelog/unreleased_20/9963.rst b/doc/build/changelog/unreleased_20/9963.rst new file mode 100644 index 0000000000..8832081678 --- /dev/null +++ b/doc/build/changelog/unreleased_20/9963.rst @@ -0,0 +1,13 @@ +.. change:: + :tags: bug, orm + :tickets: 9963 + + Fixed issue in support for the :class:`.Enum` datatype in the + :paramref:`_orm.registry.type_annotation_map` first added as part of + :ticket:`8859` where using a custom :class:`.Enum` with fixed configuration + in the map would fail to transfer the :paramref:`.Enum.name` parameter, + which among other issues would prevent PostgreSQL enums from working if the + enum values were passed as individual values. Logic has been updated so + that "name" is transferred over, but also that the default :class:`.Enum` + which is against the plain Python `enum.Enum` class or other "empty" enum + won't set a hardcoded name of ``"enum"`` either. diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py index 5fdca7d0a7..2ed4c8b209 100644 --- a/lib/sqlalchemy/sql/sqltypes.py +++ b/lib/sqlalchemy/sql/sqltypes.py @@ -1463,7 +1463,11 @@ class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]): super().__init__(length=length) - if self.enum_class: + # assign name to the given enum class if no other name, and this + # enum is not an "empty" enum. if the enum is "empty" we assume + # this is a template enum that will be used to generate + # new Enum classes. + if self.enum_class and values: kw.setdefault("name", self.enum_class.__name__.lower()) SchemaType.__init__( self, @@ -1549,11 +1553,9 @@ class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]): enum_args = self._enums_argument # make a new Enum that looks like this one. - # pop the "name" so that it gets generated based on the enum # arguments or other rules kw = self._make_enum_kw({}) - kw.pop("name", None) if native_enum is False: kw["native_enum"] = False @@ -1671,7 +1673,8 @@ class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]): def _make_enum_kw(self, kw): kw.setdefault("validate_strings", self.validate_strings) - kw.setdefault("name", self.name) + if self.name: + kw.setdefault("name", self.name) kw.setdefault("schema", self.schema) kw.setdefault("inherit_schema", self.inherit_schema) kw.setdefault("metadata", self.metadata) diff --git a/test/orm/declarative/test_tm_future_annotations_sync.py b/test/orm/declarative/test_tm_future_annotations_sync.py index f0a74b26c3..0255408c26 100644 --- a/test/orm/declarative/test_tm_future_annotations_sync.py +++ b/test/orm/declarative/test_tm_future_annotations_sync.py @@ -1511,11 +1511,16 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): __dialect__ = "default" - @testing.variation("use_callable", [True, False]) + @testing.variation("use_explicit_name", [True, False]) + @testing.variation("use_individual_values", [True, False]) @testing.variation("include_generic", [True, False]) @testing.variation("set_native_enum", ["none", True, False]) def test_enum_explicit( - self, use_callable, include_generic, set_native_enum: Variation + self, + include_generic, + set_native_enum: Variation, + use_explicit_name, + use_individual_values, ): global FooEnum @@ -1525,6 +1530,9 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): kw = {"length": 500} + if use_explicit_name: + kw["name"] = "my_foo_enum" + if set_native_enum.none: expected_native_enum = True elif set_native_enum.set_native_enum: @@ -1536,8 +1544,8 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): else: set_native_enum.fail() - if use_callable: - tam = {FooEnum: Enum(FooEnum, **kw)} + if use_individual_values: + tam = {FooEnum: Enum("foo", "bar", **kw)} else: tam = {FooEnum: Enum(FooEnum, **kw)} @@ -1551,9 +1559,18 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): id: Mapped[int] = mapped_column(primary_key=True) data: Mapped[FooEnum] + if use_explicit_name: + eq_(MyClass.__table__.c.data.type.name, "my_foo_enum") + elif use_individual_values: + is_(MyClass.__table__.c.data.type.enum_class, None) + eq_(MyClass.__table__.c.data.type.name, None) + else: + is_(MyClass.__table__.c.data.type.enum_class, FooEnum) + eq_(MyClass.__table__.c.data.type.name, "fooenum") + is_true(isinstance(MyClass.__table__.c.data.type, Enum)) eq_(MyClass.__table__.c.data.type.length, 500) - is_(MyClass.__table__.c.data.type.enum_class, FooEnum) + is_(MyClass.__table__.c.data.type.native_enum, expected_native_enum) @testing.variation("set_native_enum", ["none", True, False]) @@ -1620,9 +1637,18 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): is_true(isinstance(MyClass.__table__.c.data.type, Enum)) eq_(MyClass.__table__.c.data.type.length, 9) is_(MyClass.__table__.c.data.type.enum_class, FooEnum) + eq_(MyClass.__table__.c.data.type.name, "fooenum") # and not 'enum' @testing.variation( - "sqltype", ["custom", "base_enum", "specific_enum", "string"] + "sqltype", + [ + "custom", + "base_enum_name_none", + "base_enum_default_name", + "specific_unnamed_enum", + "specific_named_enum", + "string", + ], ) @testing.variation("indicate_type_explicitly", [True, False]) def test_pep586_literal( @@ -1645,11 +1671,20 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): self._possible_values = get_args(literal_type) our_type = mapped_col_type = LiteralSqlType(Status) - elif sqltype.specific_enum: + elif sqltype.specific_unnamed_enum: our_type = mapped_col_type = Enum( "to-do", "in-progress", "done", native_enum=False ) - elif sqltype.base_enum: + elif sqltype.specific_named_enum: + our_type = mapped_col_type = Enum( + "to-do", "in-progress", "done", name="specific_name" + ) + elif sqltype.base_enum_name_none: + our_type = Enum(enum.Enum, native_enum=False, name=None) + mapped_col_type = Enum( + "to-do", "in-progress", "done", native_enum=False + ) + elif sqltype.base_enum_default_name: our_type = Enum(enum.Enum, native_enum=False) mapped_col_type = Enum( "to-do", "in-progress", "done", native_enum=False @@ -1678,12 +1713,27 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): Foo.__table__.c.status.type._possible_values, ("to-do", "in-progress", "done"), ) - elif sqltype.specific_enum or sqltype.base_enum: + elif ( + sqltype.specific_unnamed_enum + or sqltype.base_enum_name_none + or sqltype.base_enum_default_name + ): eq_( Foo.__table__.c.status.type.enums, ["to-do", "in-progress", "done"], ) is_(Foo.__table__.c.status.type.native_enum, False) + elif sqltype.specific_named_enum: + is_(Foo.__table__.c.status.type.native_enum, True) + + if ( + sqltype.specific_unnamed_enum + or sqltype.base_enum_name_none + or sqltype.base_enum_default_name + ): + eq_(Foo.__table__.c.status.type.name, None) + elif sqltype.specific_named_enum: + eq_(Foo.__table__.c.status.type.name, "specific_name") @testing.variation("indicate_type_explicitly", [True, False]) def test_pep586_literal_defaults_to_enum( diff --git a/test/orm/declarative/test_typed_mapping.py b/test/orm/declarative/test_typed_mapping.py index 564db64722..c38092139d 100644 --- a/test/orm/declarative/test_typed_mapping.py +++ b/test/orm/declarative/test_typed_mapping.py @@ -1502,11 +1502,16 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): __dialect__ = "default" - @testing.variation("use_callable", [True, False]) + @testing.variation("use_explicit_name", [True, False]) + @testing.variation("use_individual_values", [True, False]) @testing.variation("include_generic", [True, False]) @testing.variation("set_native_enum", ["none", True, False]) def test_enum_explicit( - self, use_callable, include_generic, set_native_enum: Variation + self, + include_generic, + set_native_enum: Variation, + use_explicit_name, + use_individual_values, ): # anno only: global FooEnum @@ -1516,6 +1521,9 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): kw = {"length": 500} + if use_explicit_name: + kw["name"] = "my_foo_enum" + if set_native_enum.none: expected_native_enum = True elif set_native_enum.set_native_enum: @@ -1527,8 +1535,8 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): else: set_native_enum.fail() - if use_callable: - tam = {FooEnum: Enum(FooEnum, **kw)} + if use_individual_values: + tam = {FooEnum: Enum("foo", "bar", **kw)} else: tam = {FooEnum: Enum(FooEnum, **kw)} @@ -1542,9 +1550,18 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): id: Mapped[int] = mapped_column(primary_key=True) data: Mapped[FooEnum] + if use_explicit_name: + eq_(MyClass.__table__.c.data.type.name, "my_foo_enum") + elif use_individual_values: + is_(MyClass.__table__.c.data.type.enum_class, None) + eq_(MyClass.__table__.c.data.type.name, None) + else: + is_(MyClass.__table__.c.data.type.enum_class, FooEnum) + eq_(MyClass.__table__.c.data.type.name, "fooenum") + is_true(isinstance(MyClass.__table__.c.data.type, Enum)) eq_(MyClass.__table__.c.data.type.length, 500) - is_(MyClass.__table__.c.data.type.enum_class, FooEnum) + is_(MyClass.__table__.c.data.type.native_enum, expected_native_enum) @testing.variation("set_native_enum", ["none", True, False]) @@ -1611,9 +1628,18 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): is_true(isinstance(MyClass.__table__.c.data.type, Enum)) eq_(MyClass.__table__.c.data.type.length, 9) is_(MyClass.__table__.c.data.type.enum_class, FooEnum) + eq_(MyClass.__table__.c.data.type.name, "fooenum") # and not 'enum' @testing.variation( - "sqltype", ["custom", "base_enum", "specific_enum", "string"] + "sqltype", + [ + "custom", + "base_enum_name_none", + "base_enum_default_name", + "specific_unnamed_enum", + "specific_named_enum", + "string", + ], ) @testing.variation("indicate_type_explicitly", [True, False]) def test_pep586_literal( @@ -1636,11 +1662,20 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): self._possible_values = get_args(literal_type) our_type = mapped_col_type = LiteralSqlType(Status) - elif sqltype.specific_enum: + elif sqltype.specific_unnamed_enum: our_type = mapped_col_type = Enum( "to-do", "in-progress", "done", native_enum=False ) - elif sqltype.base_enum: + elif sqltype.specific_named_enum: + our_type = mapped_col_type = Enum( + "to-do", "in-progress", "done", name="specific_name" + ) + elif sqltype.base_enum_name_none: + our_type = Enum(enum.Enum, native_enum=False, name=None) + mapped_col_type = Enum( + "to-do", "in-progress", "done", native_enum=False + ) + elif sqltype.base_enum_default_name: our_type = Enum(enum.Enum, native_enum=False) mapped_col_type = Enum( "to-do", "in-progress", "done", native_enum=False @@ -1669,12 +1704,27 @@ class EnumOrLiteralTypeMapTest(fixtures.TestBase, testing.AssertsCompiledSQL): Foo.__table__.c.status.type._possible_values, ("to-do", "in-progress", "done"), ) - elif sqltype.specific_enum or sqltype.base_enum: + elif ( + sqltype.specific_unnamed_enum + or sqltype.base_enum_name_none + or sqltype.base_enum_default_name + ): eq_( Foo.__table__.c.status.type.enums, ["to-do", "in-progress", "done"], ) is_(Foo.__table__.c.status.type.native_enum, False) + elif sqltype.specific_named_enum: + is_(Foo.__table__.c.status.type.native_enum, True) + + if ( + sqltype.specific_unnamed_enum + or sqltype.base_enum_name_none + or sqltype.base_enum_default_name + ): + eq_(Foo.__table__.c.status.type.name, None) + elif sqltype.specific_named_enum: + eq_(Foo.__table__.c.status.type.name, "specific_name") @testing.variation("indicate_type_explicitly", [True, False]) def test_pep586_literal_defaults_to_enum(