diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 1957cadcbb1592..7a741f633ac905 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2315,7 +2315,8 @@ without the dedicated syntax, as documented below. .. versionadded:: 3.10 -.. class:: TypeAliasType(name, value, *, type_params=(), qualname=None) +.. class:: TypeAliasType(name, value, *, type_params=(), qualname=None, \ + module=None) The type of type aliases created through the :keyword:`type` statement. @@ -2327,8 +2328,20 @@ without the dedicated syntax, as documented below. >>> type(Alias) + When *module* is given, it is used as the new alias's + :attr:`~TypeAliasType.__module__`. When not given (or when ``None`` is + passed explicitly), the module is inferred from the calling frame's + globals. Inference can yield ``None`` when no ``__name__`` is in scope + (e.g. inside :func:`exec` with a fresh globals dict), or it can yield + an unwanted module name when an alias is built inside a factory helper + or generated code. Pass *module* explicitly when the inferred module + would be wrong or unavailable. + .. versionadded:: 3.12 + .. versionchanged:: 3.15 + Added the *module* parameter. + .. attribute:: __name__ The name of the type alias: diff --git a/Lib/test/test_type_aliases.py b/Lib/test/test_type_aliases.py index 9ceee565764c9a..33d733ddf06b87 100644 --- a/Lib/test/test_type_aliases.py +++ b/Lib/test/test_type_aliases.py @@ -328,6 +328,20 @@ def test_keywords(self): self.assertEqual(TA.__type_params__, ()) self.assertEqual(TA.__module__, __name__) + def test_with_module(self): + TA = TypeAliasType("TA", int, module="my.custom.pkg") + self.assertEqual(TA.__module__, "my.custom.pkg") + + def test_with_module_with_exec(self): + ns = {} + exec( + "from typing import TypeAliasType\n" + "TA = TypeAliasType('TA', int, module='x.y')", + ns, + ns, + ) + self.assertEqual(ns["TA"].__module__, "x.y") + def test_errors(self): with self.assertRaises(TypeError): TypeAliasType() diff --git a/Misc/NEWS.d/next/Library/2026-04-29-12-00-00.gh-issue-149132.aB3cDe.rst b/Misc/NEWS.d/next/Library/2026-04-29-12-00-00.gh-issue-149132.aB3cDe.rst new file mode 100644 index 00000000000000..d94163a5a64669 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-29-12-00-00.gh-issue-149132.aB3cDe.rst @@ -0,0 +1,5 @@ +Add an optional keyword-only ``module`` argument to :class:`typing.TypeAliasType`. +When supplied, it overrides the module inferred from the calling frame, which +can yield ``None`` (e.g. inside :func:`exec` with a fresh globals dict) or +an unwanted module (e.g. inside factory helpers). Mirrors the long-standing +``module`` argument of :class:`enum.Enum`'s functional API. diff --git a/Objects/clinic/typevarobject.c.h b/Objects/clinic/typevarobject.c.h index d2f350a3487f08..df99ff40a89a61 100644 --- a/Objects/clinic/typevarobject.c.h +++ b/Objects/clinic/typevarobject.c.h @@ -727,14 +727,16 @@ typealias_reduce(PyObject *self, PyObject *Py_UNUSED(ignored)) } PyDoc_STRVAR(typealias_new__doc__, -"typealias(name, value, *, type_params=, qualname=None)\n" +"typealias(name, value, *, type_params=, qualname=None,\n" +" module=None)\n" "--\n" "\n" "Create a TypeAliasType."); static PyObject * typealias_new_impl(PyTypeObject *type, PyObject *name, PyObject *value, - PyObject *type_params, PyObject *qualname); + PyObject *type_params, PyObject *qualname, + PyObject *module); static PyObject * typealias_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) @@ -742,7 +744,7 @@ typealias_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 4 + #define NUM_KEYWORDS 5 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -751,7 +753,7 @@ typealias_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) .ob_hash = -1, - .ob_item = { &_Py_ID(name), &_Py_ID(value), &_Py_ID(type_params), &_Py_ID(qualname), }, + .ob_item = { &_Py_ID(name), &_Py_ID(value), &_Py_ID(type_params), &_Py_ID(qualname), &_Py_ID(module), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -760,14 +762,14 @@ typealias_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"name", "value", "type_params", "qualname", NULL}; + static const char * const _keywords[] = {"name", "value", "type_params", "qualname", "module", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "typealias", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[4]; + PyObject *argsbuf[5]; PyObject * const *fastargs; Py_ssize_t nargs = PyTuple_GET_SIZE(args); Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 2; @@ -775,6 +777,7 @@ typealias_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) PyObject *value; PyObject *type_params = NULL; PyObject *qualname = NULL; + PyObject *module = NULL; fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -796,11 +799,17 @@ typealias_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) goto skip_optional_kwonly; } } - qualname = fastargs[3]; + if (fastargs[3]) { + qualname = fastargs[3]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + module = fastargs[4]; skip_optional_kwonly: - return_value = typealias_new_impl(type, name, value, type_params, qualname); + return_value = typealias_new_impl(type, name, value, type_params, qualname, module); exit: return return_value; } -/*[clinic end generated code: output=2e7dd170924d92e5 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=71c872adc9d34913 input=a9049054013a1b77]*/ diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c index cdc0ea42eac263..257d0fd939224a 100644 --- a/Objects/typevarobject.c +++ b/Objects/typevarobject.c @@ -2149,14 +2149,16 @@ typealias.__new__ as typealias_new * type_params: object = NULL qualname: object(c_default="NULL") = None + module: object(c_default="NULL") = None Create a TypeAliasType. [clinic start generated code]*/ static PyObject * typealias_new_impl(PyTypeObject *type, PyObject *name, PyObject *value, - PyObject *type_params, PyObject *qualname) -/*[clinic end generated code: output=b7f6d9f1c577cd9c input=cbec290f8c4886ef]*/ + PyObject *type_params, PyObject *qualname, + PyObject *module) +/*[clinic end generated code: output=263bf69bc54213c5 input=229c9f63109a44b9]*/ { if (type_params != NULL && !PyTuple_Check(type_params)) { PyErr_SetString(PyExc_TypeError, "type_params must be a tuple"); @@ -2179,14 +2181,19 @@ typealias_new_impl(PyTypeObject *type, PyObject *name, PyObject *value, } } - PyObject *module = caller(); - if (module == NULL) { - return NULL; + PyObject *resolved_module; + if (module == NULL || module == Py_None) { + resolved_module = caller(); + if (resolved_module == NULL) { + return NULL; + } + } else { + resolved_module = Py_NewRef(module); } PyObject *ta = (PyObject *)typealias_alloc( - name, qualname, checked_params, NULL, value, module); - Py_DECREF(module); + name, qualname, checked_params, NULL, value, resolved_module); + Py_DECREF(resolved_module); return ta; }