From 518f739938278b75d0ff2a414dd078550ba00166 Mon Sep 17 00:00:00 2001 From: theOehrly <23384863+theOehrly@users.noreply.github.com> Date: Wed, 12 Apr 2023 18:50:52 +0200 Subject: [PATCH] add CI mode for Cache: only request uncached data --- conftest.py | 5 +++-- fastf1/ergast/interface.py | 1 + fastf1/req.py | 42 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/conftest.py b/conftest.py index cc4f94931..4ccd0fd81 100644 --- a/conftest.py +++ b/conftest.py @@ -87,5 +87,6 @@ def reference_laps_data(): def fastf1_setup(): import fastf1 from fastf1.logger import LoggingManager - fastf1.Cache.enable_cache('test_cache') - LoggingManager.debug = True + fastf1.Cache.enable_cache('test_cache') # use specific cache directory + fastf1.Cache.ci_mode(True) # only request uncached data + LoggingManager.debug = True # raise all exceptions diff --git a/fastf1/ergast/interface.py b/fastf1/ergast/interface.py index 1cb42a027..4ca472734 100644 --- a/fastf1/ergast/interface.py +++ b/fastf1/ergast/interface.py @@ -489,6 +489,7 @@ def _get(cls, url: str, params: dict) -> Union[dict, list]: try: return json.loads(r.content.decode('utf-8')) except Exception as exc: + Cache.delete_response(url) # don't keep a corrupted response raise ErgastJsonError( f"Failed to parse Ergast response ({url})" ) from exc diff --git a/fastf1/req.py b/fastf1/req.py index e52bc6478..f0a18376e 100644 --- a/fastf1/req.py +++ b/fastf1/req.py @@ -205,6 +205,7 @@ class Cache: _requests_session: requests.Session = _SessionWithRateLimiting() _default_cache_enabled = False # flag to ensure that warning about disabled cache is logged once only # noqa: E501 _tmp_disabled = False + _ci_mode = False @classmethod def enable_cache( @@ -261,6 +262,15 @@ def requests_get(cls, *args, **kwargs): cls._enable_default_cache() if (cls._requests_session_cached is None) or cls._tmp_disabled: return cls._requests_session.get(*args, **kwargs) + + if cls._ci_mode: + # try to return a cached response first + resp = cls._requests_session_cached.get( + *args, only_if_cached=True, **kwargs) + # 504 indicates that no cached response was found + if resp.status_code != 504: + return resp + return cls._requests_session_cached.get(*args, **kwargs) @classmethod @@ -275,8 +285,24 @@ def requests_post(cls, *args, **kwargs): cls._enable_default_cache() if (cls._requests_session_cached is None) or cls._tmp_disabled: return cls._requests_session.post(*args, **kwargs) + + if cls._ci_mode: + # try to return a cached response first + resp = cls._requests_session_cached.post( + *args, only_if_cached=True, **kwargs) + # 504 indicates that no cached response was found + if resp.status_code != 504: + return resp + return cls._requests_session_cached.post(*args, **kwargs) + @classmethod + def delete_response(cls, url): + """Deletes a single cached response from the cache, if caching is + enabled. If caching is not enabled, this call is ignored.""" + if cls._requests_session_cached is not None: + cls._requests_session_cached.cache.delete(urls=[url]) + @staticmethod def _custom_cache_filter(response: requests.Response): # this function provides custom filtering to decide which responses @@ -554,6 +580,22 @@ def offline_mode(cls, enabled: bool): cls._enable_default_cache() cls._requests_session_cached.settings.only_if_cached = enabled + @classmethod + def ci_mode(cls, enabled: bool): + """Enable or disable CI mode. + + In this mode, cached requests will be reused even if they are expired. + Only uncached data will actually be requested and is then cached. This + means, as long as CI mode is enabled, every request is only ever made + once and reused indefinetly. + + This serves two purposes. First, reduce the number of requests that is + sent on when a large number of tests is run in parallel, potentially + in multiple environments simultaneously. Second, make test runs more + predictable because data usually does not change between runs. + """ + cls._ci_mode = enabled + @classmethod def _convert_size(cls, size_bytes): # https://stackoverflow.com/questions/5194057/better-way-to-convert-file-sizes-in-python # noqa: E501 if size_bytes == 0: