Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@sfc-gh-jkinkead
Copy link
Contributor

Describe your changes

Add a subclass of TTLCache that supports release hooks.

This will be used to implement resource caches that close resources when they leave the cache. See the spec.

Screenshot or video (only for visual changes)

GitHub Issue Link (if applicable)

Testing Plan

  • Unit Tests (JS and/or Python)

Added.


Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

This will be used to implement resource caches that close resources when they leave the cache.
Copilot AI review requested due to automatic review settings December 19, 2025 00:12
@snyk-io
Copy link
Contributor

snyk-io bot commented Dec 19, 2025

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 19, 2025

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-13415/streamlit-1.52.2-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-13415.streamlit.app (☁️ Deploy here if not accessible)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a TTLCleanupCache class that extends cachetools.TTLCache to support release hooks, enabling automatic cleanup of resources when they are evicted from the cache. This is part of implementing session-scoped connections and resource caches.

Key changes:

  • Adds TTLCleanupCache class that overrides popitem() and expire() to call release hooks when items are removed
  • Introduces OnRelease type alias for cleanup callback functions
  • Provides comprehensive unit tests covering both max-size and TTL-based eviction scenarios

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
lib/streamlit/runtime/caching/ttl_cleanup_cache.py New module implementing TTLCleanupCache class with release hook functionality
lib/streamlit/runtime/caching/cache_utils.py Adds OnRelease type alias and reorganizes imports to support the new cache class
lib/tests/streamlit/runtime/caching/ttl_cleanup_cache_test.py New test file with unit tests verifying release hook behavior for size-based and TTL-based evictions

@lukasmasuch lukasmasuch added security-assessment-completed Security assessment has been completed for PR change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Dec 19, 2025
@lukasmasuch
Copy link
Collaborator

@cursor review

@sfc-gh-jkinkead sfc-gh-jkinkead requested a review from a team as a code owner December 19, 2025 18:34
@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Dec 22, 2025
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Dec 22, 2025
@github-actions
Copy link
Contributor

Summary

This PR adds a new TTLCleanupCache class that extends cachetools.TTLCache to support release hooks (callbacks) when items are removed from the cache. This is foundational infrastructure for implementing session-scoped connections and cache expiration hooks as described in the linked spec. The PR also bumps the minimum cachetools version from 4.0 to 5.5, which is required because the expire() method only returns expired items starting in version 5.5.

Key changes:

  • New file: lib/streamlit/runtime/caching/ttl_cleanup_cache.py - TTLCleanupCache class
  • New file: lib/tests/streamlit/runtime/caching/ttl_cleanup_cache_test.py - Unit tests
  • Added OnRelease type alias to cache_utils.py
  • Bumped cachetools minimum version from 4.0 to 5.5

Code Quality

Issues Found:

  1. Missing from __future__ import annotations in lib/streamlit/runtime/caching/ttl_cleanup_cache.py (line 1-27)

    All other files in the lib/streamlit/runtime/caching/ directory include this import. This is a codebase standard that should be followed for consistency and forward compatibility.

  2. Missing from __future__ import annotations in lib/tests/streamlit/runtime/caching/ttl_cleanup_cache_test.py

    All other test files in the same directory include this import.

  3. Test file lacks complete type annotations

    According to the Python unit test guide: "New tests should be fully annotated with types." The test class TestTTLCleanupCache is missing return type annotations on test methods (should be -> None).

Suggestions:

  1. Incomplete on_release coverage for manual removal operations

    The implementation only overrides popitem() and expire(), which handle automatic evictions (LRU and TTL). However, manual removal operations (clear(), del cache[key], pop(key)) will NOT call on_release.

    The spec states: "This will be called whenever an item is removed from the cache, including via clear calls."

    If this is intentional (only handling automatic evictions), this behavior should be clearly documented in the class docstring. If not, consider overriding __delitem__, pop, and clear methods as well.

  2. Exception handling in on_release

    Consider what happens if the on_release callback raises an exception. Currently, an exception would propagate up and could leave the cache in an inconsistent state (e.g., in expire(), some items might have been released while others haven't). Consider wrapping on_release calls in try/except with logging.

Test Coverage

The tests cover the two main automatic eviction scenarios:

  • ✅ LRU eviction when cache hits size limit (test_releases_when_hits_size)
  • ✅ TTL expiration (test_releases_when_hits_ttl)

Missing Test Cases:

  1. Empty cache behavior - What happens when expire() is called on an empty cache?
  2. Manual deletion - Tests for del cache[key], cache.pop(key), and cache.clear() to document whether they trigger on_release (even if the answer is "no, they don't")
  3. Exception in on_release - Verify the behavior when the callback raises an exception

Backwards Compatibility

⚠️ Breaking Change: cachetools version bump from 4.0 to 5.5

This is a significant version jump. Users who have cachetools==4.x pinned in their requirements may experience dependency resolution failures when upgrading Streamlit.

The bump is necessary because:

  • The expire() method in TTLCache only returns expired items starting in cachetools 5.5
  • Earlier versions (4.x, 5.0-5.4) either don't have expire() or it returns None

Recommendation: Consider adding a note in the PR description or release notes about this dependency change.

Security & Risk

  • Low risk: This is internal infrastructure with no direct user-facing API changes
  • No security concerns: The code doesn't handle sensitive data or external inputs
  • Regression risk is minimal: The new class is additive and existing cache implementations are unchanged

Recommendations

Required (blocking):

  1. Add from __future__ import annotations to the top of lib/streamlit/runtime/caching/ttl_cleanup_cache.py (after the license header)

  2. Add from __future__ import annotations to the top of lib/tests/streamlit/runtime/caching/ttl_cleanup_cache_test.py (after the license header)

  3. Add return type annotations (-> None) to test methods in the test file

Recommended (non-blocking):

  1. Clarify in the TTLCleanupCache docstring that on_release is only called for automatic evictions (LRU/TTL), not manual deletions - OR implement __delitem__, pop, and clear overrides to call on_release for all removal operations

  2. Consider wrapping self._on_release(value) calls in try/except to prevent callback exceptions from breaking cache operations:

    def _safe_release(self, value: V) -> None:
        try:
            self._on_release(value)
        except Exception:
            _LOGGER.exception("Error in on_release callback")
  3. Add edge case tests for empty cache behavior and document the behavior when manual deletion methods are used

Verdict

CHANGES REQUESTED: The PR is well-structured and the implementation is solid, but it needs to conform to codebase standards by adding from __future__ import annotations to both new files and adding proper type annotations to the test methods. These are straightforward fixes that should be addressed before merging.


This is an automated AI review. Please verify the feedback and use your judgment.

@github-actions github-actions bot added the do-not-merge PR is blocked from merging label Dec 22, 2025
@lukasmasuch
Copy link
Collaborator

Overall, lgtm 👍 but I think these two aspects from the AI review are probably valid:

Consider what happens if the on_release callback raises an exception. Currently, an exception would propagate up and could leave the cache in an inconsistent state

It might be good to ensure that the cache is always cleaned, but I'm unsure whether we should simply log the error to the console or display it to the user.

The implementation only overrides popitem() and expire(), which handle automatic evictions (LRU and TTL). However, manual removal operations (clear(), del cache[key], pop(key)) will NOT call on_release.

Based on the spec, we likely want to call on_release whenever the item is cleaned through any method?

@lukasmasuch lukasmasuch removed the do-not-merge PR is blocked from merging label Dec 22, 2025
@sfc-gh-jkinkead
Copy link
Contributor Author

Consider what happens if the on_release callback raises an exception. Currently, an exception would propagate up and could leave the cache in an inconsistent state

This is not how the base class works.

The implementation only overrides popitem() and expire(), which handle automatic evictions (LRU and TTL). However, manual removal operations (clear(), del cache[key], pop(key)) will NOT call on_release.

I already replied to the AI on this, but it doesn't seem smart enough to listen to older comments. The base class handles this.

Copy link
Collaborator

@lukasmasuch lukasmasuch left a comment

Choose a reason for hiding this comment

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

Ok 👍 I guess if an exception happens within the release function, an uncaught exception error is shown to the user?

@sfc-gh-jkinkead
Copy link
Contributor Author

an uncaught exception error is shown to the user?

We'll be catching it and wrapping it if possible - but yes, it would end up shown to the user. The wrapping will happen in a higher layer.

@sfc-gh-jkinkead sfc-gh-jkinkead merged commit e5b4781 into develop Dec 22, 2025
51 checks passed
@sfc-gh-jkinkead sfc-gh-jkinkead deleted the jkinkead-add-ttl-cache-with-expires branch December 22, 2025 17:47
@lukasmasuch lukasmasuch added impact:internal PR changes only affect internal code and removed impact:users PR changes affect end users labels Dec 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:internal PR changes only affect internal code security-assessment-completed Security assessment has been completed for PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants