From 6e473687da326951384a4ec8f49875973ff014a3 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Thu, 21 Apr 2022 05:39:37 +0200 Subject: [PATCH] :spiral_notepad: Keep improving the docs --- docs/index.rst | 20 ++++-- docs/quickstart.rst | 167 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 179 insertions(+), 8 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index a1f0232..bae91a5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,20 +8,26 @@ The :mod:`cfig` package provides a simple but powerful configuration manager for Pitch ===== -Ever had trouble making your application configurable? -Ever fought with global objects which impeded code reuse? -Ever had a confused user ask you how to configure your application? -In that case, :mod:`cfig` is for you! +| Ever had trouble making your application configurable? +| Ever fought with global objects which impeded code reuse? +| Ever had a confused user ask you how to configure your application? +| In that case, :mod:`cfig` is for you! Minimal example =============== -.. image:: example-definition.png +.. figure:: example-definition.png -.. image:: example-usage.png + An example configuration value is defined. -.. image:: example-configuration.png +.. figure:: example-usage.png + + The previously defined configuration value is used in the program. + +.. figure:: example-configuration.png + + An user configuring the program previews the current configuration. Table of contents diff --git a/docs/quickstart.rst b/docs/quickstart.rst index a78946c..4bf5e69 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -12,16 +12,181 @@ This page describes how to use :mod:`cfig` in an application. Creating a configuration module =============================== -.. todo:: +First, create a new ``.py`` file inside your package with the following contents: + +.. code-block:: python + :emphasize-lines: 1,2,4 + + import cfig + import typing as t + + config = cfig.Configuration() + +This will: + +#. Import :mod:`cfig` into your module +#. Import :mod:`typing` into your module and alias it as ``t`` for ease of use +#. Create a new :class:`~cfig.config.Configuration` with the default parameters, which will be able to be configured from `environment variables`_ and from environment files (files whose path is specified in an environment variable suffixed with ``_FILE``) + +.. _environment variables: https://wiki.archlinux.org/title/Environment_variables Creating configurable variables =============================== +Basics +------ + +To make use of :mod:`cfig`, you'll need to create one or more configurable variables in your module file: + +.. code-block:: python + :emphasize-lines: 6,7,8,9 + + import cfig + import typing as t + + config = cfig.Configuration() + + @config.required() + def SECRET_PASSWORD(val: str) -> str: + """The secret password required to use this application!""" + return val + +The newly added lines create a new configurable value named ``SECRET_PASSWORD``: + +* the **name** of the function is used as :term:`key` of the configurable value; +* the ``@config.required()`` **decorator** marks the value as required, preventing your application from launching if it is not set; +* the **function parameters** consist of a single :class:`str` parameter named ``val``, which is the string read from the environment variable having the same name of the function; +* the **docstring** defines the meaning of the configuration value in natural language; +* the **contents of the function** are used to process the input string into more refined Python objects; +* the **return annotation** of the function is used to let IDEs know what type this configuration value will be. + .. todo:: + Maybe say that it is called a :term:`resolver`? + + +Optional +-------- + +Configuration values can be optional: + +.. code-block:: python + :emphasize-lines: 11,12,13,14,15,16 + + import cfig + import typing as t + + config = cfig.Configuration() + + @config.required() + def SECRET_PASSWORD(val: str) -> str: + """The secret password required to use this application!""" + return val + + @config.optional() + def SECRET_USERNAME(val: t.Optional[str]) -> str: + """The username to require users to login as. If unset, defaults to `root`.""" + if val is None: + return "root" + return val + +Optional values differ from required ones in their decorator and signature: + +#. The decorator is ``@config.optional()`` instead of ``@config.required()``; +#. Since the passed ``val`` can be :data:`None`, it is given a signature of :data:`typing.Optional`. + + +Processing +---------- + +.. todo:: + + A few words about value processing. + +.. code-block:: python + :emphasize-lines: 18,19,20,21,22,23,24 + + import cfig + import typing as t + + config = cfig.Configuration() + + @config.required() + def SECRET_PASSWORD(val: str) -> str: + """The secret password required to use this application!""" + return val + + @config.optional() + def SECRET_USERNAME(val: t.Optional[str]) -> str: + """The username to require users to login as. If unset, defaults to `root`.""" + if val is None: + return "root" + return val + + @config.required() + def MAX_USERS(val: str) -> int: + """The maximum number of users that will be able to login to this application.""" + try: + return int(val) + except ValueError: + raise cfig.InvalidValueError("Not an int.") + +.. todo:: + + A few words about slower resolvers. + Adding CLI support ================== .. todo:: + + What is the CLI and why is it useful? + +.. code-block:: python + :emphasize-lines: 26,27 + + import cfig + import typing as t + + config = cfig.Configuration() + + @config.required() + def SECRET_PASSWORD(val: str) -> str: + """The secret password required to use this application!""" + return val + + @config.optional() + def SECRET_USERNAME(val: t.Optional[str]) -> str: + """The username to require users to login as. If unset, defaults to `root`.""" + if val is None: + return "root" + return val + + @config.required() + def MAX_USERS(val: str) -> int: + """The maximum number of users that will be able to login to this application.""" + try: + return int(val) + except ValueError: + raise cfig.InvalidValueError("Not an int.") + + if __name__ == "__main__": + config.cli() + +.. todo:: + + What will be displayed here? + + +Use the configuration +===================== + +.. todo:: + + How do I use the created values in my application? + +.. todo:: + + Why does ``is None`` not work? \ No newline at end of file