ttl-cache-decorator 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ * text=auto
2
+ *.sh text eol=lf
@@ -0,0 +1,38 @@
1
+ name: Documentation
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ tags:
7
+ - "v*"
8
+ permissions:
9
+ contents: write
10
+ concurrency:
11
+ group: docs-publish
12
+ cancel-in-progress: false
13
+ jobs:
14
+ publish-docs:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v7
18
+ with:
19
+ fetch-depth: 0
20
+ - uses: astral-sh/setup-uv@v6
21
+ with:
22
+ enable-cache: true
23
+ - name: Install dependencies
24
+ run: |
25
+ uv sync --group docs --no-install-project
26
+ uv pip install git+https://github.com/squidfunk/mike.git
27
+ - name: Configure git
28
+ run: |
29
+ git config user.name "${{ github.actor }}"
30
+ git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com"
31
+ - name: Publish dev docs
32
+ if: github.ref == 'refs/heads/main'
33
+ run: uv run mike deploy --push dev
34
+ - name: Publish release docs
35
+ if: startsWith(github.ref, 'refs/tags/v')
36
+ run: |
37
+ VERSION="${GITHUB_REF_NAME#v}"
38
+ uv run mike deploy --push --update-aliases "$VERSION" latest
@@ -0,0 +1,33 @@
1
+ name: Publish
2
+ on:
3
+ push:
4
+ tags:
5
+ - "v*"
6
+ workflow_run:
7
+ workflows:
8
+ - Release
9
+ types:
10
+ - completed
11
+ permissions:
12
+ id-token: write
13
+ jobs:
14
+ publish:
15
+ if: >-
16
+ github.event_name == 'push' ||
17
+ github.event.workflow_run.conclusion == 'success'
18
+ runs-on: ubuntu-latest
19
+ environment:
20
+ name: pypi
21
+ url: https://pypi.org/p/ttl-cache-decorator
22
+ steps:
23
+ - uses: actions/checkout@v7
24
+ with:
25
+ ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.ref }}
26
+ fetch-depth: 0
27
+ - uses: astral-sh/setup-uv@v6
28
+ with:
29
+ enable-cache: true
30
+ - name: Build distributions
31
+ run: uv build
32
+ - name: Publish to PyPI
33
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,43 @@
1
+ name: Release
2
+ on:
3
+ workflow_dispatch:
4
+ inputs:
5
+ bump:
6
+ description: Version bump
7
+ type: choice
8
+ options:
9
+ - patch
10
+ - minor
11
+ - major
12
+ default: patch
13
+ permissions:
14
+ contents: write
15
+ jobs:
16
+ release:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ # A personal access token is required so the pushed tag triggers the
20
+ # test/docs/publish workflows — pushes made with the default
21
+ # GITHUB_TOKEN never start new workflow runs.
22
+ - uses: actions/checkout@v7
23
+ with:
24
+ token: ${{ secrets.RELEASE_PAT || github.token }}
25
+ fetch-depth: 0
26
+ - uses: astral-sh/setup-uv@v6
27
+ - uses: taiki-e/install-action@v2
28
+ with:
29
+ tool: git-cliff
30
+ - name: Bump version
31
+ run: uv version --bump ${{ inputs.bump }}
32
+ - name: Generate changelog
33
+ run: git-cliff --config cliff.toml --tag "v$(uv version --short)" --output CHANGELOG.md
34
+ - name: Commit, tag and push
35
+ run: |
36
+ VERSION=$(uv version --short)
37
+ git config user.name "${{ github.actor }}"
38
+ git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com"
39
+ git add pyproject.toml uv.lock CHANGELOG.md
40
+ git commit -m "chore: release v$VERSION"
41
+ git tag -a "v$VERSION" -m "Release v$VERSION"
42
+ git push origin main
43
+ git push origin "v$VERSION"
@@ -0,0 +1,44 @@
1
+ name: Tests
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ tags:
7
+ - "v*"
8
+ pull_request:
9
+ workflow_run:
10
+ workflows:
11
+ - Release
12
+ types:
13
+ - completed
14
+ permissions:
15
+ contents: read
16
+ concurrency:
17
+ group: tests-${{ github.ref }}
18
+ cancel-in-progress: true
19
+ jobs:
20
+ test:
21
+ runs-on: ${{ matrix.os }}
22
+ strategy:
23
+ fail-fast: false
24
+ matrix:
25
+ os:
26
+ - ubuntu-latest
27
+ - windows-latest
28
+ - macos-latest
29
+ python-version:
30
+ - "3.12"
31
+ - "3.13"
32
+ - "3.14"
33
+ - "3.15"
34
+ steps:
35
+ - uses: actions/checkout@v7
36
+ - uses: astral-sh/setup-uv@v6
37
+ with:
38
+ enable-cache: true
39
+ - name: Set up Python ${{ matrix.python-version }}
40
+ run: uv python install ${{ matrix.python-version }}
41
+ - name: Install dependencies
42
+ run: uv sync --group test --python ${{ matrix.python-version }}
43
+ - name: Run tests
44
+ run: uv run pytest
@@ -0,0 +1,40 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+ .env
12
+ *.env.*
13
+ !.env.example
14
+
15
+ # Streamlit secrets
16
+ **/.streamlit/secrets.toml
17
+
18
+ # Database
19
+ .sqlite/*.db
20
+
21
+ # Big json files
22
+ full_describe.json
23
+
24
+ # Gitkeep
25
+
26
+ # MkDocs
27
+ site/
28
+ .cache/
29
+
30
+ .vscode/**
31
+ !.vscode/extensions.json
32
+ !.vscode/settings.json
33
+ !.vscode/launch.json
34
+ !project.code-snippets
35
+ .coverage
36
+
37
+ tests/temp/**
38
+ *.db
39
+
40
+ !.gitkeep
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,15 @@
1
+ {
2
+ "recommendations": [
3
+ "streetsidesoftware.code-spell-checker",
4
+ "streetsidesoftware.code-spell-checker-british-english",
5
+ "esbenp.prettier-vscode",
6
+ "dbaeumer.vscode-eslint",
7
+ "charliermarsh.ruff",
8
+ "ms-python.python",
9
+ "ms-python.vscode-pylance",
10
+ "ms-python.debugpy",
11
+ "njpwerner.autodocstring",
12
+ "usernamehw.errorlens"
13
+ ],
14
+ "unwantedRecommendations": ["ms-python.flake8"]
15
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "name": "Python: Debug Tests",
6
+ "type": "debugpy",
7
+ "request": "launch",
8
+ "program": "${file}",
9
+ "purpose": ["debug-test"],
10
+ "console": "integratedTerminal",
11
+ "justMyCode": true
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ // Git settings
3
+ "git.autofetch": true,
4
+ // Ruff settings - config lives in pyproject.toml
5
+ "ruff.configuration": "${workspaceFolder}/pyproject.toml",
6
+ // Mypy settings - auto-discovers mypy.ini in workspace root
7
+ "python.analysis.typeCheckingMode": "standard",
8
+ // Python testing settings
9
+ "python.testing.pytestEnabled": true,
10
+ "python.testing.unittestEnabled": false,
11
+ "python.testing.pytestArgs": [
12
+ "tests",
13
+ "--cov",
14
+ "--cov-config=pyproject.toml"
15
+ ],
16
+ "[python]": {
17
+ "editor.defaultFormatter": "charliermarsh.ruff",
18
+ "editor.formatOnSave": true,
19
+ "editor.codeActionsOnSave": {
20
+ "source.organizeImports.ruff": "explicit",
21
+ "source.fixAll.ruff": "explicit"
22
+ }
23
+ },
24
+ // Code formatting
25
+ "editor.formatOnSave": true,
26
+ // Editor settings
27
+ "editor.rulers": [120],
28
+ "files.insertFinalNewline": true,
29
+ "files.trimTrailingWhitespace": true,
30
+ "files.eol": "\n",
31
+ // Codespell settings - explicit workspace paths
32
+ "cSpell.enabled": true,
33
+ "cSpell.language": "en-GB",
34
+ "cSpell.customDictionaries": {
35
+ "project-words": {
36
+ "name": "project-words",
37
+ "path": "${workspaceFolder}/.project-words.txt",
38
+ "addWords": true,
39
+ "scope": "workspace"
40
+ }
41
+ },
42
+ // Error Lens settings
43
+ "errorLens.enabled": true,
44
+ "errorLens.enabledDiagnosticLevels": ["error", "warning", "info", "hint"],
45
+ "errorLens.onSave": true,
46
+ "errorLens.onSaveTimeout": 1000,
47
+ "errorLens.delay": 500,
48
+ "errorLens.followCursor": "allLines"
49
+ }
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ ### Miscellaneous
6
+
7
+ - Initial commit ([c96fc0e](https://github.com/Johandielangman/ttl-cache-decorator/commit/c96fc0efa65c289c731b6b8d04358cbd9943eeeb))
8
+ - Add the wheel ([20e5b76](https://github.com/Johandielangman/ttl-cache-decorator/commit/20e5b761ea79e1553682a770e2cbfc51ace33ca0))
9
+ - Add docs dependencies ([23eacf4](https://github.com/Johandielangman/ttl-cache-decorator/commit/23eacf4ebc5f9bcd78e7aac6a727ba54f041bda3))
10
+ - Use a cliff uses ([3e1c78a](https://github.com/Johandielangman/ttl-cache-decorator/commit/3e1c78a6dd6a14053e3f0b0285cc1383fc253806))
11
+ - Release v0.1.1 ([91408a2](https://github.com/Johandielangman/ttl-cache-decorator/commit/91408a2770190351a4996c1f996a415e42d3f463))
12
+ - Better tagging ([4b45da8](https://github.com/Johandielangman/ttl-cache-decorator/commit/4b45da8adeb4545a7a3bd04f600f615198a4f7d9))
13
+ <!-- generated by git-cliff -->
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Johandielangman <jghanekom2@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.5
2
+ Name: ttl-cache-decorator
3
+ Version: 0.1.2
4
+ Summary: LRU Cache Decorator with a Time to Live
5
+ Project-URL: Changelog, https://github.com/Johandielangman/ttl-cache-decorator/blob/main/CHANGELOG.md
6
+ Project-URL: Documentation, https://johandielangman.github.io/ttl-cache-decorator/
7
+ Project-URL: Issues, https://github.com/Johandielangman/ttl-cache-decorator/issues
8
+ Project-URL: Source, https://github.com/Johandielangman/ttl-cache-decorator
9
+ Author-email: Johandielangman <johan@datatreehouse.org>
10
+ License-Expression: MIT
11
+ License-File: LICENSE.txt
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
21
+ Classifier: Programming Language :: Python :: Implementation :: CPython
22
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
23
+ Requires-Python: >=3.12
24
+ Description-Content-Type: text/markdown
25
+
26
+ # TTL Cache
27
+
28
+ [![PyPI - Version](https://img.shields.io/pypi/v/ttl-cache-decorator.svg)](https://pypi.org/project/ttl-cache-decorator)
29
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ttl-cache-decorator.svg)](https://pypi.org/project/ttl-cache-decorator)
30
+
31
+ An LRU cache decorator with a time to live — like `functools.lru_cache`, but entries expire after a configurable number of seconds.
32
+
33
+ ## Installation
34
+
35
+ ```console
36
+ pip install ttl-cache-decorator
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```python
42
+ from ttl_cache import ttl_lru_cache
43
+
44
+
45
+ @ttl_lru_cache(ttl=30)
46
+ def fetch_user(user_id: int) -> dict:
47
+ return {"id": user_id, "name": "Ada"}
48
+
49
+
50
+ fetch_user(1) # Calls the function
51
+ fetch_user(1) # Cache hit
52
+ # ... 30 seconds later ...
53
+ fetch_user(1) # Expired — calls the function again
54
+ ```
55
+
56
+ ## License
57
+
58
+ `ttl-cache-decorator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -0,0 +1,33 @@
1
+ # TTL Cache
2
+
3
+ [![PyPI - Version](https://img.shields.io/pypi/v/ttl-cache-decorator.svg)](https://pypi.org/project/ttl-cache-decorator)
4
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ttl-cache-decorator.svg)](https://pypi.org/project/ttl-cache-decorator)
5
+
6
+ An LRU cache decorator with a time to live — like `functools.lru_cache`, but entries expire after a configurable number of seconds.
7
+
8
+ ## Installation
9
+
10
+ ```console
11
+ pip install ttl-cache-decorator
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```python
17
+ from ttl_cache import ttl_lru_cache
18
+
19
+
20
+ @ttl_lru_cache(ttl=30)
21
+ def fetch_user(user_id: int) -> dict:
22
+ return {"id": user_id, "name": "Ada"}
23
+
24
+
25
+ fetch_user(1) # Calls the function
26
+ fetch_user(1) # Cache hit
27
+ # ... 30 seconds later ...
28
+ fetch_user(1) # Expired — calls the function again
29
+ ```
30
+
31
+ ## License
32
+
33
+ `ttl-cache-decorator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -0,0 +1,37 @@
1
+ # git-cliff ~ changelog configuration
2
+ # https://git-cliff.org/docs/configuration
3
+
4
+ [changelog]
5
+ header = """
6
+ # Changelog
7
+
8
+ All notable changes to this project are documented in this file.
9
+ """
10
+ body = """
11
+ {% for group, commits in commits | group_by(attribute="group") %}
12
+ ### {{ group | upper_first }}
13
+ {% for commit in commits %}
14
+ - {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/Johandielangman/ttl-cache-decorator/commit/{{ commit.id }}))\
15
+ {% endfor %}
16
+ {% endfor %}
17
+ """
18
+ footer = """
19
+ <!-- generated by git-cliff -->
20
+ """
21
+ trim = true
22
+
23
+ [git]
24
+ conventional_commits = true
25
+ filter_unconventional = true
26
+ commit_parsers = [
27
+ { message = "^feat", group = "Features" },
28
+ { message = "^fix", group = "Bug Fixes" },
29
+ { message = "^perf", group = "Performance" },
30
+ { message = "^refactor", group = "Refactoring" },
31
+ { message = "^docs", group = "Documentation" },
32
+ { message = "^test", group = "Testing" },
33
+ { message = "^chore\\(release\\)", skip = true },
34
+ { message = "^chore", group = "Miscellaneous" },
35
+ ]
36
+ filter_commits = false
37
+ tag_pattern = "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
@@ -0,0 +1,138 @@
1
+ ---
2
+ icon: lucide/timer
3
+ ---
4
+
5
+ # TTL Cache
6
+
7
+ An LRU cache decorator with a ==time to live==. `ttl_lru_cache` works exactly
8
+ like [`functools.lru_cache`][lru_cache], except entries automatically expire
9
+ after a configurable number of seconds.
10
+
11
+ [lru_cache]: https://docs.python.org/3/library/functools.html#functools.lru_cache
12
+
13
+ ## Installation
14
+
15
+ === "pip"
16
+
17
+ ``` console
18
+ pip install ttl-cache-decorator
19
+ ```
20
+
21
+ === "uv"
22
+
23
+ ``` console
24
+ uv add ttl-cache-decorator
25
+ ```
26
+
27
+ Requires **Python 3.12+**.
28
+
29
+ ## Quickstart
30
+
31
+ ``` python title="Basic usage" hl_lines="1 4"
32
+ from ttl_cache import ttl_lru_cache
33
+
34
+
35
+ @ttl_lru_cache(ttl=30) # (1)!
36
+ def fetch_user(user_id: int) -> dict:
37
+ print("Fetching from the network...")
38
+ return {"id": user_id, "name": "Ada"}
39
+
40
+
41
+ fetch_user(1) # Prints "Fetching from the network..."
42
+ fetch_user(1) # Cache hit, nothing printed
43
+ # ... 30 seconds later ...
44
+ fetch_user(1) # Expired — fetches again
45
+ ```
46
+
47
+ 1. Cache results for 30 seconds. While the TTL window is open, repeat calls
48
+ with the same arguments are served from the cache.
49
+
50
+ ## How it works
51
+
52
+ Under the hood it still uses `lru_cache`, but the cached call receives an extra
53
+ positional-only *time bucket* argument derived from a monotonic clock:
54
+
55
+ ``` python title="The time bucket"
56
+ time_bucket = int((time.monotonic() + offset) // ttl)
57
+ ```
58
+
59
+ An entry stays "alive" while the bucket value is unchanged and expires when the
60
+ bucket rolls over:
61
+
62
+ ``` mermaid
63
+ graph LR
64
+ A[Call] --> B{Bucket changed?}
65
+ B -->|No| C[Serve cached value]
66
+ B -->|Yes| D[Recompute and cache]
67
+ C --> E[Return]
68
+ D --> E
69
+ ```
70
+
71
+ !!! note
72
+
73
+ The bucket boundary is shared per TTL window, so an entry lives for *at
74
+ most* `ttl` seconds and *at least* the remainder of the current bucket.
75
+
76
+ ## API reference
77
+
78
+ ### `ttl_lru_cache`
79
+
80
+ ``` python
81
+ ttl_lru_cache(
82
+ ttl: float = 60,
83
+ jitter: float = 0,
84
+ maxsize: int | None = 128,
85
+ typed: bool = False,
86
+ )
87
+ ```
88
+
89
+ | Parameter | Type | Default | Description |
90
+ |---|---|---|---|
91
+ | `ttl` | `float` | `60` | Time to live in seconds. Must be positive. |
92
+ | `jitter` | `float` | `0` | Max random offset (seconds) applied to bucket boundaries. Must be non-negative. |
93
+ | `maxsize` | `int \| None` | `128` | Max size of the cache, or `None` for unbounded. |
94
+ | `typed` | `bool` | `False` | Whether argument types are part of the cache key. |
95
+
96
+ Returns a decorator whose wrapped function keeps its signature and exposes the
97
+ usual `lru_cache` helpers: `cache_info()` and `cache_clear()`.
98
+
99
+ !!! warning "Validation"
100
+
101
+ A `ValueError` is raised at decoration time if `ttl` is not positive or
102
+ `jitter` is negative.
103
+
104
+ ### Jitter
105
+
106
+ `jitter` draws a random offset once per decorated function from
107
+ `random.uniform(0, jitter)`, so different functions (or processes) expire at
108
+ different moments instead of all at once. Use it to avoid a *thundering herd*
109
+ when many cached values would otherwise expire simultaneously:
110
+
111
+ ``` python title="Adding jitter" hl_lines="1"
112
+ @ttl_lru_cache(ttl=60, jitter=10)
113
+ def fetch_metrics(service: str) -> dict:
114
+ ...
115
+ ```
116
+
117
+ ### Cache helpers
118
+
119
+ ``` python title="Inspecting and clearing the cache"
120
+ fetch_user.cache_info() # CacheInfo(hits=..., misses=..., maxsize=128, currsize=...)
121
+ fetch_user.cache_clear() # Drop all cached entries
122
+ ```
123
+
124
+ ??? info "Typing"
125
+
126
+ The decorator is fully generic via `ParamSpec` and returns a
127
+ `TTLCachedFunction` protocol, so type checkers preserve the wrapped
128
+ function's signature — including keyword arguments.
129
+
130
+ ``` python
131
+ from ttl_cache import TTLCachedFunction
132
+
133
+ cached_fetch: TTLCachedFunction[[str], dict] = ttl_lru_cache(ttl=5)(fetch_user)
134
+ ```
135
+
136
+ ## License
137
+
138
+ `ttl-cache-decorator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.