fastapi-singleton 0.1.0__tar.gz → 0.2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-singleton
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Application-scoped dependencies for FastAPI
5
5
  Author: Alex Ward
6
6
  Author-email: Alex Ward <alxwrd@googlemail.com>
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.14
14
14
  Classifier: Programming Language :: Python :: 3 :: Only
15
15
  Classifier: Framework :: FastAPI
16
16
  Requires-Dist: fastapi>=0.115
17
- Requires-Python: >=3.13
17
+ Requires-Python: >=3.10
18
18
  Project-URL: Repository, https://github.com/alxwrd/fastapi-singleton
19
19
  Project-URL: Releases, https://github.com/alxwrd/fastapi-singleton/releases
20
20
  Description-Content-Type: text/markdown
@@ -24,23 +24,16 @@ Description-Content-Type: text/markdown
24
24
  <p align="center"><i>
25
25
  Application-scoped dependencies for <code>fastapi</code>
26
26
  </i></p>
27
- <img width="256px" src=".github/assets/three-card-trickster-768.png">
27
+ <img width="256px" src="https://github.com/alxwrd/fastapi-singleton/raw/main/.github/assets/three-card-trickster-768.png">
28
28
  <div align="center">
29
29
  <a href="https://github.com/alxwrd/fastapi-singleton/actions/workflows/test.yml"><img src="https://img.shields.io/github/actions/workflow/status/alxwrd/fastapi-singleton/test.yml?branch=main&label=main"></a>
30
30
  <a href="https://pypi.python.org/pypi/fastapi-singleton"><img src="https://img.shields.io/pypi/v/fastapi-singleton.svg"></a>
31
31
  <a href="https://github.com/alxwrd/fastapi-singleton/blob/main/LICENCE"><img src="https://img.shields.io/pypi/l/fastapi-singleton.svg?"></a>
32
32
  </div>
33
-
34
- Every dependency resolved through FastAPI's `Depends` is request-scoped:
35
- created on each request and discarded once the response is sent. That's the
36
- right default for most things, but it's the wrong default for connection
37
- pools, HTTP clients, and anything else that's expensive to create and safe to
38
- share.
39
-
40
- `fastapi-singleton` gives you a `@singleton` decorator that turns any
41
- dependency, function or class, into one shared instance per process, with
42
- proper startup and shutdown hooks wired into FastAPI's `lifespan`, instead of
43
- leaving it to whatever a `SIGTERM` does to a `@lru_cache`d object.
33
+ Gives you a `@singleton` decorator that turns any
34
+ dependency, function or class, into one shared instance per process, with
35
+ proper startup and shutdown hooks wired into FastAPI's `lifespan`, instead of
36
+ leaving it to whatever a `SIGTERM` does to a `@lru_cache`d object.
44
37
  </div>
45
38
 
46
39
  ## Example
@@ -125,14 +118,6 @@ class Connection:
125
118
  self.other = other
126
119
  ```
127
120
 
128
- A class singleton's `__init__` is the constructor, plain and simple -
129
- `Depends(Connection)` calls it exactly once, the same way any FastAPI
130
- class-based dependency works. `__init__` can never be `async def` in
131
- Python, so a class singleton can't do real async setup itself - if you need
132
- that (an async connection pool, an `await`-based client, anything with
133
- teardown), write it as a function singleton instead and have your class
134
- depend on it, the same way `Connection` depends on `get_other` above.
135
-
136
121
  A singleton can't depend on a regular, request-scoped dependency - there's
137
122
  no request to resolve it from when the singleton is constructed eagerly at
138
123
  startup, or directly in plain Python. `@singleton`-ing something that
@@ -3,23 +3,16 @@
3
3
  <p align="center"><i>
4
4
  Application-scoped dependencies for <code>fastapi</code>
5
5
  </i></p>
6
- <img width="256px" src=".github/assets/three-card-trickster-768.png">
6
+ <img width="256px" src="https://github.com/alxwrd/fastapi-singleton/raw/main/.github/assets/three-card-trickster-768.png">
7
7
  <div align="center">
8
8
  <a href="https://github.com/alxwrd/fastapi-singleton/actions/workflows/test.yml"><img src="https://img.shields.io/github/actions/workflow/status/alxwrd/fastapi-singleton/test.yml?branch=main&label=main"></a>
9
9
  <a href="https://pypi.python.org/pypi/fastapi-singleton"><img src="https://img.shields.io/pypi/v/fastapi-singleton.svg"></a>
10
10
  <a href="https://github.com/alxwrd/fastapi-singleton/blob/main/LICENCE"><img src="https://img.shields.io/pypi/l/fastapi-singleton.svg?"></a>
11
11
  </div>
12
-
13
- Every dependency resolved through FastAPI's `Depends` is request-scoped:
14
- created on each request and discarded once the response is sent. That's the
15
- right default for most things, but it's the wrong default for connection
16
- pools, HTTP clients, and anything else that's expensive to create and safe to
17
- share.
18
-
19
- `fastapi-singleton` gives you a `@singleton` decorator that turns any
20
- dependency, function or class, into one shared instance per process, with
21
- proper startup and shutdown hooks wired into FastAPI's `lifespan`, instead of
22
- leaving it to whatever a `SIGTERM` does to a `@lru_cache`d object.
12
+ Gives you a `@singleton` decorator that turns any
13
+ dependency, function or class, into one shared instance per process, with
14
+ proper startup and shutdown hooks wired into FastAPI's `lifespan`, instead of
15
+ leaving it to whatever a `SIGTERM` does to a `@lru_cache`d object.
23
16
  </div>
24
17
 
25
18
  ## Example
@@ -104,14 +97,6 @@ class Connection:
104
97
  self.other = other
105
98
  ```
106
99
 
107
- A class singleton's `__init__` is the constructor, plain and simple -
108
- `Depends(Connection)` calls it exactly once, the same way any FastAPI
109
- class-based dependency works. `__init__` can never be `async def` in
110
- Python, so a class singleton can't do real async setup itself - if you need
111
- that (an async connection pool, an `await`-based client, anything with
112
- teardown), write it as a function singleton instead and have your class
113
- depend on it, the same way `Connection` depends on `get_other` above.
114
-
115
100
  A singleton can't depend on a regular, request-scoped dependency - there's
116
101
  no request to resolve it from when the singleton is constructed eagerly at
117
102
  startup, or directly in plain Python. `@singleton`-ing something that
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fastapi-singleton"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "Application-scoped dependencies for FastAPI"
5
5
  license = "MIT"
6
6
  readme = "README.md"
@@ -17,7 +17,7 @@ classifiers = [
17
17
  "Programming Language :: Python :: 3 :: Only",
18
18
  "Framework :: FastAPI",
19
19
  ]
20
- requires-python = ">=3.13"
20
+ requires-python = ">=3.10"
21
21
  dependencies = [
22
22
  "fastapi>=0.115",
23
23
  ]
@@ -61,7 +61,6 @@ class _BaseFunctionSingleton(Generic[_LockT]):
61
61
  self._provider = Provider(self._fn)
62
62
  self._created = None
63
63
  self._torn_down = False
64
- self._before_end_done = False
65
64
  self._value = _UNSET
66
65
  self._construction_kwargs = {}
67
66
  self._lock = self.LOCK_METHOD()
@@ -114,12 +113,12 @@ class SyncFunctionSingleton(_BaseFunctionSingleton[threading.Lock]):
114
113
  def teardown(self) -> None:
115
114
  if not self._should_teardown():
116
115
  return
117
- if not self._before_end_done:
118
- self._before_end_done = True
119
- _hooks.run_sync(self._hooks.before_end)
120
- self._provider.teardown()
121
- _hooks.run_sync(self._hooks.after_end)
122
116
  self._torn_down = True
117
+ try:
118
+ _hooks.run_sync(self._hooks.before_end)
119
+ finally:
120
+ self._provider.teardown()
121
+ _hooks.run_sync(self._hooks.after_end)
123
122
 
124
123
 
125
124
  class AsyncFunctionSingleton(_BaseFunctionSingleton[asyncio.Lock]):
@@ -143,14 +142,14 @@ class AsyncFunctionSingleton(_BaseFunctionSingleton[asyncio.Lock]):
143
142
  async def teardown(self) -> None:
144
143
  if not self._should_teardown():
145
144
  return
146
- if not self._before_end_done:
147
- self._before_end_done = True
148
- await _hooks.run_async(self._hooks.before_end)
149
- result = self._provider.teardown()
150
- if inspect.isawaitable(result):
151
- await result
152
- await _hooks.run_async(self._hooks.after_end)
153
145
  self._torn_down = True
146
+ try:
147
+ await _hooks.run_async(self._hooks.before_end)
148
+ finally:
149
+ result = self._provider.teardown()
150
+ if inspect.isawaitable(result):
151
+ await result
152
+ await _hooks.run_async(self._hooks.after_end)
154
153
 
155
154
 
156
155
  def make_function_singleton(
@@ -55,11 +55,15 @@ def guard_against_cycles(singleton: Any) -> Iterator[None]:
55
55
 
56
56
 
57
57
  def _values_equal(a: Any, b: Any) -> bool:
58
- """Like `==`, but treats two NaN floats as equal to each other.
59
-
60
- Plain `==` follows IEEE 754, where NaN is never equal to anything,
61
- including another NaN - so a repeat call with a semantically-unchanged
62
- NaN-valued argument would otherwise look like a conflicting argument."""
58
+ """Like `==`, but recurses into dicts/lists/tuples and treats two NaN
59
+ floats as equal to each other.
60
+
61
+ Covers the ways `==` alone is unreliable for argument values: NaN
62
+ follows IEEE 754, where it's never equal to anything (including
63
+ another NaN), and unhashable containers need their elements compared
64
+ individually to catch a NaN nested inside them. Without this, a repeat
65
+ call with semantically-unchanged arguments could look like a
66
+ conflicting one."""
63
67
  if (
64
68
  isinstance(a, float)
65
69
  and isinstance(b, float)