protodial 1.0.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 (121) hide show
  1. protodial-1.0.0/.gitignore +16 -0
  2. protodial-1.0.0/CHANGELOG.md +21 -0
  3. protodial-1.0.0/LICENSE +21 -0
  4. protodial-1.0.0/PKG-INFO +306 -0
  5. protodial-1.0.0/README.md +276 -0
  6. protodial-1.0.0/docs/diagnostics.md +107 -0
  7. protodial-1.0.0/examples/README.md +86 -0
  8. protodial-1.0.0/examples/call.sh +33 -0
  9. protodial-1.0.0/examples/deadlines/Dockerfile +27 -0
  10. protodial-1.0.0/examples/deadlines/call.sh +25 -0
  11. protodial-1.0.0/examples/deadlines/deadlines.proto +15 -0
  12. protodial-1.0.0/examples/deadlines/server.py +89 -0
  13. protodial-1.0.0/examples/errors/Dockerfile +27 -0
  14. protodial-1.0.0/examples/errors/call.sh +24 -0
  15. protodial-1.0.0/examples/errors/errors.proto +16 -0
  16. protodial-1.0.0/examples/errors/server.py +107 -0
  17. protodial-1.0.0/examples/formats/Dockerfile +27 -0
  18. protodial-1.0.0/examples/formats/call.sh +34 -0
  19. protodial-1.0.0/examples/formats/formats.proto +31 -0
  20. protodial-1.0.0/examples/formats/server.py +99 -0
  21. protodial-1.0.0/examples/hello/Dockerfile +27 -0
  22. protodial-1.0.0/examples/hello/call.sh +23 -0
  23. protodial-1.0.0/examples/hello/hello.proto +16 -0
  24. protodial-1.0.0/examples/hello/server.py +83 -0
  25. protodial-1.0.0/examples/lib.sh +30 -0
  26. protodial-1.0.0/examples/run.sh +157 -0
  27. protodial-1.0.0/examples/server_logging.py +75 -0
  28. protodial-1.0.0/examples/streaming/Dockerfile +27 -0
  29. protodial-1.0.0/examples/streaming/call.sh +26 -0
  30. protodial-1.0.0/examples/streaming/server.py +171 -0
  31. protodial-1.0.0/examples/streaming/streaming.proto +24 -0
  32. protodial-1.0.0/pyproject.toml +93 -0
  33. protodial-1.0.0/src/protodial/__init__.py +3 -0
  34. protodial-1.0.0/src/protodial/__main__.py +5 -0
  35. protodial-1.0.0/src/protodial/_rich_details.py +35 -0
  36. protodial-1.0.0/src/protodial/argument_parser/__init__.py +7 -0
  37. protodial-1.0.0/src/protodial/argument_parser/construction.py +406 -0
  38. protodial-1.0.0/src/protodial/argument_parser/parsing.py +115 -0
  39. protodial-1.0.0/src/protodial/argument_parser/resolution.py +162 -0
  40. protodial-1.0.0/src/protodial/argument_parser/target.py +227 -0
  41. protodial-1.0.0/src/protodial/argument_parser/validation.py +249 -0
  42. protodial-1.0.0/src/protodial/cli.py +662 -0
  43. protodial-1.0.0/src/protodial/commands/__init__.py +36 -0
  44. protodial-1.0.0/src/protodial/commands/models.py +131 -0
  45. protodial-1.0.0/src/protodial/commands/operations.py +29 -0
  46. protodial-1.0.0/src/protodial/errors.py +164 -0
  47. protodial-1.0.0/src/protodial/formats.py +14 -0
  48. protodial-1.0.0/src/protodial/message_io/__init__.py +61 -0
  49. protodial-1.0.0/src/protodial/message_io/dispatch.py +65 -0
  50. protodial-1.0.0/src/protodial/message_io/json_decoding.py +56 -0
  51. protodial-1.0.0/src/protodial/message_io/json_input.py +411 -0
  52. protodial-1.0.0/src/protodial/message_io/limits.py +59 -0
  53. protodial-1.0.0/src/protodial/message_io/models.py +90 -0
  54. protodial-1.0.0/src/protodial/message_io/output.py +94 -0
  55. protodial-1.0.0/src/protodial/message_io/protobuf_input.py +87 -0
  56. protodial-1.0.0/src/protodial/message_io/raw_input.py +125 -0
  57. protodial-1.0.0/src/protodial/message_io/text_input.py +114 -0
  58. protodial-1.0.0/src/protodial/presentation/__init__.py +51 -0
  59. protodial-1.0.0/src/protodial/presentation/_grapheme_data.py +1899 -0
  60. protodial-1.0.0/src/protodial/presentation/_graphemes.py +233 -0
  61. protodial-1.0.0/src/protodial/presentation/_truncation.py +198 -0
  62. protodial-1.0.0/src/protodial/presentation/descriptor.py +586 -0
  63. protodial-1.0.0/src/protodial/presentation/diagnostics/__init__.py +39 -0
  64. protodial-1.0.0/src/protodial/presentation/diagnostics/context.py +28 -0
  65. protodial-1.0.0/src/protodial/presentation/diagnostics/error_events.py +667 -0
  66. protodial-1.0.0/src/protodial/presentation/diagnostics/models.py +571 -0
  67. protodial-1.0.0/src/protodial/presentation/diagnostics/rendering.py +3161 -0
  68. protodial-1.0.0/src/protodial/presentation/rpc.py +195 -0
  69. protodial-1.0.0/src/protodial/presentation/sanitization.py +30 -0
  70. protodial-1.0.0/src/protodial/presentation/templates.py +131 -0
  71. protodial-1.0.0/src/protodial/py.typed +1 -0
  72. protodial-1.0.0/src/protodial/request_metadata/__init__.py +26 -0
  73. protodial-1.0.0/src/protodial/request_metadata/parsing.py +187 -0
  74. protodial-1.0.0/src/protodial/request_metadata/types.py +11 -0
  75. protodial-1.0.0/src/protodial/rich_status/__init__.py +36 -0
  76. protodial-1.0.0/src/protodial/rich_status/decoding.py +457 -0
  77. protodial-1.0.0/src/protodial/rich_status/models.py +43 -0
  78. protodial-1.0.0/src/protodial/rich_status/rendering.py +245 -0
  79. protodial-1.0.0/src/protodial/rpc.py +249 -0
  80. protodial-1.0.0/src/protodial/rpc_core/__init__.py +1 -0
  81. protodial-1.0.0/src/protodial/rpc_core/call.py +843 -0
  82. protodial-1.0.0/src/protodial/rpc_core/discovery.py +99 -0
  83. protodial-1.0.0/src/protodial/rpc_core/input.py +115 -0
  84. protodial-1.0.0/src/protodial/rpc_core/invoke.py +191 -0
  85. protodial-1.0.0/src/protodial/rpc_core/models.py +49 -0
  86. protodial-1.0.0/src/protodial/rpc_core/protocols.py +233 -0
  87. protodial-1.0.0/src/protodial/rpc_core/runtime.py +32 -0
  88. protodial-1.0.0/src/protodial/schema/__init__.py +1 -0
  89. protodial-1.0.0/src/protodial/schema/composition.py +95 -0
  90. protodial-1.0.0/src/protodial/schema/context.py +229 -0
  91. protodial-1.0.0/src/protodial/schema/contracts.py +170 -0
  92. protodial-1.0.0/src/protodial/schema/export.py +101 -0
  93. protodial-1.0.0/src/protodial/schema/factory.py +47 -0
  94. protodial-1.0.0/src/protodial/schema/local.py +607 -0
  95. protodial-1.0.0/src/protodial/schema/paths.py +54 -0
  96. protodial-1.0.0/src/protodial/schema/reflection.py +570 -0
  97. protodial-1.0.0/src/protodial/schema/registry.py +477 -0
  98. protodial-1.0.0/src/protodial/schema/source_locations.py +20 -0
  99. protodial-1.0.0/src/protodial/schema/source_protos.py +113 -0
  100. protodial-1.0.0/src/protodial/transport.py +103 -0
  101. protodial-1.0.0/src/protodial/transport_core/__init__.py +1 -0
  102. protodial-1.0.0/src/protodial/transport_core/channels.py +185 -0
  103. protodial-1.0.0/src/protodial/transport_core/insecure.py +584 -0
  104. protodial-1.0.0/src/protodial/transport_core/options.py +134 -0
  105. protodial-1.0.0/src/protodial/transport_core/protocols.py +82 -0
  106. protodial-1.0.0/src/protodial/transport_core/tls.py +335 -0
  107. protodial-1.0.0/tests/test_cli.py +1867 -0
  108. protodial-1.0.0/tests/test_cli_end_to_end.py +1580 -0
  109. protodial-1.0.0/tests/test_cli_io_security_edges.py +360 -0
  110. protodial-1.0.0/tests/test_diagnostics_metadata_status.py +710 -0
  111. protodial-1.0.0/tests/test_discovery.py +497 -0
  112. protodial-1.0.0/tests/test_examples.py +136 -0
  113. protodial-1.0.0/tests/test_messages.py +1077 -0
  114. protodial-1.0.0/tests/test_rpc_input_failures.py +229 -0
  115. protodial-1.0.0/tests/test_rpc_integration.py +385 -0
  116. protodial-1.0.0/tests/test_rpc_results.py +637 -0
  117. protodial-1.0.0/tests/test_schema_edge_cases.py +801 -0
  118. protodial-1.0.0/tests/test_schema_sources.py +1130 -0
  119. protodial-1.0.0/tests/test_structured_diagnostics.py +4021 -0
  120. protodial-1.0.0/tests/test_transport.py +722 -0
  121. protodial-1.0.0/tests/test_tunnel_lifecycle_edges.py +256 -0
@@ -0,0 +1,16 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+
7
+ # Virtual environments
8
+ .venv/
9
+
10
+ # Builds
11
+ build/
12
+ dist/
13
+ *.egg-info/
14
+
15
+ # Editors and operating systems
16
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 — 2026-10-25
4
+
5
+ Initial release.
6
+
7
+ - Invoke unary, client-streaming, server-streaming, and bidirectional gRPC methods.
8
+ - Discover services and protobuf symbols through server reflection, local
9
+ `.proto` files, or binary descriptor sets. Generate request templates and
10
+ export resolved descriptor sets.
11
+ - Read and write ProtoJSON, textproto, serialized protobuf, and raw bytes.
12
+ Stream requests from stdin and bind files to protobuf bytes fields.
13
+ - Configure TLS, custom CAs, mutual TLS, server identity, and HTTP/2 authority.
14
+ Support plaintext and explicit unverified TLS connections.
15
+ - Send shared or scoped request metadata. Inspect response headers, trailers,
16
+ status, and structured gRPC errors.
17
+ - Emit human-readable or versioned NDJSON diagnostics on stderr while keeping
18
+ response payloads on stdout.
19
+ - Configure deadlines, readiness checks, message limits, compression, and
20
+ keepalives. Support resolver URIs and Unix-socket targets.
21
+ - Provide a Python 3.10+ package, a `protodial` command, and a Docker image build.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 félix
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,306 @@
1
+ Metadata-Version: 2.5
2
+ Name: protodial
3
+ Version: 1.0.0
4
+ Summary: Call gRPC services from the command line using reflection or local proto files
5
+ Project-URL: Repository, https://github.com/ocku/protodial
6
+ Project-URL: Issues, https://github.com/ocku/protodial/issues
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: cli,grpc,protobuf,reflection,rpc
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: System :: Networking
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: cryptography>=42
24
+ Requires-Dist: googleapis-common-protos>=1.63
25
+ Requires-Dist: grpcio-reflection>=1.62
26
+ Requires-Dist: grpcio-tools>=1.62
27
+ Requires-Dist: grpcio>=1.62
28
+ Requires-Dist: protobuf>=7
29
+ Description-Content-Type: text/markdown
30
+
31
+ # protodial
32
+
33
+ A Python command-line client for gRPC. Call methods, list services, inspect
34
+ protobuf definitions, and generate request templates using server reflection,
35
+ local `.proto` files, or descriptor sets. No generated client code is required.
36
+
37
+ Supports unary, client-streaming, server-streaming, and bidirectional RPCs.
38
+ Requests and responses can use ProtoJSON, textproto, serialized protobuf, or
39
+ raw bytes. Response payloads go to stdout; diagnostics go to stderr.
40
+
41
+ ## Installation
42
+
43
+ Requires Python 3.10 or newer. Install the checkout with
44
+ [uv](https://docs.astral.sh/uv/):
45
+
46
+ ```console
47
+ uv tool install .
48
+ protodial --version
49
+ ```
50
+
51
+ If the package is available on your package index, install version 1.0.0 with
52
+ `uv tool install protodial==1.0.0` or `python -m pip install protodial==1.0.0`.
53
+ You can also run it as `python -m protodial`.
54
+
55
+ ## Quick start
56
+
57
+ Call a server with [reflection](https://grpc.io/docs/guides/reflection/) enabled:
58
+
59
+ ```console
60
+ protodial -d '{"name":"Ada"}' \
61
+ grpc://localhost:50051/example.Greeter/SayHello
62
+ ```
63
+
64
+ The [hello example](examples/README.md) returns compact ProtoJSON:
65
+
66
+ ```json
67
+ {"message":"Hello, Ada!","requestId":"example-123"}
68
+ ```
69
+
70
+ Use `grpc://` for plaintext and `grpcs://` for TLS. Bare targets and gRPC
71
+ resolver URIs use TLS by default. For resolver or Unix-socket targets, specify
72
+ `--method` separately:
73
+
74
+ ```console
75
+ protodial --plaintext --method example.Greeter/SayHello dns:///localhost:50051
76
+ protodial --plaintext --method example.Greeter/SayHello unix:///tmp/example.sock
77
+ ```
78
+
79
+ When reflection is unavailable, provide a schema:
80
+
81
+ ```console
82
+ protodial --proto api.proto --proto-path protos -d @request.json \
83
+ grpcs://api.example.com:443/example.Greeter/SayHello
84
+ ```
85
+
86
+ Invocation options precede the target. Discovery options follow `list` or
87
+ `describe`. Run `protodial --help`, `protodial list --help`, or
88
+ `protodial describe --help` for the full option reference.
89
+
90
+ ## Schema discovery
91
+
92
+ ```console
93
+ # List services, then methods on a service.
94
+ protodial list grpc://localhost:50051
95
+ protodial list grpc://localhost:50051 example.Greeter
96
+
97
+ # Inspect a service, method, message, or field.
98
+ protodial describe grpc://localhost:50051 example.Greeter
99
+ protodial describe grpc://localhost:50051/example.Greeter/SayHello
100
+ protodial describe grpc://localhost:50051 example.HelloRequest.name
101
+
102
+ # Generate a ProtoJSON request template.
103
+ protodial describe --template \
104
+ grpc://localhost:50051/example.Greeter/SayHello
105
+ ```
106
+
107
+ Service names are sorted; methods retain declaration order. Reflection services
108
+ are hidden unless `--show-reflection` is set. Unique short or suffix names are
109
+ accepted; ambiguous names report the qualified candidates.
110
+
111
+ `describe --template` accepts a message or method. For methods, it uses the
112
+ request type. Templates include one repeated item and the first alternative
113
+ of each oneof. `--template-depth` limits nesting (default: 8).
114
+
115
+ ### Local schemas and descriptor sets
116
+
117
+ ```console
118
+ protodial list --proto api.proto --proto-path protos unused.example:443
119
+ protodial describe --protoset api.protoset \
120
+ unused.example:443 example.HelloRequest
121
+ protodial list --protoset-out schema.pb grpc://localhost:50051
122
+ ```
123
+
124
+ - Repeat `--proto` to compile multiple entrypoints or `--protoset` to combine
125
+ binary `FileDescriptorSet` files. Conflicting definitions of the same file
126
+ are rejected.
127
+ - Local schemas disable reflection by default. Add `--use-reflection` to resolve
128
+ missing symbols from the server.
129
+ - Local-only discovery does not contact the target, but still requires a target
130
+ operand.
131
+ - `--protoset-out` atomically exports the resolved schema and its dependencies.
132
+ - Repeat `--proto-path` for import roots. The default reusable schema directory
133
+ is `$XDG_DATA_HOME/protodial/protos`, falling back to
134
+ `$HOME/.local/share/protodial/protos`. It is not created automatically.
135
+
136
+ Invocation URL paths use `/SERVICE/METHOD`. Discovery URLs can contain
137
+ `/SERVICE` for `list`, or `/SYMBOL` or `/SERVICE/METHOD` for `describe`.
138
+ URL credentials, query strings, and fragments are not supported.
139
+
140
+ ## Request and response formats
141
+
142
+ Select encodings independently with `--input-format` and `--output-format`.
143
+ Both default to `json`.
144
+
145
+ | Format | Unary | Streaming |
146
+ | --- | --- | --- |
147
+ | `json` | One ProtoJSON value | Whitespace-separated values; output is NDJSON |
148
+ | `text` | One textproto document | Documents separated by a line containing `---` |
149
+ | `protobuf` | One serialized message | Four-byte unsigned big-endian length, then message bytes |
150
+ | `raw` | One top-level `bytes` field | Input byte chunks; concatenated output bytes |
151
+
152
+ Protobuf file framing is a protodial convention, not gRPC wire framing.
153
+ ProtoJSON `bytes` fields use base64 strings.
154
+
155
+ `-d` / `--data` accepts a literal value, `@path` for a file, or `-` / `@-` for
156
+ stdin. Without `--data`, unary calls send one empty message and client streams
157
+ send no messages, regardless of format.
158
+
159
+ JSON client streams are parsed incrementally. NDJSON, multiline values, and
160
+ consecutive whitespace-separated values are accepted. Responses are compact
161
+ JSON values, one per line:
162
+
163
+ ```console
164
+ printf '%s\n' '{"value":1}' '{"value":2}' | \
165
+ protodial -d @- grpc://localhost:50051/example.Service/Upload
166
+
167
+ protodial -d '{"name":"Ada"}' \
168
+ grpc://localhost:50051/example.Greeter/SayHello | jq .
169
+ ```
170
+
171
+ Use `--allow-unknown-fields` to ignore unknown JSON input fields,
172
+ `--emit-defaults` to include default-valued output fields, and
173
+ `--max-input-buffer` to limit buffered parser input.
174
+
175
+ ### Raw file input
176
+
177
+ Bind a file to a `bytes` field with `--field:file`; this selects raw input.
178
+ Use repeatable `--field` options for other top-level fields:
179
+
180
+ ```console
181
+ protodial --field filename=video.bin --field:file content=video.bin \
182
+ grpc://localhost:50051/example.Service/Upload
183
+ ```
184
+
185
+ `--field` values are parsed as ProtoJSON values, or as strings if they are not
186
+ valid JSON. `--field:file content=-` reads bytes from stdin.
187
+
188
+ With `--input-format raw` or `--output-format raw`, the bytes field is inferred
189
+ if there is exactly one eligible field. Otherwise, select it with
190
+ `--input-field` or `--output-field`. Client-streaming raw input is split into
191
+ `--chunk-size` byte messages; `--field` values apply to every message.
192
+
193
+ Server streams flush each response as it arrives. Bidirectional calls send and
194
+ receive concurrently. Failed streams preserve partial stdout output and exit
195
+ nonzero. Ctrl-C and a closed output pipe cancel the active RPC. Interactive
196
+ prompting is not supported; use `-d @-` for streaming stdin.
197
+
198
+ ## Metadata and diagnostics
199
+
200
+ ```console
201
+ protodial -H 'authorization:Bearer token' --rpc-header 'x-request-id:1234' \
202
+ -i --show-trailers --show-status \
203
+ grpcs://api.example.com:443/example.Service/Call
204
+ ```
205
+
206
+ `-H` / `--header` metadata is sent to reflection and the application RPC.
207
+ `--reflect-header` and `--rpc-header` add scope-specific metadata without
208
+ replacing duplicate keys. Values for keys ending in `-bin` must be base64.
209
+ `--expand-headers` expands `$NAME` and `${NAME}` in values; `$$` is a literal
210
+ dollar sign. Expansion does not invoke a shell.
211
+
212
+ Diagnostics never share stdout with response data:
213
+
214
+ - `-i` / `--include`: response headers.
215
+ - `--show-trailers`: response trailers.
216
+ - `--show-status`: final status, message counts, and elapsed time.
217
+ - `--format-error`: decode `google.rpc.Status` and its detail messages.
218
+ - `-v`: response diagnostics, structured errors, and resolved method.
219
+ - `-vv`: also include connection, transport, schema, and RPC lifecycle events.
220
+
221
+ Human diagnostics use `*` for status, `>` for request metadata, and `<` for
222
+ response metadata. `--color auto|always|never` controls stderr color. Automatic
223
+ color requires a terminal and honors `NO_COLOR` and `TERM=dumb`.
224
+
225
+ For scripts, use NDJSON diagnostics rather than parsing human output:
226
+
227
+ ```console
228
+ protodial -vv --diagnostic-format json \
229
+ grpc://localhost:50051/example.Greeter/SayHello \
230
+ >responses.ndjson 2>diagnostics.ndjson
231
+ ```
232
+
233
+ `PROTODIAL_DIAGNOSTIC_FORMAT=human|json` sets the default; the CLI option takes
234
+ precedence. See [the diagnostic schema](docs/diagnostics.md) for the
235
+ `protodial.diagnostic.v1` contract.
236
+
237
+ ## TLS and connection controls
238
+
239
+ TLS is enabled by default. Use `--plaintext` or a `grpc://` URL to disable it.
240
+
241
+ | Option | Purpose |
242
+ | --- | --- |
243
+ | `--cacert FILE` | Trust a PEM CA bundle; repeatable |
244
+ | `--cert FILE --key FILE` | Present a client certificate and private key |
245
+ | `--key-password PASSWORD` | Decrypt an encrypted private key |
246
+ | `--servername NAME` | Set TLS SNI and the verified server identity |
247
+ | `--authority NAME` | Set HTTP/2 `:authority` |
248
+ | `-k` / `--insecure` | Disable TLS peer verification; unsafe |
249
+
250
+ `--cert` and `--key` are required together. Certificate options cannot be used
251
+ with `--plaintext`. A key password passed on the command line may be exposed
252
+ in shell history and the process list.
253
+
254
+ `--insecure` uses a local TLS relay and always emits a warning. On Unix, the
255
+ relay uses an owner-only temporary directory and a socket with mode `0600`.
256
+ The loopback TCP fallback on platforms without Unix sockets rejects client
257
+ certificates to prevent other local processes from using that identity.
258
+ Neither mode protects against remote man-in-the-middle attacks.
259
+
260
+ `--connect-timeout` bounds the channel-readiness check before schema discovery
261
+ or invocation. `-m` / `--max-time` sets the RPC deadline in seconds.
262
+ `--wait-for-ready` lets an RPC wait for an available channel within that
263
+ deadline; local-schema calls with this flag skip the readiness preflight.
264
+ Reflection still requires the preflight.
265
+
266
+ `--max-send-message-size` and `--max-recv-message-size` set gRPC message limits.
267
+ `--compression` accepts `none`, `gzip`, or `deflate`. `--keepalive-time` sets the
268
+ HTTP/2 keepalive interval in seconds; aggressive intervals may be rejected by
269
+ servers. `-A` / `--user-agent` sets the primary gRPC user agent.
270
+
271
+ ## Exit codes
272
+
273
+ | Code | Meaning |
274
+ | ---: | --- |
275
+ | `0` | Success |
276
+ | `1` | Dependency, RPC, input, schema, or file error |
277
+ | `2` | Invalid CLI usage |
278
+ | `130` | Interrupted with Ctrl-C |
279
+
280
+ ## Docker
281
+
282
+ ```console
283
+ docker build -t protodial:1.0.0 .
284
+ docker run --rm protodial:1.0.0 --version
285
+ docker run --rm protodial:1.0.0 -d '{"name":"Ada"}' \
286
+ grpc://host.docker.internal:50051/example.Greeter/SayHello
287
+ ```
288
+
289
+ On Docker Desktop, `host.docker.internal` addresses the host. On Linux, use
290
+ `--network host` with a localhost target when appropriate. Mount local schema,
291
+ request, and certificate files into the container; add `-i` when reading stdin.
292
+
293
+ ## Development
294
+
295
+ ```console
296
+ uv sync --dev
297
+ uv run protodial --help
298
+ uv run pytest
299
+ uv run ruff check .
300
+ uv run pyright
301
+ uv build
302
+ ```
303
+
304
+ See [examples](examples/README.md) for runnable servers and calls, and
305
+ [CHANGELOG.md](CHANGELOG.md) for release notes. The CLI is the primary supported
306
+ interface; this README does not define a stable Python library API.
@@ -0,0 +1,276 @@
1
+ # protodial
2
+
3
+ A Python command-line client for gRPC. Call methods, list services, inspect
4
+ protobuf definitions, and generate request templates using server reflection,
5
+ local `.proto` files, or descriptor sets. No generated client code is required.
6
+
7
+ Supports unary, client-streaming, server-streaming, and bidirectional RPCs.
8
+ Requests and responses can use ProtoJSON, textproto, serialized protobuf, or
9
+ raw bytes. Response payloads go to stdout; diagnostics go to stderr.
10
+
11
+ ## Installation
12
+
13
+ Requires Python 3.10 or newer. Install the checkout with
14
+ [uv](https://docs.astral.sh/uv/):
15
+
16
+ ```console
17
+ uv tool install .
18
+ protodial --version
19
+ ```
20
+
21
+ If the package is available on your package index, install version 1.0.0 with
22
+ `uv tool install protodial==1.0.0` or `python -m pip install protodial==1.0.0`.
23
+ You can also run it as `python -m protodial`.
24
+
25
+ ## Quick start
26
+
27
+ Call a server with [reflection](https://grpc.io/docs/guides/reflection/) enabled:
28
+
29
+ ```console
30
+ protodial -d '{"name":"Ada"}' \
31
+ grpc://localhost:50051/example.Greeter/SayHello
32
+ ```
33
+
34
+ The [hello example](examples/README.md) returns compact ProtoJSON:
35
+
36
+ ```json
37
+ {"message":"Hello, Ada!","requestId":"example-123"}
38
+ ```
39
+
40
+ Use `grpc://` for plaintext and `grpcs://` for TLS. Bare targets and gRPC
41
+ resolver URIs use TLS by default. For resolver or Unix-socket targets, specify
42
+ `--method` separately:
43
+
44
+ ```console
45
+ protodial --plaintext --method example.Greeter/SayHello dns:///localhost:50051
46
+ protodial --plaintext --method example.Greeter/SayHello unix:///tmp/example.sock
47
+ ```
48
+
49
+ When reflection is unavailable, provide a schema:
50
+
51
+ ```console
52
+ protodial --proto api.proto --proto-path protos -d @request.json \
53
+ grpcs://api.example.com:443/example.Greeter/SayHello
54
+ ```
55
+
56
+ Invocation options precede the target. Discovery options follow `list` or
57
+ `describe`. Run `protodial --help`, `protodial list --help`, or
58
+ `protodial describe --help` for the full option reference.
59
+
60
+ ## Schema discovery
61
+
62
+ ```console
63
+ # List services, then methods on a service.
64
+ protodial list grpc://localhost:50051
65
+ protodial list grpc://localhost:50051 example.Greeter
66
+
67
+ # Inspect a service, method, message, or field.
68
+ protodial describe grpc://localhost:50051 example.Greeter
69
+ protodial describe grpc://localhost:50051/example.Greeter/SayHello
70
+ protodial describe grpc://localhost:50051 example.HelloRequest.name
71
+
72
+ # Generate a ProtoJSON request template.
73
+ protodial describe --template \
74
+ grpc://localhost:50051/example.Greeter/SayHello
75
+ ```
76
+
77
+ Service names are sorted; methods retain declaration order. Reflection services
78
+ are hidden unless `--show-reflection` is set. Unique short or suffix names are
79
+ accepted; ambiguous names report the qualified candidates.
80
+
81
+ `describe --template` accepts a message or method. For methods, it uses the
82
+ request type. Templates include one repeated item and the first alternative
83
+ of each oneof. `--template-depth` limits nesting (default: 8).
84
+
85
+ ### Local schemas and descriptor sets
86
+
87
+ ```console
88
+ protodial list --proto api.proto --proto-path protos unused.example:443
89
+ protodial describe --protoset api.protoset \
90
+ unused.example:443 example.HelloRequest
91
+ protodial list --protoset-out schema.pb grpc://localhost:50051
92
+ ```
93
+
94
+ - Repeat `--proto` to compile multiple entrypoints or `--protoset` to combine
95
+ binary `FileDescriptorSet` files. Conflicting definitions of the same file
96
+ are rejected.
97
+ - Local schemas disable reflection by default. Add `--use-reflection` to resolve
98
+ missing symbols from the server.
99
+ - Local-only discovery does not contact the target, but still requires a target
100
+ operand.
101
+ - `--protoset-out` atomically exports the resolved schema and its dependencies.
102
+ - Repeat `--proto-path` for import roots. The default reusable schema directory
103
+ is `$XDG_DATA_HOME/protodial/protos`, falling back to
104
+ `$HOME/.local/share/protodial/protos`. It is not created automatically.
105
+
106
+ Invocation URL paths use `/SERVICE/METHOD`. Discovery URLs can contain
107
+ `/SERVICE` for `list`, or `/SYMBOL` or `/SERVICE/METHOD` for `describe`.
108
+ URL credentials, query strings, and fragments are not supported.
109
+
110
+ ## Request and response formats
111
+
112
+ Select encodings independently with `--input-format` and `--output-format`.
113
+ Both default to `json`.
114
+
115
+ | Format | Unary | Streaming |
116
+ | --- | --- | --- |
117
+ | `json` | One ProtoJSON value | Whitespace-separated values; output is NDJSON |
118
+ | `text` | One textproto document | Documents separated by a line containing `---` |
119
+ | `protobuf` | One serialized message | Four-byte unsigned big-endian length, then message bytes |
120
+ | `raw` | One top-level `bytes` field | Input byte chunks; concatenated output bytes |
121
+
122
+ Protobuf file framing is a protodial convention, not gRPC wire framing.
123
+ ProtoJSON `bytes` fields use base64 strings.
124
+
125
+ `-d` / `--data` accepts a literal value, `@path` for a file, or `-` / `@-` for
126
+ stdin. Without `--data`, unary calls send one empty message and client streams
127
+ send no messages, regardless of format.
128
+
129
+ JSON client streams are parsed incrementally. NDJSON, multiline values, and
130
+ consecutive whitespace-separated values are accepted. Responses are compact
131
+ JSON values, one per line:
132
+
133
+ ```console
134
+ printf '%s\n' '{"value":1}' '{"value":2}' | \
135
+ protodial -d @- grpc://localhost:50051/example.Service/Upload
136
+
137
+ protodial -d '{"name":"Ada"}' \
138
+ grpc://localhost:50051/example.Greeter/SayHello | jq .
139
+ ```
140
+
141
+ Use `--allow-unknown-fields` to ignore unknown JSON input fields,
142
+ `--emit-defaults` to include default-valued output fields, and
143
+ `--max-input-buffer` to limit buffered parser input.
144
+
145
+ ### Raw file input
146
+
147
+ Bind a file to a `bytes` field with `--field:file`; this selects raw input.
148
+ Use repeatable `--field` options for other top-level fields:
149
+
150
+ ```console
151
+ protodial --field filename=video.bin --field:file content=video.bin \
152
+ grpc://localhost:50051/example.Service/Upload
153
+ ```
154
+
155
+ `--field` values are parsed as ProtoJSON values, or as strings if they are not
156
+ valid JSON. `--field:file content=-` reads bytes from stdin.
157
+
158
+ With `--input-format raw` or `--output-format raw`, the bytes field is inferred
159
+ if there is exactly one eligible field. Otherwise, select it with
160
+ `--input-field` or `--output-field`. Client-streaming raw input is split into
161
+ `--chunk-size` byte messages; `--field` values apply to every message.
162
+
163
+ Server streams flush each response as it arrives. Bidirectional calls send and
164
+ receive concurrently. Failed streams preserve partial stdout output and exit
165
+ nonzero. Ctrl-C and a closed output pipe cancel the active RPC. Interactive
166
+ prompting is not supported; use `-d @-` for streaming stdin.
167
+
168
+ ## Metadata and diagnostics
169
+
170
+ ```console
171
+ protodial -H 'authorization:Bearer token' --rpc-header 'x-request-id:1234' \
172
+ -i --show-trailers --show-status \
173
+ grpcs://api.example.com:443/example.Service/Call
174
+ ```
175
+
176
+ `-H` / `--header` metadata is sent to reflection and the application RPC.
177
+ `--reflect-header` and `--rpc-header` add scope-specific metadata without
178
+ replacing duplicate keys. Values for keys ending in `-bin` must be base64.
179
+ `--expand-headers` expands `$NAME` and `${NAME}` in values; `$$` is a literal
180
+ dollar sign. Expansion does not invoke a shell.
181
+
182
+ Diagnostics never share stdout with response data:
183
+
184
+ - `-i` / `--include`: response headers.
185
+ - `--show-trailers`: response trailers.
186
+ - `--show-status`: final status, message counts, and elapsed time.
187
+ - `--format-error`: decode `google.rpc.Status` and its detail messages.
188
+ - `-v`: response diagnostics, structured errors, and resolved method.
189
+ - `-vv`: also include connection, transport, schema, and RPC lifecycle events.
190
+
191
+ Human diagnostics use `*` for status, `>` for request metadata, and `<` for
192
+ response metadata. `--color auto|always|never` controls stderr color. Automatic
193
+ color requires a terminal and honors `NO_COLOR` and `TERM=dumb`.
194
+
195
+ For scripts, use NDJSON diagnostics rather than parsing human output:
196
+
197
+ ```console
198
+ protodial -vv --diagnostic-format json \
199
+ grpc://localhost:50051/example.Greeter/SayHello \
200
+ >responses.ndjson 2>diagnostics.ndjson
201
+ ```
202
+
203
+ `PROTODIAL_DIAGNOSTIC_FORMAT=human|json` sets the default; the CLI option takes
204
+ precedence. See [the diagnostic schema](docs/diagnostics.md) for the
205
+ `protodial.diagnostic.v1` contract.
206
+
207
+ ## TLS and connection controls
208
+
209
+ TLS is enabled by default. Use `--plaintext` or a `grpc://` URL to disable it.
210
+
211
+ | Option | Purpose |
212
+ | --- | --- |
213
+ | `--cacert FILE` | Trust a PEM CA bundle; repeatable |
214
+ | `--cert FILE --key FILE` | Present a client certificate and private key |
215
+ | `--key-password PASSWORD` | Decrypt an encrypted private key |
216
+ | `--servername NAME` | Set TLS SNI and the verified server identity |
217
+ | `--authority NAME` | Set HTTP/2 `:authority` |
218
+ | `-k` / `--insecure` | Disable TLS peer verification; unsafe |
219
+
220
+ `--cert` and `--key` are required together. Certificate options cannot be used
221
+ with `--plaintext`. A key password passed on the command line may be exposed
222
+ in shell history and the process list.
223
+
224
+ `--insecure` uses a local TLS relay and always emits a warning. On Unix, the
225
+ relay uses an owner-only temporary directory and a socket with mode `0600`.
226
+ The loopback TCP fallback on platforms without Unix sockets rejects client
227
+ certificates to prevent other local processes from using that identity.
228
+ Neither mode protects against remote man-in-the-middle attacks.
229
+
230
+ `--connect-timeout` bounds the channel-readiness check before schema discovery
231
+ or invocation. `-m` / `--max-time` sets the RPC deadline in seconds.
232
+ `--wait-for-ready` lets an RPC wait for an available channel within that
233
+ deadline; local-schema calls with this flag skip the readiness preflight.
234
+ Reflection still requires the preflight.
235
+
236
+ `--max-send-message-size` and `--max-recv-message-size` set gRPC message limits.
237
+ `--compression` accepts `none`, `gzip`, or `deflate`. `--keepalive-time` sets the
238
+ HTTP/2 keepalive interval in seconds; aggressive intervals may be rejected by
239
+ servers. `-A` / `--user-agent` sets the primary gRPC user agent.
240
+
241
+ ## Exit codes
242
+
243
+ | Code | Meaning |
244
+ | ---: | --- |
245
+ | `0` | Success |
246
+ | `1` | Dependency, RPC, input, schema, or file error |
247
+ | `2` | Invalid CLI usage |
248
+ | `130` | Interrupted with Ctrl-C |
249
+
250
+ ## Docker
251
+
252
+ ```console
253
+ docker build -t protodial:1.0.0 .
254
+ docker run --rm protodial:1.0.0 --version
255
+ docker run --rm protodial:1.0.0 -d '{"name":"Ada"}' \
256
+ grpc://host.docker.internal:50051/example.Greeter/SayHello
257
+ ```
258
+
259
+ On Docker Desktop, `host.docker.internal` addresses the host. On Linux, use
260
+ `--network host` with a localhost target when appropriate. Mount local schema,
261
+ request, and certificate files into the container; add `-i` when reading stdin.
262
+
263
+ ## Development
264
+
265
+ ```console
266
+ uv sync --dev
267
+ uv run protodial --help
268
+ uv run pytest
269
+ uv run ruff check .
270
+ uv run pyright
271
+ uv build
272
+ ```
273
+
274
+ See [examples](examples/README.md) for runnable servers and calls, and
275
+ [CHANGELOG.md](CHANGELOG.md) for release notes. The CLI is the primary supported
276
+ interface; this README does not define a stable Python library API.