1
0
Fork 0

Adding upstream version 3.0.30.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-04-16 08:48:42 +02:00
parent b6d29f411e
commit 635a85607c
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
35 changed files with 212 additions and 195 deletions

View file

@ -10,29 +10,32 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v2
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Dependencies
- name: Type Checking
run: |
sudo apt remove python3-pip
python -m pip install --upgrade pip
python -m pip install . ruff mypy pytest readme_renderer
pip list
- name: Type Checker
uvx --with . mypy src/ptpython/
uvx --with . mypy examples/
- name: Code formatting
if: ${{ matrix.python-version == '3.13' }}
run: |
mypy ptpython
ruff check .
ruff format --check .
- name: Run Tests
uvx ruff check .
uvx ruff format --check .
- name: Typos
if: ${{ matrix.python-version == '3.13' }}
run: |
./tests/run_tests.py
uvx typos .
- name: Unit test
run: |
uvx --with . pytest tests/
- name: Validate README.md
if: ${{ matrix.python-version == '3.13' }}
# Ensure that the README renders correctly (required for uploading to PyPI).
run: |
uv pip install readme_renderer
python -m readme_renderer README.rst > /dev/null

View file

@ -1,6 +1,17 @@
CHANGELOG
=========
3.0.30: 2025-04-15
------------------
New features:
- Show exception cause/context when printing chained exceptions.
- Reworked project layout and use pyproject.toml instead of setup.py.
Breaking changes:
- Drop Python 3.7 support.
3.0.29: 2024-07-22
------------------

View file

@ -25,7 +25,7 @@ async def print_counter() -> None:
Coroutine that prints counters and saves it in a global variable.
"""
while True:
print("Counter: %i" % counter[0])
print(f"Counter: {counter[0]}")
counter[0] += 1
await asyncio.sleep(3)

View file

@ -44,8 +44,8 @@ async def main(port: int = 8222) -> None:
def create_server() -> MySSHServer:
return MySSHServer(lambda: environ)
print("Listening on :%i" % port)
print('To connect, do "ssh localhost -p %i"' % port)
print(f"Listening on: {port}")
print(f'To connect, do "ssh localhost -p {port}"')
await asyncssh.create_server(
create_server, "", port, server_host_keys=["/etc/ssh/ssh_host_dsa_key"]

View file

@ -6,6 +6,8 @@ Thanks to Vincent Michel for this!
https://gist.github.com/vxgmichel/7685685b3e5ead04ada4a3ba75a48eef
"""
from __future__ import annotations
import asyncio
import pathlib
@ -15,7 +17,7 @@ from prompt_toolkit.contrib.ssh.server import (
PromptToolkitSSHServer,
PromptToolkitSSHSession,
)
from prompt_toolkit.contrib.telnet.server import TelnetServer
from prompt_toolkit.contrib.telnet.server import TelnetConnection, TelnetServer
from ptpython.repl import embed
@ -28,7 +30,7 @@ def ensure_key(filename: str = "ssh_host_key") -> str:
return str(path)
async def interact(connection: PromptToolkitSSHSession) -> None:
async def interact(connection: PromptToolkitSSHSession | TelnetConnection) -> None:
global_dict = {**globals(), "print": print_formatted_text}
await embed(return_asyncio_coroutine=True, globals=global_dict)

View file

@ -1,6 +0,0 @@
[mypy]
ignore_missing_imports = True
no_implicit_optional = True
platform = win32
strict_equality = True
strict_optional = True

View file

@ -1,3 +1,55 @@
[project]
name = "ptpython"
version = "3.0.30"
description = "Python REPL build on top of prompt_toolkit"
readme = "README.rst"
authors = [{ name = "Jonathan Slenders" }]
classifiers = [
"License :: OSI Approved :: BSD License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python",
]
requires-python = ">=3.8"
dependencies = [
"appdirs",
"jedi>=0.16.0",
# Use prompt_toolkit 3.0.43, because of `OneStyleAndTextTuple` import.
"prompt_toolkit>=3.0.43,<3.1.0",
"pygments",
]
[project.urls]
Homepage = "https://github.com/prompt-toolkit/ptpython"
Changelog = "https://github.com/prompt-toolkit/ptpython/blob/master/CHANGELOG"
"Bug Tracker" = "https://github.com/prompt-toolkit/ptpython/issues"
"Source Code" = "https://github.com/prompt-toolkit/ptpython"
[project.scripts]
ptpython = "ptpython.entry_points.run_ptpython:run"
ptipython = "ptpython.entry_points.run_ptipython:run"
[project.optional-dependencies]
ptipython = ["ipython"] # For ptipython, we need to have IPython
[tool.mypy]
ignore_missing_imports = true
no_implicit_optional = true
platform = "win32"
strict_equality = true
strict_optional = true
[tool.ruff]
target-version = "py37"
lint.select = [
@ -22,14 +74,22 @@ lint.ignore = [
[tool.ruff.lint.per-file-ignores]
"examples/*" = ["T201"] # Print allowed in examples.
"examples/ptpython_config/config.py" = ["F401"] # Unused imports in config.
"ptpython/entry_points/run_ptipython.py" = ["T201", "F401"] # Print, import usage.
"ptpython/entry_points/run_ptpython.py" = ["T201"] # Print usage.
"ptpython/ipython.py" = ["T100"] # Import usage.
"ptpython/repl.py" = ["T201"] # Print usage.
"ptpython/printer.py" = ["T201"] # Print usage.
"tests/run_tests.py" = ["F401"] # Unused imports.
"src/ptpython/entry_points/run_ptipython.py" = ["T201", "F401"] # Print, import usage.
"src/ptpython/entry_points/run_ptpython.py" = ["T201"] # Print usage.
"src/ptpython/ipython.py" = ["T100"] # Import usage.
"src/ptpython/repl.py" = ["T201"] # Print usage.
"src/ptpython/printer.py" = ["T201"] # Print usage.
[tool.ruff.lint.isort]
known-first-party = ["ptpython"]
known-third-party = ["prompt_toolkit", "pygments", "asyncssh"]
[tool.typos.default]
extend-ignore-re = [
"impotr" # Intentional typo in: ./examples/ptpython_config/config.py
]
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

View file

@ -1,41 +0,0 @@
[bdist_wheel]
universal=1
[flake8]
exclude=__init__.py
max_line_length=150
ignore=
E114,
E116,
E117,
E121,
E122,
E123,
E125,
E126,
E127,
E128,
E131,
E171,
E203,
E211,
E221,
E227,
E231,
E241,
E251,
E301,
E402,
E501,
E701,
E702,
E704,
E731,
E741,
F401,
F403,
F405,
F811,
W503,
W504,
E722

View file

@ -1,67 +0,0 @@
#!/usr/bin/env python
import os
import sys
from setuptools import find_packages, setup
with open(os.path.join(os.path.dirname(__file__), "README.rst")) as f:
long_description = f.read()
setup(
name="ptpython",
author="Jonathan Slenders",
version="3.0.29",
url="https://github.com/prompt-toolkit/ptpython",
description="Python REPL build on top of prompt_toolkit",
long_description=long_description,
package_urls={
"Changelog": "https://github.com/prompt-toolkit/ptpython/blob/master/CHANGELOG",
},
project_urls={
"Bug Tracker": "https://github.com/prompt-toolkit/ptpython/issues",
"Source Code": "https://github.com/prompt-toolkit/ptpython",
"Changelog": "https://github.com/prompt-toolkit/ptpython/blob/master/CHANGELOG",
},
packages=find_packages("."),
package_data={"ptpython": ["py.typed"]},
install_requires=[
"appdirs",
"importlib_metadata;python_version<'3.8'",
"jedi>=0.16.0",
# Use prompt_toolkit 3.0.43, because of `OneStyleAndTextTuple` import.
"prompt_toolkit>=3.0.43,<3.1.0",
"pygments",
],
python_requires=">=3.7",
classifiers=[
"License :: OSI Approved :: BSD License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python",
],
entry_points={
"console_scripts": [
"ptpython = ptpython.entry_points.run_ptpython:run",
"ptipython = ptpython.entry_points.run_ptipython:run",
f"ptpython{sys.version_info[0]} = ptpython.entry_points.run_ptpython:run",
"ptpython{}.{} = ptpython.entry_points.run_ptpython:run".format(
*sys.version_info[:2]
),
f"ptipython{sys.version_info[0]} = ptpython.entry_points.run_ptipython:run",
"ptipython{}.{} = ptpython.entry_points.run_ptipython:run".format(
*sys.version_info[:2]
),
]
},
extras_require={
"ptipython": ["ipython"], # For ptipython, we need to have IPython
"all": ["black"], # Black not always possible on PyPy
},
)

View file

@ -30,8 +30,9 @@ import asyncio
import os
import pathlib
import sys
from importlib import metadata
from textwrap import dedent
from typing import IO
from typing import Protocol
import appdirs
from prompt_toolkit.formatted_text import HTML
@ -39,17 +40,15 @@ from prompt_toolkit.shortcuts import print_formatted_text
from ptpython.repl import PythonRepl, embed, enable_deprecation_warnings, run_config
try:
from importlib import metadata # type: ignore
except ImportError:
import importlib_metadata as metadata # type: ignore
__all__ = ["create_parser", "get_config_and_history_file", "run"]
class _SupportsWrite(Protocol):
def write(self, s: str, /) -> object: ...
class _Parser(argparse.ArgumentParser):
def print_help(self, file: IO[str] | None = None) -> None:
def print_help(self, file: _SupportsWrite | None = None) -> None:
super().print_help()
print(
dedent(

View file

@ -58,13 +58,15 @@ from ptpython.layout import get_inputmode_fragments
from .utils import if_mousedown
if TYPE_CHECKING:
from typing_extensions import TypeAlias
from .python_input import PythonInput
HISTORY_COUNT = 2000
__all__ = ["HistoryLayout", "PythonHistory"]
E = KeyPressEvent
E: TypeAlias = KeyPressEvent
HELP_TEXT = """
This interface is meant to select multiple lines from the

View file

@ -22,6 +22,8 @@ from prompt_toolkit.keys import Keys
from .utils import document_is_multiline_python
if TYPE_CHECKING:
from typing_extensions import TypeAlias
from .python_input import PythonInput
__all__ = [
@ -30,7 +32,7 @@ __all__ = [
"load_confirm_exit_bindings",
]
E = KeyPressEvent
E: TypeAlias = KeyPressEvent
@Condition

View file

@ -108,7 +108,7 @@ def python_sidebar(python_input: PythonInput) -> Window:
tokens.extend(
[
("class:sidebar", " "),
("class:sidebar.title", " %-36s" % category.title),
("class:sidebar.title", f" {category.title:36}"),
("class:sidebar", "\n"),
]
)
@ -130,7 +130,7 @@ def python_sidebar(python_input: PythonInput) -> Window:
sel = ",selected" if selected else ""
tokens.append(("class:sidebar" + sel, " >" if selected else " "))
tokens.append(("class:sidebar.label" + sel, "%-24s" % label, select_item))
tokens.append(("class:sidebar.label" + sel, f"{label:24}", select_item))
tokens.append(("class:sidebar.status" + sel, " ", select_item))
tokens.append(("class:sidebar.status" + sel, f"{status}", goto_next))
@ -332,7 +332,7 @@ class PythonPromptMargin(PromptMargin):
width: int, line_number: int, is_soft_wrap: bool
) -> StyleAndTextTuples:
if python_input.show_line_numbers and not is_soft_wrap:
text = ("%i " % (line_number + 1)).rjust(width)
text = f"{line_number + 1} ".rjust(width)
return [("class:line-number", text)]
else:
return to_formatted_text(get_prompt_style().in2_prompt(width))
@ -368,8 +368,7 @@ def status_bar(python_input: PythonInput) -> Container:
append(
(
TB,
"%i/%i "
% (python_buffer.working_index + 1, len(python_buffer._working_lines)),
f"{python_buffer.working_index + 1}/{len(python_buffer._working_lines)} ",
)
)
@ -492,8 +491,7 @@ def show_sidebar_button_info(python_input: PythonInput) -> Container:
("class:status-toolbar", " - "),
(
"class:status-toolbar.python-version",
"%s %i.%i.%i"
% (platform.python_implementation(), version[0], version[1], version[2]),
f"{platform.python_implementation()} {version[0]}.{version[1]}.{version[2]}",
),
("class:status-toolbar", " "),
]

View file

@ -1,6 +1,5 @@
from __future__ import annotations
import sys
import traceback
from dataclasses import dataclass
from enum import Enum
@ -254,8 +253,7 @@ class OutputPrinter:
columns_in_buffer += width
current_line.append((style, c))
if len(current_line) > 0:
yield current_line
yield current_line
def _print_paginated_formatted_text(
self, lines: Iterable[StyleAndTextTuples]
@ -323,14 +321,20 @@ class OutputPrinter:
def _format_exception_output(
self, e: BaseException, highlight: bool
) -> Generator[OneStyleAndTextTuple, None, None]:
# Instead of just calling ``traceback.format_exc``, we take the
# traceback and skip the bottom calls of this framework.
t, v, tb = sys.exc_info()
if e.__cause__:
yield from self._format_exception_output(e.__cause__, highlight=highlight)
yield (
"",
"\nThe above exception was the direct cause of the following exception:\n\n",
)
elif e.__context__:
yield from self._format_exception_output(e.__context__, highlight=highlight)
yield (
"",
"\nDuring handling of the above exception, another exception occurred:\n\n",
)
# Required for pdb.post_mortem() to work.
sys.last_type, sys.last_value, sys.last_traceback = t, v, tb
tblist = list(traceback.extract_tb(tb))
tblist = list(traceback.extract_tb(e.__traceback__))
for line_nr, tb_tuple in enumerate(tblist):
if tb_tuple[0] == "<stdin>":
@ -340,7 +344,7 @@ class OutputPrinter:
tb_list = traceback.format_list(tblist)
if tb_list:
tb_list.insert(0, "Traceback (most recent call last):\n")
tb_list.extend(traceback.format_exception_only(t, v))
tb_list.extend(traceback.format_exception_only(type(e), e))
tb_str = "".join(tb_list)

View file

@ -20,7 +20,17 @@ import types
import warnings
from dis import COMPILER_FLAG_NAMES
from pathlib import Path
from typing import Any, Callable, ContextManager, Iterable, NoReturn, Sequence
from typing import (
Any,
Callable,
ContextManager,
Coroutine,
Iterable,
Literal,
NoReturn,
Sequence,
overload,
)
from prompt_toolkit.formatted_text import OneStyleAndTextTuple
from prompt_toolkit.patch_stdout import patch_stdout as patch_stdout_context
@ -362,7 +372,7 @@ class PythonRepl(PythonInput):
def _store_eval_result(self, result: object) -> None:
locals: dict[str, Any] = self.get_locals()
locals["_"] = locals["_%i" % self.current_statement_index] = result
locals["_"] = locals[f"_{self.current_statement_index}"] = result
def get_compiler_flags(self) -> int:
return super().get_compiler_flags() | PyCF_ALLOW_TOP_LEVEL_AWAIT
@ -378,6 +388,10 @@ class PythonRepl(PythonInput):
)
def _handle_exception(self, e: BaseException) -> None:
# Required for pdb.post_mortem() to work.
t, v, tb = sys.exc_info()
sys.last_type, sys.last_value, sys.last_traceback = t, v, tb
self._get_output_printer().display_exception(
e,
highlight=self.enable_syntax_highlighting,
@ -501,6 +515,34 @@ class ReplExit(Exception):
"""
@overload
def embed(
globals: dict[str, Any] | None = ...,
locals: dict[str, Any] | None = ...,
configure: Callable[[PythonRepl], None] | None = ...,
vi_mode: bool = ...,
history_filename: str | None = ...,
title: str | None = ...,
startup_paths: Sequence[str | Path] | None = ...,
patch_stdout: bool = ...,
return_asyncio_coroutine: Literal[False] = ...,
) -> None: ...
@overload
def embed(
globals: dict[str, Any] | None = ...,
locals: dict[str, Any] | None = ...,
configure: Callable[[PythonRepl], None] | None = ...,
vi_mode: bool = ...,
history_filename: str | None = ...,
title: str | None = ...,
startup_paths: Sequence[str | Path] | None = ...,
patch_stdout: bool = ...,
return_asyncio_coroutine: Literal[True] = ...,
) -> Coroutine[Any, Any, None]: ...
def embed(
globals: dict[str, Any] | None = None,
locals: dict[str, Any] | None = None,
@ -511,7 +553,7 @@ def embed(
startup_paths: Sequence[str | Path] | None = None,
patch_stdout: bool = False,
return_asyncio_coroutine: bool = False,
) -> None:
) -> None | Coroutine[Any, Any, None]:
"""
Call this to embed Python shell at the current point in your program.
It's similar to `IPython.embed` and `bpython.embed`. ::
@ -573,3 +615,4 @@ def embed(
else:
with patch_context:
repl.run()
return None

View file

@ -1,24 +0,0 @@
#!/usr/bin/env python
from __future__ import annotations
import unittest
import ptpython.completer
import ptpython.eventloop
import ptpython.filters
import ptpython.history_browser
import ptpython.key_bindings
import ptpython.layout
import ptpython.python_input
import ptpython.repl
import ptpython.style
import ptpython.utils
import ptpython.validator
# For now there are no tests here.
# However this is sufficient for Travis to do at least a syntax check.
# That way we are at least sure to restrict to the Python 2.6 syntax.
if __name__ == "__main__":
unittest.main()

31
tests/test_dummy.py Executable file
View file

@ -0,0 +1,31 @@
#!/usr/bin/env python
from __future__ import annotations
import ptpython.completer
import ptpython.eventloop
import ptpython.filters
import ptpython.history_browser
import ptpython.key_bindings
import ptpython.layout
import ptpython.python_input
import ptpython.repl
import ptpython.style
import ptpython.utils
import ptpython.validator
# For now there are no tests here.
# However this is sufficient to do at least a syntax check.
def test_dummy() -> None:
assert ptpython.completer
assert ptpython.eventloop
assert ptpython.filters
assert ptpython.history_browser
assert ptpython.key_bindings
assert ptpython.layout
assert ptpython.python_input
assert ptpython.repl
assert ptpython.style
assert ptpython.utils
assert ptpython.validator