Skip to content

Commit c7129e5

Browse files
committed
fix: Refactor Quix Config Lookup version selection and add comprehensive tests
- Refactor Configuration.find_valid_version and _find_versions to handle edge cases - Add tests the quix configuration service lookup
1 parent 5cc2844 commit c7129e5

File tree

2 files changed

+1171
-17
lines changed

2 files changed

+1171
-17
lines changed

quixstreams/dataframe/joins/lookups/quix_configuration_service/models.py

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -230,33 +230,52 @@ def find_valid_version(self, timestamp: int) -> Optional[ConfigurationVersion]:
230230
231231
:returns: Optional[ConfigurationVersion]: The valid version, or None if not found.
232232
"""
233+
# No versions exist
233234
if not self.versions:
234235
return None
236+
237+
# If no version is cached yet, find and cache the versions for this timestamp
235238
if self.version is None:
236239
self.previous_version, self.version, self.next_version = (
237240
self._find_versions(timestamp)
238241
)
239242
return self.version
240-
if (
241-
self.next_version
242-
and self.next_version.valid_from
243-
and self.next_version.valid_from <= timestamp
244-
):
245-
self.previous_version, self.version, self.next_version = (
246-
self._find_versions(timestamp)
247-
)
248-
return self.version
249-
if self.version:
250-
if self.version.valid_from is None:
243+
244+
# Check if the next version has become valid (timestamp has moved forward)
245+
# If so, recalculate all cached versions
246+
if self.next_version:
247+
# If next version has no valid_from date, it's always valid
248+
if self.next_version.valid_from is None:
249+
self.previous_version, self.version, self.next_version = (
250+
self._find_versions(timestamp)
251+
)
251252
return self.version
252-
if self.version.valid_from <= timestamp:
253+
# If next version's valid_from is before or at the timestamp, it's valid
254+
if self.next_version.valid_from <= timestamp:
255+
self.previous_version, self.version, self.next_version = (
256+
self._find_versions(timestamp)
257+
)
253258
return self.version
259+
260+
# Check if the current cached version is still valid for this timestamp
261+
# If version has no valid_from date, it's always valid
262+
if self.version.valid_from is None:
263+
return self.version
264+
# If version's valid_from is before or at the timestamp, it's valid
265+
if self.version.valid_from <= timestamp:
266+
return self.version
267+
268+
# Fallback: check if the previous version is valid for this timestamp
269+
# This can happen when messages are out of order and timestamp is before the current version's valid_from
254270
if self.previous_version:
271+
# If previous version has no valid_from date, it's always valid
255272
if self.previous_version.valid_from is None:
256273
return self.previous_version
274+
# If previous version's valid_from is before or at the timestamp, it's valid
257275
if self.previous_version.valid_from <= timestamp:
258276
return self.previous_version
259277

278+
# If cached versions don't match, recalculate all versions for this timestamp
260279
self.previous_version, self.version, self.next_version = self._find_versions(
261280
timestamp
262281
)
@@ -285,27 +304,45 @@ def _find_versions(
285304
current_version: Optional[ConfigurationVersion] = None
286305
next_version: Optional[ConfigurationVersion] = None
287306

307+
# Iterate through versions in descending order (highest version number first)
308+
# This ensures we process the most recent versions first
288309
for _, version in sorted(
289310
self.versions.items(), reverse=True, key=lambda x: x[0]
290311
):
312+
# Handle versions with no valid_from timestamp (always valid)
291313
if version.valid_from is None:
314+
# First version with no valid_from becomes the current version
292315
if current_version is None:
293316
current_version = version
317+
return previous_version, current_version, next_version
318+
# Second version with no valid_from becomes the previous version
294319
elif previous_version is None:
295320
previous_version = version
321+
# Early return since we have current and previous, no next needed for no-timestamp versions
296322
return previous_version, current_version, next_version
323+
324+
# Handle versions that are valid in the future (after the timestamp)
297325
elif version.valid_from > timestamp:
326+
# First future version becomes the next version
298327
if next_version is None:
299328
next_version = version
329+
# If we find an earlier future version, it becomes the new next version
300330
elif (
301331
next_version.valid_from is not None
302332
and version.valid_from < next_version.valid_from
303333
):
304334
next_version = version
305-
elif current_version is None:
306-
current_version = version
307-
elif previous_version is None:
308-
previous_version = version
309-
return previous_version, current_version, next_version
310335

336+
# Handle versions that are valid at or before the timestamp
337+
else: # version.valid_from <= timestamp
338+
# First valid version becomes the current version
339+
if current_version is None:
340+
current_version = version
341+
# Second valid version becomes the previous version
342+
elif previous_version is None:
343+
previous_version = version
344+
# Early return since we have found current and previous versions
345+
return previous_version, current_version, next_version
346+
347+
# Return the final state of all three version slots
311348
return previous_version, current_version, next_version

0 commit comments

Comments
 (0)