Metadata-Version: 2.4
Name: lzconfig
Version: 0.1.0
Summary: Zero-boilerplate configuration library — import and go
Author-email: Lyu <shlv@qq.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/SihanLv/lzconfig
Keywords: config,configuration,yaml,json,toml,ini,dotenv,xml,zero-boilerplate
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: yaml
Requires-Dist: pyyaml>=6.0; extra == "yaml"
Provides-Extra: toml
Requires-Dist: tomli>=2.0; extra == "toml"
Provides-Extra: dotenv
Requires-Dist: python-dotenv>=1.0; extra == "dotenv"
Provides-Extra: all
Requires-Dist: pyyaml>=6.0; extra == "all"
Requires-Dist: tomli>=2.0; extra == "all"
Requires-Dist: python-dotenv>=1.0; extra == "all"
Dynamic: license-file

# lzconfig — Zero-Boilerplate Configuration

> lz: Too lazy to type `lazy`

[中文文档](README.zh.md)

**Import and go.** No init, no loader calls, no boilerplate.

```python
import lzconfig

# Attribute-style access
print(lzconfig.database.host)           # → "localhost"
print(lzconfig.database.credentials.user)  # → "admin"

# Dict-style access
print(lzconfig['database']['host'])     # → "localhost"

# Mixed access
print(lzconfig.database['port'])        # → 5432

# Safe access with defaults
print(lzconfig.get('debug', False))     # → True
```

## How It Works

At import time, `lzconfig` discovers your config file, parses it, interpolates
environment variables, and replaces itself in `sys.modules` with the loaded
configuration object.  From then on, `lzconfig` **is** your config.

## Config File Resolution

1. If `LZCONFIG_FILE` is set (and non-empty), that file is used.  Relative
   paths are resolved against the current working directory.
2. Otherwise, the current directory is scanned for the first match:

   | Priority | Filename |
   |----------|----------|
   | 1 | `lzconfig.yaml` |
   | 2 | `lzconfig.yml` |
   | 3 | `lzconfig.json` |
   | 4 | `lzconfig.toml` |
   | 5 | `lzconfig.ini` |
   | 6 | `lzconfig.cfg` |
   | 7 | `lzconfig.env` |
   | 8 | `lzconfig.xml` |

3. If nothing is found, `ConfigNotFoundError` is raised at import time.

## Supported Formats

| Format | Extensions | Dependency |
|--------|-----------|------------|
| YAML   | `.yaml` `.yml` | [PyYAML](https://pypi.org/project/PyYAML/) |
| JSON   | `.json` | stdlib |
| TOML   | `.toml` | [`tomli`](https://pypi.org/project/tomli/) (Python < 3.11) / `tomllib` (3.11+) |
| INI    | `.ini` `.cfg` | stdlib (`configparser`) |
| .env   | `.env` | [`python-dotenv`](https://pypi.org/project/python-dotenv/) (optional) |
| XML    | `.xml` | stdlib |

Core formats (JSON, INI, XML) work with zero dependencies.  Install extras
as needed:

```bash
pip install lzconfig[yaml,toml,dotenv]   # specific extras
pip install lzconfig[all]                # everything
```

## Environment Variable Interpolation

`$VAR` and `${VAR}` references in config values are expanded at load time
using `os.path.expandvars`:

```yaml
# lzconfig.yaml
database:
  host: $DB_HOST
  password: ${DB_PASSWORD}
```

Undefined variables are left as literal text — no error is raised.

## Access Patterns

| Pattern | Example |
|---------|---------|
| Attribute chain | `lzconfig.a.b.c` |
| Dict chain | `lzconfig['a']['b']['c']` |
| Mixed | `lzconfig.a['b'].c` |
| List index | `lzconfig['items'][0].name` |
| Membership | `'key' in lzconfig` |
| Safe get | `lzconfig.get('key', default)` |
| Keys | `lzconfig.keys()` |
| Length | `len(lzconfig)` |
| Iteration | `for k in lzconfig: ...` |

## Key / Method Collision

If a config key happens to have the same name as a ConfigObject method
(e.g., `get`, `items`, `keys`):

- **Attribute access** (`lzconfig.items`) returns the **method**.
- **Dict access** (`lzconfig['items']`) always returns the **config value**.

This is a deliberate trade-off.  When in doubt, use `[]` — it always works.

## Error Handling

| Exception | When |
|-----------|------|
| `ConfigNotFoundError` | No config file found at import time |
| `ConfigParseError` | File found but cannot be parsed |
| `ConfigKeyError` | Accessing a non-existent key |

All exceptions inherit from `ConfigError` and can be imported directly:

```python
from lzconfig import ConfigError, ConfigNotFoundError, ConfigKeyError
```

## License

MIT
