-
Notifications
You must be signed in to change notification settings - Fork 133
Add support for gRPC binary metadata values #1070
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for gRPC binary metadata values #1070
Conversation
6a68726
to
5001e92
Compare
Would close temporalio/features#671 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I think poe lint
is failing here in tests/api/test_grpc_stub.py
(see CI run).
self.retry_client | ||
.get_client() | ||
.set_headers(ascii_headers) | ||
.map_err(|err| PyValueError::new_err(err.to_string()))?; | ||
self.retry_client | ||
.get_client() | ||
.set_binary_headers(binary_headers) | ||
.map_err(|err| PyValueError::new_err(err.to_string()))?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are both fallible and non-atomic 😕. Right now there's a bit of a risk that the client is left in an inconsistent state (in a way that other code could observe) if one of them fails.
I suspect you'll run into this in the other SDKs, too.
I made a test to reproduce/document the issue:
sdk-python/tests/api/test_grpc_stub.py
Lines 235 to 265 in 54487f5
# Setting invalid RPC metadata in a mixed client will partially fail: | |
client.rpc_metadata = { | |
"x-my-binary-bin": b"\x00", | |
"x-my-ascii": "foo", | |
} | |
assert client.rpc_metadata == { | |
"x-my-binary-bin": b"\x00", | |
"x-my-ascii": "foo", | |
} | |
with pytest.raises( | |
ValueError, | |
match="Invalid binary header key 'x-invalid-ascii-with-bin-value': invalid gRPC metadata key name", | |
): | |
client.rpc_metadata = { | |
"x-invalid-ascii-with-bin-value": b"not-ascii", | |
"x-my-ascii": "bar", | |
} | |
assert client.rpc_metadata == { | |
"x-my-binary-bin": b"\x00", | |
"x-my-ascii": "foo", | |
} | |
await client.workflow_service.get_system_info(GetSystemInfoRequest()) | |
workflow_server.assert_last_metadata( | |
{ | |
"authorization": "Bearer my-api-key", | |
# This is inconsistent with what `client.rpc_metadata` returns | |
# (`x-my-ascii` was updated): | |
"x-my-binary-bin": b"\x00", | |
"x-my-ascii": "bar", | |
} | |
) |
Let me know if we should fix this. I think another PR to SDK core that either:
- Adds an atomic
ConfiguredClient::set_ascii_and_binary_headers(&self, ascii_headers: HashMap<String, String>, binary_headers: HashMap<String, Vec<u8>>) -> Result<(), InvalidHeaderError>
- Adds
ConfiguredClient::clear_headers(&self) -> ()
/ConfiguredClient::clear_binary_headers(&self) -> ()
that can be used to infallibly clear headers, as a way to at least move the client into a consistent state if there any errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not that concerned, though arguably we could have changed set_headers
to accept both types of headers instead of just one. We can just document this above the rpc metadata setter I think. I appreciate that ascii is done first since I think y'all may be the only one doing binary :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could have changed set_headers to accept both types of headers instead of just one.
FWIW the reason I didn't do something like that originally is that it would have broken semver on the Core SDK client crate (although maybe I could have done some impl Into<...>
magic to make existing callers still work?)
We can just document this above the rpc metadata setter I think.
Ack, I can make that change.
I appreciate that ascii is done first since I think y'all may be the only one doing binary :-)
Heh, fair. Yeah I think if the ASCII is invalid (and setting it fails), this same issue doesn't exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the docs in 8b1dd3e:

|
||
target_url: str | ||
metadata: Mapping[str, str] | ||
metadata: Mapping[str, Union[str, bytes]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to use str | bytes
luckily ran into a CI issue about Python 3.9 incompatibility (which I hadn't realized). These all use Union[str, bytes]
as a result.
#[derive(FromPyObject)] | ||
enum RpcMetadataValue { | ||
#[pyo3(transparent, annotation = "str")] | ||
Str(String), | ||
#[pyo3(transparent, annotation = "bytes")] | ||
Bytes(Vec<u8>), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the tests in tests/api/test_grpc_stub.py
for what the error message looks like, when an invalid type is specified. I think pyo3 does a good job at producing a reasonably-good error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 And not too worried about error quality for this rare situation
What was changed
This PR makes it possible to set gRPC binary metadata values on outbound requests, both on a client-wide basis and on a per-RPC basis.
This extends the type of most gRPC metadata bags throughout the SDK from
Mapping[str, str]
→Mapping[str, Union[str, bytes]]
. This is the main user-facing change. Internally, the core SDK bridge interprets this (using pyo3's support for turning Python unions into Rust enums) and sets the appropriate core-SDK-client field or tonic metadata field as appropriate.This PR depends on the changes in temporalio/sdk-core#997 (and updates the core SDK submodule to point to a sufficiently newer commit).
Why?
#1063
Checklist
Closes [Feature Request] Ensure gRPC binary metadata headers are supported #1063
How was this tested:
tests/api/test_grpc_stub.py
poe test
(although this doesn't seem to run thetests/api/...
tests 😕)poe test ./tests/api
, which does run the updated testThis worked, as expected:
Any docs updates needed?