]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
default Enum name to None and don't remove given name
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 15 Jun 2023 22:34:57 +0000 (18:34 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 19 Jun 2023 13:46:15 +0000 (09:46 -0400)
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

doc/build/changelog/unreleased_20/9963.rst [new file with mode: 0644]
lib/sqlalchemy/sql/sqltypes.py
test/orm/declarative/test_tm_future_annotations_sync.py
test/orm/declarative/test_typed_mapping.py

diff --git a/doc/build/changelog/unreleased_20/9963.rst b/doc/build/changelog/unreleased_20/9963.rst
new file mode 100644 (file)
index 0000000..8832081
--- /dev/null
@@ -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.
index 5fdca7d0a7bf0d63e4f1c970af02008af2afbd9a..2ed4c8b2090fbf9557658c89f4cb4bd25608f843 100644 (file)
@@ -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)
index f0a74b26c3030f687b50414077f3829d8a42cb5e..0255408c267abb296a49ea89c7897f3935b0a896 100644 (file)
@@ -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(
index 564db647228a78c34ad99e8235113f3115463b55..c38092139d8947edb5034201bec8150c6bdb0291 100644 (file)
@@ -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(