Skip to content

Commit a12a031

Browse files
authored
Merge branch 'main' into 3.14-zstd-python-code
2 parents a99c5dd + ea59873 commit a12a031

File tree

10 files changed

+156
-27
lines changed

10 files changed

+156
-27
lines changed

Include/internal/pycore_crossinterp.h

+7
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ PyAPI_FUNC(int) _PyMarshal_GetXIData(
185185
PyObject *,
186186
_PyXIData_t *);
187187

188+
// _PyObject_GetXIData() for code objects
189+
PyAPI_FUNC(PyObject *) _PyCode_FromXIData(_PyXIData_t *);
190+
PyAPI_FUNC(int) _PyCode_GetXIData(
191+
PyThreadState *,
192+
PyObject *,
193+
_PyXIData_t *);
194+
188195

189196
/* using cross-interpreter data */
190197

Lib/asyncio/__main__.py

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ def interrupt(self) -> None:
146146
parser = argparse.ArgumentParser(
147147
prog="python3 -m asyncio",
148148
description="Interactive asyncio shell and CLI tools",
149+
color=True,
149150
)
150151
subparsers = parser.add_subparsers(help="sub-commands", dest="command")
151152
ps = subparsers.add_parser(

Lib/asyncio/tools.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,21 @@ class CycleFoundException(Exception):
2121

2222

2323
# ─── indexing helpers ───────────────────────────────────────────
24+
def _format_stack_entry(elem: tuple[str, str, int] | str) -> str:
25+
if isinstance(elem, tuple):
26+
fqname, path, line_no = elem
27+
return f"{fqname} {path}:{line_no}"
28+
29+
return elem
30+
31+
2432
def _index(result):
2533
id2name, awaits = {}, []
2634
for _thr_id, tasks in result:
2735
for tid, tname, awaited in tasks:
2836
id2name[tid] = tname
2937
for stack, parent_id in awaited:
30-
stack = [elem[0] if isinstance(elem, tuple) else elem for elem in stack]
38+
stack = [_format_stack_entry(elem) for elem in stack]
3139
awaits.append((parent_id, stack, tid))
3240
return id2name, awaits
3341

@@ -106,7 +114,7 @@ def dfs(v):
106114
# ─── PRINT TREE FUNCTION ───────────────────────────────────────
107115
def build_async_tree(result, task_emoji="(T)", cor_emoji=""):
108116
"""
109-
Build a list of strings for pretty-print a async call tree.
117+
Build a list of strings for pretty-print an async call tree.
110118
111119
The call tree is produced by `get_all_async_stacks()`, prefixing tasks
112120
with `task_emoji` and coroutine frames with `cor_emoji`.
@@ -169,7 +177,7 @@ def build_task_table(result):
169177
return table
170178

171179
def _print_cycle_exception(exception: CycleFoundException):
172-
print("ERROR: await-graph contains cycles cannot print a tree!", file=sys.stderr)
180+
print("ERROR: await-graph contains cycles - cannot print a tree!", file=sys.stderr)
173181
print("", file=sys.stderr)
174182
for c in exception.cycles:
175183
inames = " → ".join(exception.id2name.get(tid, hex(tid)) for tid in c)

Lib/test/_code_definitions.py

+14
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ def spam_with_globals_and_builtins():
2929
print(res)
3030

3131

32+
def spam_args_attrs_and_builtins(a, b, /, c, d, *args, e, f, **kwargs):
33+
if args.__len__() > 2:
34+
return None
35+
return a, b, c, d, e, f, args, kwargs
36+
37+
3238
def spam_returns_arg(x):
3339
return x
3440

@@ -46,6 +52,10 @@ def eggs():
4652
eggs()
4753

4854

55+
def spam_annotated(a: int, b: str, c: object) -> tuple:
56+
return a, b, c
57+
58+
4959
def spam_full(a, b, /, c, d:int=1, *args, e, f:object=None, **kwargs) -> tuple:
5060
# arg defaults, kwarg defaults
5161
# annotations
@@ -134,9 +144,11 @@ def ham_C_closure(z):
134144
spam_minimal,
135145
spam_with_builtins,
136146
spam_with_globals_and_builtins,
147+
spam_args_attrs_and_builtins,
137148
spam_returns_arg,
138149
spam_with_inner_not_closure,
139150
spam_with_inner_closure,
151+
spam_annotated,
140152
spam_full,
141153
spam,
142154
# outer func
@@ -170,7 +182,9 @@ def ham_C_closure(z):
170182
spam,
171183
spam_minimal,
172184
spam_with_builtins,
185+
spam_args_attrs_and_builtins,
173186
spam_returns_arg,
187+
spam_annotated,
174188
spam_with_inner_not_closure,
175189
spam_with_inner_closure,
176190
spam_N,

Lib/test/test_asyncio/test_tools.py

+24-17
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,18 @@
1818
3,
1919
"timer",
2020
[
21-
[["awaiter3", "awaiter2", "awaiter"], 4],
22-
[["awaiter1_3", "awaiter1_2", "awaiter1"], 5],
23-
[["awaiter1_3", "awaiter1_2", "awaiter1"], 6],
24-
[["awaiter3", "awaiter2", "awaiter"], 7],
21+
[[("awaiter3", "/path/to/app.py", 130),
22+
("awaiter2", "/path/to/app.py", 120),
23+
("awaiter", "/path/to/app.py", 110)], 4],
24+
[[("awaiterB3", "/path/to/app.py", 190),
25+
("awaiterB2", "/path/to/app.py", 180),
26+
("awaiterB", "/path/to/app.py", 170)], 5],
27+
[[("awaiterB3", "/path/to/app.py", 190),
28+
("awaiterB2", "/path/to/app.py", 180),
29+
("awaiterB", "/path/to/app.py", 170)], 6],
30+
[[("awaiter3", "/path/to/app.py", 130),
31+
("awaiter2", "/path/to/app.py", 120),
32+
("awaiter", "/path/to/app.py", 110)], 7],
2533
],
2634
),
2735
(
@@ -91,29 +99,29 @@
9199
" │ └── __aexit__",
92100
" │ └── _aexit",
93101
" │ ├── (T) child1_1",
94-
" │ │ └── awaiter",
95-
" │ │ └── awaiter2",
96-
" │ │ └── awaiter3",
102+
" │ │ └── awaiter /path/to/app.py:110",
103+
" │ │ └── awaiter2 /path/to/app.py:120",
104+
" │ │ └── awaiter3 /path/to/app.py:130",
97105
" │ │ └── (T) timer",
98106
" │ └── (T) child2_1",
99-
" │ └── awaiter1",
100-
" │ └── awaiter1_2",
101-
" │ └── awaiter1_3",
107+
" │ └── awaiterB /path/to/app.py:170",
108+
" │ └── awaiterB2 /path/to/app.py:180",
109+
" │ └── awaiterB3 /path/to/app.py:190",
102110
" │ └── (T) timer",
103111
" └── (T) root2",
104112
" └── bloch",
105113
" └── blocho_caller",
106114
" └── __aexit__",
107115
" └── _aexit",
108116
" ├── (T) child1_2",
109-
" │ └── awaiter",
110-
" │ └── awaiter2",
111-
" │ └── awaiter3",
117+
" │ └── awaiter /path/to/app.py:110",
118+
" │ └── awaiter2 /path/to/app.py:120",
119+
" │ └── awaiter3 /path/to/app.py:130",
112120
" │ └── (T) timer",
113121
" └── (T) child2_2",
114-
" └── awaiter1",
115-
" └── awaiter1_2",
116-
" └── awaiter1_3",
122+
" └── awaiterB /path/to/app.py:170",
123+
" └── awaiterB2 /path/to/app.py:180",
124+
" └── awaiterB3 /path/to/app.py:190",
117125
" └── (T) timer",
118126
]
119127
]
@@ -589,7 +597,6 @@
589597

590598

591599
class TestAsyncioToolsTree(unittest.TestCase):
592-
593600
def test_asyncio_utils(self):
594601
for input_, tree in TEST_INPUTS_TREE:
595602
with self.subTest(input_):

Lib/test/test_code.py

+26
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,16 @@ def test_local_kinds(self):
687687
'checks': CO_FAST_LOCAL,
688688
'res': CO_FAST_LOCAL,
689689
},
690+
defs.spam_args_attrs_and_builtins: {
691+
'a': POSONLY,
692+
'b': POSONLY,
693+
'c': POSORKW,
694+
'd': POSORKW,
695+
'e': KWONLY,
696+
'f': KWONLY,
697+
'args': VARARGS,
698+
'kwargs': VARKWARGS,
699+
},
690700
defs.spam_returns_arg: {
691701
'x': POSORKW,
692702
},
@@ -697,6 +707,11 @@ def test_local_kinds(self):
697707
'x': CO_FAST_CELL,
698708
'eggs': CO_FAST_LOCAL,
699709
},
710+
defs.spam_annotated: {
711+
'a': POSORKW,
712+
'b': POSORKW,
713+
'c': POSORKW,
714+
},
700715
defs.spam_full: {
701716
'a': POSONLY,
702717
'b': POSONLY,
@@ -892,6 +907,14 @@ def new_var_counts(*,
892907
purelocals=5,
893908
globalvars=6,
894909
),
910+
defs.spam_args_attrs_and_builtins: new_var_counts(
911+
posonly=2,
912+
posorkw=2,
913+
kwonly=2,
914+
varargs=1,
915+
varkwargs=1,
916+
attrs=1,
917+
),
895918
defs.spam_returns_arg: new_var_counts(
896919
posorkw=1,
897920
),
@@ -902,6 +925,9 @@ def new_var_counts(*,
902925
othercells=1,
903926
purelocals=1,
904927
),
928+
defs.spam_annotated: new_var_counts(
929+
posorkw=3,
930+
),
905931
defs.spam_full: new_var_counts(
906932
posonly=2,
907933
posorkw=2,

Lib/test/test_crossinterp.py

+40-6
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,39 @@ def test_user_exception(self):
725725
])
726726

727727

728+
class CodeTests(_GetXIDataTests):
729+
730+
MODE = 'code'
731+
732+
def test_function_code(self):
733+
self.assert_roundtrip_equal_not_identical([
734+
*(f.__code__ for f in defs.FUNCTIONS),
735+
*(f.__code__ for f in defs.FUNCTION_LIKE),
736+
])
737+
738+
def test_functions(self):
739+
self.assert_not_shareable([
740+
*defs.FUNCTIONS,
741+
*defs.FUNCTION_LIKE,
742+
])
743+
744+
def test_other_objects(self):
745+
self.assert_not_shareable([
746+
None,
747+
True,
748+
False,
749+
Ellipsis,
750+
NotImplemented,
751+
9999,
752+
'spam',
753+
b'spam',
754+
(),
755+
[],
756+
{},
757+
object(),
758+
])
759+
760+
728761
class ShareableTypeTests(_GetXIDataTests):
729762

730763
MODE = 'xidata'
@@ -817,6 +850,13 @@ def test_object(self):
817850
object(),
818851
])
819852

853+
def test_code(self):
854+
# types.CodeType
855+
self.assert_not_shareable([
856+
*(f.__code__ for f in defs.FUNCTIONS),
857+
*(f.__code__ for f in defs.FUNCTION_LIKE),
858+
])
859+
820860
def test_function_object(self):
821861
for func in defs.FUNCTIONS:
822862
assert type(func) is types.FunctionType, func
@@ -935,12 +975,6 @@ def test_builtin_objects(self):
935975
self.assert_not_shareable([
936976
types.MappingProxyType({}),
937977
types.SimpleNamespace(),
938-
# types.CodeType
939-
defs.spam_minimal.__code__,
940-
defs.spam_full.__code__,
941-
defs.spam_CC.__code__,
942-
defs.eggs_closure_C.__code__,
943-
defs.ham_C_closure.__code__,
944978
# types.CellType
945979
types.CellType(),
946980
# types.FrameType

Modules/_testinternalcapi.c

+5
Original file line numberDiff line numberDiff line change
@@ -1984,6 +1984,11 @@ get_crossinterp_data(PyObject *self, PyObject *args, PyObject *kwargs)
19841984
goto error;
19851985
}
19861986
}
1987+
else if (strcmp(mode, "code") == 0) {
1988+
if (_PyCode_GetXIData(tstate, obj, xidata) != 0) {
1989+
goto error;
1990+
}
1991+
}
19871992
else {
19881993
PyErr_Format(PyExc_ValueError, "unsupported mode %R", modeobj);
19891994
goto error;

Python/crossinterp.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -512,9 +512,12 @@ _unpickle_context_set_module(struct _unpickle_context *ctx,
512512
struct sync_module_result res = {0};
513513
struct sync_module_result *cached = NULL;
514514
const char *filename = NULL;
515+
const char *run_modname = modname;
515516
if (strcmp(modname, "__main__") == 0) {
516517
cached = &ctx->main.cached;
517518
filename = ctx->main.filename;
519+
// We don't want to trigger "if __name__ == '__main__':".
520+
run_modname = "<fake __main__>";
518521
}
519522
else {
520523
res.failed = PyExc_NotImplementedError;
@@ -533,7 +536,7 @@ _unpickle_context_set_module(struct _unpickle_context *ctx,
533536
res.failed = PyExc_NotImplementedError;
534537
goto finally;
535538
}
536-
res.loaded = runpy_run_path(filename, modname);
539+
res.loaded = runpy_run_path(filename, run_modname);
537540
if (res.loaded == NULL) {
538541
Py_CLEAR(res.module);
539542
res.failed = _PyErr_GetRaisedException(ctx->tstate);

Python/crossinterp_data_lookup.h

+24
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,30 @@ _tuple_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
654654
return -1;
655655
}
656656

657+
// code
658+
659+
PyObject *
660+
_PyCode_FromXIData(_PyXIData_t *xidata)
661+
{
662+
return _PyMarshal_ReadObjectFromXIData(xidata);
663+
}
664+
665+
int
666+
_PyCode_GetXIData(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
667+
{
668+
if (!PyCode_Check(obj)) {
669+
_PyXIData_FormatNotShareableError(tstate, "expected code, got %R", obj);
670+
return -1;
671+
}
672+
if (_PyMarshal_GetXIData(tstate, obj, xidata) < 0) {
673+
return -1;
674+
}
675+
assert(_PyXIData_CHECK_NEW_OBJECT(xidata, _PyMarshal_ReadObjectFromXIData));
676+
_PyXIData_SET_NEW_OBJECT(xidata, _PyCode_FromXIData);
677+
return 0;
678+
}
679+
680+
657681
// registration
658682

659683
static void

0 commit comments

Comments
 (0)