Skip to content

Commit 25d6f30

Browse files
Improve Rodi docs
1 parent a4f2cce commit 25d6f30

4 files changed

Lines changed: 71 additions & 19 deletions

File tree

rodi/docs/async.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@ Support for async resolution is intentionally out of the scope of the library be
44
constructing objects should be lightweight.
55

66
This page provides guidelines for working with objects that require asynchronous
7-
initialization.
7+
initialization or disposal.
88

99
## A common example
1010

11-
A common example of this situation are objects that handle TCP/IP connection pooling,
12-
such as `HTTP` clients and database clients. These objects are usually implemented as
13-
*context managers* in Python because they need to implement connection pooling and
14-
gracefully close TCP connections when disposed.
11+
A common example of objects requiring asynchronous disposal are objects that
12+
handle TCP/IP connection pooling, such as `HTTP` clients and database clients.
13+
These objects are typically implemented as *context managers* in Python because
14+
they need to manage connection pooling and gracefully close TCP connections
15+
upon disposal.
1516

16-
Python supports [`asynchronous` context managers](https://peps.python.org/pep-0492/#asynchronous-context-managers-and-async-with) for this kind of scenario.
17+
Python provides [`asynchronous` context managers](https://peps.python.org/pep-0492/#asynchronous-context-managers-and-async-with) for this kind of scenario.
1718

1819
Consider the following example, of a `SendGrid` API client to send emails using the
1920
SendGrid API, with asynchronous code and using [`httpx`](https://www.python-httpx.org/async/).
@@ -82,7 +83,7 @@ class SendGridClient(EmailHandler):
8283
},
8384
json=self.get_body(email),
8485
)
85-
# Note: in case of error, inspect response.text
86+
# TODO: in case of error, log response.text
8687
response.raise_for_status() # Raise an error for bad responses
8788

8889
def get_body(self, email: Email) -> dict:
@@ -110,7 +111,7 @@ the one shown on this page to send emails using SendGrid in async code.
110111
///
111112

112113
The **SendGridClient** depends on an instance of `SendGridClientSettings` (providing a
113-
SendGrid API Key), and on an instance of `httpx.AsyncClient` able to make HTTP requests.
114+
SendGrid API Key), and on an instance of `httpx.AsyncClient` to make async HTTP requests.
114115

115116
The code below shows how to register the object that requires asynchronous
116117
initialization and use it across the lifetime of your application.
@@ -182,9 +183,10 @@ HTTP client disposed
182183

183184
## Considerations
184185

185-
- It is not Rodi's responsibility to administer the lifecycle of the application. It is
186-
the responsibility of the code that bootstrap the application, to handle objects that
187-
require asynchronous initialization and disposal.
186+
- It is not Rodi's responsibility to administer the lifecycle of the
187+
application. It is the responsibility of the code that bootstraps the
188+
application, to handle objects that require asynchronous initialization and
189+
disposal.
188190
- Python's `asynccontextmanager` is convenient for these scenarios.
189191
- In the example above, the HTTP Client is configured as singleton to benefit from TCP
190192
connection pooling. It would also be possible to configure it as transient or scoped

rodi/docs/dependency-inversion.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,9 @@ The above prints to screen:
309309
type: warning
310310

311311
Note how the generics `Repository[Product]` and `Repository[Customer]` are both
312-
configured to be resolved using `Repository` as concrete type. Instances of
313-
`GenericAlias` are not considered as actual classes. The following wouldn't
314-
work:
312+
configured to be resolved using `Repository` as concrete type. In Python,
313+
instances of `GenericAlias` are not considered as actual classes. The following
314+
wouldn't work:
315315

316316
```python
317317
container.add_scoped(Repository[Product]) # No. 💥
@@ -375,7 +375,7 @@ key `Repository[T]` when instantiating the `ProductsService`, not for
375375
container.add_scoped(Repository[Product], Repository) # No. 💥
376376
```
377377

378-
Note that, in practice, this does not cause any issues at runtime, because of
378+
Note that, in practice, this does not cause issues at runtime, because of
379379
**type erasure**. For more information, refer to [_Instantiating generic classes and type erasure_](https://typing.python.org/en/latest/spec/generics.html#instantiating-generic-classes-and-type-erasure).
380380

381381
If you need to define a more specialized class for `Repository[Product]`,

rodi/docs/registering-types.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,60 @@ with scoped lifetime:
279279
assert c1.context is not c2.context
280280
```
281281

282+
/// details | Nested scopes.
283+
type: warning
284+
285+
Rodi was not designed having _nested_ scopes in mind. Scopes are designed to
286+
identify a resolution call for a single event, such as DI resolution for a
287+
single HTTP request.
288+
289+
Since version `2.0.7`, Rodi offers the possibility to specify the
290+
`ActivationScope` class used by the container, when instantiating the
291+
`Container` object. This class will be used when creating new scopes. Version
292+
`2.0.7` also added an **experimental** class, `TrackingActivationScope` to
293+
support nested scopes transparently, using `contextvars.ContextVar`.
294+
295+
```python {linenums="1" hl_lines="2 12 16 27"}
296+
def test_nested_scope_1():
297+
container = Container(scope_cls=TrackingActivationScope)
298+
container.add_scoped(Ok)
299+
provider = container.build_provider()
300+
301+
with provider.create_scope() as context_1:
302+
a = provider.get(Ok, context_1)
303+
304+
with provider.create_scope() as context_2:
305+
b = provider.get(Ok, context_2)
306+
307+
assert a is b
308+
309+
310+
def test_nested_scope_2():
311+
container = Container(scope_cls=TrackingActivationScope)
312+
container.add_scoped(Ok)
313+
provider = container.build_provider()
314+
315+
with provider.create_scope():
316+
with provider.create_scope() as context:
317+
a = provider.get(Ok, context)
318+
319+
with provider.create_scope() as context:
320+
b = provider.get(Ok, context)
321+
322+
assert a is not b
323+
```
324+
325+
///
326+
327+
Note how the generics `Repository[Product]` and `Repository[Customer]` are both
328+
configured to be resolved using `Repository` as concrete type. Instances of
329+
`GenericAlias` are not considered as actual classes. The following wouldn't
330+
work:
331+
332+
```python
333+
container.add_scoped(Repository[Product]) # No. 💥
334+
container.add_scoped(Repository[Customer]) # No. 💥
335+
```
282336

283337
## Using factories
284338

rodi/mkdocs.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ theme:
3434
name: Switch to dark mode
3535
name: "material"
3636
custom_dir: overrides/
37-
# highlightjs: true # ?
3837
favicon: img/neoteroi.ico
3938
logo: img/neoteroi-w.svg
4039
icon:
@@ -55,9 +54,6 @@ plugins:
5554
- neoteroi.contribs
5655

5756
markdown_extensions:
58-
# - markdown.extensions.codehilite:
59-
# linenums: true
60-
# guess_lang: false
6157
- pymdownx.highlight:
6258
use_pygments: true
6359
guess_lang: false

0 commit comments

Comments
 (0)