diff --git a/docs/contributing.md b/docs/contributing.md index 4d5ce2ee83b..1f1110f0158 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -204,6 +204,15 @@ base branch. A maintainer might ask you to ensure the branch is up-to-date prior All pull requests, unless otherwise instructed, need to be first accepted into the `main` branch. Maintainers will generally decide if any backports to other branches are required, and carry them out as needed. +#### Performance contributions + +Performance contributions should result in a significant improvement when running a Poetry command. +Micro-optimizations that do not meet this criterion are only acceptable if they do not decrease code readability. + +When making performance measurements, you can set the `POETRY_FORCE_HTTP_CACHE` environment variable +to force using the HTTP cache. This helps make performance measurements more consistent by reducing +variability from network requests. + ### Issue triage {{% note %}} diff --git a/src/poetry/utils/authenticator.py b/src/poetry/utils/authenticator.py index be527cdbcc5..b32af9b479f 100644 --- a/src/poetry/utils/authenticator.py +++ b/src/poetry/utils/authenticator.py @@ -10,6 +10,7 @@ from pathlib import Path from typing import TYPE_CHECKING from typing import Any +from typing import Literal import requests import requests.adapters @@ -17,7 +18,9 @@ import requests.exceptions from cachecontrol import CacheControlAdapter +from cachecontrol import CacheController from cachecontrol.caches import SeparateBodyFileCache +from requests import PreparedRequest from requests_toolbelt import user_agent from poetry.__version__ import __version__ @@ -25,6 +28,7 @@ from poetry.console.exceptions import ConsoleMessage from poetry.console.exceptions import PoetryRuntimeError from poetry.exceptions import PoetryError +from poetry.utils.constants import FORCE_HTTP_CACHE from poetry.utils.constants import REQUESTS_TIMEOUT from poetry.utils.constants import RETRY_AFTER_HEADER from poetry.utils.constants import STATUS_FORCELIST @@ -34,11 +38,29 @@ if TYPE_CHECKING: from cleo.io.io import IO + from urllib3 import HTTPResponse logger = logging.getLogger(__name__) +class ForceCacheController(CacheController): + """ + A custom CacheController that forces the use of cached responses + if they are available, even if they are stale, and does not attempt + to revalidate them with the server. + + This is useful for performance analyses + to rule out a source of uncertainty. + """ + + def cached_request(self, request: PreparedRequest) -> HTTPResponse | Literal[False]: + resp = self._load_from_cache(request) + if not resp: + return False # genuinely not cached -> nothing we can do + return resp + + @dataclasses.dataclass(frozen=True) class RepositoryCertificateConfig: cert: Path | None = dataclasses.field(default=None) @@ -145,6 +167,7 @@ def create_session(self) -> requests.Session: adapter = CacheControlAdapter( cache=self._cache_control, pool_maxsize=self._pool_size, + controller_class=ForceCacheController if FORCE_HTTP_CACHE else None, ) session.mount("http://", adapter) session.mount("https://", adapter) diff --git a/src/poetry/utils/constants.py b/src/poetry/utils/constants.py index f9e5d8248ce..10151f26503 100644 --- a/src/poetry/utils/constants.py +++ b/src/poetry/utils/constants.py @@ -13,3 +13,7 @@ # Server response codes to retry requests on. STATUS_FORCELIST = [429, 500, 501, 502, 503, 504] + +# Force the use of cached http responses even if they are stale. +# This is useful for performance analyses to rule out a source of uncertainty. +FORCE_HTTP_CACHE = os.getenv("POETRY_FORCE_HTTP_CACHE", "").lower() in ("true", "1")