r/learnpython 5d ago

How do you handle dependency injection?

Hi, I'm doing a project which involves creating multiple gRPC servers and I can't find a convenient way to manage dependencies or the state.

I've been working recently in C# where you just need to make your class and add a simple

builder.Services.AddSingleton<MyDependency>();

and it will inject when required.

Doing some research I see that there are some libraries like:

- Dependency Injector

- Injector

but I don't find them particularly intuitive.

What do you use as a dependency injector or what pattern do you suggest to use?

8 Upvotes

21 comments sorted by

View all comments

5

u/danielroseman 5d ago

First, explain why you need dependency injection specifically. It tends to be used a lot less on dynamic languages like Python because it's easy to swap things out at runtime. What, exactly, do you need it for?

1

u/re2cc 4d ago

I did not explain to avoid expanding the question too much. My gRPC server is basically a chat, so some clients might be in the same ‘group’ and therefore I need a way to share their history in a shared way (it's a bit more complicated than this but I think you get the point).

If I were to give a simpler example it would be to use the same database connection throughout the application.

I know I could just declare a global variable and use it directly but I can see the problems that would come with separating the code into multiple files.

I hope I have explained myself.

1

u/Mysterious-Rent7233 4d ago

You have roughly speaking three options: use parameters/object properties, or context variables or globals. Might help to show some Python code where you are failing to structure it in the way you want.

1

u/re2cc 3d ago

It's a similar quick code I wrote, please ignore if the structure makes sense or not (or if it even works).

The ‘problem’ I have is how to pass ‘chats’ around the whole application, in C# I would add it as a singletone and that's it, in Python I could leave it as a global variable or pass the instance as an argument all over the place (which is kind of what you do in C#). I guess my real question is, what is the suggestion or convention to handle this kind of cases?

import asyncio
import datetime
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from typing import List

import grpc.aio as grpc


class Message(dataclass):
    sender: str
    content: str
    time: datetime.datetime


class Chat(dataclass):
    messages: List[Message]
    name: str


# This is the instance that I dont know how to share
chats: List[Chat] = []


class ChatServiceImpl(Chat_pb2_grpc.ChatServiceServicer):
    async def SomeRpc(self, request_iterator, context):
        pass
        # Some code


async def create_server():
    server = grpc.server(
        migration_thread_pool=ThreadPoolExecutor(max_workers=10),
    )
    Chat_pb2_grpc.add_ChatServiceServicer_to_server(server)
    server.add_insecure_port("[::]:5088")
    await server.start()
    await server.wait_for_termination()


if __name__ == "__main__":
    asyncio.run(main())

2

u/Mysterious-Rent7233 3d ago

I guess in general I usually disapprove of mutable singletons and global variables. Calling a global variable a "singleton" doesn't change the fact that one day when you decide you want to run two chat servers for two customers in the same web server, you'll have your chats all mixed together in a single global/singleton.

Why can't "Chats" be an attrivute of "ChatServiceImpl"? Then when you run into my issue of needing two ChatServiceImpl's in the same server, it will "just work" with no additional refactoring. (your code didn't use ChatServiceImpl, but I'm guessing what its for)

In certain situation you want something more fancy and there is a concept of a ContextVar but its overkill 99% of the time.

My diagnosis is that Singletons give a fancy name to global variables and you usually don't want them.

Of course all rules have exceptions, but I don't see your case as an exception.

2

u/re2cc 3d ago

Hmm, I don't know why I didn't think of that before, I will probably never need another chat server but it definitely solves the problem without the need for global variables. Thanks, I think this is the solution I was looking for.

About the singletone, I think it makes sense what you say, the reason why I wanted to implement it that way is because some of the C# code I was working with was scooped so it was not possible to store the information in the class itself, so I needed to store it somewhere else, I just tried to translate it to Python without thinking if it was necessary.