Skip to content

Commit

Permalink
Update documentation
Browse files Browse the repository at this point in the history
This updates a bunch of documentation by reducing language confusion
and cleaning up example code.
  • Loading branch information
willkg committed Jul 27, 2021
1 parent bc18108 commit 654b6d9
Show file tree
Hide file tree
Showing 13 changed files with 316 additions and 100 deletions.
109 changes: 76 additions & 33 deletions README.rst
Expand Up @@ -62,10 +62,43 @@ You set up a configuration manager like this::
and use it to get configuration values like this::

api_host = config("api_host")
max_bytes = config("max_bytes", default="1000", parser=int)
debug_mode = config("debug", default="False", parser=bool)

max_bytes = config("max_bytes", default=1000, parser=int)

debug_mode = config("debug", default="False", parser=bool)
You can create a basic configuration that points to your documentation
like this::

config = ConfigManager.basic_config(
doc="Check https://example.com/configuration for docs."
)


If the user sets ``DEBUG`` with a bad value, they get a helpful error message
with the documentation for the configuration option and the ``ConfigManager``::

$ DEBUG=foo python myprogram.py
<traceback>
DEBUG requires a value parseable by bool
DEBUG docs: Set to True for debugmode; False for regular mode.
Project docs: Check https://example.com/configuration for docs.


You can use ``config_override`` in your tests to test various configuration
values::

from everett.manager import config_override

from myapp import debug_mode


def test_debug_on():
with config_override(DEBUG="on"):
assert debug_mode == True

def test_debug_off():
with config_override(DEBUG="off"):
assert debug_mode == False


When you outgrow that or need different variations of it, you can switch to
Expand All @@ -92,77 +125,80 @@ environments::

Then set up the ``ConfigManager``::

# myapp.py

import os
import sys

from everett.ext.inifile import ConfigIniEnv
from everett.manager import ConfigManager, ConfigOSEnv


def build_config_manager():
return ConfigManager(
# Specify one or more configuration environments in
# the order they should be checked
environments=[
# Look in OS process environment first
ConfigOSEnv(),
CONFIG = ConfigManager(
# Specify one or more configuration environments in
# the order they should be checked
environments=[
# Look in OS process environment first
ConfigOSEnv(),

# Look in INI files in order specified
ConfigIniEnv(
possible_paths=[
os.environ.get("MYAPP_INI"),
"~/.myapp.ini",
"/etc/myapp.ini"
]
),
],
# Look in INI files in order specified
ConfigIniEnv(
possible_paths=[
os.environ.get("MYAPP_INI"),
"~/.myapp.ini",
"/etc/myapp.ini"
]
),
],

# Provide users a link to documentation for when they hit
# configuration errors
doc="Check https://example.com/configuration for docs."
)
# Provide users a link to documentation for when they hit
# configuration errors
doc="Check https://example.com/configuration for docs."
)


Then use it::

# myapp.py continued

def is_debug(config):
return config(
"debug", default="False", parser=bool,
"debug",
default="False",
parser=bool,
doc="Set to True for debugmode; False for regular mode."
)

config = build_config_manager()

if is_debug(config):
if is_debug(CONFIG):
print('DEBUG MODE ON!')


Let's write some tests that verify behavior based on the ``debug``
configuration value::

from myapp import get_config, is_debug
from myapp import CONFIG, is_debug

from everett.manager import config_override


@config_override(DEBUG="true")
def test_debug_true():
assert is_debug(get_config()) is True
assert is_debug(CONFIG) is True


def test_debug_false():
with config_override(DEBUG="false"):
assert is_debug(get_config()) is False
assert is_debug(CONFIG) is False


If the user sets ``DEBUG`` with a bad value, they get a helpful error message
with the documentation for the configuration option and the ``ConfigManager``::

$ DEBUG=foo python myprogram.py
<traceback>
namespace=None key=debug requires a value parseable by bool
Set to True for debugmode; False for regular mode.
Check https://example.com/configuration for docs.
DEBUG requires a value parseable by bool
DEBUG docs: Set to True for debugmode; False for regular mode.
Project docs: Check https://example.com/configuration for docs.


Configuration classes
Expand All @@ -174,6 +210,8 @@ a class. Let's rewrite the above example using a configuration class.

First, create a configuration class::

# myapp.py

import os
import sys

Expand All @@ -191,7 +229,10 @@ First, create a configuration class::

Then we set up a ``ConfigManager`` to look at the process environment
for configuration::
for configuration and bound to the configuration options specified in
``AppConfig``::

# myapp.py continued

def get_config():
manager = ConfigManager(
Expand All @@ -213,6 +254,8 @@ for configuration::

Then use it::

# myapp.py continued

config = get_config()

if config("debug"):
Expand Down
65 changes: 65 additions & 0 deletions docs/api.rst
@@ -0,0 +1,65 @@
===
API
===

This is the API of functions and classes in Everett.

Configuration things:

* :py:class:`everett.manager.ConfigManager`
* :py:class:`everett.manager.Option`

Utility functions:

* :py:class:`everett.manager.config_override`
* :py:class:`everett.manager.generate_uppercase_key`
* :py:class:`everett.manager.get_config_for_class`
* :py:class:`everett.manager.get_runtime_config`
* :py:class:`everett.manager.qualname`

Configuration environments:

* :py:class:`everett.manager.ConfigObjEnv`
* :py:class:`everett.manager.ConfigDictEnv`
* :py:class:`everett.manager.ConfigEnvFileEnv`
* :py:class:`everett.manager.ConfigOSEnv`
* (INI) :py:class:`everett.ext.inifile.ConfigIniEnv`
* (YAML) :py:class:`everett.ext.yamlfile.ConfigYamlEnv`

Errors:

* :py:class:`everett.ConfigurationError`
* :py:class:`everett.InvalidKeyError`
* :py:class:`everett.ConfigurationMissingError`
* :py:class:`everett.InvalidValueError`

Parsers:

* :py:func:`everett.manager.parse_class`
* :py:func:`everett.manager.ListOf`


everett
=======

.. automodule:: everett
:members:

everett.manager
===============

.. automodule:: everett.manager
:members:


everett.ext.inifile
===================

.. automodule:: everett.ext.inifile
:members:

everett.ext.yamlfile
====================

.. automodule:: everett.ext.yamlfile
:members:
7 changes: 1 addition & 6 deletions docs/code/recipes_alternate_keys.py
Expand Up @@ -20,12 +20,7 @@ def __init__(self, config):


# Define a shared configuration
config = ConfigManager.from_dict(
{
"DB_USERNAME": "foo",
"DB_PASSWORD": "bar"
}
)
config = ConfigManager.from_dict({"DB_USERNAME": "foo", "DB_PASSWORD": "bar"})

reader = DatabaseReader(config.with_namespace("reader"))
assert reader.config("username") == "foo"
Expand Down
5 changes: 4 additions & 1 deletion docs/code/recipes_appconfig.py
Expand Up @@ -32,7 +32,10 @@ class Config:
loglevel = Option(
parser=parse_loglevel,
default="INFO",
doc=("Log level for the application; CRITICAL, ERROR, WARNING, INFO, " "DEBUG"),
doc=(
"Log level for the application; CRITICAL, ERROR, WARNING, INFO, "
"DEBUG"
),
)


Expand Down
7 changes: 7 additions & 0 deletions docs/conf.py
Expand Up @@ -126,6 +126,13 @@
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False

autodoc_typehints = "description"
autoclass_content = "both"
autodoc_default_options = {
"class-doc-from": "both",
"member-order": "bysource",
"inheireted-members": True,
}

# -- Options for HTML output ----------------------------------------------

Expand Down
10 changes: 7 additions & 3 deletions docs/configuration.rst
Expand Up @@ -158,7 +158,7 @@ environment and should return ``NO_VALUE`` if and only if the key does not
exist in that environment.

For exceptions, it depends on what you want to have happen. It's ok to let
exceptions go unhandled--Everett will wrap them in a ``ConfigurationError``.
exceptions go unhandled--Everett will wrap them in a :py:class:`everett.ConfigurationError`.
If your environment promises never to throw an exception, then you should
handle them all and return ``NO_VALUE`` since with that promise all exceptions
would indicate the key is not in the environment.
Expand All @@ -184,7 +184,7 @@ Some more examples:

There is no default value provided so if "password" isn't provided in any of
the configuration sources, then this will raise a
``everett.ConfigurationError``.
:py:class:`everett.ConfigurationError`.

This is what you want to do to require that a configuration value exist.

Expand Down Expand Up @@ -224,7 +224,7 @@ Some more examples:

There's no default, so if there's no "username" in namespace "db"
configuration variable set in the sources, this will raise a
``everett.ConfigurationError``.
:py:class:`everett.ConfigurationError`.

If you're looking up values in the process environment, then the full
key would be ``DB_USERNAME``.
Expand Down Expand Up @@ -258,12 +258,16 @@ Some more examples:


.. autoclass:: everett.ConfigurationError
:noindex:

.. autoclass:: everett.InvalidValueError
:noindex:

.. autoclass:: everett.ConfigurationMissingError
:noindex:

.. autoclass:: everett.InvalidKeyError
:noindex:


Handling exceptions when extracting values
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Expand Up @@ -12,8 +12,8 @@ Contents
documenting
testing
recipes
api
history
library
dev


Expand Down
9 changes: 0 additions & 9 deletions docs/library.rst

This file was deleted.

11 changes: 6 additions & 5 deletions setup.py
Expand Up @@ -30,12 +30,13 @@ def get_file(fn):
"black==20.8b1",
"check-manifest==0.46",
"flake8==3.9.2",
"mypy==0.902",
"mypy==0.910",
"pytest==6.2.4",
"Sphinx==4.0.2",
"tox==3.23.1",
"twine==3.4.1",
"types-PyYAML==0.1.7",
"Sphinx==4.1.2",
"sphinx_rtd_theme==0.5.2",
"tox==3.24.0",
"twine==3.4.2",
"types-PyYAML==5.4.3",
],
}

Expand Down
5 changes: 5 additions & 0 deletions src/everett/ext/inifile.py
Expand Up @@ -125,6 +125,11 @@ class ConfigIniEnv:
"""

def __init__(self, possible_paths: Union[str, List[str]]) -> None:
"""
:param possible_paths: either a single string with a file path (e.g.
``"/etc/project.ini"`` or a list of strings with file paths
"""
self.cfg = {}
self.path = None
possible_paths = listify(possible_paths)
Expand Down
5 changes: 5 additions & 0 deletions src/everett/ext/yamlfile.py
Expand Up @@ -110,6 +110,11 @@ class ConfigYamlEnv:
"""

def __init__(self, possible_paths: Union[str, List[str]]) -> None:
"""
:param possible_paths: either a single string with a file path (e.g.
``"/etc/project.yaml"`` or a list of strings with file paths
"""
self.cfg = {}
self.path = None
possible_paths = listify(possible_paths)
Expand Down

0 comments on commit 654b6d9

Please sign in to comment.