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.
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/ISSUE_TEMPLATE/config.yml +1 -1
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/workflows/ci.yml +2 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.pre-commit-config.yaml +1 -0
- pygrpc_native-0.4.0/CHANGELOG.md +110 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/CONTRIBUTING.md +7 -5
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/Cargo.lock +29 -32
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/Cargo.toml +9 -3
- pygrpc_native-0.4.0/PKG-INFO +196 -0
- pygrpc_native-0.4.0/README.md +181 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/SECURITY.md +1 -1
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/benchmarks/README.md +2 -1
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/benchmarks/echo.py +4 -2
- pygrpc_native-0.2.0/README.md → pygrpc_native-0.4.0/docs/low-level-api.md +66 -120
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/pyproject.toml +4 -2
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/python/pygrpc_native/_native.pyi +8 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/python/pygrpc_native/_server.py +161 -41
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/python/pygrpc_native/aio.py +190 -16
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/src/lib.rs +104 -5
- pygrpc_native-0.4.0/src/metrics.rs +340 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/src/server.rs +367 -47
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_aio.py +103 -3
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_api.py +36 -2
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_calls.py +4 -5
- pygrpc_native-0.4.0/tests/test_connection.py +381 -0
- pygrpc_native-0.4.0/tests/test_downstream.py +386 -0
- pygrpc_native-0.4.0/tests/test_interceptors.py +421 -0
- pygrpc_native-0.4.0/tests/test_metrics.py +152 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_server.py +1 -3
- pygrpc_native-0.4.0/vendor/http/CHANGELOG.md +269 -0
- pygrpc_native-0.4.0/vendor/http/Cargo.toml +77 -0
- pygrpc_native-0.4.0/vendor/http/LICENSE-APACHE +201 -0
- pygrpc_native-0.4.0/vendor/http/LICENSE-MIT +25 -0
- pygrpc_native-0.4.0/vendor/http/README.md +80 -0
- pygrpc_native-0.4.0/vendor/http/src/byte_str.rs +91 -0
- pygrpc_native-0.4.0/vendor/http/src/convert.rs +17 -0
- pygrpc_native-0.4.0/vendor/http/src/error.rs +167 -0
- pygrpc_native-0.4.0/vendor/http/src/extensions.rs +358 -0
- pygrpc_native-0.4.0/vendor/http/src/header/map.rs +4044 -0
- pygrpc_native-0.4.0/vendor/http/src/header/mod.rs +142 -0
- pygrpc_native-0.4.0/vendor/http/src/header/name.rs +1895 -0
- pygrpc_native-0.4.0/vendor/http/src/header/value.rs +776 -0
- pygrpc_native-0.4.0/vendor/http/src/lib.rs +210 -0
- pygrpc_native-0.4.0/vendor/http/src/method.rs +521 -0
- pygrpc_native-0.4.0/vendor/http/src/request.rs +1072 -0
- pygrpc_native-0.4.0/vendor/http/src/response.rs +781 -0
- pygrpc_native-0.4.0/vendor/http/src/status.rs +596 -0
- pygrpc_native-0.4.0/vendor/http/src/uri/authority.rs +749 -0
- pygrpc_native-0.4.0/vendor/http/src/uri/builder.rs +261 -0
- pygrpc_native-0.4.0/vendor/http/src/uri/mod.rs +1132 -0
- pygrpc_native-0.4.0/vendor/http/src/uri/path.rs +709 -0
- pygrpc_native-0.4.0/vendor/http/src/uri/port.rs +151 -0
- pygrpc_native-0.4.0/vendor/http/src/uri/scheme.rs +361 -0
- pygrpc_native-0.4.0/vendor/http/src/uri/tests.rs +519 -0
- pygrpc_native-0.4.0/vendor/http/src/version.rs +75 -0
- pygrpc_native-0.4.0/vendor/http/tests/header_map.rs +774 -0
- pygrpc_native-0.4.0/vendor/http/tests/header_map_fuzz.rs +365 -0
- pygrpc_native-0.4.0/vendor/http/tests/status_code.rs +82 -0
- pygrpc_native-0.2.0/PKG-INFO +0 -241
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/CODEOWNERS +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/dependabot.yml +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/workflows/dco.yml +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/workflows/release.yml +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.github/workflows/stale.yml +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/.gitignore +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/CODE_OF_CONDUCT.md +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/DCO +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/LICENSE +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/python/pygrpc_native/__init__.py +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/python/pygrpc_native/py.typed +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_add_methods.py +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_bearer_auth.py +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_call_details.py +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_delay_stream_headers.py +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_fallback.py +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_front.py +0 -0
- {pygrpc_native-0.2.0 → pygrpc_native-0.4.0}/tests/test_response_metadata.py +0 -0
- {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/
|
|
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
|
|
@@ -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.
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
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.
|
|
34
|
+
version = "0.12.1"
|
|
35
35
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
36
|
-
checksum = "
|
|
36
|
+
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
|
37
37
|
dependencies = [
|
|
38
|
-
"
|
|
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.
|
|
61
|
+
version = "0.3.1"
|
|
56
62
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
57
|
-
checksum = "
|
|
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.
|
|
94
|
+
version = "0.2.2"
|
|
89
95
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
90
|
-
checksum = "
|
|
96
|
+
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
|
91
97
|
dependencies = [
|
|
92
|
-
"
|
|
93
|
-
"typenum",
|
|
98
|
+
"hybrid-array",
|
|
94
99
|
]
|
|
95
100
|
|
|
96
101
|
[[package]]
|
|
97
102
|
name = "digest"
|
|
98
|
-
version = "0.
|
|
103
|
+
version = "0.11.3"
|
|
99
104
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
100
|
-
checksum = "
|
|
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.
|
|
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.
|
|
502
|
+
version = "0.11.1"
|
|
500
503
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
501
|
-
checksum = "
|
|
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.
|
|
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/
|
|
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.
|
|
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
|
+
|