@@ -74,6 +74,9 @@ class Field(BaseField):
74
74
def __post_init__ (self ) -> None :
75
75
"""
76
76
Compile the JSONPath expression after initialization.
77
+
78
+ This method is called automatically after the dataclass is initialized to ensure that the JSONPath expression is compiled and ready for use.
79
+ Since the dataclass is frozen, we cannot modify its attributes directly in the constructor and must use `__setattr__` to set the `_jsonpath` attribute.
77
80
"""
78
81
super ().__setattr__ ("_jsonpath" , parse (self .jsonpath ))
79
82
@@ -121,6 +124,11 @@ class ConfigurationVersion:
121
124
"""
122
125
Represents a specific version of a configuration.
123
126
127
+ This class is designed to be immutable (frozen) and hashable so it can be safely used as a key in an LRU cache.
128
+ The `retry_count` and `retry_at` attributes are intentionally excluded from the hash calculation and immutability,
129
+ because they are mutable fields used for tracking API retry logic. These fields are not relevant for caching or equality,
130
+ and should be updated by calling `__setattr__` directly, since the dataclass is otherwise frozen.
131
+
124
132
:param id: The configuration ID.
125
133
:param version: The version number.
126
134
:param contentUrl: URL to fetch the configuration content.
@@ -132,7 +140,7 @@ class ConfigurationVersion:
132
140
version : int
133
141
contentUrl : str
134
142
sha256sum : str
135
- valid_from : Optional [ float ] # timestamp ms
143
+ valid_from : float # timestamp ms
136
144
retry_count : int = dataclasses .field (default = 0 , hash = False , init = False )
137
145
retry_at : int = dataclasses .field (default = sys .maxsize , hash = False , init = False )
138
146
@@ -148,7 +156,7 @@ def from_event(cls, event: Event) -> "ConfigurationVersion":
148
156
149
157
raw_valid_from = event ["metadata" ]["valid_from" ]
150
158
if raw_valid_from is None :
151
- valid_from : Optional [ float ] = None
159
+ valid_from : float = 0
152
160
else :
153
161
# TODO python 3.11: Use `datetime.fromisoformat` when additional format are available
154
162
try :
@@ -251,37 +259,21 @@ def find_valid_version(self, timestamp: int) -> Optional[ConfigurationVersion]:
251
259
252
260
# Check if the next version has become valid (timestamp has moved forward)
253
261
# If so, recalculate all cached versions
254
- if self .next_version :
255
- # If next version has no valid_from date, it's always valid
256
- if self .next_version .valid_from is None :
257
- self .previous_version , self .version , self .next_version = (
258
- self ._find_versions (timestamp )
259
- )
260
- return self .version
261
- # If next version's valid_from is before or at the timestamp, it's valid
262
- if self .next_version .valid_from <= timestamp :
263
- self .previous_version , self .version , self .next_version = (
264
- self ._find_versions (timestamp )
265
- )
266
- return self .version
262
+ if self .next_version and self .next_version .valid_from <= timestamp :
263
+ self .previous_version , self .version , self .next_version = (
264
+ self ._find_versions (timestamp )
265
+ )
266
+ return self .version
267
267
268
268
# Check if the current cached version is still valid for this timestamp
269
- # If version has no valid_from date, it's always valid
270
- if self .version .valid_from is None :
271
- return self .version
272
269
# If version's valid_from is before or at the timestamp, it's valid
273
270
if self .version .valid_from <= timestamp :
274
271
return self .version
275
272
276
273
# Fallback: check if the previous version is valid for this timestamp
277
274
# This can happen when messages are out of order and timestamp is before the current version's valid_from
278
- if self .previous_version :
279
- # If previous version has no valid_from date, it's always valid
280
- if self .previous_version .valid_from is None :
281
- return self .previous_version
282
- # If previous version's valid_from is before or at the timestamp, it's valid
283
- if self .previous_version .valid_from <= timestamp :
284
- return self .previous_version
275
+ if self .previous_version and self .previous_version .valid_from <= timestamp :
276
+ return self .previous_version
285
277
286
278
# If cached versions don't match, recalculate all versions for this timestamp
287
279
self .previous_version , self .version , self .next_version = self ._find_versions (
@@ -317,35 +309,31 @@ def _find_versions(
317
309
for _ , version in sorted (
318
310
self .versions .items (), reverse = True , key = lambda x : x [0 ]
319
311
):
320
- # Handle versions with no valid_from timestamp (always valid)
321
- if version .valid_from is None :
322
- # First version with no valid_from becomes the current version
323
- if current_version is None :
324
- current_version = version
325
- return previous_version , current_version , next_version
326
- # Second version with no valid_from becomes the previous version
327
- elif previous_version is None :
328
- previous_version = version
329
- # Early return since we have current and previous, no next needed for no-timestamp versions
330
- return previous_version , current_version , next_version
331
-
332
312
# Handle versions that are valid in the future (after the timestamp)
333
- elif version .valid_from > timestamp :
313
+ if version .valid_from > timestamp :
314
+ # If we already have a current version, if has a higher version number,
315
+ # so we can skip all future versions
316
+ if current_version is not None :
317
+ continue
318
+
334
319
# First future version becomes the next version
335
320
if next_version is None :
336
321
next_version = version
337
322
# If we find an earlier future version, it becomes the new next version
338
- elif (
339
- next_version .valid_from is not None
340
- and version .valid_from < next_version .valid_from
341
- ):
323
+ elif version .valid_from < next_version .valid_from :
342
324
next_version = version
343
325
344
326
# Handle versions that are valid at or before the timestamp
345
327
else : # version.valid_from <= timestamp
346
328
# First valid version becomes the current version
347
329
if current_version is None :
348
330
current_version = version
331
+ # We can short circuit if we find a version that is valid always valid.
332
+ # There is no need for a previous_version as the current is valid from the beginning.
333
+ # There is no need to look for a next_version either, it will have a lower version number
334
+ # since the loop is ordered by version number.
335
+ if current_version .valid_from == 0.0 :
336
+ return previous_version , current_version , next_version
349
337
# Second valid version becomes the previous version
350
338
elif previous_version is None :
351
339
previous_version = version
0 commit comments