pygrpc-native 0.2.0__tar.gz → 0.4.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 (80) hide show
  1. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/ISSUE_TEMPLATE/config.yml +1 -1
  2. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/workflows/ci.yml +2 -0
  3. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.pre-commit-config.yaml +1 -0
  4. pygrpc_native-0.4.0/CHANGELOG.md +110 -0
  5. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/CONTRIBUTING.md +7 -5
  6. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/Cargo.lock +29 -32
  7. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/Cargo.toml +9 -3
  8. pygrpc_native-0.4.0/PKG-INFO +196 -0
  9. pygrpc_native-0.4.0/README.md +181 -0
  10. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/SECURITY.md +1 -1
  11. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/benchmarks/README.md +2 -1
  12. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/benchmarks/echo.py +4 -2
  13. pygrpc_native-0.2.0/README.md → pygrpc_native-0.4.0/docs/low-level-api.md +66 -120
  14. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/pyproject.toml +4 -2
  15. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/python/pygrpc_native/_native.pyi +8 -0
  16. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/python/pygrpc_native/_server.py +161 -41
  17. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/python/pygrpc_native/aio.py +190 -16
  18. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/src/lib.rs +104 -5
  19. pygrpc_native-0.4.0/src/metrics.rs +340 -0
  20. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/src/server.rs +367 -47
  21. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_aio.py +103 -3
  22. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_api.py +36 -2
  23. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_calls.py +4 -5
  24. pygrpc_native-0.4.0/tests/test_connection.py +381 -0
  25. pygrpc_native-0.4.0/tests/test_downstream.py +386 -0
  26. pygrpc_native-0.4.0/tests/test_interceptors.py +421 -0
  27. pygrpc_native-0.4.0/tests/test_metrics.py +152 -0
  28. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_server.py +1 -3
  29. pygrpc_native-0.4.0/vendor/http/CHANGELOG.md +269 -0
  30. pygrpc_native-0.4.0/vendor/http/Cargo.toml +77 -0
  31. pygrpc_native-0.4.0/vendor/http/LICENSE-APACHE +201 -0
  32. pygrpc_native-0.4.0/vendor/http/LICENSE-MIT +25 -0
  33. pygrpc_native-0.4.0/vendor/http/README.md +80 -0
  34. pygrpc_native-0.4.0/vendor/http/src/byte_str.rs +91 -0
  35. pygrpc_native-0.4.0/vendor/http/src/convert.rs +17 -0
  36. pygrpc_native-0.4.0/vendor/http/src/error.rs +167 -0
  37. pygrpc_native-0.4.0/vendor/http/src/extensions.rs +358 -0
  38. pygrpc_native-0.4.0/vendor/http/src/header/map.rs +4044 -0
  39. pygrpc_native-0.4.0/vendor/http/src/header/mod.rs +142 -0
  40. pygrpc_native-0.4.0/vendor/http/src/header/name.rs +1895 -0
  41. pygrpc_native-0.4.0/vendor/http/src/header/value.rs +776 -0
  42. pygrpc_native-0.4.0/vendor/http/src/lib.rs +210 -0
  43. pygrpc_native-0.4.0/vendor/http/src/method.rs +521 -0
  44. pygrpc_native-0.4.0/vendor/http/src/request.rs +1072 -0
  45. pygrpc_native-0.4.0/vendor/http/src/response.rs +781 -0
  46. pygrpc_native-0.4.0/vendor/http/src/status.rs +596 -0
  47. pygrpc_native-0.4.0/vendor/http/src/uri/authority.rs +749 -0
  48. pygrpc_native-0.4.0/vendor/http/src/uri/builder.rs +261 -0
  49. pygrpc_native-0.4.0/vendor/http/src/uri/mod.rs +1132 -0
  50. pygrpc_native-0.4.0/vendor/http/src/uri/path.rs +709 -0
  51. pygrpc_native-0.4.0/vendor/http/src/uri/port.rs +151 -0
  52. pygrpc_native-0.4.0/vendor/http/src/uri/scheme.rs +361 -0
  53. pygrpc_native-0.4.0/vendor/http/src/uri/tests.rs +519 -0
  54. pygrpc_native-0.4.0/vendor/http/src/version.rs +75 -0
  55. pygrpc_native-0.4.0/vendor/http/tests/header_map.rs +774 -0
  56. pygrpc_native-0.4.0/vendor/http/tests/header_map_fuzz.rs +365 -0
  57. pygrpc_native-0.4.0/vendor/http/tests/status_code.rs +82 -0
  58. pygrpc_native-0.2.0/PKG-INFO +0 -241
  59. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/CODEOWNERS +0 -0
  60. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  61. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  62. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  63. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/dependabot.yml +0 -0
  64. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/workflows/dco.yml +0 -0
  65. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/workflows/release.yml +0 -0
  66. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/workflows/stale.yml +0 -0
  67. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.gitignore +0 -0
  68. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  69. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/DCO +0 -0
  70. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/LICENSE +0 -0
  71. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/python/pygrpc_native/__init__.py +0 -0
  72. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/python/pygrpc_native/py.typed +0 -0
  73. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_add_methods.py +0 -0
  74. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_bearer_auth.py +0 -0
  75. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_call_details.py +0 -0
  76. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_delay_stream_headers.py +0 -0
  77. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_fallback.py +0 -0
  78. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_front.py +0 -0
  79. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_response_metadata.py +0 -0
  80. {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_stream_try_send.py +0 -0
@@ -1,5 +1,5 @@
1
1
  blank_issues_enabled: true
2
2
  contact_links:
3
3
  - name: Report a security vulnerability
4
- url: https://github.com/theminjunchoi/pygrpc-native/security/advisories/new
4
+ url: https://github.com/pygrpc/pygrpc-native/security/advisories/new
5
5
  about: Please report security issues privately, not as a public issue.
@@ -48,6 +48,8 @@ jobs:
48
48
  - name: Build and install
49
49
  run: |
50
50
  python -m pip install maturin pytest grpcio grpcio-tools grpcio-health-checking grpcio-reflection uvloop
51
+ # Interceptor libraries that downstream servers use, for tests/test_downstream.py.
52
+ python -m pip install grpc-interceptor opentelemetry-instrumentation-grpc
51
53
  maturin build --release --locked --out dist
52
54
  python -m pip install dist/*.whl
53
55
  - run: pytest -q tests
@@ -1,3 +1,4 @@
1
+ exclude: ^vendor/
1
2
  repos:
2
3
  - repo: https://github.com/astral-sh/ruff-pre-commit
3
4
  rev: v0.11.7
@@ -0,0 +1,110 @@
1
+ # Changelog
2
+
3
+ All notable changes are listed here. The project follows
4
+ [semantic versioning](https://semver.org); see
5
+ [Compatibility](README.md#compatibility) for what that covers.
6
+
7
+ ## 0.4.0 (2026-10-27)
8
+
9
+ `server()` and `aio.server()` now take the interceptors and options grpcio
10
+ servers commonly pass. Code written for 0.3 keeps working; the changes in
11
+ behavior below bring the servers closer to grpcio.
12
+
13
+ ### Added
14
+
15
+ - `server()` and `aio.server()` take grpcio server interceptors. (#39)
16
+ - `server()` and `aio.server()` take the `grpc.keepalive_time_ms`,
17
+ `grpc.keepalive_timeout_ms` and `grpc.keepalive_permit_without_calls`
18
+ options, and `NativeGrpcFront` the matching `keepalive_*` arguments. (#47)
19
+ - `server()` and `aio.server()` take `grpc.max_concurrent_streams`, and
20
+ `NativeGrpcFront` a matching `max_concurrent_streams` argument. (#51)
21
+ - `server()` and `aio.server()` take `grpc.max_metadata_size` and
22
+ `grpc.absolute_max_metadata_size`, and `NativeGrpcFront` a matching
23
+ `max_metadata_size` argument. (#51)
24
+ - `server()` and `aio.server()` ignore options they don't take, as grpcio
25
+ does, such as options only clients read, `grpc.max_message_length` and
26
+ `grpc.http2.max_frame_size`, and values that aren't integers. Only options
27
+ asking for something the server can't do, such as closing connections by
28
+ age or compressing responses, still raise an error. (#47, #51)
29
+ - `metrics()` also reports each call's sent and received message bytes,
30
+ like gRFC A66's `grpc.server.call.sent_total_compressed_message_size` and
31
+ `rcvd_total_compressed_message_size`, under the new keys `size_bounds`,
32
+ `sent` and `rcvd`. (#37)
33
+
34
+ ### Changed
35
+
36
+ - Like grpc-core, the keepalive ping now waits until a connection has
37
+ received nothing for the keepalive time, so busy connections aren't
38
+ pinged. (#47)
39
+
40
+ ### Fixed
41
+
42
+ - Handlers that aren't coroutines get grpcio's sync context on
43
+ `aio.server()`, so calling `abort()` from them ends the call with that
44
+ status instead of INTERNAL. (#51)
45
+ - `aio.server()` encodes responses as grpc.aio does: a handler's `None` goes
46
+ out as an empty message when there's no serializer, and a response that
47
+ fails to serialize ends the call with UNKNOWN, like any other error in the
48
+ handler, instead of INTERNAL. (#51)
49
+ - `server()` and `aio.server()` ignore a message size limit that isn't an
50
+ integer, as grpcio does, instead of converting it. (#47)
51
+ - grpcio clients can call a Unix socket server without setting
52
+ `grpc.default_authority`. This uses a patched copy of the `http` crate
53
+ until hyperium/http#836 is fixed upstream. (#35)
54
+
55
+ ## 0.3.0 (2026-10-26)
56
+
57
+ Everything here is an addition, so code written for 0.1 and 0.2 keeps working.
58
+
59
+ ### Added
60
+
61
+ - `NativeGrpcFront(metrics=True)` and `metrics()`: call counts and latencies
62
+ per method and status, recorded in Rust without the GIL. The keys
63
+ `metrics()` returns are covered by the compatibility promise. (#29, #31)
64
+
65
+ ### Changed
66
+
67
+ - The low-level API reference moved from the README to
68
+ [docs/low-level-api.md](docs/low-level-api.md). (#31)
69
+
70
+ ## 0.2.0 (2026-10-25)
71
+
72
+ Everything here is an addition, so code written for 0.1 keeps working.
73
+
74
+ ### Added
75
+
76
+ - `pygrpc_native.server()` and `pygrpc_native.aio.server()` take the same
77
+ arguments as `grpc.server()` and `grpc.aio.server()` and serve grpcio-tools
78
+ generated servicers unchanged. Status codes, metadata, deadlines and
79
+ cancellation behave as in grpcio, and the aio server runs on uvloop too.
80
+ (#17, #18, #20)
81
+ - Methods the front doesn't serve itself, such as client-streaming and
82
+ bidirectional methods or server reflection, go to a grpcio server behind it.
83
+ (#19)
84
+ - `NativeGrpcFront` can require a bearer token (`bearer_token_sha256`,
85
+ `unauthenticated_message`). Calls without one are rejected in Rust before
86
+ they reach Python. (#23)
87
+ - `call_details` and `recv_batch_details()` report each call's request
88
+ metadata, peer and deadline. (#10)
89
+ - `add_methods()` registers methods after the front is bound. (#11)
90
+ - `stream_try_send()` for callers that can't block, such as an event loop.
91
+ (#12)
92
+ - `respond()`, `fail()` and `stream_end()` take response metadata, and `fail()`
93
+ can send `grpc-status-details-bin`. (#13)
94
+ - `delay_stream_headers` and `stream_send_initial_metadata()` hold a stream's
95
+ headers back until its first message. (#14)
96
+ - With `call_details`, a cancelled unary call or one that misses its deadline
97
+ is reported as an `EVENT_CANCEL`. (#16)
98
+
99
+ ### Changed
100
+
101
+ - The extension module moved to `pygrpc_native._native`, so the
102
+ `pygrpc_native.pygrpc_native` attribute is gone. Import names from
103
+ `pygrpc_native` as before. (#15)
104
+
105
+ ## 0.1.0 (2026-10-24)
106
+
107
+ First release: `NativeGrpcFront`, a gRPC server whose HTTP/2 and gRPC work
108
+ runs in Rust, outside the GIL. Unary and server-streaming methods, gzip
109
+ requests, deadlines and batched calls to Python. abi3 wheels for CPython 3.10
110
+ and later on Linux (x86_64, aarch64) and macOS (arm64, x86_64).
@@ -59,20 +59,22 @@ commit without it.
59
59
  ## What a PR needs to merge
60
60
 
61
61
  The DCO, lint, rust and python checks have to pass, and a PR needs one
62
- approving review with every review thread resolved. `main` keeps a linear
63
- history: PRs are squash-merged with the PR title as the commit title, and
64
- force pushes and branch deletion are blocked. Issues and PRs with no activity
62
+ approving review with every review thread resolved. While the project has a
63
+ single maintainer, the maintainer's own PRs are merged once the checks pass.
64
+ `main` keeps a linear history: PRs are squash-merged with the PR title as the
65
+ commit title, and force pushes and branch deletion are blocked. Issues and PRs with no activity
65
66
  for 60 days are marked stale and closed 30 days later.
66
67
 
67
68
  ## Releases
68
69
 
69
70
  Releases follow semantic versioning. Existing users call the low-level API
70
71
  directly, so `tests/test_api.py` pins its names, signatures and event tuples;
71
- change it only together with a breaking release.
72
+ change it only together with a new major version.
72
73
 
73
74
  Before a release:
74
75
 
75
- 1. Bump the version in `Cargo.toml`.
76
+ 1. Bump the version in `Cargo.toml` and move the changes into a new section
77
+ of `CHANGELOG.md`.
76
78
  2. Build the wheel and run the tests of known downstream projects against it.
77
79
  3. Push a `vX.Y.Z` tag. The release workflow builds the wheels and publishes
78
80
  them to PyPI.
@@ -31,11 +31,11 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
31
31
 
32
32
  [[package]]
33
33
  name = "block-buffer"
34
- version = "0.10.4"
34
+ version = "0.12.1"
35
35
  source = "registry+https://github.com/rust-lang/crates.io-index"
36
- checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
36
+ checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
37
37
  dependencies = [
38
- "generic-array",
38
+ "hybrid-array",
39
39
  ]
40
40
 
41
41
  [[package]]
@@ -50,11 +50,17 @@ version = "1.0.5"
50
50
  source = "registry+https://github.com/rust-lang/crates.io-index"
51
51
  checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
52
52
 
53
+ [[package]]
54
+ name = "const-oid"
55
+ version = "0.10.2"
56
+ source = "registry+https://github.com/rust-lang/crates.io-index"
57
+ checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
58
+
53
59
  [[package]]
54
60
  name = "cpufeatures"
55
- version = "0.2.17"
61
+ version = "0.3.1"
56
62
  source = "registry+https://github.com/rust-lang/crates.io-index"
57
- checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
63
+ checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
58
64
  dependencies = [
59
65
  "libc",
60
66
  ]
@@ -85,21 +91,21 @@ checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
85
91
 
86
92
  [[package]]
87
93
  name = "crypto-common"
88
- version = "0.1.7"
94
+ version = "0.2.2"
89
95
  source = "registry+https://github.com/rust-lang/crates.io-index"
90
- checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
96
+ checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
91
97
  dependencies = [
92
- "generic-array",
93
- "typenum",
98
+ "hybrid-array",
94
99
  ]
95
100
 
96
101
  [[package]]
97
102
  name = "digest"
98
- version = "0.10.7"
103
+ version = "0.11.3"
99
104
  source = "registry+https://github.com/rust-lang/crates.io-index"
100
- checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
105
+ checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
101
106
  dependencies = [
102
107
  "block-buffer",
108
+ "const-oid",
103
109
  "crypto-common",
104
110
  ]
105
111
 
@@ -165,16 +171,6 @@ dependencies = [
165
171
  "slab",
166
172
  ]
167
173
 
168
- [[package]]
169
- name = "generic-array"
170
- version = "0.14.7"
171
- source = "registry+https://github.com/rust-lang/crates.io-index"
172
- checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
173
- dependencies = [
174
- "typenum",
175
- "version_check",
176
- ]
177
-
178
174
  [[package]]
179
175
  name = "h2"
180
176
  version = "0.4.20"
@@ -209,8 +205,6 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
209
205
  [[package]]
210
206
  name = "http"
211
207
  version = "1.5.0"
212
- source = "registry+https://github.com/rust-lang/crates.io-index"
213
- checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
214
208
  dependencies = [
215
209
  "bytes",
216
210
  "itoa",
@@ -251,6 +245,15 @@ version = "1.0.3"
251
245
  source = "registry+https://github.com/rust-lang/crates.io-index"
252
246
  checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
253
247
 
248
+ [[package]]
249
+ name = "hybrid-array"
250
+ version = "0.4.15"
251
+ source = "registry+https://github.com/rust-lang/crates.io-index"
252
+ checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
253
+ dependencies = [
254
+ "typenum",
255
+ ]
256
+
254
257
  [[package]]
255
258
  name = "hyper"
256
259
  version = "1.12.0"
@@ -392,7 +395,7 @@ dependencies = [
392
395
 
393
396
  [[package]]
394
397
  name = "pygrpc_native"
395
- version = "0.2.0"
398
+ version = "0.4.0"
396
399
  dependencies = [
397
400
  "bytes",
398
401
  "crossbeam-channel",
@@ -496,9 +499,9 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
496
499
 
497
500
  [[package]]
498
501
  name = "sha2"
499
- version = "0.10.9"
502
+ version = "0.11.1"
500
503
  source = "registry+https://github.com/rust-lang/crates.io-index"
501
- checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
504
+ checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19"
502
505
  dependencies = [
503
506
  "cfg-if",
504
507
  "cpufeatures",
@@ -729,12 +732,6 @@ version = "1.0.27"
729
732
  source = "registry+https://github.com/rust-lang/crates.io-index"
730
733
  checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
731
734
 
732
- [[package]]
733
- name = "version_check"
734
- version = "0.9.5"
735
- source = "registry+https://github.com/rust-lang/crates.io-index"
736
- checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
737
-
738
735
  [[package]]
739
736
  name = "want"
740
737
  version = "0.3.2"
@@ -2,11 +2,11 @@
2
2
 
3
3
  [package]
4
4
  name = "pygrpc_native"
5
- version = "0.2.0"
5
+ version = "0.4.0"
6
6
  edition = "2021"
7
7
  license = "Apache-2.0"
8
8
  description = "A gRPC server in Rust that hands calls to Python handlers in batches"
9
- repository = "https://github.com/theminjunchoi/pygrpc-native"
9
+ repository = "https://github.com/pygrpc/pygrpc-native"
10
10
  # tonic 0.14 needs rustc 1.88.
11
11
  rust-version = "1.88"
12
12
  readme = "README.md"
@@ -32,7 +32,7 @@ hyper-util = { version = "0.1", features = ["tokio"] }
32
32
  log = "0.4"
33
33
  pyo3 = { version = "0.29", features = ["abi3-py310"] }
34
34
  pyo3-log = "0.13"
35
- sha2 = "0.10"
35
+ sha2 = "0.11"
36
36
  subtle = "2.6"
37
37
  tokio = { version = "1", features = ["rt-multi-thread", "net", "sync", "time", "macros"] }
38
38
  tonic = { version = "0.14", default-features = false, features = ["server", "gzip"] }
@@ -41,3 +41,9 @@ tonic = { version = "0.14", default-features = false, features = ["server", "gzi
41
41
  [profile.release]
42
42
  lto = "thin"
43
43
  codegen-units = 1
44
+
45
+ # grpc-core sends a Unix socket path, percent-encoded, as :authority, which
46
+ # http 1.5 rejects (hyperium/http#836). The copy allows valid escapes in the
47
+ # host. Drop it once upstream does.
48
+ [patch.crates-io]
49
+ http = { path = "vendor/http" }
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: pygrpc-native
3
+ Version: 0.4.0
4
+ Classifier: Programming Language :: Python :: 3
5
+ Classifier: Programming Language :: Rust
6
+ Classifier: Operating System :: POSIX :: Linux
7
+ Classifier: Operating System :: MacOS
8
+ License-File: LICENSE
9
+ Summary: A drop-in grpcio server whose HTTP/2 and gRPC work runs in Rust, outside the GIL
10
+ License-Expression: Apache-2.0
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
13
+ Project-URL: Repository, https://github.com/pygrpc/pygrpc-native
14
+
15
+ # pygrpc-native
16
+
17
+ A gRPC server for Python whose HTTP/2 and gRPC work runs in Rust, outside the
18
+ GIL. Your handlers stay in Python.
19
+
20
+ When a Python gRPC server runs out of CPU, the usual fixes are running more
21
+ server processes or rewriting the servicers in another language. pygrpc-native
22
+ instead moves the gRPC work itself (framing, flow control, deadlines and
23
+ cancellation) onto a tokio runtime outside the GIL, and leaves your servicers,
24
+ interceptors and generated code alone, so Python spends its time on your code.
25
+ `pygrpc_native.server()` and `pygrpc_native.aio.server()` take the same
26
+ arguments as grpcio's servers, so switching is usually a one-line change, and
27
+ nothing changes for clients.
28
+
29
+ Underneath, each call reaches Python as an event: an id, the method, the
30
+ request bytes and who sent it.
31
+
32
+ ```
33
+ grpc client ──HTTP/2──▶ tokio threads (no GIL)
34
+ │ path → method id
35
+ ▼
36
+ event queue ──▶ recv_batch() in Python
37
+ ◀── respond(call_id, bytes) from any thread
38
+ ```
39
+
40
+ Calls reach Python in batches, so a burst takes one trip across the GIL
41
+ rather than one per call.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install pygrpc-native
47
+ ```
48
+
49
+ Wheels are published for Linux (x86_64, aarch64) and macOS (arm64,
50
+ x86_64) and work on CPython 3.10 and later. To build from a checkout:
51
+
52
+ ```bash
53
+ pip install maturin
54
+ maturin develop --release
55
+ ```
56
+
57
+ ## With grpcio servicers
58
+
59
+ `pygrpc_native.server()` takes the same arguments as `grpc.server()` and
60
+ serves servicers from grpcio-tools generated code (1.64 or later) unchanged:
61
+
62
+ ```python
63
+ from concurrent import futures
64
+ import pygrpc_native
65
+
66
+ server = pygrpc_native.server(futures.ThreadPoolExecutor(16))
67
+ greeter_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
68
+ server.add_insecure_port("[::]:50051")
69
+ server.start()
70
+ server.wait_for_termination()
71
+ ```
72
+
73
+ Status codes, error messages, metadata, deadlines, cancellation and
74
+ `ServicerContext` behave as they do in grpcio.
75
+
76
+ Unary and server-streaming methods are served natively on one insecure port.
77
+ The rest, such as client-streaming and bidirectional methods, server
78
+ reflection, or handlers added only with `add_generic_rpc_handlers`, as code
79
+ generated by grpcio-tools before 1.64 does, go to a grpcio server on a Unix
80
+ socket behind it. They work, but they aren't any faster, and `peer()` in
81
+ them is the socket.
82
+
83
+ Interceptors run as in grpcio, on the methods served here and on the fallback
84
+ server. Of the server options, the message size limits, the keepalive
85
+ settings (`grpc.keepalive_time_ms`, `grpc.keepalive_timeout_ms` and
86
+ `grpc.keepalive_permit_without_calls`), `grpc.max_concurrent_streams` and the
87
+ metadata size limits (`grpc.max_metadata_size` and
88
+ `grpc.absolute_max_metadata_size`) work as in grpcio. Options that ask for
89
+ something this server can't do raise an error: closing connections by age or
90
+ idleness (`grpc.max_connection_age_ms`, `grpc.max_connection_age_grace_ms`,
91
+ `grpc.max_connection_idle_ms`), `grpc.max_allowed_incoming_connections`,
92
+ response compression and `grpc.so_reuseport`, as do `maximum_concurrent_rpcs`
93
+ and TLS. Any other option is ignored, as grpcio ignores the many that only
94
+ clients read. That covers the limits on client pings, which are accepted but
95
+ not enforced, and `grpc.http2.max_frame_size`, which grpc-core's flow control
96
+ overrides anyway. Values that aren't integers are ignored too, as in grpcio.
97
+
98
+ There are two known differences. If a unary handler calls
99
+ `send_initial_metadata()` and then fails, the metadata goes out with the
100
+ status, so the client sees it as trailing metadata. And a connection whose
101
+ client doesn't answer a keepalive ping is closed after
102
+ `grpc.keepalive_timeout_ms`, as gRPC's keepalive documentation describes,
103
+ while grpcio waits for `grpc.http2.ping_timeout_ms`, a minute by default.
104
+
105
+ `pygrpc_native.aio.server()` does the same for `grpc.aio.server()`:
106
+
107
+ ```python
108
+ import pygrpc_native.aio
109
+
110
+ server = pygrpc_native.aio.server()
111
+ greeter_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
112
+ server.add_insecure_port("[::]:50051")
113
+ await server.start()
114
+ await server.wait_for_termination()
115
+ ```
116
+
117
+ Handlers run on the event loop that called `start()`, uvloop included.
118
+ `abort()` raises `grpc.aio.AbortError`, and a handler whose client cancels or
119
+ whose deadline passes gets `asyncio.CancelledError`, as in grpc.aio. Streams
120
+ can yield from an async generator or call `context.write()`, and handlers
121
+ that aren't coroutines run in `migration_thread_pool` with grpcio's sync
122
+ context, where `abort()` returns instead of raising. The fallback,
123
+ the unsupported options and the known differences are the same as
124
+ for the sync server.
125
+
126
+ ## Low-level API
127
+
128
+ `NativeGrpcFront` is the layer under `server()`. Each call reaches Python as
129
+ an event that you answer by id:
130
+
131
+ ```python
132
+ import threading
133
+ from pygrpc_native import EVENT_CALL, NativeGrpcFront
134
+
135
+ front = NativeGrpcFront("0.0.0.0:50051", [("/echo.Echo/Say", False)])
136
+ front.start()
137
+
138
+ def serve():
139
+ while (events := front.recv_batch(64)) is not None:
140
+ for kind, call_id, method_id, payload, client_key in events:
141
+ if kind == EVENT_CALL:
142
+ front.respond(call_id, payload)
143
+
144
+ threading.Thread(target=serve).start()
145
+ ```
146
+
147
+ Streaming, call details, response metadata, bearer tokens and metrics are
148
+ covered in [docs/low-level-api.md](https://github.com/pygrpc/pygrpc-native/blob/main/docs/low-level-api.md).
149
+
150
+ ## Compatibility
151
+
152
+ pygrpc-native follows semantic versioning, and the low-level API stays
153
+ compatible within 0.x as well. `tests/test_api.py` pins the 0.1 names,
154
+ signatures and event tuples of `NativeGrpcFront`: they won't change or go
155
+ away, and new parameters only come after the existing ones, with a default.
156
+ The keys `metrics()` returns won't be renamed or removed within 0.x either,
157
+ though new ones may be added. `server()` and `aio.server()` follow grpcio's
158
+ API, and any difference from grpcio's behavior is a bug unless it is listed in
159
+ this README. Changes are listed in [CHANGELOG.md](CHANGELOG.md).
160
+
161
+ ## Limitations
162
+
163
+ - `NativeGrpcFront` serves unary and server-streaming methods only; other
164
+ kinds can go through the fallback server. `server()` and `aio.server()`
165
+ send them there on their own.
166
+ - No TLS yet. `NativeGrpcFront` checks bearer tokens itself; `server()` and
167
+ `aio.server()` run grpcio interceptors instead.
168
+ - Like grpc-core, binding a Unix socket path replaces any socket file
169
+ already there.
170
+ - The server never sets `SO_REUSEPORT`, so binding a port another server is
171
+ using fails, where grpcio's default lets both bind it. `grpc.so_reuseport`
172
+ set to 0 is accepted; other integers raise an error.
173
+ - Per connection: at most 1024 concurrent streams unless
174
+ `grpc.max_concurrent_streams` says otherwise. A connection that hasn't
175
+ finished the HTTP/2 preface after 120 s is dropped.
176
+ - A call with more request metadata than the limit (16 KiB unless set) is
177
+ refused. grpcio also refuses some calls at random between
178
+ `grpc.max_metadata_size` and that limit; this server doesn't.
179
+
180
+ ## Benchmarks
181
+
182
+ With an empty handler on an Apple M4 Pro, `pygrpc_native.server()` answers
183
+ about 1.5x the calls per second of `grpc.server()` from one client thread and
184
+ about 5x from 32. On a 40-vCPU Linux container the low-level API answered
185
+ about 12x at 32 threads. See [benchmarks](https://github.com/pygrpc/pygrpc-native/blob/main/benchmarks/README.md) for the
186
+ numbers and how they were measured.
187
+
188
+ ## Contributing
189
+
190
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Every commit needs a DCO sign-off
191
+ (`git commit -s`).
192
+
193
+ ## License
194
+
195
+ Apache-2.0
196
+