Code Guidelines
These guidelines cover coding conventions for the Python, TypeScript, and C# Prompty runtimes. Following them keeps the codebase consistent and makes PRs easier to review.
Repository Layout
Section titled “Repository Layout”runtime/├── python/prompty/ # Python runtime (pip: prompty)│ ├── prompty/ # Package source│ └── tests/ # Unit + integration tests├── typescript/ # TypeScript monorepo│ └── packages/│ ├── core/ # @prompty/core│ ├── openai/ # @prompty/openai│ ├── foundry/ # @prompty/foundry│ └── anthropic/ # @prompty/anthropic└── csharp/ # C# solution ├── Prompty.Core/ # Core runtime ├── Prompty.OpenAI/ # OpenAI provider ├── Prompty.Foundry/ # Foundry provider ├── Prompty.Anthropic/ # Anthropic provider └── Prompty.*.Tests/ # Test projectsPython Runtime
Section titled “Python Runtime”Environment Setup
Section titled “Environment Setup”- Python ≥ 3.11 — use modern syntax (
X | Yunions,match/case, f-strings). - Use
uvexclusively for environment and package management. Never usepiporpython -m venvdirectly.
cd runtime/python/promptyuv venvuv pip install -e ".[dev,all]"Linting & Formatting
Section titled “Linting & Formatting”We use Ruff for both linting and formatting. Configuration is in pyproject.toml:
| Setting | Value |
|---|---|
| Line length | 120 |
| Target | py311 |
| Rules | E, F, I (isort), UP (pyupgrade) |
| Ignored | E501 (line length — handled by formatter) |
# Check for lint issuesuv run ruff check .
# Auto-formatuv run ruff format .Code Style
Section titled “Code Style”from __future__ import annotationsat the top of every module.- Type hints everywhere — all function signatures, return types, and class attributes.
- Docstrings — every module gets a module-level docstring. Public classes and functions get docstrings too.
__all__— define in modules that export public API.- Private names — prefix internal helpers with
_(e.g.,_message_to_wire). - No bare
except:— always catch specific exceptions (except Exception:at minimum). - Constants —
UPPER_SNAKE_CASEat module level.
Import Conventions
Section titled “Import Conventions”# Internal code → relative importsfrom .types import Messagefrom ..tracing.tracer import trace
# Tests → absolute importsfrom prompty.core.types import Messagefrom prompty.tracing.tracer import Tracer
# Optional dependencies → lazy imports inside functionsdef execute(self, agent, messages): from openai import OpenAI # only imported when actually calledImport sort order is enforced by Ruff’s I (isort) rule: stdlib → third-party → local, alphabetized within each group.
Async Conventions
Section titled “Async Conventions”Every public pipeline function has a sync and async variant:
# Syncmessages = prepare("prompt.prompty", inputs={"name": "Jane"})
# Asyncmessages = await prepare_async("prompt.prompty", inputs={"name": "Jane"})Protocol classes define both render() / render_async(), parse() / parse_async(), etc.
Testing
Section titled “Testing”- Framework: pytest + pytest-asyncio
- Run unit tests:
uv run pytest tests/ -q - Run integration tests:
pytest tests/integration/ -v -o "addopts=" - Mock external services — never make real API calls in unit tests.
- Async tests use
@pytest.mark.asynciowithasyncio_mode = "strict".
Integration tests require API keys in a .env file and are excluded by default via
the integration marker.
Dependencies
Section titled “Dependencies”- Required deps go in
[project.dependencies]. - Optional deps go in
[project.optional-dependencies]— never add optional deps to the required list. - Entry points in
pyproject.tomlregister invokers for the plugin discovery system. - After changing entry points or deps, reinstall:
uv pip install -e ".[dev,all]".
Error Handling
Section titled “Error Handling”| Error Type | When to Use |
|---|---|
InvokerError | Missing invoker registrations |
ValueError | Invalid frontmatter, missing env vars without defaults |
FileNotFoundError | Missing .prompty files or ${file:...} references |
DeprecationWarning | Legacy property migrations (via warnings.warn) |
TypeScript Runtime
Section titled “TypeScript Runtime”Environment Setup
Section titled “Environment Setup”- Node.js ≥ 18
- Standard npm workspace monorepo
cd runtime/typescriptnpm installnpm run buildProject Structure
Section titled “Project Structure”Each package follows an identical layout:
packages/<name>/├── src/ # Source code├── tests/ # Vitest test files├── dist/ # Build output (ESM + CJS)├── package.json├── tsconfig.json└── tsup.config.tsTypeScript Config
Section titled “TypeScript Config”Packages extend tsconfig.base.json:
| Setting | Value |
|---|---|
| Target | ES2022 |
| Module | ES2022 |
| Module Resolution | bundler |
| Strict | true |
Building & Linting
Section titled “Building & Linting”# Build all packagesnpm run build
# Type-check (used as lint)tsc --noEmit
# Run testsnpm run testThe project uses tsup for bundling — dual ESM + CJS output with declaration files, targeting node18.
Code Style
Section titled “Code Style”- Named exports only — no default exports.
- Barrel exports — each directory has an
index.tsthat re-exports public API. - Explicit
.jsextensions in relative imports (required for ESM compatibility). - Strong typing —
strict: truein tsconfig, avoidanywhere possible.
// ✅ Named export with .js extensionexport { PromptyChatParser } from './parsers/prompty.js';
// ❌ Don't use default exportsexport default class MyParser { ... }Testing
Section titled “Testing”- Framework: Vitest
- Test files:
tests/**/*.test.ts - Run:
npm run test(across all workspaces) - Tests use Vitest’s built-in mocking (
vi.fn(),vi.mock()). - Coverage is collected via the
v8provider.
Adding a New Provider Package
Section titled “Adding a New Provider Package”- Create
packages/<provider>/following the existing package structure. - Add
src/index.tswith named exports for your executor and processor. - Add a
tsup.config.tsandvitest.config.tsmatching the existing packages. - Register the package in the root
package.jsonworkspaces array.
C# Runtime
Section titled “C# Runtime”Environment Setup
Section titled “Environment Setup”- .NET 9 — use the latest SDK features and C# 13 syntax.
- Solution file at
runtime/csharp/prompty.sln.
cd runtime/csharpdotnet builddotnet testProject Structure
Section titled “Project Structure”| Project | Description |
|---|---|
Prompty.Core | Core runtime — loader, pipeline, protocols, types |
Prompty.OpenAI | OpenAI provider (executor + processor) |
Prompty.Foundry | Azure AI Foundry provider |
Prompty.Anthropic | Anthropic Claude provider |
Prompty.Core.Tests | Core unit tests (xUnit) |
Prompty.Providers.Tests | Provider integration tests (xUnit) |
Building & Formatting
Section titled “Building & Formatting”# Build the solutiondotnet build
# Run all testsdotnet test
# Check formatting (CI uses --verify-no-changes)dotnet format --verify-no-changes
# Auto-formatdotnet formatCode Style
Section titled “Code Style”- Fluent builder pattern for provider registration:
new PromptyBuilder().AddOpenAI(). - Extension methods for provider registration — each provider package exposes
AddXxx()extensions onPromptyBuilder. - Nullable reference types enabled — annotate all public APIs.
IAsyncEnumerable<T>for streaming responses.- File-scoped namespaces — one namespace per file, no extra nesting.
// ✅ Fluent builder registrationvar builder = new PromptyBuilder() .AddOpenAI() .AddFoundry();
// ✅ Extension method patternpublic static class OpenAIExtensions{ public static PromptyBuilder AddOpenAI(this PromptyBuilder builder) { builder.RegisterExecutor("openai", new OpenAIExecutor()); builder.RegisterProcessor("openai", new OpenAIProcessor()); return builder; }}Testing
Section titled “Testing”- Framework: xUnit +
FluentAssertions - Run tests:
dotnet test - Tests use xUnit’s
[Fact]and[Theory]attributes. - Mock external services with
NSubstitute— never make real API calls in unit tests. - Integration tests use the
[Trait("Category", "Integration")]attribute and are excluded by default.
NuGet Packages
Section titled “NuGet Packages”NuGet packages are published via CI. Version numbers follow the solution-level Directory.Build.props.
General Guidelines
Section titled “General Guidelines”Commit Messages
Section titled “Commit Messages”- Use clear, descriptive commit messages.
- Include a
Co-authored-bytrailer when pair-programming or using AI assistance.
Pull Requests
Section titled “Pull Requests”- Run all linters and tests locally before opening a PR.
- Keep PRs focused — one feature or fix per PR.
- Update documentation if your change affects user-facing behavior.
- Add tests for new functionality.
Adding a New Provider
Section titled “Adding a New Provider”- Create
prompty/providers/<name>/with__init__.py,executor.py,processor.py. - Implement the
ExecutorProtocolandProcessorProtocolinterfaces. - Register entry points in
pyproject.toml:
[project.entry-points."prompty.executors"]my_provider = "prompty.providers.my_provider.executor:MyExecutor"
[project.entry-points."prompty.processors"]my_provider = "prompty.providers.my_provider.processor:MyProcessor"- Add optional dependency:
my_provider = ["my-provider-sdk"] - Reinstall:
uv pip install -e ".[dev,all]"
- Create
packages/<provider>/following the existing package structure. - Add
src/index.tswith named exports for your executor and processor. - Add a
tsup.config.tsandvitest.config.tsmatching the existing packages. - Register the package in the root
package.jsonworkspaces array.
export { MyExecutor } from "./executor.js";export { MyProcessor } from "./processor.js";- Create a new project
Prompty.MyProviderin the solution. - Implement
IExecutorandIProcessorinterfaces. - Add a
PromptyBuilderextension method for registration:
public static class MyProviderExtensions{ public static PromptyBuilder AddMyProvider(this PromptyBuilder builder) { builder.RegisterExecutor("myprovider", new MyExecutor()); builder.RegisterProcessor("myprovider", new MyProcessor()); return builder; }}- Add a NuGet package spec in the project file.
- Add a test project
Prompty.MyProvider.Tests.