Clients --- Talking to the outside world
========================================

In the previous chapter, we integrated Baseplate.py into our Pyramid
application and got some observers working to see how things were performing.
Most applications are not so self-contained though and have to talk to other
services to do their job. In this chapter, we'll add a dependency on a database
to see what that looks like.

Adding a database
-----------------

We're going to use a popular Python ORM called `SQLAlchemy`_ to talk to our
database. Let's install that to get started:

.. _`SQLAlchemy`: https://www.sqlalchemy.org/

.. code-block:: console

   $ pip install sqlalchemy

Now that's installed, we can use Baseplate.py's helpers to add SQLAlchemy to
our service.

.. literalinclude:: helloworld.py
   :language: python
   :emphasize-lines: 2,10,17

Pretty simple, but there's something subtle going on here. Let's dig into it.

.. literalinclude:: helloworld.py
   :language: python
   :start-at: configure_context
   :end-before: Configurator

This call to :py:meth:`~baseplate.Baseplate.configure_context`, during
application startup, tells Baseplate.py that we want to add a SQLAlchemy
:py:class:`~sqlalchemy.orm.session.Session` to the "context" with the name
``db``.

What exactly the "context" is depends on what framework you're using, but for
Pyramid applications it's the ``request`` object that Pyramid gives to every
request handler.

.. note::
   Why do we pass in the context configuration as a dictionary? It's possible
   to set up multiple clients at the same time this way. You can even do more
   complicated things like nesting dictionaries to organize the stuff you add
   to the context. See :py:meth:`~baseplate.Baseplate.configure_context` for
   more info.

.. literalinclude:: helloworld.py
   :language: python
   :start-at: request.db
   :end-at: request.db

Since we have connected the :py:class:`~baseplate.Baseplate` object with
Pyramid and told it to configure the context like this, we'll now see a ``db``
attribute on the ``request`` that has that SQLAlchemy session we wanted.

OK. Now we have got that wired up, let's try running it.

.. code-block:: console

   $ baseplate-serve --debug helloworld.ini
   ...
   baseplate.lib.config.ConfigurationError: db.url: no value specified

Ah! It looks like we have got some configuring to do.

Configure the new client
------------------------

Telling Baseplate.py that we wanted to add the SQLAlchemy session to our
context did not actually give it any hint about how that session should be
configured. SQLAlchemy can transparently handle different SQL databases for us
and the location at which to find them will be different depending on if we're
running in production, staging, or development. So it's time for the
configuration file again.

.. literalinclude:: helloworld.ini
   :language: ini
   :emphasize-lines: 7

To wire up the database, all we need is to add a SQLAlchemy
:py:class:`~sqlalchemy.engine.url.URL` to the configuration file. Because we
configured the session to use the name ``db`` the relevant configuration line
is prefixed with that name.

We're just going to use an in-memory `SQLite`_ database here because it's built
into Python and we don't have to install anything else.

.. _`SQLite`: https://www.sqlite.org/index.html

Now when we fire up the service, it launches and returns requests with our new
data.

.. code-block:: console

   $ curl localhost:9090
   {"Hello": "World", "Now": "2021-03-02"}

Great! If you look at the server logs when you make a request, you'll notice
there are new metrics:

.. code-block:: console
   :emphasize-lines: 3-4

   $ baseplate-serve --debug helloworld.ini
   ...
   {"message": "Would send metric b'baseplate.client.latency,client=db,endpoint=execute:0.287533|ms'", ...
   {"message": "Would send metric b'baseplate.client.rate,client=db,endpoint=execute,success=True:1|c'", ...
   {"message": "Would send metric b'baseplate.server.latency,endpoint=hello_world:9.87291|ms'", ...
   {"message": "Would send metric b'baseplate.server.rate,endpoint=hello_world,success=True:1|c'", ...
   ...

The Baseplate.py SQLAlchemy integration automatically tracked usage of our
database and reports timers, counters, and other goodies to our monitoring
systems.

Summary
-------

We have now hooked up our service to a simple database and when we run queries
Baseplate.py automatically tracks them and emits telemetry.
