Metadata-Version: 2.4
Name: fastgrpcpy
Version: 0.1.0
Summary: FastAPI-style gRPC for Python
Project-URL: Homepage, https://asheshvidyut.github.io/fastgrpcpy/
Project-URL: Repository, https://github.com/asheshvidyut/fastgrpcpy
Project-URL: Documentation, https://asheshvidyut.github.io/fastgrpcpy/
Project-URL: Issues, https://github.com/asheshvidyut/fastgrpcpy/issues
Author-email: Ashesh Vidyut <ashesh.vidyut@gmail.com>
License: MIT
License-File: LICENSE
Keywords: async,fastapi,grpc,protobuf,python,rpc
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: grpcio-reflection>=1.80
Requires-Dist: grpcio-tools>=1.80
Requires-Dist: grpcio>=1.80
Requires-Dist: rich>=13.0
Requires-Dist: typer>=0.12
Requires-Dist: watchfiles>=0.21
Provides-Extra: dev
Requires-Dist: pyright>=1.1.380; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
Description-Content-Type: text/markdown

<h1 align="center">fastgrpcpy</h1>

<p align="center">
  <strong>FastAPI-style gRPC for Python.</strong><br>
  Define services with type-annotated Python. Get the protobuf schema, generated stubs, and a running server — automatically.
</p>

<p align="center">
  <a href="#"><img alt="Python" src="https://img.shields.io/badge/python-3.11%2B-blue"></a>
  <a href="#"><img alt="License" src="https://img.shields.io/badge/license-MIT-green"></a>
  <a href="#"><img alt="Status" src="https://img.shields.io/badge/status-alpha-orange"></a>
</p>

---

## Overview

fastgrpcpy treats your Python type hints as the source of truth for a gRPC service. The framework derives the `.proto` schema from your code, compiles the gRPC stubs, wires your handlers into the generated servicer, and runs the server — sync or async, with hot reload in development.

```python
from dataclasses import dataclass
from fastgrpcpy import rpc, service

@dataclass
class User:
    id: int
    name: str

@service
class UserService:
    @rpc
    async def get_user(self, user_id: int) -> User:
        return User(id=user_id, name="FastgRPC")
```

```bash
fastgrpcpy dev main.py
```

Call it from any gRPC client. With server reflection enabled in dev mode, `grpcurl` works with no extra setup:

```bash
grpcurl -plaintext -d '{"user_id": 42}' \
  127.0.0.1:50051 fastgrpcpy.UserService/GetUser
# {"id": "42", "name": "Ashesh"}
```

That single command performs introspection, code generation, compilation, and serving in one step. No `.proto` files to write or maintain by hand.

---

## Features

- **Zero-boilerplate service definition** — decorate a class with `@service`, decorate methods with `@rpc`. fastgrpcpy handles the rest.
- **Hot reload in development**, including transitive Python imports, powered by a Rust-backed file watcher.
- **Automatic protobuf generation** from Python type hints, including nested dataclasses, `Optional`, `list[T]`, `dict[str, T]`, and `AsyncIterator[T]`.
- **Wire compatibility guarantees** — field numbers are pinned in a lock file (`.fastgrpcpy.lock`), preventing accidental wire-breaking changes.
- **Sync and async** servers, auto-selected from your handler signatures (`grpc.aio` for async, `grpc.server` for sync).
- **All four streaming modes** — unary-unary, unary-stream, stream-unary, stream-stream.
- **gRPC server reflection** enabled in dev mode, so `grpcurl`, Postman, and BloomRPC work without any configuration.
- **Multi-file projects** — split services, business logic, and models freely; reload follows imports.

---

## Installation

Requires Python 3.11 or later.

```bash
pip install fastgrpcpy
```

For local development:

```bash
pip install -e '.[dev]'
```

---

## Command-line interface

| Command | Purpose |
|---|---|
| `fastgrpcpy dev <file>` | Start the development server with hot reload and reflection |
| `fastgrpcpy run <file>` | Start the production server (no reload, no reflection) |
| `fastgrpcpy proto <file>` | Print the generated `.proto` schema to stdout |
| `fastgrpcpy compile <file>` | Write the `.proto` and compiled stubs to a directory |

### `fastgrpcpy dev`

The development server selects the appropriate gRPC stack based on your handler signatures:

- Any `async def` handler → `grpc.aio.server()`
- All `def` handlers → `grpc.server(ThreadPoolExecutor)`
- Mixed sync and async → fails fast with a `ValidationError`

```bash
fastgrpcpy dev examples/complex/main.py
fastgrpcpy dev examples/complex/main.py --host 0.0.0.0 --port 50051
```

On every file save the framework executes the following pipeline:

1. The Rust-backed watcher detects a `.py` change in the project tree.
2. User modules are evicted from `sys.modules` so transitive imports reload cleanly.
3. The entry-point file is re-imported, re-running the `@service` and `@rpc` decorators.
4. The pipeline runs: inspector → field-number lock → validator → proto writer → `protoc` compiler.
5. The previous server is gracefully stopped; a new one is built with the rewired handlers.

Reflection is enabled, so `grpcurl -plaintext localhost:50051 list` works out of the box.

### `fastgrpcpy run`

Production server. No watcher, no reflection.

```bash
fastgrpcpy run main.py --host 0.0.0.0 --port 50051
```

### `fastgrpcpy proto`

Print the generated `.proto` schema to stdout. Suitable for piping into a clients repository or copying into another build pipeline.

```bash
fastgrpcpy proto main.py
fastgrpcpy proto main.py > schemas/user.proto
fastgrpcpy proto main.py --package myco.users
```

The command consults `.fastgrpcpy.lock` to keep field numbers stable across runs.

### `fastgrpcpy compile`

Produce a complete client bundle: the `.proto` source plus pre-compiled `_pb2.py` and `_pb2_grpc.py` modules.

```bash
fastgrpcpy compile main.py --out ./client/
```

```
client/
├── fastgrpcpy.proto
├── fastgrpcpy_pb2.py
└── fastgrpcpy_pb2_grpc.py
```

The output directory can be checked in to your clients repository or published as its own package.

---

## Watch mode

`fastgrpcpy dev` runs a continuous build-and-serve loop:

```
edit main.py / business.py / models.py
        ↓ (Rust file watcher fires)
purge user modules from sys.modules
        ↓
re-import main.py — decorators re-register services
        ↓
inspector → lock → validator → proto writer → protoc compile
        ↓
stop the running server, start a new one with rewired handlers
```

Behavior worth knowing:

- **Transitive imports reload.** Editing `business.py` or `models.py` triggers a full rebuild. Without the explicit `sys.modules` purge, Python would serve cached versions.
- **Framework modules persist.** `fastgrpcpy`, `grpc`, and `google.protobuf` are excluded from the purge to keep reload latency low; re-importing the generated grpc stubs every keystroke would be prohibitively slow.
- **`.py` files trigger reload.** Generated `.proto` and `_pb2.py` artifacts are filtered out by the watcher to prevent reload loops.
- **Reload errors are isolated.** A failed rebuild (syntax error, validation failure, etc.) does not kill the running server; the previous server keeps serving traffic and the error is logged.

---

## Project layout

A typical fastgrpcpy service:

```
my_service/
├── main.py                    # @service classes
├── business.py                # business logic, imported by main.py
├── models.py                  # dataclasses, imported by both
├── .fastgrpcpy/                 # gitignored, ephemeral build artifacts
│   ├── fastgrpcpy.proto         # generated schema, refreshed every reload
│   ├── fastgrpcpy_pb2.py        # compiled message classes
│   └── fastgrpcpy_pb2_grpc.py   # compiled servicer base + stub
└── .fastgrpcpy.lock             # committed; tracks protobuf field numbers
```

Everything inside `.fastgrpcpy/` is regenerated on every `fastgrpcpy dev` reload. To export the schema for client teams, use `fastgrpcpy proto` (stdout) or `fastgrpcpy compile --out <dir>` (writes a clean bundle wherever you choose).

### Field-number stability and `.fastgrpcpy.lock`

Protobuf identifies fields by number, not by name. If field numbers shift between releases — for example because a new field was inserted in the middle of a dataclass — every existing client will silently misinterpret the wire payload.

`.fastgrpcpy.lock` is the authoritative record of every field number ever assigned. New fields receive the next available number; removed fields are tombstoned and never reused.

```toml
[User]
id = 1
name = 2

[User.removed]
legacy_email = 3   # tombstoned — number 3 is permanently retired
```

Commit `.fastgrpcpy.lock` and review changes in pull requests, the same way you would treat a lock file or a database migration history.

---

## Examples

The `examples/` directory contains progressively larger services:

| Folder | Demonstrates |
|---|---|
| `hello_world/` | Minimal async service |
| `async_basic/` | Async handler with `await asyncio.sleep` |
| `sync/` | Sync handler running on the grpc thread-pool server |
| `complex/` | Multi-file project: `main.py`, `business.py`, `models.py`, two services, nested dataclasses |
| `streaming/` | Server, client, and bidirectional streaming |
| `middleware/` | Interceptor pattern (logging example) |

```bash
fastgrpcpy dev examples/complex/main.py --port 50065

grpcurl -plaintext 127.0.0.1:50065 list
grpcurl -plaintext -d '{"user_id": 1}' \
  127.0.0.1:50065 fastgrpcpy.UserService/GetUser
```

---

## Architecture

```
src/fastgrpcpy/
├── decorators.py        # @service, @rpc, the registry
├── app.py               # App configuration, interceptor registry
├── exceptions.py        # gRPC-status-mapped exception classes
├── codegen/
│   ├── ir.py            # ProtoFile / ProtoMessage / ProtoField (dataclasses)
│   ├── inspector.py     # Python types → IR
│   ├── lock.py          # field-number stability via .fastgrpcpy.lock
│   ├── validator.py     # IR and registry sanity checks
│   ├── proto_writer.py  # IR → .proto source string
│   └── compiler.py      # invokes grpcio-tools / protoc
├── server/
│   ├── runner.py        # build pipeline + lifecycle
│   ├── wiring.py        # servicer synthesis, sync/async detection
│   ├── converter.py     # dataclass ↔ protobuf recursive conversion
│   ├── watcher.py       # watchfiles-based hot reload
│   └── reflection.py    # gRPC server reflection setup
└── cli/
    └── main.py          # Typer CLI: dev, run, proto, compile
```

The codegen layer is a sequence of pure functions over an in-memory `ProtoFile` IR. The IR itself is never persisted; only the final `.proto` and `.fastgrpcpy.lock` are.

---

## Development

```bash
pip install -e '.[dev]'
pytest                # unit + integration tests
ruff check .
mypy src
```

The test suite includes end-to-end integration tests that spin up a real gRPC server (both sync and async) and exercise it over the wire.

---

## Project status

Alpha. The core pipeline — code generation, lock-file management, sync and async server runtimes, hot reload, multi-file projects — is implemented and covered by tests. Known gaps include TLS support, OpenTelemetry tracing, and HTTP/JSON transcoding.

---

## License

MIT
