Metaclass issues
Closed this issue · 3 comments
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,
Hello!
Currently, you can solve it in two ways:
- Use the
@service
decorator designed explicitly for that reason when inheritingService
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')
- 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?
- One of the primary goals of Antidote is to enforce maintainable code, in the sense of explicit code, whenever possible. Using metaclasses instead of decorator forces the declaration of Antidote's configuration to be where the class is actually defined. With a decorator you can apply it somewhere else, it's easier to do magic stuff that hinders maintainability. As the
@service
decorator exists as an escape hatch, I find it IMHO as flexible. - Metaclasses provide a better typing experience for the
dependency @ factory
anddependency.parameterized(...)
syntax. - The wiring of classes is cleaner, at least for me as a maintainer, with a nested configuration class than class decorators. It required having to copy-paste several arguments and their documentation and the API design was worse IMHO.
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
- a note in the
Service
docstring (API Reference). It's obviously missing - a recipe in the documentation for it.
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?
Hello,
Thank you so much for your awesome response !
Indeed @service is solving my problem :)
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.