Finistere/antidote

rename @service to @injectable & add factory argument

Closed this issue · 4 comments

@injectable is less specific than @service

The factory argument would only allow class/static method names:

from antidote import injectable

@injectable(factory='build')
class Element:
    @classmethod
    def build(cls) -> Element:
        return Element()

This would make it easier to use other injectables only for build purposes without interfering with __init__ which can be typically generated when using @dataclass. Another advantage is allowing to use a stateful factory:

from antidote import injectable, inject

@injectable
class ElementFactory:
    def create(self) -> Element:
        return Element()

@injectable(factory='build')
class Element:
    @classmethod
    def build(cls, factory: ElementFactory = inject.me()) -> Element:
        return factory.create()

Now one may ask why only static/class methods and not directly the function? The purpose is to avoid abusive use of @injectable on class which one doesn't own:

# redis library
class Redis:
    pass
from antidote import injectable, inject

# Forcing a method makes declaring a factory for Redis impossible without it being there already.
injectable(Redis, factory='???')

Regarding without interfering with __init__ ... I believe __post_init__ plays this role. Differences: __post_init__ isn't actually the initializer, it does work after instantiation and (b) perhaps you want to allow injection on build.

Minor DX note...in wired we looked for a specially-named __wired_factory__ class method. Perhaps you could eliminate need for factory=build for most cases?

Differences: post_init isn't actually the initializer, it does work after instantiation

Yes, which means you can't use an external factory to build the class, which is useful if you want to keep some state to build non-singleton injectables typically.

and (b) perhaps you want to allow injection on build.

All methods, including __post_init__() and build() if defined, are injected by default with @injectable/@service. Controlled by the wiring argument. Using @inject directly overrides anything declared by wiring.

Minor DX note...in wired we looked for a specially-named wired_factory class method. Perhaps you could eliminate need for factory=build for most cases?

Good point! It would probably be named __antidote_factory__(). But unsure about it, it feels long as a name a bit intrusive in the sense that it's more tightly coupled with the Antidote framework, even though there's nothing specific about it. Contrary to predicates typically. As a user, I'll probably test the factory directly and as such would probably prefer writing and using a name that makes the most sense to me rather than __antidote_factory__(). So for now I'll avoid it as it's easier to add than remove.

Released in 1.3.0