Configuration helper for Python applications.
gConfigs provides a unified API to read configuration values from different sources:
- Environment variables
- Dotenv files
- INI files
- TOML files
- Local mounted files in a directory
- Individual local files
Why another config library?
I made it for myself. That's it.
Python 3.11+
pip install gconfigsor with uv:
uv add gconfigsOr even better. It's small enough, avoid the dependency entirely, and copy gconfigs/ into your project.
Extra effort to convince you to avoid dependencies: maybe all you need is just os.environ.
import gconfigs
# 1) Environment variables
envs = gconfigs.envs()
debug = envs("DEBUG", default=False, cast=bool)
home = envs("HOME", default="/")
# 2) Mounted directory (for configs or secrets)
configs = gconfigs.local_files("/run/configs")
language_code = configs("LANGUAGE_CODE", default="en-us")
# 3) Single local file
secrets = gconfigs.local_file()
db_password = secrets("/run/secrets/DB_PASSWORD")
# 4) Dotenv file
dotenvs = gconfigs.dotenvs(".env")
project_name = dotenvs("PROJECT_NAME", default="my-app")
# 5) INI file
ini = gconfigs.ini_file("./config/settings.ini")
app_name = ini("app.name", default="my-app")
# 6) TOML file
toml = gconfigs.toml_file("./config/settings.toml")
db_port = toml("database.port", cast=int)The package exposes six factory functions:
- gconfigs.envs() -> reads from process environment variables
- gconfigs.dotenvs(filepath=".env") -> reads from dotenv files
- gconfigs.ini_file(filepath=".ini") -> reads from INI files using section.option keys
- gconfigs.toml_file(filepath=".toml") -> reads from TOML files using dotted keys
- gconfigs.local_files(path="/run/configs", pattern="*") -> reads from files in a directory
- gconfigs.local_file() -> reads from a single file path provided at call time
Each factory returns a GConfigs instance.
Main call signature:
GConfigs.get(key, *, default=NOTSET, use_instead=NOTSET, strip=None, cast=None, list_sep=None, bool_values=None, **backend_kwargs)import gconfigs
envs = gconfigs.envs()
home = envs("HOME")
workers = envs("WORKERS", default="2", cast=int)
debug = envs("DEBUG", default=False, cast=bool)import gconfigs
dotenvs = gconfigs.dotenvs("./config/.env")
dsn = dotenvs("DATABASE_DSN")
# load another dotenv file with a different GConfigs instance
other = gconfigs.dotenvs("./config/another.env")import gconfigs
ini = gconfigs.ini_file("./config/settings.ini")
app_name = ini("app.name")
db_host = ini("database.host")
db_port = ini("database.port", cast=int)INI parser behavior:
- Keys use section.option format
- Values are read as strings by default
- Use cast to convert values
- Missing section/option raises KeyError
import gconfigs
toml = gconfigs.toml_file("./config/settings.toml")
app_name = toml("name")
db_host = toml("database.host")
db_port = toml("database.port", cast=int)
pool_size = toml("database.pool.size", cast=int)TOML parser behavior:
- Keys use dotted access for nested tables
- Values preserve native TOML types (e.g. int, bool, list)
- Missing nested keys raise KeyError
Dotenv parser behavior:
- Ignores lines starting with #, ;, and [section]
- Ignores lines without =
- Splits at the first = so values can contain =
- Strips key whitespace
- Preserves value whitespace (except trailing newline characters)
- Last duplicated key wins
import gconfigs
configs = gconfigs.local_files("/run/configs")
secrets = gconfigs.local_files("/run/secrets")
api_url = configs("API_URL")
api_key = secrets("API_KEY")How it works:
- Relative file path is the config key
- File content is the config value
- Optional pattern argument filters which files are considered
- Keys are file names directly inside the configured base path
- Reads are restricted to the configured base path (path traversal like
../secretis blocked) - Recursive access is intentionally not supported (nested paths like
nested/key.txtare rejected)
only_app = gconfigs.local_files("/run/configs", pattern="APP_*")* Uses fnmatch for pattern matching. fnmatch docs
import gconfigs
secrets = gconfigs.local_file()
token = secrets("/run/secrets/SERVICE_TOKEN")port = envs("PORT", default="8000")host = envs("SERVICE_HOST", use_instead="HOST", default="127.0.0.1")By default, returned string values are stripped.
value = envs("MY_KEY") # strip=True by default
raw_value = envs("MY_KEY", strip=False)Use cast in GConfigs.get (or by calling the instance directly) to convert values.
- Accepts native bool values
- Accepts case-insensitive string pairs from bool_values
- Default bool_values pairs:
- ("true", "false")
- ("1", "0")
- ("yes", "no")
- ("y", "n")
- ("on", "off")
debug = envs("DEBUG", default=False, cast=bool)
feature_enabled = envs(
"FEATURE_X",
cast=bool,
bool_values=(("enabled", "disabled"),),
)- Accepts native iterable values for conversion between list/tuple/set
- Accepts JSON-style list strings like "[1, 2, 3]"
- Accepts separator-delimited strings using list_sep
hosts = envs("ALLOWED_HOSTS", cast=list)
hosts_csv = envs("ALLOWED_HOSTS_CSV", cast=list, list_sep=",")
hosts_pipe = envs("ALLOWED_HOSTS_PIPE", cast=list, list_sep="|")
coords = envs("COORDS", cast=tuple)
labels = envs("LABELS", cast=set)- Accepts native dict values
- Accepts JSON-style object strings like "{"workers": 2}"
options = envs("APP_OPTIONS", cast=dict)from decimal import Decimal
price = envs("PRICE", cast=Decimal)
def normalize_slug(value):
return str(value).strip().lower().replace(" ", "-")
slug = envs("PROJECT_NAME", cast=normalize_slug)GConfigs delegates output conversion and strip behavior to ValueOutput.
Default behavior:
- strip=True
- list_sep=","
- bool_values as described in the casting section
You can pass a custom ValueOutput instance to GConfigs:
from gconfigs.gconfigs import GConfigs, ValueOutput
class DictBackend:
def __init__(self):
self.data = {"DEBUG": "enabled", "HOSTS": "a|b|c"}
def keys(self):
return self.data.keys()
def get(self, key, **backend_kwargs):
if key not in self.data:
raise KeyError(f"{key} not set")
return self.data[key]
output_fmt = ValueOutput(list_sep="|", bool_values=(("enabled", "disabled"),))
configs = GConfigs(backend=DictBackend, output_fmt=output_fmt)
debug = configs("DEBUG", cast=bool)
hosts = configs("HOSTS", cast=list)GConfigs implements container and iterator protocols.
import gconfigs
envs = gconfigs.envs()
if "HOME" in envs:
print("HOME exists")
print(len(envs))
for item in envs:
print(item.key, item.value)
print(envs.json())Notes:
- Iteration yields namedtuples with key and value fields
- Use .iterator() when you need a fresh independent iterator
Typical exceptions you may see:
- KeyError for missing environment variable or missing dotenv key
- FileNotFoundError for missing paths/files in file backends
- PermissionError for unreadable files in local_file backend
- ValueError for invalid cast values
Use default=... to avoid exceptions for missing keys when appropriate.
You can plug your own backend into GConfigs.
Required backend methods:
- get(key: str, **backend_kwargs)
- keys()
Example:
from gconfigs.gconfigs import GConfigs
class DictBackend:
def __init__(self):
self.data = {"NAME": "my-app", "DEBUG": "false"}
def additional_method(self):
return "This is an additional method in the backend class."
def keys(self):
return self.data.keys()
def get(self, key, **backend_kwargs):
if key not in self.data:
raise KeyError(f"{key} not set")
return self.data[key]
configs = GConfigs(backend=DictBackend)
name = configs("NAME")
debug = configs("DEBUG", cast=bool)
# You can access the backend instance directly from GConfigs instance
assert configs.backend.additional_methodMIT. See LICENSE-MIT.