Skip to content

Commit

Permalink
Documentation updates
Browse files Browse the repository at this point in the history
  • Loading branch information
agronholm committed Apr 19, 2016
1 parent c831bca commit 68d5c56
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 20 deletions.
30 changes: 21 additions & 9 deletions asphalt/core/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ async def start(self, ctx: Context):
"""
Perform any necessary tasks to start the services provided by this component.
The context can be used to:
* add context event listeners (:meth:`~Context.add_listener`)
* publish resources (:meth:`~Context.publish_resource` and
:meth:`~Context.publish_lazy_resource`)
* request resources (:meth:`~Context.request_resource`)
Components typically use the context to:
* publish resources (:meth:`~asphalt.core.context.Context.publish_resource` and
:meth:`~asphalt.core.context.Context.publish_lazy_resource`)
* request resources (:meth:`~asphalt.core.context.Context.request_resource`)
It is advisable for Components to first publish all the resources they can before
requesting any. This will speed up the dependency resolution and prevent deadlocks.
Expand All @@ -35,6 +34,12 @@ async def start(self, ctx: Context):


class ContainerComponent(Component):
"""
A component that can contain other components.
This class is essential to building nontrivial applications.
"""

__slots__ = 'child_components', 'component_config'

def __init__(self, components: Dict[str, Any]=None):
Expand All @@ -43,16 +48,23 @@ def __init__(self, components: Dict[str, Any]=None):

def add_component(self, alias: str, type: Union[str, type]=None, **kwargs):
"""
Instantiate a component using :func:`create_component` and adds it as a child component of
this container.
Add a child component.
This will instantiate a component class, as specified by the ``type`` argument.
If the second argument is omitted, the value of ``alias`` is used as its value.
The locally given configuration can be overridden by component configuration parameters
supplied to the constructor (the ``components`` argument).
When configuration values are provided both as keyword arguments to this method and
component configuration through the ``components`` constructor argument, the configurations
are merged together using :func:`~asphalt.core.util.merge_config` in a way that the
configuration values from the ``components`` argument override the keyword arguments to
this method.
:param alias: a name for the component instance, unique within this container
:param type: entry point name or :cls:`Component` subclass or a textual reference to one
:param type: entry point name or :class:`Component` subclass or a textual reference to one
"""
assert check_argument_types()
Expand All @@ -70,7 +82,7 @@ def add_component(self, alias: str, type: Union[str, type]=None, **kwargs):
async def start(self, ctx: Context):
"""
Create child components that have been configured but not yet created and then calls their
:meth:`Component.start` methods in separate tasks and waits until they have completed.
:meth:`~Component.start` methods in separate tasks and waits until they have completed.
"""
for alias in self.component_config:
Expand Down
2 changes: 1 addition & 1 deletion asphalt/core/connectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
class ConnectorError(LookupError):
"""
Raised by :func:`create_connector` when the requested connector
could not be created orlooked up.
could not be created or looked up.
:ivar endpoint: the endpoint value
"""
Expand Down
2 changes: 1 addition & 1 deletion asphalt/core/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def register_topic(name: str, event_class: type = Event):
A subclass may override an event topic by re-registering it with an event class that is a
subclass of the previously registered event class. Attempting to override the topic with an
incompatible class will raise a :exception:`TypeError`.
incompatible class will raise a :exc:`TypeError`.
:param name: name of the topic (must consist of alphanumeric characters and ``_``)
:param event_class: the event class associated with this topic
Expand Down
4 changes: 2 additions & 2 deletions asphalt/core/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ def run_application(component: Component, *, max_threads: int=None,
"""
Configure logging and start the given root component in the default asyncio event loop.
Assuming the application start is successful, the event loop will continue running until the
process is terminated.
Assuming the root component was started successfully, the event loop will continue running
until the process is terminated.
Initializes the logging system first based on the value of ``logging``:
* If the value is a dictionary, it is passed to :func:`logging.config.dictConfig` as
Expand Down
4 changes: 2 additions & 2 deletions docs/userguide/components.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Building Asphalt components
===========================
Working with components and contexts
====================================

TODO
137 changes: 137 additions & 0 deletions docs/userguide/deployment.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
Configuration and deployment
============================

As your application grows more complex, you may find that you need to have different settings for
your development environment and your production environment. You may even have multiple
deployments that all need their own custom configuration.

For this purpose, Asphalt provides a command line interface that will read a YAML_ formatted
configuration file and run the application it describes.

Running the Asphalt launcher
----------------------------

Running the launcher is very straightfoward:

.. code-block:: bash
asphalt run yourconfig.yaml
What this will do is:

#. read ``yourconfig.yaml`` as a dictionary
#. resolve the runner function from the ``runner`` option
#. resolve and instantiate the root component
#. call the runner function

Except for the ``runner`` and ``component`` options, all keys in the configuration dictionary
are passed as-is to the runner function as keyword arguments. If no runner function has been
defined, :func:`asphalt.core.runner.run_application` is used.

Writing a configuration file
----------------------------

A production-ready configuration file should contain at least the following options:

* ``component``: a dictionary containing the class name and keyword arguments for its constructor
* ``logging``: a dictionary to be passed to :func:`logging.config.dictConfig`

Suppose you had the following component class as your root component::

class MyRootComponent(ContainerComponent):
def __init__(self, components, data_directory: str):
super().__init__(components)
self.data_directory = data_directory

async def start(ctx):
self.add_component('mailer', type='smtp')
self.add_component('sqlalchemy')
await super().start(ctx)

You could then write a configuration file like this:

.. code-block:: yaml
component:
type: myproject:MyRootComponent
data_directory: /some/file/somewhere
components:
mailer:
connector: tcp+ssl://smtp.mycompany.com
sqlalchemy:
url: postgresql:///mydatabase
max_threads: 20
logging:
version: 1
disable_existing_loggers: false
handlers:
console:
class: logging.StreamHandler
formatter: generic
formatters:
generic:
format: "%(asctime)s:%(levelname)s:%(name)s:%(message)s"
root:
handlers: [console]
level: INFO
In the above configuration you have three top level configuration keys: ``component``,
``max_threads`` and ``logging``.

The ``component`` section defines the type of the root component using the specially processed
``type`` option. You can either specify a setuptools entry point name (from the
``asphalt.components`` namespace) or a text reference like ``module:class`` (see
:func:`~asphalt.core.util.resolve_reference` for details).

The keys inside ``component``, save for ``type``, are passed directly to the constructor of the
``MyRootComponent`` class. The ``components`` section has similar processing; refer to
:meth:`~asphalt.core.component.ContainerComponent.add_component` for details.
Suffice to say that the per-component configuration values are merged with those provided in the
``start()`` method of ``MyRootComponent``. See the next section for a more elaborate explanation.

With ``max_threads: 20``, the maximum number of threads in the default thread pool executor is set
to 20.

The ``logging`` configuration tree here sets up a root logger that prints all log entries of at
least ``INFO`` level to the console. You may want to set up more granular logging in your own
configuration file. See the
:ref:`Python standard library documentation <python:logging-config-dictschema>` for details.

Configuration overlays
----------------------

Any options you specify in the configuration file override or augment the hard coded configuration,
specified when you use :meth:`~asphalt.core.component.ContainerComponent.add_component`.
This allows you to avoid unnecessary duplication in your configuration file by putting all the
common parts of the component configuration in the code and only specifying the parts that are
different in the actual configuration file.

In the above example configuration, the ``mailer`` component gets passed two options:

* ``type='smtp'``
* ``connector: tcp+ssl://smtp.mycompany.com``

The first one is provided in the root component code while the second option comes from the YAML
file. You could also override the mailer's type in the configuration file if you wanted. The same
effect can be achieved programmatically by supplying the override configuration to the container
component via its ``components`` constructor argument. This is very useful when writing tests
against your application. For example, you might want to use the ``mock`` mailer in your test suite
configuration to test that the application correctly sends out emails. See the documentation of the
:func:`~asphalt.core.util.merge_config` function for details on how configuration merging works.

Enabling optimizations
----------------------

Asphalt employs a number of potentially expensive validation steps in this code. The performance
hit of these checks is not a concern in development and testing, but in a production environment
you will probably want to maximize the performance.

To do this, you will want to disable Python's debugging mode by either setting the environment
variable ``PYTHONOPTIMIZE`` to ``1`` or (if applicable) running Python with the ``-O`` switch.
This has the effect of completely eliminating all ``assert`` statements and blocks starting with
``if __debug__:`` from the compiled bytecode.


.. _YAML: http://yaml.org/
.. _asphalt-mailer: https://github.com/asphalt-framework/asphalt-mailer
.. _asphalt-sqlalchemy: https://github.com/asphalt-framework/asphalt-sqlalchemy
11 changes: 6 additions & 5 deletions docs/userguide/events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ Events

Events are a handy way to make your code react to changes in another part of the application.
Asphalt events originate from classes that inherit from the
:class:`~asphalt.core.context.EventSource` mixin class.
:class:`~asphalt.core.event.EventSource` mixin class.
An example of such class is the :class:`~asphalt.core.context.Context` class.

Listening to events
-------------------

To listen to events dispatched from an :class:`~asphalt.core.context.EventSource`, call
:meth:`~asphalt.core.context.EventSource.add_listener` to add an event listener callback.
To listen to events dispatched from an :class:`~asphalt.core.event.EventSource`, call
:meth:`~asphalt.core.event.EventSource.add_listener` to add an event listener callback.

The event listener can be either a regular function or a coroutine function::

Expand All @@ -20,9 +20,11 @@ The event listener can be either a regular function or a coroutine function::
def listener(event: Event):
print(event)


async def asynclistener(event: Event):
print(event)


async def event_test():
context = Context()
context.add_listener('finished', listener)
Expand Down Expand Up @@ -63,7 +65,7 @@ Creating new event sources and event types
------------------------------------------

Any class can be made into an event source simply by inheriting from the
:class:`~asphalt.core.context.EventSource` mixin class. Then you'll just need to register one or
:class:`~asphalt.core.event.EventSource` mixin class. Then you'll just need to register one or
more event topics by using the ``@register_topic`` decorator::

from asphalt.core import EventSource, Event, register_topic
Expand Down Expand Up @@ -101,4 +103,3 @@ the topic(s)::
And to dispatch a single ``MyCustomEvent`` from your new event source::

await MyEventSource().dispatch_event('sometopic', 'foo_value', bar='bar_value')

1 change: 1 addition & 0 deletions docs/userguide/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ take a look at the :ref:`tutorials/index` first.
events
components
testing
deployment

0 comments on commit 68d5c56

Please sign in to comment.