Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2e45505
rename file_ to file
Niccolum Mar 28, 2026
c401af2
dependabot once per month
Niccolum Mar 28, 2026
3c8c577
from dature import ... -> import dature
Niccolum Mar 28, 2026
668eff1
no Merge class
Niccolum Mar 28, 2026
1f00da3
fix dature error for loading without sources or with wrong sources
Niccolum Mar 29, 2026
ee883da
add towncrier changes
Niccolum Mar 29, 2026
b684ea1
refactor enums
Niccolum Mar 30, 2026
d031fc4
refactor validators
Niccolum Mar 31, 2026
342764b
dature.errors.exceptions -> dature.errors
Niccolum Mar 31, 2026
dc4cf1f
fix field group docs
Niccolum Mar 31, 2026
8a93a2a
fix cache docs
Niccolum Mar 31, 2026
9c3488e
refactor docs
Niccolum Mar 31, 2026
976e721
fix
Niccolum Mar 31, 2026
f56e8ec
dataclass_ -> schema
Niccolum Mar 31, 2026
f46e93e
fix version
Niccolum Mar 31, 2026
abe0a7a
fix
Niccolum Mar 31, 2026
f6197e8
fix test
Niccolum Mar 31, 2026
2b96cd2
fix mypy
Niccolum Mar 31, 2026
9bf07a6
fix global config
Niccolum Mar 31, 2026
ff71819
fix filename
Niccolum Mar 31, 2026
e00792a
fix mypy
Niccolum Mar 31, 2026
cfdc8bf
Trigger RTD build
Niccolum Mar 31, 2026
c02aba5
Trigger RTD build
Niccolum Mar 31, 2026
db1e208
fix mask_secrets/secret_field_names behaviour
Niccolum Mar 31, 2026
3ffb917
fix docs for masking parameters
Niccolum Mar 31, 2026
d893e31
secret_field_names combined
Niccolum Apr 1, 2026
d01d22b
traceback as separate file
Niccolum Apr 1, 2026
750f142
refactor CustomLoader -> CustomSource
Niccolum Apr 7, 2026
f624b9b
fix subprocess
Niccolum Apr 7, 2026
cbdb5d0
fix
Niccolum Apr 7, 2026
2e8195c
fix subprocess
Niccolum Apr 7, 2026
6a7b49c
fix subprocess
Niccolum Apr 7, 2026
3898c07
fix
Niccolum Apr 7, 2026
3ec5798
fix windows
Niccolum Apr 7, 2026
5a6099d
fix windows
Niccolum Apr 7, 2026
cc86c42
fix devin comments
Niccolum Apr 7, 2026
59de386
Merge branch 'main' into refactor/public_interface
Niccolum Apr 7, 2026
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
6 changes: 2 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
day: "saturday"
interval: "monthly"
time: "09:00"
timezone: "Europe/Moscow"
open-pull-requests-limit: 10
Expand All @@ -16,8 +15,7 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "saturday"
interval: "monthly"
time: "09:00"
timezone: "Europe/Moscow"
labels:
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# dature
<div align="center">

---
<img src="docs/assets/img/logo.svg" alt="dature" width="400">

<div align="center">
---

[![PyPI](https://img.shields.io/pypi/v/dature)](https://pypi.org/project/dature/)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/dature)](https://pypi.org/project/dature/)
Expand Down Expand Up @@ -44,28 +44,29 @@ pip install dature[secure] # Secret detection heuristics

```python
from dataclasses import dataclass
from dature import Source, load

import dature

@dataclass
class Config:
host: str
port: int
debug: bool = False

config = load(Source(file_="config.yaml"), Config)
config = dature.load(dature.Yaml12Source(file="config.yaml"), Config)
```

## Key Features

- **Multiple formats** — YAML, JSON, JSON5, TOML, INI, ENV, environment variables, Docker secrets
- **Merging** — combine multiple sources with configurable strategies (`LAST_WINS`, `FIRST_WINS`, `RAISE_ON_CONFLICT`)
- **Merging** — combine multiple sources with configurable strategies (`"last_wins"`, `"first_wins"`, `"raise_on_conflict"`)
- **Validation** — `Annotated` field validators, root validators, `__post_init__` support
- **Naming** — automatic field name mapping (`snake_case` ↔ `camelCase` ↔ `UPPER_SNAKE` etc.)
- **Secret masking** — automatic masking in error messages and logs by field type, name, or heuristic
- **ENV expansion** — `$VAR`, `${VAR:-default}` substitution in all file formats
- **Special types** — `SecretStr`, `ByteSize`, `PaymentCardNumber`, `URL`, `Base64UrlStr`
- **Debug report** — `debug=True` shows which source provided each field value
- **Decorator mode** — `@load(meta)` auto-loads config on dataclass instantiation with caching
- **Decorator mode** — `@dature.load(meta)` auto-loads config on dataclass instantiation with caching

See the **[documentation](https://dature.readthedocs.io/)** for detailed guides and API reference.

Expand Down
1 change: 1 addition & 0 deletions changes/+common-loading.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Extracted shared ``resolve_mask_secrets`` logic from ``single.py`` and ``multi.py`` into ``loading/common.py``.
1 change: 1 addition & 0 deletions changes/+concrete-sources-exported.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
All concrete source classes (``EnvSource``, ``JsonSource``, ``Yaml11Source``, ``Yaml12Source``, ``Toml10Source``, ``Toml11Source``, ``IniSource``, ``Json5Source``, ``EnvFileSource``, ``DockerSecretsSource``, ``FileSource``) are now exported from ``dature`` directly.
1 change: 1 addition & 0 deletions changes/+docs-tuning.doc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improved documentation for Caching, Merge Rules, Configure, Custom Types, and Field Groups sections.
2 changes: 1 addition & 1 deletion changes/+file-path-expansion.feature
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Environment variables in `Source(file_=...)` are now expanded automatically in strict mode. Both directory paths (`$CONFIG_DIR/config.toml`) and file names (`config.$APP_ENV.toml`) are supported.
Environment variables in `Source(file=...)` are now expanded automatically in strict mode. Both directory paths (`$CONFIG_DIR/config.toml`) and file names (`config.$APP_ENV.toml`) are supported.
1 change: 1 addition & 0 deletions changes/+fix-merge-conflict-filecontent.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed attribute name typo (``filecontent`` → ``file_content``) in ``raise_on_conflict`` merge strategy that caused ``AttributeError`` when conflicting fields were detected.
1 change: 1 addition & 0 deletions changes/+import-style.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Recommended import style changed from `from dature import load, Source` to `import dature` with access via `dature.load()`, `dature.Source()`.
1 change: 1 addition & 0 deletions changes/+internal-enum-hints.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Internal type hints now use `MergeStrategyEnum`/`FieldMergeStrategyEnum` instead of `MergeStrategyName`/`FieldMergeStrategyName` Literal aliases. Public API type hints remain unchanged.
1 change: 1 addition & 0 deletions changes/+merge-config-public.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Renamed ``_MergeConfig`` to ``MergeConfig``.
1 change: 1 addition & 0 deletions changes/+normalize-metadata-naming.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Renamed ``metadata``/``source_meta`` parameters to ``source`` throughout the loading module.
1 change: 1 addition & 0 deletions changes/+remove-loader-protocol.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Removed ``LoaderProtocol`` from ``dature.protocols``. Source classes now handle loading internally.
1 change: 1 addition & 0 deletions changes/+remove-merge-class.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`Merge` class has been removed. Use `load()` with multiple `Source` arguments instead.
1 change: 1 addition & 0 deletions changes/+remove-source-mutations.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Source user-facing attributes are no longer mutated during ``load()``. All parameter resolution happens via ``resolve_source_params()`` in ``source_loading.py``. The ``retorts`` cache is still populated lazily during loading.
1 change: 1 addition & 0 deletions changes/+rename-display-name.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Renamed ``display_name`` to ``format_name`` and ``display_label`` to ``location_label`` across all source classes and error types.
1 change: 1 addition & 0 deletions changes/+rename-file-param.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`Source(file_=...)` has been renamed to `Source(file=...)`.
1 change: 1 addition & 0 deletions changes/+rename-sources-loader-package.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Renamed internal package ``sources_loader`` to ``sources`` (source classes) and ``loaders`` (type conversion). All public imports from ``dature`` are unchanged.
1 change: 1 addition & 0 deletions changes/+retort-factory.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Extracted retort factory methods from ``Source`` into free functions in ``sources/retort.py``. ``transform_to_dataclass`` is now a free function.
1 change: 1 addition & 0 deletions changes/+simplify-configure.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`configure()` now accepts dicts instead of dataclass instances: `masking={"mask": "***"}`, `error_display={"max_visible_lines": 5}`, `loading={"debug": True}`, `type_loaders={MyType: my_loader}`.
1 change: 1 addition & 0 deletions changes/+simplify-enums.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Removed `MergeStrategy` and `FieldMergeStrategy` enums from public API. Use string literals instead: `"last_wins"`, `"first_wins"`, `"first_found"`, `"raise_on_conflict"` for merge strategies; `"first_wins"`, `"last_wins"`, `"append"`, `"append_unique"`, `"prepend"`, `"prepend_unique"` for field merge strategies.
1 change: 1 addition & 0 deletions changes/+simplify-field-group.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Removed `FieldGroup` dataclass from public API. Pass `field_groups` as `tuple[tuple[F[Config].field, ...], ...]` instead.
1 change: 1 addition & 0 deletions changes/+simplify-merge-rule.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Removed `MergeRule` dataclass from public API. Pass `field_merges` as `dict` mapping `F[Config].field` to a strategy string or callable instead.
1 change: 1 addition & 0 deletions changes/+simplify-type-loader.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Removed `TypeLoader` dataclass from public API. Pass `type_loaders` as `dict[type, Callable]` instead.
1 change: 1 addition & 0 deletions changes/+simplify-validator-args.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Built-in validators (`Ge`, `Le`, `Gt`, `Lt`, `MinLength`, `MaxLength`, `RegexPattern`, `MinItems`, `MaxItems`, `UniqueItems`) now accept `value` as a positional argument: `Ge(1)` instead of `Ge(value=1)`. `RootValidator` now accepts `func` as a positional argument: `RootValidator(check)` instead of `RootValidator(func=check)`. `error_message` remains keyword-only in all validators.
1 change: 1 addition & 0 deletions changes/+type-utils.refactor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deduplicated ``_find_nested_dataclasses`` into shared ``type_utils.find_nested_dataclasses``.
1 change: 1 addition & 0 deletions changes/+unit-tests.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added unit tests for ``loading/context``, ``loading/source_loading``, ``masking/detection``, ``validators/base``, ``loaders/common``, ``loaders/base``.
14 changes: 11 additions & 3 deletions docs/advanced/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

In decorator mode, caching is enabled by default:

```python
--8<-- "examples/docs/advanced/caching/advanced_caching.py"
```
=== "cache=True"

```python
--8<-- "examples/docs/advanced/caching/advanced_caching_enabled.py"
```

=== "cache=False"

```python
--8<-- "examples/docs/advanced/caching/advanced_caching_disabled.py"
```

Caching can also be configured globally via `configure()`.
2 changes: 1 addition & 1 deletion docs/advanced/configure.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Customize defaults for the entire application — programmatically or via enviro

### type_loaders

Register global custom type loaders that apply to all `load()` calls. See [Custom Types & Loaders](custom_types.md#per-source-vs-global).
Register global custom type loaders that apply to all `dature.load()` calls. See [Custom Types & Loaders](custom_types.md#per-source-vs-global).

## Environment Variables

Expand Down
81 changes: 60 additions & 21 deletions docs/advanced/custom_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,31 @@

Use `type_loaders` to teach dature how to parse custom types from strings.

Each `TypeLoader` maps a type to a conversion function:
Pass `type_loaders` as a `dict[type, Callable]` mapping types to conversion functions:

```python
--8<-- "examples/docs/advanced/custom_types/custom_type.py"
```
=== "Python"

```yaml title="custom_type_common.yaml"
--8<-- "examples/docs/advanced/custom_types/sources/custom_type_common.yaml"
```
```python
--8<-- "examples/docs/advanced/custom_types/custom_type.py"
```

=== "custom_type_common.yaml"

```yaml
--8<-- "examples/docs/advanced/custom_types/sources/custom_type_common.yaml"
```

### Per-source vs Global

`type_loaders` can be set per-source in `Source`, per-merge in `Merge`, or globally via `configure()`:
`type_loaders` can be set per-source in `Source`, in `dature.load()` for merge mode, or globally via `configure()`:

=== "Per-source (Source)"

```python
--8<-- "examples/docs/advanced/custom_types/custom_type.py"
```

=== "Per-merge (Merge)"
=== "Per-merge (load)"

```python
--8<-- "examples/docs/advanced/custom_types/custom_type_merge.py"
Expand All @@ -36,25 +40,46 @@ Each `TypeLoader` maps a type to a conversion function:
--8<-- "examples/docs/advanced/custom_types/advanced_configure_type_loaders.py"
```

When both per-source and global `type_loaders` are set, they merge — per-source loaders take priority (placed first in the recipe).
When both per-source and global `type_loaders` are set, they merge — per-source loaders take priority.

## Custom Source Classes

For formats that dature doesn't support out of the box, you can create your own source by subclassing one of the base classes from `dature.sources.base`:

### Choosing a base class

### TypeLoader Reference
| Base class | Use when | You implement | You get for free |
|------------|----------|---------------|------------------|
| [`Source`](../api-reference.md#source) | Non-file data (API, database, custom protocol) | `format_name`, `_load() -> JSONValue` | Prefix filtering, env var expansion, type coercion, validation, merge support |
| [`FileSource`](../api-reference.md#filesourcesource) | File-based format (XML, CSV, HCL, …) | `format_name`, `_load_file(path: FileOrStream) -> JSONValue` | Everything from `Source` + `file` parameter, stream support, `file_display()`, `file_path_for_errors()`, `__repr__` |
| [`FlatKeySource`](../api-reference.md#flatkeysourcesource) | Flat key=value data (custom env store, Consul KV, …) | `format_name`, `_load() -> JSONValue` (flat `dict[str, str]`) | Everything from `Source` + `split_symbols` nesting, `nested_resolve`, automatic string→type parsing (`int`, `bool`, `date`, …) |

All base classes are in `dature.sources.base`:

```python
--8<-- "src/dature/metadata.py:type-loader"
--8<-- "examples/docs/advanced/custom_types/custom_source_import.py"
```

| Parameter | Description |
|-----------|-------------|
| `type_` | The target type to register a loader for |
| `func` | A callable that converts the raw value to the target type |
### Minimal interface

Every custom source needs:

1. **`format_name`** — class-level string shown in `__repr__` and error messages (e.g. `"xml"`, `"consul"`)
2. **A load method** — `_load()` for `Source`/`FlatKeySource`, or `_load_file(path)` for `FileSource`. Must return `JSONValue` (a nested dict).

## Custom Loaders
### Optional overrides

For formats that dature doesn't support out of the box, subclass `BaseLoader` and implement two things:
| Method | Default | Override when |
|--------|---------|---------------|
| `additional_loaders()` | `[]` (FileSource) or string-value loaders (FlatKeySource) | Your format stores all values as strings and needs extra type parsers (e.g. `bool`, `float`). |
| `file_display()` | `None` | Your source has a meaningful display path (shown in logs and errors). |
| `file_path_for_errors()` | `None` | Your source points to a file on disk (used in error messages). |
| `resolve_location(...)` | Empty `SourceLocation` | You want errors to show line numbers or variable names from your source. |
| `location_label` | inherited | Change the label in error messages (e.g. `"FILE"`, `"ENV"`, `"API"`). |

1. `display_name` — a class-level string shown in error messages
2. `_load(path)` — returns `JSONValue` (a nested dict) from the source
### Example: FileSource subclass

The most common case — reading a file format:

```python
--8<-- "examples/docs/advanced/custom_types/custom_loader.py"
Expand All @@ -64,4 +89,18 @@ For formats that dature doesn't support out of the box, subclass `BaseLoader` an
--8<-- "examples/docs/advanced/custom_types/sources/custom_loader.xml"
```

Pass your custom loader via the `loader` parameter in `Source`. All built-in features (type coercion, validation, prefix extraction, ENV expansion) work automatically.
`FileSource` handles the `file` parameter, path expansion, and stream detection. Your `_load_file()` receives a `Path` or file-like object and returns a dict.

### Example: Source subclass (non-file)

For sources that don't read files — e.g. an API, a database, or an in-memory dict:

```python
--8<-- "examples/docs/advanced/custom_types/custom_dict_source.py"
```

### Tips

- All built-in features (type coercion, validation, prefix extraction, ENV expansion, merge support) work automatically with any custom source.
- Override `additional_loaders()` to return `_string_value_loaders()` from `dature.sources.base` if your format stores everything as strings (like INI or ENV).
- Pass your custom source to `dature.load()` the same way as any built-in source.
4 changes: 2 additions & 2 deletions docs/advanced/env-expansion.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Set the mode on `Source`:
--8<-- "examples/docs/advanced/env_expansion/sources/advanced_env_expansion_strict.yaml"
```

For merge mode, set on `Merge` as default for all sources:
For merge mode, pass `expand_env_vars` to `dature.load()` as default for all sources:

=== "Python"

Expand Down Expand Up @@ -131,7 +131,7 @@ The `${VAR:-default}` fallback syntax works in all modes.

## File Path Expansion

Environment variables in `Source(file_=...)` are expanded automatically in `"strict"` mode — if a variable is missing, `EnvVarExpandError` is raised immediately at `Source` creation time.
Environment variables in the `file=...` parameter of Source subclasses are expanded automatically in `"strict"` mode — if a variable is missing, `EnvVarExpandError` is raised immediately at Source creation time.

This works for both directory paths and file names:

Expand Down
40 changes: 39 additions & 1 deletion docs/advanced/field-groups.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
# Field Groups

Ensure related fields are always overridden together. If a source partially overrides a group, `FieldGroupError` is raised:
Ensure related fields are always overridden together:

=== "Python"

```python
--8<-- "examples/docs/advanced/field_groups/field_groups_basic.py"
```

=== "common_field_groups_defaults.yaml"

```yaml
--8<-- "examples/docs/shared/common_field_groups_defaults.yaml"
```

=== "common_field_groups_overrides.yaml"

```yaml
--8<-- "examples/docs/shared/common_field_groups_overrides.yaml"
```

If `overrides.yaml` changes `host` and `port` together, the group constraint is satisfied. If a source partially overrides a group, `FieldGroupError` is raised:

=== "Python"

Expand All @@ -20,6 +40,12 @@ Ensure related fields are always overridden together. If a source partially over
--8<-- "examples/docs/advanced/field_groups/sources/field_groups_partial_overrides.yaml"
```

=== "Error"

```
--8<-- "examples/docs/advanced/field_groups/advanced_field_groups_nested_error.stderr"
```

## Nested Dataclass Expansion

Passing a dataclass field expands it into all its leaf fields:
Expand All @@ -42,6 +68,12 @@ Passing a dataclass field expands it into all its leaf fields:
--8<-- "examples/docs/advanced/field_groups/sources/advanced_field_groups_expansion_error_overrides.yaml"
```

=== "Error"

```
--8<-- "examples/docs/advanced/field_groups/advanced_field_groups_expansion_error.stderr"
```

## Multiple Groups

If a source partially overrides multiple groups, all violations are reported:
Expand All @@ -64,4 +96,10 @@ If a source partially overrides multiple groups, all violations are reported:
--8<-- "examples/docs/advanced/field_groups/sources/advanced_field_groups_multiple_error_overrides.yaml"
```

=== "Error"

```
--8<-- "examples/docs/advanced/field_groups/advanced_field_groups_multiple_error.stderr"
```

Field groups work with all merge strategies and can be combined with `field_merges`.
Loading
Loading