goodconf


Namegoodconf JSON
Version 4.0.2 PyPI version JSON
download
home_pageNone
SummaryLoad configuration variables from a file or environment
upload_time2024-02-10 18:03:04
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseCopyright (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.
keywords config env json toml yaml
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            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

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "goodconf",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "config,env,json,toml,yaml",
    "author": null,
    "author_email": "Peter Baumgartner <brett@python.org>",
    "download_url": "https://files.pythonhosted.org/packages/48/95/0863d05640b8d66d79fb4d4da8dcfe4730980c3fbc2a61dd57c59cf57e04/goodconf-4.0.2.tar.gz",
    "platform": null,
    "description": "Goodconf\n========\n\n.. image:: https://github.com/lincolnloop/goodconf/actions/workflows/test.yml/badge.svg?branch=main&event=push\n    :target: https://github.com/lincolnloop/goodconf/actions/workflows/test.yml?query=branch%3Amain+event%3Apush\n\n.. image:: https://results.pre-commit.ci/badge/github/lincolnloop/goodconf/main.svg\n    :target: https://results.pre-commit.ci/latest/github/lincolnloop/goodconf/main\n    :alt: pre-commit.ci status\n\n.. image:: https://img.shields.io/codecov/c/github/lincolnloop/goodconf.svg\n    :target: https://codecov.io/gh/lincolnloop/goodconf\n\n.. image:: https://img.shields.io/pypi/v/goodconf.svg\n    :target: https://pypi.python.org/pypi/goodconf\n\n.. image:: https://img.shields.io/pypi/pyversions/goodconf.svg\n    :target: https://pypi.python.org/pypi/goodconf\n\nA thin wrapper over `Pydantic's settings management <https://pydantic-docs.helpmanual.io/usage/settings/>`__.\nAllows you to define configuration variables and load them from environment or JSON/YAML\nfile. Also generates initial configuration files and documentation for your\ndefined configuration.\n\n\nInstallation\n------------\n\n``pip install goodconf`` or ``pip install goodconf[yaml]`` /\n``pip install goodconf[toml]`` if parsing/generating YAML/TOML\nfiles is required.\n\n\nQuick Start\n-----------\n\nLet's use configurable Django settings as an example.\n\nFirst, create a ``conf.py`` file in your project's directory, next to\n``settings.py``:\n\n.. code:: python\n\n    import base64\n    import os\n\n    from goodconf import GoodConf, Field\n    from pydantic import PostgresDsn\n\n    class AppConfig(GoodConf):\n        \"Configuration for My App\"\n        DEBUG: bool\n        DATABASE_URL: PostgresDsn = \"postgres://localhost:5432/mydb\"\n        SECRET_KEY: str = Field(\n            initial=lambda: base64.b64encode(os.urandom(60)).decode(),\n            description=\"Used for cryptographic signing. \"\n            \"https://docs.djangoproject.com/en/2.0/ref/settings/#secret-key\")\n\n        class Config:\n            default_files = [\"/etc/myproject/myproject.yaml\", \"myproject.yaml\"]\n\n    config = AppConfig()\n\nNext, use the config in your ``settings.py`` file:\n\n.. code:: python\n\n    import dj_database_url\n    from .conf import config\n\n    config.load()\n\n    DEBUG = config.DEBUG\n    SECRET_KEY = config.SECRET_KEY\n    DATABASES = {\"default\": dj_database_url.parse(config.DATABASE_URL)}\n\nIn your initial developer installation instructions, give some advice such as:\n\n.. code:: shell\n\n    python -c \"import myproject; print(myproject.conf.config.generate_yaml(DEBUG=True))\" > myproject.yaml\n\nBetter yet, make it a function and `entry point <https://setuptools.readthedocs.io/en/latest/setuptools.html#automatic-script-creation>`__ so you can install\nyour project and run something like ``generate-config > myproject.yaml``.\n\nUsage\n-----\n\n\n``GoodConf``\n^^^^^^^^^^^^\n\nYour subclassed ``GoodConf`` object can include a ``Config`` class with the following\nattributes:\n\n``file_env_var``\n  The name of an environment variable which can be used for\n  the name of the configuration file to load.\n``default_files``\n  If no file is passed to the ``load`` method, try to load a\n  configuration from these files in order.\n\nIt also has one method:\n\n``load``\n  Trigger the load method during instantiation. Defaults to False.\n\nUse plain-text docstring for use as a header when generating a configuration\nfile.\n\nEnvironment variables always take precedence over variables in the configuration files.\n\nSee Pydantic's docs for examples of loading:\n\n* `Dotenv (.env) files <https://pydantic-docs.helpmanual.io/usage/settings/#dotenv-env-support>`_\n* `Docker secrets <https://pydantic-docs.helpmanual.io/usage/settings/#secret-support>`_\n\n\nFields\n^^^^^^\n\nDeclare configuration values by subclassing ``GoodConf`` and defining class\nattributes which are standard Python type definitions or Pydantic ``FieldInfo``\ninstances generated by the ``Field`` function.\n\nGoodconf can use one extra argument provided to the ``Field`` to define an function\nwhich can generate an initial value for the field:\n\n``initial``\n  Callable to use for initial value when generating a config\n\n\nDjango Usage\n------------\n\nA helper is provided which monkey-patches Django's management commands to\naccept a ``--config`` argument. Replace your ``manage.py`` with the following:\n\n.. code:: python\n\n    # Define your GoodConf in `myproject/conf.py`\n    from myproject.conf import config\n\n    if __name__ == '__main__':\n        config.django_manage()\n\n\nWhy?\n----\n\nI took inspiration from `logan <https://github.com/dcramer/logan>`__ (used by\nSentry) and `derpconf <https://github.com/globocom/derpconf>`__ (used by\nThumbor). Both, however used Python files for configuration. I wanted a safer\nformat and one that was easier to serialize data into from a configuration\nmanagement system.\n\nEnvironment Variables\n^^^^^^^^^^^^^^^^^^^^^\n\nI don't like working with environment variables. First, there are potential\nsecurity issues:\n\n1. Accidental leaks via logging or error reporting services.\n2. Child process inheritance (see `ImageTragick <https://imagetragick.com/>`__\n   for an idea why this could be bad).\n\nSecond, in practice on deployment environments, environment variables end up\ngetting written to a number of files (cron, bash profile, service definitions,\nweb server config, etc.). Not only is it cumbersome, but also increases the\npossibility of leaks via incorrect file permissions.\n\nI prefer a single structured file which is explicitly read by the application.\nI also want it to be easy to run my applications on services like Heroku\nwhere environment variables are the preferred configuration method.\n\nThis module let's me do things the way I prefer in environments I control, but\nstill run them with environment variables on environments I don't control with\nminimal fuss.\n\n\nContribute\n----------\n\nCreate virtual environment and install package and dependencies.\n\n.. code:: shell\n\n    pip install -e \".[tests]\"\n\n\nRun tests\n\n.. code:: shell\n\n    pytest\n\nReleasing a new version to PyPI:\n\n.. code:: shell\n\n    export VERSION=X.Y.Z\n    git tag -s v$VERSION -m v$VERSION\n    git push --tags\n    rm -rf ./dist\n    hatch build\n    hatch publish --user __token__\n    gh release create v$VERSION dist/goodconf-$VERSION* --generate-notes --verify-tag\n",
    "bugtrack_url": null,
    "license": "Copyright (c) 2018 Lincoln Loop\n        \n        Permission is hereby granted, free of charge, to any person\n        obtaining a copy of this software and associated documentation\n        files (the \"Software\"), to deal in the Software without\n        restriction, including without limitation the rights to use,\n        copy, modify, merge, publish, distribute, sublicense, and/or sell\n        copies of the Software, and to permit persons to whom the\n        Software is furnished to do so, subject to the following\n        conditions:\n        \n        The above copyright notice and this permission notice shall be\n        included in all copies or substantial portions of the Software.\n        \n        THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n        EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n        OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n        NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n        HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n        WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n        FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n        OTHER DEALINGS IN THE SOFTWARE.",
    "summary": "Load configuration variables from a file or environment",
    "version": "4.0.2",
    "project_urls": {
        "changelog": "https://github.com/lincolnloop/goodconf/blob/main/CHANGES.rst",
        "homepage": "https://github.com/lincolnloop/goodconf/"
    },
    "split_keywords": [
        "config",
        "env",
        "json",
        "toml",
        "yaml"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "1ea352452ba8221f108849d258629a9dce0d5e99c0b911f12b0f2eae1c5f5a9f",
                "md5": "fa3508faf094482a878e351375098351",
                "sha256": "4d8780b65f5ac0b03e95af997c4deb4cca49fb6a1e35c09dd6357de1535b37c9"
            },
            "downloads": -1,
            "filename": "goodconf-4.0.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "fa3508faf094482a878e351375098351",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 10044,
            "upload_time": "2024-02-10T18:03:09",
            "upload_time_iso_8601": "2024-02-10T18:03:09.755330Z",
            "url": "https://files.pythonhosted.org/packages/1e/a3/52452ba8221f108849d258629a9dce0d5e99c0b911f12b0f2eae1c5f5a9f/goodconf-4.0.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "48950863d05640b8d66d79fb4d4da8dcfe4730980c3fbc2a61dd57c59cf57e04",
                "md5": "5a0866840869f3827c2f392e7836f7e7",
                "sha256": "29e212a1b44cbe99616cc5aa2c90309414f8eb8b7c5cde744437f02a446de5e3"
            },
            "downloads": -1,
            "filename": "goodconf-4.0.2.tar.gz",
            "has_sig": false,
            "md5_digest": "5a0866840869f3827c2f392e7836f7e7",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 25193586,
            "upload_time": "2024-02-10T18:03:04",
            "upload_time_iso_8601": "2024-02-10T18:03:04.427458Z",
            "url": "https://files.pythonhosted.org/packages/48/95/0863d05640b8d66d79fb4d4da8dcfe4730980c3fbc2a61dd57c59cf57e04/goodconf-4.0.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-02-10 18:03:04",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "lincolnloop",
    "github_project": "goodconf",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "goodconf"
}
        
Elapsed time: 0.18036s