Closed ianjosephwilson closed 9 months ago
The context is being lost here:
The Greeter
factory is registered with no context, which means svc_info.wants_context
is False
. When you look up the Greeter
service with an explicit context, and no existing greeter is found, wired
creates a nested container where the context
is None
. This nested container is passed into the Greeter
factory, and is the reason why the default Config
is returned rather than the FrenchConfig
.
The problem is that you have a generic (non-context-specific) service (Greeter
) which depends on a context-specific service (Config
). wired
caches services according to their context. Because your Greeter
isn't registered for any particular context, wired
will only store a single instance of it in the container. But that would be incorrect because you actually need a different Greeter
for each Customer
class.
You can get the test to pass by changing the Greeter registration to:
registry.register_factory(greeter_factory, Greeter, context=Customer)
This ensures that the customer is preserved in the greeter_factory
and used to select the correct Config
factory. It's not an ideal fix because it requires the Greeter
to know more about the Config
dependencies. You'll also end up with separate Greeter
instances for each Customer
instance
A better way to solve it is to make the Greeter
require a Config
object as the context. For example:
def greeter_from_config(container):
config = container.context
return Greeter(config=config)
registry.register_factory(greeter_from_config, Greeter, context=Config)
Semantically this is better, as only a single Greeter
instance will be created per Config
instance. It makes it harder to construct Greeter
instances, because you first need to get hold of the Config
, but you can register a second factory function to hide that:
def greeter_from_anything(container):
config = container.get(Config)
return container.get(Greeter, context=config)
registry.register_factory(greeter_from_anything, Greeter, context=object)
Using object
as the context for this factory means that it will pass any context through to the Config
factory. If no appropriate factory has been registered for Config
, it will throw a LookupError
.
With those 2 factories registered, these two tests pass:
def get_greeting(customer):
container = registry.create_container(context=customer)
greeter = container.get(Greeter)
return greeter.greet(customer)
default_customer = Customer()
assert get_greeting(default_customer) == "Hi Mary"
french_customer = FrenchCustomer()
assert get_greeting(french_customer) == "Bonjour Henri"
Excellent response @simonk52 - thanks for that.
Context is primarily used to define "when" to create a new service object. The container is using this info to cache the service objects it has created properly. If you declare a service as "not different per-context" then the context passed to that factory will always be None
, to try and ensure the factory is not making assumptions that the context is always the same for this service instance.
It always goes back to how the service was registered, and not what was passed in to the .get()
.
I've been using this library for a few years and I still don't understand context. This is a contrived example but in general I want to lookup a service that may or may not be context dependent but then have any dependencies resolved with the original context. It seems that the context is dropped. Is there a way to continue to use the original context?
Ie. this example fails like so