Metadata-Version: 2.4
Name: promptree
Version: 0.1.1
Summary: Type-safe Jinja prompt trees with runtime validation and generated Python stubs.
Requires-Python: >=3.10
Requires-Dist: jinja2>=3.0.0
Provides-Extra: watch
Requires-Dist: watchdog>=3.0; extra == 'watch'
Description-Content-Type: text/markdown

# promptree

`promptree` turns a directory of Jinja templates into a type-safe Python prompt tree, with runtime validation and generated `.pyi` stubs for IDE autocomplete.

It gives you:

- Dot-notation access to prompt folders and files
- Runtime validation through Jinja `StrictUndefined`
- Generated `.pyi` stubs for IDE autocomplete
- Full Jinja support, including `include`, `extends`, and macros
- Raw strings as the only integration format, so there is no adapter layer

## Install

```bash
pip install promptree
```

Optional extras:

```bash
pip install promptree[watch]
pip install promptree[pydantic-ai]
```

## Quick Start

Directory layout:

```text
prompts/
├── system.md
└── user/
    ├── greeting.md
    └── farewell.txt
```

Example templates:

```jinja
{# prompts/system.md #}
System prompt for {{ name }}.
```

```jinja
{# prompts/user/greeting.md #}
Hello {{ name }}!
```

```jinja
{# prompts/user/farewell.txt #}
Goodbye {{ name }}.
```

Use them from Python:

```python
from promptree import Promptree

prompts = Promptree("./prompts")

print(prompts.system(name="Claude"))
print(prompts.user.greeting(name="Tim"))
print(prompts.user.farewell(name="Tim"))
```

The CLI can generate an importable package and matching type stubs inside the prompt directory:

```bash
promptree generate ./prompts
promptree check ./prompts
```

You can also run the CLI as a module:

```bash
python -m promptree generate ./prompts
```

## Pre-commit And CI

`promptree check` regenerates the stubs in memory and exits non-zero if the files on disk are stale.
That makes it useful for both local pre-commit enforcement and CI.

If you keep a hand-written `__init__.py` in a prompt directory, `promptree generate` will leave it alone and `promptree check` will ignore that file.

The repository includes a hook definition in [`.pre-commit-hooks.yaml`](./.pre-commit-hooks.yaml), and a consumer can wire it up like this:

```yaml
- repo: https://github.com/your-org/promptree
  rev: v0.1.0
  hooks:
    - id: promptree-check
      files: ^prompts/
```

For CI, run the same command directly:

```bash
promptree check ./prompts
```

## Why No Adapter Layer

Rendered prompt text is the common format across libraries like pydantic_ai, LangChain, and the OpenAI Python client.
`promptree` focuses on generating and validating that text well, rather than wrapping every downstream API.

## pydantic_ai

Full runnable example:

```python
from dataclasses import dataclass

from pydantic_ai import Agent, RunContext
from promptree import Promptree

prompts = Promptree("./prompts")


@dataclass
class MyDeps:
    user_name: str
    language: str


agent = Agent("openai:gpt-4o", deps_type=MyDeps)

# Static: render once at agent creation
agent_static = Agent(
    "openai:gpt-4o",
    instructions=prompts.system(name="Claude"),
)


# Dynamic: re-evaluated every run with access to ctx.deps
@agent.instructions
def dynamic_instructions(ctx: RunContext[MyDeps]) -> str:
    return prompts.system(
        name=ctx.deps.user_name,
        language=ctx.deps.language,
    )


result = agent.run_sync(
    prompts.user.greeting(name="Tim"),
    deps=MyDeps(user_name="Tim", language="en"),
)
print(result.output)
```

## LangChain

```python
from langchain_core.messages import HumanMessage, SystemMessage

from promptree import Promptree

prompts = Promptree("./prompts")

messages = [
    SystemMessage(content=prompts.system(name="Claude")),
    HumanMessage(content=prompts.user.greeting(name="Tim")),
]
```

## Raw OpenAI Client

```python
from openai import OpenAI

from promptree import Promptree

client = OpenAI()
prompts = Promptree("./prompts")

client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": prompts.system(name="Claude")},
        {"role": "user", "content": prompts.user.greeting(name="Tim")},
    ],
)
```

## Generated Package

When you run `promptree generate ./prompts`, promptree writes two files into the prompt directory:

- `__init__.py` with a `tree` object backed by `Promptree`
- `__init__.pyi` with nested classes and typed call signatures for autocomplete

That means you can import the generated package and get both runtime access and IDE support from the same directory.
