Skip to content

Commit b556dbc

Browse files
authored
refactor: extract CacheableSource class (#850)
1 parent ef2162b commit b556dbc

1 file changed

Lines changed: 74 additions & 39 deletions

File tree

diracx-core/src/diracx/core/config/sources.py

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from datetime import datetime, timezone
1313
from pathlib import Path
1414
from tempfile import TemporaryDirectory
15-
from typing import Annotated
15+
from typing import Annotated, Generic, TypeVar
1616
from urllib.parse import urlparse, urlunparse
1717

1818
import sh
@@ -56,20 +56,16 @@ class AnyUrlWithoutHost(AnyUrl):
5656

5757
ConfigSourceUrl = Annotated[AnyUrlWithoutHost, BeforeValidator(_apply_default_scheme)]
5858

59+
T = TypeVar("T")
5960

60-
class ConfigSource(metaclass=ABCMeta):
61-
"""Abstract class for the configuration source.
6261

63-
This class takes care of the expected caching and locking logic. Subclasses
64-
are responsible for implementing the actual logic to find revisions and
65-
reading the configuration.
66-
"""
62+
class CacheableSource(Generic[T], metaclass=ABCMeta):
63+
"""Abstract base class for sources that can be cached.
6764
68-
# Keep a mapping between the scheme and the class
69-
__registry: dict[str, type["ConfigSource"]] = {}
70-
scheme: str
65+
Handles the caching of the latest revision and its content using a two-level cache.
66+
"""
7167

72-
def __init__(self, *, backend_url: ConfigSourceUrl) -> None:
68+
def __init__(self):
7369
# Revision cache is used to store the latest revision and its
7470
# modification date. This cache has two TTLs, one which triggers the
7571
# background refresh and the other which is results in a hard failure.
@@ -96,13 +92,66 @@ def latest_revision(self) -> tuple[str, datetime]:
9692
"""
9793

9894
@abstractmethod
99-
def read_raw(self, hexsha: str, modified: datetime) -> Config:
95+
def read_raw(self, hexsha: str, modified: datetime) -> T:
10096
"""Abstract method.
10197
102-
Return the Config object that corresponds to the
103-
specific hash
104-
The `modified` parameter is just added as a attribute to the config.
98+
Return the Source object that corresponds to the specific hash
99+
The `modified` parameter is just added as a attribute to the source.
100+
"""
101+
102+
def read(self) -> T:
103+
"""Load the source from the backend with appropriate caching.
104+
105+
:raises: diracx.core.exceptions.NotReadyError if the source is being loaded still
106+
:raises: git.exc.BadName if version does not exist
107+
"""
108+
hexsha = self._revision_cache.get(
109+
"latest_revision", self._read_work, blocking=True
110+
)
111+
return self._content_cache[hexsha]
112+
113+
async def read_non_blocking(self) -> T:
114+
"""Load the source from the backend with appropriate caching.
115+
116+
:raises: diracx.core.exceptions.NotReadyError if the source is being loaded still
117+
:raises: git.exc.BadName if version does not exist
118+
"""
119+
hexsha = self._revision_cache.get(
120+
"latest_revision", self._read_work, blocking=False
121+
)
122+
return self._content_cache[hexsha]
123+
124+
def _read_work(self) -> str:
125+
"""Work function for the thread pool of `self._revision_cache`.
126+
127+
This function ensures that the latest revision is loaded into the
128+
content cache before it is admitted into the revision cache.
105129
"""
130+
hexsha, modified = self.latest_revision()
131+
if hexsha not in self._content_cache:
132+
self._content_cache[hexsha] = self.read_raw(hexsha, modified)
133+
return hexsha
134+
135+
def clear_caches(self):
136+
"""Clear the caches."""
137+
self._revision_cache.clear()
138+
self._content_cache.clear()
139+
140+
141+
class ConfigSource(CacheableSource[Config]):
142+
"""Abstract class for the configuration source.
143+
144+
This class takes care of the expected caching and locking logic. Subclasses
145+
are responsible for implementing the actual logic to find revisions and
146+
reading the configuration.
147+
"""
148+
149+
# Keep a mapping between the scheme and the class
150+
__registry: dict[str, type["ConfigSource"]] = {}
151+
scheme: str
152+
153+
def __init__(self, *, backend_url: ConfigSourceUrl) -> None:
154+
super().__init__()
106155

107156
def __init_subclass__(cls) -> None:
108157
"""Keep a record of <scheme: class>."""
@@ -122,43 +171,29 @@ def create_from_url(
122171
url = TypeAdapter(ConfigSourceUrl).validate_python(str(backend_url))
123172
return cls.__registry[url.scheme](backend_url=url)
124173

174+
@abstractmethod
175+
def read_raw(self, hexsha: str, modified: datetime) -> Config:
176+
"""Abstract method.
177+
178+
Return the Config object that corresponds to the specific hash
179+
The `modified` parameter is just added as a attribute to the config.
180+
"""
181+
125182
def read_config(self) -> Config:
126183
"""Load the configuration from the backend with appropriate caching.
127184
128185
:raises: diracx.core.exceptions.NotReadyError if the config is being loaded still
129186
:raises: git.exc.BadName if version does not exist
130187
"""
131-
hexsha = self._revision_cache.get(
132-
"latest_revision", self._read_config_work, blocking=True
133-
)
134-
return self._content_cache[hexsha]
188+
return super().read()
135189

136190
async def read_config_non_blocking(self) -> Config:
137191
"""Load the configuration from the backend with appropriate caching.
138192
139193
:raises: diracx.core.exceptions.NotReadyError if the config is being loaded still
140194
:raises: git.exc.BadName if version does not exist
141195
"""
142-
hexsha = self._revision_cache.get(
143-
"latest_revision", self._read_config_work, blocking=False
144-
)
145-
return self._content_cache[hexsha]
146-
147-
def _read_config_work(self) -> str:
148-
"""Work function for the thread pool of `self._revision_cache`.
149-
150-
This function ensures that the latest revision is loaded into the
151-
content cache before it is admitted into the revision cache.
152-
"""
153-
hexsha, modified = self.latest_revision()
154-
if hexsha not in self._content_cache:
155-
self._content_cache[hexsha] = self.read_raw(hexsha, modified)
156-
return hexsha
157-
158-
def clear_caches(self):
159-
"""Clear the caches."""
160-
self._revision_cache.clear()
161-
self._content_cache.clear()
196+
return await super().read_non_blocking()
162197

163198

164199
class BaseGitConfigSource(ConfigSource):

0 commit comments

Comments
 (0)