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.
@@ -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
+ ![SimpleGRPC](https://i.ibb.co/Qv3LjyDz/simplegrpc-logo.png)
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
+ ![SimpleGRPC](https://i.ibb.co/Qv3LjyDz/simplegrpc-logo.png)
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"]