Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-46613: Add PyType_GetModuleByDef to the public & limited API #31081

Merged
merged 7 commits into from
Feb 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Doc/c-api/type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ Type Objects
``Py_TYPE(self)`` may be a *subclass* of the intended class, and subclasses
are not necessarily defined in the same module as their superclass.
See :c:type:`PyCMethod` to get the class that defines the method.
See :c:func:`PyType_GetModuleByDef` for cases when ``PyCMethod`` cannot
be used.

.. versionadded:: 3.9

Expand All @@ -166,6 +168,21 @@ Type Objects

.. versionadded:: 3.9

.. c:function:: PyObject* PyType_GetModuleByDef(PyTypeObject *type, struct PyModuleDef *def)

Find the first superclass whose module was created from
the given :c:type:`PyModuleDef` *def*, and return that module.

If no module is found, raises a :py:class:`TypeError` and returns ``NULL``.

This function is intended to be used together with
:c:func:`PyModule_GetState()` to get module state from slot methods (such as
:c:member:`~PyTypeObject.tp_init` or :c:member:`~PyNumberMethods.nb_add`)
and other places where a method's defining class cannot be passed using the
:c:type:`PyCMethod` calling convention.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may mention somewhere that this function only works on a type created by PyType_FromModuleAndSpec.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works with any method that attaches the module to the class. Currently there's just one, but I've been adding new functions with extra arguments relatively fast. I'd rather not document one specific function.


.. versionadded:: 3.11


Creating Heap-Allocated Types
.............................
Expand Down
6 changes: 3 additions & 3 deletions Doc/howto/clinic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1249,15 +1249,15 @@ The ``defining_class`` converter is not compatible with ``__init__`` and ``__new
methods, which cannot use the ``METH_METHOD`` convention.

It is not possible to use ``defining_class`` with slot methods. In order to
fetch the module state from such methods, use ``_PyType_GetModuleByDef`` to
look up the module and then :c:func:`PyModule_GetState` to fetch the module
fetch the module state from such methods, use :c:func:`PyType_GetModuleByDef`
to look up the module and then :c:func:`PyModule_GetState` to fetch the module
state. Example from the ``setattro`` slot method in
``Modules/_threadmodule.c``::

static int
local_setattro(localobject *self, PyObject *name, PyObject *v)
{
PyObject *module = _PyType_GetModuleByDef(Py_TYPE(self), &thread_module);
PyObject *module = PyType_GetModuleByDef(Py_TYPE(self), &thread_module);
thread_module_state *state = get_thread_state(module);
...
}
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,11 @@ New Features

(Contributed by Christian Heimes in :issue:`45459`.)

* Added the :c:data:`PyType_GetModuleByDef` function, used to get the module
in which a method was defined, in cases where this information is not
available directly (via :c:type:`PyCMethod`).
(Contributed by Petr Viktorin in :issue:`46613`.)


Porting to Python 3.11
----------------------
Expand Down
2 changes: 1 addition & 1 deletion Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ PyAPI_FUNC(PyTypeObject *) _PyType_CalculateMetaclass(PyTypeObject *, PyObject *
PyAPI_FUNC(PyObject *) _PyType_GetDocFromInternalDoc(const char *, const char *);
PyAPI_FUNC(PyObject *) _PyType_GetTextSignatureFromInternalDoc(const char *, const char *);
struct PyModuleDef;
PyAPI_FUNC(PyObject *) _PyType_GetModuleByDef(PyTypeObject *, struct PyModuleDef *);
PyAPI_FUNC(PyObject *) PyType_GetModuleByDef(PyTypeObject *, struct PyModuleDef *);

struct _Py_Identifier;
PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int);
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1069,15 +1069,15 @@ def test_state_access(self):
increment_count(1, 2, 3)

def test_get_module_bad_def(self):
# _PyType_GetModuleByDef fails gracefully if it doesn't
# PyType_GetModuleByDef fails gracefully if it doesn't
# find what it's looking for.
# see bpo-46433
instance = self.module.StateAccessType()
with self.assertRaises(TypeError):
instance.getmodulebydef_bad_def()

def test_get_module_static_in_mro(self):
# Here, the class _PyType_GetModuleByDef is looking for
# Here, the class PyType_GetModuleByDef is looking for
# appears in the MRO after a static type (Exception).
# see bpo-46433
class Subclass(BaseException, self.module.StateAccessType):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added function :c:func:`PyType_GetModuleByDef`, which allows accesss to
module state when a method's defining class is not available.
2 changes: 1 addition & 1 deletion Modules/_csv.c
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ static char *dialect_kws[] = {
static _csvstate *
_csv_state_from_type(PyTypeObject *type, const char *name)
{
PyObject *module = _PyType_GetModuleByDef(type, &_csvmodule);
PyObject *module = PyType_GetModuleByDef(type, &_csvmodule);
if (module == NULL) {
return NULL;
}
Expand Down
2 changes: 1 addition & 1 deletion Modules/_functoolsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ partial_call(partialobject *pto, PyObject *args, PyObject *kwargs);
static inline _functools_state *
get_functools_state_by_type(PyTypeObject *type)
{
PyObject *module = _PyType_GetModuleByDef(type, &_functools_module);
PyObject *module = PyType_GetModuleByDef(type, &_functools_module);
if (module == NULL) {
return NULL;
}
Expand Down
2 changes: 1 addition & 1 deletion Modules/_queuemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ simplequeue_get_state(PyObject *module)
}
static struct PyModuleDef queuemodule;
#define simplequeue_get_state_by_type(type) \
(simplequeue_get_state(_PyType_GetModuleByDef(type, &queuemodule)))
(simplequeue_get_state(PyType_GetModuleByDef(type, &queuemodule)))

typedef struct {
PyObject_HEAD
Expand Down
2 changes: 1 addition & 1 deletion Modules/_randommodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ get_random_state(PyObject *module)
static struct PyModuleDef _randommodule;

#define _randomstate_type(type) \
(get_random_state(_PyType_GetModuleByDef(type, &_randommodule)))
(get_random_state(PyType_GetModuleByDef(type, &_randommodule)))

typedef struct {
PyObject_HEAD
Expand Down
2 changes: 1 addition & 1 deletion Modules/_sqlite/module.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ extern struct PyModuleDef _sqlite3module;
static inline pysqlite_state *
pysqlite_get_state_by_type(PyTypeObject *tp)
{
PyObject *module = _PyType_GetModuleByDef(tp, &_sqlite3module);
PyObject *module = PyType_GetModuleByDef(tp, &_sqlite3module);
assert(module != NULL);
return pysqlite_get_state(module);
}
Expand Down
4 changes: 2 additions & 2 deletions Modules/_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2989,8 +2989,8 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
int result;

/* slower approach, walk MRO and get borrowed reference to module.
* _PyType_GetModuleByDef is required for SSLContext subclasses */
PyObject *module = _PyType_GetModuleByDef(type, &_sslmodule_def);
* PyType_GetModuleByDef is required for SSLContext subclasses */
PyObject *module = PyType_GetModuleByDef(type, &_sslmodule_def);
if (module == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"Cannot find internal module state");
Expand Down
2 changes: 1 addition & 1 deletion Modules/_ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ get_ssl_state(PyObject *module)
}

#define get_state_type(type) \
(get_ssl_state(_PyType_GetModuleByDef(type, &_sslmodule_def)))
(get_ssl_state(PyType_GetModuleByDef(type, &_sslmodule_def)))
#define get_state_ctx(c) (((PySSLContext *)(c))->state)
#define get_state_sock(s) (((PySSLSocket *)(s))->ctx->state)
#define get_state_obj(o) ((_sslmodulestate *)PyType_GetModuleState(Py_TYPE(o)))
Expand Down
2 changes: 1 addition & 1 deletion Modules/_struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ get_struct_state(PyObject *module)
static struct PyModuleDef _structmodule;

#define get_struct_state_structinst(self) \
(get_struct_state(_PyType_GetModuleByDef(Py_TYPE(self), &_structmodule)))
(get_struct_state(PyType_GetModuleByDef(Py_TYPE(self), &_structmodule)))
#define get_struct_state_iterinst(self) \
(get_struct_state(PyType_GetModule(Py_TYPE(self))))

Expand Down
12 changes: 6 additions & 6 deletions Modules/_testmultiphase.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,21 +136,21 @@ _testmultiphase.StateAccessType.get_defining_module

Return the module of the defining class.

Also tests that result of _PyType_GetModuleByDef matches defining_class's
Also tests that result of PyType_GetModuleByDef matches defining_class's
module.
[clinic start generated code]*/

static PyObject *
_testmultiphase_StateAccessType_get_defining_module_impl(StateAccessTypeObject *self,
PyTypeObject *cls)
/*[clinic end generated code: output=ba2a14284a5d0921 input=356f999fc16e0933]*/
/*[clinic end generated code: output=ba2a14284a5d0921 input=d2c7245c8a9d06f8]*/
{
PyObject *retval;
retval = PyType_GetModule(cls);
if (retval == NULL) {
return NULL;
}
assert(_PyType_GetModuleByDef(Py_TYPE(self), &def_meth_state_access) == retval);
assert(PyType_GetModuleByDef(Py_TYPE(self), &def_meth_state_access) == retval);
Py_INCREF(retval);
return retval;
}
Expand All @@ -160,15 +160,15 @@ _testmultiphase.StateAccessType.getmodulebydef_bad_def

cls: defining_class

Test that result of _PyType_GetModuleByDef with a bad def is NULL.
Test that result of PyType_GetModuleByDef with a bad def is NULL.
[clinic start generated code]*/

static PyObject *
_testmultiphase_StateAccessType_getmodulebydef_bad_def_impl(StateAccessTypeObject *self,
PyTypeObject *cls)
/*[clinic end generated code: output=64509074dfcdbd31 input=906047715ee293cd]*/
/*[clinic end generated code: output=64509074dfcdbd31 input=edaff09aa4788204]*/
{
_PyType_GetModuleByDef(Py_TYPE(self), &def_nonmodule); // should raise
PyType_GetModuleByDef(Py_TYPE(self), &def_nonmodule); // should raise
assert(PyErr_Occurred());
return NULL;
}
Expand Down
6 changes: 3 additions & 3 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ local_new(PyTypeObject *type, PyObject *args, PyObject *kw)
}
}

PyObject *module = _PyType_GetModuleByDef(type, &thread_module);
PyObject *module = PyType_GetModuleByDef(type, &thread_module);
thread_module_state *state = get_thread_state(module);

localobject *self = (localobject *)type->tp_alloc(type, 0);
Expand Down Expand Up @@ -930,7 +930,7 @@ _ldict(localobject *self, thread_module_state *state)
static int
local_setattro(localobject *self, PyObject *name, PyObject *v)
{
PyObject *module = _PyType_GetModuleByDef(Py_TYPE(self), &thread_module);
PyObject *module = PyType_GetModuleByDef(Py_TYPE(self), &thread_module);
thread_module_state *state = get_thread_state(module);

PyObject *ldict = _ldict(self, state);
Expand Down Expand Up @@ -987,7 +987,7 @@ static PyType_Spec local_type_spec = {
static PyObject *
local_getattro(localobject *self, PyObject *name)
{
PyObject *module = _PyType_GetModuleByDef(Py_TYPE(self), &thread_module);
PyObject *module = PyType_GetModuleByDef(Py_TYPE(self), &thread_module);
thread_module_state *state = get_thread_state(module);

PyObject *ldict = _ldict(self, state);
Expand Down
2 changes: 1 addition & 1 deletion Modules/arraymodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ get_array_state(PyObject *module)
}

#define find_array_state_by_type(tp) \
(get_array_state(_PyType_GetModuleByDef(tp, &arraymodule)))
(get_array_state(PyType_GetModuleByDef(tp, &arraymodule)))
#define get_array_state_by_class(cls) \
(get_array_state(PyType_GetModule(cls)))

Expand Down
2 changes: 1 addition & 1 deletion Modules/cjkcodecs/multibytecodec.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ static struct PyModuleDef _multibytecodecmodule;
static _multibytecodec_state *
_multibyte_codec_find_state_by_type(PyTypeObject *type)
{
PyObject *module = _PyType_GetModuleByDef(type, &_multibytecodecmodule);
PyObject *module = PyType_GetModuleByDef(type, &_multibytecodecmodule);
assert(module != NULL);
return _multibytecodec_get_state(module);
}
Expand Down
6 changes: 3 additions & 3 deletions Modules/clinic/_testmultiphase.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 2 additions & 6 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3735,13 +3735,9 @@ PyType_GetModuleState(PyTypeObject *type)

/* Get the module of the first superclass where the module has the
* given PyModuleDef.
* Implemented by walking the MRO, is relatively slow.
*
* This is internal API for experimentation within stdlib. Discussion:
* https://mail.python.org/archives/list/capi-sig@python.org/thread/T3P2QNLNLBRFHWSKYSTPMVEIL2EEKFJU/
*/
PyObject *
_PyType_GetModuleByDef(PyTypeObject *type, struct PyModuleDef *def)
PyType_GetModuleByDef(PyTypeObject *type, struct PyModuleDef *def)
{
assert(PyType_Check(type));

Expand Down Expand Up @@ -3770,7 +3766,7 @@ _PyType_GetModuleByDef(PyTypeObject *type, struct PyModuleDef *def)

PyErr_Format(
PyExc_TypeError,
"_PyType_GetModuleByDef: No superclass of '%s' has the given module",
"PyType_GetModuleByDef: No superclass of '%s' has the given module",
type->tp_name);
return NULL;
}
Expand Down
2 changes: 1 addition & 1 deletion Python/Python-tokenize.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ get_tokenize_state(PyObject *module) {
}

#define _tokenize_get_state_by_type(type) \
get_tokenize_state(_PyType_GetModuleByDef(type, &_tokenizemodule))
get_tokenize_state(PyType_GetModuleByDef(type, &_tokenizemodule))

#include "clinic/Python-tokenize.c.h"

Expand Down