Add narrative docs for app configuration
This commit is contained in:
		
							parent
							
								
									f9a7b41f94
								
							
						
					
					
						commit
						4641e24afd
					
				
					 11 changed files with 596 additions and 47 deletions
				
			
		
							
								
								
									
										202
									
								
								docs/narr/config/files.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								docs/narr/config/files.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,202 @@
 | 
			
		|||
 | 
			
		||||
Config Files
 | 
			
		||||
============
 | 
			
		||||
 | 
			
		||||
A :term:`config file` is just a text file with :term:`config
 | 
			
		||||
settings<config setting>`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Basic Syntax
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
Currently only INI-style syntax is supported.  Under the hood a
 | 
			
		||||
:class:`~python:configparser.ConfigParser` instance is used to read
 | 
			
		||||
the files.
 | 
			
		||||
 | 
			
		||||
There is no "type hinting" within the config file itself, although you
 | 
			
		||||
can ask the config object to interpret values according to a specific
 | 
			
		||||
type.  See also :ref:`reading-config-settings`.
 | 
			
		||||
 | 
			
		||||
The basic syntax looks like this:
 | 
			
		||||
 | 
			
		||||
.. code-block:: ini
 | 
			
		||||
 | 
			
		||||
   [myapp]
 | 
			
		||||
   foo = A
 | 
			
		||||
   bar = 2
 | 
			
		||||
   feature = true
 | 
			
		||||
   words = the,quick,brown,fox,"did something unusual"
 | 
			
		||||
   paths =
 | 
			
		||||
       /path/to/first/folder
 | 
			
		||||
       "/path/to/folder with spaces"
 | 
			
		||||
       /another/one   /and/another
 | 
			
		||||
 | 
			
		||||
   [more]
 | 
			
		||||
   things = go here
 | 
			
		||||
 | 
			
		||||
Note that ``words`` and ``paths`` show 2 ways of defining lists, for
 | 
			
		||||
use with :meth:`~wuttjamaican.conf.WuttaConfig.get_list()`.  This
 | 
			
		||||
splits the value by whitespace as well as commas; quotation marks may
 | 
			
		||||
be used to avoid unwanted splits.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Specifying via Command Line
 | 
			
		||||
---------------------------
 | 
			
		||||
 | 
			
		||||
All :term:`commands<command>` accept the ``-c`` or ``--config`` params:
 | 
			
		||||
 | 
			
		||||
.. code-block:: sh
 | 
			
		||||
 | 
			
		||||
   wutta --config=myapp.conf
 | 
			
		||||
 | 
			
		||||
   wutta -c first.conf -c second.conf
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Specifying via Environment Variable
 | 
			
		||||
-----------------------------------
 | 
			
		||||
 | 
			
		||||
Probably most useful for command line scripts etc.  Note that if the
 | 
			
		||||
command line itself specifies ``-c`` or ``--config`` then the
 | 
			
		||||
environment variables are ignored.
 | 
			
		||||
 | 
			
		||||
.. code-block:: sh
 | 
			
		||||
 | 
			
		||||
   WUTTA_CONFIG_FILES=myapp.conf
 | 
			
		||||
 | 
			
		||||
   WUTTA_CONFIG_FILES=first.conf:second.conf
 | 
			
		||||
 | 
			
		||||
The env variable name used will depend on the :term:`app name`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Specifying via Python
 | 
			
		||||
---------------------
 | 
			
		||||
 | 
			
		||||
Pass the files directly to :func:`~wuttjamaican.conf.make_config()`::
 | 
			
		||||
 | 
			
		||||
   make_config('myapp.conf')
 | 
			
		||||
 | 
			
		||||
   make_config(['first.conf', 'second.conf'])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
File Priority
 | 
			
		||||
-------------
 | 
			
		||||
 | 
			
		||||
If multiple config files are used then the sequence will matter in
 | 
			
		||||
terms of value lookup.  Effectively, whenever
 | 
			
		||||
:meth:`~wuttjamaican.conf.WuttaConfig.get()` is called on the config
 | 
			
		||||
object, each file will be searched until a value is found.
 | 
			
		||||
 | 
			
		||||
For example let's say you have 3 config files:
 | 
			
		||||
 | 
			
		||||
* ``app.conf`` ("most specific to the app")
 | 
			
		||||
* ``machine.conf`` ("less specific to the app")
 | 
			
		||||
* ``site.conf`` ("least specific to the app")
 | 
			
		||||
 | 
			
		||||
To ensure that sequence you must specify the files in that order (*),
 | 
			
		||||
e.g. via command line:
 | 
			
		||||
 | 
			
		||||
.. code-block:: sh
 | 
			
		||||
 | 
			
		||||
   wutta -c app.conf -c machine.conf -c site.conf
 | 
			
		||||
 | 
			
		||||
or via Python::
 | 
			
		||||
 | 
			
		||||
   config = make_config(['app.conf', 'machine.conf', 'site.conf'])
 | 
			
		||||
 | 
			
		||||
(*) Actually that isn't always true, but for now let's pretend.
 | 
			
		||||
 | 
			
		||||
That way, if both ``app.conf`` and ``site.conf`` have a particular
 | 
			
		||||
setting defined, the value from ``app.conf`` will "win" and the value
 | 
			
		||||
from ``site.conf`` is simply ignored.
 | 
			
		||||
 | 
			
		||||
The sequence of files actually read into the config object may be
 | 
			
		||||
confirmed by inspecting either
 | 
			
		||||
:attr:`~wuttjamaican.conf.WuttaConfig.files_read` or (for typical
 | 
			
		||||
setups) the log file.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Including More Files
 | 
			
		||||
--------------------
 | 
			
		||||
 | 
			
		||||
When :func:`~wuttjamaican.conf.make_config()` is called, it first
 | 
			
		||||
determines the set of config files based on caller params etc.  It
 | 
			
		||||
then gives that set of files to the
 | 
			
		||||
:class:`~wuttjamaican.conf.WuttaConfig` constructor.
 | 
			
		||||
 | 
			
		||||
But when these files are actually read into the config object, they
 | 
			
		||||
can in turn "include" (or "require") additional files.
 | 
			
		||||
 | 
			
		||||
For example let's again say you have these 3 config files:
 | 
			
		||||
 | 
			
		||||
* ``app.conf``
 | 
			
		||||
* ``machine.conf``
 | 
			
		||||
* ``site.conf``
 | 
			
		||||
 | 
			
		||||
In the previous section we mentioned you could request all 3 files in
 | 
			
		||||
the correct order:
 | 
			
		||||
 | 
			
		||||
.. code-block:: sh
 | 
			
		||||
 | 
			
		||||
   wutta -c app.conf -c machine.conf -c site.conf
 | 
			
		||||
 | 
			
		||||
But another, usually better way is to add config settings such as:
 | 
			
		||||
 | 
			
		||||
in ``app.conf``
 | 
			
		||||
 | 
			
		||||
.. code-block:: ini
 | 
			
		||||
 | 
			
		||||
   [wutta.config]
 | 
			
		||||
   include = %(here)s/machine.conf
 | 
			
		||||
 | 
			
		||||
in ``machine.conf``
 | 
			
		||||
 | 
			
		||||
.. code-block:: ini
 | 
			
		||||
 | 
			
		||||
   [wutta.config]
 | 
			
		||||
   include = %(here)s/site.conf
 | 
			
		||||
 | 
			
		||||
And then you need only specify the main file when running the app:
 | 
			
		||||
 | 
			
		||||
.. code-block:: sh
 | 
			
		||||
 | 
			
		||||
   wutta -c app.conf
 | 
			
		||||
 | 
			
		||||
or via Python::
 | 
			
		||||
 | 
			
		||||
   make_config('app.conf')
 | 
			
		||||
 | 
			
		||||
Examples above show the ``include`` syntax but ``require`` is similar:
 | 
			
		||||
 | 
			
		||||
.. code-block:: ini
 | 
			
		||||
 | 
			
		||||
   [wutta.config]
 | 
			
		||||
   require = /path/to/otherfile.conf
 | 
			
		||||
 | 
			
		||||
If an "included" file is missing it will be skipped, but if a
 | 
			
		||||
"required" file is missing an error will be raised.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Default Locations
 | 
			
		||||
-----------------
 | 
			
		||||
 | 
			
		||||
If no config files were specified via any method, then some default
 | 
			
		||||
file paths may be tried as fallback.
 | 
			
		||||
 | 
			
		||||
The actual paths used for defaults will vary based on :term:`app name`
 | 
			
		||||
and other details such as operating system.  But as a simple (and
 | 
			
		||||
incomplete) example, with app name of ``wutta`` running on Linux,
 | 
			
		||||
default paths would include things like:
 | 
			
		||||
 | 
			
		||||
* ``~/.wutta.conf``
 | 
			
		||||
* ``/usr/local/etc/wutta.conf``
 | 
			
		||||
* ``/etc/wutta.conf``
 | 
			
		||||
 | 
			
		||||
While it is hoped that some may find this feature useful, it is
 | 
			
		||||
perhaps better to be explicit about which config files you want the
 | 
			
		||||
app to use.
 | 
			
		||||
 | 
			
		||||
Custom apps may also wish to devise ways to override the logic
 | 
			
		||||
responsible for choosing default paths.
 | 
			
		||||
 | 
			
		||||
For more details see :func:`~wuttjamaican.conf.get_config_paths()` and
 | 
			
		||||
:func:`~wuttjamaican.conf.generic_default_files()`.
 | 
			
		||||
							
								
								
									
										11
									
								
								docs/narr/config/index.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								docs/narr/config/index.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
 | 
			
		||||
Configuration
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 2
 | 
			
		||||
 | 
			
		||||
   overview
 | 
			
		||||
   settings
 | 
			
		||||
   object
 | 
			
		||||
   files
 | 
			
		||||
							
								
								
									
										56
									
								
								docs/narr/config/object.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								docs/narr/config/object.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,56 @@
 | 
			
		|||
 | 
			
		||||
Config Object
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
The app has a global :term:`config object` to track its settings.
 | 
			
		||||
This object is an instance of :class:`~wuttjamaican.conf.WuttaConfig`
 | 
			
		||||
and is usually available as e.g. ``self.config`` within code.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Creating the Config Object
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
All apps create the config object by calling
 | 
			
		||||
:func:`~wuttjamaican.conf.make_config()` during startup.  The desired
 | 
			
		||||
config files may be specified directly via call params, or indirectly
 | 
			
		||||
via environment variables.  (See also :doc:`files`.)
 | 
			
		||||
 | 
			
		||||
In some cases, notably the :term:`command` line interface, there is
 | 
			
		||||
already code in place to handle the ``make_config()`` call, and you
 | 
			
		||||
must specify the config files in another way - command line parameters
 | 
			
		||||
in this case.
 | 
			
		||||
 | 
			
		||||
One-off scripts should create the config object before doing anything
 | 
			
		||||
else.  To be safe this should happen before other modules are
 | 
			
		||||
imported::
 | 
			
		||||
 | 
			
		||||
   from wuttjamaican.conf import make_config
 | 
			
		||||
 | 
			
		||||
   config = make_config()
 | 
			
		||||
 | 
			
		||||
   from otherlib import foo
 | 
			
		||||
 | 
			
		||||
   foo(config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Creating the App Handler
 | 
			
		||||
------------------------
 | 
			
		||||
 | 
			
		||||
The config object is also responsible for creating the :term:`app handler`.
 | 
			
		||||
 | 
			
		||||
Whereas the process of creating the config object is "stable" and
 | 
			
		||||
"always" produces an object of the same class, the app handler is more
 | 
			
		||||
likely to vary.  So while there is a default
 | 
			
		||||
:class:`~wuttjamaican.app.AppHandler` provided, it is expected that
 | 
			
		||||
some apps will want to override that.
 | 
			
		||||
 | 
			
		||||
The relationship between config object and app handler may be thought
 | 
			
		||||
of as "one-to-one" since each app will have a global config object as
 | 
			
		||||
well as a global app handler.  But the config object does come first,
 | 
			
		||||
to solve the "chicken-vs-egg" problem::
 | 
			
		||||
 | 
			
		||||
   from wuttjamaican.conf import make_config
 | 
			
		||||
 | 
			
		||||
   config = make_config()
 | 
			
		||||
 | 
			
		||||
   app = config.get_app()
 | 
			
		||||
							
								
								
									
										36
									
								
								docs/narr/config/overview.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								docs/narr/config/overview.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
 | 
			
		||||
Overview
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
The app uses a global :term:`config object` to keep track of its
 | 
			
		||||
:term:`config settings<config setting>`.  See also :doc:`object`.
 | 
			
		||||
 | 
			
		||||
The app must call :func:`~wuttjamaican.conf.make_config()` during
 | 
			
		||||
startup to obtain the config object.
 | 
			
		||||
 | 
			
		||||
Values come (mostly) from :term:`config files<config file>` and/or the
 | 
			
		||||
:term:`settings table`.  See also :doc:`settings`.
 | 
			
		||||
 | 
			
		||||
Values are always strings in their raw format, as returned by
 | 
			
		||||
:meth:`~wuttjamaican.conf.WuttaConfig.get()`.  But the config object
 | 
			
		||||
also has methods to coerce values to various types, e.g.:
 | 
			
		||||
 | 
			
		||||
* :meth:`~wuttjamaican.conf.WuttaConfig.get_bool()`
 | 
			
		||||
* :meth:`~wuttjamaican.conf.WuttaConfig.get_int()`
 | 
			
		||||
* :meth:`~wuttjamaican.conf.WuttaConfig.get_list()`
 | 
			
		||||
 | 
			
		||||
The config object is also responsible for creating the :term:`app
 | 
			
		||||
handler`::
 | 
			
		||||
 | 
			
		||||
   from wuttjamaican.conf import make_config
 | 
			
		||||
 | 
			
		||||
   config = make_config()
 | 
			
		||||
   app = config.get_app()
 | 
			
		||||
 | 
			
		||||
   if config.get_bool('foo.bar'):
 | 
			
		||||
       print('YES for foo.bar')
 | 
			
		||||
   else:
 | 
			
		||||
       print('NO for foo.bar')
 | 
			
		||||
 | 
			
		||||
   with app.short_session() as session:
 | 
			
		||||
       print(session.bind)
 | 
			
		||||
							
								
								
									
										104
									
								
								docs/narr/config/settings.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								docs/narr/config/settings.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,104 @@
 | 
			
		|||
 | 
			
		||||
Config Settings
 | 
			
		||||
===============
 | 
			
		||||
 | 
			
		||||
The app uses :term:`config settings<config setting>` to control its
 | 
			
		||||
behavior at runtime.
 | 
			
		||||
 | 
			
		||||
The term "config setting" may be thought of as a combination of these
 | 
			
		||||
terms:
 | 
			
		||||
 | 
			
		||||
* :term:`config file`
 | 
			
		||||
* :term:`settings table`
 | 
			
		||||
 | 
			
		||||
It really refers to the **value** of such a config setting, when you
 | 
			
		||||
get right down to it.  The app uses a :term:`config object` to keep
 | 
			
		||||
track of its config settings.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _reading-config-settings:
 | 
			
		||||
 | 
			
		||||
Reading Values via Python
 | 
			
		||||
-------------------------
 | 
			
		||||
 | 
			
		||||
Call the config object's :meth:`~wuttjamaican.conf.WuttaConfig.get()`
 | 
			
		||||
method to retrieve a value based on the setting name.
 | 
			
		||||
 | 
			
		||||
Note that raw values are always strings.  The config object has other
 | 
			
		||||
methods if you want to interpret the value as a particular type::
 | 
			
		||||
 | 
			
		||||
   from wuttjamaican.conf import make_config
 | 
			
		||||
 | 
			
		||||
   config = make_config()
 | 
			
		||||
 | 
			
		||||
   config.get('foo.bar')
 | 
			
		||||
   config.get_int('foo.baz')
 | 
			
		||||
   config.get_bool('foo.feature')
 | 
			
		||||
   config.get_list('foo.words')
 | 
			
		||||
 | 
			
		||||
See :class:`~wuttjamaican.conf.WuttaConfig` for full details.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _where-config-settings-come-from:
 | 
			
		||||
 | 
			
		||||
Where Values Come From
 | 
			
		||||
----------------------
 | 
			
		||||
 | 
			
		||||
Config settings usually come from either a :term:`config file` or a
 | 
			
		||||
:term:`settings table`.  The :term:`config object` is ultimately
 | 
			
		||||
responsible for sorting out which value to return.
 | 
			
		||||
 | 
			
		||||
Technically the app may also specify some fallback/default values; for
 | 
			
		||||
sake of this discussion we'll treat those as if they come from config
 | 
			
		||||
file.
 | 
			
		||||
 | 
			
		||||
All apps are expected to use config file(s), but not all will have a
 | 
			
		||||
settings table.  The config file(s) may specify whether a settings
 | 
			
		||||
table should be used.
 | 
			
		||||
 | 
			
		||||
There are only 2 config settings which control this behavior.  For a
 | 
			
		||||
typical example which enables both:
 | 
			
		||||
 | 
			
		||||
.. code-block:: ini
 | 
			
		||||
 | 
			
		||||
   [wutta.config]
 | 
			
		||||
   usedb = true
 | 
			
		||||
   preferdb = true
 | 
			
		||||
 | 
			
		||||
   [wutta.db]
 | 
			
		||||
   default.url = sqlite://
 | 
			
		||||
 | 
			
		||||
Note that to use a settings table you must of course define a DB
 | 
			
		||||
connection.
 | 
			
		||||
 | 
			
		||||
So the ``usedb`` and ``preferdb`` flags may be set to accomplish any
 | 
			
		||||
of these scenarios:
 | 
			
		||||
 | 
			
		||||
* enable both - settings table is checked first, config files used as
 | 
			
		||||
  fallback
 | 
			
		||||
* enable ``usedb`` but not ``preferdb`` - config files are checked
 | 
			
		||||
  first, settings table used as fallback
 | 
			
		||||
* disable ``usedb`` - config files only; do not use settings table
 | 
			
		||||
 | 
			
		||||
Most apps will want to enable both flags so that when the settings
 | 
			
		||||
table is updated, it will immediately affect app behavior regardless
 | 
			
		||||
of what values are in the config files.
 | 
			
		||||
 | 
			
		||||
The values for these flags is available at runtime as:
 | 
			
		||||
 | 
			
		||||
* :attr:`~wuttjamaican.conf.WuttaConfig.usedb`
 | 
			
		||||
* :attr:`~wuttjamaican.conf.WuttaConfig.preferdb`
 | 
			
		||||
 | 
			
		||||
Regardless of what the "normal" behavior is for the config object (per
 | 
			
		||||
those flags), you can explcitly request other behavior by passing
 | 
			
		||||
similar flags to the config object's
 | 
			
		||||
:meth:`~wuttjamaican.conf.WuttaConfig.get()` method::
 | 
			
		||||
 | 
			
		||||
   config.get('foo.bar', usedb=True, preferdb=True)
 | 
			
		||||
 | 
			
		||||
   config.get('foo.baz', usedb=False)
 | 
			
		||||
 | 
			
		||||
Some of the "core" settings in the framework are fetched with
 | 
			
		||||
``usedb=False`` so they will never be read from the settings table.
 | 
			
		||||
Canonical example of this would be the setting(s) which defines the DB
 | 
			
		||||
connection itself.
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue