chalk-remote-call-python 1.4.0__tar.gz → 1.5.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 (62) hide show
  1. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/PKG-INFO +1 -1
  2. chalk_remote_call_python-1.5.0/chalk_remote_call/_version.py +1 -0
  3. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/cli.py +12 -0
  4. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/server.py +44 -5
  5. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/servicer.py +111 -0
  6. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call_python.egg-info/PKG-INFO +1 -1
  7. chalk_remote_call_python-1.4.0/chalk_remote_call/_version.py +0 -1
  8. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/MANIFEST.in +0 -0
  9. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/README.md +0 -0
  10. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/Cargo.lock +0 -0
  11. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/Cargo.toml +0 -0
  12. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-proto/Cargo.toml +0 -0
  13. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.auth.v1.rs +0 -0
  14. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.common.v1.rs +0 -0
  15. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.runtime.v1.rs +0 -0
  16. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.runtime.v1.tonic.rs +0 -0
  17. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/chalk.utils.v1.rs +0 -0
  18. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/gen/descriptor.bin +0 -0
  19. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-proto/src/lib.rs +0 -0
  20. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-server/Cargo.toml +0 -0
  21. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-server/src/coalesce.rs +0 -0
  22. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-server/src/lib.rs +0 -0
  23. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-server/src/python_bridge.rs +0 -0
  24. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-server/src/server.rs +0 -0
  25. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/chalk-remote-call-server/src/service.rs +0 -0
  26. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk-remote-call-rs/rust-toolchain.toml +0 -0
  27. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/__init__.py +0 -0
  28. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/__main__.py +0 -0
  29. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/__init__.py +0 -0
  30. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/__init__.py +0 -0
  31. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/auth/__init__.py +0 -0
  32. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/auth/v1/__init__.py +0 -0
  33. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/auth/v1/permissions_pb2.py +0 -0
  34. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/auth/v1/permissions_pb2_grpc.py +0 -0
  35. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/common/__init__.py +0 -0
  36. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/common/v1/__init__.py +0 -0
  37. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/common/v1/chalk_error_pb2.py +0 -0
  38. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/common/v1/chalk_error_pb2_grpc.py +0 -0
  39. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/runtime/__init__.py +0 -0
  40. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/runtime/v1/__init__.py +0 -0
  41. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/runtime/v1/remote_python_call_pb2.py +0 -0
  42. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/runtime/v1/remote_python_call_pb2_grpc.py +0 -0
  43. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/utils/__init__.py +0 -0
  44. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/utils/v1/__init__.py +0 -0
  45. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/utils/v1/encoding_pb2.py +0 -0
  46. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/utils/v1/encoding_pb2_grpc.py +0 -0
  47. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/utils/v1/field_change_pb2.py +0 -0
  48. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/utils/v1/field_change_pb2_grpc.py +0 -0
  49. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/utils/v1/sensitive_pb2.py +0 -0
  50. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_gen/chalk/utils/v1/sensitive_pb2_grpc.py +0 -0
  51. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/_native.pyi +0 -0
  52. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/arrow_utils.py +0 -0
  53. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/handler_loader.py +0 -0
  54. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call/input_transform.py +0 -0
  55. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call_python.egg-info/SOURCES.txt +0 -0
  56. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call_python.egg-info/dependency_links.txt +0 -0
  57. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call_python.egg-info/entry_points.txt +0 -0
  58. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call_python.egg-info/requires.txt +0 -0
  59. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/chalk_remote_call_python.egg-info/top_level.txt +0 -0
  60. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/pyproject.toml +0 -0
  61. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.0}/setup.cfg +0 -0
  62. {chalk_remote_call_python-1.4.0 → chalk_remote_call_python-1.5.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.4.0
3
+ Version: 1.5.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
@@ -0,0 +1 @@
1
+ __version__ = "1.5.0"
@@ -80,6 +80,17 @@ def main() -> None:
80
80
  "(env: CHALK_REMOTE_CALL_MAX_BUFFER_DURATION_MS)"
81
81
  ),
82
82
  )
83
+ parser.add_argument(
84
+ "--batching-mode",
85
+ choices=["per_caller", "combined"],
86
+ default=os.environ.get("CHALK_REMOTE_CALL_BATCHING_MODE") or None,
87
+ help=(
88
+ "Coalescing variant when --max-batching-size is set. "
89
+ "'per_caller' (default) passes events as a list of dicts; "
90
+ "'combined' passes a single concatenated RecordBatch with "
91
+ "offsets. (env: CHALK_REMOTE_CALL_BATCHING_MODE)"
92
+ ),
93
+ )
83
94
  parser.add_argument(
84
95
  "--log-level",
85
96
  default="INFO",
@@ -135,4 +146,5 @@ def main() -> None:
135
146
  arg_names=arg_names,
136
147
  max_batching_size=args.max_batching_size,
137
148
  max_buffer_duration_ms=args.max_buffer_duration_ms,
149
+ batching_mode=args.batching_mode,
138
150
  )
@@ -1,14 +1,38 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ import os
4
5
  from collections.abc import Callable
5
- from typing import Any
6
+ from typing import Any, Literal
6
7
 
7
8
  from chalk_remote_call._native import start_server
8
- from chalk_remote_call.servicer import process_batches, process_batches_coalesced
9
+ from chalk_remote_call.servicer import (
10
+ process_batches,
11
+ process_batches_coalesced,
12
+ process_batches_coalesced_combined,
13
+ )
9
14
 
10
15
  logger = logging.getLogger(__name__)
11
16
 
17
+ _BATCHING_MODE_ENV = "CHALK_REMOTE_CALL_BATCHING_MODE"
18
+ _VALID_BATCHING_MODES = ("per_caller", "combined")
19
+
20
+
21
+ def _resolve_batching_mode(kwarg: str | None) -> str:
22
+ """Resolve batching mode from kwarg or env var.
23
+
24
+ Precedence: explicit kwarg > env var > default "per_caller". The env var
25
+ is the canonical opt-in for combined mode; the kwarg exists so embedders
26
+ can override programmatically.
27
+ """
28
+ if kwarg is not None:
29
+ mode = kwarg
30
+ else:
31
+ mode = (os.environ.get(_BATCHING_MODE_ENV, "") or "per_caller").strip() or "per_caller"
32
+ if mode not in _VALID_BATCHING_MODES:
33
+ raise ValueError(f"batching_mode must be one of {_VALID_BATCHING_MODES}, got {mode!r}")
34
+ return mode
35
+
12
36
 
13
37
  def serve(
14
38
  handler: Callable[..., Any],
@@ -20,15 +44,21 @@ def serve(
20
44
  arg_names: list[str] | None = None,
21
45
  max_batching_size: int | None = None,
22
46
  max_buffer_duration_ms: int | None = None,
47
+ batching_mode: Literal["per_caller", "combined"] | None = None,
23
48
  ) -> None:
24
49
  """Start the gRPC server implementing RemoteCallService.
25
50
 
26
51
  Args:
27
52
  handler: The user's handler function.
28
53
  Single-request mode (default): (event, context) -> result.
29
- Coalesced mode (when max_batching_size is set):
54
+ Coalesced per-caller mode (when max_batching_size is set and
55
+ batching_mode == "per_caller"):
30
56
  (events: list[dict], contexts: list[dict]) -> list[result],
31
57
  one result per input.
58
+ Coalesced combined mode (when max_batching_size is set and
59
+ batching_mode == "combined"):
60
+ (combined: pa.RecordBatch, offsets: list[int], contexts: list[dict])
61
+ -> pa.RecordBatch with combined.num_rows rows.
32
62
  host: The host to bind to.
33
63
  port: The port to bind to.
34
64
  workers: Number of worker threads for the tokio runtime.
@@ -40,21 +70,30 @@ def serve(
40
70
  max_buffer_duration_ms: Max time in milliseconds to buffer incoming
41
71
  requests before flushing even if max_batching_size hasn't been
42
72
  reached. Defaults to 1000ms when batching is enabled.
73
+ batching_mode: "per_caller" (default) or "combined". Only effective
74
+ when max_batching_size > 0. Overrides the
75
+ CHALK_REMOTE_CALL_BATCHING_MODE env var when set; otherwise the
76
+ env var (or "per_caller") wins.
43
77
  """
44
78
  if on_startup is not None:
45
79
  logger.info("Running startup hook...")
46
80
  on_startup()
47
81
 
82
+ mode = _resolve_batching_mode(batching_mode)
83
+
48
84
  if max_batching_size is not None and max_batching_size > 0:
49
- process_fn = process_batches_coalesced
85
+ process_fn = process_batches_coalesced_combined if mode == "combined" else process_batches_coalesced
50
86
  batch_size = max_batching_size
51
87
  duration_ms = max_buffer_duration_ms if max_buffer_duration_ms is not None else 1000
52
88
  logger.info(
53
- "Batching enabled: max_batching_size=%d max_buffer_duration_ms=%d",
89
+ "Batching enabled: mode=%s max_batching_size=%d max_buffer_duration_ms=%d",
90
+ mode,
54
91
  batch_size,
55
92
  duration_ms,
56
93
  )
57
94
  else:
95
+ if mode == "combined":
96
+ raise ValueError("batching_mode='combined' requires max_batching_size > 0")
58
97
  process_fn = process_batches
59
98
  batch_size = 0
60
99
  duration_ms = 0
@@ -295,3 +295,114 @@ def process_batches_coalesced(
295
295
  raise RuntimeError(f"Failed to encode response for caller {i}: {e}") from e
296
296
 
297
297
  return output
298
+
299
+
300
+ def process_batches_coalesced_combined(
301
+ items_ipc_bytes: list[bytes],
302
+ handler: Callable[..., Any],
303
+ arg_names: list[str] | None,
304
+ context_metadatas: list[dict[str, Any]],
305
+ ) -> list[bytes]:
306
+ """Coalesced bridge, combined-RecordBatch variant.
307
+
308
+ Vertically concatenates every caller's batches into one RecordBatch,
309
+ builds a CSR-style offsets array marking caller boundaries, and invokes
310
+ the handler once. The handler returns one RecordBatch with row count
311
+ equal to the combined input; the framework slices it back into per-caller
312
+ responses (zero-copy on Arrow buffers).
313
+
314
+ Handler signature:
315
+ (combined: pa.RecordBatch, offsets: list[int], contexts: list[dict])
316
+ -> pa.RecordBatch
317
+
318
+ `offsets` has length `len(contexts) + 1`; caller `i` owns rows
319
+ `[offsets[i] : offsets[i+1])`.
320
+
321
+ Raises:
322
+ ValueError: empty input, length mismatch, schema mismatch across
323
+ callers, arg_names length mismatch, or handler output row count
324
+ doesn't match combined input.
325
+ TypeError: handler is a generator / async generator (not supported
326
+ in coalesced modes — each caller expects exactly one response
327
+ chunk).
328
+ """
329
+ if not items_ipc_bytes:
330
+ raise ValueError("process_batches_coalesced_combined called with no items")
331
+ if len(items_ipc_bytes) != len(context_metadatas):
332
+ raise ValueError(
333
+ f"items_ipc_bytes ({len(items_ipc_bytes)}) and context_metadatas "
334
+ f"({len(context_metadatas)}) lengths must match"
335
+ )
336
+ if inspect.isgeneratorfunction(handler) or inspect.isasyncgenfunction(handler):
337
+ raise TypeError("Generator/async-generator handlers are not supported when batching is enabled")
338
+
339
+ per_caller: list[pa.RecordBatch] = []
340
+ reference_schema: pa.Schema | None = None
341
+ for i, ipc_bytes in enumerate(items_ipc_bytes):
342
+ batches = decode_ipc_stream(ipc_bytes)
343
+ if not batches:
344
+ raise ValueError(f"Caller {i} produced no batches")
345
+ rb = _concat_batches(batches)
346
+ if reference_schema is None:
347
+ reference_schema = rb.schema
348
+ elif not rb.schema.equals(reference_schema):
349
+ raise ValueError(
350
+ f"Schema mismatch in coalesced batch: caller 0 has {reference_schema}, caller {i} has {rb.schema}"
351
+ )
352
+ per_caller.append(rb)
353
+
354
+ assert reference_schema is not None # guaranteed by the empty-input check above
355
+
356
+ offsets: list[int] = [0]
357
+ for rb in per_caller:
358
+ offsets.append(offsets[-1] + rb.num_rows)
359
+
360
+ table = pa.Table.from_batches(per_caller)
361
+ combined_batches = table.combine_chunks().to_batches()
362
+ if combined_batches:
363
+ combined = combined_batches[0]
364
+ else:
365
+ combined = pa.RecordBatch.from_pydict(
366
+ {name: [] for name in reference_schema.names},
367
+ schema=reference_schema,
368
+ )
369
+
370
+ if arg_names is not None:
371
+ if len(arg_names) != combined.num_columns:
372
+ raise ValueError(
373
+ f"CHALK_INPUT_ARGS specifies {len(arg_names)} names but "
374
+ f"combined batch has {combined.num_columns} columns"
375
+ )
376
+ combined = combined.rename_columns(arg_names)
377
+
378
+ try:
379
+ result = handler(combined, offsets, context_metadatas)
380
+ if inspect.iscoroutine(result):
381
+ result = _run_async(result)
382
+ except Exception as e:
383
+ raise RuntimeError(f"Exception raised during handler execution: {e}") from e
384
+
385
+ if isinstance(result, pa.RecordBatch):
386
+ result_rb = result
387
+ else:
388
+ try:
389
+ result_rb = _coerce_to_record_batch(result)
390
+ except Exception as e:
391
+ raise RuntimeError(f"Failed to coerce handler output: {e}") from e
392
+
393
+ if result_rb.num_rows != combined.num_rows:
394
+ raise ValueError(
395
+ f"Combined handler must return a RecordBatch with {combined.num_rows} rows "
396
+ f"(matching input); got {result_rb.num_rows}"
397
+ )
398
+
399
+ output: list[bytes] = []
400
+ for i in range(len(per_caller)):
401
+ start = offsets[i]
402
+ length = offsets[i + 1] - start
403
+ sliced = result_rb.slice(start, length)
404
+ try:
405
+ output.append(encode_record_batch(sliced))
406
+ except Exception as e:
407
+ raise RuntimeError(f"Failed to encode response for caller {i}: {e}") from e
408
+ return output
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chalk-remote-call-python
3
- Version: 1.4.0
3
+ Version: 1.5.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
@@ -1 +0,0 @@
1
- __version__ = "1.4.0"