Metadata-Version: 2.4
Name: attrs-validation
Version: 0.2.0
Summary: Typed validation helpers for attrs classes
Author-email: Armontex <armontex.work@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: attrs,validation
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.13
Requires-Dist: attrs<27.0.0,>=26.1.0
Description-Content-Type: text/markdown

# attrs-validation

Small typed validation helpers for [`attrs`](https://www.attrs.org/) classes.

`attrs-validation` wraps selected `attrs.validators` and raises one stable
exception type: `attrs_validation.ValidationError`. The original `attrs`
validators remain responsible for validation rules; this package makes their
errors easier to handle in application code, APIs, CLIs, and tests.

## Requirements

- Python 3.13+
- attrs 26.1+

## Installation

```bash
python -m pip install attrs-validation
```

For local development from this repository:

```bash
uv sync --all-groups
```

## Quick Start

```python
import attrs

from attrs_validation import ValidationError, ge, instance_of, max_len, min_len


@attrs.define
class User:
    name: str = attrs.field(validator=[instance_of(str), min_len(2), max_len(40)])
    age: int = attrs.field(validator=[instance_of(int), ge(18)])


user = User(name="Ada", age=36)
print(user)
# User(name='Ada', age=36)

try:
    User(name="A", age=17)
except ValidationError as exc:
    print(exc.field)
    print(exc.code)
    print(exc.message)
    print(exc.value)
```

## Why Use It?

`attrs` already provides excellent validators. They intentionally raise
standard exceptions such as `TypeError` and `ValueError`.

`attrs-validation` keeps the same validation behavior while adding a stable
error shape:

```python
except ValidationError as exc:
    assert exc.field == "age"
    assert exc.code == "greater_than_or_equal"
    assert exc.value == 17
```

This is useful when validation errors need to be serialized, mapped to API
responses, shown in forms, or asserted in tests.

## Validators

The package currently exposes wrappers for these `attrs.validators` helpers:

| Validator | Error code |
| --- | --- |
| `instance_of(type)` | `invalid_type` |
| `ge(value)` | `greater_than_or_equal` |
| `gt(value)` | `greater_than` |
| `le(value)` | `less_than_or_equal` |
| `lt(value)` | `less_than` |
| `in_(options)` | `not_in` |
| `not_(*validators)` | `forbidden_value` |
| `or_(*validators)` | `no_valid_option` |
| `is_callable()` | `not_callable` |
| `matches_re(regex, flags=0, func=None)` | `pattern_mismatch` |
| `max_len(length)` | `max_length` |
| `min_len(length)` | `min_length` |

## ValidationError

`ValidationError` is the public exception raised by wrapped validators.

```python
class ValidationError(Exception):
    field: str
    code: str
    message: str
    value: Any
```

- `field` is the attrs field name.
- `code` is a machine-readable error code.
- `message` is the original validator message.
- `value` is the rejected value.

## Composition

Wrapped validators can be composed with `attrs` in the usual way:

```python
import attrs

from attrs_validation import in_, instance_of, not_, or_


@attrs.define
class Account:
    username: str = attrs.field(validator=[instance_of(str), not_(in_({"root", "admin"}))])
    identifier: str | int = attrs.field(validator=or_(instance_of(str), instance_of(int)))
```

## Development

Install the project with all development groups:

```bash
uv sync --all-groups
```

Run the standard checks:

```bash
uv run ruff format --check .
uv run ruff check .
uv run pyright
uv run pytest -q
```

Build distributions:

```bash
uv build
```

Inspect artifacts:

```bash
tar -tf dist/attrs_validation-*.tar.gz
python -m zipfile -l dist/attrs_validation-*-py3-none-any.whl
```

## Release Checklist

Before publishing a release:

1. Ensure `CHANGELOG.md` has an entry for the release.
2. Ensure the repository is tagged with the release version, for example `v0.2.0`.
3. Run formatting, linting, type checking, and tests.
4. Build wheel and sdist with `uv build`.
5. Install the built wheel into a clean environment and run a smoke test.

## License

MIT. See [LICENSE](LICENSE).
