Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
96e1e78
Add the multiple interpreters howto doc.
ericsnowcurrently Jul 16, 2024
e5e980e
Drop extraneous changes.
ericsnowcurrently Jun 19, 2025
2fcaedd
Finish howto doc, minus recipes.
ericsnowcurrently Jun 20, 2025
aea8278
Add a TODO list.
ericsnowcurrently Jun 26, 2025
4bb1e2f
Drop the execcomponents ref.
ericsnowcurrently Jun 30, 2025
d1ab402
Fix a ref.
ericsnowcurrently Jun 30, 2025
1c2d40f
Drop the examples section for now.
ericsnowcurrently Jun 30, 2025
ade2ce3
Fix typos.
ericsnowcurrently Jul 1, 2025
40f50e7
Add a misc section to the tutorial.
ericsnowcurrently Jul 1, 2025
0c3105b
Clarify about prepare_main().
ericsnowcurrently Jul 1, 2025
6028349
Fix a pseudo-ref.
ericsnowcurrently Jul 1, 2025
d7a7cf0
Fix the examples.
ericsnowcurrently Jul 1, 2025
fb13944
Tweak one example.
ericsnowcurrently Jul 1, 2025
72e46fb
Add a ref.
ericsnowcurrently Jul 2, 2025
675246d
Clarify about Interpreter.exec().
ericsnowcurrently Jul 3, 2025
22e3ee0
Clarify about calling different kinds of function.
ericsnowcurrently Jul 3, 2025
f7661da
Clarify a note.
ericsnowcurrently Jul 3, 2025
28f0218
Add a caveat about -c and the REPL.
ericsnowcurrently Jul 3, 2025
1b79755
Add a pro tip.
ericsnowcurrently Jul 3, 2025
b75c380
Add run-examples.py and fix some of the examples.
ericsnowcurrently Sep 23, 2025
35101a0
Do not try reformatting run-examples.py.
ericsnowcurrently Sep 24, 2025
ddae34c
Fix some of the examples.
ericsnowcurrently Sep 24, 2025
652abd3
Do not lint run-examples.py.
ericsnowcurrently Sep 24, 2025
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
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ repos:
name: Run Ruff (lint) on Doc/
args: [--exit-non-zero-on-fix]
files: ^Doc/
exclude: ^Doc/howto/multiple-interpreters-run-examples.py
Copy link
Member

Choose a reason for hiding this comment

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

Let's remove these exclusions and do the fixes?

Suggested change
exclude: ^Doc/howto/multiple-interpreters-run-examples.py

- id: ruff
name: Run Ruff (lint) on Lib/test/
args: [--exit-non-zero-on-fix]
Expand All @@ -22,6 +23,7 @@ repos:
name: Run Ruff (format) on Doc/
args: [--check]
files: ^Doc/
exclude: ^Doc/howto/multiple-interpreters-run-examples.py
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
exclude: ^Doc/howto/multiple-interpreters-run-examples.py

- id: ruff-format
name: Run Ruff (format) on Tools/build/check_warnings.py
args: [--check, --config=Tools/build/.ruff.toml]
Expand Down
2 changes: 2 additions & 0 deletions Doc/howto/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Python Library Reference.
isolating-extensions.rst
timerfd.rst
mro.rst
multiple-interpreters.rst
free-threading-python.rst
free-threading-extensions.rst
remote_debugging.rst
Expand All @@ -57,6 +58,7 @@ Advanced development:
* :ref:`freethreading-python-howto`
* :ref:`freethreading-extensions-howto`
* :ref:`isolating-extensions-howto`
* :ref:`multiple-interpreters-howto`
* :ref:`python_2.3_mro`
* :ref:`socket-howto`
* :ref:`timerfd-howto`
Expand Down
177 changes: 177 additions & 0 deletions Doc/howto/multiple-interpreters-run-examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import os
import os.path
import shutil
import subprocess
import sys
import tempfile
from textwrap import dedent
import traceback


HOWTO_DIR = os.path.dirname(__file__)
HOWTO_FILE = os.path.join(HOWTO_DIR, 'multiple-interpreters.rst')

VERBOSITY = 3


def get_examples(lines):
examples = {}
current = None
expected = None
start = None
for lno, line in enumerate(lines, 1):
if current is None:
if line.endswith('::'):
# It *might* be an example.
current = []
else:
if not current:
if line.strip():
if line == ' from concurrent import interpreters':
# It *is* an example.
current.append(line)
expected = []
start = lno
else:
# It wasn't actually an example.
current = None
elif not line.strip() or line.startswith(' '):
# The example continues.
current.append(line)
before, sep, after = line.partition('# prints: ')
if sep:
assert not before.strip(), before
expected.append(after)
else:
# We've reached the end of the example.
assert not current[-1].strip(), current
example = dedent(os.linesep.join(current))
expected = ''.join(f'{l}{os.linesep}' for l in expected)
examples[start] = (example, expected)
current = expected = start = None

if line.endswith('::'):
# It *might* be an example.
current = []
if current:
if current[-1].strip():
current.append('')
example = dedent(os.linesep.join(current))
expected = ''.join(f'{l}{os.linesep}' for l in expected)
examples[start] = (example, expected)
current = expected = start = None
Copy link
Member

Choose a reason for hiding this comment

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

This line is unused:

Suggested change
current = expected = start = None

return examples


def write_example(examplesdir, name, text):
filename = os.path.join(examplesdir, f'example-{name}.py')
Comment on lines +66 to +67
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
def write_example(examplesdir, name, text):
filename = os.path.join(examplesdir, f'example-{name}.py')
def write_example(examples_dir, name, text):
filename = os.path.join(examples_dir, f'example-{name}.py')

with open(filename, 'w') as outfile:
outfile.write(text)
return filename, name, text


def run_example(filename, expected):
if isinstance(expected, str):
rc = 0
stdout = expected
stderr = ''
else:
rc, stdout, stderr = expected

proc = subprocess.run(
[sys.executable, filename],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
Comment on lines +83 to +84
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
capture_output=True,

text=True,
cwd=os.path.dirname(filename),
)
if proc.returncode != rc:
if proc.returncode != 0 and proc.stderr:
print(proc.stderr)
raise AssertionError((proc.returncode, rc))
assert proc.stdout == stdout, (proc.stdout, stdout)
assert proc.stderr == stderr, (proc.stderr, stderr)


#######################################
# the script

def parse_args(argv=sys.argv[1:], prog=sys.argv[0]):
import argparse
parser = argparse.ArgumentParser(prog=prog)

parser.add_argument('--dry-run', dest='dryrun', action='store_true')

parser.add_argument('-v', '--verbose', action='count', default=0)
parser.add_argument('-q', '--quiet', action='count', default=0)

parser.add_argument('requested', nargs='*')

args = parser.parse_args(argv)
ns = vars(args)

args.verbosity = max(0, VERBOSITY + ns.pop('verbose') - ns.pop('quiet'))

requested = []
for req in args.requested:
for i in req.replace(',', ' ').split():
requested.append(int(i))
args.requested = requested or None

return ns


def main(requested=None, *, verbosity=VERBOSITY, dryrun=False):
with open(HOWTO_FILE) as infile:
examples = get_examples(l.rstrip(os.linesep) for l in infile)
examplesdir = tempfile.mkdtemp(prefix='multi-interp-howto-')
try:
if requested:
requested = set(requested)
_req = f', {len(requested)} requested'
else:
_req = ''
summary = f'({len(examples)} found{_req})'
print('#', summary)

failed = []
for i, (lno, (text, expected)) in enumerate(examples.items(), 1):
if requested and i not in requested:
continue
name = 'multiinterp-{i}'
print()
print('#'*60)
print(f'# example {i} ({os.path.relpath(HOWTO_FILE)}:{lno})')
print('#'*60)
print()
if verbosity > VERBOSITY or (verbosity == VERBOSITY and dryrun):
print(text)
print('----')
if expected.rstrip():
print(expected.rstrip(os.linesep))
print('----')
if dryrun:
continue
filename, _, _ = write_example(examplesdir, name, text)
try:
run_example(filename, expected)
except Exception:
traceback.print_exc()
failed.append(str(i))

print()
if failed:
print(f'{len(failed)} failed: {",".join(failed)}')
else:
print(f'all succeeded')
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
print(f'all succeeded')
print('all succeeded')

print(summary)

return len(failed)
finally:
shutil.rmtree(examplesdir)


if __name__ == '__main__':
kwargs = parse_args()
ec = main(**kwargs)
sys.exit(ec)
Loading
Loading