Skip to content
This repository was archived by the owner on Sep 30, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/2.build-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install -r ./requirements.txt
python -m pip install pytest==7.4.1 pytest-cov==4.1.0
python -m pip install -r ./requirements.test.txt
- name: Test with pytest
run: python -m pytest -sv
# run: python -m pytest -sv -o log_cli=true
Expand All @@ -50,7 +49,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install build==0.10.0 twine==4.0.2
python -m pip install -r ./requirements.build.txt
- name: Build and publish package
# run: |
# echo -e "[testpypi]\nusername = __token__\npassword = ${{ secrets.TEST_PYPI_API_TOKEN }}" > ~/.pypirc
Expand Down
205 changes: 147 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![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.
`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.

Expand Down Expand Up @@ -109,44 +109,94 @@ export PYTHONPATH="${PWD}:${PYTHONPATH}"

## Usage/Examples

### **Simple**

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

```python
from onion_config import ConfigLoader
```

You can then create an instance of `ConfigLoader`:

```python
config_loader = ConfigLoader(auto_load=True)
from onion_config import ConfigLoader, BaseConfig
```

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:
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 = config_loader.config
config: BaseConfig = ConfigLoader(auto_load=True).config
```

### **Advanced**
### **Simple**

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

```yaml
env: production
env: test

app:
name: "My App"
bind_host: "0.0.0.0"
version: "0.0.1"
ignore_val: "Ignore me"
nested:
key: "value"
```

logger:
output: "stdout"
[**`configs/2.extra.yml`**](https://github.com/bybatkhuu/mod.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
```

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

```python
import sys
import pprint
import logging

from onion_config import ConfigLoader, BaseConfig


logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logger = logging.getLogger(__name__)


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

if __name__ == "__main__":
logger.info(f" App name: {config.app['name']}")
logger.info(f" Config:\n{pprint.pformat(config.model_dump())}\n")
```

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

```sh
cd ./examples/simple

python ./main.py
```

Output:

```txt
INFO:__main__: App name: New App
INFO:__main__: Config:
{'another_val': {'extra': 1},
'app': {'description': 'Description of my app.',
'name': 'New App',
'nested': {'key': 'value', 'some': 'value'},
'version': '0.0.1'},
'env': 'test'}
```

### **Advanced**

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

```json
{
Expand All @@ -158,7 +208,23 @@ logger:
}
```

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

```yaml
env: production

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

logger:
output: "stdout"
```

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

```sh
ENV=development
Expand All @@ -169,31 +235,27 @@ APP_NAME="New App"
APP_SECRET="my_secret"
```

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

```python
import sys
import pprint
import logging

logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logger = logging.getLogger(__name__)
```

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

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

from pydantic import Field, SecretStr
from pydantic_settings import SettingsConfigDict

from onion_config import ConfigLoader, BaseConfig


logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logger = logging.getLogger(__name__)


# 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!"
from onion_config import BaseConfig

return config_data

# Environments as Enum:
class EnvEnum(str, Enum):
Expand All @@ -219,18 +281,45 @@ class ConfigSchema(BaseConfig):
env: EnvEnum = Field(EnvEnum.LOCAL)
debug: bool = Field(False)
app: AppConfig = Field(...)
```

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

if __name__ == "__main__":
try:
# Main 'config' object for usage:
config: ConfigSchema = ConfigLoader(
config_schema=ConfigSchema, pre_load_hook=_pre_load_hook
).load()
except Exception:
logger.exception("Failed to load config:")
exit(2)
```python
from onion_config import ConfigLoader

from logger import logger
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, pre_load_hook=_pre_load_hook
)
# Main config object:
config: ConfigSchema = _config_loader.load()
except Exception:
logger.exception("Failed to load config:")
exit(2)
```

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

```python
import pprint

from config import config
from logger import logger


if __name__ == "__main__":
logger.info(f" ENV: {config.env}")
logger.info(f" DEBUG: {config.debug}")
logger.info(f" Extra: {config.extra_val}")
Expand All @@ -246,25 +335,25 @@ if __name__ == "__main__":
logger.error(f" {e}\n")
```

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

```sh
cd ./examples
cd ./examples/advanced

python ./main.py
python ./app.py
```

Output:

```txt
INFO:__main__: ENV: development
INFO:__main__: DEBUG: True
INFO:__main__: Extra: Something extra!
INFO:__main__: Logger: {'level': 'info', 'output': 'stdout'}
INFO:__main__: App: name='New App' bind_host='0.0.0.0' port=80 secret=SecretStr('**********') version='0.0.1' description=None
INFO:__main__: Secret: 'my_secret'

INFO:__main__: Config:
INFO:logger: ENV: development
INFO:logger: DEBUG: True
INFO:logger: Extra: Something extra!
INFO:logger: Logger: {'level': 'info', 'output': 'stdout'}
INFO:logger: App: name='New App' bind_host='0.0.0.0' port=80 secret=SecretStr('**********') version='0.0.1' description=None
INFO:logger: Secret: 'my_secret'

INFO:logger: Config:
{'app': {'bind_host': '0.0.0.0',
'description': None,
'name': 'New App',
Expand All @@ -276,7 +365,7 @@ INFO:__main__: Config:
'extra_val': 'Something extra!',
'logger': {'level': 'info', 'output': 'stdout'}}

ERROR:__main__: 1 validation error for AppConfig
ERROR:logger: 1 validation error for AppConfig
port
Instance is frozen [type=frozen_instance, input_value=8443, input_type=int]
```
Expand All @@ -288,8 +377,8 @@ port
To run tests, run the following command:

```sh
# Install python development dependencies:
pip install -r ./requirements.dev.txt
# Install python test dependencies:
pip install -r ./requirements.test.txt

# Run tests:
python -m pytest -sv
Expand Down
6 changes: 0 additions & 6 deletions examples/.env

This file was deleted.

23 changes: 23 additions & 0 deletions examples/advanced/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pprint

from config import config
from logger import logger


if __name__ == "__main__":
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")
logger.info(f" Config:\n{pprint.pformat(config.model_dump())}\n")

try:
# This will raise ValidationError
config.app.port = 8443
except Exception as e:
logger.error(f" {e}\n")
25 changes: 25 additions & 0 deletions examples/advanced/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-

from onion_config import ConfigLoader

from logger import logger
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, pre_load_hook=_pre_load_hook
)
# Main config object:
config: ConfigSchema = _config_loader.load()
except Exception:
logger.exception("Failed to load config:")
exit(2)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ env: production

app:
name: "My App"
port: 9000
bind_host: "0.0.0.0"
version: "0.0.1"
ignore_val: "Ignore me"
Expand Down
8 changes: 8 additions & 0 deletions examples/advanced/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-

import sys
import logging


logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logger = logging.getLogger(__name__)
Loading