Contributing¶
Contributions are welcome! This page describes how to set up the development environment, run tests, and submit changes.
Prerequisites¶
This project uses mise to manage tool versions. Install it first:
Then install all pinned tools (buf, just, uv, protoc, golangci-lint, pre-commit):
Without mise: install
go,buf,protoc,uv,golangci-lint,just, andpre-commitmanually, then runjust init.
Setup¶
After installing tools, set up the project (sync the Python virtualenv, install pre-commit hooks):
Development workflow¶
just dev # Full cycle: build → generate → test
just build # Build the Go binary
just generate # Build + regenerate Python models from test protos
just test # Run Python tests only
just lint # Run all linters (Go + Python + type check)
Run just --list to see all available recipes.
Project structure¶
├── main.go # Entry point + proto option builders
├── generator.go # Processing + type resolution
├── types.go # Domain types + data maps
├── constraints.go # buf.validate translation
├── template.go # Python template constants + buildProtoTypesContent
├── format.go # Formatting utilities
├── go.mod / go.sum
├── Justfile # just command runner recipes
├── buf.yaml / buf.gen.yaml # Buf workspace and codegen config
└── test/
├── proto/api/v1/*.proto # Test proto definitions
├── gen/api/v1/*_pydantic.py # Generated output (committed)
├── gen_options/ # Generated output with non-default options
└── tests/ # Pytest test suite
Architecture¶
The plugin is split across six files in package main:
| File | Responsibility |
|---|---|
main.go |
Entry point, plugin options, proto option builders |
generator.go |
processFile(), processMessage(), type resolution |
types.go |
Domain types (Message, Field, Enum, …), wellKnownTypes, reservedNames |
constraints.go |
buf.validate extraction (extractFieldConstraints(), applyConstraintTypeOverrides()) |
template.go |
modelTemplate constant, buildProtoTypesContent() |
format.go |
Formatting utilities |
Key functions:
processFile()— iterates messages and enums in a proto fileprocessMessage()— buildsMessagestructs with fields, nested types, constraintsresolveType()/resolveBaseType()— maps proto types to Python typesresolveQualifiedName()— returns the dotted path for nested type referencesextractFieldConstraints()— reads buf.validate field options viadynamicpbbuildProtoTypesContent()— assembles_proto_types.pyconditionally per directorymodelTemplate— Gotext/templateconstant that renders the Python output
Adding a new feature¶
Adding a field type mapping¶
Edit the wellKnownTypes map in types.go to add a new WKT → Python type mapping.
Adding a plugin option¶
- Add a field to the
GeneratorConfigstruct - Parse the option in the flag setup section of
main() - Pass it through to the template context
- Use it in the
modelTemplate
Adding buf.validate support¶
- Add constraint extraction logic in
extractFieldConstraints() - Apply type overrides (if needed) in
applyConstraintTypeOverrides() - Add any new helper functions to the
protoTypes*constants intemplate.go - Update
buildProtoTypesContent()to conditionally include new helpers
Adding tests¶
-
Add proto definitions to
test/proto/api/v1/*.proto(ortest/proto/partial/v1/for buf.validate subset tests) -
Rebuild and regenerate:
-
Add pytest functions to the matching test file in
test/tests/(e.g.test_scalars.py,test_validate.py) -
Run tests:
Test conventions¶
- Use plain pytest functions, not
class Test...wrappers - Use
@pytest.mark.parametrizefor multiple similar cases - Use fixtures for shared setup
- Generated files are checked by
test_ruff_format(ruff) andtest_ty(type checker) — runjust testto catch format/type issues in generated output
Verifying generated files¶
The CI checks that generated files match what's committed. After any Go changes, regenerate and commit the updated output:
You can verify locally with:
Linting¶
just lint # All linters
just lint-go # Go: golangci-lint (uses goimports)
just lint-python # Python: ruff on test suite
just lint-types # Python: ty type checker on tests/ only
just fix-python # Auto-fix Python lint issues
To auto-fix Go imports:
Submitting changes¶
- Fork the repository and create a branch from
main - Make your changes
- Ensure
just devpasses (build + generate + test) - Ensure
just lintpasses - Commit the updated generated files alongside your code changes
- Open a pull request with a clear description of what changed and why
Reporting issues¶
Please open an issue with:
- The proto file(s) involved
- The expected generated output
- The actual generated output
- Your
protoc-gen-pydanticversion (protoc-gen-pydantic --version)