onion-config


Nameonion-config JSON
Version 5.1.0 PyPI version JSON
download
home_pagehttps://github.com/bybatkhuu/module.python-config
Summary'onion_config' is a python package that allows for easy configuration management. It allows for loading and validating configuration data from environment variables and config files in JSON and YAML formats.
upload_time2023-09-21 05:59:13
maintainer
docs_urlNone
authorBatkhuu Byambajav
requires_python>=3.8
licenseMIT
keywords onion_config config configs dotenv python-dotenv pydantic pydantic-config pydantic-settings custom-config
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # onion_config

[![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bybatkhuu/module.python-config/2.build-publish.yml?logo=GitHub)](https://github.com/bybatkhuu/module.python-config/actions/workflows/2.build-publish.yml)
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/bybatkhuu/module.python-config?logo=GitHub)](https://github.com/bybatkhuu/module.python-config/releases)
[![PyPI](https://img.shields.io/pypi/v/onion-config?logo=PyPi)](https://pypi.org/project/onion-config)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/onion-config?logo=Python)](https://docs.conda.io/en/latest/miniconda.html)

`onion_config` is a python package that allows for easy configuration management. It allows for loading and validating configuration data from environment variables and config files in JSON and YAML formats.

`Pydantic` based custom config package for python projects.

## Features

- **Main config** based on **Pydantic schema** - <https://pypi.org/project/pydantic>
- Load **environment variables** - <https://pypi.org/project/python-dotenv>
- Load from **multiple** configs directories
- Load configs from **YAML** and **JSON** files
- Update the default config with additional configurations (**`extra_dir`** directory)
- **Pre-load hook** function to modify config data before loading and validation
- **Validate config** values with **Pydantic validators**
- Config as **dictionary** or **Pydantic model** (with type hints)
- **Pre-defined** base config schema for common config (**`BaseConfig`**)
- **Base** for custom config loader (**`ConfigLoader`**)

---

## Installation

### 1. Prerequisites

- **Python (>= v3.8)**
- **PyPi (>= v23)**

### 2. Install onion-config package

Choose one of the following methods to install the package **[A ~ F]**:

**A.** [**RECOMMENDED**] Install from **PyPi**

```sh
# Install or upgrade package:
# Pydantic-v1:
pip install -U onion-config[pydantic-v1]

# Pydantic-v2:
pip install -U onion-config[pydantic-settings]
```

**B.** Install latest version from **GitHub**

```sh
# Install package by git:
# Pydantic-v1:
pip install git+https://github.com/bybatkhuu/module.python-config.git[pydantic-v1]

# Pydantic-v2:
pip install git+https://github.com/bybatkhuu/module.python-config.git[pydantic-settings]
```

**C.** Install from **pre-built release** files

1. Download **`.whl`** or **`.tar.gz`** file from **releases** - <https://github.com/bybatkhuu/module.python-config/releases>
2. Install with pip:

```sh
# Pydantic-v1:
# Install from .whl file:
pip install ./onion_config-[VERSION]-py3-none-any.whl[pydantic-v1]
# Or install from .tar.gz file:
pip install ./onion_config-[VERSION].tar.gz[pydantic-v1]

# Pydantic-v2:
# Install from .whl file:
pip install ./onion_config-[VERSION]-py3-none-any.whl[pydantic-settings]
# Or install from .tar.gz file:
pip install ./onion_config-[VERSION].tar.gz[pydantic-settings]
```

**D.** Install from **source code** by building package

```sh
# Clone repository by git:
git clone https://github.com/bybatkhuu/module.python-config.git onion_config
cd ./onion_config

# Install python build tool:
pip install -U pip build

# Build python package:
python -m build

_VERSION=$(./scripts/get-version.sh)

# Pydantic-v1:
# Install from .whl file:
pip install ./dist/onion_config-${_VERSION}-py3-none-any.whl[pydantic-v1]
# Or install from .tar.gz file:
pip install ./dist/onion_config-${_VERSION}.tar.gz[pydantic-v1]

# Pydantic-v2:
# Install from .whl file:
pip install ./dist/onion_config-${_VERSION}-py3-none-any.whl[pydantic-settings]
# Or install from .tar.gz file:
pip install ./dist/onion_config-${_VERSION}.tar.gz[pydantic-settings]
```

**E.** Install with pip editable **development mode** (from source code)

```sh
# Clone repository by git:
git clone https://github.com/bybatkhuu/module.python-config.git onion_config
cd ./onion_config

# Install with editable development mode:
# Pydantic-v1:
pip install -e .[pydantic-v1]

# Pydantic-v2:
pip install -e .[pydantic-settings]
```

**F.** Manually add to **PYTHONPATH** (not recommended)

```sh
# Clone repository by git:
git clone https://github.com/bybatkhuu/module.python-config.git onion_config
cd ./onion_config

# Install python dependencies:
# Pydantic-v1:
pip install -r ./requirements.txt

# Pydantic-v2:
pip install -r ./requirements.pydantic-v2.txt

# Add current path to PYTHONPATH:
export PYTHONPATH="${PWD}:${PYTHONPATH}"
```

## Usage/Examples

To use `onion_config`, import the `ConfigLoader` class from the package:

```python
from onion_config import ConfigLoader, BaseConfig
```

You can create an instance of `ConfigLoader` with `auto_load` flag. This will automatically load configuration data from environment variables and config files located in the default directory (`'./configs'`). The configuration data can then be accessed via the `config` property of the `ConfigLoader` instance:

```python
config: BaseConfig = ConfigLoader(auto_load=True).config
```

### **Simple**

[**`.env`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/simple/.env)

```sh
ENV=production
```

[**`configs/1.base.yml`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/simple/configs/1.base.yml):

```yaml
env: test

app:
  name: "My App"
  version: "0.0.1"
  nested:
    key: "value"
```

[**`configs/2.extra.yml`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/simple/configs/2.extra.yml):

```yaml
app:
  name: "New App"
  nested:
    some: "value"
  description: "Description of my app."

another_val:
  extra: 1
```

[**`main.py`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/simple/main.py)

```python
import pprint

from loguru import logger
try:
    import pydantic_settings

    _has_pydantic_settings = True
except ImportError:
    _has_pydantic_settings = False

from onion_config import ConfigLoader, BaseConfig


class ConfigSchema(BaseConfig):
    env: str = "local"

try:
    config: ConfigSchema = ConfigLoader(config_schema=ConfigSchema).load()
except Exception:
    logger.exception("Failed to load config:")
    exit(2)

if __name__ == "__main__":
    logger.info(f"All: {config}")
    logger.info(f"App name: {config.app['name']}")

    if _has_pydantic_settings:
        # Pydantic-v2:
        logger.info(f"Config:\n{pprint.pformat(config.model_dump())}\n")
    else:
        # Pydantic-v1:
        logger.info(f"Config:\n{pprint.pformat(config.dict())}\n")
```

Run the [**`examples/simple`**](https://github.com/bybatkhuu/module.python-config/tree/main/examples/simple):

```sh
cd ./examples/simple

python ./main.py
```

**Output**:

```txt
2023-09-01 00:00:00.000 | INFO     | __main__:<module>:29 - All: env='production' another_val={'extra': 1} app={'name': 'New App', 'version': '0.0.1', 'nested': {'key': 'value', 'some': 'value'}, 'description': 'Description of my app.'}
2023-09-01 00:00:00.000 | INFO     | __main__:<module>:30 - App name: New App
2023-09-01 00:00:00.000 | INFO     | __main__:<module>:35 - Config:
{'another_val': {'extra': 1},
 'app': {'description': 'Description of my app.',
         'name': 'New App',
         'nested': {'key': 'value', 'some': 'value'},
         'version': '0.0.1'},
 'env': 'production'}
```

### **Advanced**

[**`.env.base`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/.env.base):

```sh
ENV=development
DEBUG=true
APP_NAME="Old App"
ONION_CONFIG_EXTRA_DIR="extra_configs"
```

[**`.env.prod`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/.env.prod):

```sh
ENV=production
APP_NAME="New App"
APP_SECRET="my_secret"
```

[**`configs/config.yml`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/configs/config.yml):

```yaml
env: local

app:
  name: "My App"
  port: 9000
  bind_host: "0.0.0.0"
  version: "0.0.1"
  ignore_val: "Ignore me"

logger:
  output: "file"
```

[**`configs/logger.json`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/configs/logger.json):

```json
{
    "logger": {
        "level": "info",
        "output": "stdout"
    }
}
```

[**`configs_2/config.yml`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/configs_2/config.yml):

```yaml
extra:
  config:
    key1: 1
```

[**`configs_2/config_2.yml`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/configs_2/config_2.yml):

```yaml
extra:
  config:
    key2: 2
```

[**`extra_configs/extra.json`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/extra_configs/extra.json):

```json
{
    "extra": {
        "type": "json"
    }
}
```

[**`schema.py`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/schema.py):

```python
from enum import Enum
from typing import Union

import pydantic
from pydantic import Field, SecretStr
_has_pydantic_settings = False
if "2.0.0" <= pydantic.__version__:
    try:
        from pydantic_settings import SettingsConfigDict

        _has_pydantic_settings = True
    except ImportError:
        pass

from onion_config import BaseConfig


# Environments as Enum:
class EnvEnum(str, Enum):
    LOCAL = "local"
    DEVELOPMENT = "development"
    TEST = "test"
    DEMO = "demo"
    STAGING = "staging"
    PRODUCTION = "production"

# App config schema:
class AppConfig(BaseConfig):
    name: str = Field("App", min_length=2, max_length=32)
    bind_host: str = Field("localhost", min_length=2, max_length=128)
    port: int = Field(8000, ge=80, lt=65536)
    secret: SecretStr = Field(..., min_length=8, max_length=64)
    version: str = Field(..., min_length=5, max_length=16)
    description: Union[str, None] = Field(None, min_length=4, max_length=64)

    if _has_pydantic_settings:
        # Pydantic-v2:
        model_config = SettingsConfigDict(extra="ignore", env_prefix="APP_")
    else:
        # Pydantic-v1:
        class Config:
            extra = "ignore"
            env_prefix = "APP_"

# Main config schema:
class ConfigSchema(BaseConfig):
    env: EnvEnum = Field(EnvEnum.LOCAL)
    debug: bool = Field(False)
    app: AppConfig = Field(...)
```

[**`config.py`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/config.py):

```python
from loguru import logger

from onion_config import ConfigLoader

from schema import ConfigSchema


# Pre-load function to modify config data before loading and validation:
def _pre_load_hook(config_data: dict) -> dict:
    config_data["app"]["port"] = "80"
    config_data["extra_val"] = "Something extra!"
    return config_data

config = None
try:
    _config_loader = ConfigLoader(
        config_schema=ConfigSchema,
        configs_dirs=["configs", "configs_2", "/not_exists/path/configs_3"],
        env_file_paths=[".env", ".env.base", ".env.prod"],
        pre_load_hook=_pre_load_hook,
        config_data={"base": "start_value"},
        warn_mode="ALWAYS",
    )
    # Main config object:
    config: ConfigSchema = _config_loader.load()
except Exception:
    logger.exception("Failed to load config:")
    exit(2)

```

[**`main.py`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/main.py):

```python
import pprint

from loguru import logger
try:
    import pydantic_settings
    _has_pydantic_settings = True
except ImportError:
    _has_pydantic_settings = False

from config import config


if __name__ == "__main__":
    logger.info(f"All: {config}")
    logger.info(f"ENV: {config.env}")
    logger.info(f"DEBUG: {config.debug}")
    logger.info(f"Extra: {config.extra_val}")
    logger.info(f"Logger: {config.logger}")
    logger.info(f"App: {config.app}")
    logger.info(f"Secret: '{config.app.secret.get_secret_value()}'\n")

    if _has_pydantic_settings:
        # Pydantic-v2:
        logger.info(f"Config:\n{pprint.pformat(config.model_dump())}\n")
    else:
        # Pydantic-v1:
        logger.info(f"Config:\n{pprint.pformat(config.dict())}\n")

    try:
        # This will raise ValidationError
        config.app.port = 8443
    except Exception as e:
        logger.error(f"{e}\n")
```

Run the [**`examples/advanced`**](https://github.com/bybatkhuu/module.python-config/tree/main/examples/advanced):

```sh
cd ./examples/advanced

python ./main.py
```

**Output**:

```txt
2023-09-01 00:00:00.000 | INFO     | onion_config._base:load:143 - Loading all configs...
2023-09-01 00:00:00.000 | WARNING  | onion_config._base:_load_dotenv_file:201 - '/home/user/workspaces/projects/onion_config/examples/advanced/.env' file is not exist!
2023-09-01 00:00:00.000 | WARNING  | onion_config._base:_load_configs_dir:257 - '/not_exists/path/configs_3' directory is not exist!
2023-09-01 00:00:00.000 | SUCCESS  | onion_config._base:load:171 - Successfully loaded all configs!
2023-09-01 00:00:00.000 | INFO     | __main__:<module>:19 - All: env=<EnvEnum.PRODUCTION: 'production'> debug=True app=AppConfig(name='New App', bind_host='0.0.0.0', port=80, secret=SecretStr('**********'), version='0.0.1', description=None) extra={'config': {'key1': 1, 'key2': 2}, 'type': 'json'} extra_val='Something extra!' logger={'output': 'stdout', 'level': 'info'} base='start_value'
2023-09-01 00:00:00.000 | INFO     | __main__:<module>:20 - ENV: production
2023-09-01 00:00:00.000 | INFO     | __main__:<module>:21 - DEBUG: True
2023-09-01 00:00:00.000 | INFO     | __main__:<module>:22 - Extra: Something extra!
2023-09-01 00:00:00.000 | INFO     | __main__:<module>:23 - Logger: {'output': 'stdout', 'level': 'info'}
2023-09-01 00:00:00.000 | INFO     | __main__:<module>:24 - App: name='New App' bind_host='0.0.0.0' port=80 secret=SecretStr('**********') version='0.0.1' description=None
2023-09-01 00:00:00.000 | INFO     | __main__:<module>:25 - Secret: 'my_secret'

2023-09-01 00:00:00.000 | INFO     | __main__:<module>:30 - Config:
{'app': {'bind_host': '0.0.0.0',
         'description': None,
         'name': 'New App',
         'port': 80,
         'secret': SecretStr('**********'),
         'version': '0.0.1'},
 'base': 'start_value',
 'debug': True,
 'env': <EnvEnum.PRODUCTION: 'production'>,
 'extra': {'config': {'key1': 1, 'key2': 2}, 'type': 'json'},
 'extra_val': 'Something extra!',
 'logger': {'level': 'info', 'output': 'stdout'}}

2023-09-01 00:00:00.000 | ERROR    | __main__:<module>:36 - "AppConfig" is immutable and does not support item assignment
```

---

## Running Tests

To run tests, run the following command:

```sh
# Pydantic-v1:
pip install -r ./requirements.txt

# Pydantic-v2:
pip install -r ./requirements.pydantic-v2.txt

# Install python test dependencies:
pip install -r ./requirements.test.txt

# Run tests:
python -m pytest -v
```

## FAQ

### What is the order of loading config?

Load order:

1. Load all dotenv files from `env_file_paths` into environment variables.
2. Check if required environment variables exist or not.
3. Load all config files from `configs_dirs` into `config_data`.
4. Load extra config files from `extra_dir` into `config_data`.
5. Execute `pre_load_hook` method to modify `config_data`.
6. Init `config_schema` with `config_data` into final **`config`**.

## Environment Variables

You can use the following environment variables inside [**`.env.example`**](https://github.com/bybatkhuu/module.python-config/blob/main/.env.example) file:

```sh
ONION_CONFIG_EXTRA_DIR="./extra_configs"
```

## Documentation

- [docs](https://github.com/bybatkhuu/module.python-config/blob/main/docs/README.md)
- [scripts](https://github.com/bybatkhuu/module.python-config/blob/main/docs/scripts/README.md)

---

## References

- <https://docs.pydantic.dev>
- <https://github.com/pydantic/pydantic>
- <https://docs.pydantic.dev/latest/usage/pydantic_settings>
- <https://github.com/pydantic/pydantic-settings>
- <https://saurabh-kumar.com/python-dotenv>
- <https://github.com/theskumar/python-dotenv>
- <https://packaging.python.org/tutorials/packaging-projects>

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/bybatkhuu/module.python-config",
    "name": "onion-config",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "",
    "keywords": "onion_config,config,configs,dotenv,python-dotenv,pydantic,pydantic-config,pydantic-settings,custom-config",
    "author": "Batkhuu Byambajav",
    "author_email": "batkhuu10@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/73/dd/06f161d7d79356edcdfc1a63fccfa79e3022f18235ec50ba589c35d923d9/onion_config-5.1.0.tar.gz",
    "platform": null,
    "description": "# onion_config\n\n[![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/)\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bybatkhuu/module.python-config/2.build-publish.yml?logo=GitHub)](https://github.com/bybatkhuu/module.python-config/actions/workflows/2.build-publish.yml)\n[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/bybatkhuu/module.python-config?logo=GitHub)](https://github.com/bybatkhuu/module.python-config/releases)\n[![PyPI](https://img.shields.io/pypi/v/onion-config?logo=PyPi)](https://pypi.org/project/onion-config)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/onion-config?logo=Python)](https://docs.conda.io/en/latest/miniconda.html)\n\n`onion_config` is a python package that allows for easy configuration management. It allows for loading and validating configuration data from environment variables and config files in JSON and YAML formats.\n\n`Pydantic` based custom config package for python projects.\n\n## Features\n\n- **Main config** based on **Pydantic schema** - <https://pypi.org/project/pydantic>\n- Load **environment variables** - <https://pypi.org/project/python-dotenv>\n- Load from **multiple** configs directories\n- Load configs from **YAML** and **JSON** files\n- Update the default config with additional configurations (**`extra_dir`** directory)\n- **Pre-load hook** function to modify config data before loading and validation\n- **Validate config** values with **Pydantic validators**\n- Config as **dictionary** or **Pydantic model** (with type hints)\n- **Pre-defined** base config schema for common config (**`BaseConfig`**)\n- **Base** for custom config loader (**`ConfigLoader`**)\n\n---\n\n## Installation\n\n### 1. Prerequisites\n\n- **Python (>= v3.8)**\n- **PyPi (>= v23)**\n\n### 2. Install onion-config package\n\nChoose one of the following methods to install the package **[A ~ F]**:\n\n**A.** [**RECOMMENDED**] Install from **PyPi**\n\n```sh\n# Install or upgrade package:\n# Pydantic-v1:\npip install -U onion-config[pydantic-v1]\n\n# Pydantic-v2:\npip install -U onion-config[pydantic-settings]\n```\n\n**B.** Install latest version from **GitHub**\n\n```sh\n# Install package by git:\n# Pydantic-v1:\npip install git+https://github.com/bybatkhuu/module.python-config.git[pydantic-v1]\n\n# Pydantic-v2:\npip install git+https://github.com/bybatkhuu/module.python-config.git[pydantic-settings]\n```\n\n**C.** Install from **pre-built release** files\n\n1. Download **`.whl`** or **`.tar.gz`** file from **releases** - <https://github.com/bybatkhuu/module.python-config/releases>\n2. Install with pip:\n\n```sh\n# Pydantic-v1:\n# Install from .whl file:\npip install ./onion_config-[VERSION]-py3-none-any.whl[pydantic-v1]\n# Or install from .tar.gz file:\npip install ./onion_config-[VERSION].tar.gz[pydantic-v1]\n\n# Pydantic-v2:\n# Install from .whl file:\npip install ./onion_config-[VERSION]-py3-none-any.whl[pydantic-settings]\n# Or install from .tar.gz file:\npip install ./onion_config-[VERSION].tar.gz[pydantic-settings]\n```\n\n**D.** Install from **source code** by building package\n\n```sh\n# Clone repository by git:\ngit clone https://github.com/bybatkhuu/module.python-config.git onion_config\ncd ./onion_config\n\n# Install python build tool:\npip install -U pip build\n\n# Build python package:\npython -m build\n\n_VERSION=$(./scripts/get-version.sh)\n\n# Pydantic-v1:\n# Install from .whl file:\npip install ./dist/onion_config-${_VERSION}-py3-none-any.whl[pydantic-v1]\n# Or install from .tar.gz file:\npip install ./dist/onion_config-${_VERSION}.tar.gz[pydantic-v1]\n\n# Pydantic-v2:\n# Install from .whl file:\npip install ./dist/onion_config-${_VERSION}-py3-none-any.whl[pydantic-settings]\n# Or install from .tar.gz file:\npip install ./dist/onion_config-${_VERSION}.tar.gz[pydantic-settings]\n```\n\n**E.** Install with pip editable **development mode** (from source code)\n\n```sh\n# Clone repository by git:\ngit clone https://github.com/bybatkhuu/module.python-config.git onion_config\ncd ./onion_config\n\n# Install with editable development mode:\n# Pydantic-v1:\npip install -e .[pydantic-v1]\n\n# Pydantic-v2:\npip install -e .[pydantic-settings]\n```\n\n**F.** Manually add to **PYTHONPATH** (not recommended)\n\n```sh\n# Clone repository by git:\ngit clone https://github.com/bybatkhuu/module.python-config.git onion_config\ncd ./onion_config\n\n# Install python dependencies:\n# Pydantic-v1:\npip install -r ./requirements.txt\n\n# Pydantic-v2:\npip install -r ./requirements.pydantic-v2.txt\n\n# Add current path to PYTHONPATH:\nexport PYTHONPATH=\"${PWD}:${PYTHONPATH}\"\n```\n\n## Usage/Examples\n\nTo use `onion_config`, import the `ConfigLoader` class from the package:\n\n```python\nfrom onion_config import ConfigLoader, BaseConfig\n```\n\nYou can create an instance of `ConfigLoader` with `auto_load` flag. This will automatically load configuration data from environment variables and config files located in the default directory (`'./configs'`). The configuration data can then be accessed via the `config` property of the `ConfigLoader` instance:\n\n```python\nconfig: BaseConfig = ConfigLoader(auto_load=True).config\n```\n\n### **Simple**\n\n[**`.env`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/simple/.env)\n\n```sh\nENV=production\n```\n\n[**`configs/1.base.yml`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/simple/configs/1.base.yml):\n\n```yaml\nenv: test\n\napp:\n  name: \"My App\"\n  version: \"0.0.1\"\n  nested:\n    key: \"value\"\n```\n\n[**`configs/2.extra.yml`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/simple/configs/2.extra.yml):\n\n```yaml\napp:\n  name: \"New App\"\n  nested:\n    some: \"value\"\n  description: \"Description of my app.\"\n\nanother_val:\n  extra: 1\n```\n\n[**`main.py`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/simple/main.py)\n\n```python\nimport pprint\n\nfrom loguru import logger\ntry:\n    import pydantic_settings\n\n    _has_pydantic_settings = True\nexcept ImportError:\n    _has_pydantic_settings = False\n\nfrom onion_config import ConfigLoader, BaseConfig\n\n\nclass ConfigSchema(BaseConfig):\n    env: str = \"local\"\n\ntry:\n    config: ConfigSchema = ConfigLoader(config_schema=ConfigSchema).load()\nexcept Exception:\n    logger.exception(\"Failed to load config:\")\n    exit(2)\n\nif __name__ == \"__main__\":\n    logger.info(f\"All: {config}\")\n    logger.info(f\"App name: {config.app['name']}\")\n\n    if _has_pydantic_settings:\n        # Pydantic-v2:\n        logger.info(f\"Config:\\n{pprint.pformat(config.model_dump())}\\n\")\n    else:\n        # Pydantic-v1:\n        logger.info(f\"Config:\\n{pprint.pformat(config.dict())}\\n\")\n```\n\nRun the [**`examples/simple`**](https://github.com/bybatkhuu/module.python-config/tree/main/examples/simple):\n\n```sh\ncd ./examples/simple\n\npython ./main.py\n```\n\n**Output**:\n\n```txt\n2023-09-01 00:00:00.000 | INFO     | __main__:<module>:29 - All: env='production' another_val={'extra': 1} app={'name': 'New App', 'version': '0.0.1', 'nested': {'key': 'value', 'some': 'value'}, 'description': 'Description of my app.'}\n2023-09-01 00:00:00.000 | INFO     | __main__:<module>:30 - App name: New App\n2023-09-01 00:00:00.000 | INFO     | __main__:<module>:35 - Config:\n{'another_val': {'extra': 1},\n 'app': {'description': 'Description of my app.',\n         'name': 'New App',\n         'nested': {'key': 'value', 'some': 'value'},\n         'version': '0.0.1'},\n 'env': 'production'}\n```\n\n### **Advanced**\n\n[**`.env.base`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/.env.base):\n\n```sh\nENV=development\nDEBUG=true\nAPP_NAME=\"Old App\"\nONION_CONFIG_EXTRA_DIR=\"extra_configs\"\n```\n\n[**`.env.prod`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/.env.prod):\n\n```sh\nENV=production\nAPP_NAME=\"New App\"\nAPP_SECRET=\"my_secret\"\n```\n\n[**`configs/config.yml`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/configs/config.yml):\n\n```yaml\nenv: local\n\napp:\n  name: \"My App\"\n  port: 9000\n  bind_host: \"0.0.0.0\"\n  version: \"0.0.1\"\n  ignore_val: \"Ignore me\"\n\nlogger:\n  output: \"file\"\n```\n\n[**`configs/logger.json`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/configs/logger.json):\n\n```json\n{\n    \"logger\": {\n        \"level\": \"info\",\n        \"output\": \"stdout\"\n    }\n}\n```\n\n[**`configs_2/config.yml`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/configs_2/config.yml):\n\n```yaml\nextra:\n  config:\n    key1: 1\n```\n\n[**`configs_2/config_2.yml`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/configs_2/config_2.yml):\n\n```yaml\nextra:\n  config:\n    key2: 2\n```\n\n[**`extra_configs/extra.json`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/extra_configs/extra.json):\n\n```json\n{\n    \"extra\": {\n        \"type\": \"json\"\n    }\n}\n```\n\n[**`schema.py`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/schema.py):\n\n```python\nfrom enum import Enum\nfrom typing import Union\n\nimport pydantic\nfrom pydantic import Field, SecretStr\n_has_pydantic_settings = False\nif \"2.0.0\" <= pydantic.__version__:\n    try:\n        from pydantic_settings import SettingsConfigDict\n\n        _has_pydantic_settings = True\n    except ImportError:\n        pass\n\nfrom onion_config import BaseConfig\n\n\n# Environments as Enum:\nclass EnvEnum(str, Enum):\n    LOCAL = \"local\"\n    DEVELOPMENT = \"development\"\n    TEST = \"test\"\n    DEMO = \"demo\"\n    STAGING = \"staging\"\n    PRODUCTION = \"production\"\n\n# App config schema:\nclass AppConfig(BaseConfig):\n    name: str = Field(\"App\", min_length=2, max_length=32)\n    bind_host: str = Field(\"localhost\", min_length=2, max_length=128)\n    port: int = Field(8000, ge=80, lt=65536)\n    secret: SecretStr = Field(..., min_length=8, max_length=64)\n    version: str = Field(..., min_length=5, max_length=16)\n    description: Union[str, None] = Field(None, min_length=4, max_length=64)\n\n    if _has_pydantic_settings:\n        # Pydantic-v2:\n        model_config = SettingsConfigDict(extra=\"ignore\", env_prefix=\"APP_\")\n    else:\n        # Pydantic-v1:\n        class Config:\n            extra = \"ignore\"\n            env_prefix = \"APP_\"\n\n# Main config schema:\nclass ConfigSchema(BaseConfig):\n    env: EnvEnum = Field(EnvEnum.LOCAL)\n    debug: bool = Field(False)\n    app: AppConfig = Field(...)\n```\n\n[**`config.py`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/config.py):\n\n```python\nfrom loguru import logger\n\nfrom onion_config import ConfigLoader\n\nfrom schema import ConfigSchema\n\n\n# Pre-load function to modify config data before loading and validation:\ndef _pre_load_hook(config_data: dict) -> dict:\n    config_data[\"app\"][\"port\"] = \"80\"\n    config_data[\"extra_val\"] = \"Something extra!\"\n    return config_data\n\nconfig = None\ntry:\n    _config_loader = ConfigLoader(\n        config_schema=ConfigSchema,\n        configs_dirs=[\"configs\", \"configs_2\", \"/not_exists/path/configs_3\"],\n        env_file_paths=[\".env\", \".env.base\", \".env.prod\"],\n        pre_load_hook=_pre_load_hook,\n        config_data={\"base\": \"start_value\"},\n        warn_mode=\"ALWAYS\",\n    )\n    # Main config object:\n    config: ConfigSchema = _config_loader.load()\nexcept Exception:\n    logger.exception(\"Failed to load config:\")\n    exit(2)\n\n```\n\n[**`main.py`**](https://github.com/bybatkhuu/module.python-config/blob/main/examples/advanced/main.py):\n\n```python\nimport pprint\n\nfrom loguru import logger\ntry:\n    import pydantic_settings\n    _has_pydantic_settings = True\nexcept ImportError:\n    _has_pydantic_settings = False\n\nfrom config import config\n\n\nif __name__ == \"__main__\":\n    logger.info(f\"All: {config}\")\n    logger.info(f\"ENV: {config.env}\")\n    logger.info(f\"DEBUG: {config.debug}\")\n    logger.info(f\"Extra: {config.extra_val}\")\n    logger.info(f\"Logger: {config.logger}\")\n    logger.info(f\"App: {config.app}\")\n    logger.info(f\"Secret: '{config.app.secret.get_secret_value()}'\\n\")\n\n    if _has_pydantic_settings:\n        # Pydantic-v2:\n        logger.info(f\"Config:\\n{pprint.pformat(config.model_dump())}\\n\")\n    else:\n        # Pydantic-v1:\n        logger.info(f\"Config:\\n{pprint.pformat(config.dict())}\\n\")\n\n    try:\n        # This will raise ValidationError\n        config.app.port = 8443\n    except Exception as e:\n        logger.error(f\"{e}\\n\")\n```\n\nRun the [**`examples/advanced`**](https://github.com/bybatkhuu/module.python-config/tree/main/examples/advanced):\n\n```sh\ncd ./examples/advanced\n\npython ./main.py\n```\n\n**Output**:\n\n```txt\n2023-09-01 00:00:00.000 | INFO     | onion_config._base:load:143 - Loading all configs...\n2023-09-01 00:00:00.000 | WARNING  | onion_config._base:_load_dotenv_file:201 - '/home/user/workspaces/projects/onion_config/examples/advanced/.env' file is not exist!\n2023-09-01 00:00:00.000 | WARNING  | onion_config._base:_load_configs_dir:257 - '/not_exists/path/configs_3' directory is not exist!\n2023-09-01 00:00:00.000 | SUCCESS  | onion_config._base:load:171 - Successfully loaded all configs!\n2023-09-01 00:00:00.000 | INFO     | __main__:<module>:19 - All: env=<EnvEnum.PRODUCTION: 'production'> debug=True app=AppConfig(name='New App', bind_host='0.0.0.0', port=80, secret=SecretStr('**********'), version='0.0.1', description=None) extra={'config': {'key1': 1, 'key2': 2}, 'type': 'json'} extra_val='Something extra!' logger={'output': 'stdout', 'level': 'info'} base='start_value'\n2023-09-01 00:00:00.000 | INFO     | __main__:<module>:20 - ENV: production\n2023-09-01 00:00:00.000 | INFO     | __main__:<module>:21 - DEBUG: True\n2023-09-01 00:00:00.000 | INFO     | __main__:<module>:22 - Extra: Something extra!\n2023-09-01 00:00:00.000 | INFO     | __main__:<module>:23 - Logger: {'output': 'stdout', 'level': 'info'}\n2023-09-01 00:00:00.000 | INFO     | __main__:<module>:24 - App: name='New App' bind_host='0.0.0.0' port=80 secret=SecretStr('**********') version='0.0.1' description=None\n2023-09-01 00:00:00.000 | INFO     | __main__:<module>:25 - Secret: 'my_secret'\n\n2023-09-01 00:00:00.000 | INFO     | __main__:<module>:30 - Config:\n{'app': {'bind_host': '0.0.0.0',\n         'description': None,\n         'name': 'New App',\n         'port': 80,\n         'secret': SecretStr('**********'),\n         'version': '0.0.1'},\n 'base': 'start_value',\n 'debug': True,\n 'env': <EnvEnum.PRODUCTION: 'production'>,\n 'extra': {'config': {'key1': 1, 'key2': 2}, 'type': 'json'},\n 'extra_val': 'Something extra!',\n 'logger': {'level': 'info', 'output': 'stdout'}}\n\n2023-09-01 00:00:00.000 | ERROR    | __main__:<module>:36 - \"AppConfig\" is immutable and does not support item assignment\n```\n\n---\n\n## Running Tests\n\nTo run tests, run the following command:\n\n```sh\n# Pydantic-v1:\npip install -r ./requirements.txt\n\n# Pydantic-v2:\npip install -r ./requirements.pydantic-v2.txt\n\n# Install python test dependencies:\npip install -r ./requirements.test.txt\n\n# Run tests:\npython -m pytest -v\n```\n\n## FAQ\n\n### What is the order of loading config?\n\nLoad order:\n\n1. Load all dotenv files from `env_file_paths` into environment variables.\n2. Check if required environment variables exist or not.\n3. Load all config files from `configs_dirs` into `config_data`.\n4. Load extra config files from `extra_dir` into `config_data`.\n5. Execute `pre_load_hook` method to modify `config_data`.\n6. Init `config_schema` with `config_data` into final **`config`**.\n\n## Environment Variables\n\nYou can use the following environment variables inside [**`.env.example`**](https://github.com/bybatkhuu/module.python-config/blob/main/.env.example) file:\n\n```sh\nONION_CONFIG_EXTRA_DIR=\"./extra_configs\"\n```\n\n## Documentation\n\n- [docs](https://github.com/bybatkhuu/module.python-config/blob/main/docs/README.md)\n- [scripts](https://github.com/bybatkhuu/module.python-config/blob/main/docs/scripts/README.md)\n\n---\n\n## References\n\n- <https://docs.pydantic.dev>\n- <https://github.com/pydantic/pydantic>\n- <https://docs.pydantic.dev/latest/usage/pydantic_settings>\n- <https://github.com/pydantic/pydantic-settings>\n- <https://saurabh-kumar.com/python-dotenv>\n- <https://github.com/theskumar/python-dotenv>\n- <https://packaging.python.org/tutorials/packaging-projects>\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "'onion_config' is a python package that allows for easy configuration management. It allows for loading and validating configuration data from environment variables and config files in JSON and YAML formats.",
    "version": "5.1.0",
    "project_urls": {
        "Download": "https://github.com/bybatkhuu/module.python-config/archive/v5.1.0.tar.gz",
        "Homepage": "https://github.com/bybatkhuu/module.python-config"
    },
    "split_keywords": [
        "onion_config",
        "config",
        "configs",
        "dotenv",
        "python-dotenv",
        "pydantic",
        "pydantic-config",
        "pydantic-settings",
        "custom-config"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ce31fb29bf348804132792fe7c638718453297d181a106d9acb68a45cc052c4f",
                "md5": "b37882a4e9eb2a13b36132ec82dd87c4",
                "sha256": "a7f3caa3e9f8e4a91bec3613ddc9c0008213f2cf87256beee0c1bb19b53bc6cd"
            },
            "downloads": -1,
            "filename": "onion_config-5.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "b37882a4e9eb2a13b36132ec82dd87c4",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 16421,
            "upload_time": "2023-09-21T05:59:12",
            "upload_time_iso_8601": "2023-09-21T05:59:12.416075Z",
            "url": "https://files.pythonhosted.org/packages/ce/31/fb29bf348804132792fe7c638718453297d181a106d9acb68a45cc052c4f/onion_config-5.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "73dd06f161d7d79356edcdfc1a63fccfa79e3022f18235ec50ba589c35d923d9",
                "md5": "583ed890d00e0b571632fcc249b07580",
                "sha256": "2bfce053c0f283b5724f31454338277cb396187994c047f71aa6e3d5f8965337"
            },
            "downloads": -1,
            "filename": "onion_config-5.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "583ed890d00e0b571632fcc249b07580",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 18867,
            "upload_time": "2023-09-21T05:59:13",
            "upload_time_iso_8601": "2023-09-21T05:59:13.802777Z",
            "url": "https://files.pythonhosted.org/packages/73/dd/06f161d7d79356edcdfc1a63fccfa79e3022f18235ec50ba589c35d923d9/onion_config-5.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-09-21 05:59:13",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "bybatkhuu",
    "github_project": "module.python-config",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "onion-config"
}
        
Elapsed time: 0.14085s