Skip to content

Commit

Permalink
Merge ccbd1b2 into 3ec45b6
Browse files Browse the repository at this point in the history
  • Loading branch information
osantana committed Jan 31, 2019
2 parents 3ec45b6 + ccbd1b2 commit feb7acd
Show file tree
Hide file tree
Showing 34 changed files with 894 additions and 455 deletions.
2 changes: 1 addition & 1 deletion .ci/pre.linux.sh
Original file line number Diff line number Diff line change
@@ -1 +1 @@
echo "There's no need to execute anything before running"
echo "There's no need to execute anything before running"
2 changes: 1 addition & 1 deletion .ci/pre.osx.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
echo "Use pyenv rehash before executing tests"
pyenv rehash
pyenv rehash
24 changes: 5 additions & 19 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,22 @@ sudo: false

matrix:
include:
- os: linux
python: 2.7
- os: linux
python: 3.4
- os: linux
python: 3.5
- os: linux
python: pypy
- os: osx
language: generic
env:
- PYTHON_VERSION=2.7.10
- PYENV_ROOT=~/.pyenv
- PATH=$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH
- os: osx
language: generic
env:
- PYTHON_VERSION=3.4.3
- PYENV_ROOT=~/.pyenv
- PATH=$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH
python: 3.6
- os: linux
python: pypy3
- os: osx
language: generic
env:
- PYTHON_VERSION=3.5.0
- PYTHON_VERSION=3.5.6
- PYENV_ROOT=~/.pyenv
- PATH=$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH
- os: osx
language: generic
env:
- PYTHON_VERSION=pypy-2.6.0
- PYTHON_VERSION=3.6.8
- PYENV_ROOT=~/.pyenv
- PATH=$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH

Expand Down
8 changes: 8 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
2.0.0
=====

- Refactor strategy to find configuration files (thanks to @hernantz)
- Lots of improvements on documentation
- Dropped support for py2
- Dropped tox support

1.2.3
=====

Expand Down
174 changes: 174 additions & 0 deletions docs/source/advanced.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
Advanced Usage
--------------


Most of the time you can use the ``prettyconf.config`` function to get your
settings and use the ``prettyconf``'s standard behaviour. But some times
you need to change this behaviour.

To make this changes possible you can always create your own
``Configuration()`` instance and change it's default behaviour:

.. code-block:: python
from prettyconf import Configuration
config = Configuration()
.. warning:: ``prettyconf`` will skip configuration files inside ``.zip``,
``.egg`` or wheel packages.


.. _discovery-customization:

Customizing the configuration discovery
+++++++++++++++++++++++++++++++++++++++

By default the library will use the envrionment and the directory of the file
where ``config()`` was called as the start directory to look for a ``.env``
configuration file. Consider the following file structure:

.. code-block:: text
project/
app/
.env
config.ini
settings.py
If you call ``config()`` from ``project/app/settings.py`` the library will
inspect the envrionment and then look for configuration files at
``project/app``.

You can change that behaviour, by customizing configuration loaders to look at
a different ``path`` when instantiating your ``Configuration()``:

.. code-block:: python
# Code example in project/app/settings.py
import os
from prettyconf import Configuration
from prettyconf.loaders import Environment, EnvFile
project_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
env_file = f"{project_path}/.env"
config = Configuration(loaders=[Environment(), EnvFile(filename=env_file)])
The example above will start looking for configuration in the environment and
then in a ``.env`` file at ``project/`` instead of ``project/app``.

Because ``config`` is nothing but an already instantiated ``Configuration`` object,
you can also alter this ``loaders`` attribute in ``prettyconf.config`` before use it:

.. code-block:: python
# Code example in project/app/settings.py
import os
from prettyconf import config
from prettyconf.loaders import Environment, EnvFile
project_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
env_file = f"{project_path}/.env"
config.loaders = [Environment(), EnvFile(filename=env_file)]
Read more about how loaders can be configured in the :doc:`loaders section<loaders>`.

.. _variable-naming:

Naming conventions for variables
++++++++++++++++++++++++++++++++

There happen to be some formating conventions for configuration paramenters
based on where they are set. For example, it is common to name environment
variables in uppercase:

.. code-block:: sh
$ DEBUG=yes OTHER_CONFIG=10 ./app.py
but if you were to set this config in an ``.ini`` file, it should probably be
in lower case:

.. code-block:: ini
[settings]
debug=yes
other_config=10
command line argments have yet another conventions:

.. code-block:: sh
$ ./app.py --debug=yes --another-config=10
Prettyconf let's you follow these aesthetics patterns by setting a
``var_format`` function when instantiating the :doc:`loaders<loaders>`.

By default, the :py:class:`Environment<prettyconf.loaders.Environment>` is
instantiated with ``var_format=str.upper`` so that lookups play nice with the
env variables.

.. code-block:: python
from prettyconf import Configuration
from prettyconf.loaders import Environment
config = Configuration(loaders=[Environment(var_format=str.upper)])
debug = config('debug', default=False, cast=config.boolean) # lookups for DEBUG=[yes|no]
Writing your own loader
+++++++++++++++++++++++

If you need a custom loader, you should just extend the :py:class:`AbstractConfigurationLoader<prettyconf.loaders.AbstractConfigurationLoader>`.

.. autoclass:: prettyconf.loaders.AbstractConfigurationLoader

For example, say you want to write a Yaml loader. It is important to note
that by raising a ``KeyError`` exception from the loader, prettyconf knows
that it has to keep looking down the loaders chain for a specific config.

.. code-block:: python
import yaml
from prettyconf.loaders import AbstractConfigurationLoader
class YamlFile(AbstractConfigurationLoader):
def __init__(self, filename):
self.filename = filename
self.config = None
def _parse(self):
if self.config is not None:
return
with open(self.filename, 'r') as f:
self.config = yaml.load(f)
def __contains__(self, item):
try:
self._parse()
except:
return False
return item in self.config
def __getitem__(self, item):
try:
self._parse()
except:
# KeyError tells prettyconf to keep looking elsewhere!
raise KeyError("{!r}".format(item))
return self.config[item]
Then configure prettyconf to use it.

.. code-block:: python
from prettyconf import config
config.loaders = [YamlFile('config.yml')]
10 changes: 10 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Changelog
---------

All notable changes to this project will be documented in this file.

This project adheres to `Semantic Versioning`_.

.. _`Semantic Versioning`: https://semver.org/spec/v2.0.0.html

.. include:: ../../CHANGES.txt
7 changes: 5 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath('../..'))

# -- General configuration ------------------------------------------------

Expand All @@ -32,7 +32,10 @@
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = []
extensions = ['sphinx.ext.autodoc']

# Both the class’ and the __init__ method’s docstring are concatenated and inserted.
autoclass_content = 'both'

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
Expand Down
49 changes: 36 additions & 13 deletions docs/source/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ But this code have some issues:
file that will be used if `DEBUG` *envvar* is not defined.


Is prettyconf tied to Django_ or Flask_?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

No, prettyconf was designed to be framework agnostic, be it for the web or cli
applications.

.. _`Django`: https://www.djangoproject.com/
.. _`Flask`: http://flask.pocoo.org/


What is the difference between prettyconf and python-decouple_?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand All @@ -35,6 +45,11 @@ highly inspired in ``python-decouple`` and provides almost the same API.
The implementation of ``prettyconf`` is more extensible and flexible to make
behaviour configurations easier.

You can use any of them. Both are good libraries and provides a similar set of
features.

.. _`python-decouple`: https://github.com/henriquebastos/python-decouple


Why you created a library similar to python-decouple instead of use it?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -45,25 +60,33 @@ incompatible, so, it could break software that relies on the old behaviour.
Besides that it's hard to make this change on ``python-decouple`` due to
the way it's implemented.

See the lookup order of configurations below::
See the lookup order of configurations below

+---------------+-----------------+------------------------+-------------------------+
| Lookup Order | prettyconf | python-decouple (<3.0) | python-decouple (>=3.0) |
+---------------+-----------------+------------------------+-------------------------+
| 1 | ENVVAR | .env | ENVVAR |
| 2 | .env | settings.ini | .env |
| 3 | *.cfg or *.ini | ENVVAR | settings.ini |
+---------------+-----------------+------------------------+-------------------------+
+---------------+------------------+------------------------+-------------------------+
| Lookup Order | prettyconf | python-decouple (<3.0) | python-decouple (>=3.0) |
+===============+==================+========================+=========================+
| 1 | ENVVAR | .env | ENVVAR |
+---------------+------------------+------------------------+-------------------------+
| 2 | .env | settings.ini | .env |
+---------------+------------------+------------------------+-------------------------+
| 3 | \*.cfg or \*.ini | ENVVAR | settings.ini |
+---------------+------------------+------------------------+-------------------------+

.. _some: https://github.com/henriquebastos/python-decouple/pull/4
.. _contributions: https://github.com/henriquebastos/python-decouple/pull/5


Why use ``prettyconf`` instead of ``python-decouple``?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
How does prettyconf compare to python-dotenv?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can use any of them. Both are good libraries and provides a similar set of
features.
python-dotenv_ reads the key, value pair from .env file and adds them to
environment variable. It is good for some tools that simply proxy the env to
some other process, like docker-compose_ or pipenv_.

On the other hand, prettyconf does not populate the ``os.environ`` dictionary,
because it is designed to discover configuration from diferent sources, the
environment being just one of them.

.. _`python-decouple`: https://github.com/henriquebastos/python-decouple
.. _`python-dotenv`: https://github.com/theskumar/python-dotenv
.. _`pipenv`: https://pipenv.readthedocs.io/en/latest/advanced/#automatic-loading-of-env
.. _`docker-compose`: https://docs.docker.com/compose/env-file/
3 changes: 3 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ Contents:
introduction.rst
installation.rst
usage.rst
advanced.rst
loaders.rst
faq.rst
changelog.rst


Indices and tables
Expand Down
37 changes: 34 additions & 3 deletions docs/source/introduction.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
What's prettyconf
-----------------

Pretty Conf is a Python library created to make easy the separation of
configuration and code following the recomendations of `12 Factor`_'s topic
about configs.
Prettyconf is a framework agnostic python library created to make easy the
separation of configuration and code following the recomendations of `12
Factor`_'s topic about configs.


Motivation
++++++++++

Configuration is just another API of you app, aimed for users who will install
and run it, that allows them to *preset* the state of a program, without having
to interact with it, only through static files or environment variables.

It is an important aspect of the architecture of any system, yet it is
sometimes overlooked.

It is important to provide a clear separation of configuration and code. This
is because config varies substantially across deploys and executions, code
should not. The same code can be run inside a container or in a regular
machine, it can be executed in production or in testing environments.

Well designed applications allow different ways to be configured. A proper
settings-discoverability chain goes as follows:

1. First CLI args are checked.
2. Then Environment variables.
3. Config files in different directories, that also imply some hierarchy. For
example: config files in ``/etc/myapp/settings.ini`` are applied
system-wide, while ``~/.config/myapp/settings.ini`` take precedence and are
user-specific.
4. Hardcoded constants.

This raises the need to consolidate configuration in a single source of truth
to avoid having config management scattered all over the codebase.


.. _`12 Factor`: http://12factor.net/
Loading

0 comments on commit feb7acd

Please sign in to comment.