summaryrefslogtreecommitdiff
path: root/requests_cache
diff options
context:
space:
mode:
authorJordan Cook <jordan.cook@pioneer.com>2022-06-17 23:00:45 -0500
committerJordan Cook <jordan.cook.git@proton.me>2023-03-01 17:37:20 -0600
commit5ce540ea551a76c75509800280a19379c04ad123 (patch)
tree08afd116721ccf7290635081808cf2ec535cb33e /requests_cache
parente51acf47868fc152d6a80c92cbf1e4d7d19a3a8b (diff)
downloadrequests-cache-5ce540ea551a76c75509800280a19379c04ad123.tar.gz
WIP: Support multiple modules, opt-out mode, and implement uninstall with module_only
Diffstat (limited to 'requests_cache')
-rw-r--r--requests_cache/patcher.py142
-rw-r--r--requests_cache/policy/settings.py5
2 files changed, 112 insertions, 35 deletions
diff --git a/requests_cache/patcher.py b/requests_cache/patcher.py
index f32ef9e..36143d6 100644
--- a/requests_cache/patcher.py
+++ b/requests_cache/patcher.py
@@ -26,42 +26,61 @@ else:
MIXIN_BASE = object
+# TODO: This is going to require thinking through many more edge cases. Lots of ways this could go wrong.
class ModuleCacheMixin(MIXIN_BASE):
- """Session mixin that optionally caches requests only if sent from specific modules"""
+ """Session mixin that only caches requests sent from specific modules. May be used in one of two
+ modes:
+
+ * Opt-in: caching is disabled by default, and enabled for modules in ``include_modules``
+ * Opt-out: caching is enabled by default, and disabled for modules in ``exclude_modules``
+
+ Args:
+ include_modules: List of modules to enable caching for
+ exclude_modules: List of modules to disable caching for
+ opt_in: Whether to use opt-in mode (``True``) or opt-out mode (``False``)
+ """
def __init__(
- self, *args, module_only: bool = False, modules: Optional[List[str]] = None, **kwargs
+ self,
+ *args,
+ include_modules: Optional[List[str]] = None,
+ exclude_modules: Optional[List[str]] = None,
+ opt_in: bool = True,
+ **kwargs,
):
super().__init__(*args, **kwargs)
- self.modules = modules or []
- self.module_only = module_only
+ self.include_modules = set(include_modules or [])
+ self.exclude_modules = set(exclude_modules or [])
+ self.opt_in = opt_in
def request(self, *args, **kwargs):
- if self._is_module_enabled():
+ if self.is_module_enabled(back=3):
return super().request(*args, **kwargs)
else:
return OriginalSession.request(self, *args, **kwargs)
- def _is_module_enabled(self) -> bool:
- if not self.module_only:
- return True
- return _calling_module(back=3) in self.modules
+ def is_module_enabled(self, back: int = 2) -> bool:
+ module = _calling_module(back=back)
+ if self.opt_in:
+ return module in self.include_modules
+ else:
+ return module not in self.exclude_modules
def enable_module(self):
- self.modules.append(_calling_module())
+ module = _calling_module()
+ if self.opt_in:
+ self.include_modules |= {module}
+ else:
+ self.exclude_modules -= {module}
+ logger.info(f'Caching enabled for {module}')
def disable_module(self):
- try:
- self.modules.remove(_calling_module())
- except ValueError:
- pass
-
-
-def _calling_module(back: int = 2) -> str:
- """Get the name of the module ``back`` frames up in the call stack"""
- frame = inspect.stack()[back].frame
- module = inspect.getmodule(frame)
- return getattr(module, '__name__', '')
+ module = _calling_module()
+ if self.opt_in:
+ self.include_modules -= {module}
+ else:
+ self.exclude_modules |= {module}
+ logger.info(f'Caching disabled for {module}')
def install_cache(
@@ -86,24 +105,27 @@ def install_cache(
:py:class:`.CachedSession` or :py:class:`.CacheMixin`
"""
backend = init_backend(cache_name, backend, **kwargs)
- module = _calling_module()
+ if module_only:
+ modules = get_installed_modules() + [_calling_module()]
+ _install_modules(cache_name, backend, session_factory, modules, **kwargs)
- class _ConfiguredCachedSession(ModuleCacheMixin, session_factory): # type: ignore # See mypy issue #5865
+ class _ConfiguredCachedSession(session_factory): # type: ignore # See mypy issue #5865
def __init__(self):
- super().__init__(
- cache_name=cache_name,
- backend=backend,
- module_only=module_only,
- modules=[module],
- **kwargs,
- )
+ super().__init__(cache_name=cache_name, backend=backend, **kwargs)
_patch_session_factory(_ConfiguredCachedSession)
def uninstall_cache(module_only: bool = False):
- """Disable the cache by restoring the original :py:class:`requests.Session`"""
- _patch_session_factory(OriginalSession)
+ """Disable the cache by restoring the original :py:class:`requests.Session`
+
+ Args:
+ module_only: Only uninstall the cache for the current module
+ """
+ if module_only:
+ _uninstall_module()
+ else:
+ _patch_session_factory(OriginalSession)
@contextmanager
@@ -153,14 +175,18 @@ def get_installed_modules() -> List[str]:
"""Get all modules that have caching installed"""
session = requests.Session()
if isinstance(session, ModuleCacheMixin):
- return session.modules
+ return list(session.include_modules)
else:
return []
def is_installed() -> bool:
"""Indicate whether or not requests-cache is currently installed"""
- return isinstance(requests.Session(), CachedSession)
+ session = requests.Session()
+ if isinstance(session, ModuleCacheMixin):
+ return session.is_module_enabled()
+ else:
+ return isinstance(session, CachedSession)
def clear():
@@ -187,6 +213,54 @@ def remove_expired_responses():
delete(expired=True)
+def _calling_module(back: int = 2) -> str:
+ """Get the name of the module ``back`` frames up in the call stack"""
+ frame = inspect.stack()[back].frame
+ module = inspect.getmodule(frame)
+ return getattr(module, '__name__', '')
+
+
+def _install_modules(
+ cache_name: str,
+ backend: BackendSpecifier,
+ session_factory: Type[OriginalSession],
+ modules: List[str],
+ **kwargs,
+):
+ """Install the cache for specific modules"""
+
+ class _ConfiguredCachedSession(ModuleCacheMixin, session_factory): # type: ignore # See mypy issue #5865
+ def __init__(self):
+ super().__init__(
+ cache_name=cache_name, backend=backend, include_modules=modules, **kwargs
+ )
+
+ _patch_session_factory(_ConfiguredCachedSession)
+
+
+def _uninstall_module():
+ """Uninstall the cache for the current module"""
+ session = requests.Session()
+ if not isinstance(session, ModuleCacheMixin):
+ return
+
+ modules = get_installed_modules()
+ modules.remove(_calling_module())
+
+ # No enabled modules remaining; restore the original Session
+ if not modules:
+ uninstall_cache()
+ # Reinstall cache with updated modules
+ else:
+ _install_modules(
+ session.cache.cache_name,
+ session.cache,
+ CachedSession,
+ modules,
+ **session.settings.to_dict(),
+ )
+
+
def _patch_session_factory(session_factory: Type[OriginalSession] = CachedSession):
logger.debug(f'Patching requests.Session with class: {session_factory.__name__}')
requests.Session = requests.sessions.Session = session_factory # type: ignore
diff --git a/requests_cache/policy/settings.py b/requests_cache/policy/settings.py
index 8e8e26b..33c382b 100644
--- a/requests_cache/policy/settings.py
+++ b/requests_cache/policy/settings.py
@@ -1,6 +1,6 @@
from typing import Dict, Iterable, Union
-from attr import define, field
+from attr import asdict, define, field
from .._utils import get_valid_kwargs
from ..models import RichMixin
@@ -56,3 +56,6 @@ class CacheSettings(RichMixin):
if 'include_get_headers' in kwargs:
kwargs['match_headers'] = kwargs.pop('include_get_headers')
return kwargs
+
+ def to_dict(self):
+ return asdict(self)