diff options
| author | Jordan Cook <jordan.cook@pioneer.com> | 2022-06-17 23:00:45 -0500 |
|---|---|---|
| committer | Jordan Cook <jordan.cook.git@proton.me> | 2023-03-01 17:37:20 -0600 |
| commit | 5ce540ea551a76c75509800280a19379c04ad123 (patch) | |
| tree | 08afd116721ccf7290635081808cf2ec535cb33e /requests_cache | |
| parent | e51acf47868fc152d6a80c92cbf1e4d7d19a3a8b (diff) | |
| download | requests-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.py | 142 | ||||
| -rw-r--r-- | requests_cache/policy/settings.py | 5 |
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) |
