1212from datetime import datetime , timezone
1313from pathlib import Path
1414from tempfile import TemporaryDirectory
15- from typing import Annotated
15+ from typing import Annotated , Generic , TypeVar
1616from urllib .parse import urlparse , urlunparse
1717
1818import sh
@@ -56,20 +56,16 @@ class AnyUrlWithoutHost(AnyUrl):
5656
5757ConfigSourceUrl = 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
164199class BaseGitConfigSource (ConfigSource ):
0 commit comments