Metadata-Version: 2.4
Name: picoconf
Version: 0.2.0
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Rust
Requires-Dist: pre-commit ; extra == 'dev'
Requires-Dist: pytest ; extra == 'dev'
Requires-Dist: ruff ; extra == 'dev'
Requires-Dist: build ; extra == 'setup'
Requires-Dist: setuptools ; extra == 'setup'
Requires-Dist: twine ; extra == 'setup'
Provides-Extra: dev
Provides-Extra: setup
License-File: LICENSE
Summary: The blazing-fast, tiny opinionated config loader.
Keywords: picoconf,config,configuration,settings,nanoconf,yaml,rust
Author-email: Jacob J Callahan <jacob.callahan05@gmail.com>
Requires-Python: >=3.11
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Repository, https://github.com/JacobCallahan/picoconf

PicoConf
========
PicoConf is a tiny, opinionated, lightning fast, and easy to use configuration library for Python. It is designed to be used in small to medium sized projects where a full blown configuration library is overkill.

This project is a Rust port of my NanoConf project, so inherits the usage patterns from that. However, it is roughtly 40x faster!

Installation
------------
```bash
uv pip install picoconf
```

Usage
-----
```python
from picoconf import PicoConf
# or if PicoConf if too long of a name
from picoconf import PC

# Create a new configuration object
config = PicoConf("/path/to/config.pconf")

# Access config values using dictionary-style access
print(config["some_key"])

# Or use dotted attribute access (recommended for cleaner code)
print(config.some_key)

# Both methods work interchangeably
assert config["some_key"] == config.some_key

# Nested values support both access methods too
print(config.database.host)  # attribute access
print(config["database"]["host"])  # dictionary access

# Convert to plain Python dict (recursively)
plain_dict = config.to_dict()
# All nested PicoConf objects become regular dicts
```

Configuration File Format
-------------------------
PicoConf uses a simple configuration file format that is easy to read and write.
Each File is YAML formatted and contains a single top-level dictionary.
Even though the top-level must be a dictionary, you can nest dictionaries and lists as deep as you want.
Each config file also must have the .pconf extension. This ensures that PicoConf will only load files that are meant to be configuration files.
```yaml
key: value
test: 1
overriden: false
things:
    - thing1
    - thing2
    - thing3
top:
    v1: 1
    middle:
        v2: 2
        inner:
            v3: 3
            deep:
                v4: 4
```
If you have multiple config files you want to load into a single config object, you can put them all in the same directory and pass that directory to PicoConf.
PicoConf will automatically place sub-files by their filename as an attribute of the parent file.
The contents of that file will be accessible as you'd expect under the corresponding filename attribute.

```
<project root>
conf_dir
  |__ cfg1.pconf
  |__ cfg2.pconf
  |__ cfg3.pconf
```

```python
# load an entire directory
proj_config = PicoConf("/path/to/conf_dir")
print(proj_config.cfg1.test)
```

Or you can import additional files or directories from within any config file by using the `_import` keyword.
```yaml
# main.pconf
_import:
    - /path/to/project/more_config
key: value
test: 1
```

```
<project root>
main.pconf
more_config
  |__ subcfg1.pconf
  |__ subcfg2.pconf
  |__ subcfg3.pconf
```

```python
# loading the main config file will also load the sub-configs
proj_config = PicoConf("/path/to/project/main.pconf")
print(proj_config.more_config.subcfg1.test)
```
Notice how the directory structure was also maintained in the attribute path. This makes it easier to find the file that a value came from.

Environment Variables
---------------------
PicoConf supports environment variables either as overrides to existing values or as additions to the loaded config.
Envars are evaluated on a per-file basis, so you can have different envars for different config files.
The way we manage this is by having a special `_envar_prefix` key in the config file.
**Note:** Environment variable names are matched case-insensitively and normalized to lowercase for cross-platform compatibility. Both the `_envar_prefix` and the config keys extracted from environment variables are converted to lowercase to ensure consistent behavior across all platforms (Windows treats env vars as case-insensitive).
```yaml
_envar_prefix: myapp
key: value
overrideme: original
```
```bash
export myapp_overrideme=changed
```
```python
config = PicoConf("/path/to/config.pconf")
print(config.overrideme)
```

You can also pass complex data structures as JSON strings in environment variables.
```bash
export myapp_abc='{"a": 1, "b": 2, "c": 3}'
```
```python
config = PicoConf("/path/to/config.pconf")
print(config.abc.b)
```

Converting to Plain Dictionaries
---------------------------------
PicoConf objects can be recursively converted to plain Python dictionaries using the `to_dict()` method. This is useful for serialization, passing to libraries that expect plain dicts, or API responses.

```python
config = PicoConf("/path/to/config.pconf")

# Convert entire config to plain dict
plain = config.to_dict()

# All nested PicoConf objects become regular dicts
assert isinstance(plain, dict)
assert not isinstance(plain, PicoConf)

# Works with deeply nested structures
if "database" in config:
    db_dict = config.database.to_dict()
    # Can now be serialized to JSON, YAML, etc.
```

