4. 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:

$ pip install sqlalchemy

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

from baseplate import Baseplate
from baseplate.clients.sqlalchemy import SQLAlchemySession
from baseplate.frameworks.pyramid import BaseplateConfigurator
from pyramid.config import Configurator
from pyramid.view import view_config


@view_config(route_name="hello_world", renderer="json")
def hello_world(request):
    result = request.db.execute("SELECT date('now');")
    return {"Hello": "World", "Now": result.scalar()}


def make_wsgi_app(app_config):
    baseplate = Baseplate(app_config)
    baseplate.configure_observers()
    baseplate.configure_context({"db": SQLAlchemySession()})

    configurator = Configurator(settings=app_config)
    configurator.include(BaseplateConfigurator(baseplate).includeme)
    configurator.add_route("hello_world", "/", request_method="GET")
    configurator.scan()
    return configurator.make_wsgi_app()

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

    baseplate.configure_context({"db": SQLAlchemySession()})

This call to configure_context(), during application startup, tells Baseplate.py that we want to add a SQLAlchemy 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 configure_context() for more info.

    result = request.db.execute("SELECT date('now');")

Since we have connected the 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.

$ 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.

[app:main]
factory = helloworld:make_wsgi_app

metrics.tagging = true
metrics.log_if_unconfigured = true

db.url = sqlite:///

[server:main]
factory = baseplate.server.wsgi

To wire up the database, all we need is to add a SQLAlchemy 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.

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

$ 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:

$ 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.