pilosus/piny

View on GitHub
README.rst

Summary

Maintainability
Test Coverage
Piny
====

|Logo|

|PyPI| |Coverage| |Downloads| |License|

**Piny** is YAML config loader with environment variables interpolation for Python.

Keep your app's configuration in a YAML file.
Mark up sensitive data in the config as *environment variables*.
Set environment variables on application deployment.
Now let the *piny* load your config and substitute environment variables
in it with their values.

Piny is developed with Docker and Kubernetes in mind,
though it's not limited to any deployment system.


Rationale
---------

Piny combines *readability and versioning* you get when using config files,
and *security* that environment variables provide. Read more about this approach
in the `blog post`_.


Help
----

See `documentation`_ for more details.


Installation
------------

Just run::

  pip install -U piny


Usage
-----

Set your environment variables, add them to your YAML configuration file:

.. code-block:: yaml

    db:
      login: user
      password: ${DB_PASSWORD}
    mail:
      login: user
      password: ${MAIL_PASSWORD:-my_default_password}
    sentry:
      dsn: ${VAR_NOT_SET}

Then load your config:

.. code-block:: python

    from piny import YamlLoader

    config = YamlLoader(path="config.yaml").load()
    print(config)
    # {'db': {'login': 'user', 'password': 'my_db_password'},
    # 'mail': {'login': 'user', 'password': 'my_default_password'},
    # 'sentry': {'dsn': None}}

You may want to discourage Bash-style envs with defaults in your configs.
In such case, use a ``StrictMatcher``:

.. code-block:: python

    from piny import YamlLoader, StrictMatcher

    config = YamlLoader(path="config.yaml", matcher=StrictMatcher).load()

Both strict and default matchers produce ``None`` value if environment variable
matched is not set in the system (and no default syntax used in the case of
default matcher).

Piny also comes with *command line utility* that works both with files and standard
input and output:

.. code-block:: bash

  $ export PASSWORD=mySecretPassword
  $ echo "db: \${PASSWORD}" | piny
  db: mySecretPassword


Validation
----------

Piny supports *optional* data validation using third-party libraries:
`Marshmallow`_, `Pydantic`_, `Trafaret`_.

.. code-block:: python

  import marshmallow as ma
  from piny import MarshmallowValidator, StrictMatcher, YamlLoader

  class DBSchema(ma.Schema):
      login = ma.fields.String(required=True)
      password = ma.fields.String()

  class ConfigSchema(ma.Schema):
      db = ma.fields.Nested(DBSchema)

  config = YamlLoader(
      path="database.yaml",
      matcher=StrictMatcher,
      validator=MarshmallowValidator,
      schema=ConfigSchema,
  ).load(many=False)


Exceptions
----------

``LoadingError`` is thrown when something goes wrong with reading or parsing YAML-file.
``ValidationError`` is a wrapper for exceptions raised by the libraries for optional data validation.
Original exception can be accessed by ``origin`` attribute. It comes in handy when you need more than
just an original exception message (e.g. a dictionary of validation errors).

Both exceptions inherit from the ``ConfigError``.


Best practices
--------------

- Maintain a healthy security/convenience balance for your config

- Mark up entity as an environment variable in your YAML if and only if
  it really is a *secret* (login/passwords, private API keys, crypto keys,
  certificates, or maybe DB hostname too? You decide)

- When loading config file, validate your data.
  Piny supports a few popular data validation tools.

- Store your config files in the version control system along with your app’s code.

- Environment variables are set by whoever is responsible for the deployment.
  Modern orchestration systems like `Kubernetes`_ make it easier to keep envs secure
  (see `Kubernetes Secrets`_).


Fun facts
---------

*Piny* is a recursive acronym for *Piny Is Not YAML*.
Not only it's a library name, but also a name for YAML marked up
with environment variables.


Changelog
---------

See `CHANGELOG.rst`_.


Contributing
------------

See `CONTRIBUTING.rst`_.

.. |PyPI| image:: https://img.shields.io/pypi/v/piny
   :alt: PyPI
   :target: https://pypi.org/project/piny/
.. |Coverage| image:: https://img.shields.io/codecov/c/github/pilosus/piny.svg
   :alt: Codecov
   :target: https://codecov.io/gh/pilosus/piny
.. |License| image:: https://img.shields.io/github/license/pilosus/piny.svg
   :alt: MIT License
   :target: https://github.com/pilosus/piny/blob/master/LICENSE
.. |Logo| image:: https://piny.readthedocs.io/en/latest/_static/piny_logo_noborder.png
   :alt: Piny logo
   :target: https://pypi.org/project/piny/
.. |Downloads| image:: https://img.shields.io/pypi/dm/piny
   :alt: PyPI - Downloads
   :target: https://pypistats.org/packages/piny

.. _blog post: https://blog.pilosus.org/blog/application-configs-files-or-environment-variables-actually-both
.. _future releases: https://github.com/pilosus/piny/issues/2
.. _Kubernetes: https://kubernetes.io/
.. _Kubernetes Secrets: https://kubernetes.io/docs/concepts/configuration/secret/
.. _Pydantic: https://pydantic-docs.helpmanual.io/
.. _Marshmallow: https://marshmallow.readthedocs.io/
.. _Trafaret: https://trafaret.readthedocs.io/
.. _tests: https://github.com/pilosus/piny/tree/master/tests
.. _source code: https://github.com/pilosus/piny/tree/master/piny
.. _coming soon: https://github.com/pilosus/piny/issues/12
.. _CONTRIBUTING.rst: https://github.com/pilosus/piny/tree/master/CONTRIBUTING.rst
.. _CHANGELOG.rst: https://github.com/pilosus/piny/tree/master/CHANGELOG.rst
.. _documentation: https://piny.readthedocs.io/