diff --git a/README.md b/README.md index 166596b..61d3894 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ # IDF Component Manager The IDF Component Manager is a tool that ensures the correct versions of all components required for a successful build are present in your [ESP-IDF](https://www.espressif.com/en/products/sdks/esp-idf) project. + - The Component Manager downloads the dependencies for your project automatically during a `CMake` run. - The components can be sourced either from [the ESP Component Registry](https://components.espressif.com/) or from a Git repository. - A list of components can be found at https://components.espressif.com/ ## Contributing to the project + See [CONTRIBUTING.md](CONTRIBUTING.md) ## Resources + - [Offical Documentation at docs.espressif.com](https://docs.espressif.com/projects/idf-component-manager/en/latest/) - The Python Package Index project page https://pypi.org/project/idf-component-manager/ - The Component Manager section in the [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html) @@ -43,7 +46,6 @@ python -m pip uninstall -y idf-component-manager python -m pip install git+https://github.com/espressif/idf-component-manager.git@main ``` - ## Disabling the Component Manager The Component Manager can be explicitly disabled by setting `IDF_COMPONENT_MANAGER` environment variable to `0`. @@ -164,3 +166,4 @@ examples: | IDF_COMPONENT_SUPPRESS_UNKNOWN_FILE_WARNINGS | 0 | Ignore unknown files in managed_components directory | | IDF_COMPONENT_CHECK_NEW_VERSION | 1 | Check for new versions of components | | IDF_COMPONENT_VERIFY_SSL | 1 | Verify SSL certificates when making requests to the registry, set it 0 to disable or provide a CA bundle path | +| IDF_COMPONENT_CACHE_HTTP_REQUESTS | 1 | Cache HTTP requests to the registry during runtime, set it 0 to disable | diff --git a/idf_component_tools/environment.py b/idf_component_tools/environment.py index c1b781b..07905da 100644 --- a/idf_component_tools/environment.py +++ b/idf_component_tools/environment.py @@ -109,6 +109,7 @@ class ComponentManagerSettings(BaseSettings): ) API_TOKEN: t.Optional[str] = None VERIFY_SSL: t.Union[bool, str] = True + CACHE_HTTP_REQUESTS: bool = True PROFILE: t.Optional[str] = Field( default=None, validation_alias=AliasChoices( diff --git a/idf_component_tools/registry/base_client.py b/idf_component_tools/registry/base_client.py index 3525631..aff5c1f 100644 --- a/idf_component_tools/registry/base_client.py +++ b/idf_component_tools/registry/base_client.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import platform import typing as t +from functools import lru_cache import requests from requests.adapters import HTTPAdapter @@ -24,6 +25,7 @@ MAX_RETRIES = 3 +@lru_cache(maxsize=None) def create_session( token: t.Optional[str] = None, ) -> requests.Session: diff --git a/idf_component_tools/registry/request_processor.py b/idf_component_tools/registry/request_processor.py index 3366cbe..3f22a1c 100644 --- a/idf_component_tools/registry/request_processor.py +++ b/idf_component_tools/registry/request_processor.py @@ -9,7 +9,7 @@ from pydantic import ValidationError from requests import Response -from idf_component_tools import ComponentManagerSettings +from idf_component_tools import ComponentManagerSettings, debug from .api_models import ApiBaseModel, ErrorResponse from .client_errors import ( @@ -25,6 +25,9 @@ 60.1, # Read timeout ) +# Storage for caching requests +_request_cache: t.Dict[t.Tuple[t.Any], Response] = {} + def join_url(*args) -> str: """ @@ -34,6 +37,23 @@ def join_url(*args) -> str: return '/'.join(parts) +def cache_request(func): + """Decorator to conditionally cache function output based on CACHE_HTTP_REQUESTS""" + + def wrapper(*args, **kwargs): + if ComponentManagerSettings().CACHE_HTTP_REQUESTS: + cache_key = (args, frozenset(kwargs.items())) + if cache_key in _request_cache: + return _request_cache[cache_key] + result = func(*args, **kwargs) + _request_cache[cache_key] = result + return result + return func(*args, **kwargs) + + return wrapper + + +@cache_request def make_request( method: str, session: requests.Session, @@ -44,6 +64,7 @@ def make_request( timeout: t.Union[float, t.Tuple[float, float]], ) -> Response: try: + debug(f'HTTP request: {method.upper()} {endpoint}') response = session.request( method, endpoint, @@ -59,6 +80,7 @@ def make_request( except requests.exceptions.RequestException as e: raise APIClientError(f'HTTP request error {e}', endpoint=endpoint) + debug(f'HTTP response: {response.status_code} total: {response.elapsed.total_seconds()}s') return response diff --git a/tests/conftest.py b/tests/conftest.py index ad5ed45..d2598da 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,6 +36,11 @@ def monkeypatch_idf_version_and_tools_path(monkeypatch, tmp_path): monkeypatch.setenv('IDF_TOOLS_PATH', str(tmp_path)) +@pytest.fixture(autouse=True) +def monkeypatch_disable_request_cache(monkeypatch): + monkeypatch.setenv('IDF_COMPONENT_CACHE_HTTP_REQUESTS', '0') + + @pytest.fixture() def valid_manifest(): return {