Metadata-Version: 2.4
Name: larzconf
Version: 0.1.0
Summary: Layered, typed application configuration: defaults + files + .env + environment variables. Zero dependencies.
Author: larz-scripter
License: MIT
Project-URL: Homepage, https://github.com/larz-scripter/larzconf
Project-URL: Repository, https://github.com/larz-scripter/larzconf
Project-URL: Documentation, https://github.com/larz-scripter/larzconf#readme
Project-URL: Issues, https://github.com/larz-scripter/larzconf/issues
Keywords: config,configuration,settings,dotenv,env,12-factor,environment-variables,zero-dependency,pure-python
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# larzconf

**Layered, typed application configuration. Pure Python, zero dependencies.**

Read configuration from defaults, files, `.env`, and environment variables —
layered so later sources win — and access it with the right types. No more
`int(os.environ.get("PORT", "8000"))` scattered through your code, and no
dependency to add.

```python
from larzconf import Config

config = (Config()
          .from_dict({"port": 8000, "debug": False})   # defaults
          .from_file("config.json", optional=True)      # committed config
          .from_dotenv(".env")                           # local dev
          .from_env(prefix="APP_"))                      # production wins

config.int("port")                 # int, not "8000"
config.bool("debug")               # True / False from "true"/"1"/"yes"...
config.list("allowed_hosts")       # comma-split -> list
config.get("db.host")              # dotted access into nested config
config.get("secret_key", required=True)   # raises if missing
```

## Why

- **Zero dependencies.** No `python-dotenv`, no `pydantic-settings`, no
  framework. Just the standard library.
- **The 12-factor layering you actually want.** Defaults, then a config file,
  then `.env` for local development, then real environment variables in
  production — each layer deep-merges over the last.
- **Typed, not stringly.** `config.int(...)`, `.bool(...)`, `.float(...)`,
  `.list(...)` coerce values and raise a clear `ConfigError` on bad input;
  `required=True` fails fast at startup instead of at 3am.
- **Nested & dotted.** JSON config nests naturally; access it with
  `config.get("db.host")`. Env vars map to nesting via `__`
  (`APP_DB__HOST` → `db.host`).
- **Validates end-to-end.** Hand the whole config to a schema (`larzvalidate`, or
  anything with `.load(dict)`) to type-check it once at boot.

## Install

```bash
pip install larzconf
```

## Loading

```python
Config().from_dict({...})              # defaults / literals
       .from_json("config.json")       # a JSON file
       .from_dotenv(".env")            # KEY=VALUE (export/comments/quotes ok)
       .from_env(prefix="APP_")        # APP_PORT -> port, APP_DB__HOST -> db.host
       .from_file(path, optional=True) # dispatch by extension
```

Each call returns the `Config`, so they chain, and each layer overrides the ones
before it.

## Reading

```python
config.get("key", default=None, cast=int, required=False)
config.int("port")            config.float("ratio")
config.bool("debug")          config.list("hosts")     config.str("name")
config.get("db.host")         # nested, dotted
"port" in config             config["port"]            config.as_dict()
```

## Validate at startup (optional)

```python
from larzvalidate import Schema, Integer, Boolean, String

class Settings(Schema):
    port  = Integer(min=1, max=65535, default=8000)
    debug = Boolean(default=False)
    secret_key = String(required=True)

settings = config.validate(Settings())   # cleaned, typed dict — or raises
```

`larzconf` doesn't depend on `larzvalidate` — `validate()` accepts anything with
a `.load(dict)` method.

## Tests

```bash
python -m unittest discover -s tests -v      # 25 tests, zero deps
```

## The Larz stack

Pure-Python, zero-dependency building blocks: **[larz](https://github.com/larz-scripter/larz)** · **[larzchain](https://github.com/larz-scripter/larzchain)** · **[larzmoney](https://github.com/larz-scripter/larzmoney)** · **[larzcrypt](https://github.com/larz-scripter/larzcrypt)** · **[larzdb](https://github.com/larz-scripter/larzdb)** · **[larzagent](https://github.com/larz-scripter/larzagent)** · **[larzchart](https://github.com/larz-scripter/larzchart)** · **[larzmark](https://github.com/larz-scripter/larzmark)** · **[larztask](https://github.com/larz-scripter/larztask)** · **[larzvault](https://github.com/larz-scripter/larzvault)** · **[larzvm](https://github.com/larz-scripter/larzvm)** · **[larzcache](https://github.com/larz-scripter/larzcache)** · **[larzvalidate](https://github.com/larz-scripter/larzvalidate)** · **[larzid](https://github.com/larz-scripter/larzid)** · **[larzrpc](https://github.com/larz-scripter/larzrpc)** · **[larzstate](https://github.com/larz-scripter/larzstate)** · **[larzhttp](https://github.com/larz-scripter/larzhttp)** · **larzconf**

## License

MIT © larz-scripter
