Skip to content

Commit c4b2c6c

Browse files
authored
feat: 📝 Update documentation
1 parent 817bfba commit c4b2c6c

10 files changed

Lines changed: 88 additions & 23 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ if __name__ == "__main__":
5555

5656
## Resources
5757

58+
> ⚠️ The package isn't threadsafe, for better performance in single-threaded applications and those using `asyncio`.
59+
> So remember to use `threading.Lock` if you're writing a multithreaded program.
60+
5861
* [**Basic usage**](https://github.com/100nm/python-injection/tree/prod/documentation/basic-usage.md)
5962
* [**Scoped dependencies**](https://github.com/100nm/python-injection/tree/prod/documentation/scoped-dependencies.md)
6063
* [**Testing**](https://github.com/100nm/python-injection/tree/prod/documentation/testing.md)

documentation/basic-usage.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ class SomeDataClass:
7979
service_a: ServiceA = ...
8080
```
8181

82+
### Threadsafe injection
83+
84+
With `threadsafe=True`, the injection logic is wrapped in a `threading.Lock`.
85+
86+
```python
87+
@inject(threadsafe=True)
88+
def some_function(service_a: ServiceA):
89+
""" function implementation """
90+
```
91+
8292
## Get an instance
8393

8494
_Example with `get_instance` function:_

documentation/integrations.md

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,70 @@
44

55
## [FastAPI](https://github.com/fastapi/fastapi)
66

7-
Exemple:
7+
### Inject a dependency
8+
9+
Here's how to inject an instance into a FastAPI endpoint.
10+
11+
> Import:
812
913
```python
10-
from fastapi import FastAPI
1114
from injection.integrations.fastapi import Inject
15+
```
1216

13-
app = FastAPI()
17+
> With **Annotated**:
1418
19+
```python
1520
@app.get("/")
16-
async def my_endpoint(service: MyService = Inject(MyService)):
21+
async def my_endpoint(
22+
service: Annotated[MyService, Inject(MyService)],
23+
) -> None:
1724
...
1825
```
26+
27+
> Without **Annotated**:
28+
29+
```python
30+
@app.get("/")
31+
async def my_endpoint(
32+
service: MyService = Inject(MyService),
33+
) -> None:
34+
...
35+
```
36+
37+
### Useful scopes
38+
39+
Two fairly common scopes in FastAPI:
40+
* **Application lifespan scope**: associate with application lifespan.
41+
* **Request scope**: associate with http request lifetime.
42+
43+
_For a better understanding of the scopes, [here's the associated documentation](scoped-dependencies.md)._
44+
45+
Here's how to configure FastAPI:
46+
47+
```python
48+
from collections.abc import AsyncIterator, Awaitable, Callable
49+
from contextlib import asynccontextmanager
50+
from enum import StrEnum, auto
51+
52+
from fastapi import FastAPI, Request, Response
53+
from injection import adefine_scope
54+
55+
class InjectionScope(StrEnum):
56+
LIFESPAN = auto()
57+
REQUEST = auto()
58+
59+
@asynccontextmanager
60+
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
61+
async with adefine_scope(InjectionScope.LIFESPAN, shared=True):
62+
yield
63+
64+
app = FastAPI(lifespan=lifespan)
65+
66+
@app.middleware("http")
67+
async def define_request_scope_middleware(
68+
request: Request,
69+
handler: Callable[[Request], Awaitable[Response]],
70+
) -> Response:
71+
async with adefine_scope(InjectionScope.REQUEST):
72+
return await handler(request)
73+
```

documentation/scoped-dependencies.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ There are two kinds of scopes:
1919

2020
First of all, the scope must be defined:
2121

22-
*By default, the `shared` parameter is `False`.*
22+
_By default, the `shared` parameter is `False`._
2323

2424
> Define an asynchronous scope:
2525
@@ -47,7 +47,7 @@ def main() -> None:
4747

4848
### "contextmanager-like" recipes
4949

50-
*Anything after the `yield` keyword will be executed when the scope is closed.*
50+
_Anything after the `yield` keyword will be executed when the scope is closed._
5151

5252
> Asynchronous (asynchronous scope required):
5353

injection/__init__.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ class Module:
281281
module: Module,
282282
*,
283283
priority: Priority | PriorityStr = ...,
284-
) -> Iterator[None]:
284+
) -> Iterator[Self]:
285285
"""
286286
Context manager or decorator for temporary use of a module.
287287
"""
@@ -305,7 +305,7 @@ class Module:
305305
"""
306306

307307
@contextmanager
308-
def load_profile(self, *names: str) -> Iterator[None]: ...
308+
def load_profile(self, *names: str) -> Iterator[Self]: ...
309309
async def all_ready(self) -> None: ...
310310
def add_logger(self, logger: Logger) -> Self: ...
311311
@classmethod

injection/_core/module.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -739,11 +739,11 @@ def use_temporarily(
739739
module: Module,
740740
*,
741741
priority: Priority | PriorityStr = Priority.get_default(),
742-
) -> Iterator[None]:
742+
) -> Iterator[Self]:
743743
self.use(module, priority=priority)
744744

745745
try:
746-
yield
746+
yield self
747747
finally:
748748
self.stop_using(module)
749749

@@ -762,7 +762,7 @@ def unlock(self) -> Self:
762762

763763
return self
764764

765-
def load_profile(self, *names: str) -> ContextManager[None]:
765+
def load_profile(self, *names: str) -> ContextManager[Self]:
766766
modules = tuple(self.from_name(name) for name in names)
767767

768768
for module in modules:
@@ -773,8 +773,8 @@ def load_profile(self, *names: str) -> ContextManager[None]:
773773
del module, modules
774774

775775
@contextmanager
776-
def cleaner() -> Iterator[None]:
777-
yield
776+
def cleaner() -> Iterator[Self]:
777+
yield self
778778
self.unlock().init_modules()
779779

780780
return cleaner()

injection/_core/scope.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,8 @@ def _bind_scope(name: str, scope: Scope, shared: bool) -> Iterator[None]:
121121
f"Scope `{name}` is already defined in the current context."
122122
)
123123

124-
strategy = (
125-
state.bind_shared_scope(scope) if shared else state.bind_contextual_scope(scope)
126-
)
127-
128-
with strategy:
124+
strategy = state.bind_shared_scope if shared else state.bind_contextual_scope
125+
with strategy(scope):
129126
yield
130127

131128

injection/testing/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import ContextManager, Final
22

3-
from injection import mod
3+
from injection import Module, mod
44
from injection.utils import load_profile
55

66
__all__ = (
@@ -23,5 +23,5 @@
2323
test_singleton = mod(_TEST_PROFILE_NAME).singleton
2424

2525

26-
def load_test_profile(*names: str) -> ContextManager[None]:
26+
def load_test_profile(*names: str) -> ContextManager[Module]:
2727
return load_profile(_TEST_PROFILE_NAME, *names)

injection/testing/__init__.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ test_injectable = __MODULE.injectable
1111
test_scoped = __MODULE.scoped
1212
test_singleton = __MODULE.singleton
1313

14-
def load_test_profile(*names: str) -> ContextManager[None]:
14+
def load_test_profile(*names: str) -> ContextManager[Module]:
1515
"""
1616
Context manager or decorator for temporary use test module.
1717
"""

injection/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
from types import ModuleType as PythonModule
66
from typing import ContextManager
77

8+
from injection import Module, mod
89
from injection import __name__ as injection_package_name
9-
from injection import mod
1010

1111
__all__ = ("load_modules_with_keywords", "load_packages", "load_profile")
1212

1313

14-
def load_profile(*names: str) -> ContextManager[None]:
14+
def load_profile(*names: str) -> ContextManager[Module]:
1515
"""
1616
Injection module initialization function based on profile name.
1717
A profile name is equivalent to an injection module name.

0 commit comments

Comments
 (0)