From c6d48deea19cfe43a77f47df2023a9b3efec6575 Mon Sep 17 00:00:00 2001 From: Josiah Outram Halstead Date: Wed, 5 Nov 2025 12:15:40 +0000 Subject: [PATCH 1/2] Implement lazy loading of `__version__` Fixes #2028 --- src/prompt_toolkit/__init__.py | 56 ++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/src/prompt_toolkit/__init__.py b/src/prompt_toolkit/__init__.py index 7a0c4cee9..0fbe0c91d 100644 --- a/src/prompt_toolkit/__init__.py +++ b/src/prompt_toolkit/__init__.py @@ -16,25 +16,55 @@ from __future__ import annotations -import re -from importlib import metadata - -# note: this is a bit more lax than the actual pep 440 to allow for a/b/rc/dev without a number -pep440 = re.compile( - r"^([1-9]\d*!)?(0|[1-9]\d*)(\.(0|[1-9]\d*))*((a|b|rc)(0|[1-9]\d*)?)?(\.post(0|[1-9]\d*))?(\.dev(0|[1-9]\d*)?)?$", - re.UNICODE, -) +from typing import Any + from .application import Application from .formatted_text import ANSI, HTML from .shortcuts import PromptSession, choice, print_formatted_text, prompt -# Don't forget to update in `docs/conf.py`! -__version__ = metadata.version("prompt_toolkit") +__version__: str +VERSION: tuple[int, int, int] + + +def _load_version() -> str: + """ + Load the package version from importlib.metadata and cache both __version__ + and VERSION in the module globals. + """ + import re + from importlib import metadata + + # note: this is a bit more lax than the actual pep 440 to allow for a/b/rc/dev without a number + pep440_pattern = ( + r"^([1-9]\d*!)?(0|[1-9]\d*)(\.(0|[1-9]\d*))*" + r"((a|b|rc)(0|[1-9]\d*)?)?(\.post(0|[1-9]\d*))?(\.dev(0|[1-9]\d*)?)?$" + ) + + version = metadata.version("prompt_toolkit") + assert re.fullmatch(pep440_pattern, version) + + # Don't forget to update in `docs/conf.py`! + globals()["__version__"] = version + # Version tuple. + globals()["VERSION"] = tuple(int(v.rstrip("abrc")) for v in version.split(".")[:3]) + return version + + +def __getattr__(name: str) -> Any: + if name in {"__version__", "VERSION"}: + _load_version() + return globals()[name] + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") -assert pep440.match(__version__) -# Version tuple. -VERSION = tuple(int(v.rstrip("abrc")) for v in __version__.split(".")[:3]) +def __dir__() -> list[str]: + return sorted( + { + *globals().keys(), + "__version__", + "VERSION", + } + ) __all__ = [ From 5fc03bd2b33f4a22ffd921eff8f86a49580360cb Mon Sep 17 00:00:00 2001 From: Josiah Outram Halstead Date: Mon, 10 Nov 2025 11:00:21 +0000 Subject: [PATCH 2/2] Use globals and do not return --- src/prompt_toolkit/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/prompt_toolkit/__init__.py b/src/prompt_toolkit/__init__.py index 0fbe0c91d..dc49c40e3 100644 --- a/src/prompt_toolkit/__init__.py +++ b/src/prompt_toolkit/__init__.py @@ -26,11 +26,13 @@ VERSION: tuple[int, int, int] -def _load_version() -> str: +def _load_version() -> None: """ Load the package version from importlib.metadata and cache both __version__ and VERSION in the module globals. """ + global __version__, VERSION + import re from importlib import metadata @@ -44,10 +46,9 @@ def _load_version() -> str: assert re.fullmatch(pep440_pattern, version) # Don't forget to update in `docs/conf.py`! - globals()["__version__"] = version + __version__ = version # Version tuple. - globals()["VERSION"] = tuple(int(v.rstrip("abrc")) for v in version.split(".")[:3]) - return version + VERSION = tuple(int(v.rstrip("abrc")) for v in version.split(".")[:3]) def __getattr__(name: str) -> Any: