feat: Slice 1 — Python client library (define + run pipelines in code, over gRPC) - #1
Conversation
…RPC API Implements docs/design/20260724-embed-grpc-client-libraries.md's Slice 1: define-and-run pipelines in code against Conduit's existing control-plane API (proto/api/v1/api.proto), per the ADR (20260724-embed-bindings-via-grpc.md) that chose gRPC over a C-ABI. Named connector plugins only (Case A); inline_source/inline_destination (Case B) is Slice 2, gated on the external-connector engine feature. - Pipeline builder (.source/.destination/.process/.dlq) -> exact CreatePipeline/CreateConnector/CreateProcessor/UpdateDLQ/StartPipeline payloads. Settings accepts both **kwargs (identifier-shaped keys) and an explicit settings= dict (for real connector config keys like "format.type" that can't be Python keywords). - conduit.local(): download-on-first-use, checksum-verified conduit binary provisioning (GitHub Releases, GoReleaser artifact naming), subprocess supervision with a stable (never ephemeral-temp) default state_dir, and graceful-then-forced shutdown. - conduit.connect(addr): thin client for an already-running engine. - Run handle: wait_running()/status()/stop(), and ConduitError translating every grpc.RpcError (including the google.rpc.ErrorInfo detail Conduit's server attaches) -- never a raw gRPC stack trace. - Vendored gRPC/protobuf stubs generated via buf from proto/api/v1/api.proto + transitive deps (conduit-commons, googleapis, grpc-gateway openapiv2 options); tools/generate-stubs.sh regenerates them. Gates green locally: ruff format/check, mypy --strict, and pytest (45 unit tests covering builder->payload mapping, error translation, and provisioning with a mocked download; 1 integration test that for real downloads a conduit v0.18.0 binary, runs a generator->log pipeline end-to-end, and gracefully stops it -- this sandbox has network access, so it ran rather than skipped). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GQFzakPShAYj8CcwajYDD
…tial run() failure PR #1 review fixes (Slice 1): - Pipeline.dlq() silently sent window_size=0/window_nack_threshold=0 on the wire whenever the caller didn't pass them explicitly. UpdateDLQ takes these at face value with no server-side defaulting (unlike pipeline creation's DefaultDLQ, window_size=1), so window_size=0 disables the nack-window stop-safety -- calling .dlq("builtin:log") was worse than never calling it. Now defaults window_size=1/window_nack_threshold=0 to mirror the engine's DefaultDLQ, unless the caller passes explicit values (including 0). Added a regression test asserting the UpdateDLQ payload's window fields. - Client.run()'s mid-sequence failure (pipeline created, then a connector/processor/DLQ/start call fails) raised a ConduitError with no way for the caller to find the already-created pipeline to clean it up. Added ConduitError.pipeline_id, set by run() whenever CreatePipeline already succeeded before the failing step. - connect(check_version=True) only checks reachability, not version -- left the behavior, added a TODO at the call site and tightened the docstring so the flag name doesn't overpromise. Gates: ruff format --check, ruff check, mypy (strict, packages=["conduit"]) all clean; full pytest (50 tests, including the new DLQ + pipeline_id regression tests) green; generated _grpc untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GQFzakPShAYj8CcwajYDD
|
Pushed review fixes (5e02df3), not merged — Tier 1, needs DeVaris sign-off. MUST-FIX — DLQ nack-window (data-integrity). SHOULD-FIX — partial-creation cleanup. TRACK — Gates: |
The pending publisher is configured (project conduit-client, owner ConduitIO, repo conduit-client-python, workflow release.yml, no environment), so the OIDC exchange now has a counterpart to match. Adds permissions: id-token: write and uncomments the pypa/gh-action-pypi-publish step. Publishing is gated on a tag push, so workflow_dispatch remains a build+check dry run — the workflow stays usable for verification without risking a publish. FLAGGED, NOT RESOLVED: README's 'Open questions for DeVaris' #1 lists the distribution name as unsettled (conduit-client vs conduit-embed vs bare conduit). Configuring a pending publisher does not claim a name, but the FIRST PUBLISH does — permanently, since PyPI neither frees nor reuses names. That question needs answering before the first tag, not after.
First slice of the Conduit Python client library — the gRPC embed direction (ADR: embed bindings via gRPC, not a C-ABI). Define a pipeline in code, run it, control its lifecycle — all over Conduit's existing control-plane gRPC API. No core-engine changes.
What's here (Slice 1 / Case A)
Pipelinebuilder:conduit.Pipeline(id).source(plugin, **cfg).destination(plugin, **cfg).process(plugin, **cfg).dlq(...)→ a pureBuildPlan.conduit.local(...): spawns + supervises aconduitsubprocess (download-on-first-use from GitHub Releases, GoReleaser naming, SHA-256 verified againstchecksums.txt, cached per-version viaplatformdirs, never touchesPATH), stable state-dir default (./.conduit/state, warns, never ephemeral — at-least-once safety), graceful teardown on exit.conduit.connect(addr): bind to an already-running Conduit (local subprocess or remote/service).run = client.run(pipeline)→wait_running()/status()/stop().conduit.ConduitError(decodes Conduit's realgoogle.rpc.ErrorInfo— code + message, not raw stack traces).CreatePipeline/GetPipeline/StartPipeline/StopPipeline/UpdateDLQ,CreateConnector,CreateProcessor,GetInfo.Verification
run()sequencing, provisioning with mocked download).generator→logend-to-end through the client, stopped gracefully.ruff+mypy --strictclean; buf-generated stubs committed + reproducible.Scope
Slice 1 = Case A (define + run). Out (later slices): the external-connector/
inline_sourcehost-as-source (Slice 2, needs the engine-side dial feature), Node (Slice 3), typed connector-config codegen (fast-follow — connector settings are**kwargsdicts for now).Settled (were flagged open)
conduit-client(PyPI-available, confirmed).Risk tier
Tier 1 — frozen public API surface (import path +
New/Pipeline/local/connect/runshapes). Needs maintainer (DeVaris) sign-off; a fresh-context DX/correctness review runs before merge.🤖 Generated with Claude Code
https://claude.ai/code/session_015GQFzakPShAYj8CcwajYDD