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.