Metadata-Version: 2.4
Name: copier-pydantic
Version: 0.1.5
Summary: Adds support for Pydantic Models in Copier templates
Author: Chris Brown
Author-email: Chris Brown <cbrown1234@hotmail.co.uk>
License-Expression: MIT
License-File: LICENSE
Requires-Dist: copier>=3.6
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# Copier Pydantic

[![pypi version](https://img.shields.io/pypi/v/copier-pydantic.svg)](https://pypi.org/project/copier-pydantic/)

Jinja2 extensions for Copier that enable using Pydantic Models for validation and within templates

## Installation

With pip:

```bash
pip install copier-pydantic
```

With uv:

```bash
uv tool install copier --with copier-pydantic
```

With pipx:

```bash
pipx install copier
pipx inject copier copier-pydantic
```

## Usage with Copier

In your copier template configuration:

```yaml
# Add the jinja extensions
_jinja_extensions:
  - copier_pydantic.MultilineValidation
  - copier_pydantic.PydanticExtension

# and exclude the model.py file from your template
_exclude:
  - models.py
# or use the best practice of having the template in a sub directory
_subdirectory: template
```

So your template will look something like this

```
📁 template_root
├── 📄 models.py
├── 📄 copier.yml
└── 📁 template
    ├── 📄 {{_copier_conf.answers_file}}.jinja
    └── 📄 ...
```

With `models.py` containing your Pydantic `BaseModel`'s like this

```python
from pydantic import BaseModel

class DatabaseConfig(BaseModel):
    db_host: str
    db_port: int
```

you can then use your model to validate the question input like this

```yaml
validated_example:
  type: yaml
  multiline: true
  default: |
    db_host: 'localhost'
    db_port: 5432
  validator: "{{ validated_example | validate_as(DatabaseConfig) }}"
```

## Features

### `MultilineValidation` — multiline error messages

By default Copier truncates validation error messages to a single line. Loading `copier_pydantic.MultilineValidation` makes copier displays the full validation message,
(e.g. complete Pydantic errors) which is more clear to users!

```yaml
_jinja_extensions:
  - copier_pydantic.MultilineValidation
  - copier_pydantic.PydanticExtension
```

### `validate_as` — validation in `copier.yml`

Returns an empty string when the input is valid, or a Pydantic error message when it isn't. Use it in a `validator:` field to give the user feedback at the prompt.

```yaml
validator: "{{ my_input | validate_as(DatabaseConfig) }}"
```

### `to_model` — access model instances in templates

Validates the input and returns a Pydantic model instance, giving you access to methods and computed properties.

```
{{ my_input | to_model(DatabaseConfig) }}
```

### `to_model_dict` — access validated data as a dict in templates

Validates the input and returns a plain dictionary. Useful when you want default values filled in or field validators applied, but prefer dict-style access in Jinja2.

```
{{ my_input | to_model_dict(DatabaseConfig) }}
```

For example, if `DatabaseConfig` has `db_port: int = 5432` and the user omits `db_port`, `to_model_dict` will include it with its default value.

### Jinja2 `is` tests — conditional logic in templates

For each model, two Jinja2 tests are registered automatically:

- `value is ModelName` — true if the value is valid according to the model
- `value is ModelName_Model` — true if the value is already an instance of the model

```
{% if my_input is DatabaseConfig %}
  db_host: {{ my_input.db_host }}
{% endif %}
```

### `models` dict — lookup by name

All models are also available via `models.ModelName` or `models["ModelName"]` as an alternative to the bare name. This is useful for instance for:

**Dynamic lookup** — when which model to validate against depends on a question answer, you can index `models` by a variable, which you can't do with bare names:

```yaml
# copier.yml
backend:
  type: str
  choices: [PostgresConfig, SqliteConfig]
db_config:
  type: yaml
```

```jinja
{{ db_config | validate_as(models[backend]) }}
```

**Iterating all models** — generating docs, schemas, or repeated config blocks for every model defined in `models.py`:

```jinja
{% for name, model in models.items() %}
## {{ name }}
{{ db_config | to_model_dict(model) }}
{% endfor %}
```

**Avoiding name collisions** — if a Copier question shares a name with a model (e.g. a question called `config` and a model called `Config`), the question answer shadows the model in the template namespace. `models.Config` is always unambiguous.
