diff --git a/src/pyconify/__init__.py b/src/pyconify/__init__.py index ef1ace9..e882204 100644 --- a/src/pyconify/__init__.py +++ b/src/pyconify/__init__.py @@ -10,6 +10,7 @@ __author__ = "Talley Lambert" __email__ = "talley.lambert@gmail.com" __all__ = [ + "clear_api_cache", "clear_cache", "collection", "collections", @@ -21,12 +22,14 @@ "keywords", "last_modified", "search", + "set_api_cache_maxsize", "svg", "svg_path", ] from ._cache import clear_cache, get_cache_directory from .api import ( + clear_api_cache, collection, collections, css, @@ -35,6 +38,7 @@ keywords, last_modified, search, + set_api_cache_maxsize, svg, svg_path, ) diff --git a/src/pyconify/api.py b/src/pyconify/api.py index ced50ac..3db65ae 100644 --- a/src/pyconify/api.py +++ b/src/pyconify/api.py @@ -10,7 +10,7 @@ import warnings from contextlib import suppress from pathlib import Path -from typing import TYPE_CHECKING, Literal, overload +from typing import TYPE_CHECKING, Any, Literal, Protocol, cast, overload from ._cache import CACHE_DISABLED, _SVGCache, cache_key, svg_cache @@ -20,6 +20,11 @@ import requests + class _lru_cache_wrapper(Protocol): + __wrapped__: Callable[..., Any] + + def cache_clear(self) -> None: ... + F = TypeVar("F", bound=Callable) from .iconify_types import ( @@ -35,6 +40,44 @@ ROOT = "https://api.iconify.design" +API_FUNCTIONS: set[str] = {"collections", "collection", "last_modified", "css"} + + +def clear_api_cache() -> None: + """Clear all cached responses to the iconify API from this session.""" + for func_name in API_FUNCTIONS: + wrapper = cast("_lru_cache_wrapper", globals()[func_name]) + wrapper.cache_clear() + + +def set_api_cache_maxsize(maxsize: int | None) -> None: + """Set the `lru_cache` maxsize for all calls to the iconify API. + + This is NOT the same thing as the on-disk SVG cache + + This will also clear all cached responses to the iconify API from this session. + """ + import pyconify + + if maxsize is not None: + if not isinstance(maxsize, int): # pragma: no cover + raise TypeError( + f"maxsize must be an integer, not {type(maxsize).__name__}." + ) + if maxsize < 1: # pragma: no cover + maxsize = 0 + + for func_name in API_FUNCTIONS: + # get the lrue_cache-wrapped function and clear it + wrapper = cast("_lru_cache_wrapper", globals()[func_name]) + wrapper.cache_clear() + # get the original function and wrap it with the new maxsize + func = wrapper.__wrapped__ + new_func = functools.lru_cache(maxsize=maxsize)(func) + # update the names in both this module and top-level pyconify + globals()[func_name] = new_func + setattr(pyconify, func_name, new_func) + @functools.cache def _session() -> requests.Session: @@ -46,7 +89,7 @@ def _session() -> requests.Session: return session -@functools.cache +@functools.lru_cache(maxsize=128) def collections(*prefixes: str) -> dict[str, IconifyInfo]: """Return collections where key is icon set prefix, value is IconifyInfo object. @@ -67,7 +110,7 @@ def collections(*prefixes: str) -> dict[str, IconifyInfo]: return resp.json() # type: ignore -@functools.cache +@functools.lru_cache(maxsize=128) def collection( prefix: str, info: bool = False, @@ -260,7 +303,7 @@ def _cached_svg_path(svg_cache_key: str) -> Path | None: return None # pragma: no cover -@functools.cache +@functools.lru_cache(maxsize=128) def svg_path( *key: str, color: str | None = None, @@ -327,7 +370,7 @@ def _remove_tmp_svg() -> None: return Path(tmp_name) -@functools.cache +@functools.lru_cache(maxsize=128) def css( *keys: str, selector: str | None = None, diff --git a/tests/test_pyconify.py b/tests/test_pyconify.py index 170fb8b..b02b909 100644 --- a/tests/test_pyconify.py +++ b/tests/test_pyconify.py @@ -112,3 +112,14 @@ def test_search() -> None: def test_iconify_version() -> None: assert isinstance(pyconify.iconify_version(), str) + + +def test_clear_api_cache() -> None: + assert pyconify.collections.cache_info().currsize > 0 + assert pyconify.collections.cache_info().maxsize == 128 + + pyconify.clear_api_cache() + pyconify.set_api_cache_maxsize(10) + + assert pyconify.collections.cache_info().currsize == 0 + assert pyconify.collections.cache_info().maxsize == 10