# pmgfal

pydantic model generator for atproto lexicons

rust-powered lexicon parsing and python code generation.

## install

```bash
uv add pmgfal
```

## usage

```bash
# auto-detect ./lexicons or current dir
uvx pmgfal

# explicit paths
uvx pmgfal ./lexicons -o ./src/atproto

# filter by namespace
uvx pmgfal -p fm.plyr

# force regeneration (skip cache)
uvx pmgfal --no-cache
```

## caching

pmgfal caches generated models based on a hash of your lexicon files. on subsequent runs with unchanged lexicons, it copies from cache instead of regenerating.

cache location:
- unix (linux/macos/bsd): `~/.cache/pmgfal/` (or `$XDG_CACHE_HOME/pmgfal/`)
- windows: `%LOCALAPPDATA%/pmgfal/`

the cache key includes:
- pmgfal version (cache invalidates on upgrade)
- namespace prefix filter
- content of all lexicon json files

## output

```python
# auto-generated by pmgfal - do not edit

from __future__ import annotations

from pydantic import BaseModel, Field


class FmPlyrTrack(BaseModel):
    """fm.plyr.track record"""

    uri: str
    title: str
    artist: str
    duration_ms: int | None = Field(default=None, alias="durationMs")
```

## adoption guide

### 1. add lexicons to your project

```
your-project/
├── lexicons/
│   └── fm/
│       └── plyr/
│           ├── track.json
│           ├── like.json
│           └── comment.json
├── src/
│   └── atproto/
│       └── .gitkeep
└── pyproject.toml
```

### 2. generate models

```bash
uvx pmgfal ./lexicons -o ./src/atproto -p fm.plyr
```

### 3. use in your code

```python
from your_project.atproto import FmPlyrTrack, FmPlyrLike

track = FmPlyrTrack(
    uri="at://did:plc:xyz/fm.plyr.track/123",
    title="my song",
    artist="me",
)
```

### 4. regenerate when lexicons change

**option a: pre-commit hook**

```yaml
# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: pmgfal
        name: generate atproto models
        entry: uvx pmgfal ./lexicons -o ./src/atproto -p fm.plyr
        language: system
        files: ^lexicons/.*\.json$
        pass_filenames: false
```

**option b: justfile**

```just
# justfile
generate:
    uvx pmgfal ./lexicons -o ./src/atproto -p fm.plyr
```

**option c: github actions**

```yaml
# .github/workflows/ci.yml
- name: generate models
  run: uvx pmgfal ./lexicons -o ./src/atproto -p fm.plyr
```

caching ensures regeneration is fast (~0.3s for 300 lexicons) when files haven't changed.

## external refs

pmgfal bundles all `com.atproto.*` lexicons and automatically resolves external refs. for example, if your lexicon references `com.atproto.repo.strongRef`, pmgfal generates:

```python
class ComAtprotoRepoStrongRef(BaseModel):
    uri: str
    cid: str

class FmPlyrLike(BaseModel):
    subject: ComAtprotoRepoStrongRef  # properly typed!
    created_at: str = Field(alias="createdAt")
```

## how it works

1. parses lexicon json using [atrium-lex](https://github.com/atrium-rs/atrium) (rust)
2. resolves internal (`#localDef`) and external (`com.atproto.*`) refs
3. generates pydantic v2 models with field aliases
4. outputs standalone python - no atproto sdk dependency
