Servers and Configuration files =============================== In this chapter, we'll lay the foundation for using Baseplate.py in the service. Install Baseplate.py -------------------- First off, let's install Baseplate.py in your virtual environment so we can start using its components. .. code-block:: console $ pip install baseplate In the previous chapter, we made our service run its own HTTP/WSGI server. Now we're going to use Baseplate.py's server instead which is run with :program:`baseplate-serve`. .. code-block:: console $ baseplate-serve usage: baseplate-serve [-h] [--debug] [--reload] [--app-name NAME] [--server-name NAME] [--bind ENDPOINT] config_file baseplate-serve: error: the following arguments are required: config_file Uh oh! ``config_file``!? I guess we have got some more to do first. A configuration file -------------------- Baseplate services rely on configuration to allow them to behave differently in different environments (development, staging, production, etc.). For Baseplate.py, configuration is stored in a file in standard Python INI file format as understood by :py:mod:`configparser`. Open a new ``helloworld.ini`` in the tutorial directory and copy this into it: .. literalinclude:: helloworld.ini :language: ini Breaking it down, there are two sections to this configuration file, ``[app:main]`` and ``[server:main]``. .. literalinclude:: helloworld.ini :language: ini :start-at: [app:main] :end-before: [server:main] The first section defines the entrypoint and settings for the application itself. The ``factory`` is a function that returns an application object. In this case, it lives in the Python module ``helloworld`` and the function is called ``make_wsgi_app``. .. literalinclude:: helloworld.ini :language: ini :start-at: [server:main] The second section defines what kind of server we'll run and the settings for that server. Since our application is built for HTTP/WSGI, we use the WSGI server in Baseplate.py. .. note:: You might notice that both application and server sections have ``:main`` in their names. By default, Baseplate.py tools like :program:`baseplate-serve` will look for the sections with ``main`` in them, but you can override this with ``--app-name=foo`` to look up ``[app:foo]`` or ``--server-name`` similarly. This allows you to have multiple applications and servers defined in the same configuration file. OK! Now let's try :program:`baseplate-serve` with our configuration file. .. code-block:: console $ baseplate-serve helloworld.ini Traceback (most recent call last): File "/home/user/tutorial/venv/bin/baseplate-serve", line 14, in load_app_and_run_server() File "/home/user/tutorial/venv/lib/python3.7/site-packages/baseplate/server/__init__.py", line 226, in load_app_and_run_server app = make_app(config.app) File "/home/user/tutorial/venv/lib/python3.7/site-packages/baseplate/server/__init__.py", line 180, in make_app return factory(app_config) TypeError: make_wsgi_app() takes 0 positional arguments but 1 was given It looks like we'll need a little bit more. Run the service with :program:`baseplate-serve` ----------------------------------------------- In the previous section, we learned that the ``[app:main]`` section both tells :program:`baseplate-serve` where to find the application *and* holds configuration for that application. The function that we specify in ``factory`` needs to take a dictionary of the raw configuration values as an argument. Let's add that to our service. .. literalinclude:: helloworld.py :language: python :emphasize-lines: 10-11 All we had to do was add one parameter. We also pass it through to Pyramid's Configurator so any :ref:`framework-specific settings ` can be picked up. Since we're not using the :py:mod:`wsgiref` server anymore, we can drop the whole ``if __name__ == "__main__":`` section at the end of the file now. Alright, third time's the charm, right? .. code-block:: console $ baseplate-serve --debug helloworld.ini {"message": "No metrics client configured. Server metrics will not be sent.", ... {"message": "Listening on ('127.0.0.1', 9090), PID:2303772", ... Success! The ``--debug`` flag will turn on some extra log messages, so we can see a request log when we try hitting the service with :program:`curl` again. .. code-block:: console $ curl localhost:9090 {"Hello": "World"} And something shows up in the server's logs: .. code-block:: none {"message": "127.0.0.1 - - [2021-03-02 15:05:46] \"GET / HTTP/1.1\" 200 126 0.002916", ... You'll notice the logs look a bit different from before. In fact, they're quite a bit longer (though we've truncated a lot in the examples above.) :program:`baseplate-serve` adds some extra info to help give context to your log entries and structures them as JSON so the log entries can be parsed and indexed. Summary ------- We have now made a configuration file and made it possible to run our service with :program:`baseplate-serve`. So what did any of this do for us? :program:`baseplate-serve` is how we let production infrastructure run our application and interact with it. It knows how to process multiple requests simultaneously and will handle things like the infrastructure asking it to gracefully shut down. But the real fun of Baseplate.py comes when we start using its framework integration to get some visibility into the guts of the application. Let's see what that looks like in the next chapter.