Skip to content
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
54 changes: 39 additions & 15 deletions Lib/asyncio/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
import types
import warnings

from _colorize import get_theme
from _pyrepl.console import InteractiveColoredConsole
try:
from _colorize import get_theme
from _pyrepl.console import InteractiveColoredConsole as InteractiveConsole
except ModuleNotFoundError:
from code import InteractiveConsole

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Define the failure sentinel for the basic-console fallback

When _pyrepl.console is unavailable and this fallback is selected, any evaluated statement that raises reaches AsyncIOInteractiveConsole.runcode(), which returns self.STATEMENT_FAILED; code.InteractiveConsole does not define that attribute (it exists only on InteractiveColoredConsole). The resulting AttributeError escapes the basic console's interact() loop and REPLThread stops the event loop, so the first normal user error terminates the fallback REPL. Define the sentinel on AsyncIOInteractiveConsole or avoid returning it on this path.

Useful? React with 👍 / 👎.


from . import futures


class AsyncIOInteractiveConsole(InteractiveColoredConsole):
class AsyncIOInteractiveConsole(InteractiveConsole):

def __init__(self, locals, loop):
super().__init__(locals, filename="<stdin>")
Expand Down Expand Up @@ -96,7 +99,7 @@ def run(self):

console.write(banner)

if not sys.flags.isolated and (startup_path := os.getenv("PYTHONSTARTUP")):
if not sys.flags.ignore_environment and (startup_path := os.getenv("PYTHONSTARTUP")):
sys.audit("cpython.run_startup", startup_path)
try:
import tokenize
Expand All @@ -112,13 +115,17 @@ def run(self):
if CAN_USE_PYREPL:
theme = get_theme().syntax
ps1 = f"{theme.prompt}{ps1}{theme.reset}"
console.write(f"{ps1}import asyncio\n")
import_line = f'{theme.keyword}import{theme.reset} asyncio'
else:
import_line = "import asyncio"
console.write(f"{ps1}{import_line}\n")

if CAN_USE_PYREPL:
from _pyrepl.simple_interact import (
run_multiline_interactive_console,
)
try:
sys.ps1 = ps1
run_multiline_interactive_console(console)
except SystemExit:
# expected via the `exit` and `quit` commands
Expand Down Expand Up @@ -152,24 +159,35 @@ def interrupt(self) -> None:
parser = argparse.ArgumentParser(
prog="python3 -m asyncio",
description="Interactive asyncio shell and CLI tools",
color=True,
)
subparsers = parser.add_subparsers(help="sub-commands", dest="command")
ps = subparsers.add_parser(
"ps", help="Display a table of all pending tasks in a process"
)
ps.add_argument("pid", type=int, help="Process ID to inspect")
ps.add_argument(
"--retries",
type=int,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject negative retry counts before invoking the tools

When either new --retries option is given a negative integer (for example, ps 42 --retries -1), argparse accepts it because type=int; asyncio.tools._get_awaited_by_tasks() then iterates range(retries + 1) zero times and returns None, after which the table/tree builder raises TypeError and the CLI emits a traceback. The generated help advertises --retries RETRIES as “Number of retries on transient attach errors”, so use a non-negative parser validator while preserving 0 as one initial attempt.

Useful? React with 👍 / 👎.

default=3,
help="Number of retries on transient attach errors",
)
pstree = subparsers.add_parser(
"pstree", help="Display a tree of all pending tasks in a process"
)
pstree.add_argument("pid", type=int, help="Process ID to inspect")
pstree.add_argument(
"--retries",
type=int,
default=3,
help="Number of retries on transient attach errors",
)
args = parser.parse_args()
match args.command:
case "ps":
asyncio.tools.display_awaited_by_tasks_table(args.pid)
asyncio.tools.display_awaited_by_tasks_table(args.pid, retries=args.retries)
sys.exit(0)
case "pstree":
asyncio.tools.display_awaited_by_tasks_tree(args.pid)
asyncio.tools.display_awaited_by_tasks_tree(args.pid, retries=args.retries)
sys.exit(0)
case None:
pass # continue to the interactive shell
Expand All @@ -182,20 +200,26 @@ def interrupt(self) -> None:

sys.audit("cpython.run_stdin")

if os.getenv('PYTHON_BASIC_REPL'):
if not sys.flags.ignore_environment and os.getenv('PYTHON_BASIC_REPL'):
CAN_USE_PYREPL = False
else:
from _pyrepl.main import CAN_USE_PYREPL
try:
from _pyrepl.main import CAN_USE_PYREPL
except ModuleNotFoundError:
CAN_USE_PYREPL = False

return_code = 0
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

repl_locals = {'asyncio': asyncio}
for key in {'__name__', '__package__',
'__loader__', '__spec__',
'__builtins__', '__file__'}:
repl_locals[key] = locals()[key]
repl_locals = {
'asyncio': asyncio,
'__name__': __name__,
'__package__': None,
'__loader__': __loader__,
'__spec__': None,
'__builtins__': __builtins__,
}

console = AsyncIOInteractiveConsole(repl_locals, loop)

Expand Down
8 changes: 7 additions & 1 deletion Lib/ctypes/macholib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@
And also Apple's documentation.
"""

__version__ = '1.0'
def __getattr__(name):
if name == "__version__":
from warnings import _deprecated

_deprecated("__version__", remove=(3, 20))
return "1.0" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
Loading
Loading