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}'