Skip to content

Releases: python-anydi/anydi

0.82.0

Choose a tag to compare

@antonrh antonrh released this 30 Oct 07:33
2c5592c

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

Choose a tag to compare

@antonrh antonrh released this 04 Oct 10:09
e825ecb

What's Changed

  • Put the pytest plugin's container in test mode from the start by @antonrh in #333

Full Changelog: 0.80.0...0.81.0

0.80.0

Choose a tag to compare

@antonrh antonrh released this 31 Aug 08:00
2172c0c

Breaking

  • Only the class a decorator was applied to is provided. is_provided read __provided__ with hasattr, and a subclass inherits it, so container.scan() registered classes nobody marked, an inherited alias registered the same interface twice, and an inherited from_context claimed a registration nobody made. A subclass that used to resolve by inheriting its base's scope now raises LookupError naming 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 #316
  • container.release(T) drops the instance even when the finaliser raises, and container.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 with while 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 TypeVar renamed by a base class resolves, including nested in an argument like Repo[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 after install() is validated as it is added. FastStream: every handler of a subscriber is injected, not only the first. pydantic_settings.install() can run twice with override=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 pytest plugin no longer breaks every test run when anyio renames 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-Python ObjectProxy, which wrapt.wrappers exposes 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

Choose a tag to compare

@antonrh antonrh released this 28 Jul 11:03

What's Changed

Full Changelog: 0.79.0...0.79.1

0.79.0

Choose a tag to compare

@antonrh antonrh released this 28 Jul 10:46

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

  • Add global container and global references by @antonrh in #309

Full Changelog: 0.78.0...0.79.0

0.78.0

Choose a tag to compare

@antonrh antonrh released this 28 Jul 08:28

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 raises LookupError instead of building it from the removed provider. Unregistering by alias releases the instance too.
  • sniffio is no longer a dependency. The Typer extension uses it when available and falls back to asyncio detection.

What's Changed

Full Changelog: 0.77.0...0.78.0

0.77.0

Choose a tag to compare

@antonrh antonrh released this 28 Jul 07:46

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

  • Add lazy dependency references via container.ref() by @antonrh in #304

Full Changelog: 0.76.0...0.77.0

0.76.0

Choose a tag to compare

@antonrh antonrh released this 27 Jul 08:13

What's Changed

Full Changelog: 0.75.0...0.76.0

0.75.0

Choose a tag to compare

@antonrh antonrh released this 29 Jun 07:57

What's Changed

Full Changelog: 0.74.2...0.75.0

0.74.2

Choose a tag to compare

@antonrh antonrh released this 23 Feb 06:59
c7d3691

What's Changed

  • Fix child scope providers unable to access parent scope from_context … by @antonrh in #299

Full Changelog: 0.74.1...0.74.2