holy-diver


Nameholy-diver JSON
Version 0.1.0a3 PyPI version JSON
download
home_pagehttps://github.com/ndgigliotti/holy-diver
SummaryA dot-accessible configuration manager for deeply nested configuration files.
upload_time2023-06-17 06:47:01
maintainer
docs_urlNone
authorNick Gigliotti
requires_python>=3.9
licenseMIT license
keywords holy_diver holy-diver dot config config configuration config file configuration file configuration manager config manager configuration management yaml json dot accessible dot-accessible attribute attribute accessible attribute-accessible recursive nested nested configuration nested config deeply nested
VCS
bugtrack_url
requirements PyYAML setuptools
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ==========
holy-diver
==========


.. image:: https://img.shields.io/pypi/v/holy-diver.svg
        :target: https://pypi.python.org/pypi/holy-diver

``holy-diver`` is a Python library that provides a simple, dot-accessible configuration
manager, which especially handy for deeply nested configuration files.
It's named after the song "Holy Diver" by Dio, due to its divine usefulness for diving
deep into nested configuration data.
It offers some convenient features such handling default settings, checking for the
presence of required keys, and initializing directly from YAML, JSON, and TOML files.
It employs two main classes: ``Config`` and ``ConfigList``. ``Config`` is a
dictionary-like class that allows you to access nested keys using dot notation
(i.e. recursively accessing keys as if they were attributes). ``ConfigList`` is a list-like
class that allows you to access elements using indices and dot notation for nested keys.
Both classes work together in harmony to make it as easy as possible to manage deeply
nested configuration data.

Main Features
=============

- Easy-to-use API for managing configuration data
- Recursively dot-accessible dictionary-like ``Config`` class
- Recursively dot-accessible list-like ``ConfigList`` class
- Support for YAML, JSON, and TOML configuration file formats

Installation
============

To install holy-diver, simply run:

.. code-block:: bash

    pip install holy-diver

Usage
=====

Config
-------------

Here's a quick example of how to use the ``Config`` class. First create a
``Config`` object from a dictionary:

.. code-block:: python

    from holy_diver import Config

    config_data = {
        "database": {
            "host": "localhost",
            "port": 5432,
            "credentials": {
                "user": "admin",
                "password": "secret",
            },
        }
    }

    config = Config.from_dict(config_data)

Access nested keys attribute-style using dot notation:

.. code-block:: python

    print(config.database.host)  # Output: localhost
    print(config.database.port)  # Output: 5432

You can also set values using dot notation:

.. code-block:: python

    config.database.host = "impala.company.com"
    print(config.database.host)  # Output: impala.company.com

Alternatively, you can directly look up nested keys:

.. code-block:: python

    print(config["database.host"])  # Output: localhost
    print(config["database.port"])  # Output: 5432

And of course, you can access them the old fashioned way:

.. code-block:: python

    print(config["database"]["host"])  # Output: localhost
    print(config["database"]["port"])  # Output: 5432

ConfigList
-----------------

Here's a quick example of how to use the ``ConfigList`` class.
Items in ``ConfigList`` can be accessed using normal indexing and
dot notation interchangeably. All indices can be accessed entirely with dot notation,
which allows for easier handling of nested keys and data structures.

.. code-block:: python

    from holy_diver import ConfigList

    list_data = [
        {"name": "Alice", "age": 30},
        {"name": "Bob", "age": 25}
    ]

    config_list = ConfigList.from_list(list_data)

Access elements using indices and dot notation for nested keys:

.. code-block:: python

    print(config_list[0].name)  # Output: Alice
    print(config_list[1].age)   # Output: 25

Or, do it all with dot notation, if you prefer:

.. code-block:: python

    print(config_list._0.name) # Output: Alice
    print(config_list._1.age)  # Output: 25

The leading underscore allows numeric indices to be accessed as attributes. The
leading underscore is always required for attribute access, but is optional in other
contexts. You can see all the nested keys using the ``deep_keys()`` method, which shows
the leading underscore for numeric indices:

.. code-block:: python

    print(config_list.deep_keys())
    # Output: ['_0', '_1', '_0.name', '_0.age', '_1.name', '_1.age']

You can also look up nested keys directly:

.. code-block:: python

    print(config_list["_0.name"]) # Output: Alice
    print(config_list["_1.age"])  # Output: 25

    # It also works without the underscore
    print(config_list["0.name"]) # Output: Alice
    print(config_list["1.age"])  # Output: 25

Loading from a Configuration File
---------------------------------

You can load a configuration file in YAML format using the ``Config.from_yaml()`` method:

.. code-block:: python

    from holy_diver import Config

    config = Config.from_yaml("config.yaml")

Loading a JSON file works in much the same way:

.. code-block:: python

    from holy_diver import Config

    config = Config.from_json("config.json")

Alternative Constructors
------------------------
It's generally recommended to use one of the ``from_*()`` constructors
(e.g. ``from_dict()``, ``from_yaml()``) to create either a ``Config``
or ``ConfigList``, because these class methods automatically
convert nested dictionaries and lists to manager classes. It shouldn't affect the
functionality much if you use the main constructor, but it may cost you a few
milliseconds of processing time down the road, as more conversions must be
performed on the fly.

Writing to a Configuration File
-------------------------------

You can dump the configuration in various formats: YAML, JSON, and TOML.
Simply use the corresponding ``to_*()`` method (e.g. ``to_yaml()``, ``to_json()``)
and supply a path. Note that ``ConfigList`` objects can only be dumped to
YAML and JSON.

Converting and Deconverting
---------------------------
If you want to, you can convert the entire hierarchy to nested managers using the
``convert()`` method. This is done automatically when using the ``from_*()`` constructors,
but if you've used the main constructor or added some keys and values (an odd thing to do),
you might want to obtain a converted copy of the hierarchy. Again, this has a barely noticeable
effect on the functionality. Alternatively, you can deconvert the hierarchy to nested dicts and
lists using the ``deconvert()`` method. This is useful if you want the configuration data
in vanilla Python data structures for serialization.

.. code-block:: python

    from holy_diver import Config

    config_data = {
        "database": {
            "host": "localhost",
            "port": 5432,
            "credentials": {
                "user": "admin",
                "password": "secret",
            },
        }
    }

    config = Config(config_data) # Create a manager using main constructor
    converted = config.convert() # Convert to nested managers
    deconverted = converted.deconvert() # Deconvert to nested dicts and lists

    # Access nested keys
    print(config.database.host)  # Output: localhost
    print(converted.database.host)  # Output: localhost
    print(deconverted["database"]["host"])  # Output: localhost


Setting Defaults
----------------
You can set default values for keys that may not be present in the configuration data.
Simply pass the ``defaults`` keyword argument to any of the ``Config`` constructors.
This argument should be a dictionary of default values. If a key is not present in the
configuration data, the default value will be used instead. The user configuration is recursively
merged with the defaults to ensure that nested keys are handled properly.

.. code-block:: python

    from holy_diver import Config

    default_config = {
        "database": {
            "host": "impala.megacorp.com", # Will be overridden
            "port": 21050, # Will be overridden
            "auth_method": "LDAP", # Not present in the config data
        }
    }
    config_data = {"database": {"host": "localhost", "port": 5432}}

    config = Config.from_dict(config_data, defaults=default_config)

    print(config.database.host)  # Output: localhost
    print(config.database.port) # Output: 5432
    print(config.database.auth_method)  # Output: LDAP


Checking for Required Keys
--------------------------
One of the nice features of ``Config`` is that it allows you to check for the presence of
required keys. This is especially useful because it works for nested keys using dot notation.

.. code-block:: python

    from holy_diver import Config

    config_data = {
        "database": {
            "host": "localhost",
            "port": 5432,
            "credentials": {
                "user": "admin",
                "password": "secret",
            },
        }
    }

    required_keys = ["database.host", "database.credentials.user", "database.auth_method"]

    config = Config.from_dict(config_data) # Create a manager

    config.check_required_keys(required_keys, if_missing="raise")
    # Output: KeyError: Configuration is missing required keys: ['database.auth_method']

Raise a warning instead of an exception by passing ``if_missing="warn"``:

.. code-block:: python

    missing_keys = config.check_required_keys(required_keys, if_missing="warn")
    # Output: UserWarning: Configuration is missing required keys: ['database.auth_method']
    print(missing_keys) # Output: ["database.auth_method"]

Or, quietly get a list of missing keys by passing ``if_missing="return"``:

.. code-block:: python

    missing_keys = config.check_required_keys(required_keys, if_missing="return")
    print(missing_keys) # Output: ["database.auth_method"]

You can also check for required keys by passing ``required_keys`` to any of the
``Config`` constructors.

.. code-block:: python

    config = Config.from_dict(config_data, required_keys=required_keys)
    # Output: KeyError: Configuration is missing required keys: ['database.auth_method']


Contributing
============

We appreciate your contributions to the project! Please submit a pull request or create an issue on the GitHub repository to contribute.

License
=======

``holy-diver`` is released under the MIT License. See the LICENSE file for more details.

Credits
-------

This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.

.. _Cookiecutter: https://github.com/audreyr/cookiecutter
.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/ndgigliotti/holy-diver",
    "name": "holy-diver",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": "",
    "keywords": "holy_diver,holy-diver,dot config,config,configuration,config file,configuration file,configuration manager,config manager,configuration management,yaml,json,dot accessible,dot-accessible,attribute,attribute accessible,attribute-accessible,recursive,nested,nested configuration,nested config,deeply nested",
    "author": "Nick Gigliotti",
    "author_email": "ndgigliotti@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/c3/ab/a3839b953f9afdaa4a3b123a55e265b04de9aa8e2e19cf857adb9bcba532/holy-diver-0.1.0a3.tar.gz",
    "platform": null,
    "description": "==========\nholy-diver\n==========\n\n\n.. image:: https://img.shields.io/pypi/v/holy-diver.svg\n        :target: https://pypi.python.org/pypi/holy-diver\n\n``holy-diver`` is a Python library that provides a simple, dot-accessible configuration\nmanager, which especially handy for deeply nested configuration files.\nIt's named after the song \"Holy Diver\" by Dio, due to its divine usefulness for diving\ndeep into nested configuration data.\nIt offers some convenient features such handling default settings, checking for the\npresence of required keys, and initializing directly from YAML, JSON, and TOML files.\nIt employs two main classes: ``Config`` and ``ConfigList``. ``Config`` is a\ndictionary-like class that allows you to access nested keys using dot notation\n(i.e. recursively accessing keys as if they were attributes). ``ConfigList`` is a list-like\nclass that allows you to access elements using indices and dot notation for nested keys.\nBoth classes work together in harmony to make it as easy as possible to manage deeply\nnested configuration data.\n\nMain Features\n=============\n\n- Easy-to-use API for managing configuration data\n- Recursively dot-accessible dictionary-like ``Config`` class\n- Recursively dot-accessible list-like ``ConfigList`` class\n- Support for YAML, JSON, and TOML configuration file formats\n\nInstallation\n============\n\nTo install holy-diver, simply run:\n\n.. code-block:: bash\n\n    pip install holy-diver\n\nUsage\n=====\n\nConfig\n-------------\n\nHere's a quick example of how to use the ``Config`` class. First create a\n``Config`` object from a dictionary:\n\n.. code-block:: python\n\n    from holy_diver import Config\n\n    config_data = {\n        \"database\": {\n            \"host\": \"localhost\",\n            \"port\": 5432,\n            \"credentials\": {\n                \"user\": \"admin\",\n                \"password\": \"secret\",\n            },\n        }\n    }\n\n    config = Config.from_dict(config_data)\n\nAccess nested keys attribute-style using dot notation:\n\n.. code-block:: python\n\n    print(config.database.host)  # Output: localhost\n    print(config.database.port)  # Output: 5432\n\nYou can also set values using dot notation:\n\n.. code-block:: python\n\n    config.database.host = \"impala.company.com\"\n    print(config.database.host)  # Output: impala.company.com\n\nAlternatively, you can directly look up nested keys:\n\n.. code-block:: python\n\n    print(config[\"database.host\"])  # Output: localhost\n    print(config[\"database.port\"])  # Output: 5432\n\nAnd of course, you can access them the old fashioned way:\n\n.. code-block:: python\n\n    print(config[\"database\"][\"host\"])  # Output: localhost\n    print(config[\"database\"][\"port\"])  # Output: 5432\n\nConfigList\n-----------------\n\nHere's a quick example of how to use the ``ConfigList`` class.\nItems in ``ConfigList`` can be accessed using normal indexing and\ndot notation interchangeably. All indices can be accessed entirely with dot notation,\nwhich allows for easier handling of nested keys and data structures.\n\n.. code-block:: python\n\n    from holy_diver import ConfigList\n\n    list_data = [\n        {\"name\": \"Alice\", \"age\": 30},\n        {\"name\": \"Bob\", \"age\": 25}\n    ]\n\n    config_list = ConfigList.from_list(list_data)\n\nAccess elements using indices and dot notation for nested keys:\n\n.. code-block:: python\n\n    print(config_list[0].name)  # Output: Alice\n    print(config_list[1].age)   # Output: 25\n\nOr, do it all with dot notation, if you prefer:\n\n.. code-block:: python\n\n    print(config_list._0.name) # Output: Alice\n    print(config_list._1.age)  # Output: 25\n\nThe leading underscore allows numeric indices to be accessed as attributes. The\nleading underscore is always required for attribute access, but is optional in other\ncontexts. You can see all the nested keys using the ``deep_keys()`` method, which shows\nthe leading underscore for numeric indices:\n\n.. code-block:: python\n\n    print(config_list.deep_keys())\n    # Output: ['_0', '_1', '_0.name', '_0.age', '_1.name', '_1.age']\n\nYou can also look up nested keys directly:\n\n.. code-block:: python\n\n    print(config_list[\"_0.name\"]) # Output: Alice\n    print(config_list[\"_1.age\"])  # Output: 25\n\n    # It also works without the underscore\n    print(config_list[\"0.name\"]) # Output: Alice\n    print(config_list[\"1.age\"])  # Output: 25\n\nLoading from a Configuration File\n---------------------------------\n\nYou can load a configuration file in YAML format using the ``Config.from_yaml()`` method:\n\n.. code-block:: python\n\n    from holy_diver import Config\n\n    config = Config.from_yaml(\"config.yaml\")\n\nLoading a JSON file works in much the same way:\n\n.. code-block:: python\n\n    from holy_diver import Config\n\n    config = Config.from_json(\"config.json\")\n\nAlternative Constructors\n------------------------\nIt's generally recommended to use one of the ``from_*()`` constructors\n(e.g. ``from_dict()``, ``from_yaml()``) to create either a ``Config``\nor ``ConfigList``, because these class methods automatically\nconvert nested dictionaries and lists to manager classes. It shouldn't affect the\nfunctionality much if you use the main constructor, but it may cost you a few\nmilliseconds of processing time down the road, as more conversions must be\nperformed on the fly.\n\nWriting to a Configuration File\n-------------------------------\n\nYou can dump the configuration in various formats: YAML, JSON, and TOML.\nSimply use the corresponding ``to_*()`` method (e.g. ``to_yaml()``, ``to_json()``)\nand supply a path. Note that ``ConfigList`` objects can only be dumped to\nYAML and JSON.\n\nConverting and Deconverting\n---------------------------\nIf you want to, you can convert the entire hierarchy to nested managers using the\n``convert()`` method. This is done automatically when using the ``from_*()`` constructors,\nbut if you've used the main constructor or added some keys and values (an odd thing to do),\nyou might want to obtain a converted copy of the hierarchy. Again, this has a barely noticeable\neffect on the functionality. Alternatively, you can deconvert the hierarchy to nested dicts and\nlists using the ``deconvert()`` method. This is useful if you want the configuration data\nin vanilla Python data structures for serialization.\n\n.. code-block:: python\n\n    from holy_diver import Config\n\n    config_data = {\n        \"database\": {\n            \"host\": \"localhost\",\n            \"port\": 5432,\n            \"credentials\": {\n                \"user\": \"admin\",\n                \"password\": \"secret\",\n            },\n        }\n    }\n\n    config = Config(config_data) # Create a manager using main constructor\n    converted = config.convert() # Convert to nested managers\n    deconverted = converted.deconvert() # Deconvert to nested dicts and lists\n\n    # Access nested keys\n    print(config.database.host)  # Output: localhost\n    print(converted.database.host)  # Output: localhost\n    print(deconverted[\"database\"][\"host\"])  # Output: localhost\n\n\nSetting Defaults\n----------------\nYou can set default values for keys that may not be present in the configuration data.\nSimply pass the ``defaults`` keyword argument to any of the ``Config`` constructors.\nThis argument should be a dictionary of default values. If a key is not present in the\nconfiguration data, the default value will be used instead. The user configuration is recursively\nmerged with the defaults to ensure that nested keys are handled properly.\n\n.. code-block:: python\n\n    from holy_diver import Config\n\n    default_config = {\n        \"database\": {\n            \"host\": \"impala.megacorp.com\", # Will be overridden\n            \"port\": 21050, # Will be overridden\n            \"auth_method\": \"LDAP\", # Not present in the config data\n        }\n    }\n    config_data = {\"database\": {\"host\": \"localhost\", \"port\": 5432}}\n\n    config = Config.from_dict(config_data, defaults=default_config)\n\n    print(config.database.host)  # Output: localhost\n    print(config.database.port) # Output: 5432\n    print(config.database.auth_method)  # Output: LDAP\n\n\nChecking for Required Keys\n--------------------------\nOne of the nice features of ``Config`` is that it allows you to check for the presence of\nrequired keys. This is especially useful because it works for nested keys using dot notation.\n\n.. code-block:: python\n\n    from holy_diver import Config\n\n    config_data = {\n        \"database\": {\n            \"host\": \"localhost\",\n            \"port\": 5432,\n            \"credentials\": {\n                \"user\": \"admin\",\n                \"password\": \"secret\",\n            },\n        }\n    }\n\n    required_keys = [\"database.host\", \"database.credentials.user\", \"database.auth_method\"]\n\n    config = Config.from_dict(config_data) # Create a manager\n\n    config.check_required_keys(required_keys, if_missing=\"raise\")\n    # Output: KeyError: Configuration is missing required keys: ['database.auth_method']\n\nRaise a warning instead of an exception by passing ``if_missing=\"warn\"``:\n\n.. code-block:: python\n\n    missing_keys = config.check_required_keys(required_keys, if_missing=\"warn\")\n    # Output: UserWarning: Configuration is missing required keys: ['database.auth_method']\n    print(missing_keys) # Output: [\"database.auth_method\"]\n\nOr, quietly get a list of missing keys by passing ``if_missing=\"return\"``:\n\n.. code-block:: python\n\n    missing_keys = config.check_required_keys(required_keys, if_missing=\"return\")\n    print(missing_keys) # Output: [\"database.auth_method\"]\n\nYou can also check for required keys by passing ``required_keys`` to any of the\n``Config`` constructors.\n\n.. code-block:: python\n\n    config = Config.from_dict(config_data, required_keys=required_keys)\n    # Output: KeyError: Configuration is missing required keys: ['database.auth_method']\n\n\nContributing\n============\n\nWe appreciate your contributions to the project! Please submit a pull request or create an issue on the GitHub repository to contribute.\n\nLicense\n=======\n\n``holy-diver`` is released under the MIT License. See the LICENSE file for more details.\n\nCredits\n-------\n\nThis package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.\n\n.. _Cookiecutter: https://github.com/audreyr/cookiecutter\n.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage\n",
    "bugtrack_url": null,
    "license": "MIT license",
    "summary": "A dot-accessible configuration manager for deeply nested configuration files.",
    "version": "0.1.0a3",
    "project_urls": {
        "Download": "https://github.com/ndgigliotti/holy-diver/archive/refs/tags/v0.1.0-alpha.3.tar.gz",
        "Homepage": "https://github.com/ndgigliotti/holy-diver"
    },
    "split_keywords": [
        "holy_diver",
        "holy-diver",
        "dot config",
        "config",
        "configuration",
        "config file",
        "configuration file",
        "configuration manager",
        "config manager",
        "configuration management",
        "yaml",
        "json",
        "dot accessible",
        "dot-accessible",
        "attribute",
        "attribute accessible",
        "attribute-accessible",
        "recursive",
        "nested",
        "nested configuration",
        "nested config",
        "deeply nested"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "936d7ec0bf7a93c457ef5c92ed3de36a3ddcec84a26d674d6f757a995007b9a0",
                "md5": "a4471ace4d8413e02f55bb97d731d4af",
                "sha256": "cb1365df5eb3c1be76a55b596c5f3c66b89a8b75d6e066e489e6e95622b3ab96"
            },
            "downloads": -1,
            "filename": "holy_diver-0.1.0a3-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "a4471ace4d8413e02f55bb97d731d4af",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": ">=3.9",
            "size": 12841,
            "upload_time": "2023-06-17T06:47:00",
            "upload_time_iso_8601": "2023-06-17T06:47:00.288179Z",
            "url": "https://files.pythonhosted.org/packages/93/6d/7ec0bf7a93c457ef5c92ed3de36a3ddcec84a26d674d6f757a995007b9a0/holy_diver-0.1.0a3-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c3aba3839b953f9afdaa4a3b123a55e265b04de9aa8e2e19cf857adb9bcba532",
                "md5": "e24afafd89a7230c33baafa19f769920",
                "sha256": "d13b9acdfde6ff7e2cdb0029b632adb37061d5c33092c90f1b782a39199d0c1c"
            },
            "downloads": -1,
            "filename": "holy-diver-0.1.0a3.tar.gz",
            "has_sig": false,
            "md5_digest": "e24afafd89a7230c33baafa19f769920",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 23061,
            "upload_time": "2023-06-17T06:47:01",
            "upload_time_iso_8601": "2023-06-17T06:47:01.878837Z",
            "url": "https://files.pythonhosted.org/packages/c3/ab/a3839b953f9afdaa4a3b123a55e265b04de9aa8e2e19cf857adb9bcba532/holy-diver-0.1.0a3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-06-17 06:47:01",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "ndgigliotti",
    "github_project": "holy-diver",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [
        {
            "name": "PyYAML",
            "specs": [
                [
                    "==",
                    "6.0"
                ]
            ]
        },
        {
            "name": "setuptools",
            "specs": [
                [
                    "==",
                    "66.0.0"
                ]
            ]
        }
    ],
    "lcname": "holy-diver"
}
        
Elapsed time: 0.10427s