Overrides
Here’s one of the best parts of wired
, not seen in other Python plugin libraries/frameworks: not only can you extend, but you can replace (aka override) existing, core functionality.
In this sample the custom.py
“add-on” continues to add a FrenchCustomer
and a FrenchGreeter
.
But it also replaces the core Greeter
with a different implementation.
As a further example of this, the test_overrides.py
illustrates an extra, local re-definition.
Along the way, we refactor our the core dataclasses into a models.py
file, to make it easier for the add-on to import without circular imports happening.
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
- A Datastore which stores/retrieves instances of Customers
"""
from typing import List
from wired import ServiceContainer, ServiceRegistry
from .models import Customer, Datastore, Greeter, Settings
def setup(registry: ServiceRegistry, settings: Settings):
"""Initialize the features in the core application"""
# Make and register the Datastore
datastore = Datastore()
registry.register_singleton(datastore, Datastore)
# **** Default Greeter
# Make the greeter factory, using punctuation from settings
punctuation = settings.punctuation
def default_greeter_factory(container) -> Greeter: # pragma: no cover
# 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)
# During bootstrap, make some Customers
customer1 = Customer(name='Mary')
datastore.customers.append(customer1)
def app_bootstrap(settings: Settings) -> ServiceRegistry:
# Make the registry
registry = ServiceRegistry()
# Do setup for the core application features
setup(registry, settings)
# Import the add-on and initialize it
from .custom import setup as addon_setup
addon_setup(registry, settings)
return registry
def customer_interaction(container: ServiceContainer, customer: Customer) -> str:
"""Customer comes in, handle the steps in greeting them"""
# 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 sample_interactions(registry: ServiceRegistry) -> List[str]:
"""Pretend to do a couple of customer interactions"""
greetings = []
bootstrap_container: ServiceContainer = registry.create_container()
datastore: Datastore = bootstrap_container.get(Datastore)
for customer in datastore.customers:
# Do a sample "interaction" (aka transaction, aka request) for
# each customer. This is like handling a view for a request.
interaction_container = registry.create_container()
greeting = customer_interaction(interaction_container, customer)
greetings.append(greeting)
return greetings
def main():
settings = Settings(punctuation='!!')
registry = app_bootstrap(settings)
greetings = sample_interactions(registry)
assert ['Override Mary !!', 'Bonjour Henri !!'] == greetings
custom.py
"""
A custom add-on to our app which adds FrenchCustomer and
French Greeter.
"""
from dataclasses import dataclass
from wired import ServiceContainer, ServiceRegistry
from .models import Customer, Datastore, Greeter, Settings
@dataclass
class FrenchCustomer(Customer):
pass
@dataclass
class FrenchGreeter(Greeter):
greeting: str = 'Bonjour'
@dataclass
class OverrideGreeter(Greeter):
greeting: str = 'Override'
def setup(registry: ServiceRegistry, settings: Settings):
# The French greeter, using context of FrenchCustomer
punctuation = settings.punctuation
def french_greeter_factory(container) -> Greeter:
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)
# *** OVERRIDE !!! This add-on replaces the core, built-in Greeter
# with a different implementation.
def override_greeter_factory(container) -> Greeter:
return OverrideGreeter(punctuation=punctuation)
# Register it as a factory using its class for the "key", but
# this time register with a "context"
registry.register_factory(override_greeter_factory, Greeter, context=Customer)
# Grab the Datastore and add a FrenchCustomer
container: ServiceContainer = registry.create_container()
datastore: Datastore = container.get(Datastore)
customer1 = FrenchCustomer(name='Henri')
datastore.customers.append(customer1)
models.py
"""
Models used in the core application.
Putting models in their own file is considered good practice for
code clarity. It also solves the problem of potential circular
imports.
"""
from dataclasses import dataclass, field
from typing import List
@dataclass
class Customer:
name: str
@dataclass
class Datastore:
customers: List[Customer] = field(default_factory=list)
@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}'