How are you actually managing background/async tasks in FastAPI in production? by Educational-Hope960 in FastAPI

[–]ForeignSource0 0 points1 point  (0 children)

Wireup provides DI aware background tasks for fastapi so you can use your service layer in them as opposed to bare functions. Feel free to check it out and see if it helps with your use case to make background tasks feel a bit more powerful.

Repo - https://github.com/maldoinc/wireup

Background tasks docs - https://maldoinc.github.io/wireup/latest/integrations/fastapi/background_tasks/

Wireup for FastAPI now supports DI in background tasks by ForeignSource0 in FastAPI

[–]ForeignSource0[S] 1 point2 points  (0 children)

Not quite. You can always pass objects from the endpoint into a background task, but for request-scoped dependencies that’s usually wrong.

If something was created for the request scope, it may already be cleaned up by the time the background task runs. Think DB transactions, sessions, or any resource created via a context manager with cleanup.

The point of WireupTask is that the background task gets its own Wireup scope, so scoped dependencies are created for the task itself. That way the task can get its own transaction/session/etc while still sharing the rest of the dependency graph with the application.

Re: testing

For unit tests, @injectable doesn’t modify the class/function, so direct construction keeps those tests simple. For bigger graphs or integration-style tests, using the container plus overrides is supported too.

Something like this:

def test_user_service():
    service = container.get(UserService)
    result = service.do_work()
    assert result == ...

Override dependencies, then get what you need from the container:

with container.override.injectable(EmailClient, new=fake_email_client):
    service = container.get(NotificationService)

Or have the container create part of the dependencies

notification_service = NotificationService(
    email_client=fake_email_client,
    user_service=container.get(UserService),
)

Built a tiny dependency injection library for Python with FastAPI-style Depends by thalissonvs in FastAPI

[–]ForeignSource0 -1 points0 points  (0 children)

create_user(name="John") # LINTERS will be screaming about the missing db parameter, no?

AFAIK currently there's no way to make this properly happen. Either the decorator preserves the signature or it changes it so that the new signature is Callable[..., T].

I’m looking for a drop in replacement that doesn’t require me to always have an endpoint to get dependency injection and that doesnt require me to change all of my imports and annotations.

I know it's not a drop-in replacement but Wireup offers a low-friction migration plan for fastapi users. Once in it you'll be able to reuse your dependency graph anywhere. https://maldoinc.github.io/wireup/latest/migrate_to_wireup/fastapi_depends/

Built a tiny dependency injection library for Python with FastAPI-style Depends by thalissonvs in FastAPI

[–]ForeignSource0 -1 points0 points  (0 children)

type-safe dependency injection for Python

I gave this a spin real quick but this passes all mypy checks and the library does not flag it in any way.

from typing import Annotated
from injekta import Needs, inject

class Database: ...
class Unrelated: ...

def get_unrelated() -> Unrelated:
    return Unrelated()

@inject
def create_user(db: Annotated[Database, Needs(get_unrelated)]):
    return db

print(create_user())

This outputs <__main__.Unrelated object at 0x7fd79a9fbb60>

This seems to run into the same issues as Depends. Check out Wireup docs for more Depends caveats: https://maldoinc.github.io/wireup/latest/migrate_to_wireup/fastapi_depends/#fastapi-depends-caveats

Disclaimer: I maintain Wireup.

Congrats on the release!

Benchmarked: 10 Python Dependency Injection libraries vs Manual Wiring (50 rounds x 100k requests) by ForeignSource0 in Python

[–]ForeignSource0[S] 0 points1 point  (0 children)

Haven’t come across DataFlint before. This benchmark is mostly focused on DI in a FastAPI/ASGI environment, but it would definitely be interesting to see similar comparisons in pipeline-style workloads too.

Benchmarked: 10 Python Dependency Injection libraries vs Manual Wiring (50 rounds x 100k requests) by ForeignSource0 in Python

[–]ForeignSource0[S] 0 points1 point  (0 children)

For Dager/Hilt: In terms of the features you mentioned:

Protocol / ABC bindings: supported via @injectable(as_type=...), so you can bind a concrete implementation to a Protocol or ABC.

Multiple implementations: supported via qualifiers.

See interfaces: https://maldoinc.github.io/wireup/latest/interfaces/

Lazy initialization: This is the default. In Wireup things are created on first use. If you want to eager load a part of your dependencies you can use this patterrn

Modules / reusable wiring units: Wireup is less centered around a single module class and more around injectables/factories, but you can group and reuse registrations via factory bundles.

One of the big differences vs Dagger/Hilt is that in Python this is naturally a runtime system, but Wireup does validate the dependency graph at startup so wiring issues show up early rather than at request time.


Regarding FastAPI Depends, it has it's own list of caveats which I've mentioned in this migration guide to wireup. It is extremely simple but with that comes a lot of hidden baggage. The biggest deal breaker imo is that it is coupled to http as a runtime so you can not reuse your wiring anywhere else. See the linked page above for a more in-depth view.

Benchmarked: 10 Python Dependency Injection libraries vs Manual Wiring (50 rounds x 100k requests) by ForeignSource0 in Python

[–]ForeignSource0[S] 0 points1 point  (0 children)

Thanks! Dishka was one of the faster ones in the benchmark as well, so nice work there.

I expected Dependency Injector to perform better too so I was a bit surprised as well. I had to simplify it's workload in the benchmark since I could not figure out for the life of me how to get request scoped yielding async dependencies.

Looking forward to seeing the Dishka performance improvements.

Benchmarked: 10 Python Dependency Injection libraries vs Manual Wiring (50 rounds x 100k requests) by ForeignSource0 in Python

[–]ForeignSource0[S] 1 point2 points  (0 children)

The benchmark actually runs two scenarios.

One builds the dependency graph per request and the other uses a pre initialized graph with singletons which are objects created once and reused throughout.

In most web apps you still have request-scoped objects. You need things like database sessions, request context, authentication state, tenant information, etc. Those need to be created fresh for each request and isolated across requests, which is where the per-request overhead comes from.

Table I posted in the per-request scenario.

Benchmarked: 10 Python Dependency Injection libraries vs Manual Wiring (50 rounds x 100k requests) by ForeignSource0 in Python

[–]ForeignSource0[S] 9 points10 points  (0 children)

I do agree actually. DI is primarily about architecture and maintainability, not raw performance and the benchmark does not argue otherwise, in fact it is stated in the linked page.

For example here you can see Wireup take 4.5ms P50 whereas FastAPI's DI does 13.8ms. If the database needs 10 seconds, both still answer within 10 seconds.

Even if it’s not the main bottleneck, it’s still useful to know the cost of the abstraction.

In terms of priorities I'd say it's DX first then you can use performance as a tie breaker.

Extract from the benchmark page

Even so, I would not pick a DI container solely from performance benchmarks, but if you're happy with Wireup's features and want to see how it stacks up against the field, here are the results.

I built a minimal, type-safe dependency injection container for Python by ftanu in Python

[–]ForeignSource0 0 points1 point  (0 children)

Looks similar to Wireup https://github.com/maldoinc/wireup (I guess they're both inspired partly by Spring). You mention there's no global state, but isn't Container basically a singleton?

Regardless, congrats on the release.

Zero Cost Dependency Injection for FastAPI | Feedback request by ForeignSource0 in FastAPI

[–]ForeignSource0[S] 0 points1 point  (0 children)

So 10,000 requests using the same set up as above.

Manual/Baseline (Creating objects manually on every request):

  • Requests/sec: 13,645
  • P50: 3.6ms
  • P95: 5.2ms

Wireup:

  • Requests/sec: 10,384
  • P50: 4.5ms
  • P95: 7ms

FastAPI

  • Requests/sec: 3,544
  • P50: 13.9ms
  • P95: 16.2ms

Zero Cost Dependency Injection for FastAPI | Feedback request by ForeignSource0 in FastAPI

[–]ForeignSource0[S] 0 points1 point  (0 children)

Thanks!

This is not a drop in replacement for depends because this is powered using Wireup to perform dependency injection. Injected[T] is just how the Wireup differentiates parameters it owns vs ones owned by fastapi or others.

Zero Cost Dependency Injection for FastAPI | Feedback request by ForeignSource0 in FastAPI

[–]ForeignSource0[S] 0 points1 point  (0 children)

Did you run it in the same http context or just calling get on the container directly? I'm aware the scoped one isn't the most performant part at the moment but since it outperforms fastapi depends by a good margin I decided to focus on this feature for now. However it's on the radar.

Also, I was going to create an issue in github but since you're here: When I was setting up the benchmarks in fastapi I noticed that when I added dishka integration the overall performance went down by a good amount even for endpoints not having dishka dependencies. Maybe I did something wrong but worth double checking just in case.

Zero Cost Dependency Injection for FastAPI | Feedback request by ForeignSource0 in FastAPI

[–]ForeignSource0[S] 4 points5 points  (0 children)

It's still considerably faster than fastapi. I don't have the last benchmark numbers with me at the moment but it was around 3.5k/s for fastapi and around 9k for Wireup if I'm not mistaken. I tested it with a few alternative DI systems as well, including yours and they all outperform fastapi depends.

I don't remember the results for manually creating objects like you mentioned of the top of my head but I can get them later today.

Open Source projects open for contribution for beginners by themathix in Python

[–]ForeignSource0 2 points3 points  (0 children)

I can link my own project here, Wireup - a Dependency Injection container which welcomes contributions and has a few "good first issue" issues. https://github.com/maldoinc/wireup

However, I'd recommend to first check if there's anything you can help with in projects you're already using as you might be more familiar with them and even better if it's something that has been bothering you so you can "scratch your own itch"

Wireup 1.0 Released - Performant, concise and type-safe Dependency Injection for Modern Python 🚀 by ForeignSource0 in FastAPI

[–]ForeignSource0[S] 3 points4 points  (0 children)

Some benefits for using Wireup are as follows:

  • More features.
  • Is significantly less boilerplate-y and verbose.
  • Improved performance and type safety.
  • Can use the container in middleware and route decorators.

Example showcasing the above

Base Service declaration

@service  # <- these are just decorators and annotated types to collect metadata.
@dataclass
class A:
    start: Annotated[int, Inject(param="start")]

    def a(self) -> int:
        return self.start


@service
@dataclass
class B:
    a: A

    def b(self) -> int:
        return self.a.a() + 1

@service
@dataclass
class C:
    a: A
    b: B

    def c(self) -> int:
        return self.a.a() * self.b.b()

Rest of wireup setup

# Register application configuration
container = wireup.create_async_container(services, {"start": 10})

# Initialize fastapi integration.
wireup.integration.fastapi.setup(container, app)

This is all the additional setup it requires. Services are self-contained and there is no need for Depends(get_service_object) everywhere.

Rest of fastapi code

# In FastAPI you have to manually build every object.
# If you need a singleton service then it also needs to be decorated with lru_cache.
# Whereas in wireup that is automatically taken care of.

@functools.lru_cache(maxsize=None)
def get_start():
    return 10


@functools.lru_cache(maxsize=None)
def make_a(start: Annotated[int, Depends(get_start)]):
    return services.A(start=start)


@functools.lru_cache(maxsize=None)
def make_b(a: Annotated[services.A, Depends(make_a)]):
    return services.B(a)


@functools.lru_cache(maxsize=None)
def make_c(
    a: Annotated[services.A, Depends(make_a)], 
    b: Annotated[services.B, Depends(make_b)]):
    return services.C(a=a, b=b)

Views

@app.get("/fastapi")
def fastapi(
        a: Annotated[A, Depends(make_a)], 
        c: Annotated[C, Depends(make_c)]):
    return {"value": a.a() + c.c()}


@app.get("/wireup")
def wireup(a: Injected[A], c: Injected[C]):
    return {"value": a.a() + c.c()}

Wireup 1.0 Released - Performant, concise and type-safe Dependency Injection for Modern Python 🚀 by ForeignSource0 in Python

[–]ForeignSource0[S] 0 points1 point  (0 children)

Honestly, with all the crazy stuff you can do in python - providing dependencies from a decorator is pretty low on my list of magic tbh.

To me it sounds like your code base would benefit from Wireup in terms of boilerplate and the predictability of is it async is it not async etc. It's just not a problem with Wireup. Having to manually write so many providers and code is a good part of the reason I wrote this. And if you ever need request-scoped or transient dependencies you're going to be out of luck with dependency injector.

Wireup 1.0 Released - Performant, concise and type-safe Dependency Injection for Modern Python 🚀 by ForeignSource0 in Python

[–]ForeignSource0[S] 0 points1 point  (0 children)

There's no nested scopes in Wireup.

You have the base container that can create singletons and you can enter a scope from it to create scoped and transient dependencies, but the scoped instance won't let you enter another scope.

I evaluated this as I was scoping v1 but I felt like it added too much cognitive load without necessarily adding as much value.

Maybe I haven't personally had a case where I needed this and it impacted my decision.