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

gh-91053: Add an optional callback that is invoked whenever a function is modified #97834

Closed
wants to merge 152 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
152 commits
Select commit Hold shift + click to select a range
48eb802
Add an optional callback that is invoked whenever a function is modified
mpage Sep 23, 2022
18329a1
Merge branch 'main' into func-modified-cb
mpage Oct 4, 2022
501c4dd
Fix the build on windows
mpage Oct 4, 2022
b727aa2
Fix refcounting for the result of vectorcall
mpage Oct 4, 2022
e3a8230
Move callback into _PyRuntimeState
mpage Oct 5, 2022
b2e20ef
Handle multiple events being dispatched
mpage Oct 5, 2022
0a30690
Add NEWS entry
mpage Oct 5, 2022
73dd809
Make the callback per-interpreter, rather than per-runtime
mpage Oct 5, 2022
4fa1cc8
Call the builtin `id` to return the id for a function that's about to…
mpage Oct 5, 2022
80f49f0
Fix prototype for restore_func_event_callback
mpage Oct 6, 2022
81fcd2c
gh-93357: Start porting asyncio server test cases to IsolatedAsyncioT…
arhadthedev Oct 4, 2022
cc60df0
GH-95913: Update what's new in 3.11 for asyncio (#97806)
gvanrossum Oct 4, 2022
39bef03
gh-90301: Doc: Add references to PEP 686 (#96816)
methane Oct 4, 2022
de65e64
gh-96448: fix documentation for _thread.lock.acquire (#96449)
dgiger42 Oct 4, 2022
c00e5c5
gh-97008: Add a Python implementation of AttributeError and NameError…
ambv Oct 4, 2022
75af114
gh-58451: Add optional delete_on_close parameter to NamedTemporaryFil…
Ev2geny Oct 4, 2022
937cc6d
gh-95913: Move py.exe to appropriate What's New section & refine text…
CAM-Gerlach Oct 4, 2022
1fce505
gh-88355: Fix backslashes in AF_PIPE (#96543)
cousteaulecommandant Oct 4, 2022
bb89f15
gh-93738: Documentation C syntax (:c:type:`Py_UNICODE*` -> :c:expr:`P…
AA-Turner Oct 4, 2022
1007409
gh-93738: Documentation C syntax (:c:type:`PyUnicodeObject*` -> :c:ex…
AA-Turner Oct 4, 2022
86e2243
gh-93738: Documentation C syntax (:c:type:`PyBytesObject*` -> :c:expr…
AA-Turner Oct 4, 2022
ff43073
gh-93738: Documentation C syntax (:c:type:`PyTupleObject*` -> :c:expr…
AA-Turner Oct 4, 2022
b220c7f
gh-93738: Documentation C syntax (:c:type:`PyInterpreterState *` -> :…
AA-Turner Oct 4, 2022
82b2939
gh-93738: Documentation C syntax (:c:type:`PyObject` -> :c:expr:`PyOb…
AA-Turner Oct 4, 2022
c9733b2
gh-95913: Copyedit/improve Other Language Changes What's New section …
CAM-Gerlach Oct 4, 2022
9455377
gh-93738: Documentation C syntax (:c:data:`view->obj` -> :c:expr:`vie…
AA-Turner Oct 4, 2022
3d795a2
gh-93738: Documentation C syntax (Use `c:struct`) (#97772)
AA-Turner Oct 4, 2022
cb408c6
gh-93738: Documentation C syntax (:c:type:`TYPE` -> :c:expr:`TYPE`) (…
AA-Turner Oct 4, 2022
dde4563
gh-93738: Documentation C syntax (:c:type:`FILE` -> :c:expr:`FILE`) (…
AA-Turner Oct 4, 2022
3a134eb
gh-93738: Documentation C syntax (:c:type: to :c:expr:, misc. cases) …
AA-Turner Oct 4, 2022
556e861
gh-95913: Copyedit/improve Implementation Changes What's New section …
CAM-Gerlach Oct 5, 2022
2eeacb0
gh-97837: Change deprecation warning message in `unittest` (#97838)
sobolevn Oct 5, 2022
76e702c
GH-97779: Ensure that *all* frame objects are backed by "complete" fr…
brandtbucher Oct 5, 2022
0ba4aad
GH-91079: Decouple C stack overflow checks from Python recursion chec…
markshannon Oct 5, 2022
86eb98c
gh-97654: Add auto exception chaining example to tutorial (#97703)
smheidrich Oct 5, 2022
83e1296
Add re.VERBOSE flag documentation example (#97678)
athos-ribeiro Oct 5, 2022
2efca9c
gh-97825: fix AttributeError when calling subprocess.check_output(inp…
akulakov Oct 5, 2022
ae65da9
gh-93738: Documentation C syntax (:c:type:`PyTypeObject*` -> :c:expr:…
AA-Turner Oct 5, 2022
681e059
GH-96704: Add {Task,Handle}.get_context(), use it in call_exception_h…
gvanrossum Oct 5, 2022
6d4d702
gh-87092: bring compiler code closer to a preprocessing-opt-assembler…
iritkatriel Oct 5, 2022
45b9d05
gh-97661: Improve accuracy of sqlite3.Cursor.fetchone docs (#97662)
jiajunjie Oct 5, 2022
c34bef6
gh-74696: Pass root_dir to custom archivers which support it (GH-94251)
serhiy-storchaka Oct 5, 2022
18523e0
gh-97758: Fix a crash in getpath_joinpath() called without arguments …
serhiy-storchaka Oct 5, 2022
a47df46
gh-95196: Disable incorrect pickling of the C implemented classmethod…
serhiy-storchaka Oct 5, 2022
2d20cf5
gh-93357: Port test cases to IsolatedAsyncioTestCase, part 2 (#97896)
arhadthedev Oct 5, 2022
d1e99a7
gh-93738: Documentation C syntax (Function glob patterns -> literal m…
AA-Turner Oct 5, 2022
129a24f
gh-88050: Fix asyncio subprocess to kill process cleanly when process…
kumaraditya303 Oct 5, 2022
4017dd4
GH-95172 Make the same version `versionadded` oneline (#95172)
180909 Oct 5, 2022
6eee387
build(deps): bump actions/stale from 5 to 6 (#97701)
dependabot[bot] Oct 5, 2022
f200192
gh-91539: improve performance of get_proxies_environment (#91566)
eendebakpt Oct 5, 2022
5da6c6b
gh-93738: Documentation C syntax (:c:type:<C type> -> :c:expr:<C type…
AA-Turner Oct 5, 2022
9a24191
I changed my surname early this year (#96671)
tshepang Oct 5, 2022
0aca8ca
gh-97850: Remove all known instances of module_repr() (#97876)
warsaw Oct 5, 2022
0ee0e4d
docs(typing): add "see PEP 675" to LiteralString (#97926)
simon04 Oct 5, 2022
351eda5
gh-65961: Raise `DeprecationWarning` when `__package__` differs from …
brettcannon Oct 5, 2022
adc2815
gh-96865: [Enum] fix Flag to use CONFORM boundary (GH-97528)
ethanfurman Oct 5, 2022
3335bbe
GH-88968: Add notes about socket ownership transfers (#97936)
gvanrossum Oct 5, 2022
97a27d8
gh-95691: Doc BufferedWriter and BufferedReader (#95703)
180909 Oct 5, 2022
a857648
gh-94808: Cover `PyObject_PyBytes` case with custom `__bytes__` metho…
sobolevn Oct 6, 2022
d458682
gh-94808: Cover `PyUnicode_Count` in CAPI (#96929)
sobolevn Oct 6, 2022
79fbf97
gh-97897: Prevent os.mkfifo and os.mknod segfaults with macOS 13 SDK …
ned-deily Oct 6, 2022
a0d2d63
gh-95986: Fix the example using match keyword (#95989)
180909 Oct 6, 2022
32cc2fd
gh-93738: Disallow pre-v3 syntax in the C domain (#97962)
AA-Turner Oct 6, 2022
bf234f5
GH-88050: fix race in closing subprocess pipe in asyncio (#97951)
kumaraditya303 Oct 6, 2022
af96109
gh-94808: Coverage: Test that maximum indentation level is handled (#…
mdboom Oct 6, 2022
149ed7f
gh-97943: PyFunction_GetAnnotations should return a borrowed referenc…
larryhastings Oct 6, 2022
ff0d621
gh-86482: Document assignment expression need for ()s (#23291)
terryjreedy Oct 6, 2022
d68c84f
gh-97781: Apply changes from importlib_metadata 5. (GH-97785)
jaraco Oct 6, 2022
aa83510
Add Pynche's move to the What's new in 3.11 (#97974)
warsaw Oct 6, 2022
a9a8f2e
gh-94590: add signatures to operator itemgetter, attrgetter, methodca…
eriknw Oct 6, 2022
22986c3
Docs: pin sphinx-lint (GH-97992)
hugovk Oct 6, 2022
9ffbad4
gh-97850: Remove the open issues section from the import reference (#…
brettcannon Oct 6, 2022
7064cd5
gh-65961: Do not rely solely on `__cached__` (GH-97990)
brettcannon Oct 6, 2022
0c970bd
fixes gh-96078: os.sched_yield release the GIL while calling sched_yi…
corona10 Oct 6, 2022
078e3ed
gh-97973: Return all necessary information from the tokenizer (GH-97984)
lysnikolaou Oct 6, 2022
cea3697
GH-97002: Prevent `_PyInterpreterFrame`s from backing more than one `…
brandtbucher Oct 6, 2022
f8e002a
bpo-38693: Use f-strings instead of str.format() within importlib (#1…
gpshead Oct 6, 2022
4f9e610
GH-91052: Add C API for watching dictionaries (GH-31787)
carljm Oct 7, 2022
395bc9a
bpo-35540 dataclasses.asdict now supports defaultdict fields (gh-32056)
kwsp Oct 7, 2022
7eba427
GH-90985: Revert "Deprecate passing a message into cancel()" (#97999)
gvanrossum Oct 7, 2022
e896751
Remove extra spaces in custom openSSL documentation. (#93568)
xiaochen7 Oct 7, 2022
04a52de
gh-97850: Remove deprecated functions from `importlib.utils` (#97898)
sobolevn Oct 7, 2022
bd03a34
Docs: Fix backtick errors found by sphinx-lint (#97998)
hugovk Oct 7, 2022
c729f2d
gh-82874: Convert remaining importlib format uses to f-str. (#98005)
gpshead Oct 7, 2022
2781fef
gh-86298: Ensure that __loader__ and __spec__.loader agree in warning…
warsaw Oct 7, 2022
6fe8abf
Doc: sphinx-lint finds two other default roles. (GH-98019)
JulienPalard Oct 7, 2022
a1ce6e1
Misc updates to the itertools recipes and tests (GH-98018)
rhettinger Oct 7, 2022
81021bf
gh-71316: Update dis documentation to include changes to jump argumen…
Christopher-Chianelli Oct 7, 2022
e75ada1
gh-97983: Revert "Lay the foundation for further work in asyncio.test…
arhadthedev Oct 7, 2022
43583f7
Fix memory leaks in test_capi (#98017)
carljm Oct 7, 2022
f100fc3
gh-94808: Cover `%p` in `PyUnicode_FromFormat` (#96677)
sobolevn Oct 7, 2022
603deaf
Add note on capture_output arg to subprocess.run() docstring (#98012)
akulakov Oct 7, 2022
bdf0373
Add more syslog tests (GH-97953)
serhiy-storchaka Oct 7, 2022
72bd5b9
gh-96415: Remove `types._cell_factory` from a module namespace (#96416)
sobolevn Oct 7, 2022
5df9a71
gh-64373: Convert `_functools` to Argument Clinic (#96640)
sobolevn Oct 7, 2022
ca73c85
Fix a mistake in isSet() deprecated message doc (#95720)
marcmonfort Oct 7, 2022
4ca8bb0
Make _symtable_entry.ste_type's comment consistent wit _Py_block_ty (…
zikcheng Oct 7, 2022
97ae2e9
gh-97669: Move difflib examples to Doc/includes/ (#97964)
vstinner Oct 7, 2022
1096dfa
gh-97955: Migrate `zoneinfo` to Argument Clinic (#97958)
sobolevn Oct 7, 2022
e7d9ea0
gh-64921: Clarify wording for open()'s newline arg (#96171)
slateny Oct 7, 2022
bd66031
gh-65496: Correct wording on csv's skipinitialspace argument (#96170)
slateny Oct 7, 2022
41b8172
GH-96073: Fix wild replacement in inspect.formatannotation (#96074)
iyume Oct 7, 2022
358e173
Add a warning message about PyOS_snprintf (#95993)
eric-wieser Oct 7, 2022
af02efe
gh-96959: Update HTTP links which are redirected to HTTPS (#98039)
180909 Oct 7, 2022
8bc17aa
gh-97956: Mention `generate_global_objects.py` in `AC How-To` (#97957)
sobolevn Oct 7, 2022
dc2c10e
gh-97923: Always run Ubuntu SSL tests with others in CI (#97940)
sobolevn Oct 7, 2022
9f5d3b5
gh-97646: Change `.js` and `.mjs` files mimetype to conform to RFC 92…
noamcohen97 Oct 7, 2022
0085f20
gh-73196: Add namespace/scope clarification for inheritance section (…
slateny Oct 7, 2022
53bc2db
gh-96265: Fix some formatting in faq/design.rst (#96924)
slateny Oct 7, 2022
8ce4739
gh-91708: Revert params note in urllib.parse.urlparse table (#96699)
slateny Oct 7, 2022
053555c
gh-96346: Use double caching for re._compile() (#96347)
serhiy-storchaka Oct 7, 2022
8cb415b
GH-88968: Reject socket that is already used as a transport (#98010)
gvanrossum Oct 7, 2022
7a39f02
gh-97997: Add col_offset field to tokenizer and use that for AST node…
lysnikolaou Oct 7, 2022
ca135e1
gh-92886: [clinic.py] raise exception on invalid input instead of ass…
iritkatriel Oct 7, 2022
875841a
gh-96073: fix backticks in NEWS entry (GH-98056)
JelleZijlstra Oct 7, 2022
b9bd94d
gh-96288: Add a sentence to `os.mkdir`'s docstring. (#96271)
hagai-helman Oct 7, 2022
864f9b9
gh-61105: Add default param, note on using cookiejar subclass (#95427)
slateny Oct 7, 2022
c437b6e
GH-83901: Improve Signature.bind error message for missing keyword-on…
RazerM Oct 7, 2022
5a71338
gh-90085: Remove vestigial -t and -c timeit options (#94941)
hauntsaninja Oct 7, 2022
6444029
gh-94808: Fix regex on exotic platforms (#98036)
JelleZijlstra Oct 7, 2022
c42b931
gh-57179: Add note on symlinks for os.walk (#94799)
slateny Oct 7, 2022
dcbae74
gh-92886: make test_coroutines pass with -O (assertions off) (GH-98060)
iritkatriel Oct 7, 2022
efbc783
gh-92886: make test_ast pass with -O (assertions off) (GH-98058)
iritkatriel Oct 7, 2022
6cd489d
GH-94182: Run the PidfdChildWatcher on the running loop (#94184)
graingert Oct 8, 2022
776f894
GH-98023: Change default child watcher to PidfdChildWatcher on suppor…
kumaraditya303 Oct 8, 2022
7bba92f
gh-91052: Add PyDict_Unwatch for unwatching a dictionary (#98055)
carljm Oct 8, 2022
e20a190
gh-97822: Fix http.server documentation reference to test() function …
Jason-Y-Z Oct 8, 2022
98b3aca
[doc] Fix broken links to C extensions accelerating stdlib modules (#…
partev Oct 8, 2022
637bf6a
gh-97913 Docs: Add walrus operator to the index (#97921)
hugovk Oct 8, 2022
d2abe90
Add `@ezio-melotti` as codeowner for `.github/`. (#98079)
ezio-melotti Oct 8, 2022
b31a5ee
GitHub Workflows security hardening (#96492)
sashashura Oct 8, 2022
65d36ba
gh-97922: Run the GC only on eval breaker (#97920)
pablogsal Oct 8, 2022
423a627
gh-68686: Retire eptag ptag scripts (#98064)
nanjekyejoannah Oct 8, 2022
1e204b1
gh-95011: Migrate syslog module to Argument Clinic (GH-95012)
noamcohen97 Oct 8, 2022
5723b53
Auto-cancel old builds when new commit pushed to branch (#98009)
hugovk Oct 8, 2022
8e3d19b
GH-94597: deprecate `SafeChildWatcher`, `FastChildWatcher` and `Multi…
kumaraditya303 Oct 8, 2022
095c522
Fix link to Lifecycle of a Pull Request in CONTRIBUTING (#98102)
jacobtylerwalls Oct 8, 2022
cef2db9
Minor edits to the Descriptor HowTo Guide (GH-24901)
geryogam Oct 9, 2022
4165f80
gh-97841: Add methoddef for _filters_mutated (gh-98115)
corona10 Oct 9, 2022
69c00cc
Update whatsnew instructions for GitHub (#98124)
carljm Oct 9, 2022
4bc4dfd
gh-56133: copyreg docs: Clarify function/constructor parameter (#95497)
slateny Oct 10, 2022
a15d14e
Fix types in buffer/memoryview docs (#98118)
da-woods Oct 10, 2022
ad713e8
gh-98083: Fix URLs in `README.rst` (#98082)
kwsp Oct 10, 2022
a87db59
bpo-43564: preserve original exception in args of FTP URLError (#24938)
carljm Oct 10, 2022
096a6a0
doc: remove a misleading statement. (GH-98093)
JulienPalard Oct 10, 2022
70a2708
gh-83940: os docs: Improve wording for getenv/getenvb (#98113)
slateny Oct 10, 2022
fbfd13b
gh-94808: Add coverage for bytesarray_setitem (#95802)
mdboom Oct 10, 2022
6d91946
gh-96821: Fix undefined behaviour in `audioop.c` (#96923)
matthiasgoergens Oct 10, 2022
9eda93b
Rename function event callbacks to match dict watchers
mpage Oct 10, 2022
ebf15c7
Support multiple watchers
mpage Oct 10, 2022
f357352
Rename function events to match naming scheme used by dict watchers
mpage Oct 10, 2022
08877e6
Add documentation for the new C-API functions and types
mpage Oct 11, 2022
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
38 changes: 38 additions & 0 deletions Include/cpython/funcobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,44 @@ PyAPI_DATA(PyTypeObject) PyStaticMethod_Type;
PyAPI_FUNC(PyObject *) PyClassMethod_New(PyObject *);
PyAPI_FUNC(PyObject *) PyStaticMethod_New(PyObject *);

#define FOREACH_FUNC_EVENT(V) \
V(CREATED) \
V(DESTROY) \
V(MODIFY_CODE) \
V(MODIFY_DEFAULTS) \
V(MODIFY_KWDEFAULTS)

typedef enum {
#define DEF_EVENT(EVENT) PYFUNC_EVENT_##EVENT,
FOREACH_FUNC_EVENT(DEF_EVENT)
#undef DEF_EVENT
} PyFunction_Event;

/*
* A callback that is invoked for different events in a function's lifecycle.
*
* The callback is invoked with a borrowed reference to func, after it is
* created and before it is modified or destroyed. The callback should not
* modify func.
*
* When a function's code object, defaults, or kwdefaults are modified the
* callback will be invoked with the respective event and new_value will
* contain a borrowed reference to the new value that is about to be stored in
* the function. Otherwise the third argument is NULL.
*/
typedef void(*PyFunction_EventCallback)(
PyFunction_Event event,
PyFunctionObject *func,
PyObject *new_value);

/*
* Set the callback that will be invoked for function lifecycle events.
*
* Pass NULL to clear the callback.
*/
PyAPI_FUNC(void) PyFunction_SetEventCallback(PyFunction_EventCallback callback);
Copy link
Member

Choose a reason for hiding this comment

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

I think we are going to want to support multiple watchers (up to 8, to match dict and type watchers), which means the API here should look like int PyFunction_AddWatcher which gives you back an id 0-7 (or -1 and sets an exception if there are no more watcher IDs available), and also PyFunction_ClearWatcher(int watcher_id) to clear a watcher. Can look at the dict watcher API in Include/cpython/dictobject.h and Python/dictobject.c to make it as parallel as we can. (Obviously with the difference that here we aren't doing per-function watching so there's no equivalent to PyDict_Watch API.)

PyAPI_FUNC(PyFunction_EventCallback) PyFunction_GetEventCallback(void);

#ifdef __cplusplus
}
#endif
Expand Down
44 changes: 44 additions & 0 deletions Lib/test/test_func_events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import unittest
from _testcapi import (
PYFUNC_EVENT_CREATED,
PYFUNC_EVENT_DESTROY,
PYFUNC_EVENT_MODIFY_CODE,
PYFUNC_EVENT_MODIFY_DEFAULTS,
PYFUNC_EVENT_MODIFY_KWDEFAULTS,
restore_func_event_callback,
set_func_event_callback,
)


class FuncEventsTest(unittest.TestCase):
def test_func_events_dispatched(self):
event = None
def handle_func_event(*args):
nonlocal event
event = args
set_func_event_callback(handle_func_event)

try:
def myfunc():
pass
self.assertEqual(event, (PYFUNC_EVENT_CREATED, myfunc, None))
myfunc_id = id(myfunc)

new_code = self.test_func_events_dispatched.__code__
myfunc.__code__ = new_code
self.assertEqual(event, (PYFUNC_EVENT_MODIFY_CODE, myfunc, new_code))

new_defaults = (123,)
myfunc.__defaults__ = new_defaults
self.assertEqual(event, (PYFUNC_EVENT_MODIFY_DEFAULTS, myfunc, new_defaults))

new_kwdefaults = {"self": 123}
myfunc.__kwdefaults__ = new_kwdefaults
self.assertEqual(event, (PYFUNC_EVENT_MODIFY_KWDEFAULTS, myfunc, new_kwdefaults))

# Clear event's reference to func
event = None
del myfunc
self.assertEqual(event, (PYFUNC_EVENT_DESTROY, myfunc_id, None))
finally:
restore_func_event_callback()
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/func_events.c

# Some testing modules MUST be built as shared libraries.
*shared*
Expand Down
100 changes: 100 additions & 0 deletions Modules/_testcapi/func_events.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#include "parts.h"

static PyObject *pyfunc_callback = NULL;
static PyFunction_EventCallback orig_callback = NULL;

static void
call_pyfunc_callback(PyFunction_Event event, PyFunctionObject *func, PyObject *new_value)
{
PyObject *event_obj = PyLong_FromLong(event);
if (event_obj == NULL) {
PyErr_Clear();
return;
}
if (new_value == NULL) {
new_value = Py_None;
}
Py_INCREF(new_value);
/* Don't expose a function that's about to be destroyed to managed code */
PyObject *func_or_id = (PyObject *) func;
if (event == PYFUNC_EVENT_DESTROY) {
func_or_id = PyLong_FromLong((long) func);
if (func_or_id == NULL) {
Py_DECREF(new_value);
Py_DECREF(event_obj);
return;
}
} else {
Py_INCREF(func);
}
PyObject *stack[] = {event_obj, func_or_id, new_value};
PyObject_Vectorcall(pyfunc_callback, stack, 3, NULL);
mpage marked this conversation as resolved.
Show resolved Hide resolved
Py_DECREF(new_value);
Py_DECREF(event_obj);
Py_DECREF(func_or_id);
}

static int
add_event(PyObject *module, const char *name, PyFunction_Event event)
{
PyObject *value = PyLong_FromLong(event);
if (value == NULL) {
return -1;
}
int ok = PyModule_AddObjectRef(module, name, value);
Py_DECREF(value);
return ok;
}

static PyObject *
set_func_event_callback(PyObject *self, PyObject *func)
{
if (!PyFunction_Check(func)) {
PyErr_SetString(PyExc_TypeError, "'func' must be a function");
return NULL;
}
if (pyfunc_callback != NULL) {
PyErr_SetString(PyExc_RuntimeError, "already set callback");
return NULL;
}
Py_INCREF(func);
pyfunc_callback = func;
orig_callback = PyFunction_GetEventCallback();
PyFunction_SetEventCallback(call_pyfunc_callback);
Py_RETURN_NONE;
}

static PyObject *
restore_func_event_callback() {
if (pyfunc_callback == NULL) {
PyErr_SetString(PyExc_RuntimeError, "nothing to restore");
return NULL;
}
PyFunction_SetEventCallback(orig_callback);
orig_callback = NULL;
Py_CLEAR(pyfunc_callback);
Py_RETURN_NONE;
}

static PyMethodDef TestMethods[] = {
{"set_func_event_callback", set_func_event_callback, METH_O},
{"restore_func_event_callback", restore_func_event_callback, METH_NOARGS},
{NULL},
};

int
_PyTestCapi_Init_FuncEvents(PyObject *m) {
if (PyModule_AddFunctions(m, TestMethods) < 0) {
return -1;
}

/* Expose each event as an attribute on the module */
#define ADD_EVENT(event) \
if (add_event(m, "PYFUNC_EVENT_" #event, PYFUNC_EVENT_##event)) { \
return -1; \
}
FOREACH_FUNC_EVENT(ADD_EVENT);
#undef ADD_EVENT

return 0;
}
1 change: 1 addition & 0 deletions Modules/_testcapi/parts.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
int _PyTestCapi_Init_Vectorcall(PyObject *module);
int _PyTestCapi_Init_Heaptype(PyObject *module);
int _PyTestCapi_Init_Unicode(PyObject *module);
int _PyTestCapi_Init_FuncEvents(PyObject *module);

#ifdef LIMITED_API_AVAILABLE
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
Expand Down
3 changes: 3 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -6578,6 +6578,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_Unicode(m) < 0) {
return NULL;
}
if (_PyTestCapi_Init_FuncEvents(m) < 0) {
return NULL;
}

#ifndef LIMITED_API_AVAILABLE
PyModule_AddObjectRef(m, "LIMITED_API_AVAILABLE", Py_False);
Expand Down
29 changes: 29 additions & 0 deletions Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,29 @@

static uint32_t next_func_version = 1;

static PyFunction_EventCallback func_event_callback = NULL;
mpage marked this conversation as resolved.
Show resolved Hide resolved

static void
handle_func_event(PyFunction_Event event, PyFunctionObject *func, PyObject *new_value)
{
if (func_event_callback == NULL) {
return;
}
func_event_callback(event, func, new_value);
}

void
PyFunction_SetEventCallback(PyFunction_EventCallback callback)
{
func_event_callback = callback;
}

PyFunction_EventCallback
PyFunction_GetEventCallback()
{
return func_event_callback;
}

PyFunctionObject *
_PyFunction_FromConstructor(PyFrameConstructor *constr)
{
Expand Down Expand Up @@ -40,6 +63,7 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr)
op->vectorcall = _PyFunction_Vectorcall;
op->func_version = 0;
_PyObject_GC_TRACK(op);
handle_func_event(PYFUNC_EVENT_CREATED, op, NULL);
return op;
}

Expand Down Expand Up @@ -116,6 +140,7 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname
op->vectorcall = _PyFunction_Vectorcall;
op->func_version = 0;
_PyObject_GC_TRACK(op);
handle_func_event(PYFUNC_EVENT_CREATED, op, NULL);
return (PyObject *)op;

error:
Expand Down Expand Up @@ -402,6 +427,7 @@ func_set_code(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignored))
nclosure, nfree);
return -1;
}
handle_func_event(PYFUNC_EVENT_MODIFY_CODE, op, value);
op->func_version = 0;
Py_INCREF(value);
Py_XSETREF(op->func_code, value);
Expand Down Expand Up @@ -487,6 +513,7 @@ func_set_defaults(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignored
return -1;
}

handle_func_event(PYFUNC_EVENT_MODIFY_DEFAULTS, op, value);
op->func_version = 0;
Py_XINCREF(value);
Py_XSETREF(op->func_defaults, value);
Expand Down Expand Up @@ -529,6 +556,7 @@ func_set_kwdefaults(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignor
return -1;
}

handle_func_event(PYFUNC_EVENT_MODIFY_KWDEFAULTS, op, value);
op->func_version = 0;
Py_XINCREF(value);
Py_XSETREF(op->func_kwdefaults, value);
Expand Down Expand Up @@ -712,6 +740,7 @@ func_clear(PyFunctionObject *op)
static void
func_dealloc(PyFunctionObject *op)
{
handle_func_event(PYFUNC_EVENT_DESTROY, op, NULL);
_PyObject_GC_UNTRACK(op);
if (op->func_weakreflist != NULL) {
PyObject_ClearWeakRefs((PyObject *) op);
Expand Down