Overrides with for_ and context

We have a Greeter that is used whenever our app asks for a Greeter. But maybe we want a second kind of Greeter, used in a certain context. For example, if we ask for a Greeter but the context is FrenchCustomer, get a FrenchGreeter.

Stated differently, we want:

  • To register a FrenchGreeter

  • To be used “for” Greeter

  • …when context is ``FrenchCustomer

Our models are now richer. We add Customer, FrenchCustomer, and FrenchGreeter:

 1# models.py
 2from dataclasses import dataclass
 3
 4from wired.dataclasses import factory
 5
 6
 7@factory()
 8@dataclass
 9class Settings:
10    """Store some configuration settings for the app"""
11
12    punctuation: str = '.'
13
14
15@dataclass
16class Customer:
17    """A basic customer"""
18
19    name: str = 'Larry'
20
21
22@dataclass
23class FrenchCustomer:
24    """A certain kind of customer"""
25
26    name: str = 'Anne'
27
28
29@factory()
30@dataclass
31class Greeter:
32    """A basic greeter"""
33
34    settings: Settings
35    name: str = 'Mary'
36
37    def __call__(self, customer):
38        punctuation = self.settings.punctuation
39        return f'Hello {customer} my name is {self.name}{punctuation}'
40
41
42@factory(for_=Greeter, context=FrenchCustomer)
43@dataclass
44class FrenchGreeter:
45    """A greeter to use when the customer (context) is French"""
46
47    settings: Settings
48    name: str = 'Henri'
49
50    def __call__(self, customer):
51        punctuation = self.settings.punctuation
52        return f'Salut {customer} je m\'apelle {self.name}{punctuation}'

What’s important here is this line:

@factory(for_=Greeter, context=FrenchCustomer)

This registers a dataclass to be used as the Greeter for the case when the context is a FrenchCustomer.

Let’s change our scenario to process two requests, meaning two customers. In the first request the customer is a Customer. In the second, the customer is a FrenchCustomer. In both cases, we make a container for the request that sets the container’s context to the customer instance.

# request.py
from .models import Customer, FrenchCustomer, Greeter


def process_request(registry):
    # Handle two requests: first a regular Customer and then a
    # FrenchCustomer. In both cases:
    # - Make a request, meaning a container
    # - Make a customer instance matching that request
    # - Stash the customer in the container as the context

    # Handle a regular customer by setting the container's context
    # to an instance of Customer
    regular_customer = Customer()
    container = registry.create_container(context=regular_customer)
    greeter = container.get(Greeter)
    greeting = greeter(regular_customer.name)

    # Handle a French customer by making a container with
    # a "context" that is a FrenchCustomer
    french_customer = FrenchCustomer()
    container = registry.create_container(context=french_customer)
    french_greeter = container.get(Greeter)
    french_greeting = french_greeter(french_customer.name)

    return greeting, french_greeting

To finish, we change our assert to test two cases:

    # Application starts up
    app = App()
    app.scan()

    # Later, a request comes in
    results = process_request(app.registry)
    assert 'Hello Larry my name is Mary.' == results[0]
    assert 'Salut Anne je m\'apelle Henri.' == results[1]

With this, whenever the system asks for a Greeter, if the container’s current context is a FrenchCustomer, they’ll get FrenchGreeter instead of a plain Greeter.