simple-grpcio 0.1.5__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.
- simple_grpcio-0.1.5/PKG-INFO +172 -0
- simple_grpcio-0.1.5/README.md +149 -0
- simple_grpcio-0.1.5/pyproject.toml +82 -0
- simple_grpcio-0.1.5/simple_grpc/__init__.py +13 -0
- simple_grpcio-0.1.5/simple_grpc/applications.py +436 -0
- simple_grpcio-0.1.5/simple_grpc/dispatch.py +380 -0
- simple_grpcio-0.1.5/simple_grpc/exceptions.py +130 -0
- simple_grpcio-0.1.5/simple_grpc/logging.py +183 -0
- simple_grpcio-0.1.5/simple_grpc/models.py +243 -0
- simple_grpcio-0.1.5/simple_grpc/params.py +89 -0
- simple_grpcio-0.1.5/simple_grpc/reloading.py +176 -0
- simple_grpcio-0.1.5/simple_grpc/utils.py +72 -0
- simple_grpcio-0.1.5/tests/conftest.py +0 -0
- simple_grpcio-0.1.5/tests/test_app.py +270 -0
- simple_grpcio-0.1.5/tests/test_credentials.py +95 -0
- simple_grpcio-0.1.5/tests/test_depends.py +290 -0
- simple_grpcio-0.1.5/tests/test_exceptions.py +16 -0
- simple_grpcio-0.1.5/tests/test_exceptions_wire.py +61 -0
- simple_grpcio-0.1.5/tests/test_logging.py +338 -0
- simple_grpcio-0.1.5/tests/test_reloading.py +142 -0
- simple_grpcio-0.1.5/tests/test_schemaless.py +207 -0
- simple_grpcio-0.1.5/tests/test_stream.py +51 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simple-grpcio
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: SimpleGRPC - High-performance gRPC made simple. Write less code, ship to production faster.
|
|
5
|
+
Author: Pablo Galilea
|
|
6
|
+
Author-email: Pablo Galilea <pgalilea@me.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Dist: click>=8.5.0
|
|
19
|
+
Requires-Dist: grpcio>=1.84.0
|
|
20
|
+
Requires-Dist: grpcio-tools>=1.84.0
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
*High-performance gRPC made simple. Write less, ship to production faster.*
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Development using [gRPC](https://grpc.io/docs/what-is-grpc/introduction/) made easy. Heavily inspired by [FastAPI](https://fastapi.tiangolo.com/).
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
**Important Disclaimer**: In active development. Lock the package version and run your own tests.
|
|
33
|
+
|
|
34
|
+
## 🔥 Key Features
|
|
35
|
+
|
|
36
|
+
- **Familiar, minimal API**: an app object with a single decorator - @app.rpc("Service", "Method") - maps plain Python functions onto gRPC handlers, FastAPI-style.
|
|
37
|
+
- **Zero protobuf boilerplate**: .proto files are auto-discovered, compiled with protoc, and wired to your handlers at startup - no build step, no generated code to check in.
|
|
38
|
+
- **FastAPI-style dependency injection**: Depends() with nested dependency trees, caching-free depth-first resolution, and generator dependencies with pre/post teardown (yield), plus app-wide dependencies.
|
|
39
|
+
- **Built-in injectables**: handlers receive request, request_iterator, and context by parameter name - no protocol noise.
|
|
40
|
+
- **Schemaless RPCs**: @app.schemaless_rpc("Service", "Method") - build protobuf-free services over google.protobuf.Struct, sending and receiving plain dicts.
|
|
41
|
+
- **Full streaming support**: all four RPC flavors out of the box - unary, request-streaming, response-streaming, and bidirectional - response streams can yield messages or dicts.
|
|
42
|
+
- **Typed exceptions for every gRPC status code**: 16 concrete exceptions (NotFoundError, InvalidArgumentError, UnauthenticatedError, …) that auto-map to abort() on unary and streaming calls alike.
|
|
43
|
+
- **First-class TLS/mTLS**: ServerCredentials accepts paths or raw PEM, validates certificates fail-fast at startup, and supports client-auth.
|
|
44
|
+
- **Hot-reload dev mode**: app.dev() watches your .py and .proto files, recompiles and restarts on change - the gRPC developer loop FastAPI users expect.
|
|
45
|
+
- **Production-grade logging out of the box**: colorized access logs per RPC plus lifecycle/error loggers, configurable via LoggingConfig and honoring NO_COLOR.
|
|
46
|
+
- **Full access to gRPC internals when you need them**: pass interceptors, compression, channel options, xds, and concurrency limits straight through ServerOptions to grpc.server().
|
|
47
|
+
- **Small and inspectable**: Minimal abstraction, minimal dependencies, no magic you can't read.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## ⚙️ Installation
|
|
52
|
+
|
|
53
|
+
From pypi:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install simple-grpc
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## 🚀 Quickstart
|
|
62
|
+
|
|
63
|
+
Server-side snippets: each one is a complete, runnable file (`python server.py`).
|
|
64
|
+
|
|
65
|
+
### Schemaless endpoints
|
|
66
|
+
|
|
67
|
+
No proto files at all - plain dicts in, dicts out.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from simple_grpc import SimpleGRPC
|
|
71
|
+
|
|
72
|
+
app = SimpleGRPC(port=50051)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@app.schemaless_rpc("Greeter", "SayHello")
|
|
76
|
+
def say_hello(request):
|
|
77
|
+
return {"message": f"Hello, {request['name']}!"}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
app.run()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
### Proto-backed endpoints
|
|
87
|
+
|
|
88
|
+
Pass proto content inline; it is compiled at startup into a generated-code directory under the system temp dir - no `.proto` file, no build step.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from simple_grpc import SimpleGRPC
|
|
92
|
+
|
|
93
|
+
CALCULATOR_PROTO = """
|
|
94
|
+
syntax = "proto3";
|
|
95
|
+
|
|
96
|
+
service Calculator {
|
|
97
|
+
rpc Add (AddRequest) returns (AddReply);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
message AddRequest {
|
|
101
|
+
int64 a = 1;
|
|
102
|
+
int64 b = 2;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
message AddReply {
|
|
106
|
+
int64 sum = 1;
|
|
107
|
+
}
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
app = SimpleGRPC(raw_protos={"calculator": CALCULATOR_PROTO}, port=50052)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@app.rpc("Calculator", "Add")
|
|
114
|
+
def add(request):
|
|
115
|
+
return {"sum": request.a + request.b}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
app.run()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
### Dependency injection
|
|
125
|
+
|
|
126
|
+
Declare `Depends()` in the handler signature. Plain dependencies resolve before the call; generator dependencies (`yield`) open and tear down around it.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from typing import Annotated
|
|
130
|
+
|
|
131
|
+
from simple_grpc import Depends, SimpleGRPC
|
|
132
|
+
|
|
133
|
+
app = SimpleGRPC(port=50053)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def auth_dep(context):
|
|
137
|
+
"""Read the bearer token from the metadata headers."""
|
|
138
|
+
metadata = dict(context.invocation_metadata())
|
|
139
|
+
if metadata.get("authorization") != "Bearer abc123":
|
|
140
|
+
context.abort(16, "unauthenticated")
|
|
141
|
+
return "alice"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
UserDep = Annotated[str, Depends(auth_dep)]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def db_session():
|
|
148
|
+
"""Open a fake session for the handler and close it afterwards."""
|
|
149
|
+
print("db: session opened")
|
|
150
|
+
yield {"notes": ["shopping", "taxes"]}
|
|
151
|
+
print("db: session closed")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@app.schemaless_rpc("Notes", "GetNote")
|
|
155
|
+
def get_note(request, user: UserDep, db: Annotated[dict, Depends(db_session)]):
|
|
156
|
+
return {"user": user, "title": db["notes"][int(request["id"]) - 1]}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
app.run()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
During development, swap `app.run()` for `app.dev()` to hot-reload on file changes.
|
|
164
|
+
|
|
165
|
+
[More examples...](scripts/examples/)
|
|
166
|
+
|
|
167
|
+
## 🧪 Tests
|
|
168
|
+
|
|
169
|
+
```sh
|
|
170
|
+
uv run pytest -vs
|
|
171
|
+
```
|
|
172
|
+
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
*High-performance gRPC made simple. Write less, ship to production faster.*
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Development using [gRPC](https://grpc.io/docs/what-is-grpc/introduction/) made easy. Heavily inspired by [FastAPI](https://fastapi.tiangolo.com/).
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
**Important Disclaimer**: In active development. Lock the package version and run your own tests.
|
|
10
|
+
|
|
11
|
+
## 🔥 Key Features
|
|
12
|
+
|
|
13
|
+
- **Familiar, minimal API**: an app object with a single decorator - @app.rpc("Service", "Method") - maps plain Python functions onto gRPC handlers, FastAPI-style.
|
|
14
|
+
- **Zero protobuf boilerplate**: .proto files are auto-discovered, compiled with protoc, and wired to your handlers at startup - no build step, no generated code to check in.
|
|
15
|
+
- **FastAPI-style dependency injection**: Depends() with nested dependency trees, caching-free depth-first resolution, and generator dependencies with pre/post teardown (yield), plus app-wide dependencies.
|
|
16
|
+
- **Built-in injectables**: handlers receive request, request_iterator, and context by parameter name - no protocol noise.
|
|
17
|
+
- **Schemaless RPCs**: @app.schemaless_rpc("Service", "Method") - build protobuf-free services over google.protobuf.Struct, sending and receiving plain dicts.
|
|
18
|
+
- **Full streaming support**: all four RPC flavors out of the box - unary, request-streaming, response-streaming, and bidirectional - response streams can yield messages or dicts.
|
|
19
|
+
- **Typed exceptions for every gRPC status code**: 16 concrete exceptions (NotFoundError, InvalidArgumentError, UnauthenticatedError, …) that auto-map to abort() on unary and streaming calls alike.
|
|
20
|
+
- **First-class TLS/mTLS**: ServerCredentials accepts paths or raw PEM, validates certificates fail-fast at startup, and supports client-auth.
|
|
21
|
+
- **Hot-reload dev mode**: app.dev() watches your .py and .proto files, recompiles and restarts on change - the gRPC developer loop FastAPI users expect.
|
|
22
|
+
- **Production-grade logging out of the box**: colorized access logs per RPC plus lifecycle/error loggers, configurable via LoggingConfig and honoring NO_COLOR.
|
|
23
|
+
- **Full access to gRPC internals when you need them**: pass interceptors, compression, channel options, xds, and concurrency limits straight through ServerOptions to grpc.server().
|
|
24
|
+
- **Small and inspectable**: Minimal abstraction, minimal dependencies, no magic you can't read.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## ⚙️ Installation
|
|
29
|
+
|
|
30
|
+
From pypi:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install simple-grpc
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## 🚀 Quickstart
|
|
39
|
+
|
|
40
|
+
Server-side snippets: each one is a complete, runnable file (`python server.py`).
|
|
41
|
+
|
|
42
|
+
### Schemaless endpoints
|
|
43
|
+
|
|
44
|
+
No proto files at all - plain dicts in, dicts out.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from simple_grpc import SimpleGRPC
|
|
48
|
+
|
|
49
|
+
app = SimpleGRPC(port=50051)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@app.schemaless_rpc("Greeter", "SayHello")
|
|
53
|
+
def say_hello(request):
|
|
54
|
+
return {"message": f"Hello, {request['name']}!"}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
app.run()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
### Proto-backed endpoints
|
|
64
|
+
|
|
65
|
+
Pass proto content inline; it is compiled at startup into a generated-code directory under the system temp dir - no `.proto` file, no build step.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from simple_grpc import SimpleGRPC
|
|
69
|
+
|
|
70
|
+
CALCULATOR_PROTO = """
|
|
71
|
+
syntax = "proto3";
|
|
72
|
+
|
|
73
|
+
service Calculator {
|
|
74
|
+
rpc Add (AddRequest) returns (AddReply);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
message AddRequest {
|
|
78
|
+
int64 a = 1;
|
|
79
|
+
int64 b = 2;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
message AddReply {
|
|
83
|
+
int64 sum = 1;
|
|
84
|
+
}
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
app = SimpleGRPC(raw_protos={"calculator": CALCULATOR_PROTO}, port=50052)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@app.rpc("Calculator", "Add")
|
|
91
|
+
def add(request):
|
|
92
|
+
return {"sum": request.a + request.b}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
app.run()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
### Dependency injection
|
|
102
|
+
|
|
103
|
+
Declare `Depends()` in the handler signature. Plain dependencies resolve before the call; generator dependencies (`yield`) open and tear down around it.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from typing import Annotated
|
|
107
|
+
|
|
108
|
+
from simple_grpc import Depends, SimpleGRPC
|
|
109
|
+
|
|
110
|
+
app = SimpleGRPC(port=50053)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def auth_dep(context):
|
|
114
|
+
"""Read the bearer token from the metadata headers."""
|
|
115
|
+
metadata = dict(context.invocation_metadata())
|
|
116
|
+
if metadata.get("authorization") != "Bearer abc123":
|
|
117
|
+
context.abort(16, "unauthenticated")
|
|
118
|
+
return "alice"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
UserDep = Annotated[str, Depends(auth_dep)]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def db_session():
|
|
125
|
+
"""Open a fake session for the handler and close it afterwards."""
|
|
126
|
+
print("db: session opened")
|
|
127
|
+
yield {"notes": ["shopping", "taxes"]}
|
|
128
|
+
print("db: session closed")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@app.schemaless_rpc("Notes", "GetNote")
|
|
132
|
+
def get_note(request, user: UserDep, db: Annotated[dict, Depends(db_session)]):
|
|
133
|
+
return {"user": user, "title": db["notes"][int(request["id"]) - 1]}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
app.run()
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
During development, swap `app.run()` for `app.dev()` to hot-reload on file changes.
|
|
141
|
+
|
|
142
|
+
[More examples...](scripts/examples/)
|
|
143
|
+
|
|
144
|
+
## 🧪 Tests
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
uv run pytest -vs
|
|
148
|
+
```
|
|
149
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "simple-grpcio"
|
|
3
|
+
version = "0.1.5"
|
|
4
|
+
description = "SimpleGRPC - High-performance gRPC made simple. Write less code, ship to production faster."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Pablo Galilea", email = "pgalilea@me.com" },
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"click>=8.5.0",
|
|
25
|
+
"grpcio>=1.84.0",
|
|
26
|
+
"grpcio-tools>=1.84.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[dependency-groups]
|
|
30
|
+
dev = [
|
|
31
|
+
"pytest>=8.0.0",
|
|
32
|
+
"pytest-mock>=3.14.0",
|
|
33
|
+
"pytest-cov>=7.1.0",
|
|
34
|
+
"pytest-asyncio>=1.4.0",
|
|
35
|
+
"watchfiles>=1.3.0",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["uv_build>=0.10.0,<0.11"]
|
|
40
|
+
build-backend = "uv_build"
|
|
41
|
+
|
|
42
|
+
[tool.uv]
|
|
43
|
+
exclude-newer = "7 days"
|
|
44
|
+
|
|
45
|
+
[tool.uv.build-backend]
|
|
46
|
+
module-root = ""
|
|
47
|
+
module-name = "simple_grpc"
|
|
48
|
+
source-include = ["tests/**"]
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
line-length = 100
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
select = [
|
|
55
|
+
"E", # pycodestyle errors
|
|
56
|
+
"W", # pycodestyle warnings
|
|
57
|
+
"F", # pyflakes
|
|
58
|
+
"I", # isort
|
|
59
|
+
"B", # flake8-bugbear
|
|
60
|
+
"C4", # flake8-comprehensions
|
|
61
|
+
"UP", # pyupgrade
|
|
62
|
+
]
|
|
63
|
+
ignore = [
|
|
64
|
+
"F401",
|
|
65
|
+
"F403",
|
|
66
|
+
"B008" # allow Depends(dependency)
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint.isort]
|
|
70
|
+
lines-after-imports = 2
|
|
71
|
+
force-sort-within-sections = true
|
|
72
|
+
combine-as-imports = true
|
|
73
|
+
|
|
74
|
+
[tool.pyright]
|
|
75
|
+
pythonVersion = "3.11"
|
|
76
|
+
typeCheckingMode = "basic"
|
|
77
|
+
reportInvalidTypeForm = "none"
|
|
78
|
+
reportArgumentType = false
|
|
79
|
+
|
|
80
|
+
[tool.pytest.ini_options]
|
|
81
|
+
asyncio_mode = "auto"
|
|
82
|
+
filterwarnings = []
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
|
|
3
|
+
from google.protobuf.message import Message
|
|
4
|
+
from grpc import ServicerContext as Context, StatusCode as status
|
|
5
|
+
|
|
6
|
+
from .applications import SimpleGRPC
|
|
7
|
+
from .params import Depends
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Request = Message # Requests are application-defined proto message classes, handled opaquely,
|
|
11
|
+
RequestIterator = Iterator[Message] # so Any is the honest annotation.
|
|
12
|
+
|
|
13
|
+
# __all__ = ["Context", "Depends", "Request", "RequestIterator", "SimpleGRPC", "status"]
|