Skip to content

feat: Replace the expiringdict dependency with an internal cache - #563

Merged
jsonbailey merged 2 commits into
mainfrom
jb/sdk-3466/drop-expiringdict
Nov 5, 2026
Merged

jsonbailey merged 2 commits into
mainfrom
jb/sdk-3466/drop-expiringdict

Conversation

@jsonbailey

@jsonbailey jsonbailey commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Remove the expiringdict dependency. The SDK now uses a small private class, ldclient.impl.expiring_cache.ExpiringCache. The persistent-store cache and the Big Segment membership caches (sync and async) use it.

expiringdict has had no release since 2022, and the SDK uses only a few of its features. The new class keeps this behavior:

  • Each entry expires a fixed time after it is written. Reads do not extend its life.
  • When the cache is full, the first-written entry is evicted (FIFO, not LRU).
  • Replacing an existing key does not evict a different entry.
  • All operations are thread safe. The callers do not lock around the cache.

Two small differences:

  • Expiry uses time.monotonic(), not time.time(), so wall-clock changes no longer affect it.
  • Rewriting an existing key always moves it to the newest eviction position. In expiringdict 1.2.2, a rewrite keeps the key's original position unless the cache is full.

Both projects are Apache 2.0. The class is new code, not a copy.

This ships in the current major version. An application that imports expiringdict without declaring it as its own dependency must now add it.


Note

Overview
Removes the expiringdict dependency and wires caching through a new in-tree ExpiringCache (ldclient.impl.expiring_cache).

Persistent feature-store caching (CachingStoreWrapper) and Big Segment membership caches (sync and async managers) now instantiate ExpiringCache with the same capacity and TTL settings as before. The new type is thread-safe and exposes the dict-like surface those call sites already use (get, __setitem__, pop, clear, __len__).

Semantics are intentionally aligned with the old library: TTL is measured from last write, reads do not refresh expiry, and overflow evicts the first-written entry (FIFO, not LRU). One deliberate difference: expiry uses time.monotonic() instead of wall clock time, so NTP or manual clock changes no longer shorten or extend cache lifetimes.

Packaging: expiringdict is dropped from main and docs optional dependencies. A large dedicated test suite covers TTL, eviction, and concurrency; an existing cache-lifecycle test comment is updated to refer to “the old cache” generically.

Reviewed by Cursor Bugbot for commit b60b3fd. Bugbot is set up for automated code reviews on this repo. Configure here.

@jsonbailey
jsonbailey marked this pull request as ready for review November 4, 2026 23:31
@jsonbailey
jsonbailey requested a review from a team as a code owner November 4, 2026 23:31

@keelerm84 keelerm84 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the open question: I'd ship this in a minor release. An application that imports expiringdict without declaring it depends on our internals, and a changelog note covers that case.

Comment thread ldclient/impl/expiring_cache.py Resolved
@jsonbailey jsonbailey changed the title chore: Replace the expiringdict dependency with an internal cache fix: Replace the expiringdict dependency with an internal cache Nov 5, 2026
@jsonbailey jsonbailey changed the title fix: Replace the expiringdict dependency with an internal cache feat: Replace the expiringdict dependency with an internal cache Nov 5, 2026
@jsonbailey
jsonbailey merged commit ca34aad into main Nov 5, 2026
17 checks passed
@jsonbailey
jsonbailey deleted the jb/sdk-3466/drop-expiringdict branch November 5, 2026 18:31
jsonbailey pushed a commit that referenced this pull request Nov 5, 2026
🤖 I have created a release *beep* *boop*
---


##
[9.20.0](9.19.0...9.20.0)
(2026-11-05)


### Features

* Replace the expiringdict dependency with an internal cache
([#563](#563))
([ca34aad](ca34aad))


### Bug Fixes

* Ignore FDv2 objects with unrecognized kinds
([#541](#541))
([d5eeb80](d5eeb80))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Release Please PR that **bumps the SDK from 9.19.0 to 9.20.0** across
`pyproject.toml`, `ldclient/version.py`,
`.release-please-manifest.json`, and `PROVENANCE.md`, and adds the
**9.20.0** section to `CHANGELOG.md`.
> 
> That release (already merged on main) ships **replacement of the
`expiringdict` dependency with an internal `ExpiringCache`**, and a fix
to **skip FDv2 objects with unrecognized kinds** instead of failing on
them.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
af7a7b3. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants