kodemore / kink

Dependency injection container made for Python
MIT License
397 stars 25 forks source link

Automatically register base class(es) as aliases when using `@inject` #51

Closed takanuva15 closed 5 months ago

takanuva15 commented 8 months ago

Hi, thanks for making this awesome library (I found an article on Google recommending this library).

My question was: The documentation mentions for service aliasing that:

When you register a service with @inject decorator you can attach your own alias name

class IUserRepository(Protocol):
   ...

@inject(alias=IUserRepository)
class UserRepository:
    ...

I always explicitly define relationships between my "interfaces" and their implementations through base classes so that the IDE does static type-checking to verify that the subclass has implemented everything from the superclass. (It's also clear to others that the class is intended to implement all the methods of the interface). Simple example:

class IUserService(ABC):
    @abstractmethod
    def get_username(self, user_id: str) -> str:
        pass

@inject(alias=IUserService)
class UserService(IUserService):
    def get_username(self, user_id: str) -> str:
        return self.db.execute_query(blablabla)

The official Python documentation also shows a similar example with that recommendation:

Explicitly including a protocol as a base class is also a way of documenting that your class implements a particular protocol, and it forces mypy to verify that your class implementation is actually compatible with the protocol.

Since inheritance already implies by definition that UserService is an instance of IUserService, having to manually define that IUserService is an alias of UserService within the @inject annotation is redundant. (ie it's already clear to a Python developer that UserService is an instance of IUserService, so when I want to get all the implementations of IUserService, I should expect to see UserService in that list by definition).

(This is also the default behavior in other major di frameworks like SpringBoot in Java, which saves a lot of time).

Thus, I wanted to request that can we automatically register base class(es) as aliases when using @inject on a class?

(Searching online briefly, it seems we can use inspect.getmro(B) or cls.__bases__ to get the base classes to auto-register as aliases at runtime)

dkraczkowski commented 5 months ago

@takanuva15 We potentially could do that, but not everyone in python world is adhering to this convention. Additionally, when inspecting a class for their related class, how would you know which one is really meant to be used for aliasing without any kind of hint?

The other problem with this approach might be a namespace conflict. Imagine you have couple implementations of ISomething, which one noe should be the proper target?

I prefer to keep it explicit, I think this way it is simpler.