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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ONION_CONFIG_EXTRA_DIR="./extra_configs_dir"
ONION_CONFIG_EXTRA_DIR="./extra_dir"
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,6 @@ logs
backup
backups
archive
extra_configs
pythonpath
*.bak
*.pyc
Expand All @@ -511,5 +510,4 @@ logs/
backup/
backups/
archive/
extra_configs/
pythonpath/
112 changes: 83 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ config: BaseConfig = ConfigLoader(auto_load=True).config

### **Simple**

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

```sh
ENV=production
```

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

```yaml
Expand Down Expand Up @@ -162,8 +168,12 @@ logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logger = logging.getLogger(__name__)


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


try:
config: BaseConfig = ConfigLoader().load()
config: ConfigSchema = ConfigLoader(config_schema=ConfigSchema).load()
except Exception:
logger.exception("Failed to load config:")
exit(2)
Expand Down Expand Up @@ -191,27 +201,32 @@ INFO:__main__: Config:
'name': 'New App',
'nested': {'key': 'value', 'some': 'value'},
'version': '0.0.1'},
'env': 'test'}
'env': 'production'}
```

### **Advanced**

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

```json
{
"logger":
{
"level": "info",
"output": "file"
}
}
```sh
ENV=development
DEBUG=true
APP_NAME="Old App"
ONION_CONFIG_EXTRA_DIR="extra_configs"
```

[**`.env.prod`**](https://github.com/bybatkhuu/mod.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/mod.python-config/blob/main/examples/advanced/configs/config.yml):

```yaml
env: production
env: local

app:
name: "My App"
Expand All @@ -221,18 +236,44 @@ app:
ignore_val: "Ignore me"

logger:
output: "stdout"
output: "file"
```

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

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

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

```sh
ENV=development
```yaml
extra:
config:
key1: 1
```

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

APP_NAME="New App"
APP_SECRET="my_secret"
```yaml
extra:
config:
key2: 2
```

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

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

[**`logger.py`**](https://github.com/bybatkhuu/mod.python-config/blob/main/examples/advanced/logger.py):
Expand Down Expand Up @@ -301,7 +342,12 @@ def _pre_load_hook(config_data: dict) -> dict:
config = None
try:
_config_loader = ConfigLoader(
config_schema=ConfigSchema, pre_load_hook=_pre_load_hook
config_schema=ConfigSchema,
configs_dirs=["configs", "configs_2", "/not_exixts/path/configs_3"],
env_file_paths=[".env", ".env.base", ".env.prod"],
pre_load_hook=_pre_load_hook,
config_data={"base": "start_value"},
quiet=False,
)
# Main config object:
config: ConfigSchema = _config_loader.load()
Expand Down Expand Up @@ -346,10 +392,12 @@ python ./app.py
Output:

```txt
INFO:logger: ENV: development
WARNING:onion_config._base:'/home/user/workspaces/projects/onion_config/examples/advanced/.env' file is not exist!
WARNING:onion_config._base:'/not_exixts/path/configs_3' directory is not exist!
INFO:logger: ENV: production
INFO:logger: DEBUG: True
INFO:logger: Extra: Something extra!
INFO:logger: Logger: {'level': 'info', 'output': 'stdout'}
INFO:logger: Logger: {'output': 'stdout', 'level': 'info'}
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'

Expand All @@ -360,8 +408,10 @@ INFO:logger: Config:
'port': 80,
'secret': SecretStr('**********'),
'version': '0.0.1'},
'base': 'start_value',
'debug': True,
'env': <EnvEnum.DEVELOPMENT: 'development'>,
'env': <EnvEnum.PRODUCTION: 'production'>,
'extra': {'config': {'key1': 1, 'key2': 2}, 'type': 'json'},
'extra_val': 'Something extra!',
'logger': {'level': 'info', 'output': 'stdout'}}

Expand Down Expand Up @@ -390,19 +440,23 @@ python -m pytest -sv

Load order:

1. Load environment variables from `.env` file.
2. Check required environment variables are exist or not.
3. Load config files from `configs_dir` into `config_data`.
4. Load extra config files from `extra_configs_dir` into `config_data`.
1. Load all dotenv files from `env_file_paths` into environment variables.
1.1. Load each dotenv file into environment variables.
2. Check if required environment variables exist or not.
3. Load all config files from `configs_dirs` into `config_data`.
3.1. Load config files from each config directory into `config_data`.
3.1.a. Load each YAML config file into `config_data`.
3.1.b. Load each JSON config file 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`**.
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/mod.python-config/blob/main/.env.example) file:

```sh
ONION_CONFIG_EXTRA_DIR="./extra_configs_dir"
ONION_CONFIG_EXTRA_DIR="./extra_dir"
```

## Documentation
Expand Down
37 changes: 23 additions & 14 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,45 @@ The `ConfigLoader` class is the main class of the `onion_config` package. An ins

`ConfigLoader` instances have the following properties:

- **config**: Main config object (config_schema) for the project. It is an instance of a `BaseConfig`, `BaseSettings`, or `BaseModel` class, which holds the loaded and validated configuration data. Defaults to `None`.
- **config**: Main config object (based on `config_schema`) for the project. It is an instance of a `BaseConfig`, `BaseSettings`, or `BaseModel` class, which holds the loaded and validated configuration data. Defaults to `BaseConfig`.
- **config_schema**: Main config schema class to load and validate configs. Defaults to `BaseConfig`.
- **config_data**: Loaded data from config files as a dictionary. Defaults to `{}`.
- **configs_dir**: Main configs directory to load all config files. Defaults to `'./configs'`.
- **extra_configs_dir**: Extra configs directory to load extra config files. Defaults to `None`, but will use the `ONION_CONFIG_EXTRA_DIR` environment variable if set.
- **env_file_path**: '.env' file path to load. Defaults to `.env`.
- **configs_dirs**: Main configs directories as list to load all config files. Defaults to `['./configs']`.
- **extra_dir**: Extra configs directory to load extra config files. Defaults to `None`, but will use the `ONION_CONFIG_EXTRA_DIR` environment variable if set.
- **env_file_paths**: Dotenv file paths as list to load. Defaults to `['.env']`.
- **required_envs**: Required environment variables to check. Defaults to `[]`.
- **pre_load_hook**: Custom pre-load method, this method will be executed before validating `config`. Defaults to `lambda config_data: config_data`.
- **quiet**: Quiet mode to suppress all warning logs. Defaults to `True`.

### Methods

`ConfigLoader` instances have the following methods:

- **load()**: 0. Load and validate every config into `config`.
- **_load_dotenv()**: 1. Loading environment variables from `.env` file, if it exists.
- **_load_dotenv_files()**: 1. Load all dotenv files from `env_file_paths` into environment variables.
- **_load_dotenv_file()**: 1.1. Load each dotenv file into environment variables.
- **_check_required_envs()**: 2. Check if required environment variables exist.
- **_load_config_files()**: 3. Load config files from `configs_dir` into `config_data`.
- **_load_extra_config_files()**: 4. Load extra config files from `extra_configs_dir` and update `config_data`.
- **_load_configs_dirs()**: 3. Load all config files from `configs_dirs` into `config_data`.
- **_load_configs_dir()**: 3.1. Load config files from each config directory into `config_data`.
- **_load_yaml_file()**: 3.1.a. Load each YAML config file into `config_data`.
- **_load_json_file()**: 3.1.b. Load each JSON config file into `config_data`.
- **_load_extra_dir()**: 4. Load extra config files from `extra_dir` into `config_data`.

### Load order

1. Load environment variables from `.env` file.
2. Check required environment variables are exist or not.
3. Load config files from `configs_dir` into `config_data`.
4. Load extra config files from `extra_configs_dir` into `config_data`.
1. Load all dotenv files from `env_file_paths` into environment variables.
1.1. Load each dotenv file into environment variables.
2. Check if required environment variables exist or not.
3. Load all config files from `configs_dirs` into `config_data`.
3.1. Load config files from each config directory into `config_data`.
3.1.a. Load each YAML config file into `config_data`.
3.1.b. Load each JSON config file 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`**.
6. Init `config_schema` with `config_data` into final `config`.

## BaseConfig Class

The `BaseConfig` class is a subclass of `pydantic.BaseSettings` that is used as the default schema for validating configuration data in `onion_config`. It allows for arbitrary types and additional properties (extra).
The `BaseConfig` class is a subclass of `pydantic_settings.BaseSettings` that is used as the default schema for validating configuration data in `onion_config`. It allows for arbitrary types and additional properties (extra).

It also overrides the `customise_sources` method to specify the order in which the configuration sources are checked. In `onion_config`, the environment variables are checked first, followed by the initial settings and then the file secret settings.
It also overrides the `customise_sources` method to specify the order in which the configuration sources are checked. In `onion_config`, the dotenv files are checked first, environment variables are second, followed by the initial settings and then the file secret settings.
18 changes: 18 additions & 0 deletions docs/scripts/4.test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# test.sh

This script is used to run the pytest tests for the project.

The script performs the following operations:

- **Loading base script**: Includes the `base.sh` script to gain access to its utility functions and environment variables.
- **Running pytest**: Runs the pytest tests for the project.

**Usage**:

To execute the test script, simply run the following command in the terminal:

```sh
./test.sh
```

**Source code**: [**`test.sh`**](../../scripts/test.sh)
File renamed without changes.
8 changes: 5 additions & 3 deletions docs/scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ This document provides an overview and usage instructions for the following scri
- [**`base.sh`**](./1.base.md)
- [**`clean.sh`**](./2.clean.md)
- [**`get-version.sh`**](./3.get-version.md)
- [**`bump-version.sh`**](./4.bump-version.md)
- [**`build.sh`**](./5.build.md)
- [**`test.sh`**](./4.test.md)
- [**`bump-version.sh`**](./5.bump-version.md)
- [**`build.sh`**](./6.build.md)

All the scripts are located in the [**`scripts`**](../../scripts) directory:

Expand All @@ -16,7 +17,8 @@ scripts/
├── build.sh
├── bump-version.sh
├── clean.sh
└── get-version.sh
├── get-version.sh
└── test.sh
```

These scripts are designed to be used in a Linux or macOS environment. They may work in a Windows environment with the appropriate tools installed, but this is not guaranteed.
4 changes: 4 additions & 0 deletions examples/advanced/.env.base
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ENV=development
DEBUG=true
APP_NAME="Old App"
ONION_CONFIG_EXTRA_DIR="extra_configs"
5 changes: 1 addition & 4 deletions examples/advanced/.env → examples/advanced/.env.prod
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
ENV=development

DEBUG=true

ENV=production
APP_NAME="New App"
APP_SECRET="my_secret"
8 changes: 6 additions & 2 deletions examples/advanced/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ def _pre_load_hook(config_data: dict) -> dict:
config_data["extra_val"] = "Something extra!"
return config_data


config = None
try:
_config_loader = ConfigLoader(
config_schema=ConfigSchema, pre_load_hook=_pre_load_hook
config_schema=ConfigSchema,
configs_dirs=["configs", "configs_2", "/not_exixts/path/configs_3"],
env_file_paths=[".env", ".env.base", ".env.prod"],
pre_load_hook=_pre_load_hook,
config_data={"base": "start_value"},
quiet=False,
)
# Main config object:
config: ConfigSchema = _config_loader.load()
Expand Down
4 changes: 2 additions & 2 deletions examples/advanced/configs/config.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
env: production
env: local

app:
name: "My App"
Expand All @@ -8,4 +8,4 @@ app:
ignore_val: "Ignore me"

logger:
output: "stdout"
output: "file"
7 changes: 3 additions & 4 deletions examples/advanced/configs/logger.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"logger":
{
"logger": {
"level": "info",
"output": "file"
"output": "stdout"
}
}
}
3 changes: 3 additions & 0 deletions examples/advanced/configs_2/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extra:
config:
key1: 1
3 changes: 3 additions & 0 deletions examples/advanced/configs_2/config_2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extra:
config:
key2: 2
5 changes: 5 additions & 0 deletions examples/advanced/extra_configs/extra.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extra": {
"type": "json"
}
}
1 change: 0 additions & 1 deletion examples/advanced/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
import sys
import logging


logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logger = logging.getLogger(__name__)
1 change: 1 addition & 0 deletions examples/simple/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ENV=production
2 changes: 1 addition & 1 deletion examples/simple/configs/1.base.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
env: test
env: local

app:
name: "My App"
Expand Down
Loading