Skip to content

Releases: maldoinc/wireup

v2.8.0

03 Mar 00:01

Choose a tag to compare

What's Changed

  • Allow nested overrides #93 by @smokyabdulrahman in #108
  • as_type registration now validates compatibility at runtime and raises clearer errors for invalid or mismatched types.
  • Added wireup.integration.typer integration
  • Addedwireup.integration.fastmcp integration for FastMCP HTTP apps.
  • AddedWireupTask support in FastAPI and Starlette integrations for dependency injection in background tasks.
  • Addedwireup.integration.fastapi.inject for injecting into any function during request lifetime.
  • Addedwireup.integration.django.inject_app for app-level Django callables (commands, signals, checks, scripts).
  • Added Strawberry integration documentation.
  • New migration guides:
    • FastAPI Depends to Wireup
    • Dependency Injector to Wireup
  • Benchmark suite, generated benchmark data, and a new benchmarks documentation page.

Changed

  • You can now call setup in fastapi integration even before registering all the routes.
  • AIOHTTP integration now supports middleware_mode:
  • Django and FastAPI integration documentation was fully reorganized and expanded (setup, lifecycle patterns, testing, troubleshooting).

Fixed

  • Falsy handling for as_type checks was fixed.
  • FastAPI request-container/setup error messages improved, including clearer failure messaging when setup/request context is missing.

Full Changelog: v2.7.1...v2.8.0

v2.7.1

21 Feb 11:10

Choose a tag to compare

  • Fix overriding async scoped dependencies
  • Improve singleton lookup performance by using an instance factory once the object is created.

Full Changelog: v2.7.0...v2.7.1

v2.7.0

09 Feb 22:46

Choose a tag to compare

What's new

  • Added support for injecting into generator / async-generator functions
  • Added new opt-in concurrent_scoped_access parameter for locking scopes accessed concurrently on create_sync_container /
    create_async_container.
  • Function injection (inject_from_container and framework integrations) now generates specialized wrappers at
    decoration time, reducing per-call runtime overhead and skipping container lookups when possible.
  • Exit stack cleanup avoids runtime inspect.isasyncgen().

Bugfixes

  • Fixed a bug where an async container injecting into a def function would not inject async dependencies already
    cached in the container.
  • Qualifiers with falsy values (e.g. 0, "", False) are now handled correctly during injection.
  • Wireup now raises when encountering positional-only parameters in injectables or injected callables.

Full Changelog: v2.6.0...v2.7.0

v2.6.0

28 Jan 19:57

Choose a tag to compare

What's Changed

  • Enable dot notation in parameters by @smokyabdulrahman in #66
  • Fix overriding indirect dependencies by @maldoinc in #107
  • Use teardown_request in flask to handle resource cleanup

New Contributors

Full Changelog: v2.4.0...v2.6.0

v2.5.0

25 Jan 20:55

Choose a tag to compare

  • Wireup is now fully thread-safe. It uses a double-checked locking mechanism that guarantee safe instantiation and resolution in high-concurrency environments (threaded or async).
  • This also allows Wireup to be used in No-GIL Python.

Full Changelog: v2.4.0...v2.5.0

v2.4.0

22 Jan 22:29

Choose a tag to compare

What's Changed

1. @service -> @injectable

The @service decorator will be renamed to @injectable. The name service carries architectural baggage and is misleading for many injected values. Even in Wireup's own docs the usage of @service is confusing.

AuthenticatedUsername = NewType("AuthenticatedUsername", str)

-@service(lifetime="scoped")
+@injectable(lifetime="scoped")
def authenticated_username_factory(auth: SomeAuthService) -> AuthenticatedUsername:
    return AuthenticatedUsername(...)

Many injected values such as AuthenticatedUsername from the example, are not really "services".

The name injectable is strictly about capability: A thing that can be injected. It is agnostic to the produced object's role in the system architecture making it overall less confusing.

Note: This is a pure rename and the behavior is unchanged.

2. Inject(param=...) -> Inject(config=)

Same as "service", injecting "parameters" is somewhat ambiguous for users. The name itself is also very overloaded especially in the context of a web application: You have query parameters, path parameters, function parameters in the signature and now Wireup parameters.

The feature remains to enable co-located definitions, but it is renamed to config to make it explicit that values come from Wireup’s configuration rather than some runtime or function parameters.

-container = wireup.create_{a}sync_container(params={"api_key": "secret"})
+container = wireup.create_{a}sync_container(config={"api_key": "secret"})

@wireup.inject_from_container(container)
-def main(api_key: Annotated[str, Inject(param="api_key")]) -> None:
+def main(api_key: Annotated[str, Inject(config="api_key")]) -> None:
    pass

Similarly container.params is deprecated in favor of container.config.

Note: Same as above, this is a pure rename and the behavior is unchanged.

3. Better support for abstractions

Currently Wireup requires tagging classes to be used as "interfaces" with @abstract. The linking between the two is indirect, wireup scans bases to see if any of them was marked with @abstract to do the wiring. This also has the side-effect that the container knows both about the implementation and the abstraction.

This is now deprecated in favor of explicit binding via as_type.

  • You can now bind classes or protocols without owning/decorating them.
  • Unlike @abstract, as_type replaces the registration rather than creating an alias. The concrete type will no longer be visible to the container unless explicitly exposed (see example below).
  • If the factory where this is used returns an optional type FooImpl | None, as_type=Foo automatically registers the key as Foo | None to ensure runtime safety.
-@abstract
class Cache(abc.ABC):
    def get(self, key): ...
    def set(self, key, value): ...

-@service
+@injectable(as_type=Cache)  # as_type can be any regular class, abc or protocol.
class InMemoryCache(Cache):
    ...

For factories, you can control the registration by setting the return type or using as_type. You may choose to keep the concrete type visible (e.g. for tests) while exposing only the abstraction to the container.

@injectable(as_type=Cache, qualifier="redis")
def make_redis_cache(...) -> Redis:
    return Redis(...)

With functions since you can control the return type (unlike @injectable on a class), instead of using as_type=Cache, you can have the function return Cache for the same effect.

-@injectable(as_type=Cache, qualifier="redis")
+@injectable(qualifier="redis")
def make_redis_cache(...) -> Cache:
    return Redis(...)

If you need to keep both the implementation and the abstraction visible to the container you can explicitly expose both by writing a small adapter.

@injectable
class FooImpl: ...

@injectable
def make_foo_protocol(impl: FooImpl) -> FooProtocol:
    return impl

Both FooImpl and FooProtocol are visible to the container and will reuse the same instance.

Improved container creation signature

Given the "service" rename, the signatures must be updated as well. The new api exposes a new injectables parameter where you can place either injectables themselves or modules for wireup to scan for injectables rather than scattering them in two parameters (services and service_modules).

-container = wireup.create_{a}sync_container(service_modules=[services, repositories], services=[AuthService])
+container = wireup.create_{a}sync_container(injectables=[services, repositories, AuthService])

4. Improved handling of optional values

Wireup supports optional values as first-class citizens, with one caveat:

@injectable
def make_cache(
    redis_url: Annotated[str | None, Inject(config="redis_url")],
) -> Redis | None:
    return Redis.from_url(redis_url) if redis_url else None

This dependency can be injected into function signatures as Redis | None.
However, when using the container as a service locator, users previously had to write container.get(Redis) This meant the type checker was unaware that the value could be absent.

This is now fixed: container.get(Redis | None) is supported.

Calling container.get(T) for a dependency registered as T | None will continue to work, but will emit a deprecation warning.

PRs

  • add support for unknown dependencies that provide a default value by @iverberk in #99
  • Wireup vNext API Modernization by @maldoinc in #100

New Contributors

Full Changelog: v2.3.0...v2.4.0

v2.3.0

30 Dec 21:28

Choose a tag to compare

What's Changed

Full Changelog: v2.2.2...v2.3.0

v2.2.2

27 Dec 15:00

Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v2.2.1...v2.2.2

v2.2.1

13 Dec 13:02

Choose a tag to compare

  • Fixed a bug with overriding async dependencies
  • Fixed a bug with overriding when using class-based handlers in FastAPI/aiohttp.

Full Changelog: v2.2.0...v2.2.1

v2.2.0

11 Dec 22:24

Choose a tag to compare

What's Changed

  • New compiled container architecture. Wireup will compute ahead of time all factories and generate optimized code for object creation. This results in a 125% increased performance compared to v2.1.0 and 546% compared to v2.0.0.
  • fix asyncio.iscoroutinefunction DeprecationWarning by @MuriloScarpaSitonio in #90
  • fix wireup.ioc.util.get_globals by @MuriloScarpaSitonio in #92

Full Changelog: v2.1.0...v2.2.0