Datastore
Sharp readers will have spotted the flaw in decoupled
: the main application had to know about and create a FrenchCustomer
.
We want the add-on to be the only place that knows about.
Let’s make a mini-database of Customers and let the add-on create and store a FrenchCustomer. The main app is then blissfully unaware…it just knows to that a Customer has a type and a name.
We do this by making a Datastore
dataclass and registering an instance as a singleton.
This lets all service factories fetch the Datastore.
Along the way we did some refactoring to make the call sequence better match how an application like this should work:
greet_customer
doesn’t make its own container, instead, one is made in the core of the appRename
greet_customer
tocustomer_interaction
to connote this might be more steps than just a greetingChange the functions to receive a container (for the interaction/request) rather than the entire registry
Rename the
__init__.setup
function toapp_bootstrap
to make it more clear what it’s doingMove some of its responsibility to a
setup
function to match the add-on setup, thus the core isn’t “special”Refactor sample interactions out of
main
to make testing simpler, then decrease the test to just one integration-style test (these are sample applications, no need for unit tests)
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 dataclasses import dataclass, field
from typing import List
from wired import ServiceContainer, ServiceRegistry
@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}'
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:
# 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 greetings == ['Hello Mary !!', 'Bonjour Henri !!']
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 . import Customer, Datastore, 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)
# 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)