Repository navigation
Releases: python-anydi/anydi
Release list
0.82.0
Python 3.11 to 3.15
AnyDI now supports Python 3.15 and no longer supports Python 3.10, which
reaches end of life this month.
On Python 3.10, pip stays on 0.81.0 and does not install this release. To
upgrade, move the project to Python 3.11 or later first.
0.81.0
0.80.0
Breaking
- Only the class a decorator was applied to is provided.
is_providedread__provided__withhasattr, and a subclass inherits it, socontainer.scan()registered classes nobody marked, an inheritedaliasregistered the same interface twice, and an inheritedfrom_contextclaimed a registration nobody made. A subclass that used to resolve by inheriting its base's scope now raisesLookupErrornaming the class, so decorate it too. by @antonrh in #331
@singleton
class BaseSender: ...
@singleton
class SmtpSender(BaseSender): ...Lifespan
- Closing a container drops the instances whose finalisers ran, so reopening it makes the resources again. An instance with no resource of its own stays. by @antonrh in #328
container.release(T)closes the resource by @antonrh in #316container.release(T)drops the instance even when the finaliser raises, andcontainer.reset()releases everything before re-raising the first failure by @antonrh in #327- A scope whose entry fails closes what it had already opened, which Python skips when
__enter__raises by @antonrh in #312 - Exiting a context with
withwhile it holds an async resource raises, instead of leaving the resource open by @antonrh in #315
Resolution
- A module subclass keeps the providers its base declared, and replaces the ones it redefines by @antonrh in #331
- A
TypeVarrenamed by a base class resolves, including nested in an argument likeRepo[Box[T]]by @antonrh in #329 - A scoped instance is created once when tasks share the scope by @antonrh in #313
Extensions
FastAPI: routes in a mounted application are wired, and a route added afterinstall()is validated as it is added.FastStream: every handler of a subscriber is injected, not only the first.pydantic_settings.install()can run twice withoverride=True, and names the model and the field on a clash. by @antonrh in #330- An extension whose package is missing says what to install by @antonrh in #323
- The
pytestplugin no longer breaks every test run whenanyiorenames a helper by @antonrh in #325
Documentation
- A getting started page, a reference generated from the docstrings, and every snippet checked to run by @antonrh in #322
- The pages say it plainly, and how they are written and checked is written down by @antonrh in #321
Dependencies
wrapt>=1.17.3.container.ref()builds on the pure-PythonObjectProxy, whichwrapt.wrappersexposes from that version, because the C one ignores the__wrapped__override that makes a reference lazy. by @antonrh in #314
What's changed
- Run the tests on the Python the matrix names by @antonrh in #311
- Fix scope entry unwind by @antonrh in #312
- Create a scoped instance once when tasks share the scope by @antonrh in #313
- Require the wrapt that has the proxy Ref needs by @antonrh in #314
- Refuse to close a context that holds async resources by @antonrh in #315
- Close the resource when an instance is released by @antonrh in #316
- Docs fix broken snippets by @antonrh in #318
- Say it plainly in the extension pages by @antonrh in #319
- Write down how the documentation is written and checked by @antonrh in #321
- Correct the docs, and give them a getting started and a reference by @antonrh in #322
- Say what to install when an extension's package is missing by @antonrh in #323
- Add release job and skill by @antonrh in #324
- Keep a renamed anyio helper from breaking every test run by @antonrh in #325
- Open the testing page with what a test does by @antonrh in #326
- Forget an instance even when its finaliser raises by @antonrh in #327
- Forget an instance whose resource the context closed by @antonrh in #328
- Resolve generic parameters that a base class renamed by @antonrh in #329
- Fix injection in mounted apps, late routes and stacked subscribers by @antonrh in #330
- Keep the base module providers on a subclass by @antonrh in #331
- Release 0.80.0 by @antonrh in #332
Full Changelog: 0.79.1...0.80.0
0.79.1
0.79.0
Global container
Sometimes the container is out of reach. A module the container imports cannot import it back, so container.ref() is not available there.
global_ref() references a dependency without a container:
# app/mail.py
from anydi import global_ref
mailer = global_ref(Mailer)
def send_welcome(email: str) -> None:
mailer.send(email, "Welcome!")Create the container with create_global_container():
# app/container.py
container = create_global_container()
@container.provider(scope="singleton")
def mailer() -> Mailer:
return SmtpMailer()The reference binds on first access, so import order does not matter. Overrides work as usual.
One container per process. create_global_container() raises if one is already set. Use reset_global_container() to replace it.
In tests the container fixture becomes the global container, so references resolve against it.
Use container.ref() where the container is reachable.
See Global Container and the example application.
What's Changed
Full Changelog: 0.78.0...0.79.0
0.78.0
Faster resolution
CodSpeed reports a 19.86% overall improvement, with all 15 benchmarks faster: transient scopes by ~38%, request scopes by ~15%, singletons by ~13%.
Behaviour is unchanged. The override check no longer runs on every resolution, cached singletons skip the compiled resolver, generated resolvers lost the checks that are known at compile time, and scoped contexts are a class instead of nested generators.
Isolated scope re-entry
Entering an active scope reuses its context. Pass isolated=True to open a fresh one that is restored on exit:
async def handle(item: Item) -> None:
async with container.ascoped_context("db", isolated=True):
session = await container.aresolve(Session)
await session.execute(insert_result(item))Without it, concurrent tasks share the parent session and can commit its transaction.
See Re-entering an active scope.
Fixes
- A scope is now left correctly when its body raises. The context variable was not reset, so the next request in the same task reused the failed request's context.
- In test mode, an instance resolves the scoped dependencies of the context it was created in, not of the context active when the attribute is read.
unregister()drops the compiled resolvers, so resolving an unregistered dependency raisesLookupErrorinstead of building it from the removed provider. Unregistering by alias releases the instance too.sniffiois no longer a dependency. TheTyperextension uses it when available and falls back toasynciodetection.
What's Changed
- Faster dependency resolution by @antonrh in #305
- Add isolated scope re-entry by @antonrh in #307
- Make sniffio optional in the typer extension by @antonrh in #308
Full Changelog: 0.77.0...0.78.0
0.77.0
Lazy references
container.ref() returns a lazy reference to a dependency. It behaves like the dependency itself and resolves on first attribute access, so it can live at module level:
db = container.ref(Database)
def handler() -> None:
db.execute("delete * from users")The reference asks the container for the current instance every time, so an override applies to code that already holds it:
with container.test_mode(), container.override(Database, db_mock):
handler()Singletons are cached inside the reference. The cache is dropped whenever the container state changes.
Transient and asynchronous dependencies are rejected up front. A reference needs an instance to refer to, and it resolves synchronously.
See References.
What's Changed
Full Changelog: 0.76.0...0.77.0