Finistere / antidote

Dependency injection for Python
MIT License
90 stars 9 forks source link

Metaclass issues #31

Closed ghost closed 3 years ago

ghost commented 3 years ago

Hello,

I don't understand your decision to inherite from Service instead of using implements/register decorators of previous versions. Because now if you want to declare an ABC Class for your Service definition, you can't use it alongside Service, as both have metaclasses.

class MyServiceInterface(abc.ABC):
    @abc.abstractmethod
    def do_something(self):
        pass

class MyserviceImpl(MyServiceInterface, antidote.Service):
    def do_something(self):
        print('Just do it')

This result in metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases.

Thanks for your help,

Finistere commented 3 years ago

Hello!

Currently, you can solve it in two ways:

  1. Use the @service decorator designed explicitly for that reason when inheriting Service is cumbersome. It's very similar to the old @register, expect it won't wire the class for you. You have to manually use @inject or @wire.
import abc
from antidote import service

class MyServiceInterface(abc.ABC):
    @abc.abstractmethod
    def do_something(self):
        pass

@service
class MyserviceImpl(MyServiceInterface):
    def do_something(self):
        print('Just do it')
  1. You can do the usual trick to solve metaclass conflicts. A bit hackier though, as it's not part of the public API of Antidote:
import abc

from antidote import Service
from antidote._service import ServiceMeta

class MyServiceInterface(abc.ABC):
    @abc.abstractmethod
    def do_something(self):
        pass

class ABCServiceMeta(abc.ABCMeta, ServiceMeta):
    pass

class MyserviceImpl(Service, metaclass=ABCServiceMeta):
    def do_something(self):
        print('Just do it')

Now, why did I change the API?

Regarding the old @implements decorator. It has been replaced by the more versatile and more explicit @implementation. With @implements it wasn't obvious from where the implementation would be coming from and if Python had loaded the file before.

API ref: https://antidote.readthedocs.io/en/latest/reference.html#module-antidote.implementation Interface example: https://antidote.readthedocs.io/en/latest/recipes.html#use-interfaces

I'll add

As creating the ABCServiceMeta isn't obvious, I'm considering creating a ABCService that would do this for you. It'd be just easier and serve as documentation on how to handle this with metaclasses if features of ServiceMeta are needed.

Does this solve your issue ? Do you see anything else that could be improved?

ghost commented 3 years ago

Hello,

Thank you so much for your awesome response ! Indeed @service is solving my problem :)

Finistere commented 3 years ago

Glad it helped! :)

Just FYI I've fixed the documentation and the readme to be clearer on that issue: https://antidote.readthedocs.io/en/latest/recipes.html#resolve-metaclass-conflict-with-service

I've also added ABCService as part of the public API.

Closing the issue as your issue is solved.