fastgrpc3 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. fastgrpc3-0.1.0/LICENSE +21 -0
  2. fastgrpc3-0.1.0/PKG-INFO +214 -0
  3. fastgrpc3-0.1.0/README.md +187 -0
  4. fastgrpc3-0.1.0/pyproject.toml +93 -0
  5. fastgrpc3-0.1.0/pyproject.toml.orig +91 -0
  6. fastgrpc3-0.1.0/src/fastgrpc3/__init__.py +75 -0
  7. fastgrpc3-0.1.0/src/fastgrpc3/__main__.py +7 -0
  8. fastgrpc3-0.1.0/src/fastgrpc3/_proto/__init__.py +1 -0
  9. fastgrpc3-0.1.0/src/fastgrpc3/_proto/_descriptor.py +130 -0
  10. fastgrpc3-0.1.0/src/fastgrpc3/_proto/google/__init__.py +1 -0
  11. fastgrpc3-0.1.0/src/fastgrpc3/_proto/google/rpc/__init__.py +1 -0
  12. fastgrpc3-0.1.0/src/fastgrpc3/_proto/google/rpc/code_models.py +155 -0
  13. fastgrpc3-0.1.0/src/fastgrpc3/_proto/google/rpc/error_details_models.py +332 -0
  14. fastgrpc3-0.1.0/src/fastgrpc3/_proto/google/rpc/status_models.py +33 -0
  15. fastgrpc3-0.1.0/src/fastgrpc3/_proto/grpc/__init__.py +1 -0
  16. fastgrpc3-0.1.0/src/fastgrpc3/_proto/grpc/health/__init__.py +1 -0
  17. fastgrpc3-0.1.0/src/fastgrpc3/_proto/grpc/health/v1/__init__.py +1 -0
  18. fastgrpc3-0.1.0/src/fastgrpc3/_proto/grpc/health/v1/health_models.py +45 -0
  19. fastgrpc3-0.1.0/src/fastgrpc3/_proto/grpc/health/v1/health_service.py +62 -0
  20. fastgrpc3-0.1.0/src/fastgrpc3/_proto/grpc/reflection/__init__.py +1 -0
  21. fastgrpc3-0.1.0/src/fastgrpc3/_proto/grpc/reflection/v1/__init__.py +1 -0
  22. fastgrpc3-0.1.0/src/fastgrpc3/_proto/grpc/reflection/v1/reflection_models.py +145 -0
  23. fastgrpc3-0.1.0/src/fastgrpc3/_proto/grpc/reflection/v1/reflection_service.py +37 -0
  24. fastgrpc3-0.1.0/src/fastgrpc3/_proto/grpc/reflection/v1alpha/__init__.py +1 -0
  25. fastgrpc3-0.1.0/src/fastgrpc3/_proto/grpc/reflection/v1alpha/reflection_models.py +147 -0
  26. fastgrpc3-0.1.0/src/fastgrpc3/_proto/grpc/reflection/v1alpha/reflection_service.py +37 -0
  27. fastgrpc3-0.1.0/src/fastgrpc3/api/__init__.py +0 -0
  28. fastgrpc3-0.1.0/src/fastgrpc3/api/app.py +452 -0
  29. fastgrpc3-0.1.0/src/fastgrpc3/api/client.py +360 -0
  30. fastgrpc3-0.1.0/src/fastgrpc3/api/config.py +110 -0
  31. fastgrpc3-0.1.0/src/fastgrpc3/api/context.py +63 -0
  32. fastgrpc3-0.1.0/src/fastgrpc3/api/deps.py +314 -0
  33. fastgrpc3-0.1.0/src/fastgrpc3/api/errors.py +144 -0
  34. fastgrpc3-0.1.0/src/fastgrpc3/api/metadata.py +78 -0
  35. fastgrpc3-0.1.0/src/fastgrpc3/api/security.py +107 -0
  36. fastgrpc3-0.1.0/src/fastgrpc3/api/service.py +200 -0
  37. fastgrpc3-0.1.0/src/fastgrpc3/cli.py +299 -0
  38. fastgrpc3-0.1.0/src/fastgrpc3/codegen/__init__.py +5 -0
  39. fastgrpc3-0.1.0/src/fastgrpc3/codegen/generate.py +144 -0
  40. fastgrpc3-0.1.0/src/fastgrpc3/codegen/models.py +386 -0
  41. fastgrpc3-0.1.0/src/fastgrpc3/codegen/naming.py +97 -0
  42. fastgrpc3-0.1.0/src/fastgrpc3/codegen/source.py +165 -0
  43. fastgrpc3-0.1.0/src/fastgrpc3/ops/__init__.py +0 -0
  44. fastgrpc3-0.1.0/src/fastgrpc3/ops/docs.py +348 -0
  45. fastgrpc3-0.1.0/src/fastgrpc3/ops/health.py +88 -0
  46. fastgrpc3-0.1.0/src/fastgrpc3/ops/openapi.py +198 -0
  47. fastgrpc3-0.1.0/src/fastgrpc3/ops/reflection.py +90 -0
  48. fastgrpc3-0.1.0/src/fastgrpc3/py.typed +0 -0
  49. fastgrpc3-0.1.0/src/fastgrpc3/runtime/__init__.py +0 -0
  50. fastgrpc3-0.1.0/src/fastgrpc3/runtime/deadline.py +31 -0
  51. fastgrpc3-0.1.0/src/fastgrpc3/runtime/dispatch.py +261 -0
  52. fastgrpc3-0.1.0/src/fastgrpc3/runtime/workers.py +92 -0
  53. fastgrpc3-0.1.0/src/fastgrpc3/schema/__init__.py +20 -0
  54. fastgrpc3-0.1.0/src/fastgrpc3/schema/annotations.py +248 -0
  55. fastgrpc3-0.1.0/src/fastgrpc3/schema/breaking.py +184 -0
  56. fastgrpc3-0.1.0/src/fastgrpc3/schema/codec.py +242 -0
  57. fastgrpc3-0.1.0/src/fastgrpc3/schema/lock.py +224 -0
  58. fastgrpc3-0.1.0/src/fastgrpc3/schema/registry.py +393 -0
  59. fastgrpc3-0.1.0/src/fastgrpc3/schema/render.py +286 -0
  60. fastgrpc3-0.1.0/src/fastgrpc3/schema/schema.py +70 -0
  61. fastgrpc3-0.1.0/src/fastgrpc3/schema/verify.py +93 -0
  62. fastgrpc3-0.1.0/src/fastgrpc3/testing.py +52 -0
  63. fastgrpc3-0.1.0/src/fastgrpc3/transport/__init__.py +50 -0
  64. fastgrpc3-0.1.0/src/fastgrpc3/transport/base.py +68 -0
  65. fastgrpc3-0.1.0/src/fastgrpc3/transport/grpc_aio.py +86 -0
  66. fastgrpc3-0.1.0/src/fastgrpc3/transport/tonic.py +291 -0
  67. fastgrpc3-0.1.0/src/fastgrpc3/types.py +294 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 William Nauroy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastgrpc3
3
+ Version: 0.1.0
4
+ Summary: FastAPI-like gRPC framework for Python: contracts from .proto files or Pydantic models, powered by protoruf (Rust)
5
+ Author: William Nauroy
6
+ Author-email: William Nauroy <william.nauroy@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 1 - Planning
10
+ Classifier: Framework :: AsyncIO
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: System :: Networking
17
+ Classifier: Typing :: Typed
18
+ Requires-Dist: grpcio>=1.75.1
19
+ Requires-Dist: protoruf>=0.5.3
20
+ Requires-Dist: pydantic>=2.11
21
+ Requires-Dist: uvicorn>=0.30 ; extra == 'docs'
22
+ Requires-Dist: fastgrpc3-transport>=0.1,<0.2 ; extra == 'tonic'
23
+ Requires-Python: >=3.12
24
+ Provides-Extra: docs
25
+ Provides-Extra: tonic
26
+ Description-Content-Type: text/markdown
27
+
28
+ # fastgrpc3
29
+
30
+ A FastAPI-like gRPC framework for Python: async handlers, Pydantic models, dependency
31
+ injection and typed clients, on top of `grpc.aio`.
32
+
33
+ > **Status: early development.** Contracts, server, client, health checking, reflection and
34
+ > multi-process serving work; documentation is still to come. The API may change.
35
+
36
+ On a 20-field message, the codec is 4× faster than fastgrpc2's and the framework costs ~7 %
37
+ over plain `grpc.aio` with generated stubs, validation included (`benches/results/`).
38
+
39
+ - **Two ways to declare a contract.** Start from `.proto` files and generate the Pydantic
40
+ models, services and clients, or write Pydantic models and get the `.proto` generated.
41
+ - **Fast protobuf codec.** Encoding and decoding run in Rust through
42
+ [protoruf](https://github.com/EdwinAlkins/protoruf), straight between protobuf bytes and
43
+ Python objects, without any JSON step.
44
+ - **Standard gRPC.** Real protobuf on the wire, standard error details, health checking and
45
+ server reflection, so clients in any language can talk to it.
46
+ - **Two transports.** `grpc.aio` by default, or tonic, a Rust server 6 to 10× faster per
47
+ core under concurrency (`--transport tonic`, extra `fastgrpc3[tonic]`, experimental).
48
+ - **Interactive docs.** Swagger UI generated from the models, with a JSON gateway to try the
49
+ RPCs from the browser (`app.enable_docs()`, extra `fastgrpc3[docs]`).
50
+
51
+ Requires Python 3.12 or later (tested on 3.12, 3.13 and 3.14).
52
+
53
+ A complete example, a bookstore service with its client and tests, is in
54
+ [`examples/`](examples/README.md).
55
+
56
+ ## Server and client
57
+
58
+ ```python
59
+ import asyncio
60
+ from collections.abc import AsyncIterator
61
+
62
+ from pydantic import BaseModel
63
+ from fastgrpc3 import Context, Depends, GrpcApp, GrpcError, GrpcService, Insecure, StatusCode
64
+
65
+
66
+ class GetOrder(BaseModel):
67
+ order_number: str
68
+
69
+
70
+ class Order(BaseModel):
71
+ order_number: str
72
+ paid: bool = False
73
+
74
+
75
+ service = GrpcService("Orders", package="orders.v1")
76
+
77
+
78
+ def get_repo() -> dict[str, Order]:
79
+ return {"A": Order(order_number="A", paid=True)}
80
+
81
+
82
+ @service.unary()
83
+ async def Get(req: GetOrder, ctx: Context, repo: dict[str, Order] = Depends(get_repo)) -> Order:
84
+ if req.order_number not in repo:
85
+ raise GrpcError(StatusCode.NOT_FOUND, "no such order")
86
+ return repo[req.order_number]
87
+
88
+
89
+ @service.unary_stream()
90
+ async def Watch(req: GetOrder) -> AsyncIterator[Order]:
91
+ yield Order(order_number=req.order_number)
92
+
93
+
94
+ app = GrpcApp(address="[::]:50051", tls=Insecure()) # or TLS(cert=..., key=...)
95
+ app.add_service(service)
96
+ app.enable_health() # grpc.health.v1: Check, Watch, List (grpc-health-probe, Kubernetes)
97
+ app.enable_reflection() # grpc.reflection v1 and v1alpha (grpcurl list / describe / call)
98
+ app.enable_docs(gateway=True) # Swagger UI on 127.0.0.1:8080/docs (fastgrpc3[docs])
99
+
100
+ if __name__ == "__main__":
101
+ asyncio.run(app.run()) # stops cleanly on SIGINT / SIGTERM
102
+ ```
103
+
104
+ ```python
105
+ from fastgrpc3 import Channel, GrpcClient, Insecure
106
+
107
+
108
+ class OrdersClient(GrpcClient):
109
+ package = "orders.v1"
110
+ service = "Orders"
111
+ get = GrpcClient.unary("Get", GetOrder, Order)
112
+ watch = GrpcClient.unary_stream("Watch", GetOrder, Order)
113
+
114
+
115
+ async def main() -> None:
116
+ async with Channel("localhost:50051", tls=Insecure()) as channel:
117
+ orders = OrdersClient(channel)
118
+ order = await orders.get(GetOrder(order_number="A"))
119
+ async for update in orders.watch(GetOrder(order_number="A")):
120
+ print(update)
121
+ ```
122
+
123
+ Also: dependencies with sub-dependencies, per-call cache and `yield` cleanup;
124
+ `app.add_interceptor` (pre-call hooks), `@app.middleware` (around the handler) and
125
+ `@app.exception_handler`; rich errors with the standard `google.rpc` details (`ErrorInfo`,
126
+ `BadRequest`…); deadline propagation to calls made from a handler; `TLS` / `MTLS` /
127
+ `Insecure` always explicit; `fastgrpc3.testing.TestClient` for tests.
128
+
129
+ ```bash
130
+ fastgrpc3 run myapp.main:app --address [::]:50051 --workers 4 # one process per core
131
+ ```
132
+
133
+ Workers share the port with `SO_REUSEPORT` (Linux); otherwise a busy port makes the server
134
+ fail to start instead of silently sharing it.
135
+
136
+ ## Code first: Pydantic models -> `.proto`
137
+
138
+ `fastgrpc3 lock`, `check` and `export` take an app (`myapp.main:app`) or a `SchemaRegistry`.
139
+
140
+ ```python
141
+ from pydantic import BaseModel
142
+ from fastgrpc3 import MethodDef, SchemaRegistry, ServiceDef, field
143
+ from fastgrpc3.types import UInt32
144
+
145
+
146
+ class GetOrder(BaseModel):
147
+ order_number: str
148
+
149
+
150
+ class Order(BaseModel):
151
+ order_number: str
152
+ qty: UInt32 = 0 # proto uint32, bounds checked by Pydantic
153
+ note: str | None = field(5, default=None) # explicit number
154
+
155
+
156
+ registry = SchemaRegistry()
157
+ registry.add_service(ServiceDef("Orders", "orders.v1", (MethodDef("Get", GetOrder, Order),)))
158
+ ```
159
+
160
+ ```bash
161
+ fastgrpc3 lock myapp.contract:registry # number new fields, write proto.lock (commit it)
162
+ fastgrpc3 check myapp.contract:registry # CI: fail if proto.lock is out of date
163
+ fastgrpc3 export myapp.contract:registry -o proto/ # one .proto file per package
164
+ ```
165
+
166
+ Fields without an explicit number get one the first time they are locked, wherever they are
167
+ declared; a removed field's number is reserved for good. Types without a proto equivalent
168
+ (`Decimal`, `UUID`, unions…) travel as a JSON string, marked `// json` in the `.proto`.
169
+
170
+ ## Proto first: `.proto` -> Pydantic models
171
+
172
+ ```bash
173
+ fastgrpc3 generate proto/ -o src/myapp/gen # -I third_party/ for extra imports
174
+ fastgrpc3 generate proto/ -o src/myapp/gen --check # CI: fail if not up to date
175
+ ```
176
+
177
+ `orders/v1/orders.proto` gives `orders/v1/orders_models.py` (one `ProtoModel` per message,
178
+ one open `ProtoEnum` per enum) and `orders/v1/orders_service.py`; `_descriptor.py` embeds the
179
+ compiled descriptor, so nothing is compiled at startup. Defaults can live in `pyproject.toml`:
180
+
181
+ ```toml
182
+ [tool.fastgrpc3.generate]
183
+ proto_paths = ["proto"]
184
+ output = "src/myapp/gen"
185
+ ```
186
+
187
+ Each service module also holds the contract (`Orders`, to serve with
188
+ `GrpcService.implement(Orders)`) and a typed client (`OrdersClient`). Generated code passes
189
+ `mypy --strict` and `ruff`. Well-known types become Python types
190
+ (`Timestamp` -> `datetime`, `Struct` -> `dict[str, Any]`, wrappers -> `X | None`…).
191
+
192
+ ## Documentation
193
+
194
+ The guide and the reference live in [`docs/`](docs/index.md) (MkDocs). To build the site
195
+ into `site/`, or to serve it locally with live reload:
196
+
197
+ ```bash
198
+ uv run --project docs mkdocs build --strict
199
+ uv run --project docs mkdocs serve # http://127.0.0.1:8000
200
+ ```
201
+
202
+ ## Development
203
+
204
+ ```bash
205
+ uv sync # install the package and the dev tools
206
+ uv run pytest # tests
207
+ uv run ruff check . # lint
208
+ uv run ruff format --check # formatting
209
+ uv run mypy src tests # type checking
210
+ ```
211
+
212
+ ## License
213
+
214
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,187 @@
1
+ # fastgrpc3
2
+
3
+ A FastAPI-like gRPC framework for Python: async handlers, Pydantic models, dependency
4
+ injection and typed clients, on top of `grpc.aio`.
5
+
6
+ > **Status: early development.** Contracts, server, client, health checking, reflection and
7
+ > multi-process serving work; documentation is still to come. The API may change.
8
+
9
+ On a 20-field message, the codec is 4× faster than fastgrpc2's and the framework costs ~7 %
10
+ over plain `grpc.aio` with generated stubs, validation included (`benches/results/`).
11
+
12
+ - **Two ways to declare a contract.** Start from `.proto` files and generate the Pydantic
13
+ models, services and clients, or write Pydantic models and get the `.proto` generated.
14
+ - **Fast protobuf codec.** Encoding and decoding run in Rust through
15
+ [protoruf](https://github.com/EdwinAlkins/protoruf), straight between protobuf bytes and
16
+ Python objects, without any JSON step.
17
+ - **Standard gRPC.** Real protobuf on the wire, standard error details, health checking and
18
+ server reflection, so clients in any language can talk to it.
19
+ - **Two transports.** `grpc.aio` by default, or tonic, a Rust server 6 to 10× faster per
20
+ core under concurrency (`--transport tonic`, extra `fastgrpc3[tonic]`, experimental).
21
+ - **Interactive docs.** Swagger UI generated from the models, with a JSON gateway to try the
22
+ RPCs from the browser (`app.enable_docs()`, extra `fastgrpc3[docs]`).
23
+
24
+ Requires Python 3.12 or later (tested on 3.12, 3.13 and 3.14).
25
+
26
+ A complete example, a bookstore service with its client and tests, is in
27
+ [`examples/`](examples/README.md).
28
+
29
+ ## Server and client
30
+
31
+ ```python
32
+ import asyncio
33
+ from collections.abc import AsyncIterator
34
+
35
+ from pydantic import BaseModel
36
+ from fastgrpc3 import Context, Depends, GrpcApp, GrpcError, GrpcService, Insecure, StatusCode
37
+
38
+
39
+ class GetOrder(BaseModel):
40
+ order_number: str
41
+
42
+
43
+ class Order(BaseModel):
44
+ order_number: str
45
+ paid: bool = False
46
+
47
+
48
+ service = GrpcService("Orders", package="orders.v1")
49
+
50
+
51
+ def get_repo() -> dict[str, Order]:
52
+ return {"A": Order(order_number="A", paid=True)}
53
+
54
+
55
+ @service.unary()
56
+ async def Get(req: GetOrder, ctx: Context, repo: dict[str, Order] = Depends(get_repo)) -> Order:
57
+ if req.order_number not in repo:
58
+ raise GrpcError(StatusCode.NOT_FOUND, "no such order")
59
+ return repo[req.order_number]
60
+
61
+
62
+ @service.unary_stream()
63
+ async def Watch(req: GetOrder) -> AsyncIterator[Order]:
64
+ yield Order(order_number=req.order_number)
65
+
66
+
67
+ app = GrpcApp(address="[::]:50051", tls=Insecure()) # or TLS(cert=..., key=...)
68
+ app.add_service(service)
69
+ app.enable_health() # grpc.health.v1: Check, Watch, List (grpc-health-probe, Kubernetes)
70
+ app.enable_reflection() # grpc.reflection v1 and v1alpha (grpcurl list / describe / call)
71
+ app.enable_docs(gateway=True) # Swagger UI on 127.0.0.1:8080/docs (fastgrpc3[docs])
72
+
73
+ if __name__ == "__main__":
74
+ asyncio.run(app.run()) # stops cleanly on SIGINT / SIGTERM
75
+ ```
76
+
77
+ ```python
78
+ from fastgrpc3 import Channel, GrpcClient, Insecure
79
+
80
+
81
+ class OrdersClient(GrpcClient):
82
+ package = "orders.v1"
83
+ service = "Orders"
84
+ get = GrpcClient.unary("Get", GetOrder, Order)
85
+ watch = GrpcClient.unary_stream("Watch", GetOrder, Order)
86
+
87
+
88
+ async def main() -> None:
89
+ async with Channel("localhost:50051", tls=Insecure()) as channel:
90
+ orders = OrdersClient(channel)
91
+ order = await orders.get(GetOrder(order_number="A"))
92
+ async for update in orders.watch(GetOrder(order_number="A")):
93
+ print(update)
94
+ ```
95
+
96
+ Also: dependencies with sub-dependencies, per-call cache and `yield` cleanup;
97
+ `app.add_interceptor` (pre-call hooks), `@app.middleware` (around the handler) and
98
+ `@app.exception_handler`; rich errors with the standard `google.rpc` details (`ErrorInfo`,
99
+ `BadRequest`…); deadline propagation to calls made from a handler; `TLS` / `MTLS` /
100
+ `Insecure` always explicit; `fastgrpc3.testing.TestClient` for tests.
101
+
102
+ ```bash
103
+ fastgrpc3 run myapp.main:app --address [::]:50051 --workers 4 # one process per core
104
+ ```
105
+
106
+ Workers share the port with `SO_REUSEPORT` (Linux); otherwise a busy port makes the server
107
+ fail to start instead of silently sharing it.
108
+
109
+ ## Code first: Pydantic models -> `.proto`
110
+
111
+ `fastgrpc3 lock`, `check` and `export` take an app (`myapp.main:app`) or a `SchemaRegistry`.
112
+
113
+ ```python
114
+ from pydantic import BaseModel
115
+ from fastgrpc3 import MethodDef, SchemaRegistry, ServiceDef, field
116
+ from fastgrpc3.types import UInt32
117
+
118
+
119
+ class GetOrder(BaseModel):
120
+ order_number: str
121
+
122
+
123
+ class Order(BaseModel):
124
+ order_number: str
125
+ qty: UInt32 = 0 # proto uint32, bounds checked by Pydantic
126
+ note: str | None = field(5, default=None) # explicit number
127
+
128
+
129
+ registry = SchemaRegistry()
130
+ registry.add_service(ServiceDef("Orders", "orders.v1", (MethodDef("Get", GetOrder, Order),)))
131
+ ```
132
+
133
+ ```bash
134
+ fastgrpc3 lock myapp.contract:registry # number new fields, write proto.lock (commit it)
135
+ fastgrpc3 check myapp.contract:registry # CI: fail if proto.lock is out of date
136
+ fastgrpc3 export myapp.contract:registry -o proto/ # one .proto file per package
137
+ ```
138
+
139
+ Fields without an explicit number get one the first time they are locked, wherever they are
140
+ declared; a removed field's number is reserved for good. Types without a proto equivalent
141
+ (`Decimal`, `UUID`, unions…) travel as a JSON string, marked `// json` in the `.proto`.
142
+
143
+ ## Proto first: `.proto` -> Pydantic models
144
+
145
+ ```bash
146
+ fastgrpc3 generate proto/ -o src/myapp/gen # -I third_party/ for extra imports
147
+ fastgrpc3 generate proto/ -o src/myapp/gen --check # CI: fail if not up to date
148
+ ```
149
+
150
+ `orders/v1/orders.proto` gives `orders/v1/orders_models.py` (one `ProtoModel` per message,
151
+ one open `ProtoEnum` per enum) and `orders/v1/orders_service.py`; `_descriptor.py` embeds the
152
+ compiled descriptor, so nothing is compiled at startup. Defaults can live in `pyproject.toml`:
153
+
154
+ ```toml
155
+ [tool.fastgrpc3.generate]
156
+ proto_paths = ["proto"]
157
+ output = "src/myapp/gen"
158
+ ```
159
+
160
+ Each service module also holds the contract (`Orders`, to serve with
161
+ `GrpcService.implement(Orders)`) and a typed client (`OrdersClient`). Generated code passes
162
+ `mypy --strict` and `ruff`. Well-known types become Python types
163
+ (`Timestamp` -> `datetime`, `Struct` -> `dict[str, Any]`, wrappers -> `X | None`…).
164
+
165
+ ## Documentation
166
+
167
+ The guide and the reference live in [`docs/`](docs/index.md) (MkDocs). To build the site
168
+ into `site/`, or to serve it locally with live reload:
169
+
170
+ ```bash
171
+ uv run --project docs mkdocs build --strict
172
+ uv run --project docs mkdocs serve # http://127.0.0.1:8000
173
+ ```
174
+
175
+ ## Development
176
+
177
+ ```bash
178
+ uv sync # install the package and the dev tools
179
+ uv run pytest # tests
180
+ uv run ruff check . # lint
181
+ uv run ruff format --check # formatting
182
+ uv run mypy src tests # type checking
183
+ ```
184
+
185
+ ## License
186
+
187
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,93 @@
1
+ [project]
2
+ name = "fastgrpc3"
3
+ version = "0.1.0"
4
+ description = "FastAPI-like gRPC framework for Python: contracts from .proto files or Pydantic models, powered by protoruf (Rust)"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.12"
9
+ classifiers = [
10
+ "Development Status :: 1 - Planning",
11
+ "Framework :: AsyncIO",
12
+ "Intended Audience :: Developers",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.12",
15
+ "Programming Language :: Python :: 3.13",
16
+ "Programming Language :: Python :: 3.14",
17
+ "Topic :: System :: Networking",
18
+ "Typing :: Typed",
19
+ ]
20
+ dependencies = [
21
+ "grpcio>=1.75.1",
22
+ "protoruf>=0.5.3",
23
+ "pydantic>=2.11",
24
+ ]
25
+
26
+ [[project.authors]]
27
+ name = "William Nauroy"
28
+ email = "william.nauroy@gmail.com"
29
+
30
+ [project.scripts]
31
+ fastgrpc3 = "fastgrpc3.cli:main"
32
+
33
+ [project.optional-dependencies]
34
+ docs = ["uvicorn>=0.30"]
35
+ tonic = ["fastgrpc3-transport>=0.1,<0.2"]
36
+
37
+ [dependency-groups]
38
+ bench = [
39
+ "fastgrpc2==0.5.0",
40
+ "protobuf>=7.36.2",
41
+ ]
42
+ dev = [
43
+ "mypy>=2.4",
44
+ "pytest>=9.2",
45
+ "pytest-asyncio>=1.4",
46
+ "ruff>=0.17",
47
+ "types-grpcio>=1.85.0.20261028",
48
+ "uvicorn>=0.30",
49
+ ]
50
+
51
+ [build-system]
52
+ requires = ["uv_build>=0.13.4,<0.14.0"]
53
+ build-backend = "uv_build"
54
+
55
+ [tool.ruff]
56
+ line-length = 100
57
+ target-version = "py312"
58
+ extend-exclude = ["src/fastgrpc3/_proto"]
59
+
60
+ [tool.ruff.lint]
61
+ select = [
62
+ "E",
63
+ "F",
64
+ "W",
65
+ "I",
66
+ "UP",
67
+ "B",
68
+ "SIM",
69
+ "RUF",
70
+ ]
71
+
72
+ [tool.ruff.lint.flake8-bugbear]
73
+ extend-immutable-calls = [
74
+ "fastgrpc3.Depends",
75
+ "fastgrpc3.api.deps.Depends",
76
+ ]
77
+
78
+ [tool.mypy]
79
+ python_version = "3.12"
80
+ strict = true
81
+
82
+ [[tool.mypy.overrides]]
83
+ module = ["fastgrpc3_transport"]
84
+ ignore_missing_imports = true
85
+
86
+ [[tool.mypy.overrides]]
87
+ module = ["fastgrpc3.transport.tonic"]
88
+ warn_return_any = false
89
+
90
+ [tool.pytest.ini_options]
91
+ testpaths = ["tests"]
92
+ asyncio_mode = "auto"
93
+ asyncio_default_fixture_loop_scope = "function"
@@ -0,0 +1,91 @@
1
+ [project]
2
+ name = "fastgrpc3"
3
+ version = "0.1.0"
4
+ description = "FastAPI-like gRPC framework for Python: contracts from .proto files or Pydantic models, powered by protoruf (Rust)"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "William Nauroy", email = "william.nauroy@gmail.com" }
10
+ ]
11
+ requires-python = ">=3.12"
12
+ classifiers = [
13
+ "Development Status :: 1 - Planning",
14
+ "Framework :: AsyncIO",
15
+ "Intended Audience :: Developers",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Programming Language :: Python :: 3.14",
20
+ "Topic :: System :: Networking",
21
+ "Typing :: Typed",
22
+ ]
23
+ dependencies = [
24
+ # First release shipping CPython 3.14 wheels.
25
+ "grpcio>=1.75.1",
26
+ "protoruf>=0.5.3",
27
+ # `model_validate(..., by_name=True)` appeared in 2.11.
28
+ "pydantic>=2.11",
29
+ ]
30
+
31
+ [project.scripts]
32
+ fastgrpc3 = "fastgrpc3.cli:main"
33
+
34
+ [project.optional-dependencies]
35
+ docs = [
36
+ "uvicorn>=0.30",
37
+ ]
38
+ # The tonic transport (`GrpcApp(transport="tonic")`), a Rust server.
39
+ tonic = [
40
+ "fastgrpc3-transport>=0.1,<0.2",
41
+ ]
42
+
43
+ [dependency-groups]
44
+ bench = [
45
+ "fastgrpc2==0.5.0",
46
+ "protobuf>=7.36.2",
47
+ ]
48
+ dev = [
49
+ "mypy>=2.4",
50
+ "pytest>=9.2",
51
+ "pytest-asyncio>=1.4",
52
+ "ruff>=0.17",
53
+ "types-grpcio>=1.85.0.20261028",
54
+ "uvicorn>=0.30",
55
+ ]
56
+
57
+ [build-system]
58
+ requires = ["uv_build>=0.13.4,<0.14.0"]
59
+ build-backend = "uv_build"
60
+
61
+ [tool.ruff]
62
+ line-length = 100
63
+ target-version = "py312"
64
+ # Generated by scripts/generate_standard_protos.py (proto comments make long lines).
65
+ extend-exclude = ["src/fastgrpc3/_proto"]
66
+
67
+ [tool.ruff.lint]
68
+ select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF"]
69
+
70
+ [tool.ruff.lint.flake8-bugbear]
71
+ # `x: Repo = Depends(get_repo)` is the documented way to declare a dependency.
72
+ extend-immutable-calls = ["fastgrpc3.Depends", "fastgrpc3.api.deps.Depends"]
73
+
74
+ [tool.mypy]
75
+ python_version = "3.12"
76
+ strict = true
77
+
78
+ [[tool.mypy.overrides]]
79
+ # The tonic transport, optional (`fastgrpc3-transport`, typed when installed).
80
+ module = ["fastgrpc3_transport"]
81
+ ignore_missing_imports = true
82
+
83
+ [[tool.mypy.overrides]]
84
+ # Returns what the extension gives, typed only when fastgrpc3-transport is installed.
85
+ module = ["fastgrpc3.transport.tonic"]
86
+ warn_return_any = false
87
+
88
+ [tool.pytest.ini_options]
89
+ testpaths = ["tests"]
90
+ asyncio_mode = "auto"
91
+ asyncio_default_fixture_loop_scope = "function"
@@ -0,0 +1,75 @@
1
+ """fastgrpc3: a FastAPI-like gRPC framework for Python, powered by protoruf."""
2
+
3
+ from fastgrpc3.api.app import GrpcApp
4
+ from fastgrpc3.api.client import Channel, GrpcClient
5
+ from fastgrpc3.api.config import Compression, Keepalive, RetryPolicy
6
+ from fastgrpc3.api.context import Context
7
+ from fastgrpc3.api.deps import Depends, blocking, non_blocking
8
+ from fastgrpc3.api.errors import (
9
+ BadRequest,
10
+ ErrorInfo,
11
+ FieldViolation,
12
+ GrpcError,
13
+ StatusCode,
14
+ register_detail,
15
+ )
16
+ from fastgrpc3.api.metadata import Metadata
17
+ from fastgrpc3.api.security import MTLS, TLS, BearerToken, Insecure
18
+ from fastgrpc3.api.service import GrpcService
19
+ from fastgrpc3.runtime.dispatch import CallNext
20
+ from fastgrpc3.schema import (
21
+ Lock,
22
+ LockError,
23
+ MethodDef,
24
+ Schema,
25
+ SchemaError,
26
+ SchemaRegistry,
27
+ ServiceDef,
28
+ )
29
+ from fastgrpc3.transport import Transport, TransportCapabilities, available_transports
30
+ from fastgrpc3.types import ProtoEnum, ProtoField, ProtoModel, field, key, proto_enum
31
+
32
+ __version__ = "0.1.0"
33
+
34
+ __all__ = [
35
+ "MTLS",
36
+ "TLS",
37
+ "BadRequest",
38
+ "BearerToken",
39
+ "CallNext",
40
+ "Channel",
41
+ "Compression",
42
+ "Context",
43
+ "Depends",
44
+ "ErrorInfo",
45
+ "FieldViolation",
46
+ "GrpcApp",
47
+ "GrpcClient",
48
+ "GrpcError",
49
+ "GrpcService",
50
+ "Insecure",
51
+ "Keepalive",
52
+ "Lock",
53
+ "LockError",
54
+ "Metadata",
55
+ "MethodDef",
56
+ "ProtoEnum",
57
+ "ProtoField",
58
+ "ProtoModel",
59
+ "RetryPolicy",
60
+ "Schema",
61
+ "SchemaError",
62
+ "SchemaRegistry",
63
+ "ServiceDef",
64
+ "StatusCode",
65
+ "Transport",
66
+ "TransportCapabilities",
67
+ "__version__",
68
+ "available_transports",
69
+ "blocking",
70
+ "field",
71
+ "key",
72
+ "non_blocking",
73
+ "proto_enum",
74
+ "register_detail",
75
+ ]
@@ -0,0 +1,7 @@
1
+ """`python -m fastgrpc3`: same as the `fastgrpc3` command."""
2
+
3
+ import sys
4
+
5
+ from fastgrpc3.cli import main
6
+
7
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ # Generated by fastgrpc3: do not edit.