Closed bernoussim closed 4 years ago
Hello,
Feel free to open as many issues for questions as you need ! :) If I understood you correctly, you're talking about this part: How to / use-interfaces. For the context :
from antidote import register, implements, world
from enum import Flag, auto
class Profile(Flag):
POSTGRES = auto()
MYSQL = auto()
class Database:
pass
@implements(Database, state=Profile.POSTGRES)
@register
class PostgresDB(Database):
pass
@implements(Database, state=Profile.MYSQL)
@register
class MySQLDB(Database):
pass
world.update_singletons({Profile: Profile.POSTGRES})
world.get(Database) # Postgres
State in this example is a singleton, so it's meant to be used as a constant switch which would typically depend on the environment. For example being deployed on GCP or AWS, or a change of environment during a migration... Changing the state afterwards leads to undefined behavior in this case as Antidote caches singletons. So what you would get as the Database
implementation would depend on what has been done before the change.
However, currently it doesn't need to be a singleton. Instead of defining it with:
from antidote import world
world.update_singletons({Profile: Profile.POSTGRES})
You could use a factory which returns the current state:
from antidote import factory
horrible_global_state = Profile.POSTGRES
@factory(singleton=False)
def get_global_profile() -> Profile:
return horrible_global_state
world.get(Database) # Postgres
horrible_global_state = Profile.MYSQL
world.get(Database) # MYSQL
Note that it's @register
that defines whether the PostgresDB
and MySQLDB
are singletons or not. Now whether being able to change dynamically the state is a good idea or not, I'm not sure. :)
To be honest this is a feature, the state switch, I'm not entirely satisfied with. I imitated it from a smaller dependency injection library (don't remember its name) because I found it to be an interesting concept. But I didn't have a concrete use case in mind, contrary to most of the library. So I would love to hear about your use case to understand how I could improve it !
thank you @Finistere , using the factory worked. And thanks for the explanation, I understand better for which purpose this feature was designed. My use case is the following:
Does your device vendor really change at runtime ? If not I would define the state during initialization.
If it does, or if you want more flexibility, using a @factory
would be a
good option. If a function is not enough, you can decorate a class instead.
__call__()
will be used to generate the dependency. This allows you to
keep some state within the factory. Injections will be done on __init__()
and __call__()
by default. Weirdly I don't document this anymore in the
tutorial/how to.
I'll give you an example and update the documentation as soon as possible. :)
On Thu, Aug 20, 2020, 16:20 bernoussim notifications@github.com wrote:
thank you @Finistere https://github.com/Finistere , using the factory worked. And thanks for the explanation, I understand better for which purpose this feature was designed. My use case is the following:
- I have a service layer that interacts with network devices orchestrators, depending on the device vendor, I would either call the API for vendor A or vendor B. Therefore I wanted to have two implementation, that would be called based on the device vendor. But from your explanation, not sure anymore if switching dynamically between implementations is a good thing. Appreciate your feedback.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Finistere/antidote/issues/19#issuecomment-677695387, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB4E47JBO4AO2G5PT2Z3PXDSBUWJ5ANCNFSM4QFMDJYA .
unfortunately, it does change at runtime, this service will be exposed to users that don't need to know if the network device is from vendor A or vendor B. they will provide inputs that could be sitting within devices belonging to vendor A or vendor B. I have another service on top that first define where the input is sitting. I wanted to inject different services depending on where the input is sitting. Hope it's clear. Looking forward for the example. Thanks!
Here is the stateful factory documentation: https://antidote.readthedocs.io/en/latest/how_to.html#create-a-stateful-factory
In your case you could do something like:
from antidote import factory
class Vendor:
pass
@factory(singleton=False) # singleton applies for the dependency provided by the factory, Vendor
class VendorFactory:
def __call__(self) -> Vendor:
pass # return current vendor
Now there are two ways to handle the vendors themselves. Either you're handling everything yourself within the factory and cache them if necessary. Or you can rely on Antidote to instantiate the vendors. For example you could do the following:
from antidote import register, world
@register
class VendorA(Vendor):
pass
@register
class VendorB(Vendor):
pass
@factory(singleton=False)
class VendorFactory:
def __init__(self, vendorA: VendorA):
self._vendorA = vendorA
def __call__(self) -> Vendor:
if "vendorA":
return self._vendorA
else if "vendorB":
return world.get(VendorB) # retrieved dynamically only if necessary
Does this solve your need ?
thank you! closing it.
Hello,
Just wanted to say that I added a cleaner way to do this with the v0.8:
from antidote import implementation
# permanent meaning whether the implementation is chosen permanently or not.
@implementation(Vendor, permanent=False)
def choose_vendor():
""" Expected to return a dependency """
if "vendorA":
return VendorA
else:
return VendorB
It should be cleaner that the previous @implements
.
Hello,
I am opening an issue but it is actually more a support request. I would like to know how to implement multiple dependencies. I tried what was specified in the documentation but I still can't understand how to change a state via the enum. thanks in advance