Skip to content

Commit

Permalink
Updated references and other small things in the documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
agronholm committed Feb 5, 2024
1 parent 043e786 commit a5822ac
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 69 deletions.
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

exclude_patterns = ["_build"]
pygments_style = "sphinx"
autodoc_default_options = {"members": True, "show-inheritance": True}
highlight_language = "python3"
todo_include_todos = False

Expand Down
13 changes: 6 additions & 7 deletions docs/tutorials/echo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ directory::

import anyio

from asphalt.core import Component, Context, run_application
from asphalt.core import Component, run_application


class ServerComponent(Component):
async def start(self, ctx: Context) -> None:
async def start(self) -> None:
print("Hello, world!")

if __name__ == "__main__":
Expand Down Expand Up @@ -109,10 +109,9 @@ For this purpose, we will use AnyIO's :func:`~anyio.create_tcp_listener` functio

from asphalt.core import (
Component,
Context,
context_teardown,
run_application,
start_service_task,
start_background_task,
)


Expand All @@ -124,11 +123,11 @@ For this purpose, we will use AnyIO's :func:`~anyio.create_tcp_listener` functio

class ServerComponent(Component):
@context_teardown
async def start(self, ctx: Context) -> AsyncGenerator[None, Exception | None]:
async def start(self) -> AsyncGenerator[None, Exception | None]:
async with await anyio.create_tcp_listener(
local_host="localhost", local_port=64100
) as listener:
start_service_task(lambda: listener.serve(handle), "Echo server")
start_background_task(lambda: listener.serve(handle), "Echo server")
yield

if __name__ == '__main__':
Expand Down Expand Up @@ -179,7 +178,7 @@ Create the file ``client.py`` file in the ``echo`` package directory as follows:

import anyio

from asphalt.core import CLIApplicationComponent, Context, run_application
from asphalt.core import CLIApplicationComponent, run_application


class ClientComponent(CLIApplicationComponent):
Expand Down
8 changes: 4 additions & 4 deletions docs/tutorials/webnotifier.rst
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,11 @@ And to make the the results look nicer in an email message, you can switch to us


class ApplicationComponent(CLIApplicationComponent):
async def start(self, ctx: Context) -> None:
async def start(self) -> None:
self.add_component(
"mailer", backend="smtp", host="your.smtp.server.here",
message_defaults={"sender": "[email protected]", "to": "[email protected]"})
await super().start(ctx)
await super().start()

@inject
async def run(self, *, mailer: Mailer = resource()) -> None:
Expand Down Expand Up @@ -272,7 +272,7 @@ Asphalt application::
self.delay = delay

@context_teardown
async def start(self, ctx: Context) -> None:
async def start(self) -> None:
detector = Detector(self.url, self.delay)
await ctx.add_resource(detector)
start_service_task(detector.run, "Web page change detector")
Expand All @@ -298,7 +298,7 @@ become somewhat lighter::


class ApplicationComponent(CLIApplicationComponent):
async def start(self, ctx: Context) -> None:
async def start(self) -> None:
self.add_component("detector", ChangeDetectorComponent, url="http://imgur.com")
self.add_component(
"mailer", backend="smtp", host="your.smtp.server.here",
Expand Down
10 changes: 6 additions & 4 deletions docs/userguide/architecture.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Application architecture
========================

.. py:currentmodule:: asphalt.core
Asphalt applications are centered around the following building blocks:

* components
Expand All @@ -9,11 +11,11 @@ Asphalt applications are centered around the following building blocks:
* signals/events
* the application runner

*Components* (:class:`~asphalt.core.component.Component`) are classes that initialize one or more
*Components* (:class:`Component`) are classes that initialize one or more
services, like network servers or database connections and add them to the *context* as
*resources*. Components are started by the application runner and usually discarded afterwards.

*Contexts* (:class:`~asphalt.core.context.Context`) are "hubs" through which *resources* are shared
*Contexts* (:class:`Context`) are "hubs" through which *resources* are shared
between components. Contexts can be chained by setting a parent context for a new context.
A context has access to all its parents' resources but parent contexts cannot access the resources
of their children.
Expand All @@ -26,12 +28,12 @@ is unique in a context.
Events are dispatched asynchronously without blocking the sender. The signal system was loosely
modeled after the signal system in the Qt_ toolkit.

The *application runner* (:func:`~asphalt.core.runner.run_application`) is a function that is used
The *application runner* (:func:`~run_application`) is a function that is used
to start an Asphalt application. It configures up the Python logging module, sets up an event
loop policy (if configured), creates the root context, starts the root component and then runs the
event loop until the application exits. A command line tool (``asphalt``) is provided to better
facilitate the running of Asphalt applications. It reads the application configuration from one or
more YAML_ formatted configuration files and calls :func:`~asphalt.core.runner.run_application`
more YAML_ formatted configuration files and calls :func:`run_application`
with the resulting configuration dictionary as keyword arguments. The settings from the
configuration file are merged with hard coded defaults so the config file only needs to override
settings where necessary.
Expand Down
16 changes: 9 additions & 7 deletions docs/userguide/components.rst
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
Working with components
=======================

.. py:currentmodule:: asphalt.core
Components are the basic building blocks of an Asphalt application. They have a narrowly defined
set of responsibilities:

#. Take in configuration through the constructor
#. Validate the configuration
#. Add resources to the context (in :meth:`~asphalt.core.component.Component.start`)
#. Add resources to the context (in :meth:`Component.start`)
#. Close/shut down/clean up resources when the context is torn down (by directly adding a callback
on the context with :meth:`~asphalt.core.context.Context.add_teardown_callback`, or by using
:func:`~asphalt.core.context.context_teardown`)
on the context with :meth:`Context.add_teardown_callback`, or by using
:func:`context_teardown`)

The :meth:`~asphalt.core.component.Component.start` method is called either by the parent component
or the application runner with a :class:`~asphalt.core.context.Context` as its only argument.
The :meth:`Component.start` method is called either by the parent component
or the application runner with a :class:`Context` as its only argument.
The component can use the context to add resources for other components and the application
business logic to use. It can also request resources provided by other components to provide some
complex service that builds on those resources.

The :meth:`~asphalt.core.component.Component.start` method of a component is only called once,
The :meth:`Component.start` method of a component is only called once,
during application startup. When all components have been started, they are disposed of.
If any of the components raises an exception, the application startup process fails and any context
teardown callbacks scheduled so far are called before the process is exited.
Expand Down Expand Up @@ -49,7 +51,7 @@ The root component of virtually any nontrivial Asphalt application is a containe
Container components can of course contain other container components and so on.

When the container component starts its child components, each
:meth:`~asphalt.core.component.Component.start` call is launched in its own task. Therefore all the
:meth:`Component.start` call is launched in its own task. Therefore all the
child components start concurrently and cannot rely on the start order. This is by design.
The only way components should be relying on each other is by the sharing of resources in the
context.
40 changes: 20 additions & 20 deletions docs/userguide/contexts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Working with contexts and resources
.. py:currentmodule:: asphalt.core
Every Asphalt application has at least one context: the root context. The root context is typically
created by the :func:`~asphalt.core.runner.run_application` function and passed to the root
created by the :func:`run_application` function and passed to the root
component. This context will only be closed when the application is shutting down.

Most nontrivial applications will make use of *subcontexts*. A subcontext is a context that has a
Expand All @@ -25,7 +25,7 @@ Contexts are "activated" by entering them using ``async with Context():``, and e
that block. When entered, the previous active context becomes the parent context of the new one and
the new context becomes the currently active context. When the ``async with`` block is left, the
previously active context once again becomes the active context. The currently active context can
be retrieved using :func:`~.context.current_context`.
be retrieved using :func:`current_context`.

.. warning:: Activating contexts in asynchronous generators can lead to corruption of the context
stack. This is particularly common in asynchronous pytest fixtures because pytest
Expand Down Expand Up @@ -64,7 +64,7 @@ A static resource can be any arbitrary object (except ``None``). The same object
added to the context under several different types, as long as the type/name combination
remains unique within the same context.

A resource factory is a callable that takes a :class:`~asphalt.core.context.Context` as
A resource factory is a callable that takes a :class:`Context` as
an argument an returns the value of the resource. There are at least a couple reasons to
use resource factories instead of static resources:

Expand All @@ -77,20 +77,20 @@ use resource factories instead of static resources:
Getting resources from a context
--------------------------------

The :class:`~asphalt.core.context.Context` class offers a few ways to look up resources.
The :class:`Context` class offers a few ways to look up resources.

The first one, :meth:`~asphalt.core.context.Context.get_resource`, looks for a resource or resource
The first one, :meth:`Context.get_resource`, looks for a resource or resource
factory matching the given type and name. If the resource is found, it returns its value.

The second one, :meth:`~asphalt.core.context.Context.require_resource`, works exactly the same way
except that it raises :exc:`~asphalt.core.context.ResourceNotFound` if the resource is not found.
The second one, :meth:`Context.require_resource`, works exactly the same way
except that it raises :exc:`ResourceNotFound` if the resource is not found.

The third method, :meth:`~asphalt.core.context.Context.request_resource`, calls
:meth:`~asphalt.core.context.Context.get_resource` and if the resource is not found, it waits
The third method, :meth:`Context.request_resource`, calls
:meth:`Context.get_resource` and if the resource is not found, it waits
indefinitely for the resource to be added to the context or its parents. When that happens, it
calls :meth:`~asphalt.core.context.Context.get_resource` again, at which point success is
calls :meth:`Context.get_resource` again, at which point success is
guaranteed. This is usually used only in the components'
:meth:`~asphalt.core.component.Component.start` methods to retrieve resources provided
:meth:`Component.start` methods to retrieve resources provided
by sibling components. Resources

The order of resource lookup is as follows:
Expand All @@ -105,8 +105,8 @@ Injecting resources to functions

A type-safe way to use context resources is to use `dependency injection`_. In Asphalt, this is
done by adding parameters to a function so that they have the resource type as the type annotation,
and a :func:`~.context.resource` instance as the default value. The function then needs to be
decorated using :func:`~.context.inject`::
and a :func:`resource` instance as the default value. The function then needs to be
decorated using :func:`inject`::

from asphalt.core import inject, resource

Expand All @@ -115,7 +115,7 @@ decorated using :func:`~.context.inject`::
...

To specify a non-default name for the dependency, you can pass that name as an argument to
:func:`~.context.resource`::
:func:`resource`::

@inject
async def some_function(some_arg, some_resource: MyResourceType = resource('alternate')):
Expand All @@ -133,7 +133,7 @@ Restrictions:
* The resource arguments must not be positional-only arguments
* The resources (or their relevant factories) must already be present in the context
stack (unless declared optional) when the decorated function is called, or otherwise
:exc:`~.context.ResourceNotFound` is raised
:exc:`ResourceNotFound` is raised

.. _dependency injection: https://en.wikipedia.org/wiki/Dependency_injection

Expand All @@ -143,9 +143,9 @@ Handling resource cleanup
Any code that adds resources to a context is also responsible for cleaning them up when the context
is closed. This usually involves closing sockets and files and freeing whatever system resources
were allocated. This should be done in a *teardown callback*, scheduled using
:meth:`~asphalt.core.context.Context.add_teardown_callback`. When the context is closed, teardown
:meth:`Context.add_teardown_callback`. When the context is closed, teardown
callbacks are run in the reverse order in which they were added, and always one at a time, unlike
with the :class:`~asphalt.core.event.Signal` class. This ensures that a resource that is still in
with the :class:`Signal` class. This ensures that a resource that is still in
use by another resource is never cleaned up prematurely.

For example::
Expand All @@ -161,7 +161,7 @@ For example::
ctx.add_resource(service)


There also exists a convenience decorator, :func:`~asphalt.core.context.context_teardown`, which
There also exists a convenience decorator, :func:`context_teardown`, which
makes use of asynchronous generators::

from asphalt.core import Component, context_teardown
Expand All @@ -182,7 +182,7 @@ makes use of asynchronous generators::
Sometimes you may want the cleanup to know whether the context was ended because of an unhandled
exception. The one use that has come up so far is committing or rolling back a database
transaction. This can be achieved by passing the ``pass_exception`` keyword argument to
:meth:`~asphalt.core.context.Context.add_teardown_callback`::
:meth:`Context.add_teardown_callback`::

class FooComponent(Component):
async def start(ctx):
Expand All @@ -197,7 +197,7 @@ transaction. This can be achieved by passing the ``pass_exception`` keyword argu
ctx.add_teardown_callback(teardown, pass_exception=True)
ctx.add_resource(db)

The same can be achieved with :func:`~asphalt.core.context.context_teardown` by storing the yielded
The same can be achieved with :func:`context_teardown` by storing the yielded
value::

class FooComponent(Component):
Expand Down
14 changes: 8 additions & 6 deletions docs/userguide/deployment.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Configuration and deployment
============================

.. py:currentmodule:: asphalt.core
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.
Expand Down Expand Up @@ -29,9 +31,9 @@ What this will do is:

#. read all the given configuration files, if any, starting from ``yourconfig.yaml``
#. read the command line configuration options passed with ``--set``, if any
#. merge the configuration files' contents and the command line configuration options into a single configuration dictionary using
:func:`~asphalt.core.utils.merge_config`.
#. call :func:`~asphalt.core.runner.run_application` using the configuration dictionary as keyword
#. merge the configuration files' contents and the command line configuration options
into a single configuration dictionary using :func:`merge_config`.
#. call :func:`run_application` using the configuration dictionary as keyword
arguments

Writing a configuration file
Expand Down Expand Up @@ -86,12 +88,12 @@ You could then write a configuration file like this::

In the above configuration you have three top level configuration keys: ``max_threads``,
``component`` and ``logging``, all of which are directly passed to
:func:`~asphalt.core.runner.run_application` as keyword arguments.
:func:`run_application` as keyword arguments.

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.utils.resolve_reference` for details). The rest of the keys in this section are
:func:`resolve_reference` for details). The rest of the keys in this section are
passed directly to the constructor of the ``MyRootComponent`` class.

The ``components`` section within ``component`` is processed in a similar fashion.
Expand Down Expand Up @@ -146,7 +148,7 @@ Configuration overlays

Component configuration can be specified on several levels:

* Hard-coded arguments to :meth:`~asphalt.core.component.ContainerComponent.add_component`
* Hard-coded arguments to :meth:`ContainerComponent.add_component`
* First configuration file argument to ``asphalt run``
* Second configuration file argument to ``asphalt run``
* ...
Expand Down
Loading

0 comments on commit a5822ac

Please sign in to comment.