Skip to content

Commit 24bedb6

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 c0bcd88 commit 24bedb6

File tree

1 file changed

+54
-17
lines changed
  • quixstreams/dataframe/joins/lookups/quix_configuration_service

1 file changed

+54
-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
@@ -238,33 +238,52 @@ def find_valid_version(self, timestamp: int) -> Optional[ConfigurationVersion]:
238238
239239
:returns: Optional[ConfigurationVersion]: The valid version, or None if not found.
240240
"""
241+
# No versions exist
241242
if not self.versions:
242243
return None
244+
245+
# If no version is cached yet, find and cache the versions for this timestamp
243246
if self.version is None:
244247
self.previous_version, self.version, self.next_version = (
245248
self._find_versions(timestamp)
246249
)
247250
return self.version
248-
if (
249-
self.next_version
250-
and self.next_version.valid_from
251-
and self.next_version.valid_from <= timestamp
252-
):
253-
self.previous_version, self.version, self.next_version = (
254-
self._find_versions(timestamp)
255-
)
256-
return self.version
257-
if self.version:
258-
if self.version.valid_from is None:
251+
252+
# Check if the next version has become valid (timestamp has moved forward)
253+
# 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+
)
259260
return self.version
260-
if self.version.valid_from <= timestamp:
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+
)
261266
return self.version
267+
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+
# If version's valid_from is before or at the timestamp, it's valid
273+
if self.version.valid_from <= timestamp:
274+
return self.version
275+
276+
# Fallback: check if the previous version is valid for this timestamp
277+
# This can happen when messages are out of order and timestamp is before the current version's valid_from
262278
if self.previous_version:
279+
# If previous version has no valid_from date, it's always valid
263280
if self.previous_version.valid_from is None:
264281
return self.previous_version
282+
# If previous version's valid_from is before or at the timestamp, it's valid
265283
if self.previous_version.valid_from <= timestamp:
266284
return self.previous_version
267285

286+
# If cached versions don't match, recalculate all versions for this timestamp
268287
self.previous_version, self.version, self.next_version = self._find_versions(
269288
timestamp
270289
)
@@ -293,27 +312,45 @@ def _find_versions(
293312
current_version: Optional[ConfigurationVersion] = None
294313
next_version: Optional[ConfigurationVersion] = None
295314

315+
# Iterate through versions in descending order (highest version number first)
316+
# This ensures we process the most recent versions first
296317
for _, version in sorted(
297318
self.versions.items(), reverse=True, key=lambda x: x[0]
298319
):
320+
# Handle versions with no valid_from timestamp (always valid)
299321
if version.valid_from is None:
322+
# First version with no valid_from becomes the current version
300323
if current_version is None:
301324
current_version = version
325+
return previous_version, current_version, next_version
326+
# Second version with no valid_from becomes the previous version
302327
elif previous_version is None:
303328
previous_version = version
329+
# Early return since we have current and previous, no next needed for no-timestamp versions
304330
return previous_version, current_version, next_version
331+
332+
# Handle versions that are valid in the future (after the timestamp)
305333
elif version.valid_from > timestamp:
334+
# First future version becomes the next version
306335
if next_version is None:
307336
next_version = version
337+
# If we find an earlier future version, it becomes the new next version
308338
elif (
309339
next_version.valid_from is not None
310340
and version.valid_from < next_version.valid_from
311341
):
312342
next_version = version
313-
elif current_version is None:
314-
current_version = version
315-
elif previous_version is None:
316-
previous_version = version
317-
return previous_version, current_version, next_version
318343

344+
# Handle versions that are valid at or before the timestamp
345+
else: # version.valid_from <= timestamp
346+
# First valid version becomes the current version
347+
if current_version is None:
348+
current_version = version
349+
# Second valid version becomes the previous version
350+
elif previous_version is None:
351+
previous_version = version
352+
# Early return since we have found current and previous versions
353+
return previous_version, current_version, next_version
354+
355+
# Return the final state of all three version slots
319356
return previous_version, current_version, next_version

0 commit comments

Comments
 (0)