-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #41 from mmerickel/pe-wired-factory
pe-wired-factory
- Loading branch information
Showing
38 changed files
with
580 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
======== | ||
Examples | ||
======== | ||
|
||
.. _examples-decorators: | ||
|
||
Decorators | ||
~~~~~~~~~~ | ||
|
||
Let's show the use of `venusian <https://pypi.org/project/venusian/>`_ and the :class:`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. | ||
|
||
.. literalinclude:: ../examples/decorators/no_decorator.py | ||
|
||
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 :meth:`wired.ServiceRegistry.register_factory` can be the class itself. | ||
|
||
.. literalinclude:: ../examples/decorators/no_decorator_class.py | ||
:emphasize-lines: 25 | ||
|
||
``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. | ||
|
||
.. literalinclude:: ../examples/decorators/basic_class.py | ||
|
||
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``. | ||
|
||
.. literalinclude:: ../examples/decorators/decorator_with_wired_factory.py | ||
|
||
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 decorating | ||
- If you pass ``for_=`` to the decorator, it will be used as the ``service_or_iface`` argument to | ||
- You can also pass ``context=`` and ``name=`` | ||
|
||
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: | ||
|
||
.. literalinclude:: ../examples/decorators/decorator_args.py | ||
|
||
.. _examples-wired-factory: | ||
|
||
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 ``classmethod`` that manages construction of instances | ||
- The ``register_factory`` first argument is, thus, the class itself | ||
|
||
.. literalinclude:: ../examples/wired_factory/register_wired_factory.py | ||
|
||
With this, when some application code calls ``container.get(Greeter)``, the construction is done by ``Greeter.__wired_factory__``. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -108,6 +108,7 @@ More Information | |
:maxdepth: 1 | ||
|
||
usage | ||
examples | ||
tutorial/index | ||
dc/index | ||
dc/usage | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
""" | ||
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
""" | ||
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() |
Oops, something went wrong.