mmerickel / wired

A service locator implementation for Python.
https://wired.readthedocs.io
MIT License
17 stars 9 forks source link

Dataclasses: `for_` or use subclassing? #20

Closed pauleveritt closed 5 years ago

pauleveritt commented 5 years ago

This is a design question for Michael.

As shown in the example, you register a dataclass with a for_ argument in the decorator:

@factory(for_=Greeter, context=FrenchCustomer)
@dataclass
class FrenchGreeter:
    settings: Settings
    name: str = 'Henri'

Behind the scenes, the value of for_ becomes the second argument to registry.register_factory. Later, specialty decorators such as @view would allow eliding that and, behind the scenes, would supply for_=View. Or in the case above, @greeter would supply for_=Greeter.

Andrey (from PyCharm) argued that FrenchGreeter is a kind of Greeter and I should be doing class FrenchGreeter(Greeter). The "system" should then be able to figure out via inheritance the match.

I'm not sure wired can do that. I'm also not sure it should. Open to suggestions.

mmerickel commented 5 years ago

Andrey (from PyCharm) argued that FrenchGreeter is a kind of Greeter and I should be doing class FrenchGreeter(Greeter). The "system" should then be able to figure out via inheritance the match.

wired cannot currently do that for classes because z.i does not support it. I wish it would but it does not right now. z.i basically does not support classes at all and I hacked some basic support on for doing direct type-based lookup but inheritance is a whole thing I haven't tried to make work. It will work if you do IFrenchGreeter(IGreeter) with interfaces. Then when you query an IGreeter it will consider the IFrenchGreeter impl a match.

To be clear, the following would work:

from wired import ServiceRegistry
from zope.interface import Interface
from zope.interface import implementer

class IGreeter(Interface):
    pass

class IFrenchGreeter(IGreeter):
    pass

@implementer(IFrenchGreeter)
class FrenchGreeter:
    name = 'henri'

class FrenchCustomer:
    pass

def french_factory(container):
    return FrenchGreeter()

registry = ServiceRegistry()
registry.register_factory(french_factory, IFrenchGreeter, context=FrenchCustomer)

container = registry.create_container()
result = container.get(IGreeter, context=FrenchCustomer())
assert isinstance(result, FrenchGreeter)
pauleveritt commented 5 years ago

IMO though I might like interfaces, it would be a negative point emphasizing it. I'll close this ticket and stick with for_. The specialty decorators will make it less noisy either way.

mmerickel commented 5 years ago

Note my updated code example just for posterity.