Skip to content

What's the difference with python-dependency-injector? #207

Closed

Description

@pavalso

I do not intend to dismiss your work; however, could you clarify how this package differs from python-dependency-injector? After reviewing the documentation for your project, it appears to share some similarities with the package I mentioned. I would appreciate an explanation of the key distinctions between the two.

To reiterate, this question is asked in good faith, and I recognize that your project may offer unique features or design choices.

Activity

  1. xelandernt commented on Jan 18, 2026

    @xelandernt
    Collaborator

    I will attempt to make an unbiased comparison, of course I encourage to take this comparison
    with a grain of salt, since as one of the maintainers of this repository I will naturally be biased.

    The two packages have some similarity because I believe initially the purpose of this package was to be a simplified replacement for dependency-injector when that was left unmaintained for ~2 years.

    Relevant versions:

    depedency-injector==4.48.3
    that-depends==3.9.1

    Overview

    Both packages have a similar API with a global Container and then marking DI via @inject and Provide.

    Under the hood implementation is very different for both packages (if that's relevant for you), but they support
    many similar features, including async and sync injection, a multitude of providers, overriding, etc.

    The major difference is that dependency-injector supports and requires wiring to work correctly, while that-depends
    does not support this feature. In exchange, that-depends provides comprehensive context/state and scope management for
    select providers.

    Major differences

    Feature dependency-injector that-depends
    Wiring Support? ✅ ❌
    Wiring Required? ✅ ❌
    Supports Context Management + Scopes? ❌ ✅

    Minor differences

    Feature dependency-injector that-depends
    Configuration Providers ✅ ❌
    Pure python? ❌ (python + cython) ✅
    Debugging More involved (wiring + Cython) Simple
    Supports injection by type? ❌ ✅

    Similarities

    Feature dependency-injector that-depends
    Global Container ✅ ✅
    Supports DI-anywhere? ✅ ✅
    Supports string-injection ✅ ✅ but is slightly limited
    Supports overriding ✅ ✅

    Performance

    If performance of the DI matters to you, then dependency-injector is faster for sync injections.

    My system specs:

    Mac Mini
    Apple M4
    16 GB RAM
    macOS Tahoe 26.1
    Python 3.13.5
    

    Benchmarks

    dependency-injector:

    import asyncio
    import time
    from dataclasses import dataclass
    
    from dependency_injector import containers, providers
    from dependency_injector.wiring import Provide, inject
    
    
    @dataclass(frozen=True)
    class Repo:
        dsn: str
    
    
    @dataclass(frozen=True)
    class Service:
        repo: Repo
    
        def query_sync(self) -> str:
            return f"sync:{self.repo.dsn}"
    
        async def query_async(self) -> str:
            await asyncio.sleep(0)
            return f"async:{self.repo.dsn}"
    
    
    class Container(containers.DeclarativeContainer):
        wiring_config = containers.WiringConfiguration(modules=[__name__])
    
        repo = providers.Singleton(Repo, dsn="sqlite:///example.db")
        service = providers.Factory(Service, repo=repo)
    
    
    @inject
    def handler_sync(service: Service = Provide[Container.service]) -> str:
        return service.query_sync()
    
    
    @inject
    async def handler_async(service: Service = Provide[Container.service]) -> str:
        return await service.query_async()
    
    
    def bench_sync(label: str, fn, *, iters: int) -> None:
        t0 = time.perf_counter()
        out = None
        for _ in range(iters):
            out = fn()
        dt = time.perf_counter() - t0
        print(f"{label:28s} {dt:.6f}s  ({iters / dt:,.0f} ops/s)  last={out!r}")
    
    
    async def bench_async(label: str, fn, *, iters: int) -> None:
        t0 = time.perf_counter()
        out = None
        for _ in range(iters):
            out = await fn()
        dt = time.perf_counter() - t0
        print(f"{label:28s} {dt:.6f}s  ({iters / dt:,.0f} ops/s)  last={out!r}")
    
    
    async def main() -> None:
        iters_sync = 100_000
        iters_async = 100_000
    
        t0 = time.perf_counter()
        container = Container()
    
        container.wire(modules=[__name__])
        build_dt = time.perf_counter() - t0
        print(f"container_build+wire          {build_dt:.6f}s")
    
        bench_sync("handler_sync (@inject)", handler_sync, iters=iters_sync)
    
        await bench_async("handler_async (@inject)", handler_async, iters=iters_async)
    
        container.unwire()
    
    
    if __name__ == "__main__":
        asyncio.run(main())

    that-depends:

    import asyncio
    import time
    from dataclasses import dataclass
    
    
    from that_depends import BaseContainer, providers, Provide, inject
    
    
    @dataclass(frozen=True)
    class Repo:
        dsn: str
    
    
    @dataclass(frozen=True)
    class Service:
        repo: Repo
    
        def query_sync(self) -> str:
            return f"sync:{self.repo.dsn}"
    
        async def query_async(self) -> str:
            await asyncio.sleep(0)
            return f"async:{self.repo.dsn}"
    
    
    class Container(BaseContainer):
        repo = providers.Singleton(Repo, dsn="sqlite:///example.db")
        service = providers.Factory(Service, repo=repo)
    
    
    @inject
    def handler_sync(service: Service = Provide[Container.service]) -> str:
        return service.query_sync()
    
    
    @inject
    async def handler_async(service: Service = Provide[Container.service]) -> str:
        return await service.query_async()
    
    
    def bench_sync(label: str, fn, *, iters: int) -> None:
        t0 = time.perf_counter()
        out = None
        for _ in range(iters):
            out = fn()
        dt = time.perf_counter() - t0
        print(f"{label:28s} {dt:.6f}s  ({iters / dt:,.0f} ops/s)  last={out!r}")
    
    
    async def bench_async(label: str, fn, *, iters: int) -> None:
        t0 = time.perf_counter()
        out = None
        for _ in range(iters):
            out = await fn()
        dt = time.perf_counter() - t0
        print(f"{label:28s} {dt:.6f}s  ({iters / dt:,.0f} ops/s)  last={out!r}")
    
    
    async def main() -> None:
        iters_sync = 300_000
        iters_async = 100_000
    
        t0 = time.perf_counter()
        build_dt = time.perf_counter() - t0
        print(f"container_build+wire          {build_dt:.6f}s")
    
        bench_sync("handler_sync (@inject)", handler_sync, iters=iters_sync)
    
        await bench_async("handler_async (@inject)", handler_async, iters=iters_async)
    
    
    if __name__ == "__main__":
        asyncio.run(main())

    Results

    dependency-injector that-depends
    container_build + wire 0.000859s 0
    sync_injection 0.068563s (1,458,508 ops/s) 0.711404s (421,701 ops/s)
    async_injection 1.535209s (65,138 ops/s) 1.676443s (59,650 ops/s)
    • Build + wire is negligible
    • sync injection: dependency-injector is ~3x faster
    • async injection: no relevant difference

    Misc

    Other, perhaps relevant comparisons.

    Information collected on the 18.01.2026

    dependency-injector that-depends
    Test Coverage 95% 100%
    Typing mypy checks only on tests/typing mypy checks whole project
    Currently open issues 202 5
    Stars 4.8k 240
    Contributors 46 15
    Documentation includes migration guides for major versions? ❌ ✅
  2. locked and limited conversation to collaborators on Jan 25, 2026
  3. converted this issue into a discussion #208 on Jan 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requestedv3Relevant to version 3.*

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions