Core RPC¶
The core module provides the server, connection, transport interface, error types, and convenience functions for defining and running RPC services.
Typical Usage¶
Most users only need serve_pipe (testing) or connect (subprocess):
from vgi_rpc import serve_pipe, connect
# In-process (tests)
with serve_pipe(MyService, MyServiceImpl()) as proxy:
proxy.my_method(arg=42)
# Subprocess
with connect(MyService, ["python", "worker.py"]) as proxy:
proxy.my_method(arg=42)
For more control, use RpcServer and RpcConnection directly.
API Reference¶
RpcServer¶
RpcServer
¶
RpcServer(
protocol: type,
implementation: object,
*,
external_location: ExternalLocationConfig | None = None,
server_id: str | None = None,
server_version: str = "",
enable_describe: bool = False,
ipc_validation: IpcValidation = FULL
)
Dispatches RPC requests to an implementation over IO-stream transports.
Initialize with a protocol type and its implementation.
| PARAMETER | DESCRIPTION |
|---|---|
protocol
|
The Protocol class defining the RPC interface. If the
class declares a
TYPE:
|
implementation
|
Object implementing all methods from protocol.
TYPE:
|
external_location
|
Optional ExternalLocation configuration.
TYPE:
|
server_id
|
Optional server identifier; auto-generated if
TYPE:
|
server_version
|
Build version string included in access log entries.
TYPE:
|
enable_describe
|
When
TYPE:
|
ipc_validation
|
Validation level for incoming IPC batches.
Defaults to
TYPE:
|
Source code in vgi_rpc/rpc/_server.py
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 | |
methods
property
¶
methods: Mapping[str, RpcMethodInfo]
Return method metadata for this server's protocol.
external_config
property
¶
external_config: ExternalLocationConfig | None
The ExternalLocation configuration, if any.
server_version
property
¶
Version string passed at construction (empty if not set).
protocol_version
property
¶
Application protocol surface version declared by the Protocol class.
Read from vars(protocol).get("protocol_version") at construction
(a ClassVar[str] in canonical semver MAJOR.MINOR.PATCH form, or
None when the Protocol opts out). When set, the server enforces
an exact major+minor match on every dispatched request via
_check_protocol_version.
ctx_methods
property
¶
Method names whose implementations accept a ctx parameter.
transport_kind
property
¶
transport_kind: TransportKind | None
Coarse identifier of the bound transport, or None before serving begins.
Set by the framework right before the first request is dispatched
(lazy on HTTP for fork-safety). Workers may read this directly,
or rely on the on_serve_start lifecycle hook for one-shot
startup work.
transport_capabilities
property
¶
Capabilities advertised by the bound transport.
Currently includes "shm" when a :class:ShmPipeTransport is
bound. Empty before a transport is bound and for kinds without
special capabilities.
serve
¶
serve(transport: RpcTransport) -> None
Serve RPC requests in a loop until the transport is closed.
Source code in vgi_rpc/rpc/_server.py
serve_one
¶
serve_one(transport: RpcTransport) -> None
Handle a single RPC call (any method type) over the given transport.
Protocol-level errors (VersionError, RpcError from missing
metadata) are caught, written back as error responses, and the
method returns normally so the serve loop can continue.
| RAISES | DESCRIPTION |
|---|---|
ArrowInvalid
|
If the incoming data is not valid Arrow IPC.
An error response is written to transport before raising so
the client can read a structured |
Source code in vgi_rpc/rpc/_server.py
646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 | |
RpcConnection¶
RpcConnection
¶
RpcConnection(
protocol: type[P],
transport: RpcTransport,
on_log: Callable[[Message], None] | None = None,
*,
external_location: ExternalLocationConfig | None = None,
ipc_validation: IpcValidation = FULL
)
Context manager that provides a typed RPC proxy over a transport.
The type parameter P is the Protocol class, enabling IDE
autocompletion for all methods defined on the protocol::
with RpcConnection(MyProtocol, transport) as svc:
result = svc.add(a=1, b=2) # IDE sees MyProtocol methods
Initialize with a protocol type and transport.
Source code in vgi_rpc/rpc/_client.py
__enter__
¶
Enter the context and return a typed proxy.
Source code in vgi_rpc/rpc/_client.py
__exit__
¶
__exit__(
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None
Close the transport.
Source code in vgi_rpc/rpc/_client.py
RpcTransport¶
RpcTransport
¶
Bases: Protocol
Bidirectional byte stream transport.
RpcMethodInfo¶
RpcMethodInfo
dataclass
¶
RpcMethodInfo(
name: str,
params_schema: Schema,
result_schema: Schema,
result_type: object,
method_type: MethodType,
has_return: bool,
doc: str | None,
param_defaults: dict[str, object] = dict(),
param_types: dict[str, object] = dict(),
param_docs: dict[str, str] = dict(),
header_type: (
type[ArrowSerializableDataclass] | None
) = None,
is_exchange: bool | None = None,
)
Metadata for a single RPC method, derived from Protocol type hints.
Produced by :func:rpc_methods when introspecting a Protocol class.
Each instance describes one method's wire-protocol details: its Arrow
schemas, parameter types and defaults, and the original docstring.
| ATTRIBUTE | DESCRIPTION |
|---|---|
name |
Method name as it appears on the Protocol.
TYPE:
|
params_schema |
Arrow schema for the serialized request parameters.
TYPE:
|
result_schema |
Arrow schema for the serialized response (unary only; empty schema for stream methods).
TYPE:
|
result_type |
The raw Python return-type annotation (e.g.
TYPE:
|
method_type |
Whether this is a
TYPE:
|
has_return |
TYPE:
|
doc |
The method's docstring from the Protocol class, or
TYPE:
|
param_defaults |
Mapping of parameter name to default value for parameters that have defaults in the Protocol signature.
TYPE:
|
param_types |
Mapping of parameter name to its Python type annotation
(excludes
TYPE:
|
header_type |
For stream methods with a header, the concrete
TYPE:
|
is_exchange |
For stream methods,
TYPE:
|
MethodType¶
MethodType
¶
Bases: Enum
Classification of RPC method patterns.
Errors¶
RpcError
¶
Bases: Exception
Raised on the client side when the server reports an error.
Initialize with error details from the remote side.
Source code in vgi_rpc/rpc/_common.py
VersionError
¶
Bases: Exception
Raised when a request has a missing or incompatible protocol version.
Typed marker errors¶
These exception classes carry a stable error_kind class attribute that
the wire serializer surfaces as the vgi_rpc.error_kind metadata key on
the EXCEPTION-level batch. Clients can pattern-match the kind instead of
substring-searching the error message.
MethodNotImplementedError
¶
Bases: AttributeError
Raised server-side when no handler is registered for the requested RPC method.
Subclass of AttributeError so existing except AttributeError callers
keep working. Carries a stable error_kind class attribute that the
wire serializer surfaces as a top-level vgi_rpc.error_kind metadata
key on the EXCEPTION-level error batch, so clients can pattern-match on
the kind rather than substring-searching the message text.
Used by callers that want to detect "old server doesn't know this method" cleanly (e.g. capability detection + fallback to a legacy RPC method).
SessionLostError
¶
Bases: Exception
Raised server-side when a sticky session token cannot be honoured.
Surfaced over the wire with error_kind="session_lost" so clients can
pattern-match the kind without substring-searching the message. Causes
include: token presented to a different worker than the one that minted
it (server_id mismatch), the registry entry aged out via TTL eviction,
AAD mismatch (cross-principal replay), or any other validation failure.
Sticky session machinery is HTTP-only; this error never originates from pipe/unix transports.
ServerDrainingError
¶
Bases: Exception
Raised server-side when a sticky-enabled worker is draining and refuses new sessions.
Surfaced over the wire with error_kind="server_draining". Existing
sessions continue to serve through TTL or explicit close; only new
ctx.open_session calls are rejected. Operators trigger drain via
RpcServer.drain() (typically from a SIGTERM handler) ahead of
deploy-time worker rotation.
See also IPCError in the Serialization module.
CallStatistics¶
CallStatistics
dataclass
¶
CallStatistics(
input_batches: int = 0,
output_batches: int = 0,
input_rows: int = 0,
output_rows: int = 0,
input_bytes: int = 0,
output_bytes: int = 0,
)
Mutable accumulator of per-call I/O counters for usage accounting.
Created at dispatch start and populated as batches flow through the server. Surfaced through the access log and OTel dispatch hook.
Byte measurement: uses pa.RecordBatch.get_total_buffer_size()
which reports logical Arrow buffer sizes (O(columns), negligible cost).
This is an approximation — it does not include IPC framing
overhead (padding, schema messages, EOS markers).
| ATTRIBUTE | DESCRIPTION |
|---|---|
input_batches |
Number of input batches read by the server.
TYPE:
|
output_batches |
Number of output batches written by the server.
TYPE:
|
input_rows |
Total rows across all input batches.
TYPE:
|
output_rows |
Total rows across all output batches.
TYPE:
|
input_bytes |
Approximate logical bytes across all input batches.
TYPE:
|
output_bytes |
Approximate logical bytes across all output batches.
TYPE:
|
Convenience Functions¶
run_server
¶
run_server(
protocol_or_server: type | RpcServer,
implementation: object | None = None,
) -> None
Serve RPC requests, defaulting to stdin/stdout pipe transport.
This is the recommended entry point for subprocess workers. Accepts
either a (protocol, implementation) pair or a pre-built RpcServer.
The function parses sys.argv and supports the following CLI flags:
--http— Serve over HTTP instead of stdin/stdout (requiresvgi-rpc[http]).--host HOST— HTTP bind address (default127.0.0.1).--port PORT— HTTP port (default0, auto-select).--describe— Enable the__describe__introspection method.--access-log PATH— Append JSONL access log records toPATH. The cross-language conformance contract requires every worker to accept this flag; seedocs/access-log-spec.md.--max-response-bytes N— HTTP-only. Cap the outgoing HTTP body of every method response atNbytes (including IPC framing). For producer streams, controls when the framework mints a continuation token to split the response across multiple HTTP turns. Default: no body cap. Env:VGI_RPC_MAX_RESPONSE_BYTES.--max-externalized-response-bytes N— HTTP-only. Cap the total bytes uploaded to external storage during one HTTP response. Default: unbounded. Env:VGI_RPC_MAX_EXTERNALIZED_RESPONSE_BYTES.--max-stream-response-bytes N— Deprecated; alias for--max-response-bytes.
Without --http the server runs over stdin/stdout pipes (the
default, suitable for SubprocessTransport).
| PARAMETER | DESCRIPTION |
|---|---|
protocol_or_server
|
A Protocol class (requires implementation) or
an already-constructed
TYPE:
|
implementation
|
The implementation object. Required when
protocol_or_server is a Protocol class; must be
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
TypeError
|
On invalid argument combinations. |
Source code in vgi_rpc/rpc/__init__.py
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 | |
connect
¶
connect(
protocol: type[P],
cmd: list[str],
*,
on_log: Callable[[Message], None] | None = None,
external_location: ExternalLocationConfig | None = None,
stderr: StderrMode = INHERIT,
stderr_logger: Logger | None = None,
ipc_validation: IpcValidation = FULL
) -> Iterator[P]
Connect to a subprocess RPC server.
Context manager that spawns a subprocess, yields a typed proxy, and cleans up on exit.
| PARAMETER | DESCRIPTION |
|---|---|
protocol
|
The Protocol class defining the RPC interface.
TYPE:
|
cmd
|
Command to spawn the subprocess worker.
TYPE:
|
on_log
|
Optional callback for log messages from the server.
TYPE:
|
external_location
|
Optional ExternalLocation configuration for resolving and producing externalized batches.
TYPE:
|
stderr
|
How to handle the child's stderr stream (see :class:
TYPE:
|
stderr_logger
|
Logger for
TYPE:
|
ipc_validation
|
Validation level for incoming IPC batches.
TYPE:
|
| YIELDS | DESCRIPTION |
|---|---|
P
|
A typed RPC proxy supporting all methods defined on protocol. |
Source code in vgi_rpc/rpc/__init__.py
serve_pipe
¶
serve_pipe(
protocol: type[P],
implementation: object,
*,
on_log: Callable[[Message], None] | None = None,
external_location: ExternalLocationConfig | None = None,
ipc_validation: IpcValidation | None = None
) -> Iterator[P]
Start an in-process pipe server and yield a typed client proxy.
Useful for tests and demos — no subprocess needed. A background thread
runs RpcServer.serve() on the server side of a pipe pair.
| PARAMETER | DESCRIPTION |
|---|---|
protocol
|
The Protocol class defining the RPC interface.
TYPE:
|
implementation
|
The implementation object.
TYPE:
|
on_log
|
Optional callback for log messages from the server.
TYPE:
|
external_location
|
Optional ExternalLocation configuration for resolving and producing externalized batches.
TYPE:
|
ipc_validation
|
Validation level for incoming IPC batches.
When
TYPE:
|
| YIELDS | DESCRIPTION |
|---|---|
P
|
A typed RPC proxy supporting all methods defined on protocol. |
Source code in vgi_rpc/rpc/__init__.py
describe_rpc
¶
describe_rpc(
protocol: type,
*,
methods: Mapping[str, RpcMethodInfo] | None = None
) -> str
Return a human-readable description of an RPC protocol's methods.