Requests and Views
As housekeeping, we remove the override part from the previous step.
Web frameworks, and even systems like pytest
, model individual units of work as a “request”.
The request contains the information about the thing being worked on (the URL, the headers) along with other information unique to the operation. When finished, the request is thrown away.
Let’s borrow that idea and that jargon. Each Customer coming through the door is part of a Request
.
Let’s also steal another idea from web frameworks and use a View
to collect all the information needed for generating a result, plus the logic to generate that result.
“interaction” -> Request
View manages the generation of the output
Datastore is now a dict-like thing to get individual Customers
The
Url
is stored in the container, so anything that wants to get the currently-processed URL, can ask the container for itRequest is created from a request factory with the URL of the Customer and also stores the container (in case a View wants to do a lookup)
A View factory which gets the Request, Context, and Greeter from the container, then renders the greeting
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
- Request and View factories to assemble the full processing chain
"""
from typing import List
from wired import ServiceRegistry
from .models import Customer, Datastore, Greeter, Request, Resource, Settings, Url, View
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 setup(registry: ServiceRegistry, settings: Settings):
"""Initialize the features in the core application"""
# Make and register the Datastore singleton
datastore = Datastore()
registry.register_singleton(datastore, Datastore)
# Context factory
def context_factory(container) -> Resource:
# Presumes that "url" is in the container
ds: Datastore = container.get(Datastore)
url: str = container.get(Url)
context: Resource = ds.customers.get(url)
return context
registry.register_factory(context_factory, Resource)
# Request factory
def request_factory(container) -> Request:
url: str = container.get(Url)
request = Request(url=url, container=container)
return request
registry.register_factory(request_factory, Request)
# **** Default View
def view_factory(container) -> View:
request: Request = container.get(Request)
context: Resource = container.get(Resource)
greeter: Greeter = container.get(Greeter, context=context)
view = View(request=request, context=context, greeter=greeter)
return view
registry.register_factory(view_factory, View)
# **** Default Greeter
def default_greeter_factory(container) -> Greeter:
# Use the dataclass default for greeting
return Greeter(punctuation=settings.punctuation)
# Register it as a factory using its class for the "key"
registry.register_factory(default_greeter_factory, Greeter)
# During bootstrap, make some Customers
mary = Customer(name='mary', title='Mary')
datastore.customers['mary'] = mary
def process_request(registry: ServiceRegistry, url: str) -> str:
"""Given URL (customer name), make a Request to handle interaction"""
# Make the container that this request gets processed in
container = registry.create_container()
# Put the url into the container
container.register_singleton(url, Url)
# Create a View to generate the greeting
view = container.get(View)
# Generate a response
response = view()
return response
def sample_interactions(registry: ServiceRegistry) -> List[str]:
"""Pretend to do a couple of customer interactions"""
return [process_request(registry, url) for url in ('mary', 'henri')]
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 .models 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:
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)
henri = FrenchCustomer(name='henri', title='Henri')
datastore.customers['henri'] = henri
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 Dict
from wired import ServiceContainer
@dataclass
class Url:
value: str
@dataclass
class Resource:
name: str
title: str
@dataclass
class Customer(Resource):
pass
@dataclass
class Datastore:
customers: Dict[str, Customer] = field(default_factory=dict)
@dataclass
class Settings:
punctuation: str
@dataclass
class Request:
url: str
container: ServiceContainer
@dataclass
class Greeter:
punctuation: str
greeting: str = 'Hello'
@dataclass
class View:
request: Request
context: Resource
greeter: Greeter
def __call__(self) -> str:
g = self.greeter
return f'{g.greeting} {self.context.title} {g.punctuation}'