fastapi-memory 0.1.0__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ include LICENSE
3
+ include fastapi_memory/py.typed
@@ -0,0 +1,233 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-memory
3
+ Version: 0.1.0
4
+ Summary: Caching, retry and resilient-HTTP helpers for FastAPI services.
5
+ Author-email: Alexander Stankovic <alexdarka@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/alexdarka/fastapi-memory
8
+ Project-URL: Repository, https://github.com/alexdarka/fastapi-memory
9
+ Keywords: fastapi,cache,caching,retry,tenacity,httpx,resilience,redis
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Internet :: WWW/HTTP
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: fastapi>=0.115.0
27
+ Requires-Dist: fastapi-cache2>=0.2.2
28
+ Requires-Dist: tenacity>=8.3.0
29
+ Requires-Dist: httpx>=0.27.0
30
+ Requires-Dist: jinja2>=3.1.0
31
+ Provides-Extra: redis
32
+ Requires-Dist: redis>=5.0.0; extra == "redis"
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.0; extra == "dev"
35
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
36
+ Provides-Extra: docs
37
+ Requires-Dist: mkdocs; extra == "docs"
38
+ Requires-Dist: mkdocs-material; extra == "docs"
39
+ Requires-Dist: mkdocstrings[python]; extra == "docs"
40
+ Dynamic: license-file
41
+
42
+ # fastapi-memory
43
+
44
+ Caching, retry, and resilient-HTTP helpers for FastAPI services.
45
+
46
+ A single package that provides response caching, retry policies, a resilient
47
+ async HTTP client, and cached config singletons — tailored for FastAPI
48
+ projects that talk to upstream APIs.
49
+
50
+ ```python
51
+ from fastapi_memory import (
52
+ FmCacheManager, FmMemoryBackend, memorize,
53
+ retry, stop_after_retries, exponential_backoff, retry_on_error,
54
+ fm_lru,
55
+ )
56
+ ```
57
+
58
+ ## Why
59
+
60
+ Most FastAPI services that proxy slower or less-reliable upstream APIs end up
61
+ re-writing the same patterns:
62
+
63
+ 1. A **cache** for endpoints or data that don't change often.
64
+ 2. A **retry policy** for upstream calls — retry network errors and 5xx, but not 4xx.
65
+ 3. A **cached singleton** config object (build once, reuse everywhere).
66
+ 4. A **resilient HTTP client** with connection pooling and retries baked in.
67
+
68
+ `fastapi-memory` consolidates all of these into one import, with ready-made
69
+ helpers so you don't have to re-derive common patterns every time.
70
+
71
+ ## Installation
72
+
73
+ ```bash
74
+ # Editable install (local development)
75
+ pip install -e /path/to/fastapi-memory
76
+
77
+ # From PyPI (once published)
78
+ pip install fastapi-memory
79
+
80
+ # With optional Redis cache backend support
81
+ pip install "fastapi-memory[redis]"
82
+ ```
83
+
84
+ ## What's inside
85
+
86
+ | Module | Provides | Adds |
87
+ |--------------|-------------------------------------------------------------------|-----------------------------------------------------------|
88
+ | `caching` | `FmCacheManager`, `FmMemoryBackend`, `memorize`, `FmRedisBackend` | `init_cache()`, `clear_cache()` |
89
+ | `resilience` | `retry`, `stop_after_retries`, `exponential_backoff`, `retry_on_error` | `default_retry()`, `is_retryable_httpx_error()` |
90
+ | `config` | `fm_lru` | `cached_singleton` |
91
+ | `http` | — | `FmResilientClient` |
92
+
93
+ Everything above is re-exported from the top-level `fastapi_memory` package,
94
+ so `from fastapi_memory import <anything in the table>` works.
95
+
96
+ ---
97
+
98
+ ## Quick start
99
+
100
+ ### Caching — response caching with a single call
101
+
102
+ ```python
103
+ from contextlib import asynccontextmanager
104
+ from fastapi import FastAPI
105
+ from fastapi_memory import init_cache, clear_cache, memorize
106
+
107
+ @asynccontextmanager
108
+ async def lifespan(app: FastAPI):
109
+ init_cache(prefix="app-cache") # in-memory (default)
110
+ yield
111
+
112
+ app = FastAPI(lifespan=lifespan)
113
+
114
+ @app.get("/api/data")
115
+ @memorize(expire=60)
116
+ async def get_data():
117
+ return {"data": "computed result"}
118
+
119
+ @app.post("/api/cache/invalidate")
120
+ async def invalidate_cache():
121
+ await clear_cache()
122
+ return {"ok": True}
123
+ ```
124
+
125
+ ### Switching to Redis later
126
+
127
+ ```python
128
+ init_cache(backend="redis", prefix="app-cache", redis_url="redis://localhost:6379")
129
+ ```
130
+
131
+ ### Retry — exponential backoff with sensible defaults
132
+
133
+ ```python
134
+ from fastapi_memory import default_retry
135
+
136
+ @default_retry()
137
+ async def call_upstream():
138
+ resp = await client.get(url)
139
+ resp.raise_for_status()
140
+ return resp.json()
141
+ ```
142
+
143
+ `default_retry()` retries up to 3 times with exponential backoff (2s–10s),
144
+ skip 4xx, reraise on final failure. Override any piece:
145
+
146
+ ```python
147
+ @default_retry(attempts=5, wait_max=30)
148
+ async def flaky_call():
149
+ ...
150
+ ```
151
+
152
+ ### Config — cached singleton
153
+
154
+ ```python
155
+ from fastapi_memory import cached_singleton
156
+
157
+ class Settings:
158
+ BASE_URL: str = "http://api.example.com:8080"
159
+ CACHE_TTL: int = 300
160
+
161
+ @cached_singleton
162
+ def get_settings() -> Settings:
163
+ return Settings()
164
+
165
+ config = get_settings()
166
+ ```
167
+
168
+ ### Resilient HTTP client
169
+
170
+ ```python
171
+ from contextlib import asynccontextmanager
172
+ from fastapi import FastAPI
173
+ from fastapi_memory import FmResilientClient, init_cache
174
+
175
+ upstream = FmResilientClient(
176
+ base_url="http://api.example.com:8080",
177
+ timeout=30.0,
178
+ )
179
+
180
+ @asynccontextmanager
181
+ async def lifespan(app: FastAPI):
182
+ await upstream.start()
183
+ init_cache(prefix="app-cache")
184
+ yield
185
+ await upstream.aclose()
186
+
187
+ app = FastAPI(lifespan=lifespan)
188
+
189
+ @app.get("/api/data")
190
+ async def get_data():
191
+ return await upstream.get_json("data")
192
+ ```
193
+
194
+ `upstream.get_raw(path, params)` is also available if you want the raw
195
+ exceptions instead of `HTTPException`.
196
+
197
+ ---
198
+
199
+ ## Project layout
200
+
201
+ ```
202
+ fastapi-memory/
203
+ ├── pyproject.toml
204
+ ├── setup.py
205
+ ├── README.md
206
+ ├── LICENSE
207
+ ├── fastapi_memory/
208
+ │ ├── __init__.py # re-exports everything
209
+ │ ├── caching.py # FmCacheManager, FmMemoryBackend, memorize, init_cache, clear_cache
210
+ │ ├── resilience.py # retry + default_retry, is_retryable_httpx_error
211
+ │ ├── config.py # fm_lru + cached_singleton
212
+ │ └── http.py # FmResilientClient
213
+ └── tests/
214
+ └── test_imports.py
215
+ ```
216
+
217
+ ## Updating `requirements.txt`
218
+
219
+ Replace:
220
+
221
+ ```
222
+ tenacity>=8.3.0
223
+ fastapi-cache2>=0.2.2
224
+ redis>=5.0.0
225
+ ```
226
+
227
+ with:
228
+
229
+ ```
230
+ fastapi-memory @ file:///path/to/fastapi-memory
231
+ ```
232
+
233
+ (or once published: `fastapi-memory>=0.1.0`, optionally `fastapi-memory[redis]`.)
@@ -0,0 +1,192 @@
1
+ # fastapi-memory
2
+
3
+ Caching, retry, and resilient-HTTP helpers for FastAPI services.
4
+
5
+ A single package that provides response caching, retry policies, a resilient
6
+ async HTTP client, and cached config singletons — tailored for FastAPI
7
+ projects that talk to upstream APIs.
8
+
9
+ ```python
10
+ from fastapi_memory import (
11
+ FmCacheManager, FmMemoryBackend, memorize,
12
+ retry, stop_after_retries, exponential_backoff, retry_on_error,
13
+ fm_lru,
14
+ )
15
+ ```
16
+
17
+ ## Why
18
+
19
+ Most FastAPI services that proxy slower or less-reliable upstream APIs end up
20
+ re-writing the same patterns:
21
+
22
+ 1. A **cache** for endpoints or data that don't change often.
23
+ 2. A **retry policy** for upstream calls — retry network errors and 5xx, but not 4xx.
24
+ 3. A **cached singleton** config object (build once, reuse everywhere).
25
+ 4. A **resilient HTTP client** with connection pooling and retries baked in.
26
+
27
+ `fastapi-memory` consolidates all of these into one import, with ready-made
28
+ helpers so you don't have to re-derive common patterns every time.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ # Editable install (local development)
34
+ pip install -e /path/to/fastapi-memory
35
+
36
+ # From PyPI (once published)
37
+ pip install fastapi-memory
38
+
39
+ # With optional Redis cache backend support
40
+ pip install "fastapi-memory[redis]"
41
+ ```
42
+
43
+ ## What's inside
44
+
45
+ | Module | Provides | Adds |
46
+ |--------------|-------------------------------------------------------------------|-----------------------------------------------------------|
47
+ | `caching` | `FmCacheManager`, `FmMemoryBackend`, `memorize`, `FmRedisBackend` | `init_cache()`, `clear_cache()` |
48
+ | `resilience` | `retry`, `stop_after_retries`, `exponential_backoff`, `retry_on_error` | `default_retry()`, `is_retryable_httpx_error()` |
49
+ | `config` | `fm_lru` | `cached_singleton` |
50
+ | `http` | — | `FmResilientClient` |
51
+
52
+ Everything above is re-exported from the top-level `fastapi_memory` package,
53
+ so `from fastapi_memory import <anything in the table>` works.
54
+
55
+ ---
56
+
57
+ ## Quick start
58
+
59
+ ### Caching — response caching with a single call
60
+
61
+ ```python
62
+ from contextlib import asynccontextmanager
63
+ from fastapi import FastAPI
64
+ from fastapi_memory import init_cache, clear_cache, memorize
65
+
66
+ @asynccontextmanager
67
+ async def lifespan(app: FastAPI):
68
+ init_cache(prefix="app-cache") # in-memory (default)
69
+ yield
70
+
71
+ app = FastAPI(lifespan=lifespan)
72
+
73
+ @app.get("/api/data")
74
+ @memorize(expire=60)
75
+ async def get_data():
76
+ return {"data": "computed result"}
77
+
78
+ @app.post("/api/cache/invalidate")
79
+ async def invalidate_cache():
80
+ await clear_cache()
81
+ return {"ok": True}
82
+ ```
83
+
84
+ ### Switching to Redis later
85
+
86
+ ```python
87
+ init_cache(backend="redis", prefix="app-cache", redis_url="redis://localhost:6379")
88
+ ```
89
+
90
+ ### Retry — exponential backoff with sensible defaults
91
+
92
+ ```python
93
+ from fastapi_memory import default_retry
94
+
95
+ @default_retry()
96
+ async def call_upstream():
97
+ resp = await client.get(url)
98
+ resp.raise_for_status()
99
+ return resp.json()
100
+ ```
101
+
102
+ `default_retry()` retries up to 3 times with exponential backoff (2s–10s),
103
+ skip 4xx, reraise on final failure. Override any piece:
104
+
105
+ ```python
106
+ @default_retry(attempts=5, wait_max=30)
107
+ async def flaky_call():
108
+ ...
109
+ ```
110
+
111
+ ### Config — cached singleton
112
+
113
+ ```python
114
+ from fastapi_memory import cached_singleton
115
+
116
+ class Settings:
117
+ BASE_URL: str = "http://api.example.com:8080"
118
+ CACHE_TTL: int = 300
119
+
120
+ @cached_singleton
121
+ def get_settings() -> Settings:
122
+ return Settings()
123
+
124
+ config = get_settings()
125
+ ```
126
+
127
+ ### Resilient HTTP client
128
+
129
+ ```python
130
+ from contextlib import asynccontextmanager
131
+ from fastapi import FastAPI
132
+ from fastapi_memory import FmResilientClient, init_cache
133
+
134
+ upstream = FmResilientClient(
135
+ base_url="http://api.example.com:8080",
136
+ timeout=30.0,
137
+ )
138
+
139
+ @asynccontextmanager
140
+ async def lifespan(app: FastAPI):
141
+ await upstream.start()
142
+ init_cache(prefix="app-cache")
143
+ yield
144
+ await upstream.aclose()
145
+
146
+ app = FastAPI(lifespan=lifespan)
147
+
148
+ @app.get("/api/data")
149
+ async def get_data():
150
+ return await upstream.get_json("data")
151
+ ```
152
+
153
+ `upstream.get_raw(path, params)` is also available if you want the raw
154
+ exceptions instead of `HTTPException`.
155
+
156
+ ---
157
+
158
+ ## Project layout
159
+
160
+ ```
161
+ fastapi-memory/
162
+ ├── pyproject.toml
163
+ ├── setup.py
164
+ ├── README.md
165
+ ├── LICENSE
166
+ ├── fastapi_memory/
167
+ │ ├── __init__.py # re-exports everything
168
+ │ ├── caching.py # FmCacheManager, FmMemoryBackend, memorize, init_cache, clear_cache
169
+ │ ├── resilience.py # retry + default_retry, is_retryable_httpx_error
170
+ │ ├── config.py # fm_lru + cached_singleton
171
+ │ └── http.py # FmResilientClient
172
+ └── tests/
173
+ └── test_imports.py
174
+ ```
175
+
176
+ ## Updating `requirements.txt`
177
+
178
+ Replace:
179
+
180
+ ```
181
+ tenacity>=8.3.0
182
+ fastapi-cache2>=0.2.2
183
+ redis>=5.0.0
184
+ ```
185
+
186
+ with:
187
+
188
+ ```
189
+ fastapi-memory @ file:///path/to/fastapi-memory
190
+ ```
191
+
192
+ (or once published: `fastapi-memory>=0.1.0`, optionally `fastapi-memory[redis]`.)
@@ -0,0 +1,74 @@
1
+ """
2
+ fastapi-memory
3
+ ==============
4
+
5
+ Caching, retry, and resilient-HTTP helpers for FastAPI services.
6
+
7
+ This package provides response caching, retry policies, a resilient async
8
+ HTTP client, and cached config singletons — all in one import, designed for
9
+ FastAPI services that talk to slower upstream APIs.
10
+
11
+ Import from this package instead of juggling multiple dependencies directly::
12
+
13
+ from fastapi_memory import (
14
+ FmCacheManager, FmMemoryBackend, memorize,
15
+ retry, stop_after_retries, exponential_backoff, retry_on_error,
16
+ fm_lru,
17
+ )
18
+
19
+ Higher-level helpers
20
+ ---------------------
21
+ On top of the re-exports, fastapi-memory adds a few small conveniences:
22
+
23
+ - ``init_cache()`` -> one-line ``FmCacheManager`` setup (memory or Redis)
24
+ - ``clear_cache()`` -> ``await FmCacheManager.clear()``
25
+ - ``default_retry()`` -> the "3 attempts, exponential backoff, skip 4xx" policy
26
+ - ``is_retryable_httpx_error`` -> the retry predicate behind ``default_retry``
27
+ - ``cached_singleton`` -> ``@fm_lru(maxsize=1)`` for settings-style singletons
28
+ - ``FmResilientClient`` -> persistent ``httpx.AsyncClient`` + retries + JSON helpers
29
+
30
+ See the README for full usage examples and a migration guide.
31
+ """
32
+
33
+ from .caching import (
34
+ FmCacheManager,
35
+ FmMemoryBackend,
36
+ FmRedisBackend,
37
+ memorize,
38
+ clear_cache,
39
+ init_cache,
40
+ )
41
+ from .config import cached_singleton, fm_lru
42
+ from .http import FmResilientClient
43
+ from .resilience import (
44
+ default_retry,
45
+ is_retryable_httpx_error,
46
+ retry,
47
+ retry_on_error,
48
+ stop_after_retries,
49
+ exponential_backoff,
50
+ )
51
+
52
+ __version__ = "0.1.0"
53
+
54
+ __all__ = [
55
+ # caching
56
+ "FmCacheManager",
57
+ "FmMemoryBackend",
58
+ "FmRedisBackend",
59
+ "memorize",
60
+ "init_cache",
61
+ "clear_cache",
62
+ # resilience
63
+ "retry",
64
+ "stop_after_retries",
65
+ "exponential_backoff",
66
+ "retry_on_error",
67
+ "default_retry",
68
+ "is_retryable_httpx_error",
69
+ # config
70
+ "fm_lru",
71
+ "cached_singleton",
72
+ # http
73
+ "FmResilientClient",
74
+ ]
@@ -0,0 +1,117 @@
1
+ """
2
+ Thin convenience layer around fastapi-cache2.
3
+
4
+ Re-exports the pieces you already use directly (``FmCacheManager``,
5
+ ``FmMemoryBackend``, ``memorize``) and adds two small helpers:
6
+
7
+ - :func:`init_cache` - one-line setup for an in-memory or Redis-backed cache
8
+ - :func:`clear_cache` - ``await FmCacheManager.clear()``
9
+
10
+ The Redis backend is optional. Install it with::
11
+
12
+ pip install "fastapi-memory[redis]"
13
+
14
+ If ``redis`` isn't installed, ``FmRedisBackend`` is simply ``None`` and
15
+ ``init_cache(backend="redis", ...)`` raises a clear ``RuntimeError``
16
+ explaining how to fix it. The default ``backend="memory"`` works with no
17
+ extra dependencies, exactly like the original::
18
+
19
+ FmCacheManager.init(FmMemoryBackend(), prefix="app-cache")
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import Optional
25
+
26
+ from fastapi_cache import FastAPICache
27
+ from fastapi_cache.backends.inmemory import InMemoryBackend
28
+ from fastapi_cache.decorator import cache
29
+
30
+ # Aliases under fastapi-memory namespace
31
+ FmCacheManager = FastAPICache
32
+ FmMemoryBackend = InMemoryBackend
33
+ memorize = cache
34
+
35
+ try: # pragma: no cover - exercised only when the `redis` extra is installed
36
+ from fastapi_cache.backends.redis import RedisBackend
37
+ from redis.asyncio import from_url as _redis_from_url
38
+
39
+ FmRedisBackend = RedisBackend
40
+ _REDIS_AVAILABLE = True
41
+ except ImportError: # pragma: no cover - redis extra not installed
42
+ FmRedisBackend = None # type: ignore[assignment, misc]
43
+ _redis_from_url = None
44
+ _REDIS_AVAILABLE = False
45
+
46
+
47
+ def init_cache(
48
+ backend: str = "memory",
49
+ *,
50
+ prefix: str = "fastapi-cache",
51
+ redis_url: Optional[str] = None,
52
+ ) -> None:
53
+ """
54
+ Initialise :class:`FmCacheManager` with either an in-memory backend
55
+ (default) or a Redis backend.
56
+
57
+ Call this once during application startup, typically inside a
58
+ ``lifespan`` handler::
59
+
60
+ from contextlib import asynccontextmanager
61
+ from fastapi import FastAPI
62
+ from fastapi_memory import init_cache
63
+
64
+ @asynccontextmanager
65
+ async def lifespan(app: FastAPI):
66
+ init_cache(prefix="app-cache") # in-memory (default)
67
+ yield
68
+
69
+ app = FastAPI(lifespan=lifespan)
70
+
71
+ For Redis, pass ``backend="redis"`` and a connection URL::
72
+
73
+ init_cache(backend="redis", prefix="app-cache", redis_url="redis://localhost:6379")
74
+
75
+ Parameters
76
+ ----------
77
+ backend:
78
+ ``"memory"`` (default) or ``"redis"``.
79
+ prefix:
80
+ Cache-key prefix, passed straight through to ``FmCacheManager.init``.
81
+ redis_url:
82
+ Connection string for Redis, e.g. ``redis://localhost:6379``.
83
+ Required when ``backend="redis"``.
84
+ """
85
+ if backend == "memory":
86
+ FmCacheManager.init(FmMemoryBackend(), prefix=prefix)
87
+ return
88
+
89
+ if backend == "redis":
90
+ if not _REDIS_AVAILABLE:
91
+ raise RuntimeError(
92
+ "Redis backend requested but the 'redis' package is not "
93
+ "installed. Install it with: pip install fastapi-memory[redis]"
94
+ )
95
+ if not redis_url:
96
+ raise ValueError("redis_url is required when backend='redis'")
97
+
98
+ client = _redis_from_url(redis_url, encoding="utf8", decode_responses=False)
99
+ FmCacheManager.init(FmRedisBackend(client), prefix=prefix)
100
+ return
101
+
102
+ raise ValueError(f"Unknown backend {backend!r}, expected 'memory' or 'redis'")
103
+
104
+
105
+ async def clear_cache() -> None:
106
+ """Clear the entire cache - thin wrapper around ``await FmCacheManager.clear()``."""
107
+ await FmCacheManager.clear()
108
+
109
+
110
+ __all__ = [
111
+ "FmCacheManager",
112
+ "FmMemoryBackend",
113
+ "FmRedisBackend",
114
+ "memorize",
115
+ "init_cache",
116
+ "clear_cache",
117
+ ]
@@ -0,0 +1,34 @@
1
+ """
2
+ Tiny helper built on top of ``functools.lru_cache`` for the
3
+ "build-it-once-and-reuse-it" settings/config pattern.
4
+
5
+ Re-exports ``lru_cache`` as ``fm_lru``, plus :func:`cached_singleton`, a
6
+ small shorthand for the common::
7
+
8
+ @fm_lru(maxsize=1)
9
+ def get_settings() -> Settings:
10
+ return Settings()
11
+
12
+ pattern.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from functools import lru_cache
18
+ from typing import Callable, TypeVar
19
+
20
+ T = TypeVar("T")
21
+
22
+ # Alias under fastapi-memory namespace
23
+ fm_lru = lru_cache
24
+
25
+
26
+ def cached_singleton(func: Callable[[], T]) -> Callable[[], T]:
27
+ """
28
+ Shorthand for ``@fm_lru(maxsize=1)`` - turns a zero-argument factory
29
+ function into a cached singleton getter.
30
+ """
31
+ return fm_lru(maxsize=1)(func)
32
+
33
+
34
+ __all__ = ["fm_lru", "cached_singleton"]