# SPDX-License-Identifier: GPLv3
# Copyright © 2025 pygaindalf Rui Pinheiro
from .config.requests import RequestsConfig
from .config.cache import RequestsCacheBackend
from .filecache import CustomFileCache
from .session import CustomSession
from ..helpers import script_info
import requests_cache
import pathlib
import urllib.parse
import os
from typing import Any
[docs]
class RequestsManager:
_instance = None
[docs]
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(RequestsManager, cls).__new__(cls, *args, **kwargs)
cls._instance.initialized = False
return cls._instance
[docs]
def __init__(self):
pass
[docs]
def initialize(self, config: RequestsConfig | dict[str, Any], install : bool = False):
if not isinstance(config, RequestsConfig):
config = RequestsConfig.model_validate(config)
if self.initialized:
raise RuntimeError(f"Must not initialise {self.__class__.__name__} twice")
self.initialized = True
self.config = config
self.install()
def _create_custom_file_cache(self, **kwargs) -> CustomFileCache:
"""
Create a custom file cache instance based on the configuration.
"""
filecache = getattr(self, 'filecache', None)
if filecache is None:
filecache = CustomFileCache(**kwargs)
self.filecache = filecache
return filecache
def _get_config_kwargs(self) -> dict[str, Any]:
kwargs = self.config.cache.as_kwargs()
# We use a custom version of FileCache without any SQLite dependencies if the backend is FILESYSTEM
# Since we will be checking in the cache, we want every file to be human-readable
if kwargs['backend'] == RequestsCacheBackend.FILESYSTEM and script_info.is_unit_test():
kwargs['backend'] = self._create_custom_file_cache(**kwargs)
# If the backend is FILESYSTEM, we want to use human-readable cache keys
if kwargs['backend'] == RequestsCacheBackend.FILESYSTEM:
kwargs['key_fn'] = self.human_readable_key_fn
return kwargs
[docs]
def install(self):
"""
Install the requests_cache with the given configuration.
"""
requests_cache.install_cache(session_factory=CustomSession, **self._get_config_kwargs())
[docs]
def session(self) -> Any:
return CustomSession(**self._get_config_kwargs())
# MARK: Cache methods
[docs]
def human_readable_key_fn(
self,
request: requests_cache.models.AnyRequest,
ignored_parameters: requests_cache.cache_keys.ParamList = None,
match_headers: requests_cache.cache_keys.ParamList | bool = False,
serializer: Any = None,
**request_kwargs,
) -> str:
request = requests_cache.cache_keys.normalize_request(request, ignored_parameters)
### Parse the URL
url = urllib.parse.urlparse(request.url)
# Domain
domain = str(url.netloc)
assert domain, "Domain must not be empty in the request URL"
# URL Path
urlpath = pathlib.PurePosixPath(urllib.parse.unquote(url.path))
path_parts = [
domain,
*urlpath.parts[1:]
]
assert '/' not in path_parts, "Path parts must not contain a leading slash"
### Create a hash of the request using default implementation of create_key
key_hash = requests_cache.cache_keys.create_key(request, ignored_parameters, match_headers, serializer, **request_kwargs)
### Finish by combining the path and hash
relpath = pathlib.PurePath(*path_parts, key_hash)
assert not relpath.is_absolute(), "Relative path must not be absolute"
abspath = pathlib.PurePath(self.config.cache.cache_name_effective, relpath)
os.makedirs(abspath.parent, exist_ok=True)
return str(relpath)