config_wrangler


Nameconfig_wrangler JSON
Version 1.1.0 PyPI version JSON
download
home_pagehttps://github.com/arcann/config_wrangler
Summarypydantic based configuration wrangler. Handles reading multiple ini or toml files with inheritance rules and variable expansions.
upload_time2024-03-12 18:54:44
maintainer
docs_urlNone
authorDerek Wood
requires_python>=3.9,<4.0.0
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Config Wrangler

[![pypi](https://img.shields.io/pypi/v/config-wrangler.svg)](https://pypi.org/project/config-wrangler/)
[![license](https://img.shields.io/github/license/arcann/config_wrangler.svg)](https://github.com/arcann/config_wrangler/blob/master/LICENSE)

pydantic based configuration wrangler. Handles reading multiple ini or toml files with inheritance rules and variable expansions.

## Installation

Install using your package manager of choice:
  - `poetry add config-wrangler`
  - `pip install -U config-wrangler` 
  - `conda install config-wrangler -c conda-forge`.

## A Simple Example

config.ini
```ini
[S3_Source]
bucket_name=my.exmple-bucket
key_prefixes=processed/
user_id=AK123456789ABC
# Not a secure way to store the password, but OK for local prototype or examples.
# See KEYRING or KEEPASS for better options
password_source=CONFIG_FILE
raw_password=My secret password

[target_database]
dialect=sqlite
database_name=${test_section:my_environment:source_data_dir}/example_db

[test_section]
my_int=123
my_float=123.45
my_bool=Yes
my_str=ABC☕
my_bytes=ABCⓁⓄⓋ☕
my_list_auto_c=a,b,c
my_list_auto_nl=
    a
    b
    c
my_list_auto_pipe=a|b|c
my_list_c=a,b,c
my_list_python=['x','y','z']
my_list_json=["J","S","O","N"]
my_list_nl=
    a
    b
    c
my_list_int_c=1,2,3
my_tuple_c=a,b,c
my_tuple_nl=
    a
    b
    c
my_tuple_int_c=1,2,3
my_dict={1: "One", 2: "Two"}
my_dict_str_int={"one": 1, "two": 2}
my_set={'A','B','C'}
my_set_int=1,2,3
my_frozenset=A,B,C
my_date=2021-05-31
my_time=11:55:23
my_datetime=2021-05-31 11:23:53
my_url=https://localhost:6553/

[test_section.my_environment]
name=dev
# For example to run we'll make both paths relative to current
temp_data_dir=.\temp_data\${test_section:my_environment:name}
source_data_dir=.
```

python code

```py
import typing
from datetime import date, time, datetime

from pydantic import BaseModel, DirectoryPath, Field, AnyHttpUrl

from config_wrangler.config_data_loaders.base_config_data_loader import BaseConfigDataLoader
from config_wrangler.config_from_ini_env import ConfigFromIniEnv
from config_wrangler.config_from_loaders import ConfigFromLoaders
from config_wrangler.config_templates.config_hierarchy import ConfigHierarchy
from config_wrangler.config_templates.aws.s3_bucket import S3_Bucket
from config_wrangler.config_templates.sqlalchemy_database import SQLAlchemyDatabase
from config_wrangler.config_types.path_types import AutoCreateDirectoryPath
from config_wrangler.config_types.delimited_field import DelimitedListField


class S3_Bucket_KeyPrefixes(S3_Bucket):
    key_prefixes: typing.List[str]


class Environment(ConfigHierarchy):
    name: str = Field(..., env='env_name')
    temp_data_dir: AutoCreateDirectoryPath
    source_data_dir: DirectoryPath


class TestSection(BaseModel):
    my_int: int
    my_float: float
    my_bool: bool
    my_str: str
    my_bytes: bytes
    my_list_auto_c: list
    my_list_auto_nl: list
    my_list_auto_pipe: list
    my_list_python: list
    my_list_json: list
    my_list_c: list = DelimitedListField(delimiter=',')
    my_list_nl: list = DelimitedListField(delimiter='\n')
    my_list_int_c: typing.List[int] = DelimitedListField(delimiter=',')
    my_tuple_c: tuple = DelimitedListField(delimiter=',')
    my_tuple_nl: tuple = DelimitedListField(delimiter='\n')
    my_tuple_int_c: typing.Tuple[int, int, int] = DelimitedListField(delimiter=',')
    my_dict: dict
    my_dict_str_int: typing.Dict[str, int]
    my_set: set
    my_set_int: typing.Set[int]
    my_frozenset: frozenset
    my_date: date
    my_time: time
    my_datetime: datetime
    my_url: AnyHttpUrl
    my_environment: Environment


class ETLConfig(ConfigFromIniEnv):
    class Config:
        validate_default = True
        validate_assignment = True

    target_database: SQLAlchemyDatabase

    s3_source: S3_Bucket_KeyPrefixes

    test_section: TestSection


class ETLConfigAnyLoaders(ETLConfig):
    def __init__(
            self,
            _config_data_loaders: typing.List[BaseConfigDataLoader],
            **kwargs: typing.Dict[str, typing.Any]
    ) -> None:
        # Skip super and call the next higher class
        ConfigFromLoaders.__init__(
            self,
            _config_data_loaders=_config_data_loaders,
            **kwargs
        )


def main():
    config = ETLConfig(file_name='simple_example.ini')

    print(f"Temp data dir = {config.test_section.my_environment.temp_data_dir}")
    # > Temp data dir = temp_data\dev

    print(f"Source data dir = {config.test_section.my_environment.source_data_dir}")
    # > Source data dir = .

    print(f"my_int = {config.test_section.my_int}")
    # > my_int = 123

    print(f"my_float = {config.test_section.my_float}")
    # > my_float = 123.45

    print(f"my_str = {config.test_section.my_str}")
    # > my_str = ABC☕

    print(f"my_list_auto_c = {config.test_section.my_list_auto_c}")
    # > my_list_auto_c = ['a', 'b', 'c']

    print(f"my_list_auto_nl = {config.test_section.my_list_auto_nl}")
    # > my_list_auto_c = ['a', 'b', 'c']

    print(f"my_dict = {config.test_section.my_dict}")
    # > my_dict = {1: 'One', 2: 'Two'}

    print(f"my_set = {config.test_section.my_set}")
    # > my_set = {'C', 'A', 'B'}

    print(f"my_time = {config.test_section.my_time}")
    # > my_time = 11:55:23

    print(f"my_datetime = {config.test_section.my_datetime}")
    # > my_datetime = 2021-05-31 11:23:53

    print(f"my_url = {config.test_section.my_url}")
    # > my_url = https://localhost:6553/

    # Getting DB engine (requires sqlalchemy optional install
    engine = config.target_database.get_engine()
    print(f"target_database.engine = {engine}")
    # > target_database.engine = Engine(sqlite:///.example_db)

    print("Getting S3 Data")
    bucket = config.s3_source.get_bucket()
    print(f"S3 bucket definition = {bucket}")
    for prefix in config.s3_source.key_prefixes:
        print(f"  bucket search prefix = {prefix}")
    # > Getting S3 Data
    # > credentials.py:56: UserWarning: Passwords stored directly in config or worse in code are not safe. Please make sure to fix this before deploying.
    # > S3 bucket definitition = s3.Bucket(name='my.exmple-bucket')
    # > bucket search prefix = processed/


if __name__ == '__main__':
    main()

```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/arcann/config_wrangler",
    "name": "config_wrangler",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.9,<4.0.0",
    "maintainer_email": "",
    "keywords": "",
    "author": "Derek Wood",
    "author_email": "",
    "download_url": "https://files.pythonhosted.org/packages/28/63/83f5296c3c17ee1a2b39dfbd7b32b12887e6ab0e185d54a24a3fc11fd848/config_wrangler-1.1.0.tar.gz",
    "platform": null,
    "description": "# Config Wrangler\n\n[![pypi](https://img.shields.io/pypi/v/config-wrangler.svg)](https://pypi.org/project/config-wrangler/)\n[![license](https://img.shields.io/github/license/arcann/config_wrangler.svg)](https://github.com/arcann/config_wrangler/blob/master/LICENSE)\n\npydantic based configuration wrangler. Handles reading multiple ini or toml files with inheritance rules and variable expansions.\n\n## Installation\n\nInstall using your package manager of choice:\n  - `poetry add config-wrangler`\n  - `pip install -U config-wrangler` \n  - `conda install config-wrangler -c conda-forge`.\n\n## A Simple Example\n\nconfig.ini\n```ini\n[S3_Source]\nbucket_name=my.exmple-bucket\nkey_prefixes=processed/\nuser_id=AK123456789ABC\n# Not a secure way to store the password, but OK for local prototype or examples.\n# See KEYRING or KEEPASS for better options\npassword_source=CONFIG_FILE\nraw_password=My secret password\n\n[target_database]\ndialect=sqlite\ndatabase_name=${test_section:my_environment:source_data_dir}/example_db\n\n[test_section]\nmy_int=123\nmy_float=123.45\nmy_bool=Yes\nmy_str=ABC\u2615\nmy_bytes=ABC\u24c1\u24c4\u24cb\u2615\nmy_list_auto_c=a,b,c\nmy_list_auto_nl=\n    a\n    b\n    c\nmy_list_auto_pipe=a|b|c\nmy_list_c=a,b,c\nmy_list_python=['x','y','z']\nmy_list_json=[\"J\",\"S\",\"O\",\"N\"]\nmy_list_nl=\n    a\n    b\n    c\nmy_list_int_c=1,2,3\nmy_tuple_c=a,b,c\nmy_tuple_nl=\n    a\n    b\n    c\nmy_tuple_int_c=1,2,3\nmy_dict={1: \"One\", 2: \"Two\"}\nmy_dict_str_int={\"one\": 1, \"two\": 2}\nmy_set={'A','B','C'}\nmy_set_int=1,2,3\nmy_frozenset=A,B,C\nmy_date=2021-05-31\nmy_time=11:55:23\nmy_datetime=2021-05-31 11:23:53\nmy_url=https://localhost:6553/\n\n[test_section.my_environment]\nname=dev\n# For example to run we'll make both paths relative to current\ntemp_data_dir=.\\temp_data\\${test_section:my_environment:name}\nsource_data_dir=.\n```\n\npython code\n\n```py\nimport typing\nfrom datetime import date, time, datetime\n\nfrom pydantic import BaseModel, DirectoryPath, Field, AnyHttpUrl\n\nfrom config_wrangler.config_data_loaders.base_config_data_loader import BaseConfigDataLoader\nfrom config_wrangler.config_from_ini_env import ConfigFromIniEnv\nfrom config_wrangler.config_from_loaders import ConfigFromLoaders\nfrom config_wrangler.config_templates.config_hierarchy import ConfigHierarchy\nfrom config_wrangler.config_templates.aws.s3_bucket import S3_Bucket\nfrom config_wrangler.config_templates.sqlalchemy_database import SQLAlchemyDatabase\nfrom config_wrangler.config_types.path_types import AutoCreateDirectoryPath\nfrom config_wrangler.config_types.delimited_field import DelimitedListField\n\n\nclass S3_Bucket_KeyPrefixes(S3_Bucket):\n    key_prefixes: typing.List[str]\n\n\nclass Environment(ConfigHierarchy):\n    name: str = Field(..., env='env_name')\n    temp_data_dir: AutoCreateDirectoryPath\n    source_data_dir: DirectoryPath\n\n\nclass TestSection(BaseModel):\n    my_int: int\n    my_float: float\n    my_bool: bool\n    my_str: str\n    my_bytes: bytes\n    my_list_auto_c: list\n    my_list_auto_nl: list\n    my_list_auto_pipe: list\n    my_list_python: list\n    my_list_json: list\n    my_list_c: list = DelimitedListField(delimiter=',')\n    my_list_nl: list = DelimitedListField(delimiter='\\n')\n    my_list_int_c: typing.List[int] = DelimitedListField(delimiter=',')\n    my_tuple_c: tuple = DelimitedListField(delimiter=',')\n    my_tuple_nl: tuple = DelimitedListField(delimiter='\\n')\n    my_tuple_int_c: typing.Tuple[int, int, int] = DelimitedListField(delimiter=',')\n    my_dict: dict\n    my_dict_str_int: typing.Dict[str, int]\n    my_set: set\n    my_set_int: typing.Set[int]\n    my_frozenset: frozenset\n    my_date: date\n    my_time: time\n    my_datetime: datetime\n    my_url: AnyHttpUrl\n    my_environment: Environment\n\n\nclass ETLConfig(ConfigFromIniEnv):\n    class Config:\n        validate_default = True\n        validate_assignment = True\n\n    target_database: SQLAlchemyDatabase\n\n    s3_source: S3_Bucket_KeyPrefixes\n\n    test_section: TestSection\n\n\nclass ETLConfigAnyLoaders(ETLConfig):\n    def __init__(\n            self,\n            _config_data_loaders: typing.List[BaseConfigDataLoader],\n            **kwargs: typing.Dict[str, typing.Any]\n    ) -> None:\n        # Skip super and call the next higher class\n        ConfigFromLoaders.__init__(\n            self,\n            _config_data_loaders=_config_data_loaders,\n            **kwargs\n        )\n\n\ndef main():\n    config = ETLConfig(file_name='simple_example.ini')\n\n    print(f\"Temp data dir = {config.test_section.my_environment.temp_data_dir}\")\n    # > Temp data dir = temp_data\\dev\n\n    print(f\"Source data dir = {config.test_section.my_environment.source_data_dir}\")\n    # > Source data dir = .\n\n    print(f\"my_int = {config.test_section.my_int}\")\n    # > my_int = 123\n\n    print(f\"my_float = {config.test_section.my_float}\")\n    # > my_float = 123.45\n\n    print(f\"my_str = {config.test_section.my_str}\")\n    # > my_str = ABC\u2615\n\n    print(f\"my_list_auto_c = {config.test_section.my_list_auto_c}\")\n    # > my_list_auto_c = ['a', 'b', 'c']\n\n    print(f\"my_list_auto_nl = {config.test_section.my_list_auto_nl}\")\n    # > my_list_auto_c = ['a', 'b', 'c']\n\n    print(f\"my_dict = {config.test_section.my_dict}\")\n    # > my_dict = {1: 'One', 2: 'Two'}\n\n    print(f\"my_set = {config.test_section.my_set}\")\n    # > my_set = {'C', 'A', 'B'}\n\n    print(f\"my_time = {config.test_section.my_time}\")\n    # > my_time = 11:55:23\n\n    print(f\"my_datetime = {config.test_section.my_datetime}\")\n    # > my_datetime = 2021-05-31 11:23:53\n\n    print(f\"my_url = {config.test_section.my_url}\")\n    # > my_url = https://localhost:6553/\n\n    # Getting DB engine (requires sqlalchemy optional install\n    engine = config.target_database.get_engine()\n    print(f\"target_database.engine = {engine}\")\n    # > target_database.engine = Engine(sqlite:///.example_db)\n\n    print(\"Getting S3 Data\")\n    bucket = config.s3_source.get_bucket()\n    print(f\"S3 bucket definition = {bucket}\")\n    for prefix in config.s3_source.key_prefixes:\n        print(f\"  bucket search prefix = {prefix}\")\n    # > Getting S3 Data\n    # > credentials.py:56: UserWarning: Passwords stored directly in config or worse in code are not safe. Please make sure to fix this before deploying.\n    # > S3 bucket definitition = s3.Bucket(name='my.exmple-bucket')\n    # > bucket search prefix = processed/\n\n\nif __name__ == '__main__':\n    main()\n\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "pydantic based configuration wrangler. Handles reading multiple ini or toml files with inheritance rules and variable expansions.",
    "version": "1.1.0",
    "project_urls": {
        "Documentation": "https://bietl.dev/config_wrangler/",
        "Homepage": "https://github.com/arcann/config_wrangler",
        "Repository": "https://github.com/arcann/config_wrangler"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7a1b9bb4c90b0d289a6ea245a26ad3a2c3d191e6f901f96e6142a0d8ac8f81d1",
                "md5": "2feb857565432431eac111b3c42f22fe",
                "sha256": "b4231e74d969719bfeb92e0cfb5eb4dd9414aa98e3261006cc7888ec588c0c79"
            },
            "downloads": -1,
            "filename": "config_wrangler-1.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "2feb857565432431eac111b3c42f22fe",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9,<4.0.0",
            "size": 63490,
            "upload_time": "2024-03-12T18:54:41",
            "upload_time_iso_8601": "2024-03-12T18:54:41.737345Z",
            "url": "https://files.pythonhosted.org/packages/7a/1b/9bb4c90b0d289a6ea245a26ad3a2c3d191e6f901f96e6142a0d8ac8f81d1/config_wrangler-1.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "286383f5296c3c17ee1a2b39dfbd7b32b12887e6ab0e185d54a24a3fc11fd848",
                "md5": "86b86e4f6f30607bd24db36e9cf9fb19",
                "sha256": "9c3ce4a176cf0ba980c1ea2e02b8e72c160ee3988c8179a1332e67460e081111"
            },
            "downloads": -1,
            "filename": "config_wrangler-1.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "86b86e4f6f30607bd24db36e9cf9fb19",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9,<4.0.0",
            "size": 194340,
            "upload_time": "2024-03-12T18:54:44",
            "upload_time_iso_8601": "2024-03-12T18:54:44.331504Z",
            "url": "https://files.pythonhosted.org/packages/28/63/83f5296c3c17ee1a2b39dfbd7b32b12887e6ab0e185d54a24a3fc11fd848/config_wrangler-1.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-12 18:54:44",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "arcann",
    "github_project": "config_wrangler",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "tox": true,
    "lcname": "config_wrangler"
}
        
Elapsed time: 0.19999s