Examples
Decorators
Let’s show the use of venusian and the wired.service_factory decorator in building an app that scans for factories.
We’ll do it piece-by-piece, starting with a regular wired app.
Basic wired app
As a starting point we use an app with no decorators.
In this app we have a Greeting class that depends on a Greeter class.
As such, we register a factory for each.
from wired import ServiceRegistry, ServiceContainer
class Greeter:
def __init__(self, name):
self.name = name
def greeter_factory(container):
return Greeter('Marie')
class Greeting:
def __init__(self, greeter: Greeter):
self.greeter = greeter
def greet(self):
return f'Hello from {self.greeter.name}'
def greeting_factory(container: ServiceContainer):
greeter = container.get(Greeter)
return Greeting(greeter)
def app():
# Do this once at startup
registry = ServiceRegistry()
registry.register_factory(greeter_factory, Greeter)
registry.register_factory(greeting_factory, Greeting)
# Do this for every "request" or operation
container = registry.create_container()
greeting: Greeting = container.get(Greeting)
assert 'Hello from Marie' == greeting.greet()
This is the basics of a simple, pluggable application.
As a note, everything in the app function would typically be done once as part of your app.
Class as factory
Before getting to decorators, just to emphasize…the first argument to wired.ServiceRegistry.register_factory() can be the class itself.
from wired import ServiceContainer, ServiceRegistry
class Greeter:
def __init__(self, name):
self.name = name
class Greeting:
def __init__(self, container: ServiceContainer):
self.greeter = container.get(Greeter)
def greet(self):
return f'Hello from {self.greeter.name}'
def greeter_factory(container):
return Greeter('Marie')
def app():
# Do this once at startup
registry = ServiceRegistry()
registry.register_factory(greeter_factory, Greeter)
registry.register_factory(Greeting, Greeting)
# Do this for every "request" or operation
container = registry.create_container()
greeting: Greeting = container.get(Greeting)
assert 'Hello from Marie' == greeting.greet()
venusian scanner
We will now add venusian and its Scanner.
We make a Scanner instance and include the registry.
When we call scan on a module – in this case, the same module – it looks for the @service_factory decorator.
The decorator then extracts the registry instance we stored in the Scanner and does the registration.
"""
Simplest example for ``@service_factory`` decorator: a basic class.
"""
from venusian import Scanner
from wired import service_factory, ServiceRegistry
from .. import decorators
@service_factory()
class Greeting:
def __init__(self, container):
self.greeter = container.get(Greeter)
def greet(self):
return f'Hello from {self.greeter.name}'
class Greeter:
def __init__(self, name):
self.name = name
def greeter_factory(container):
return Greeter('Marie')
def app():
# Do this once at startup
registry = ServiceRegistry()
scanner = Scanner(registry=registry)
# Point the scanner at a package/module and scan
scanner.scan(decorators.basic_class)
registry.register_factory(greeter_factory, Greeter)
# No longer need this line
# registry.register_factory(Greeting, Greeting)
# Do this for every "request" or operation
container = registry.create_container()
greeting: Greeting = container.get(Greeting)
assert 'Hello from Marie' == greeting.greet()
What’s nice about this venusian approach: no module-level state globals stuff.
Another decorator plus __wired_factory__
We’ll now move the Greeter class to also use the @service_factory decorator instead of a manual registration.
Since it hard-codes Marie as a value to the constructor, we use the __wired_factory__ protocol as a class method to generate the instance.
This means any code that does container.get(Greeter) will run this class method to construct the Greeter.
"""
Decorators for both plus usage of the ``__wired_factory__ protocol.
"""
from venusian import Scanner
from wired import service_factory, ServiceRegistry
from .. import decorators
@service_factory()
class Greeter:
def __init__(self, name):
self.name = name
@classmethod
def __wired_factory__(cls, container):
return cls('Marie')
@service_factory()
class Greeting:
greeter: Greeter
def __init__(self, greeter: Greeter):
self.greeter = greeter
def greet(self):
return f'Hello from {self.greeter.name}'
@classmethod
def __wired_factory__(cls, container):
greeter = container.get(Greeter)
return cls(greeter)
def app():
# Do this once at startup
registry = ServiceRegistry()
scanner = Scanner(registry=registry)
# Point the scanner at a package/module and scan
scanner.scan(decorators.decorator_with_wired_factory)
# Do this for every "request" or operation
container = registry.create_container()
greeting: Greeting = container.get(Greeting)
assert 'Hello from Marie' == greeting.greet()
We also add a __wired_factory__ class method to Greeting to make it nicer.
Now its constructor no longer uses the container, which is a huge surface area.
Instead, the class is constructed just with the data it needs, which is nice for testing.
The class method acts as an “adapter”, getting stuff out of the container that is needed for the class.
Decorator arguments
The @service_factory acts as a replacement for register_factory.
Thus it needs to support the other arguments beyond the first one:
The
service_or_iface, if not provided, defaults to the class the decorator is decoratingIf you pass
for_=to the decorator, it will be used as theservice_or_ifaceargument toYou can also pass
context=andname=
Imagine our app now has a Customer and FrenchCustomer as container contexts.
Here is an example of registering different Greeter classes that are unique to those contexts:
"""
Decorators for both plus usage of the ``__wired_factory__ protocol.
"""
from venusian import Scanner
from wired import service_factory, ServiceRegistry
from .. import decorators
class Customer:
def __init__(self):
self.name = 'Jill'
class FrenchCustomer(Customer):
def __init__(self):
super().__init__()
self.name = 'Juliette'
@service_factory(context=Customer)
class Greeter:
def __init__(self, name):
self.name = name
@classmethod
def __wired_factory__(cls, container):
return cls('Susan')
@service_factory(for_=Greeter, context=FrenchCustomer)
class FrenchGreeter:
""" Serves as Greeter when container.context is FrenchCustomer """
def __init__(self, name):
self.name = name
@classmethod
def __wired_factory__(cls, container):
return cls('Marie')
@service_factory(context=Customer)
class Greeting:
greeter: Greeter
def __init__(self, greeter: Greeter, customer):
self.greeter = greeter
self.customer = customer
def greet(self):
return f'Hello from {self.greeter.name} to {self.customer.name}'
@classmethod
def __wired_factory__(cls, container):
greeter = container.get(Greeter)
customer = container.context
return cls(greeter, customer)
def app():
# Do this once at startup
registry = ServiceRegistry()
scanner = Scanner(registry=registry)
# Point the scanner at a package/module and scan
scanner.scan(decorators.decorator_args)
# First request, for a regular Customer
customer1 = Customer()
container1 = registry.create_container(context=customer1)
greeting1: Greeting = container1.get(Greeting)
assert 'Hello from Susan to Jill' == greeting1.greet()
# Second request, for a FrenchCustomer
customer2 = FrenchCustomer()
container2 = registry.create_container(context=customer2)
greeting2: Greeting = container2.get(Greeting)
assert 'Hello from Marie to Juliette' == greeting2.greet()
Wired Factory
Registering a factory means two things: a callable that constructs and returns an object, then the “kind” of thing the factory is registered for.
You can eliminate the callable as a separate function by providing a __wired_factory__ callable on, for example, the class that gets constructed.
This is the wired factory “protocol” and the callable acts as an adapter. It is handed the container, extracts what it needs, then constructs and returns an object.
Basic wired factory callable
We start again with our simple app, with a Greeting that uses a Greeter.
In this case, we do two things:
Both classes have a
classmethodthat manages construction of instancesThe
register_factoryfirst argument is, thus, the class itself
"""
A class is a callable that can be the factory supplied to
:func:`wired.ServiceRegistry.register_factory`.
"""
from wired import ServiceContainer, ServiceRegistry
class Greeter:
def __init__(self, name):
self.name = name
@classmethod
def __wired_factory__(cls, container: ServiceContainer):
return cls('Marie')
class Greeting:
def __init__(self, greeter: Greeter):
self.greeter = greeter
def greet(self):
return f'Hello from {self.greeter.name}'
@classmethod
def __wired_factory__(cls, container):
greeter = container.get(Greeter)
return cls(greeter)
def app():
# Do this once at startup
registry = ServiceRegistry()
registry.register_factory(Greeter, Greeter)
registry.register_factory(Greeting, Greeting)
# Do this for every "request" or operation
container = registry.create_container()
greeting: Greeting = container.get(Greeting)
assert 'Hello from Marie' == greeting.greet()
With this, when some application code calls container.get(Greeter), the construction is done by Greeter.__wired_factory__.