From 5021ca75e6c52e5e65616117b8bf52532e690329 Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Sat, 17 Jul 2021 13:00:19 +0300 Subject: [PATCH 1/9] bpo-44490: Add ability to serialise and annotate types.Union --- Lib/test/test_types.py | 28 +++++++++ Lib/test/test_typing.py | 10 +++ Lib/typing.py | 4 +- .../2021-07-17-12-59-19.bpo-44490.q22kWR.rst | 3 + Objects/unionobject.c | 62 ++++++++++++++++++- 5 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-07-17-12-59-19.bpo-44490.q22kWR.rst diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 6ed74714dc578e..ffe466870e231d 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -13,6 +13,9 @@ import weakref import typing + +T = typing.TypeVar('T') + class Example: pass @@ -772,6 +775,31 @@ def test_union_parameter_chaining(self): self.assertEqual((list[T] | list[S])[int, T], list[int] | list[T]) self.assertEqual((list[T] | list[S])[int, int], list[int]) + def test_union_pickle(self): + alias = list[T] | int + s = pickle.dumps(alias) + loaded = pickle.loads(s) + self.assertEqual(alias.__args__, loaded.__args__) + self.assertEqual(alias.__parameters__, loaded.__parameters__) + + def test_union_new(self): + with self.assertRaisesRegex( + TypeError, + r"^__args__ argument of typing.Union object is not a valid as type$", + ): + types.Union((1,)) + + with self.assertRaisesRegex( + TypeError, + r"Union\(\) argument '__args__' must be tuple, not int$", + ): + types.Union(1) + + alias = types.Union((int, str, T)) + + self.assertEqual(alias.__args__, (int, str, T)) + self.assertEqual(alias.__parameters__, (T,)) + def test_union_parameter_substitution_errors(self): T = typing.TypeVar("T") x = int | T diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index da6775eb486341..ea99846ae03e88 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3010,6 +3010,16 @@ def barfoo3(x: BA2): ... get_type_hints(barfoo3, globals(), locals(), include_extras=True)["x"], BA2 ) + BA3 = typing.Annotated[int | float, "const"] + def barfoo4(x: BA3): ... + self.assertEqual( + get_type_hints(barfoo4, globals(), locals()), + {"x": int | float} + ) + self.assertEqual( + get_type_hints(barfoo4, globals(), locals(), include_extras=True), + {"x": typing.Annotated[int | float, "const"]} + ) def test_get_type_hints_annotated_refs(self): diff --git a/Lib/typing.py b/Lib/typing.py index 59f3ca39c1edd0..f0390c12a53c45 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -322,7 +322,7 @@ def _eval_type(t, globalns, localns, recursive_guard=frozenset()): if isinstance(t, GenericAlias): return GenericAlias(t.__origin__, ev_args) if isinstance(t, types.Union): - return functools.reduce(operator.or_, ev_args) + return types.Union(ev_args) else: return t.copy_with(ev_args) return t @@ -1786,7 +1786,7 @@ def _strip_annotations(t): stripped_args = tuple(_strip_annotations(a) for a in t.__args__) if stripped_args == t.__args__: return t - return functools.reduce(operator.or_, stripped_args) + return types.Union(stripped_args) return t diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-17-12-59-19.bpo-44490.q22kWR.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-17-12-59-19.bpo-44490.q22kWR.rst new file mode 100644 index 00000000000000..ebb50a2134da01 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-07-17-12-59-19.bpo-44490.q22kWR.rst @@ -0,0 +1,3 @@ +Add ability to serialise ``types.Union`` objects. Fix ``types.Union`` +compatibility issue with ``typing.Annotated``. Patch provided by Yurii +Karabas. diff --git a/Objects/unionobject.c b/Objects/unionobject.c index c0c9a24bcc204a..b3c2d1e5429b3f 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -418,6 +418,13 @@ union_repr(PyObject *self) return NULL; } +static PyObject * +union_reduce(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + unionobject *alias = (unionobject *)self; + return Py_BuildValue("O(O)", Py_TYPE(alias), alias->args); +} + static PyMemberDef union_members[] = { {"__args__", T_OBJECT, offsetof(unionobject, args), READONLY}, {0} @@ -426,6 +433,7 @@ static PyMemberDef union_members[] = { static PyMethodDef union_methods[] = { {"__instancecheck__", union_instancecheck, METH_O}, {"__subclasscheck__", union_subclasscheck, METH_O}, + {"__reduce__", union_reduce, METH_NOARGS}, {0}}; @@ -494,6 +502,57 @@ static PyNumberMethods union_as_number = { .nb_or = _Py_union_type_or, // Add __or__ function }; +static PyObject * +union_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + if (!_PyArg_NoKeywords("Union", kwds)) { + return NULL; + } + if (!_PyArg_CheckPositional("Union", PyTuple_GET_SIZE(args), 1, 1)) { + return NULL; + } + + PyObject* unionargs = PyTuple_GET_ITEM(args, 0); + + if (!PyTuple_CheckExact(unionargs)) { + _PyArg_BadArgument("Union", "argument '__args__'", "tuple", unionargs); + return NULL; + } + + PyObject* result = _Py_Union(unionargs); + + if (result == Py_NotImplemented) { + Py_DECREF(result); + PyErr_SetString(PyExc_TypeError, "__args__ argument of typing.Union object is not a valid as type"); + return NULL; + } + + return result; +} + +static const char* const cls_attrs[] = { + "__name__", + "__qualname__", + "__module__", + NULL, +}; + +static PyObject * +union_getattro(PyObject *self, PyObject *name) +{ + unionobject *alias = (unionobject *)self; + + for (const char * const *p = cls_attrs; ; p++) { + if (*p == NULL) { + break; + } + if (_PyUnicode_EqualToASCIIString(name, *p)) { + return PyObject_GetAttr((PyObject *) Py_TYPE(alias), name); + } + } + return PyObject_GenericGetAttr(self, name); +} + PyTypeObject _PyUnion_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) .tp_name = "types.Union", @@ -507,7 +566,7 @@ PyTypeObject _PyUnion_Type = { .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, .tp_traverse = union_traverse, .tp_hash = union_hash, - .tp_getattro = PyObject_GenericGetAttr, + .tp_getattro = union_getattro, .tp_members = union_members, .tp_methods = union_methods, .tp_richcompare = union_richcompare, @@ -515,6 +574,7 @@ PyTypeObject _PyUnion_Type = { .tp_as_number = &union_as_number, .tp_repr = union_repr, .tp_getset = union_properties, + .tp_new = union_new, }; static PyObject * From 43556fee2c690373b7bf43470681cb54e196443a Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Sat, 17 Jul 2021 13:42:36 +0300 Subject: [PATCH 2/9] Update news --- ...-44490.q22kWR.rst => 2021-07-17-13-41-58.bpo-44662.q22kWR.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/Core and Builtins/{2021-07-17-12-59-19.bpo-44490.q22kWR.rst => 2021-07-17-13-41-58.bpo-44662.q22kWR.rst} (100%) diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-17-12-59-19.bpo-44490.q22kWR.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst similarity index 100% rename from Misc/NEWS.d/next/Core and Builtins/2021-07-17-12-59-19.bpo-44490.q22kWR.rst rename to Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst From eb52dc8547e2569c6e52dc73d173f2a0f0dbe82a Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Mon, 19 Jul 2021 12:03:12 +0300 Subject: [PATCH 3/9] Update unionobject --- Lib/test/test_types.py | 12 +++--- Lib/typing.py | 4 +- Objects/unionobject.c | 85 +++++++++++++++++++++--------------------- 3 files changed, 51 insertions(+), 50 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index ffe466870e231d..ac6c8e0029ae60 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -782,20 +782,20 @@ def test_union_pickle(self): self.assertEqual(alias.__args__, loaded.__args__) self.assertEqual(alias.__parameters__, loaded.__parameters__) - def test_union_new(self): + def test_union_from_args(self): with self.assertRaisesRegex( TypeError, - r"^__args__ argument of typing.Union object is not a valid as type$", + r"^Each union argument must be a type, got 1$", ): - types.Union((1,)) + types.Union.from_args((1,)) with self.assertRaisesRegex( TypeError, - r"Union\(\) argument '__args__' must be tuple, not int$", + r"Union.from_args\(\) argument '__args__' must be tuple, not int$", ): - types.Union(1) + types.Union.from_args(1) - alias = types.Union((int, str, T)) + alias = types.Union.from_args((int, str, T)) self.assertEqual(alias.__args__, (int, str, T)) self.assertEqual(alias.__parameters__, (T,)) diff --git a/Lib/typing.py b/Lib/typing.py index f0390c12a53c45..3653fa8ff7f99a 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -322,7 +322,7 @@ def _eval_type(t, globalns, localns, recursive_guard=frozenset()): if isinstance(t, GenericAlias): return GenericAlias(t.__origin__, ev_args) if isinstance(t, types.Union): - return types.Union(ev_args) + return types.Union.from_args(ev_args) else: return t.copy_with(ev_args) return t @@ -1786,7 +1786,7 @@ def _strip_annotations(t): stripped_args = tuple(_strip_annotations(a) for a in t.__args__) if stripped_args == t.__args__: return t - return types.Union(stripped_args) + return types.Union.from_args(stripped_args) return t diff --git a/Objects/unionobject.c b/Objects/unionobject.c index b3c2d1e5429b3f..adb236cd5f256d 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -299,6 +299,24 @@ is_unionable(PyObject *obj) return 0; } +static int +is_args_unionable(PyObject *args) +{ + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { + PyObject *arg = PyTuple_GET_ITEM(args, iarg); + int is_arg_unionable = is_unionable(arg); + if (is_arg_unionable <= 0) { + if (is_arg_unionable == 0) { + PyErr_Format(PyExc_TypeError, + "Each union argument must be a type, got %.100R", arg); + } + return 0; + } + } + return 1; +} + PyObject * _Py_union_type_or(PyObject* self, PyObject* other) { @@ -422,7 +440,12 @@ static PyObject * union_reduce(PyObject *self, PyObject *Py_UNUSED(ignored)) { unionobject *alias = (unionobject *)self; - return Py_BuildValue("O(O)", Py_TYPE(alias), alias->args); + PyObject* from_args = PyObject_GetAttrString(self, "from_args"); + if (from_args == NULL) { + return NULL; + } + + return Py_BuildValue("O(O)", from_args, alias->args); } static PyMemberDef union_members[] = { @@ -430,7 +453,23 @@ static PyMemberDef union_members[] = { {0} }; +static PyObject * +union_from_args(PyObject *cls, PyObject *args) +{ + if (!PyTuple_CheckExact(args)) { + _PyArg_BadArgument("Union.from_args", "argument '__args__'", "tuple", args); + return NULL; + } + + if (is_args_unionable(args) <= 0) { + return NULL; + } + + return make_union(args); +} + static PyMethodDef union_methods[] = { + {"from_args", union_from_args, METH_O | METH_CLASS}, {"__instancecheck__", union_instancecheck, METH_O}, {"__subclasscheck__", union_subclasscheck, METH_O}, {"__reduce__", union_reduce, METH_NOARGS}, @@ -455,18 +494,9 @@ union_getitem(PyObject *self, PyObject *item) } // Check arguments are unionable. - Py_ssize_t nargs = PyTuple_GET_SIZE(newargs); - for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { - PyObject *arg = PyTuple_GET_ITEM(newargs, iarg); - int is_arg_unionable = is_unionable(arg); - if (is_arg_unionable <= 0) { - Py_DECREF(newargs); - if (is_arg_unionable == 0) { - PyErr_Format(PyExc_TypeError, - "Each union argument must be a type, got %.100R", arg); - } - return NULL; - } + if (is_args_unionable(newargs) <= 0) { + Py_DECREF(newargs); + return NULL; } PyObject *res = make_union(newargs); @@ -502,34 +532,6 @@ static PyNumberMethods union_as_number = { .nb_or = _Py_union_type_or, // Add __or__ function }; -static PyObject * -union_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - if (!_PyArg_NoKeywords("Union", kwds)) { - return NULL; - } - if (!_PyArg_CheckPositional("Union", PyTuple_GET_SIZE(args), 1, 1)) { - return NULL; - } - - PyObject* unionargs = PyTuple_GET_ITEM(args, 0); - - if (!PyTuple_CheckExact(unionargs)) { - _PyArg_BadArgument("Union", "argument '__args__'", "tuple", unionargs); - return NULL; - } - - PyObject* result = _Py_Union(unionargs); - - if (result == Py_NotImplemented) { - Py_DECREF(result); - PyErr_SetString(PyExc_TypeError, "__args__ argument of typing.Union object is not a valid as type"); - return NULL; - } - - return result; -} - static const char* const cls_attrs[] = { "__name__", "__qualname__", @@ -574,7 +576,6 @@ PyTypeObject _PyUnion_Type = { .tp_as_number = &union_as_number, .tp_repr = union_repr, .tp_getset = union_properties, - .tp_new = union_new, }; static PyObject * From 2fcfafe8239578b56e6387fb57332d714cab0c41 Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Mon, 19 Jul 2021 19:28:29 +0300 Subject: [PATCH 4/9] Remove Union serialisation code --- Lib/test/test_types.py | 28 --------- Lib/typing.py | 4 +- .../2021-07-17-13-41-58.bpo-44662.q22kWR.rst | 5 +- Objects/unionobject.c | 62 ++++--------------- 4 files changed, 16 insertions(+), 83 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index ac6c8e0029ae60..6ed74714dc578e 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -13,9 +13,6 @@ import weakref import typing - -T = typing.TypeVar('T') - class Example: pass @@ -775,31 +772,6 @@ def test_union_parameter_chaining(self): self.assertEqual((list[T] | list[S])[int, T], list[int] | list[T]) self.assertEqual((list[T] | list[S])[int, int], list[int]) - def test_union_pickle(self): - alias = list[T] | int - s = pickle.dumps(alias) - loaded = pickle.loads(s) - self.assertEqual(alias.__args__, loaded.__args__) - self.assertEqual(alias.__parameters__, loaded.__parameters__) - - def test_union_from_args(self): - with self.assertRaisesRegex( - TypeError, - r"^Each union argument must be a type, got 1$", - ): - types.Union.from_args((1,)) - - with self.assertRaisesRegex( - TypeError, - r"Union.from_args\(\) argument '__args__' must be tuple, not int$", - ): - types.Union.from_args(1) - - alias = types.Union.from_args((int, str, T)) - - self.assertEqual(alias.__args__, (int, str, T)) - self.assertEqual(alias.__parameters__, (T,)) - def test_union_parameter_substitution_errors(self): T = typing.TypeVar("T") x = int | T diff --git a/Lib/typing.py b/Lib/typing.py index 3653fa8ff7f99a..4f6eb3f29fc4b3 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -322,7 +322,7 @@ def _eval_type(t, globalns, localns, recursive_guard=frozenset()): if isinstance(t, GenericAlias): return GenericAlias(t.__origin__, ev_args) if isinstance(t, types.Union): - return types.Union.from_args(ev_args) + return functools.reduce(operator.or_, ev_args) else: return t.copy_with(ev_args) return t @@ -1786,7 +1786,7 @@ def _strip_annotations(t): stripped_args = tuple(_strip_annotations(a) for a in t.__args__) if stripped_args == t.__args__: return t - return types.Union.from_args(stripped_args) + return functools.reduce(operator.or_, ev_args) return t diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst index ebb50a2134da01..6bb1cbb50e18ad 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst @@ -1,3 +1,2 @@ -Add ability to serialise ``types.Union`` objects. Fix ``types.Union`` -compatibility issue with ``typing.Annotated``. Patch provided by Yurii -Karabas. +Fix ``types.Union`` compatibility issue with ``typing.Annotated``. +Patch provided by Yurii Karabas. diff --git a/Objects/unionobject.c b/Objects/unionobject.c index adb236cd5f256d..80db74c46e9dbf 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -299,24 +299,6 @@ is_unionable(PyObject *obj) return 0; } -static int -is_args_unionable(PyObject *args) -{ - Py_ssize_t nargs = PyTuple_GET_SIZE(args); - for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { - PyObject *arg = PyTuple_GET_ITEM(args, iarg); - int is_arg_unionable = is_unionable(arg); - if (is_arg_unionable <= 0) { - if (is_arg_unionable == 0) { - PyErr_Format(PyExc_TypeError, - "Each union argument must be a type, got %.100R", arg); - } - return 0; - } - } - return 1; -} - PyObject * _Py_union_type_or(PyObject* self, PyObject* other) { @@ -436,43 +418,14 @@ union_repr(PyObject *self) return NULL; } -static PyObject * -union_reduce(PyObject *self, PyObject *Py_UNUSED(ignored)) -{ - unionobject *alias = (unionobject *)self; - PyObject* from_args = PyObject_GetAttrString(self, "from_args"); - if (from_args == NULL) { - return NULL; - } - - return Py_BuildValue("O(O)", from_args, alias->args); -} - static PyMemberDef union_members[] = { {"__args__", T_OBJECT, offsetof(unionobject, args), READONLY}, {0} }; -static PyObject * -union_from_args(PyObject *cls, PyObject *args) -{ - if (!PyTuple_CheckExact(args)) { - _PyArg_BadArgument("Union.from_args", "argument '__args__'", "tuple", args); - return NULL; - } - - if (is_args_unionable(args) <= 0) { - return NULL; - } - - return make_union(args); -} - static PyMethodDef union_methods[] = { - {"from_args", union_from_args, METH_O | METH_CLASS}, {"__instancecheck__", union_instancecheck, METH_O}, {"__subclasscheck__", union_subclasscheck, METH_O}, - {"__reduce__", union_reduce, METH_NOARGS}, {0}}; @@ -494,9 +447,18 @@ union_getitem(PyObject *self, PyObject *item) } // Check arguments are unionable. - if (is_args_unionable(newargs) <= 0) { - Py_DECREF(newargs); - return NULL; + Py_ssize_t nargs = PyTuple_GET_SIZE(newargs); + for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { + PyObject *arg = PyTuple_GET_ITEM(newargs, iarg); + int is_arg_unionable = is_unionable(arg); + if (is_arg_unionable <= 0) { + Py_DECREF(newargs); + if (is_arg_unionable == 0) { + PyErr_Format(PyExc_TypeError, + "Each union argument must be a type, got %.100R", arg); + } + return NULL; + } } PyObject *res = make_union(newargs); From 65558f2ca6573242fc3b382e57c5583a5f2f1a45 Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Mon, 19 Jul 2021 19:30:01 +0300 Subject: [PATCH 5/9] Fix _strip_annotations --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index 4f6eb3f29fc4b3..59f3ca39c1edd0 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1786,7 +1786,7 @@ def _strip_annotations(t): stripped_args = tuple(_strip_annotations(a) for a in t.__args__) if stripped_args == t.__args__: return t - return functools.reduce(operator.or_, ev_args) + return functools.reduce(operator.or_, stripped_args) return t From 8a13208e8d46da57a939695a8a9411e194e5ef27 Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Mon, 19 Jul 2021 19:32:35 +0300 Subject: [PATCH 6/9] Remove whitespace --- Objects/unionobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/unionobject.c b/Objects/unionobject.c index 80db74c46e9dbf..71b186520e630e 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -504,7 +504,7 @@ static const char* const cls_attrs[] = { static PyObject * union_getattro(PyObject *self, PyObject *name) { - unionobject *alias = (unionobject *)self; + unionobject *alias = (unionobject *)self; for (const char * const *p = cls_attrs; ; p++) { if (*p == NULL) { From 36db2f5ad85d1a0fb139600b9847fd104a492d4a Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Mon, 19 Jul 2021 19:55:25 +0300 Subject: [PATCH 7/9] Update Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> --- .../Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst index 6bb1cbb50e18ad..5675e6e2867af0 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst @@ -1,2 +1,3 @@ -Fix ``types.Union`` compatibility issue with ``typing.Annotated``. +Add ``__name__``, ``__qualname__`` and ``__module__`` to ``types.Union``. +This also fixes ``types.Union`` issues with ``typing.Annotated``. Patch provided by Yurii Karabas. From 136f81c3cf00bf6c8c51379e8b1ed35844560c08 Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Mon, 19 Jul 2021 20:03:10 +0300 Subject: [PATCH 8/9] Add PyUnicode_Check to union_getattro --- Objects/unionobject.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Objects/unionobject.c b/Objects/unionobject.c index 71b186520e630e..ef9683f8fdb2bd 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -505,13 +505,14 @@ static PyObject * union_getattro(PyObject *self, PyObject *name) { unionobject *alias = (unionobject *)self; - - for (const char * const *p = cls_attrs; ; p++) { - if (*p == NULL) { - break; - } - if (_PyUnicode_EqualToASCIIString(name, *p)) { - return PyObject_GetAttr((PyObject *) Py_TYPE(alias), name); + if (PyUnicode_Check(name)) { + for (const char * const *p = cls_attrs; ; p++) { + if (*p == NULL) { + break; + } + if (_PyUnicode_EqualToASCIIString(name, *p)) { + return PyObject_GetAttr((PyObject *) Py_TYPE(alias), name); + } } } return PyObject_GenericGetAttr(self, name); From b931632c34a6a30bfa8ec8f49d65b880a6de2c41 Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Mon, 26 Jul 2021 12:34:07 +0300 Subject: [PATCH 9/9] Remove __name__ and __qualname__ attrs --- .../2021-07-17-13-41-58.bpo-44662.q22kWR.rst | 6 +++--- Objects/unionobject.c | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst index 5675e6e2867af0..c165774a4cacfb 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2021-07-17-13-41-58.bpo-44662.q22kWR.rst @@ -1,3 +1,3 @@ -Add ``__name__``, ``__qualname__`` and ``__module__`` to ``types.Union``. -This also fixes ``types.Union`` issues with ``typing.Annotated``. -Patch provided by Yurii Karabas. +Add ``__module__`` to ``types.Union``. This also fixes +``types.Union`` issues with ``typing.Annotated``. Patch provided by +Yurii Karabas. diff --git a/Objects/unionobject.c b/Objects/unionobject.c index ed98ce2a476830..6e85e6d04c9231 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -423,9 +423,7 @@ static PyNumberMethods union_as_number = { }; static const char* const cls_attrs[] = { - "__name__", - "__qualname__", - "__module__", + "__module__", // Required for compatibility with typing module NULL, };