diff --git a/README.md b/README.md index 4c56cca7..766c42ec 100644 --- a/README.md +++ b/README.md @@ -39,17 +39,17 @@ pip install that-depends ## Ecosystem `that-depends` is part of the [`modern-python`](https://github.com/modern-python) family. -If you're starting a new project, consider [`modern-di`](https://github.com/modern-python/modern-di) — +If you're starting a new project, consider [`modern-di`](https://github.com/modern-python/modern-di), the newer DI framework from the same author, with separate framework adapters: -- [`modern-di`](https://github.com/modern-python/modern-di) — core DI framework with scopes +- [`modern-di`](https://github.com/modern-python/modern-di): core DI framework with scopes - [`modern-di-fastapi`](https://github.com/modern-python/modern-di-fastapi), [`modern-di-litestar`](https://github.com/modern-python/modern-di-litestar), [`modern-di-faststream`](https://github.com/modern-python/modern-di-faststream), [`modern-di-typer`](https://github.com/modern-python/modern-di-typer), [`modern-di-pytest`](https://github.com/modern-python/modern-di-pytest) -`that-depends` remains actively maintained — see the +`that-depends` remains actively maintained. See the [migration guide](https://modern-di.modern-python.org/migration/from-that-depends/) if you want to move existing projects across. @@ -61,5 +61,4 @@ want to move existing projects across. ## Part of `modern-python` -Browse the full list of templates and libraries in -[`modern-python`](https://github.com/modern-python) — see the org profile for the categorized index. +The [`modern-python`](https://github.com/modern-python) org profile has the full categorized list of templates and libraries. diff --git a/docs/assets/social-card.png b/docs/assets/social-card.png index 6ab02ea2..9569337e 100644 Binary files a/docs/assets/social-card.png and b/docs/assets/social-card.png differ diff --git a/docs/ecosystem.md b/docs/ecosystem.md index b8482b9b..26ef7054 100644 --- a/docs/ecosystem.md +++ b/docs/ecosystem.md @@ -1,11 +1,11 @@ # Ecosystem -`that-depends` is part of the [`modern-python`](https://github.com/modern-python) organization — +`that-depends` is part of the [`modern-python`](https://github.com/modern-python) organization, a collection of open-source templates and libraries for production-ready Python applications. ## Newer DI framework: `modern-di` -If you're starting a new project, consider [`modern-di`](https://github.com/modern-python/modern-di) — +If you're starting a new project, consider [`modern-di`](https://github.com/modern-python/modern-di), the newer DI framework from the same author. It ships as a small core plus a family of thin framework adapters, in contrast to `that-depends`'s batteries-included approach. @@ -28,13 +28,13 @@ walks through the API differences if you want to move an existing project across End-to-end examples using `modern-di` for dependency injection: -- [`fastapi-sqlalchemy-template`](https://github.com/modern-python/fastapi-sqlalchemy-template) — +- [`fastapi-sqlalchemy-template`](https://github.com/modern-python/fastapi-sqlalchemy-template): dockerized web application with DI on FastAPI, SQLAlchemy 2, PostgreSQL -- [`litestar-sqlalchemy-template`](https://github.com/modern-python/litestar-sqlalchemy-template) — +- [`litestar-sqlalchemy-template`](https://github.com/modern-python/litestar-sqlalchemy-template): dockerized web application on LiteStar, SQLAlchemy 2, PostgreSQL ## Full project index See the [`modern-python` organization profile](https://github.com/modern-python) for the complete categorized list, including microservice utilities (`lite-bootstrap`, the -`faststream-*` family) and helper packages (`db-retry`, `eof-fixer`). \ No newline at end of file +`faststream-*` family) and helper packages (`db-retry`, `eof-fixer`). diff --git a/docs/experimental/lazy.md b/docs/experimental/lazy.md index c35f23f1..405aa934 100644 --- a/docs/experimental/lazy.md +++ b/docs/experimental/lazy.md @@ -1,4 +1,4 @@ -# Lazy Provider +# Lazy provider The `LazyProvider` enables you to reference other providers without explicitly importing them into your module. @@ -7,7 +7,7 @@ This can be helpful if you have a circular dependency between providers in multiple containers. -## Creating a Lazy Provider +## Creating a lazy provider === "Single import string" ```python diff --git a/docs/index.md b/docs/index.md index 7128f1ee..fa48f806 100644 --- a/docs/index.md +++ b/docs/index.md @@ -49,7 +49,7 @@ async def create_async_resource(): logger.debug("Async resource destructed") ``` -### Setup Dependency Injection Container with Providers +### Set up a dependency injection container with providers ```python from that_depends import BaseContainer, providers diff --git a/docs/integrations/fastapi.md b/docs/integrations/fastapi.md index 69ef6230..61066087 100644 --- a/docs/integrations/fastapi.md +++ b/docs/integrations/fastapi.md @@ -1,13 +1,13 @@ # Usage with FastAPI !!! info "See also" - [`modern-di-fastapi`](https://github.com/modern-python/modern-di-fastapi) — the equivalent + [`modern-di-fastapi`](https://github.com/modern-python/modern-di-fastapi) is the equivalent FastAPI integration for [`modern-di`](https://github.com/modern-python/modern-di), the newer sibling DI framework. ## Installation -To use **`that-depends`** with FastAPI, you need to install the package with the `fastapi` extra. You can do this using pip: +To use `that-depends` with FastAPI, you need to install the package with the `fastapi` extra. You can do this using pip: ```bash pip install that-depends[fastapi] @@ -15,7 +15,7 @@ pip install that-depends[fastapi] --- -## Creating a Container +## Creating a container Suppose you have a simple container in a file called `mycontainer.py`: @@ -38,9 +38,9 @@ Here, `MyContainer.current_time` is a provider that, when called, creates a new --- -## Using a custom Router class +## Using a custom router class -You can use the `create_fastapi_route_class()` method to create custom Route class for your application: +You can use the `create_fastapi_route_class()` method to create a custom route class for your application: ```python from that_depends.integrations.fastapi import create_fastapi_route_class @@ -58,7 +58,7 @@ router = APIRouter(route_class=my_route_class) This will enable you to use dependency injection in your `FastAPI` endpoints: -> **Note**: If you don't want to use the custom router class, you can make use of `fastapi.Depends` instead. +> If you don't want to use the custom router class, you can use `fastapi.Depends` instead. === "Router class" @@ -90,7 +90,7 @@ This will enable you to use dependency injection in your `FastAPI` endpoints: ### Managing container context -If you wish to initialize the container context you can simply pass arguments to `create_fastapi_route_class()`: +To initialize the container context, pass arguments to `create_fastapi_route_class()`: ```python from that_depends import ContextScopes @@ -103,16 +103,16 @@ and the global context will be set. -## Integrating with FastAPI Using DIContextMiddleware +## Integrating with FastAPI using DIContextMiddleware The `DIContextMiddleware` can be used to manage context, but its features overlap with the [custom router class](#using-a-custom-router-class). The main advantage of using middleware is that you can set it up for your entire `FastAPI` application. -> **Note:** If you want to use both the `DIContextMiddleware` and the custom router class, you should not pass any arguments to `create_fastapi_route_class()`. +> If you want to use both the `DIContextMiddleware` and the custom router class, do not pass any arguments to `create_fastapi_route_class()`. -### Setting Up the FastAPI App +### Setting up the FastAPI app -You can use **`that-depends`**’s `DIContextMiddleware` so that any request automatically initializes the context for your container(s). This approach is convenient if you want to: +You can use the `DIContextMiddleware` from `that-depends` so that any request automatically initializes the context for your container(s). This approach is convenient if you want to: - Automatically initialize or tear down resources on each request. - Provide a global or request-level context dictionary you can read from your container. @@ -147,7 +147,7 @@ def get_time( ) ``` -- **`DIContextMiddleware`** automatically sets a “global context” for every request. +- `DIContextMiddleware` automatically sets a global context for every request. - The `Depends(MyContainer.current_time)` call is how you reference the container’s provider using the standard FastAPI injection system. To run this app: @@ -158,15 +158,15 @@ uvicorn main:app --reload When you make a request to `/`, you will see the current time printed, and behind the scenes the that-depends container is in a context. -> **Note**: If your container uses advanced context-based resources (e.g. `ContextResource`), you may also set `default_scope` in your container, or configure an explicit scope. See the advanced section below. +> If your container uses context-based resources such as `ContextResource`, you can also set `default_scope` in your container or configure an explicit scope. See the advanced section below. --- -## Examples of different Providers in FastAPI +## Examples of different providers in FastAPI -### Singleton Providers +### Singleton providers ```python # Suppose in mycontainer.py @@ -195,7 +195,7 @@ def read_settings(settings: AppSettings = Depends(MyAdvancedContainer.settings)) return {"db_url": settings.database_url} ``` -### Context Resources +### Context resources For “request-scoped” resources (e.g. a DB connection per request), you can use `ContextResource` in your container. This typically works in conjunction with `DIContextMiddleware` or a manual `container_context(...)` call. For example: @@ -247,7 +247,7 @@ async def read_db( When writing unit tests, you can use `TestClient` from `starlette.testclient` or `pytest-asyncio` with standard FastAPI patterns. The `DIContextMiddleware` approach ensures resources are created and torn down automatically each request, so no special arrangement is necessary. -**Example**: +For example: ```python import pytest @@ -268,21 +268,21 @@ def test_read_db(client: TestClient): --- -## Common Patterns and Tips +## Common patterns and tips -1. **Global vs. Request Context**: Decide whether your container’s dependencies should be globally shared (e.g., singletons) or created anew per request (e.g., database or session). -2. **Combining with FastAPI’s `Depends`**: Generally, you can pass `Depends(MyContainer.some_provider)` to route handlers. Under the hood, that-depends will be invoked. -3. **Overriding**: You can override a provider in tests by calling `MyContainer.some_provider.override_sync(...)` or using the context manager `with MyContainer.some_provider.override_context_sync(...):`. -4. **Performance**: If you have expensive creation logic (like a DB engine that can be reused globally), prefer using a `Singleton` or `Object` provider. If you need ephemeral resources, use `ContextResource` with the `DIContextMiddleware`. -5. **Custom Context**: If you do not want to rely on the middleware, you can manually create a context in any async function by calling `async with container_context(MyContainer):`. -6. **Multiple Containers**: You can define multiple containers and connect them (e.g., `ContainerA.connect_containers(ContainerB)`), or add them all to the `DIContextMiddleware`. For advanced usage, see the that-depends documentation on “container connection.” +- Decide whether your container's dependencies should be globally shared (e.g., singletons) or created anew per request (e.g., database or session). +- You can generally pass `Depends(MyContainer.some_provider)` to route handlers, and `that-depends` resolves the provider under the hood. +- You can override a provider in tests by calling `MyContainer.some_provider.override_sync(...)` or using the context manager `with MyContainer.some_provider.override_context_sync(...):`. +- If you have expensive creation logic (like a DB engine that can be reused globally), prefer a `Singleton` or `Object` provider. If you need ephemeral resources, use `ContextResource` with the `DIContextMiddleware`. +- If you do not want to rely on the middleware, you can create a context manually in any async function by calling `async with container_context(MyContainer):`. +- You can define multiple containers and connect them (e.g., `ContainerA.connect_containers(ContainerB)`), or add them all to the `DIContextMiddleware`. See [Usage with multiple containers](../introduction/multiple-containers.md) for details. -### Accessing the FastAPI Request or Other Context Items +### Accessing the FastAPI request or other context items Sometimes you want to pass the `fastapi.Request` (or other request-scoped data) into the container context so that providers can read it. You can do that either via the `DIContextMiddleware` (by customizing the `global_context` dynamically) or by writing your own dependency that calls `container_context()`. -**Example**: Writing a custom dependency that sets up the context with the current `Request`: +For example, a custom dependency that sets up the context with the current `Request`: ```python # request_deps.py diff --git a/docs/integrations/faststream.md b/docs/integrations/faststream.md index f860da0f..cef30598 100644 --- a/docs/integrations/faststream.md +++ b/docs/integrations/faststream.md @@ -1,11 +1,11 @@ # Usage with `FastStream` !!! info "See also" - [`modern-di-faststream`](https://github.com/modern-python/modern-di-faststream) — the + [`modern-di-faststream`](https://github.com/modern-python/modern-di-faststream) is the equivalent FastStream integration for [`modern-di`](https://github.com/modern-python/modern-di), the newer sibling DI framework. -`that-depends` is out of the box compatible with `faststream.Depends()`: +`that-depends` works out of the box with `faststream.Depends()`: ```python hl_lines="14" from typing import Annotated @@ -30,10 +30,10 @@ async def process( 1. This would be the same as `Provide[Container.suffix_factory]` -## Context Middleware +## Context middleware -If you are using [ContextResource](../providers/context-resources.md) provider, you likely will want to -initialize a context before processing message with `faststream.` +If you are using [ContextResource](../providers/context-resources.md) provider, you will likely want to +initialize a context before processing messages with `faststream.` `that-depends` provides integration for these use cases: diff --git a/docs/integrations/litestar.md b/docs/integrations/litestar.md index c7ba682e..cfd21735 100644 --- a/docs/integrations/litestar.md +++ b/docs/integrations/litestar.md @@ -1,7 +1,7 @@ # Usage with `Litestar` !!! info "See also" - [`modern-di-litestar`](https://github.com/modern-python/modern-di-litestar) — the equivalent + [`modern-di-litestar`](https://github.com/modern-python/modern-di-litestar) is the equivalent Litestar integration for [`modern-di`](https://github.com/modern-python/modern-di), the newer sibling DI framework. diff --git a/docs/introduction/generator-injection.md b/docs/introduction/generator-injection.md index 375654dc..4171b5dc 100644 --- a/docs/introduction/generator-injection.md +++ b/docs/introduction/generator-injection.md @@ -1,4 +1,4 @@ -# Injection into Generator Functions +# Injection into generator functions `that-depends` supports dependency injections into generator functions. However, this comes @@ -43,9 +43,9 @@ You can use the `@inject` decorator to inject dependencies into generator functi yield value ``` -## Supported Generators +## Supported generators -### Synchronous Generators +### Synchronous generators `that-depends` supports injection into sync generator functions with the following signature: @@ -60,7 +60,7 @@ This means that wrapping a sync generator with `@inject` will always preserve al - It will raise `StopIteration` when the generator is exhausted or otherwise returns. -### Asynchronous Generators +### Asynchronous generators `that-depends` supports injection into async generator functions with the following signature: @@ -73,7 +73,7 @@ This means that wrapping an async generator with `@inject` will have the followi - The generator will yield as expected - The generator will **not** accept values via `asend()` -If you need to send values to an async generator, you can simply resolve dependencies in the generator body: +If you need to send values to an async generator, you can resolve dependencies in the generator body: ```python @@ -95,7 +95,7 @@ as part of dependency injection into a generator. This is the case for both async and sync injection. -**For example:** +For example: ```python def sync_resource() -> typing.Iterator[float]: yield random.random() @@ -139,12 +139,10 @@ with container_context(scope=ContextScopes.REQUEST): next(injected()) ``` -Since no context initialization was needed, the generator will work as expected. - 1. Scope provided to `@inject` no longer matches scope of the `sync_provider` -### Container Context +### Container context Similarly to above, the `@container_context` also does **not** support generators: diff --git a/docs/introduction/injection.md b/docs/introduction/injection.md index a9d21ac1..4fefbb24 100644 --- a/docs/introduction/injection.md +++ b/docs/introduction/injection.md @@ -1,14 +1,14 @@ -# Injecting Providers in **that-depends** +# Injecting providers in `that-depends` -`that-depends` uses a decorator-based approach for both synchronous and asynchronous functions. By decorating a function with `@inject` and marking certain parameters as `Provide[...]`, **that-depends** will automatically resolve the specified providers at call time. +`that-depends` uses a decorator-based approach for both synchronous and asynchronous functions. By decorating a function with `@inject` and marking certain parameters as `Provide[...]`, `that-depends` will automatically resolve the specified providers at call time. --- ## Overview -In **that-depends**, you define your dependencies as `AbstractProvider` instances—e.g., `Singleton`, `Factory`, `Resource`, or others. These providers typically live inside a subclass of `BaseContainer`, making them globally accessible. +In `that-depends`, you define your dependencies as `AbstractProvider` instances, such as `Singleton`, `Factory`, or `Resource`. These providers typically live inside a subclass of `BaseContainer`, which makes them globally accessible. -When you want to use a provider in a function, you can mark a parameter’s **default value** as: +When you want to use a provider in a function, you can mark a parameter's default value as: ```python my_param = Provide[MyContainer.some_provider] @@ -18,11 +18,11 @@ You then decorate the function with `@inject`. This tells `that-depends` to auto --- -## Quick Start +## Quick start -Below is a simple example demonstrating how to define a container, declare a provider, and inject that provider into a function. +This example defines a container, declares a provider, and injects that provider into a function. -### 1. Define a Container and a Provider +### 1. Define a container and a provider ```python from that_depends import BaseContainer @@ -31,9 +31,9 @@ from that_depends.providers import Singleton class MyContainer(BaseContainer): greeting_provider = Singleton(lambda: "Hello from MyContainer") ``` -For more details on Containers, refer to the [Containers](ioc-container.md) documentation. +For more details on containers, refer to the [Containers](ioc-container.md) documentation. -### 2. Inject the Provider into a Function +### 2. Inject the provider into a function ```python from that_depends import inject, Provide @@ -48,7 +48,7 @@ Here: 1. We used `@inject` above `greet_user`. 2. We declared a parameter `greeting`, whose default value is `Provide[MyContainer.greeting_provider]`. -### 3. Call the Function +### 3. Call the function ```python print(greet_user()) # "Greeting: Hello from MyContainer" @@ -56,12 +56,12 @@ print(greet_user()) # "Greeting: Hello from MyContainer" --- -## The `@inject` Decorator in Detail +## The `@inject` decorator in detail -### Synchronous vs Asynchronous Functions +### Synchronous and asynchronous functions -`@inject` works on both sync and async functions. Just note that injecting async providers into sync functions is not supported. +`@inject` works on both sync and async functions, but you cannot inject async providers into sync functions. ```python @inject @@ -72,9 +72,9 @@ async def async_greet_user(greeting: str = Provide[MyContainer.greeting_provider --- -## Using `Provide[...]` as a Default +## Using `Provide[...]` as a default -It is recommended to wrap your provider in `Provide[...]` when using it as a default in an injected function since it provides correct type resolution: +Wrap your provider in `Provide[...]` when you use it as a default in an injected function, so that the parameter gets the correct type: ```python @inject @@ -88,17 +88,17 @@ def greet_user_direct( --- -## Injection Warnings +## Injection warnings If `@inject` finds **no** parameters whose default values are providers, it will issue a warning: > `Expected injection, but nothing found. Remove @inject decorator.` -This is to avoid accidentally decorating a function that doesn’t actually require injection. +The warning catches functions decorated by mistake that do not require injection. --- -## Specifying a Scope +## Specifying a scope By default, `@inject` uses the `ContextScopes.INJECT` scope. If you want to override that, do: @@ -111,7 +111,7 @@ def greet_user(greeting: str = Provide[MyContainer.greeting_provider]): ... ``` -When `greet_user` is called, **that-depends**: +When `greet_user` is called, `that-depends`: 1. Initializes the context for all `REQUEST` (or `ANY`) scoped `args` and `kwargs`. 2. Resolves all providers in the `args` and `kwargs` of the function. @@ -121,7 +121,7 @@ For more details regarding scopes and context management, see the [Context Resou --- -## Overriding Providers +## Overriding providers In tests or specialized scenarios, you may want to override a provider’s value temporarily. You can do so with the container’s `override_providers_sync()` method or the provider’s own `override_context_sync()`: @@ -139,24 +139,27 @@ For more details on overriding providers, see the [Overriding Providers](../test --- -## Frequently Asked Questions +## Frequently asked questions -1. **Do I need to call `@inject` every time I reference a provider?** - No—only when you want **automatic** injection of providers into function parameters. If you are resolving dependencies manually (e.g., `MyContainer.greeting_provider.resolve_sync()`), then `@inject` is not needed. +### Do I need to call `@inject` every time I reference a provider? - 2. **What if I provide a custom argument to a parameter that has a default provider?** - If you explicitly pass a value, that value overrides the injected default: +No, only when you want automatic injection of providers into function parameters. If you resolve dependencies manually (e.g., `MyContainer.greeting_provider.resolve_sync()`), you do not need `@inject`. - ~~~~python - @inject - def foo(x: int = Provide[MyContainer.number_factory]) -> int: - return x +### What if I provide a custom argument to a parameter that has a default provider? - print(foo()) # uses number_factory -> 42 - print(foo(99)) # explicitly uses 99 - ~~~~ +A value you pass explicitly overrides the injected default: -3. **Can I combine `@inject` with other decorators?** - Yes, you can. Generally, put `@inject` **below** others, depending on the order you need. If you run into issues, experiment with the order or handle context manually. +~~~~python +@inject +def foo(x: int = Provide[MyContainer.number_factory]) -> int: + return x + +print(foo()) # uses number_factory -> 42 +print(foo(99)) # explicitly uses 99 +~~~~ + +### Can I combine `@inject` with other decorators? + +Yes. Generally, put `@inject` below the others, depending on the order you need. If you run into issues, experiment with the order or handle context manually. --- diff --git a/docs/introduction/ioc-container.md b/docs/introduction/ioc-container.md index 07dac8ed..0f649a7c 100644 --- a/docs/introduction/ioc-container.md +++ b/docs/introduction/ioc-container.md @@ -1,9 +1,9 @@ -# The Dependency Injection Container +# The dependency injection container Containers serve as a central place to store and manage providers. You also define your dependency graph in the containers. -While providers can be defined outside of containers with that depends, this is not recommended +While providers can be defined outside of containers with `that-depends`, this is not recommended if you want to use any [context features](../providers/context-resources.md) @@ -39,4 +39,4 @@ class Container(BaseContainer): 1. The configuration will be resolved and then the `.db` attribute will be passed to the `create_db_session` creator as a keyword argument when resolving the `session` provider. 2. Depends on both the session and configuration providers. -3. Providers have the `cast` property that will change their type to the return type of their creator, use it to prevent type errors. +3. Providers have the `cast` property that will change their type to the return type of their creator; use it to prevent type errors. diff --git a/docs/introduction/multiple-containers.md b/docs/introduction/multiple-containers.md index aabd5af9..9b48a378 100644 --- a/docs/introduction/multiple-containers.md +++ b/docs/introduction/multiple-containers.md @@ -1,6 +1,6 @@ # Usage with multiple containers -You can use providers from other containers as following: +You can use providers from other containers as follows: ```python import datetime import typing diff --git a/docs/introduction/scopes.md b/docs/introduction/scopes.md index 5783a2e5..614fa785 100644 --- a/docs/introduction/scopes.md +++ b/docs/introduction/scopes.md @@ -1,11 +1,11 @@ -# Named Scopes +# Named scopes Named scopes allow you to define the lifecycle of a `ContextResource`. -In essence, they provide a tool to manage when `ContextResources` can be resolved and when they should be finalized. +They control when `ContextResources` can be resolved and when they should be finalized. Before continuing, make sure you're familiar with `ContextResource` providers by reading their [documentation](../providers/context-resources.md). -## Quick Start +## Quick start By default, `ContextResources` have the named scope `ANY`, meaning they will be re-initialized each time you enter a named scope. A container that defines a `ContextResource` without an explicit scope must assign `default_scope` before it, otherwise defining the container raises `DefaultScopeNotDefinedError`. @@ -133,7 +133,7 @@ await injected() # will resolve - `INJECT`: The default scope of the `@inject` wrapper. Read more in the [Named scopes with the @inject wrapper](#named-scopes-with-the-inject-wrapper) section. -> **Note:** The default scope, before entering any named scope, is `None`. You can pass `None` as a scope to providers, but since it cannot be entered, in most scenarios passing `None` simply means you did not specify a scope. +> The default scope, before entering any named scope, is `None`. You can pass `None` as a scope to providers, but since it cannot be entered, in most scenarios passing `None` means you did not specify a scope. ## Named scopes with the `@inject` wrapper @@ -146,7 +146,7 @@ def foo(...): ``` The `@inject` wrapper will enter a new context for each injected provider that matches the specified scope. -However, it will not enter the scope by default! +However, it does not enter the scope by default. Here is a simple example: ```python hl_lines="5 10" @@ -190,7 +190,7 @@ injected() 2. Context for `Container.provider` is initialized and will exit when the function returns. 3. This assertion will pass since the context for this provider is still the same. -This implementation might seem complex at first glance, but it provides the following advantages: +This design has the following advantages: - Only context for `ContextResource` providers you need is initialized. This improves performance. - It discourages explicit resolution via `.resolve()` or `.resolve_sync()` in the function body. diff --git a/docs/introduction/string-injection.md b/docs/introduction/string-injection.md index be88bde0..79b62923 100644 --- a/docs/introduction/string-injection.md +++ b/docs/introduction/string-injection.md @@ -17,10 +17,11 @@ To inject a provider by name, use the `Provide` marker with a string argument th Container.Provider[.attribute.attribute...] ``` -The string will be validated when it is passed to `Provide[]`, thus will raise an exception -immediately. +`Provide[]` checks the format of the string as soon as it receives it and raises a `ValueError` +immediately if the string does not match. It does not check that the container or provider exists; +that happens when the injected function is called. -**For example**: +For example: ```python from that_depends import BaseContainer, inject, Provide @@ -58,8 +59,8 @@ assert read() == "Damian" --- ## Considerations -This feature is primarily intended as a fallback when other options are not optimal or -simply not available, thus is recommended to be used sparingly. +This feature is intended mainly as a fallback when other options are unsuitable or +unavailable, so use it sparingly. If you do decide to use injection by name, consider the following: @@ -77,4 +78,4 @@ If you do decide to use injection by name, consider the following: injected() # will resolve ``` -- Validation of whether you have provided a correct container name and provider name will only happen when the function is called. +- `Provide[]` only checks the format of the string. The container and provider names are checked when the function is called, and an unknown container or provider raises a `ValueError` at that point. diff --git a/docs/introduction/tear-down.md b/docs/introduction/tear-down.md index 4e031508..f586c8e9 100644 --- a/docs/introduction/tear-down.md +++ b/docs/introduction/tear-down.md @@ -37,12 +37,12 @@ context manager. ## Propagation -Per default `that-depends` will propagate tear-down to dependent providers. +By default, `that-depends` propagates tear-down to dependent providers. This means that if you have defined a provider `A` that is dependent on provider `B`, when calling `await B.tear_down()`, this will also execute `await A.tear_down()`. -**For example:** +For example: ```python class MyContainer(BaseContainer): @@ -59,7 +59,7 @@ a_new = await MyContainer.A() assert a_new != a ``` -If you do not wish to propagate tear-down simply call `tear_down(propagate=False)` or `tear_down_sync(propagate=False)`. +To skip propagation, call `tear_down(propagate=False)` or `tear_down_sync(propagate=False)`. --- @@ -69,7 +69,7 @@ If you need to call tear-down from a sync context you can use the `tear_down_syn keep in mind that because dependent resources might be async, this will fail to correctly finalize these async resources. -Per default this will raise a `CannotTearDownSyncError`: +By default, this raises a `CannotTearDownSyncError`: ```python async def async_creator(val: float) -> typing.AsyncIterator[float]: diff --git a/docs/introduction/type-based-injection.md b/docs/introduction/type-based-injection.md index d57d0727..1e0977e7 100644 --- a/docs/introduction/type-based-injection.md +++ b/docs/introduction/type-based-injection.md @@ -3,7 +3,7 @@ `that-depends` also supports dependency injection without explicitly referencing the provider of the dependency. -## Quick Start +## Quick start In order to make use of this, you need to bind providers to the type they will provide: ```python @@ -11,7 +11,7 @@ class Container(BaseContainer): my_provider = providers.Factory(lambda: random.random()).bind(float) ``` -Then provide inject into your functions or generators: +Then inject into your functions or generators: === "Option 1" ```python @@ -29,7 +29,7 @@ Then provide inject into your functions or generators: ## Default bind -Per default, providers will **not** be bound to any type, even if your creator +By default, providers are **not** bound to any type, even if your creator function has type hints. So make sure to always bind your providers. You can also bind multiple types to the same provider: @@ -40,7 +40,7 @@ class Container(BaseContainer): ## Contravariant binding -Per default injection will be invariant to the bound types. +By default, injection is invariant to the bound types. If you wish to enable contravariance for your bound types you can do so by setting `#!python contravariant=True` in the `bind` method: diff --git a/docs/migration/v2.md b/docs/migration/v2.md index d268713e..12b4514b 100644 --- a/docs/migration/v2.md +++ b/docs/migration/v2.md @@ -1,6 +1,6 @@ # Migrating from 1.\* to 2.\* -## How to Read This Guide +## How to read this guide This guide is intended to help you migrate existing functionality from `that-depends` version `1.*` to `2.*`. The goal is to enable you to migrate as quickly as possible while making only the minimal necessary changes to your codebase. @@ -9,13 +9,12 @@ If you want to learn more about the new features introduced in `2.*`, please ref --- -## Deprecated or Removed Features +## Deprecated or removed features -### **`BaseContainer.init_async_resources()` removed** +### `BaseContainer.init_async_resources()` removed The method `BaseContainer.init_async_resources()` has been removed. Use `BaseContainer.init_resources()` instead. - **Example:** - If you are using containers, your setup might look like this: + For example, if you are using containers, your setup might look like this: ```python from that_depends import BaseContainer @@ -34,11 +33,10 @@ If you want to learn more about the new features introduced in `2.*`, please ref ``` --- -### **`that_depends.providers.AsyncResource` removed** +### `that_depends.providers.AsyncResource` removed The `AsyncResource` class has been removed. Use `providers.Resource` instead. - **Example:** Replace all instances of: ```python from that_depends.providers import AsyncResource @@ -52,12 +50,11 @@ If you want to learn more about the new features introduced in `2.*`, please ref --- -### **`BaseContainer` and its subclasses are no longer dynamic.** +### `BaseContainer` and its subclasses are no longer dynamic In `1.*`, you could define a container class and add providers to it dynamically. This feature has been removed in `2.*`. You must now define all providers in the container class itself. - **Example:** - In `1.*`, you could define a container and then dynamically set providers: + For example, in `1.*` you could define a container and then dynamically set providers: ```python from that_depends import BaseContainer @@ -76,7 +73,7 @@ If you want to learn more about the new features introduced in `2.*`, please ref ## Changes in the API -### **`container_context()` now requires a keyword argument for initial Context** +### `container_context()` now requires a keyword argument for initial context Previously, a global context could be initialized by passing a dictionary to the `container_context()` context manager: ```python @@ -95,7 +92,7 @@ async with container_context(global_context=my_global_context): --- -### **Context reset behavior changed in `container_context()`** +### Context reset behavior changed in `container_context()` Previously, calling `container_context(my_global_context)` would: - Set the global context to `my_global_context`, allowing values to be resolved using `fetch_context_item()`. This behavior remains the same. @@ -108,7 +105,7 @@ async with container_context(global_context=my_global_context, reset_all_contain assert fetch_context_item("some_key") == "some_value" ``` -> **Note:** `reset_all_containers=True` only reinitializes the context for `ContextResource` instances defined within containers (i.e., classes inheriting from `BaseContainer`). If you also need to reset contexts for resources defined outside containers, you must handle these explicitly. See the [ContextResource documentation](../providers/context-resources.md) for more details. +> `reset_all_containers=True` only reinitializes the context for `ContextResource` instances defined within containers (i.e., classes inheriting from `BaseContainer`). If you also need to reset contexts for resources defined outside containers, you must handle these explicitly. See the [ContextResource documentation](../providers/context-resources.md) for more details. Additionally, calling `container_context()` without any arguments will no longer reset the `global_context`, if you want to drop the `global_context` set `preserve_global_context=False`: @@ -120,11 +117,11 @@ async with container_context(preserve_global_context=False): --- -### **Container classes now require you to define `default_scope`** +### Container classes now require you to define `default_scope` In `2.*`, you must define the `default_scope` attribute in your container classes if you plan to define any `ContextResource` providers in that class. This attribute specifies the default scope for all `ContextResource` providers defined within the container. -**Example:** +For example: ```python from that_depends import BaseContainer, providers @@ -137,7 +134,7 @@ Setting the value of `default_context = None` maintains the same behaviours as i --- -## Potential Issues with `container_context()` +## Potential issues with `container_context()` If you have migrated the functionality as described above but still experience issues managing context resources, it might be due to improperly initializing resources when entering `container_context()`. @@ -158,11 +155,11 @@ To resolve such issues in `2.*`, consider the following suggestions: --- -### **Pass explicit arguments to `DIContextMiddleware`** +### Pass explicit arguments to `DIContextMiddleware` If you are using `DIContextMiddleware` with your ASGI application, you can now pass additional arguments. -**Example with `FastAPI`:** +For example, with `FastAPI`: ```python import fastapi @@ -180,10 +177,10 @@ This middleware will automatically initialize the context for the provided resou --- -### **Avoid entering `container_context()` without arguments** +### Avoid entering `container_context()` without arguments Pass all resources supporting context initialization (e.g., `providers.ContextResource` instances and `BaseContainer` subclasses) explicitly. -**Example:** +For example: ```python from that_depends import container_context @@ -201,6 +198,6 @@ Explicit initialization of container context is recommended to prevent unexpecte --- -## Further Help +## Further help If you continue to experience issues during migration, consider creating a [discussion](https://github.com/modern-python/that-depends/discussions) or opening an [issue](https://github.com/modern-python/that-depends/issues). diff --git a/docs/migration/v3.md b/docs/migration/v3.md index e749b86f..f447c65d 100644 --- a/docs/migration/v3.md +++ b/docs/migration/v3.md @@ -1,6 +1,6 @@ # Migrating from 2.\* to 3.\* -## How to Read This Guide +## How to read this guide This guide is intended to help you migrate existing functionality from `that-depends` version `2.*` to `3.*`. The goal is to enable you to migrate as quickly as possible while making only the minimal necessary changes to your codebase. @@ -9,9 +9,9 @@ If you want to learn more about the new features introduced in `3.*`, please ref --- -## Deprecated or Removed Features +## Deprecated or removed features -### **`container_context()`** can no longer be initialized without arguments. +### `container_context()` can no longer be initialized without arguments Previously the following code would reset the context for all all providers in all containers: ```python @@ -30,7 +30,7 @@ async with container_context(MyContainer_1, MyContainer_2, ...): --- -### **`container_context()`** no longer accepts `reset_all_containers` keyword argument. +### `container_context()` no longer accepts the `reset_all_containers` keyword argument You can no longer reset the context for all containers by using the `container_context` context manager. Previously you could have done something like this: @@ -47,7 +47,7 @@ async with container_context(MyContainer_1, MyContainer_2, ...): --- -### **`@inject(scope=...)`** no longer enters the scope. +### `@inject(scope=...)` no longer enters the scope The `@inject` decorator no longer enters the scope specified in the `scope` argument. @@ -71,7 +71,7 @@ For further details, please refer to the [scopes documentation](../introduction/ ## Changes in the API -### Changes to naming of methods. +### Changes to naming of methods You can expect the default implementation of provider and container methods to be async. This means that methods **not** explicitly ending with `_sync` are normally async. @@ -88,7 +88,7 @@ Other examples of similar changes include: --- -### Tear down propagation enabled per default. +### Tear-down propagation is enabled by default Tear down is now propagated to all dependencies by default. @@ -104,7 +104,7 @@ For more details regarding tear-down propagation see the [documentation](../intr --- -### Overriding is now async per default. +### Overriding is now async by default As mentioned [above](#changes-to-naming-of-methods), `.override()` methods are now async per default. @@ -122,10 +122,10 @@ This is also the case for the following methods: - `.override_context()` -> `.override_context_sync()` - `.reset_override()` -> `.reset_override_sync()` -> **Note:** Overrides now support tear-down, read more in the [documentation](../testing/provider-overriding.md) +> Overrides now support tear-down. Read more in the [documentation](../testing/provider-overriding.md) --- -## Further Help +## Further help If you continue to experience issues during migration, consider creating a [discussion](https://github.com/modern-python/that-depends/discussions) or opening an [issue](https://github.com/modern-python/that-depends/issues). diff --git a/docs/migration/v4.md b/docs/migration/v4.md index 95038036..0647b990 100644 --- a/docs/migration/v4.md +++ b/docs/migration/v4.md @@ -1,6 +1,6 @@ # Migrating from 3.\* to 4.\* -## How to Read This Guide +## How to read this guide This guide is intended to help you migrate existing functionality from `that-depends` version `3.*` to `4.*`. The goal is to enable you to migrate as quickly as possible while making only the minimal necessary changes to your codebase. @@ -13,7 +13,7 @@ If you want to learn more about the new internals introduced in `4.*`, please re ## Changes in the API -### **Collection providers now return read-only container types** +### Collection providers now return read-only container types In `4.*`, collection providers no longer resolve to mutable built-in containers: @@ -31,9 +31,9 @@ mapping = dict(MyContainer.mapping.resolve_sync()) --- -## Behaviour-Preserving Migration Examples +## Behaviour-preserving migration examples -### **`providers.List(...)`** +### `providers.List(...)` Previously in `3.*`, code like this returned a `list`: @@ -60,7 +60,7 @@ items.append("new-item") --- -### **`providers.Dict(...)`** +### `providers.Dict(...)` Previously in `3.*`, code like this returned a mutable `dict`: @@ -87,6 +87,6 @@ mapping["extra"] = "value" --- -## Further Help +## Further help If you continue to experience issues during migration, consider creating a [discussion](https://github.com/modern-python/that-depends/discussions) or opening an [issue](https://github.com/modern-python/that-depends/issues). diff --git a/docs/providers/context-resources.md b/docs/providers/context-resources.md index 497bc025..46c3bc0e 100644 --- a/docs/providers/context-resources.md +++ b/docs/providers/context-resources.md @@ -1,4 +1,4 @@ -# Context-Dependent Resources +# Context-dependent resources `that-depends` provides a way to manage two types of contexts: @@ -12,11 +12,11 @@ To interact with both types of contexts, there are two separate interfaces: and `ContextResource` providers implement. --- -## Quick Start +## Quick start You must initialize a context before you can resolve a `ContextResource`. -**Setup:** +Start with a container that defines `ContextResource` providers: ```python import typing @@ -55,7 +55,7 @@ await func() # returns "async resource" This will initialize a new context for `async_resource` each time `func` is called. --- -## Global Context +## Global context A global context can be initialized by using the `container_context` context manager. @@ -99,7 +99,7 @@ async with container_context(global_context={"key": "value"}): fetch_context_item("key") # returns 'value' ``` -Additionally, you can use the `global_context` argument in combination with `preserve_global_context` to +You can also use the `global_context` argument in combination with `preserve_global_context` to extend the global context. This merges the two contexts together by key, with the new `global_context` taking precedence: ```python async with container_context(global_context={"key_1": "value_1", "key_2": "value_2"}): @@ -128,7 +128,7 @@ with container_context(global_context={"key": 4}): --- -## Context Resources +## Context resources To resolve a `ContextResource`, you must first initialize a new context for that resource. ```python @@ -173,7 +173,7 @@ async def my_func(): ### More granular context initialization -If you do not wish to simply reinitialize the context for all containers, you can initialize a context for a specific container: +Instead of reinitializing the context for all containers, you can initialize a context for a specific container: ```python # this will init a new context for all ContextResources in MyContainer and any connected containers. async with container_context(MyContainer): @@ -189,7 +189,7 @@ async with container_context(MyContainer.async_resource): It is not necessary to use `container_context()` to do this. Instead, you can use the `SupportsContext` protocol described [here](#quick-reference). -### Context Hierarchy +### Context hierarchy Resources are cached in the context after their first resolution. They are torn down when `container_context` exits: @@ -226,8 +226,8 @@ Each time you call `await insert_into_database()`, a new instance of `session` w | Reset all resources in a container | `async with container_context(my_container):` | `async with my_container.context_async():` | `@my_container.context` | | Reset all sync resources in a container | `with container_context(my_container):` | `with my_container.context_sync():` | `@my_container.context` | -> **Note:** the `context()` wrapper is technically not part of the `SupportsContext` API, however all classes which -> implement this `SupportsContext` also implement this method. +> The `context()` wrapper is technically not part of the `SupportsContext` API, but all classes that +> implement `SupportsContext` also implement this method. --- ## Middleware @@ -236,7 +236,7 @@ For `ASGI` applications, `that-depends` provides the `DIContextMiddleware` to ma The `DIContextMiddleware` accepts containers and resources as arguments and automatically initializes the context for the provided resources when an endpoint is called. -**Example with `FastAPI`:** +For example, with `FastAPI`: ```python import fastapi from that_depends.providers import DIContextMiddleware, ContextResource diff --git a/docs/providers/factories.md b/docs/providers/factories.md index f3904b3b..6a9ef539 100644 --- a/docs/providers/factories.md +++ b/docs/providers/factories.md @@ -39,20 +39,20 @@ class DIContainer(BaseContainer): > Note: If you have a class that has dependencies which need to be resolved asynchronously, you can use `AsyncFactory` to create instances of that class. The factory will handle the async resolution of dependencies. -## Retrieving provider as a Callable +## Retrieving a provider as a callable When you use a factory‑based provider such as `Factory` (for sync logic) or `AsyncFactory` (for async logic), the resulting provider instance has two special properties: -- **`.provider`** — returns an *async callable* that, when awaited, resolves the resource. -- **`.provider_sync`** — returns a *sync callable* that, when called, resolves the resource. +- `.provider` returns an *async callable* that, when awaited, resolves the resource. +- `.provider_sync` returns a *sync callable* that, when called, resolves the resource. -You can think of these as no-argument functions that produce the resource you defined—similar to calling `resolve()` or `resolve_sync()` directly, but in a more convenient form when you want a standalone function handle. +You can think of these as no-argument functions that produce the resource you defined. They behave like calling `resolve()` or `resolve_sync()` directly and are convenient when you want a standalone function handle. --- -### Basic Usage +### Basic usage -#### Defining Providers in a Container +#### Defining providers in a container Suppose you have a `BaseContainer` subclass that defines both a sync and an async resource: @@ -79,11 +79,11 @@ Here, `sync_message` is a `Factory` which calls a plain function, while `async_m --- -#### Resolving Resources via `.provider` and `.provider_sync` +#### Resolving resources via `.provider` and `.provider_sync` The `.provider` property gives you an *async function* to await, and `.provider_sync` gives you a *synchronous* callable. They effectively wrap `.resolve()` and `.resolve_sync()`. -**Synchronous Resolution** +##### Synchronous resolution ```python # In a synchronous function or interactive session @@ -94,7 +94,7 @@ Hello from sync provider! Here, `provider_sync` is a no-argument function that immediately returns the resolved value. -**Asynchronous Resolution** +##### Asynchronous resolution ```python import asyncio @@ -111,7 +111,7 @@ Within an async function, `MyContainer.async_message.provider` gives a no-argume --- -### Passing the Provider Function Around +### Passing the provider function around Sometimes you may want to store or pass around the provider function itself (rather than resolving it immediately): @@ -134,7 +134,7 @@ Because `.provider_sync` is just a callable returning your dependency, it can be --- -### Example: Using Factories with Parameters +### Example: using factories with parameters `Factory` and `AsyncFactory` can accept dependencies (including other providers) as parameters: @@ -157,7 +157,7 @@ Under the hood, `greeting` calls `greet` with the result of `name.resolve_sync() --- -### Context Considerations +### Context considerations If your providers use `ContextResource` or require a named scope (for instance, `REQUEST`), you need to wrap your resolves in a context manager: diff --git a/docs/providers/object.md b/docs/providers/object.md index 14af879d..42fc0fdc 100644 --- a/docs/providers/object.md +++ b/docs/providers/object.md @@ -1,6 +1,6 @@ # Object -Object provider returns an object “as is”. +Object provider returns an object "as is". ```python from that_depends import BaseContainer, providers diff --git a/docs/providers/resources.md b/docs/providers/resources.md index 04166ebf..464a6ff6 100644 --- a/docs/providers/resources.md +++ b/docs/providers/resources.md @@ -1,4 +1,4 @@ -# Resource Provider +# Resource provider A `Resource` resolves once, caches the instance, and runs teardown logic from a generator or context manager. A plain `Singleton` has no teardown step. @@ -6,21 +6,18 @@ The creator can be a generator or async generator function, with teardown after or a class that implements `typing.ContextManager` or `typing.AsyncContextManager`. A `Resource` does not automatically integrate with `container_context`. -This makes `Resource` ideal for dependencies that need: - -1. A **single creation** step, -2. A **single finalization** step, -3. **Thread/async safety**—all consumers receive the same resource object, and concurrency is handled. +Use `Resource` for dependencies that need a single creation step, a single finalization step, and thread and async safety. +All consumers receive the same resource object, and concurrency is handled. --- -## How It Works +## How it works -### Defining a Sync or Async Resource +### Defining a sync or async resource -You can define your creation logic as either a **generator** or a **context manager** class (sync or async). +You can define your creation logic as either a generator or a context manager class (sync or async). -**Synchronous generator** example: +A synchronous generator: ```python import typing @@ -33,7 +30,7 @@ def create_sync_resource() -> typing.Iterator[str]: print("Tearing down sync resource") ``` -**Asynchronous generator** example: +An asynchronous generator: ```python import typing @@ -59,9 +56,9 @@ class MyContainer(BaseContainer): --- -## Resolving and Teardown +## Resolving and teardown -Once defined, you can explicitly **resolve** the resource and **tear it down**: +Once defined, you can explicitly resolve the resource and tear it down: ```python # Synchronous resource usage @@ -82,17 +79,17 @@ async def main(): asyncio.run(main()) ``` -- **`resolve_sync()`** or **`resolve()`**: Creates (if needed) and returns the resource instance. -- **`tear_down_sync()`** or **`tear_down()`**: Closes/cleans up the resource (triggering your `finally` block or exiting the context manager) and resets the cached instance to `None`. A subsequent resolve call will then recreate it. +- `resolve_sync()` or `resolve()` creates the resource instance if needed and returns it. +- `tear_down_sync()` or `tear_down()` cleans up the resource (running your `finally` block or exiting the context manager) and resets the cached instance to `None`. The next resolve call recreates it. --- -## Concurrency Safety +## Concurrency safety -`Resource` is **safe** to use under **threading** and **asyncio** concurrency. Internally, a lock ensures only one resource instance is created per container: +`Resource` is safe to use under threading and asyncio concurrency. Internally, a lock ensures only one resource instance is created per container: -- Multiple threads calling `resolve_sync()` simultaneously will produce a **single** instance for that container. -- Multiple coroutines calling `resolve()` simultaneously will likewise produce **only one** instance for that container in an async environment. +- Multiple threads calling `resolve_sync()` simultaneously will produce a single instance for that container. +- Multiple coroutines calling `resolve()` simultaneously will likewise produce only one instance for that container in an async environment. ```python # Even if multiple coroutines call resolve in parallel, @@ -106,9 +103,9 @@ MyContainer.sync_resource.resolve_sync() --- -## Using Context Managers Directly +## Using context managers directly -If your resource is a standard **context manager** or **async context manager** class, `Resource` will handle entering and exiting it under the hood. For example: +If your resource is a standard context manager or async context manager class, `Resource` will handle entering and exiting it under the hood. For example: ```python import typing diff --git a/docs/providers/selector.md b/docs/providers/selector.md index 9fc4a66c..5134a091 100644 --- a/docs/providers/selector.md +++ b/docs/providers/selector.md @@ -1,6 +1,6 @@ # Selector -The Selector provider chooses between provider based on a key. This resolves into a single dependency. +The Selector provider chooses between providers based on a key. This resolves into a single dependency. The selector can be a callable that returns a string, an instance of `AbstractProvider` or a string. @@ -50,7 +50,7 @@ class DIContainer(BaseContainer): ## Fixed string selectors -This can be useful for quickly testing. +This can be useful for quick testing. ```python class DIContainer(BaseContainer): diff --git a/docs/providers/singleton.md b/docs/providers/singleton.md index 81719915..77359dc1 100644 --- a/docs/providers/singleton.md +++ b/docs/providers/singleton.md @@ -1,8 +1,8 @@ -# Singleton Provider +# Singleton provider -A **Singleton** provider creates its instance once and caches it for all future injections or resolutions. When the instance is first requested (via `resolve_sync()` or `resolve()`), the underlying factory is called. On subsequent calls, the cached instance is returned without calling the factory again. +A `Singleton` provider creates its instance once and caches it for all future injections or resolutions. When the instance is first requested (via `resolve_sync()` or `resolve()`), the underlying factory is called. On subsequent calls, the cached instance is returned without calling the factory again. -## How it Works +## How it works ```python import random @@ -37,7 +37,7 @@ async def with_singleton(number: float = Provide[MyContainer.singleton]): ... ``` -### Teardown Support +### Teardown support If you need to reset the singleton (for example, in tests or at application shutdown), you can call: ```python await MyContainer.singleton.tear_down() @@ -49,15 +49,11 @@ For further details refer to the [teardown documentation](../introduction/tear-d --- -## Concurrency Safety +## Concurrency safety -`Singleton` is **thread-safe** and **async-safe**: - -1. **Async Concurrency** - If multiple coroutines call `resolve()` concurrently, the factory function is guaranteed to be called only once. All callers receive the same cached instance. - -2. **Thread Concurrency** - If multiple threads call `resolve_sync()` at the same time, the factory is only called once. All threads receive the same cached instance. +`Singleton` is thread-safe and async-safe. +If multiple coroutines call `resolve()` concurrently, the factory function is guaranteed to be called only once, and all callers receive the same cached instance. +If multiple threads call `resolve_sync()` at the same time, the factory is also called only once, and all threads receive the same cached instance. ```python import threading @@ -87,9 +83,9 @@ for t in threads: --- -## ThreadLocalSingleton Provider +## ThreadLocalSingleton provider -If you want each *thread* to have its own, separately cached instance, use **ThreadLocalSingleton**. This provider creates a new instance per thread and reuses that instance on subsequent calls *within the same thread*. +If you want each *thread* to have its own, separately cached instance, use `ThreadLocalSingleton`. This provider creates a new instance per thread and reuses that instance on subsequent calls *within the same thread*. ```python import random @@ -123,13 +119,13 @@ thread2.start() # thread1 and thread2 each get a different cached value ``` -You can still use `.resolve()` with `ThreadLocalSingleton`, which will also maintain isolation per thread. However, note that this does *not* isolate instances per asynchronous Task – only per OS thread. +You can still use `.resolve()` with `ThreadLocalSingleton`, which also keeps instances isolated per thread. The isolation is per OS thread, *not* per asynchronous task. --- ## Example with `pydantic-settings` -Consider a scenario where your application configuration is defined via [**pydantic-settings**](https://docs.pydantic.dev/latest/concepts/pydantic_settings/). Often, you only want to parse this configuration (e.g., from environment variables) once, then reuse it throughout the application. +Consider a scenario where your application configuration is defined via [pydantic-settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/). Often, you only want to parse this configuration (e.g., from environment variables) once, then reuse it throughout the application. ```python from pydantic_settings import BaseSettings @@ -147,9 +143,9 @@ class Settings(BaseSettings): db: DatabaseConfig = DatabaseConfig() ``` -### Defining the Container +### Defining the container -Below, we define a container with a **Singleton** provider for our settings. We also define a separate async factory that connects to the database using those settings. +Below, we define a container with a `Singleton` provider for our settings. We also define a separate async factory that connects to the database using those settings. ```python from that_depends import BaseContainer, providers @@ -171,7 +167,7 @@ class MyContainer(BaseContainer): ) ``` -### Injecting or Resolving in Code +### Injecting or resolving in code You can now inject these values directly into your functions with the `@inject` decorator: diff --git a/docs/providers/state.md b/docs/providers/state.md index 98d75792..ec4ff4a2 100644 --- a/docs/providers/state.md +++ b/docs/providers/state.md @@ -7,7 +7,7 @@ It is useful when you want to pass a value into your Container that other provid ## Creating a state provider -The `State` provider does not accept any arguments when it created. +The `State` provider does not accept any arguments when it is created. ```python from that_depends import BaseContainer, providers class Container(BaseContainer): @@ -31,12 +31,12 @@ class Container(BaseContainer): ``` -> Note: If you try to resolve a `State` provider without initializing it first it will raise an `StateNotInitializedError`. +> Note: If you try to resolve a `State` provider without initializing it first it will raise a `StateNotInitializedError`. ## Nested state -The `State` provider will always resolve the last initialize value. +The `State` provider will always resolve the last initialized value. ```python with Container.my_state.init(1): diff --git a/docs/testing/provider-overriding.md b/docs/testing/provider-overriding.md index 30b41ae3..19b82c8b 100644 --- a/docs/testing/provider-overriding.md +++ b/docs/testing/provider-overriding.md @@ -1,7 +1,7 @@ # Provider overriding Any provider in a container can be overridden, for example with a stub in tests. -**Override affects all providers that use the overridden provider (_see example_)**. +Overriding a provider affects all providers that use it, as the example below shows. ## Example @@ -105,7 +105,7 @@ def main(): --- ## Using with Litestar In order to be able to inject dependencies of any type instead of existing objects, -we need to **change the typing** for the injected parameter as follows: +we need to change the typing of the injected parameter as follows: ```python3 import typing @@ -156,7 +156,7 @@ router = Router( app = Litestar(route_handlers=[router]) ``` -Now we are ready to write tests with **overriding** and this will work with **any types**: +Tests can then override the dependency with a value of any type: ```python3 def test_litestar_endpoint_with_overriding() -> None: