From a0211b05ab087f0494b096a4223d364b42d65396 Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Wed, 20 Aug 2025 15:50:50 -0500 Subject: [PATCH 1/2] Cache values for 'is_available()' --- src/whichprovides/__init__.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/whichprovides/__init__.py b/src/whichprovides/__init__.py index 30fa592..85c366e 100644 --- a/src/whichprovides/__init__.py +++ b/src/whichprovides/__init__.py @@ -6,6 +6,7 @@ """ import dataclasses +import functools import pathlib import re import shutil @@ -290,6 +291,7 @@ def whichprovides(cls, filepaths: typing.Collection[str]) -> dict[str, ProvidedB return results +@functools.cache def _package_providers() -> list[type[PackageProvider]]: """Returns a list of package providers sorted in the order that they should be attempted. @@ -305,6 +307,25 @@ def all_subclasses(cls): return sorted(all_subclasses(PackageProvider), key=lambda p: p._resolve_order) +# Cache the value of PackageProvider.is_available() +_PACKAGE_PROVIDERS_IS_AVAILABLE: dict[type[PackageProvider], bool] = {} + + +def _available_package_providers() -> ( + typing.Generator[type[PackageProvider], None, None] +): + """We use a generator here because PackageProviders might not + all need to be queried for 'is_available()' if 'whichprovides()' + is able to find matches for all file paths. + """ + values_cache = _PACKAGE_PROVIDERS_IS_AVAILABLE + for package_provider in _package_providers(): + if package_provider not in values_cache: + values_cache[package_provider] = package_provider.is_available() + if values_cache[package_provider]: + yield package_provider + + def whichprovides(filepath: typing.Union[str, list[str]]) -> dict[str, ProvidedBy]: """Return a package URL (PURL) for the package that provides a file""" if isinstance(filepath, str): @@ -318,12 +339,10 @@ def whichprovides(filepath: typing.Union[str, list[str]]) -> dict[str, ProvidedB str(pathlib.Path(filepath).resolve()): filepath for filepath in filepaths } filepath_provided_by: dict[str, ProvidedBy] = {} - for package_provider in _package_providers(): + for package_provider in _available_package_providers(): remaining = set(resolved_filepaths) - set(filepath_provided_by) if not remaining: break - if not package_provider.is_available(): - continue results = package_provider.whichprovides(remaining) filepath_provided_by.update(results) From 01ee14f406b2b126a61af09e10b4570ecfb360e6 Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Wed, 20 Aug 2025 17:16:36 -0500 Subject: [PATCH 2/2] Use function parameter instead of global --- src/whichprovides/__init__.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/whichprovides/__init__.py b/src/whichprovides/__init__.py index 85c366e..2b5c95a 100644 --- a/src/whichprovides/__init__.py +++ b/src/whichprovides/__init__.py @@ -307,22 +307,17 @@ def all_subclasses(cls): return sorted(all_subclasses(PackageProvider), key=lambda p: p._resolve_order) -# Cache the value of PackageProvider.is_available() -_PACKAGE_PROVIDERS_IS_AVAILABLE: dict[type[PackageProvider], bool] = {} - - -def _available_package_providers() -> ( - typing.Generator[type[PackageProvider], None, None] -): +def _available_package_providers( + _is_available_cache: dict[type[PackageProvider], bool] = {} +) -> typing.Generator[type[PackageProvider], None, None]: """We use a generator here because PackageProviders might not all need to be queried for 'is_available()' if 'whichprovides()' is able to find matches for all file paths. """ - values_cache = _PACKAGE_PROVIDERS_IS_AVAILABLE for package_provider in _package_providers(): - if package_provider not in values_cache: - values_cache[package_provider] = package_provider.is_available() - if values_cache[package_provider]: + if package_provider not in _is_available_cache: + _is_available_cache[package_provider] = package_provider.is_available() + if _is_available_cache[package_provider]: yield package_provider