chalk-remote-call-python 1.9.2__tar.gz → 1.10.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 (68) hide show
  1. {chalk_remote_call_python-1.9.2/chalk_remote_call_python.egg-info → chalk_remote_call_python-1.10.0}/PKG-INFO +6 -3
  2. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/README.md +5 -2
  3. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/Cargo.lock +1 -1
  4. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-server/Cargo.toml +1 -1
  5. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-server/src/self_consumer.rs +161 -28
  6. chalk_remote_call_python-1.10.0/chalk_remote_call/_version.py +1 -0
  7. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/cli.py +14 -4
  8. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/server.py +8 -7
  9. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0/chalk_remote_call_python.egg-info}/PKG-INFO +6 -3
  10. chalk_remote_call_python-1.9.2/chalk_remote_call/_version.py +0 -1
  11. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/MANIFEST.in +0 -0
  12. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/Cargo.toml +0 -0
  13. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-proto/Cargo.toml +0 -0
  14. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.auth.v1.rs +0 -0
  15. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.common.v1.rs +0 -0
  16. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.runtime.v1.rs +0 -0
  17. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.runtime.v1.tonic.rs +0 -0
  18. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.utils.v1.rs +0 -0
  19. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/descriptor.bin +0 -0
  20. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/lib.rs +0 -0
  21. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-server/src/async_service.rs +0 -0
  22. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-server/src/coalesce.rs +0 -0
  23. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-server/src/lib.rs +0 -0
  24. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-server/src/metrics.rs +0 -0
  25. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-server/src/python_bridge.rs +0 -0
  26. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-server/src/server.rs +0 -0
  27. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/chalk-remote-call-server/src/service.rs +0 -0
  28. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk-remote-call-rs/rust-toolchain.toml +0 -0
  29. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/__init__.py +0 -0
  30. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/__main__.py +0 -0
  31. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/__init__.py +0 -0
  32. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/__init__.py +0 -0
  33. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/auth/__init__.py +0 -0
  34. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/auth/v1/__init__.py +0 -0
  35. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/auth/v1/permissions_pb2.py +0 -0
  36. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/auth/v1/permissions_pb2_grpc.py +0 -0
  37. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/common/__init__.py +0 -0
  38. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/common/v1/__init__.py +0 -0
  39. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/common/v1/chalk_error_pb2.py +0 -0
  40. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/common/v1/chalk_error_pb2_grpc.py +0 -0
  41. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/runtime/__init__.py +0 -0
  42. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/runtime/v1/__init__.py +0 -0
  43. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/runtime/v1/remote_python_call_pb2.py +0 -0
  44. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/runtime/v1/remote_python_call_pb2_grpc.py +0 -0
  45. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/utils/__init__.py +0 -0
  46. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/utils/v1/__init__.py +0 -0
  47. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/utils/v1/encoding_pb2.py +0 -0
  48. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/utils/v1/encoding_pb2_grpc.py +0 -0
  49. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/utils/v1/field_change_pb2.py +0 -0
  50. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/utils/v1/field_change_pb2_grpc.py +0 -0
  51. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/utils/v1/sensitive_pb2.py +0 -0
  52. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_gen/chalk/utils/v1/sensitive_pb2_grpc.py +0 -0
  53. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/_native.pyi +0 -0
  54. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/arrow_utils.py +0 -0
  55. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/errors.py +0 -0
  56. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/handler_loader.py +0 -0
  57. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/input_transform.py +0 -0
  58. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/logging.py +0 -0
  59. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/servicer.py +0 -0
  60. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call/tracing.py +0 -0
  61. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call_python.egg-info/SOURCES.txt +0 -0
  62. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call_python.egg-info/dependency_links.txt +0 -0
  63. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call_python.egg-info/entry_points.txt +0 -0
  64. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call_python.egg-info/requires.txt +0 -0
  65. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/chalk_remote_call_python.egg-info/top_level.txt +0 -0
  66. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/pyproject.toml +0 -0
  67. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/setup.cfg +0 -0
  68. {chalk_remote_call_python-1.9.2 → chalk_remote_call_python-1.10.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chalk-remote-call-python
3
- Version: 1.9.2
3
+ Version: 1.10.0
4
4
  Summary: Chalk remote call Python runtime interface client
5
5
  Author: Chalk AI, Inc.
6
6
  Project-URL: Homepage, https://chalk.ai
@@ -110,8 +110,11 @@ python -m chalk_remote_call --handler my_handler.handler
110
110
 
111
111
  The CLI writes Python logs to stderr as one JSON object per line, with separate
112
112
  `timestamp`, `severity`, `logger`, and `message` fields. Exceptions include an
113
- `exception.stacktrace` field. Calls to `serve()` from Python retain the embedding
114
- application's logging configuration.
113
+ `exception.stacktrace` field, which the log collector uses for the traceback.
114
+ CLI logging is configured before importing runtime dependencies. Import failures,
115
+ invalid environment configuration, and exceptions escaping startup or the server
116
+ emit a single ERROR record with the full traceback and exit with status 1.
117
+ Calls to `serve()` from Python retain the embedding application's logging configuration.
115
118
 
116
119
  Missing optional StatsD endpoints or a metrics-bus topic do not emit warnings.
117
120
  Failures initializing configured metrics sinks still warn. Routine customer
@@ -72,8 +72,11 @@ python -m chalk_remote_call --handler my_handler.handler
72
72
 
73
73
  The CLI writes Python logs to stderr as one JSON object per line, with separate
74
74
  `timestamp`, `severity`, `logger`, and `message` fields. Exceptions include an
75
- `exception.stacktrace` field. Calls to `serve()` from Python retain the embedding
76
- application's logging configuration.
75
+ `exception.stacktrace` field, which the log collector uses for the traceback.
76
+ CLI logging is configured before importing runtime dependencies. Import failures,
77
+ invalid environment configuration, and exceptions escaping startup or the server
78
+ emit a single ERROR record with the full traceback and exit with status 1.
79
+ Calls to `serve()` from Python retain the embedding application's logging configuration.
77
80
 
78
81
  Missing optional StatsD endpoints or a metrics-bus topic do not emit warnings.
79
82
  Failures initializing configured metrics sinks still warn. Routine customer
@@ -511,7 +511,7 @@ dependencies = [
511
511
  [[package]]
512
512
  name = "chalk-queue"
513
513
  version = "0.0.0"
514
- source = "git+https://github.com/chalk-ai/chalk-private.git?rev=9c61f6a938d8f885f138217ac64149e29d7a691e#9c61f6a938d8f885f138217ac64149e29d7a691e"
514
+ source = "git+https://github.com/chalk-ai/chalk-private.git?rev=c6eed6bd7bdad1f2a7568ef206e570c719c4c64f#c6eed6bd7bdad1f2a7568ef206e570c719c4c64f"
515
515
  dependencies = [
516
516
  "base64 0.22.1",
517
517
  "redis",
@@ -37,6 +37,6 @@ futures-util = "0.3"
37
37
  # `[patch.crates-io]` here it builds against vanilla crates.io redis (it only
38
38
  # uses standard APIs). Metrics and StatsD share a revision so their client types agree.
39
39
  [target.'cfg(target_os = "linux")'.dependencies]
40
- chalk-queue = { git = "https://github.com/chalk-ai/chalk-private.git", rev = "9c61f6a938d8f885f138217ac64149e29d7a691e" }
40
+ chalk-queue = { git = "https://github.com/chalk-ai/chalk-private.git", rev = "c6eed6bd7bdad1f2a7568ef206e570c719c4c64f" }
41
41
  chalk_metrics = { git = "https://github.com/chalk-ai/chalk-private.git", rev = "a0f8932ec10d3b8340af3e98cf63b800d60b4926" }
42
42
  chalk_statsd = { git = "https://github.com/chalk-ai/chalk-private.git", rev = "a0f8932ec10d3b8340af3e98cf63b800d60b4926" }
@@ -102,6 +102,112 @@ const STREAM_RECLAIM_INTERVAL: Duration = Duration::from_secs(10);
102
102
  /// inbound network peer on this path.
103
103
  const PEER: &str = "fnq-self-consumer";
104
104
 
105
+ /// Selects where a queued call publishes its result without allowing a shared
106
+ /// batch stream to lose the call identity needed by the collector.
107
+ #[derive(Debug, PartialEq, Eq)]
108
+ enum ResultDestination {
109
+ PerCall {
110
+ stream_key: String,
111
+ },
112
+ Batch {
113
+ stream_key: String,
114
+ call_id: String,
115
+ row_index: u64,
116
+ },
117
+ }
118
+
119
+ impl ResultDestination {
120
+ fn for_call(queue: &FunctionQueue, work: &QueuedCall, call_uuid: Uuid) -> Self {
121
+ let batch = work.batch_result.as_ref().map(|batch_result| {
122
+ (
123
+ queue.batch_stream_key_for(&batch_result.batch_id),
124
+ work.call_id.clone(),
125
+ batch_result.row_index,
126
+ )
127
+ });
128
+ Self::from_routing(queue.stream_key_for(call_uuid), batch)
129
+ }
130
+
131
+ fn from_routing(per_call_stream_key: String, batch: Option<(String, String, u64)>) -> Self {
132
+ match batch {
133
+ Some((stream_key, call_id, row_index)) => Self::Batch {
134
+ stream_key,
135
+ call_id,
136
+ row_index,
137
+ },
138
+ None => Self::PerCall {
139
+ stream_key: per_call_stream_key,
140
+ },
141
+ }
142
+ }
143
+
144
+ async fn write_chunk(
145
+ &self,
146
+ conn: &mut ConnectionManager,
147
+ seq: u64,
148
+ data: &[u8],
149
+ ) -> Result<(), redis::RedisError> {
150
+ match self {
151
+ Self::PerCall { stream_key } => {
152
+ stream_writer::write_chunk(conn, stream_key, seq, data).await
153
+ }
154
+ Self::Batch {
155
+ stream_key,
156
+ call_id,
157
+ row_index,
158
+ } => {
159
+ stream_writer::BatchStreamWriter::new(stream_key, call_id, *row_index)
160
+ .write_chunk(conn, seq, data)
161
+ .await
162
+ }
163
+ }
164
+ }
165
+
166
+ async fn write_end(
167
+ &self,
168
+ conn: &mut ConnectionManager,
169
+ seq: u64,
170
+ ) -> Result<(), redis::RedisError> {
171
+ match self {
172
+ Self::PerCall { stream_key } => stream_writer::write_end(conn, stream_key, seq).await,
173
+ Self::Batch {
174
+ stream_key,
175
+ call_id,
176
+ row_index,
177
+ } => {
178
+ stream_writer::BatchStreamWriter::new(stream_key, call_id, *row_index)
179
+ .write_end(conn, seq)
180
+ .await
181
+ }
182
+ }
183
+ }
184
+
185
+ async fn write_error(
186
+ &self,
187
+ conn: &mut ConnectionManager,
188
+ seq: u64,
189
+ exc_type: &str,
190
+ message: &str,
191
+ traceback: Option<&str>,
192
+ ) -> Result<(), redis::RedisError> {
193
+ match self {
194
+ Self::PerCall { stream_key } => {
195
+ stream_writer::write_error(conn, stream_key, seq, exc_type, message, traceback)
196
+ .await
197
+ }
198
+ Self::Batch {
199
+ stream_key,
200
+ call_id,
201
+ row_index,
202
+ } => {
203
+ stream_writer::BatchStreamWriter::new(stream_key, call_id, *row_index)
204
+ .write_error(conn, seq, exc_type, message, traceback)
205
+ .await
206
+ }
207
+ }
208
+ }
209
+ }
210
+
105
211
  /// Configuration for the self-consumer, read from the environment.
106
212
  ///
107
213
  /// [`from_env`](Self::from_env) returns `None` when `CHALK_FNQ_REDIS_URL` is
@@ -694,7 +800,7 @@ async fn coalesced_execute_and_write(
694
800
  metrics_pipeline: Option<&Arc<PublishingMetricsPipeline>>,
695
801
  ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
696
802
  let call_uuid = Uuid::parse_str(&work.call_id)?;
697
- let stream_key = queue.stream_key_for(call_uuid);
803
+ let result_destination = ResultDestination::for_call(queue, &work, call_uuid);
698
804
  let ipc_bytes = work.call.feather_bytes()?;
699
805
  let function_name = work.call.name.clone();
700
806
  let mut metadata = work.metadata.otel_headers.clone();
@@ -736,8 +842,10 @@ async fn coalesced_execute_and_write(
736
842
 
737
843
  match response {
738
844
  Some(Ok(resp)) => {
739
- stream_writer::write_chunk(conn, &stream_key, 1, &resp.feather_stream).await?;
740
- stream_writer::write_end(conn, &stream_key, 2).await?;
845
+ result_destination
846
+ .write_chunk(conn, 1, &resp.feather_stream)
847
+ .await?;
848
+ result_destination.write_end(conn, 2).await?;
741
849
  let _ = queue
742
850
  .complete_status(
743
851
  call_uuid,
@@ -751,15 +859,9 @@ async fn coalesced_execute_and_write(
751
859
  Some(Err(status)) => {
752
860
  // Parity with the enqueue/poll path: only a message string is
753
861
  // available, so exc_type is a fixed placeholder.
754
- stream_writer::write_error(
755
- conn,
756
- &stream_key,
757
- 1,
758
- "RemoteFunctionError",
759
- status.message(),
760
- None,
761
- )
762
- .await?;
862
+ result_destination
863
+ .write_error(conn, 1, "RemoteFunctionError", status.message(), None)
864
+ .await?;
763
865
  let summary: String = status.message().chars().take(200).collect();
764
866
  let _ = queue
765
867
  .complete_status(call_uuid, "failed", &summary, Some(processing_trace_id))
@@ -771,7 +873,8 @@ async fn coalesced_execute_and_write(
771
873
  // happen (it always sends one Ok/Err per caller), but treat a
772
874
  // missing response as a failure rather than leaving the poller hung.
773
875
  let msg = "coalesced dispatch produced no response";
774
- stream_writer::write_error(conn, &stream_key, 1, "RemoteFunctionError", msg, None)
876
+ result_destination
877
+ .write_error(conn, 1, "RemoteFunctionError", msg, None)
775
878
  .await?;
776
879
  let _ = queue
777
880
  .complete_status(call_uuid, "failed", msg, Some(processing_trace_id))
@@ -810,7 +913,7 @@ async fn execute_and_write(
810
913
  metrics_pipeline: Option<&Arc<PublishingMetricsPipeline>>,
811
914
  ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
812
915
  let call_uuid = Uuid::parse_str(&work.call_id)?;
813
- let stream_key = queue.stream_key_for(call_uuid);
916
+ let result_destination = ResultDestination::for_call(queue, &work, call_uuid);
814
917
  let ipc_bytes = work.call.feather_bytes()?;
815
918
  let function_name = work.call.name.clone();
816
919
  let mut metadata = work.metadata.otel_headers.clone();
@@ -857,7 +960,7 @@ async fn execute_and_write(
857
960
  biased;
858
961
  res = &mut handler_fut => break res,
859
962
  _ = tokio::time::timeout(flush_timeout, notify_handle.notified()) => {
860
- if let Err(e) = flush_new(conn, &stream_key, &buffer, &mut flushed, &mut seq).await {
963
+ if let Err(e) = flush_new(conn, &result_destination, &buffer, &mut flushed, &mut seq).await {
861
964
  // Non-fatal: keep draining; the cursor didn't advance past the
862
965
  // failed chunk, so the next wait retries it without duplicating.
863
966
  warn!(call_id = %work.call_id, error = %e, "failed to flush partial results; retrying");
@@ -883,13 +986,13 @@ async fn execute_and_write(
883
986
  // this is everything). This happens even if the handler panicked mid-execution,
884
987
  // so partial output is always preserved. Parity with the enqueue/poll path: a
885
988
  // generator that yields then raises surfaces its chunks followed by the error.
886
- if let Err(e) = flush_new(conn, &stream_key, &buffer, &mut flushed, &mut seq).await {
989
+ if let Err(e) = flush_new(conn, &result_destination, &buffer, &mut flushed, &mut seq).await {
887
990
  warn!(call_id = %work.call_id, error = %e, "failed to flush final chunks");
888
991
  }
889
992
 
890
993
  match result {
891
994
  Ok(()) => {
892
- stream_writer::write_end(conn, &stream_key, seq).await?;
995
+ result_destination.write_end(conn, seq).await?;
893
996
  let _ = queue
894
997
  .complete_status(
895
998
  call_uuid,
@@ -901,15 +1004,15 @@ async fn execute_and_write(
901
1004
  info!(call_id = %work.call_id, chunks = flushed, "queued call completed");
902
1005
  }
903
1006
  Err(e) => {
904
- stream_writer::write_error(
905
- conn,
906
- &stream_key,
907
- seq,
908
- "RemoteFunctionError",
909
- &e.to_string(),
910
- Some(e.details()),
911
- )
912
- .await?;
1007
+ result_destination
1008
+ .write_error(
1009
+ conn,
1010
+ seq,
1011
+ "RemoteFunctionError",
1012
+ &e.to_string(),
1013
+ Some(e.details()),
1014
+ )
1015
+ .await?;
913
1016
  let summary: String = e.to_string().chars().take(200).collect();
914
1017
  let _ = queue
915
1018
  .complete_status(call_uuid, "failed", &summary, Some(processing_trace_id))
@@ -932,7 +1035,7 @@ async fn execute_and_write(
932
1035
  /// failed chunk, so a retry re-sends only the unwritten tail (no gaps, no dupes).
933
1036
  async fn flush_new(
934
1037
  conn: &mut ConnectionManager,
935
- stream_key: &str,
1038
+ result_destination: &ResultDestination,
936
1039
  buffer: &Arc<Mutex<ChunkBuffer>>,
937
1040
  flushed: &mut usize,
938
1041
  seq: &mut u64,
@@ -947,7 +1050,7 @@ async fn flush_new(
947
1050
  .collect()
948
1051
  };
949
1052
  for chunk in &new {
950
- stream_writer::write_chunk(conn, stream_key, *seq, chunk).await?;
1053
+ result_destination.write_chunk(conn, *seq, chunk).await?;
951
1054
  *seq += 1;
952
1055
  *flushed += 1;
953
1056
  }
@@ -1022,6 +1125,35 @@ mod tests {
1022
1125
  assert_eq!(DEFAULT_FLUSH_TIMEOUT_MS, 100);
1023
1126
  }
1024
1127
 
1128
+ #[test]
1129
+ fn result_destination_preserves_per_call_stream() {
1130
+ assert_eq!(
1131
+ ResultDestination::from_routing("tenant:gen:call-1:stream".to_string(), None),
1132
+ ResultDestination::PerCall {
1133
+ stream_key: "tenant:gen:call-1:stream".to_string(),
1134
+ }
1135
+ );
1136
+ }
1137
+
1138
+ #[test]
1139
+ fn result_destination_routes_batch_call_with_correlation() {
1140
+ assert_eq!(
1141
+ ResultDestination::from_routing(
1142
+ "unused-per-call-stream".to_string(),
1143
+ Some((
1144
+ "tenant:batch:batch-1:results".to_string(),
1145
+ "call-1".to_string(),
1146
+ 42,
1147
+ )),
1148
+ ),
1149
+ ResultDestination::Batch {
1150
+ stream_key: "tenant:batch:batch-1:results".to_string(),
1151
+ call_id: "call-1".to_string(),
1152
+ row_index: 42,
1153
+ }
1154
+ );
1155
+ }
1156
+
1025
1157
  #[test]
1026
1158
  fn test_config_from_env_returns_none_when_redis_url_unset() {
1027
1159
  let _env = lock_env();
@@ -1043,6 +1175,7 @@ mod tests {
1043
1175
  std::env::remove_var(ENV_FLUSH_TIMEOUT_MS);
1044
1176
  std::env::remove_var(ENV_QUEUE_PROTOCOL);
1045
1177
  std::env::remove_var(ENV_STREAM_READ_COUNT);
1178
+ std::env::remove_var(ENV_MAX_CONCURRENT);
1046
1179
 
1047
1180
  let config = SelfConsumerConfig::from_env().expect("config should parse");
1048
1181
  assert_eq!(config.sem_ttl, Duration::from_secs(DEFAULT_SEM_TTL_SECS));
@@ -0,0 +1 @@
1
+ __version__ = "1.10.0"
@@ -6,7 +6,6 @@ import os
6
6
  import sys
7
7
 
8
8
  from chalk_remote_call.handler_loader import load_function, load_handler
9
- from chalk_remote_call.input_transform import parse_input_args
10
9
  from chalk_remote_call.logging import JsonFormatter
11
10
  from chalk_remote_call.server import serve
12
11
 
@@ -23,6 +22,17 @@ def _env_int(name: str) -> int | None:
23
22
 
24
23
 
25
24
  def main() -> None:
25
+ log_handler = logging.StreamHandler(sys.stderr)
26
+ log_handler.setFormatter(JsonFormatter())
27
+ logging.basicConfig(level=logging.INFO, handlers=[log_handler], force=True)
28
+ try:
29
+ _run()
30
+ except Exception:
31
+ logging.getLogger(__name__).exception("Remote-call server failed")
32
+ sys.exit(1)
33
+
34
+
35
+ def _run() -> None:
26
36
  parser = argparse.ArgumentParser(
27
37
  prog="chalk-remote-call",
28
38
  description="Start a gRPC server implementing the Chalk RemoteCallService.",
@@ -100,9 +110,9 @@ def main() -> None:
100
110
  )
101
111
  args = parser.parse_args()
102
112
 
103
- log_handler = logging.StreamHandler(sys.stderr)
104
- log_handler.setFormatter(JsonFormatter())
105
- logging.basicConfig(level=getattr(logging, args.log_level), handlers=[log_handler])
113
+ logging.getLogger().setLevel(args.log_level)
114
+
115
+ from chalk_remote_call.input_transform import parse_input_args
106
116
 
107
117
  # Load handler
108
118
  try:
@@ -5,13 +5,6 @@ import os
5
5
  from collections.abc import Callable
6
6
  from typing import Any, Literal
7
7
 
8
- from chalk_remote_call._native import start_server
9
- from chalk_remote_call.servicer import (
10
- process_batches,
11
- process_batches_coalesced,
12
- process_batches_coalesced_combined,
13
- )
14
-
15
8
  logger = logging.getLogger(__name__)
16
9
 
17
10
  _BATCHING_MODE_ENV = "CHALK_REMOTE_CALL_BATCHING_MODE"
@@ -75,6 +68,14 @@ def serve(
75
68
  CHALK_REMOTE_CALL_BATCHING_MODE env var when set; otherwise the
76
69
  env var (or "per_caller") wins.
77
70
  """
71
+ # Keep package imports lightweight so the CLI can configure logging before dependency failures.
72
+ from chalk_remote_call._native import start_server
73
+ from chalk_remote_call.servicer import (
74
+ process_batches,
75
+ process_batches_coalesced,
76
+ process_batches_coalesced_combined,
77
+ )
78
+
78
79
  if on_startup is not None:
79
80
  logger.info("Running startup hook...")
80
81
  on_startup()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chalk-remote-call-python
3
- Version: 1.9.2
3
+ Version: 1.10.0
4
4
  Summary: Chalk remote call Python runtime interface client
5
5
  Author: Chalk AI, Inc.
6
6
  Project-URL: Homepage, https://chalk.ai
@@ -110,8 +110,11 @@ python -m chalk_remote_call --handler my_handler.handler
110
110
 
111
111
  The CLI writes Python logs to stderr as one JSON object per line, with separate
112
112
  `timestamp`, `severity`, `logger`, and `message` fields. Exceptions include an
113
- `exception.stacktrace` field. Calls to `serve()` from Python retain the embedding
114
- application's logging configuration.
113
+ `exception.stacktrace` field, which the log collector uses for the traceback.
114
+ CLI logging is configured before importing runtime dependencies. Import failures,
115
+ invalid environment configuration, and exceptions escaping startup or the server
116
+ emit a single ERROR record with the full traceback and exit with status 1.
117
+ Calls to `serve()` from Python retain the embedding application's logging configuration.
115
118
 
116
119
  Missing optional StatsD endpoints or a metrics-bus topic do not emit warnings.
117
120
  Failures initializing configured metrics sinks still warn. Routine customer
@@ -1 +0,0 @@
1
- __version__ = "1.9.2"