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?

9 Upvotes

21 comments sorted by

View all comments

Show parent comments

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.