Skip to content

Commit

Permalink
First edition of the documentation and tutorial code
Browse files Browse the repository at this point in the history
  • Loading branch information
agronholm committed Apr 16, 2016
1 parent 9b1af68 commit f3b3e4e
Show file tree
Hide file tree
Showing 16 changed files with 605 additions and 104 deletions.
3 changes: 0 additions & 3 deletions docs/async_sync.rst

This file was deleted.

27 changes: 0 additions & 27 deletions docs/events.rst

This file was deleted.

12 changes: 3 additions & 9 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
Asphalt core documentation
==========================

Contents:
This is the core Asphalt library.

.. toctree::
:maxdepth: 2

overview
events
building
testing
tutorials/index
userguide/index
versionhistory
acknowledgements

.. warning::
Apart from the :ref:`API reference <modindex>`, the documentation is still in the very early
stages of development.

Indices and tables
==================

Expand Down
63 changes: 0 additions & 63 deletions docs/overview.rst

This file was deleted.

2 changes: 0 additions & 2 deletions docs/testing.rst

This file was deleted.

187 changes: 187 additions & 0 deletions docs/tutorials/echo.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
.. highlight:: bash

Getting your feet wet: a simple echo server and client
======================================================

This tutorial will get you started with Asphalt development from the ground up.
You will be learn how to build a simple network server that echoes back messages sent to it, along
with a matching client application. It will however not yet touch more advanced concepts like
using the ``asphalt`` command to run an application with a configuration file.

Prerequisites
-------------

Asphalt requires Python 3.5.0 or later. You will also need to have the ``pip`` and ``virtualenv``
tools installed. The former should come with most Python installations, but if it does not, you can
usually install it with your operating system's package manager (``python3-pip`` is a good guess).
As for ``virtualenv``, you can install it with the ``pip install --user virtualenv`` command.

Setting up the virtual environment
----------------------------------

Now that you have your base tools installed, it's time to create a *virtual environment* (referred
to as simply ``virtualenv`` later). Installing Python libraries in a virtual environment isolates
them from other projects, which may require different versions of the same libraries.

Now, create a project directory and a virtualenv::

mkdir tutorial
cd tutorial
virtualenv tutorialenv
source tutorialenv/bin/activate

On Windows, the last line should be:

.. code-block:: doscon
tutorialenv\Scripts\activate
The last command *activates* the virtualenv, meaning the shell will first look for commands in
its ``bin`` directory (``Scripts`` on Windows) before searching elsewhere. Also, Python will
now only import third party libraries from the virtualenv and not anywhere else. To exit the
virtualenv, you can use the ``deactivate`` command (but don't do that now!).

You can now proceed with installing Asphalt itself::

pip install asphalt

Creating the project structure
------------------------------

Every project should have a top level package, so create one now::

mkdir echo
touch echo/__init__.py

On Windows, the last line should be:

.. code-block:: doscon
copy NUL echo\__init__.py
Creating the first component
----------------------------

.. highlight:: python3

Now, let's write some code! Create a file named ``server.py`` in the ``echo`` package directory::

from asphalt.core import Component, run_application


class ServerComponent(Component):
async def start(self, ctx):
print('Hello, world!')

if __name__ == '__main__':
component = ServerComponent()
run_application(component)

The ``ServerComponent`` class is the *root component* (and in this case, the only component) of
this application. Its ``start()`` method is called by ``run_application`` when it has
set up the event loop. Finally, the ``if __name__ == '__main__':`` block is not strictly necessary
but is good, common practice that prevents ``run_application()`` from being called again if this
module is ever imported from another module.

You can now try running the above application. With the project directory (``tutorial``) as your
current directory, do:

.. code-block:: bash
python -m echo.server
This should print "Hello, world!" on the console. The event loop continues to run until you press
Ctrl+C (Ctrl+Break on Windows).

Making the server listen for connections
----------------------------------------

The next step is to make the server actually accept incoming connections.
For this purpose, the :func:`asyncio.start_server` function is a logical choice::

from asyncio import start_server

from asphalt.core import Component, run_application


async def client_connected(reader, writer):
message = await reader.readline()
writer.write(message)
writer.close()
print('Message from client:', message.decode().rstrip())


class ServerComponent(Component):
async def start(self, ctx):
await start_server(client_connected, 'localhost', 64100)

if __name__ == '__main__':
component = ServerComponent()
run_application(component)

Here, :func:`asyncio.start_server` is used to listen to incoming TCP connections on the
``localhost`` interface on port 64100. The port number is totally arbitrary and can be changed to
any other legal value you want to use.

Whenever a new connection is established, the event loop launches ``client_connected()`` as a new
task. It receives two arguments, a :class:`~asyncio.StreamReader` and a
:class:`~asyncio.StreamWriter`. In the callback we read a line from the client, write it back to
the client and then close the connection. To get at least some output from our application, we
print the received message on the console (decoding it from ``bytes`` to ``str`` and stripping the
trailing newline character first). In production applications, you will want to use the
:mod:`logging` module for this instead.

If you have the ``netcat`` utility or similar, you can already test the server like this:

.. code-block:: bash
echo Hello | nc localhost 64100
This command, if available, should print "Hello" on the console, as echoed by the server.

Creating the client
-------------------

No server is very useful without a client to access it, so we'll need to add a client module in
this project. And to make things a bit more interesting, we'll make the client accept a message to
be sent as a command line argument.

Create the file ``client.py`` file in the ``echo`` package directory as follows::

import sys
from asyncio import get_event_loop, open_connection

from asphalt.core import Component, run_application


class ClientComponent(Component):
def __init__(self, message):
self.message = message

async def start(self, ctx):
reader, writer = await open_connection('localhost', 64100)
writer.write(self.message.encode() + b'\n')
response = await reader.readline()
writer.close()
get_event_loop().stop()
print('Server responded:', response.decode().rstrip())

if __name__ == '__main__':
msg = sys.argv[1]
component = ClientComponent(msg)
run_application(component)

In the client component, the message to be sent is first extracted from the list of command line
arguments. It is then given to ``ClientComponent`` as a constructor argument and saved as an
attribute of the component instance for later use in ``start()``.

When the client component starts, it connects to ``localhost`` on port 64100. Then it converts the
message to bytes for transport (adding a newline character so the server can use ``readline()``).
Then it reads a response line from the server. Finally, it closes the connection and stops the
event loop, allowing the application to exit.

To send the "Hello" message to the server, run this in the project directory:

.. code-block:: bash
python -m echo.client Hello
14 changes: 14 additions & 0 deletions docs/tutorials/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Tutorials
=========

The following tutorials will help you get acquainted with Asphalt application development.
It is expected that the reader have at least basic understanding of the Python language.

Code for all tutorials can be found in the ``examples`` directory in the source distribution or in
the
`Github repository <https://github.com/asphalt-framework/asphalt/tree/master/asphalt/examples>`_.

.. toctree::
:maxdepth: 2

echo
31 changes: 31 additions & 0 deletions docs/userguide/architecture.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Architectural overview
======================

Asphalt applications are built by assembling a hiearchy of *components*, starting from one
*root component* which may contain subcomponents which in turn may contain their own subcomponents
and so on. Each component provides some specific functionality for the application.
Some components could come from third party libraries while some components could be part
of your own application. Typically you will write the root component yourself, but depending on the
application, this may not be necessary.

Components work together through a *context*. Each Asphalt application has at least one context
object, created by the application runner. This context is passed to the
:meth:`~asphalt.core.Component.start` method of the component.
The context is used to share *resources* between the components. Resources can be any arbitrary
objects like data or services (such as database connections). Each resource is associated with a
textual type (usually the fully qualified class name of the resource object). Components can
publish and request resources, like merchants in a marketplace.

Contexts can have subcontexts. How and if subcontexts are used dependes on the components using
them. For example, a component serving network requests will want to create a subcontext for each
request it handles to store request specific information and other state. While the subcontext will
have its own independent state, it also has full access the resources of its parent context.

An Asphalt application is normally started by the *runner*. The runner is a function that
initializes the logging system, the event loop, the top level context and then starts the root
component. The root component will start any subcomponents and so on. Finally, the runner will just
keep the event loop running until the application is shut down.

The runner is further extended by the ``asphalt`` command line tool which can read a YAML formatted
configuration file and pass the parsed configuration to the runner. This allows applications to
have different development and production configurations without having to modify any actual code.
File renamed without changes.

0 comments on commit f3b3e4e

Please sign in to comment.