Decoupled
wired
lets you register different flavors of a thing.
Above we had a Greeter
and a FrenchGreeter
.
This pattern is useful when making an extensible application: Other packages can add flavors of things that match in custom circumstances.
In this sample we change the application from a single file into a package.
In decoupled/__init__.py
we make the “default” application.
It only knows about Customer and Greeter. In custom.py
we simulate an installed add-on package which adds FrenchCustomer
and FrenchGreeter
.
custom.py
has a setup
function which wires itself up.
This application’s protocol is:
If your package has a
setup
function at the top level…..then we’ll call it, passing the registry and the settings
Same functionality as the context
, but simulating an add-on package.
Code
__init__.py
"""
A customer walks into a store. Do the steps to interact with them:
- Get a correct greeter
- Interact with them
Simple wired application:
- Settings that say what punctuation to use
- Registry
- A bundled Greeter and Customer
- An add-on which defines a FrenchGreeter and FrenchCustomer
"""
from dataclasses import dataclass
from wired import ServiceRegistry
@dataclass
class Customer:
name: str
@dataclass
class Settings:
punctuation: str
@dataclass
class Greeter:
punctuation: str
greeting: str = 'Hello'
def __call__(self, customer: Customer) -> str:
return f'{self.greeting} {customer.name} {self.punctuation}'
def setup(settings: Settings) -> ServiceRegistry:
# Make the registry
registry = ServiceRegistry()
# Make the greeter factory, using punctuation from settings
punctuation = settings.punctuation
# First the default greeter, no context
def default_greeter_factory(container) -> Greeter:
# Use the dataclass default for greeting
return Greeter(punctuation=punctuation)
# Register it as a factory using its class for the "key"
registry.register_factory(default_greeter_factory, Greeter)
# Import the add-on and initialize it
from .custom import setup
setup(registry, settings)
return registry
def greet_customer(registry: ServiceRegistry, customer: Customer) -> str:
# A customer comes in, handle the steps in the greeting
# as a container.
container = registry.create_container()
# Get a Greeter using the customer as context. Use the Customer when
# generating the greeting.
greeter: Greeter = container.get(Greeter, context=customer)
greeting = greeter(customer)
return greeting
def main():
settings = Settings(punctuation='!!')
registry = setup(settings)
# *** Default Customer
# Make a Customer, pass into the "greet_customer" interaction,
# then test the result.
customer = Customer(name='Mary')
assert 'Hello Mary !!' == greet_customer(registry, customer)
# *** French Customer
# Make a FrenchCustomer, pass into the "greet_customer" interaction,
# then test the result.
from .custom import FrenchCustomer
french_customer = FrenchCustomer(name='Henri')
assert 'Bonjour Henri !!' == greet_customer(registry, french_customer)
custom.py
"""
A custom add-on to our app which adds FrenchCustomer and
French Greeter.
"""
from dataclasses import dataclass
from wired import ServiceRegistry
from . import Customer, Greeter, Settings
@dataclass
class FrenchCustomer(Customer):
pass
@dataclass
class FrenchGreeter(Greeter):
greeting: str = 'Bonjour'
def setup(registry: ServiceRegistry, settings: Settings):
# The French greeter, using context of FrenchCustomer
punctuation = settings.punctuation
def french_greeter_factory(container) -> Greeter:
# Use the dataclass default for greeting
return FrenchGreeter(punctuation=punctuation)
# Register it as a factory using its class for the "key", but
# this time register with a "context"
registry.register_factory(french_greeter_factory, Greeter, context=FrenchCustomer)