Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions rodi/docs/async.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ Support for async resolution is intentionally out of the scope of the library be
constructing objects should be lightweight.

This page provides guidelines for working with objects that require asynchronous
initialization.
initialization or disposal.

## A common example

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

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

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

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

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

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

## Considerations

- It is not Rodi's responsibility to administer the lifecycle of the application. It is
the responsibility of the code that bootstrap the application, to handle objects that
require asynchronous initialization and disposal.
- It is not Rodi's responsibility to administer the lifecycle of the
application. It is the responsibility of the code that bootstraps the
application, to handle objects that require asynchronous initialization and
disposal.
- Python's `asynccontextmanager` is convenient for these scenarios.
- In the example above, the HTTP Client is configured as singleton to benefit from TCP
connection pooling. It would also be possible to configure it as transient or scoped
Expand Down
8 changes: 4 additions & 4 deletions rodi/docs/dependency-inversion.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,9 @@ The above prints to screen:
type: warning

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

```python
container.add_scoped(Repository[Product]) # No. 💥
Expand Down Expand Up @@ -375,7 +375,7 @@ key `Repository[T]` when instantiating the `ProductsService`, not for
container.add_scoped(Repository[Product], Repository) # No. 💥
```

Note that, in practice, this does not cause any issues at runtime, because of
Note that, in practice, this does not cause issues at runtime, because of
**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).

If you need to define a more specialized class for `Repository[Product]`,
Expand Down
54 changes: 54 additions & 0 deletions rodi/docs/registering-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,60 @@ with scoped lifetime:
assert c1.context is not c2.context
```

/// details | Nested scopes.
type: warning

Rodi was not designed having _nested_ scopes in mind. Scopes are designed to
identify a resolution call for a single event, such as DI resolution for a
single HTTP request.

Since version `2.0.7`, Rodi offers the possibility to specify the
`ActivationScope` class used by the container, when instantiating the
`Container` object. This class will be used when creating new scopes. Version
`2.0.7` also added an **experimental** class, `TrackingActivationScope` to
support nested scopes transparently, using `contextvars.ContextVar`.

```python {linenums="1" hl_lines="2 12 16 27"}
def test_nested_scope_1():
container = Container(scope_cls=TrackingActivationScope)
container.add_scoped(Ok)
provider = container.build_provider()

with provider.create_scope() as context_1:
a = provider.get(Ok, context_1)

with provider.create_scope() as context_2:
b = provider.get(Ok, context_2)

assert a is b


def test_nested_scope_2():
container = Container(scope_cls=TrackingActivationScope)
container.add_scoped(Ok)
provider = container.build_provider()

with provider.create_scope():
with provider.create_scope() as context:
a = provider.get(Ok, context)

with provider.create_scope() as context:
b = provider.get(Ok, context)

assert a is not b
```

///

Note how the generics `Repository[Product]` and `Repository[Customer]` are both
configured to be resolved using `Repository` as concrete type. Instances of
`GenericAlias` are not considered as actual classes. The following wouldn't
work:

```python
container.add_scoped(Repository[Product]) # No. 💥
container.add_scoped(Repository[Customer]) # No. 💥
```

## Using factories

Expand Down
4 changes: 0 additions & 4 deletions rodi/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ theme:
name: Switch to dark mode
name: "material"
custom_dir: overrides/
# highlightjs: true # ?
favicon: img/neoteroi.ico
logo: img/neoteroi-w.svg
icon:
Expand All @@ -55,9 +54,6 @@ plugins:
- neoteroi.contribs

markdown_extensions:
# - markdown.extensions.codehilite:
# linenums: true
# guess_lang: false
- pymdownx.highlight:
use_pygments: true
guess_lang: false
Expand Down