Metadata-Version: 2.1
Name: goodconf
Version: 4.0.2
Summary: Load configuration variables from a file or environment
Project-URL: homepage, https://github.com/lincolnloop/goodconf/
Project-URL: changelog, https://github.com/lincolnloop/goodconf/blob/main/CHANGES.rst
Author-email: Peter Baumgartner <brett@python.org>
License: Copyright (c) 2018 Lincoln Loop
        
        Permission is hereby granted, free of charge, to any person
        obtaining a copy of this software and associated documentation
        files (the "Software"), to deal in the Software without
        restriction, including without limitation the rights to use,
        copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the
        Software is furnished to do so, subject to the following
        conditions:
        
        The above copyright notice and this permission notice shall be
        included in all copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
        EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
        OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
        NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
        HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
        WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
        FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
        OTHER DEALINGS IN THE SOFTWARE.
License-File: LICENSE
Keywords: config,env,json,toml,yaml
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.8
Requires-Dist: pydantic<2,>=1.7
Provides-Extra: tests
Requires-Dist: django>=3.2.0; extra == 'tests'
Requires-Dist: pytest-cov==4.0.*; extra == 'tests'
Requires-Dist: pytest-mock==3.10.*; extra == 'tests'
Requires-Dist: pytest==7.2.*; extra == 'tests'
Requires-Dist: ruamel-yaml>=0.17.0; extra == 'tests'
Requires-Dist: tomlkit>=0.11.6; extra == 'tests'
Provides-Extra: toml
Requires-Dist: tomlkit>=0.11.6; extra == 'toml'
Provides-Extra: yaml
Requires-Dist: ruamel-yaml>=0.17.0; extra == 'yaml'
Description-Content-Type: text/x-rst

Goodconf
========

.. image:: https://github.com/lincolnloop/goodconf/actions/workflows/test.yml/badge.svg?branch=main&event=push
    :target: https://github.com/lincolnloop/goodconf/actions/workflows/test.yml?query=branch%3Amain+event%3Apush

.. image:: https://results.pre-commit.ci/badge/github/lincolnloop/goodconf/main.svg
    :target: https://results.pre-commit.ci/latest/github/lincolnloop/goodconf/main
    :alt: pre-commit.ci status

.. image:: https://img.shields.io/codecov/c/github/lincolnloop/goodconf.svg
    :target: https://codecov.io/gh/lincolnloop/goodconf

.. image:: https://img.shields.io/pypi/v/goodconf.svg
    :target: https://pypi.python.org/pypi/goodconf

.. image:: https://img.shields.io/pypi/pyversions/goodconf.svg
    :target: https://pypi.python.org/pypi/goodconf

A thin wrapper over `Pydantic's settings management <https://pydantic-docs.helpmanual.io/usage/settings/>`__.
Allows you to define configuration variables and load them from environment or JSON/YAML
file. Also generates initial configuration files and documentation for your
defined configuration.


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

``pip install goodconf`` or ``pip install goodconf[yaml]`` /
``pip install goodconf[toml]`` if parsing/generating YAML/TOML
files is required.


Quick Start
-----------

Let's use configurable Django settings as an example.

First, create a ``conf.py`` file in your project's directory, next to
``settings.py``:

.. code:: python

    import base64
    import os

    from goodconf import GoodConf, Field
    from pydantic import PostgresDsn

    class AppConfig(GoodConf):
        "Configuration for My App"
        DEBUG: bool
        DATABASE_URL: PostgresDsn = "postgres://localhost:5432/mydb"
        SECRET_KEY: str = Field(
            initial=lambda: base64.b64encode(os.urandom(60)).decode(),
            description="Used for cryptographic signing. "
            "https://docs.djangoproject.com/en/2.0/ref/settings/#secret-key")

        class Config:
            default_files = ["/etc/myproject/myproject.yaml", "myproject.yaml"]

    config = AppConfig()

Next, use the config in your ``settings.py`` file:

.. code:: python

    import dj_database_url
    from .conf import config

    config.load()

    DEBUG = config.DEBUG
    SECRET_KEY = config.SECRET_KEY
    DATABASES = {"default": dj_database_url.parse(config.DATABASE_URL)}

In your initial developer installation instructions, give some advice such as:

.. code:: shell

    python -c "import myproject; print(myproject.conf.config.generate_yaml(DEBUG=True))" > myproject.yaml

Better yet, make it a function and `entry point <https://setuptools.readthedocs.io/en/latest/setuptools.html#automatic-script-creation>`__ so you can install
your project and run something like ``generate-config > myproject.yaml``.

Usage
-----


``GoodConf``
^^^^^^^^^^^^

Your subclassed ``GoodConf`` object can include a ``Config`` class with the following
attributes:

``file_env_var``
  The name of an environment variable which can be used for
  the name of the configuration file to load.
``default_files``
  If no file is passed to the ``load`` method, try to load a
  configuration from these files in order.

It also has one method:

``load``
  Trigger the load method during instantiation. Defaults to False.

Use plain-text docstring for use as a header when generating a configuration
file.

Environment variables always take precedence over variables in the configuration files.

See Pydantic's docs for examples of loading:

* `Dotenv (.env) files <https://pydantic-docs.helpmanual.io/usage/settings/#dotenv-env-support>`_
* `Docker secrets <https://pydantic-docs.helpmanual.io/usage/settings/#secret-support>`_


Fields
^^^^^^

Declare configuration values by subclassing ``GoodConf`` and defining class
attributes which are standard Python type definitions or Pydantic ``FieldInfo``
instances generated by the ``Field`` function.

Goodconf can use one extra argument provided to the ``Field`` to define an function
which can generate an initial value for the field:

``initial``
  Callable to use for initial value when generating a config


Django Usage
------------

A helper is provided which monkey-patches Django's management commands to
accept a ``--config`` argument. Replace your ``manage.py`` with the following:

.. code:: python

    # Define your GoodConf in `myproject/conf.py`
    from myproject.conf import config

    if __name__ == '__main__':
        config.django_manage()


Why?
----

I took inspiration from `logan <https://github.com/dcramer/logan>`__ (used by
Sentry) and `derpconf <https://github.com/globocom/derpconf>`__ (used by
Thumbor). Both, however used Python files for configuration. I wanted a safer
format and one that was easier to serialize data into from a configuration
management system.

Environment Variables
^^^^^^^^^^^^^^^^^^^^^

I don't like working with environment variables. First, there are potential
security issues:

1. Accidental leaks via logging or error reporting services.
2. Child process inheritance (see `ImageTragick <https://imagetragick.com/>`__
   for an idea why this could be bad).

Second, in practice on deployment environments, environment variables end up
getting written to a number of files (cron, bash profile, service definitions,
web server config, etc.). Not only is it cumbersome, but also increases the
possibility of leaks via incorrect file permissions.

I prefer a single structured file which is explicitly read by the application.
I also want it to be easy to run my applications on services like Heroku
where environment variables are the preferred configuration method.

This module let's me do things the way I prefer in environments I control, but
still run them with environment variables on environments I don't control with
minimal fuss.


Contribute
----------

Create virtual environment and install package and dependencies.

.. code:: shell

    pip install -e ".[tests]"


Run tests

.. code:: shell

    pytest

Releasing a new version to PyPI:

.. code:: shell

    export VERSION=X.Y.Z
    git tag -s v$VERSION -m v$VERSION
    git push --tags
    rm -rf ./dist
    hatch build
    hatch publish --user __token__
    gh release create v$VERSION dist/goodconf-$VERSION* --generate-notes --verify-tag
