Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 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.Source(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/+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/+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/+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/+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/+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.
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
35 changes: 14 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,18 +40,7 @@ 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).

### TypeLoader Reference

```python
--8<-- "src/dature/metadata.py:type-loader"
```

| Parameter | Description |
|-----------|-------------|
| `type_` | The target type to register a loader for |
| `func` | A callable that converts the raw value to the target type |
When both per-source and global `type_loaders` are set, they merge — per-source loaders take priority.

## Custom Loaders

Expand Down
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 `dature.Source(file=...)` are expanded automatically in `"strict"` mode — if a variable is missing, `EnvVarExpandError` is raised immediately at `dature.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`.
66 changes: 16 additions & 50 deletions docs/advanced/merge-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ graph TD

Override the global strategy for individual fields using `field_merges`.

All available `FieldMergeStrategy` values:
Available field merge strategies:

| Strategy | Behavior |
|----------|----------|
| `FIRST_WINS` | Keep the value from the first source |
| `LAST_WINS` | Keep the value from the last source |
| `APPEND` | Concatenate lists: `base + override` |
| `APPEND_UNIQUE` | Concatenate lists, removing duplicates |
| `PREPEND` | Concatenate lists: `override + base` |
| `PREPEND_UNIQUE` | Concatenate lists in reverse order, removing duplicates |
| `"first_wins"` | Keep the value from the first source |
| `"last_wins"` | Keep the value from the last source |
| `"append"` | Concatenate lists: `base + override` |
| `"append_unique"` | Concatenate lists, removing duplicates |
| `"prepend"` | Concatenate lists: `override + base` |
| `"prepend_unique"` | Concatenate lists in reverse order, removing duplicates |

Given two sources with overlapping `tags`:

Expand All @@ -50,47 +50,47 @@ Given two sources with overlapping `tags`:

Each strategy produces a different result:

=== "FIRST_WINS"
=== "first_wins"

```python
--8<-- "examples/docs/advanced/merge_rules/merging_field_first_wins.py"
```

=== "LAST_WINS"
=== "last_wins"

```python
--8<-- "examples/docs/advanced/merge_rules/merging_field_last_wins.py"
```

=== "APPEND"
=== "append"

```python
--8<-- "examples/docs/advanced/merge_rules/merging_field_append.py"
```

=== "APPEND_UNIQUE"
=== "append_unique"

```python
--8<-- "examples/docs/advanced/merge_rules/merging_field_append_unique.py"
```

=== "PREPEND"
=== "prepend"

```python
--8<-- "examples/docs/advanced/merge_rules/merging_field_prepend.py"
```

=== "PREPEND_UNIQUE"
=== "prepend_unique"

```python
--8<-- "examples/docs/advanced/merge_rules/merging_field_prepend_unique.py"
```

Nested fields are supported: `F[Config].database.host`.
Nested fields are supported: `dature.F[Config].database.host`.

Per-field strategies work with `RAISE_ON_CONFLICT` — fields with an explicit strategy are excluded from conflict detection.
Per-field strategies work with `"raise_on_conflict"` — fields with an explicit strategy are excluded from conflict detection.

## With RAISE_ON_CONFLICT
## With raise_on_conflict

Fields with an explicit strategy are excluded from conflict detection:

Expand Down Expand Up @@ -136,40 +136,6 @@ You can also pass a callable as the strategy:

The callable receives a `list[JSONValue]` (one value per source) and returns the merged value.

## Field Groups

Ensure that related fields are always overridden together. If a source changes some fields in a group but not others, `FieldGroupError` is raised:

=== "Python"

```python
--8<-- "examples/docs/advanced/merge_rules/merging_field_groups.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 it changed only `host` but not `port`, loading would fail:

```
Config field group errors (1)

Field group (host, port) partially overridden in source 1
changed: host (from source yaml 'overrides.yaml')
unchanged: port (from source yaml 'defaults.yaml')
```

For nested dataclass expansion and multiple groups, see [Field Groups](field-groups.md).

## Skipping Broken Sources

Skip sources that fail to load (missing file, invalid syntax):
Expand Down
Loading
Loading