From 7693c11b424a25612782de924a1f7f07f649675f Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 4 Jun 2024 06:07:44 +0200 Subject: Merging upstream version 3.0.27. Signed-off-by: Daniel Baumann --- CHANGELOG | 7 +++++++ README.rst | 26 ++++++++++++++++++++----- examples/asyncio-python-embed.py | 1 + examples/asyncio-ssh-python-embed.py | 1 + examples/ptpython_config/config.py | 1 + examples/python-embed-with-custom-prompt.py | 1 + examples/python-embed.py | 4 ++-- examples/python-input.py | 4 ++-- examples/test-cases/ptpython-in-other-thread.py | 1 + ptpython/__main__.py | 1 + ptpython/completer.py | 6 +++++- ptpython/contrib/asyncssh_repl.py | 1 + ptpython/entry_points/run_ptpython.py | 1 + ptpython/eventloop.py | 4 ++-- ptpython/history_browser.py | 3 ++- ptpython/ipython.py | 10 +++++----- ptpython/layout.py | 5 +++-- ptpython/prompt_style.py | 4 ++-- ptpython/python_input.py | 26 ++++++++++++------------- ptpython/repl.py | 18 +++++++++-------- ptpython/signatures.py | 1 + ptpython/utils.py | 1 + ptpython/validator.py | 2 +- pyproject.toml | 8 ++++---- setup.py | 24 ++++++++++++++++------- 25 files changed, 105 insertions(+), 56 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d873862..6f2bbb9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,13 @@ CHANGELOG ========= +3.0.27: 2024-05-27 +------------------ + +- Limit number of completions to 5k (for performance). +- Several typing fixes. + + 3.0.26: 2024-02-06 ------------------ diff --git a/README.rst b/README.rst index 8ec9aca..06c1e02 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,7 @@ ptpython .. image :: https://github.com/jonathanslenders/ptpython/raw/master/docs/images/example1.png Ptpython is an advanced Python REPL. It should work on all -Python versions from 2.6 up to 3.9 and work cross platform (Linux, +Python versions from 2.6 up to 3.11 and work cross platform (Linux, BSD, OS X and Windows). Note: this version of ptpython requires at least Python 3.6. Install ptpython @@ -255,6 +255,22 @@ Windows. Some things might not work, but it is usable: .. image :: https://github.com/jonathanslenders/ptpython/raw/master/docs/images/windows.png +Windows terminal integration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are using the `Windows Terminal `_ and want to +integrate ``ptpython`` as a profile, go to *Settings -> Open JSON file* and add the +following profile under *profiles.list*: + +.. code-block:: JSON + + { + "commandline": "%SystemRoot%\\System32\\cmd.exe /k ptpython", + "guid": "{f91d49a3-741b-409c-8a15-c4360649121f}", + "hidden": false, + "icon": "https://upload.wikimedia.org/wikipedia/commons/e/e6/Python_Windows_interpreter_icon_2006%E2%80%932016_Tiny.png", + "name": "ptpython@cmd" + } FAQ *** @@ -288,12 +304,12 @@ Special thanks to - `wcwidth `_: Determine columns needed for a wide characters. - `prompt_toolkit `_ for the interface. -.. |Build Status| image:: https://api.travis-ci.org/prompt-toolkit/ptpython.svg?branch=master - :target: https://travis-ci.org/prompt-toolkit/ptpython# +.. |Build Status| image:: https://github.com/prompt-toolkit/ptpython/actions/workflows/test.yaml/badge.svg + :target: https://github.com/prompt-toolkit/ptpython/actions/workflows/test.yaml .. |License| image:: https://img.shields.io/github/license/prompt-toolkit/ptpython.svg :target: https://github.com/prompt-toolkit/ptpython/blob/master/LICENSE -.. |PyPI| image:: https://pypip.in/version/ptpython/badge.svg - :target: https://pypi.python.org/pypi/ptpython/ +.. |PyPI| image:: https://img.shields.io/pypi/v/ptpython.svg + :target: https://pypi.org/project/ptpython/ :alt: Latest Version diff --git a/examples/asyncio-python-embed.py b/examples/asyncio-python-embed.py index a8fbba5..38cc1c2 100755 --- a/examples/asyncio-python-embed.py +++ b/examples/asyncio-python-embed.py @@ -11,6 +11,7 @@ The ``patch_stdout`` option makes sure that when another coroutine is writing to stdout, it won't break the input line, but instead writes nicely above the prompt. """ + import asyncio from ptpython.repl import embed diff --git a/examples/asyncio-ssh-python-embed.py b/examples/asyncio-ssh-python-embed.py index be0689e..9bbad86 100755 --- a/examples/asyncio-ssh-python-embed.py +++ b/examples/asyncio-ssh-python-embed.py @@ -5,6 +5,7 @@ This requires Python 3, asyncio and asyncssh. Run this example and then SSH to localhost, port 8222. """ + import asyncio import logging diff --git a/examples/ptpython_config/config.py b/examples/ptpython_config/config.py index b25850a..bfd3914 100644 --- a/examples/ptpython_config/config.py +++ b/examples/ptpython_config/config.py @@ -5,6 +5,7 @@ Copy this file to $XDG_CONFIG_HOME/ptpython/config.py On Linux, this is: ~/.config/ptpython/config.py On macOS, this is: ~/Library/Application Support/ptpython/config.py """ + from prompt_toolkit.filters import ViInsertMode from prompt_toolkit.key_binding.key_processor import KeyPress from prompt_toolkit.keys import Keys diff --git a/examples/python-embed-with-custom-prompt.py b/examples/python-embed-with-custom-prompt.py index d54da1d..5e8c707 100755 --- a/examples/python-embed-with-custom-prompt.py +++ b/examples/python-embed-with-custom-prompt.py @@ -2,6 +2,7 @@ """ Example of embedding a Python REPL, and setting a custom prompt. """ + from prompt_toolkit.formatted_text import HTML, AnyFormattedText from ptpython.prompt_style import PromptStyle diff --git a/examples/python-embed.py b/examples/python-embed.py index 49224ac..a748101 100755 --- a/examples/python-embed.py +++ b/examples/python-embed.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -""" -""" +""" """ + from ptpython.repl import embed diff --git a/examples/python-input.py b/examples/python-input.py index 567c2ee..d586d0f 100755 --- a/examples/python-input.py +++ b/examples/python-input.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -""" -""" +""" """ + from ptpython.python_input import PythonInput diff --git a/examples/test-cases/ptpython-in-other-thread.py b/examples/test-cases/ptpython-in-other-thread.py index 7c78846..bfe1410 100644 --- a/examples/test-cases/ptpython-in-other-thread.py +++ b/examples/test-cases/ptpython-in-other-thread.py @@ -5,6 +5,7 @@ Example of running ptpython in another thread. (For testing whether it's working fine if it's not embedded in the main thread.) """ + import threading from ptpython.repl import embed diff --git a/ptpython/__main__.py b/ptpython/__main__.py index c006261..3a2f7dd 100644 --- a/ptpython/__main__.py +++ b/ptpython/__main__.py @@ -1,6 +1,7 @@ """ Make `python -m ptpython` an alias for running `./ptpython`. """ + from __future__ import annotations from .entry_points.run_ptpython import run diff --git a/ptpython/completer.py b/ptpython/completer.py index 91d6647..264918e 100644 --- a/ptpython/completer.py +++ b/ptpython/completer.py @@ -6,6 +6,7 @@ import inspect import keyword import re from enum import Enum +from itertools import islice from typing import TYPE_CHECKING, Any, Callable, Iterable from prompt_toolkit.completion import ( @@ -617,7 +618,10 @@ class HidePrivateCompleter(Completer): def get_completions( self, document: Document, complete_event: CompleteEvent ) -> Iterable[Completion]: - completions = list(self.completer.get_completions(document, complete_event)) + completions = list( + # Limit at 5k completions for performance. + islice(self.completer.get_completions(document, complete_event), 0, 5000) + ) complete_private_attributes = self.complete_private_attributes() hide_private = False diff --git a/ptpython/contrib/asyncssh_repl.py b/ptpython/contrib/asyncssh_repl.py index 2f74eb2..a86737b 100644 --- a/ptpython/contrib/asyncssh_repl.py +++ b/ptpython/contrib/asyncssh_repl.py @@ -6,6 +6,7 @@ Note that the code in this file is Python 3 only. However, we should make sure not to use Python 3-only syntax, because this package should be installable in Python 2 as well! """ + from __future__ import annotations import asyncio diff --git a/ptpython/entry_points/run_ptpython.py b/ptpython/entry_points/run_ptpython.py index 1d4a532..05df971 100644 --- a/ptpython/entry_points/run_ptpython.py +++ b/ptpython/entry_points/run_ptpython.py @@ -22,6 +22,7 @@ environment variables: PTPYTHON_CONFIG_HOME: a configuration directory to use PYTHONSTARTUP: file executed on interactive startup (no default) """ + from __future__ import annotations import argparse diff --git a/ptpython/eventloop.py b/ptpython/eventloop.py index 14ab64b..a646274 100644 --- a/ptpython/eventloop.py +++ b/ptpython/eventloop.py @@ -7,6 +7,7 @@ way we don't block the UI of for instance ``turtle`` and other Tk libraries. in readline. ``prompt-toolkit`` doesn't understand that input hook, but this will fix it for Tk.) """ + from __future__ import annotations import sys @@ -23,9 +24,8 @@ def _inputhook_tk(inputhook_context: InputHookContext) -> None: Run the Tk eventloop until prompt-toolkit needs to process the next input. """ # Get the current TK application. - import tkinter - import _tkinter # Keep this imports inline! + import tkinter root = tkinter._default_root # type: ignore diff --git a/ptpython/history_browser.py b/ptpython/history_browser.py index b667be1..ae0ac03 100644 --- a/ptpython/history_browser.py +++ b/ptpython/history_browser.py @@ -4,6 +4,7 @@ Utility to easily select lines from the history and execute them again. `create_history_application` creates an `Application` instance that runs will run as a sub application of the Repl/PythonInput. """ + from __future__ import annotations from functools import partial @@ -410,7 +411,7 @@ class HistoryMapping: if len(history_strings) > HISTORY_COUNT: history_lines[0] = ( - "# *** History has been truncated to %s lines ***" % HISTORY_COUNT + f"# *** History has been truncated to {HISTORY_COUNT} lines ***" ) self.history_lines = history_lines diff --git a/ptpython/ipython.py b/ptpython/ipython.py index ad0516a..0692214 100644 --- a/ptpython/ipython.py +++ b/ptpython/ipython.py @@ -8,6 +8,7 @@ also the power of for instance all the %-magic functions that IPython has to offer. """ + from __future__ import annotations from typing import Iterable @@ -156,7 +157,7 @@ class MagicsCompleter(Completer): for m in sorted(self.magics_manager.magics["line"]): if m.startswith(text): - yield Completion("%s" % m, -len(text)) + yield Completion(f"{m}", -len(text)) class AliasCompleter(Completer): @@ -172,7 +173,7 @@ class AliasCompleter(Completer): for a, cmd in sorted(aliases, key=lambda a: a[0]): if a.startswith(text): - yield Completion("%s" % a, -len(text), display_meta=cmd) + yield Completion(f"{a}", -len(text), display_meta=cmd) class IPythonInput(PythonInput): @@ -279,9 +280,8 @@ def initialize_extensions(shell, extensions): shell.extension_manager.load_extension(ext) except: warn( - "Error in loading extension: %s" % ext - + "\nCheck your config files in %s" - % ipy_utils.path.get_ipython_dir() + f"Error in loading extension: {ext}" + + f"\nCheck your config files in {ipy_utils.path.get_ipython_dir()}" ) shell.showtraceback() diff --git a/ptpython/layout.py b/ptpython/layout.py index 2c1ec15..622df59 100644 --- a/ptpython/layout.py +++ b/ptpython/layout.py @@ -1,6 +1,7 @@ """ Creation of the `Layout` instance for the Python input/REPL. """ + from __future__ import annotations import platform @@ -131,7 +132,7 @@ def python_sidebar(python_input: PythonInput) -> Window: tokens.append(("class:sidebar" + sel, " >" if selected else " ")) tokens.append(("class:sidebar.label" + sel, "%-24s" % label, select_item)) tokens.append(("class:sidebar.status" + sel, " ", select_item)) - tokens.append(("class:sidebar.status" + sel, "%s" % status, goto_next)) + tokens.append(("class:sidebar.status" + sel, f"{status}", goto_next)) if selected: tokens.append(("[SetCursorPosition]", "")) @@ -528,7 +529,7 @@ def create_exit_confirmation( def get_text_fragments() -> StyleAndTextTuples: # Show "Do you really want to exit?" return [ - (style, "\n %s ([y]/n) " % python_input.exit_message), + (style, f"\n {python_input.exit_message} ([y]/n) "), ("[SetCursorPosition]", ""), (style, " \n"), ] diff --git a/ptpython/prompt_style.py b/ptpython/prompt_style.py index 96b738f..465c3db 100644 --- a/ptpython/prompt_style.py +++ b/ptpython/prompt_style.py @@ -48,7 +48,7 @@ class IPythonPrompt(PromptStyle): def in_prompt(self) -> AnyFormattedText: return [ ("class:in", "In ["), - ("class:in.number", "%s" % self.python_input.current_statement_index), + ("class:in.number", f"{self.python_input.current_statement_index}"), ("class:in", "]: "), ] @@ -58,7 +58,7 @@ class IPythonPrompt(PromptStyle): def out_prompt(self) -> AnyFormattedText: return [ ("class:out", "Out["), - ("class:out.number", "%s" % self.python_input.current_statement_index), + ("class:out.number", f"{self.python_input.current_statement_index}"), ("class:out", "]:"), ("", " "), ] diff --git a/ptpython/python_input.py b/ptpython/python_input.py index 54ddbef..975d3d9 100644 --- a/ptpython/python_input.py +++ b/ptpython/python_input.py @@ -2,6 +2,7 @@ Application for reading Python input. This can be used for creation of Python REPLs. """ + from __future__ import annotations from asyncio import get_running_loop @@ -98,8 +99,7 @@ if TYPE_CHECKING: class _SupportsLessThan(Protocol): # Taken from typeshed. _T_lt is used by "sorted", which needs anything # sortable. - def __lt__(self, __other: Any) -> bool: - ... + def __lt__(self, __other: Any) -> bool: ... _T_lt = TypeVar("_T_lt", bound="_SupportsLessThan") @@ -347,14 +347,6 @@ class PythonInput: "classic": ClassicPrompt(), } - self.get_input_prompt = lambda: self.all_prompt_styles[ - self.prompt_style - ].in_prompt() - - self.get_output_prompt = lambda: self.all_prompt_styles[ - self.prompt_style - ].out_prompt() - #: Load styles. self.code_styles: dict[str, BaseStyle] = get_all_code_styles() self.ui_styles = get_all_ui_styles() @@ -425,6 +417,12 @@ class PythonInput: else: self._app = None + def get_input_prompt(self) -> AnyFormattedText: + return self.all_prompt_styles[self.prompt_style].in_prompt() + + def get_output_prompt(self) -> AnyFormattedText: + return self.all_prompt_styles[self.prompt_style].out_prompt() + def _accept_handler(self, buff: Buffer) -> bool: app = get_app() app.exit(result=buff.text) @@ -880,18 +878,18 @@ class PythonInput: Option( title="Min brightness", description="Minimum brightness for the color scheme (default=0.0).", - get_current_value=lambda: "%.2f" % self.min_brightness, + get_current_value=lambda: f"{self.min_brightness:.2f}", get_values=lambda: { - "%.2f" % value: partial(self._set_min_brightness, value) + f"{value:.2f}": partial(self._set_min_brightness, value) for value in brightness_values }, ), Option( title="Max brightness", description="Maximum brightness for the color scheme (default=1.0).", - get_current_value=lambda: "%.2f" % self.max_brightness, + get_current_value=lambda: f"{self.max_brightness:.2f}", get_values=lambda: { - "%.2f" % value: partial(self._set_max_brightness, value) + f"{value:.2f}": partial(self._set_max_brightness, value) for value in brightness_values }, ), diff --git a/ptpython/repl.py b/ptpython/repl.py index fc9b9da..ea2d84f 100644 --- a/ptpython/repl.py +++ b/ptpython/repl.py @@ -7,6 +7,7 @@ Utility for creating a Python repl. embed(globals(), locals(), vi_mode=False) """ + from __future__ import annotations import asyncio @@ -18,7 +19,8 @@ import traceback import types import warnings from dis import COMPILER_FLAG_NAMES -from typing import Any, Callable, ContextManager, Iterable +from pathlib import Path +from typing import Any, Callable, ContextManager, Iterable, Sequence from prompt_toolkit.formatted_text import OneStyleAndTextTuple from prompt_toolkit.patch_stdout import patch_stdout as patch_stdout_context @@ -63,7 +65,7 @@ def _has_coroutine_flag(code: types.CodeType) -> bool: class PythonRepl(PythonInput): def __init__(self, *a, **kw) -> None: - self._startup_paths = kw.pop("startup_paths", None) + self._startup_paths: Sequence[str | Path] | None = kw.pop("startup_paths", None) super().__init__(*a, **kw) self._load_start_paths() @@ -347,7 +349,7 @@ class PythonRepl(PythonInput): def get_compiler_flags(self) -> int: return super().get_compiler_flags() | PyCF_ALLOW_TOP_LEVEL_AWAIT - def _compile_with_flags(self, code: str, mode: str): + def _compile_with_flags(self, code: str, mode: str) -> Any: "Compile code with the right compiler flags." return compile( code, @@ -458,13 +460,13 @@ def run_config(repl: PythonInput, config_file: str | None = None) -> None: def embed( - globals=None, - locals=None, + globals: dict[str, Any] | None = None, + locals: dict[str, Any] | None = None, configure: Callable[[PythonRepl], None] | None = None, vi_mode: bool = False, history_filename: str | None = None, title: str | None = None, - startup_paths=None, + startup_paths: Sequence[str | Path] | None = None, patch_stdout: bool = False, return_asyncio_coroutine: bool = False, ) -> None: @@ -493,10 +495,10 @@ def embed( locals = locals or globals - def get_globals(): + def get_globals() -> dict[str, Any]: return globals - def get_locals(): + def get_locals() -> dict[str, Any]: return locals # Create REPL. diff --git a/ptpython/signatures.py b/ptpython/signatures.py index d4cb98c..b3e5c91 100644 --- a/ptpython/signatures.py +++ b/ptpython/signatures.py @@ -5,6 +5,7 @@ editing. Either with the Jedi library, or using `inspect.signature` if Jedi fails and we can use `eval()` to evaluate the function object. """ + from __future__ import annotations import inspect diff --git a/ptpython/utils.py b/ptpython/utils.py index 28887d2..92cfc2a 100644 --- a/ptpython/utils.py +++ b/ptpython/utils.py @@ -1,6 +1,7 @@ """ For internal use only. """ + from __future__ import annotations import re diff --git a/ptpython/validator.py b/ptpython/validator.py index 91b9c28..cf2ee54 100644 --- a/ptpython/validator.py +++ b/ptpython/validator.py @@ -59,4 +59,4 @@ class PythonValidator(Validator): except ValueError as e: # In Python 2, compiling "\x9" (an invalid escape sequence) raises # ValueError instead of SyntaxError. - raise ValidationError(0, "Syntax Error: %s" % e) + raise ValidationError(0, f"Syntax Error: {e}") diff --git a/pyproject.toml b/pyproject.toml index 5421c45..ce42037 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff] target-version = "py37" -select = [ +lint.select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # pyflakes @@ -12,14 +12,14 @@ select = [ "RUF100", # unused-noqa "Q", # quotes ] -ignore = [ +lint.ignore = [ "E501", # Line too long, handled by black "C901", # Too complex "E722", # bare except. ] -[tool.ruff.per-file-ignores] +[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. @@ -30,6 +30,6 @@ ignore = [ "tests/run_tests.py" = ["F401"] # Unused imports. -[tool.ruff.isort] +[tool.ruff.lint.isort] known-first-party = ["ptpython"] known-third-party = ["prompt_toolkit", "pygments", "asyncssh"] diff --git a/setup.py b/setup.py index a54da35..84f18be 100644 --- a/setup.py +++ b/setup.py @@ -11,18 +11,26 @@ with open(os.path.join(os.path.dirname(__file__), "README.rst")) as f: setup( name="ptpython", author="Jonathan Slenders", - version="3.0.26", + version="3.0.27", 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.34, because of `OneStyleAndTextTuple` import. - "prompt_toolkit>=3.0.34,<3.1.0", + # Use prompt_toolkit 3.0.43, because of `OneStyleAndTextTuple` import. + "prompt_toolkit>=3.0.43,<3.1.0", "pygments", ], python_requires=">=3.7", @@ -30,8 +38,11 @@ setup( "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", - "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", ], @@ -39,12 +50,11 @@ setup( "console_scripts": [ "ptpython = ptpython.entry_points.run_ptpython:run", "ptipython = ptpython.entry_points.run_ptipython:run", - "ptpython%s = ptpython.entry_points.run_ptpython:run" % sys.version_info[0], + f"ptpython{sys.version_info[0]} = ptpython.entry_points.run_ptpython:run", "ptpython{}.{} = ptpython.entry_points.run_ptpython:run".format( *sys.version_info[:2] ), - "ptipython%s = ptpython.entry_points.run_ptipython:run" - % sys.version_info[0], + f"ptipython{sys.version_info[0]} = ptpython.entry_points.run_ptipython:run", "ptipython{}.{} = ptpython.entry_points.run_ptipython:run".format( *sys.version_info[:2] ), -- cgit v1.2.3