diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..4e215de --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,28 @@ +name: lint + +on: + push: + branches: [main] + pull_request: + +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + - run: uv sync --all-extras + - run: uv run ruff format --check . + - run: uv run ruff check . + + mypy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + - run: uv sync --all-extras + - run: uv run mypy diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d52528b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: release + +# Manual release process for v0.x, matching conduit-connector-sdk-python's: +# trusted-publisher CI automation to PyPI is an explicit fast-follow, not +# blocking. This workflow builds and checks the package on a tag push; the +# actual `pypi-publish` step is intentionally left commented out until +# trusted-publisher OIDC setup is done (requires PyPI project configuration +# DeVaris has not yet performed -- do not uncomment without that). See +# README.md "Open questions for DeVaris" #1/#2 (distribution name, release +# target) -- also unresolved as of this workflow. +on: + push: + tags: ["v*"] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + - run: uv build + - run: uv run --with twine twine check dist/* + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + # Fast-follow: trusted publisher OIDC push to PyPI. + # - name: Publish to PyPI + # uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..d30f1cd --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,42 @@ +name: test + +on: + push: + branches: [main] + pull_request: + +jobs: + unit: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + # Two most recent CPython minors on the 3.11+ floor. + python-version: ["3.12", "3.13"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + python-version: ${{ matrix.python-version }} + - run: uv sync --all-extras + # Unit tests only: builder->payload mapping, error translation, + # provisioning with a mocked download. No network, no real subprocess. + - run: uv run pytest -v -m "not integration" + + integration: + # Spins up a real conduit binary via conduit.local() -- needs network + # access to GitHub Releases on the first run per OS/version combination. + # Runs on ubuntu only (the release asset matrix is exercised for real by + # the unit job's mocked-download tests across all three OSes; this job's + # job is proving the real download+launch path works at all, not + # re-proving cross-platform archive naming). + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + - run: uv sync --all-extras + - run: uv run pytest -v -m integration diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d390acb --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# Python +__pycache__/ +*.py[cod] +*.egg-info/ +.eggs/ +build/ +dist/ +.venv/ +venv/ + +# Tooling caches +.mypy_cache/ +.ruff_cache/ +.pytest_cache/ +.hypothesis/ +htmlcov/ +.coverage +.coverage.* + +# uv +.uv/ + +# Editors / OS +.vscode/ +.idea/ +.DS_Store + +# Generated protobuf/grpc stubs are checked in (vendored, see +# tools/generate-stubs.sh and src/conduit/_grpc/__init__.py) -- do NOT ignore +# src/conduit/_grpc/**, but ignore ad hoc regeneration scratch. +buf-generate.log + +# conduit.local()'s default state_dir (./.conduit/state) and this repo's +# quickstart example's state dir -- runtime artifacts, never committed. +.conduit/ +conduit-state/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..07767aa --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# Contributing + +Thanks for considering a contribution to `conduit-client-python`. + +## Tier and review bar + +Per [`ConduitIO/conduit`'s `CLAUDE.md`](https://github.com/ConduitIO/conduit/blob/main/CLAUDE.md), +this is a new public client-facing API surface (Tier 1 territory per the ADR +that authorized it, `docs/design/20260724-embed-bindings-via-grpc.md`): + +- Any change to the builder's payload shape (`pipeline.py`), the gRPC wire + adapter (`_grpc/`, `client.py`), error translation (`errors.py`), or binary + provisioning (`_provision.py`, `_local.py`) is a public-API-shape change -- + flag it loudly in the PR description; the builder/`local()`/`connect()` + surface is meant to be frozen once it ships. +- Human maintainer sign-off is required on changes to that surface -- + automated review alone is never sufficient. +- Bug fixes ship with the regression test that would have caught the bug. +- PR descriptions include a failure-mode analysis: what could this break, + what would show it, how do we roll back. + +## Local setup + +```bash +uv sync --all-extras +# or: python3 -m venv .venv && . .venv/bin/activate && pip install -e '.[dev]' +``` + +## Before opening a PR + +```bash +ruff format --check . +ruff check . +mypy +pytest -v -m "not integration" # fast, no network, no subprocess +pytest -v -m integration # real conduit binary end-to-end (needs network) +``` + +Regenerating the gRPC stubs (only needed when `proto/api/v1/api.proto` or its +dependencies change): + +```bash +./tools/generate-stubs.sh +``` + +Review the diff under `src/conduit/_grpc/` before committing -- this is the +one directory in the repo that's generated, vendored output (see +`src/conduit/_grpc/__init__.py` for why it's structured the way it is). + +## Commit style + +Conventional commits (`feat:`, `fix:`, `docs:`, `refactor:`, `chore:`), +matching the rest of the ConduitIO org. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e0173ef --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Meroxa, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3542d3 --- /dev/null +++ b/README.md @@ -0,0 +1,203 @@ +# conduit-client (Python) + +Python client library for defining and running [Conduit](https://github.com/ConduitIO/conduit) +pipelines in code, over Conduit's existing control-plane gRPC API +(`proto/api/v1/api.proto`). No changes to Conduit itself are required. + +> **Status: pre-alpha, Slice 1.** Implements +> [`docs/design/20260724-embed-grpc-client-libraries.md`](docs/design/20260724-embed-grpc-client-libraries.md)'s +> "Build slices -- Slice 1": Case A (named connector plugins) only. No release has +> shipped; the public API (`Pipeline`, `local`, `connect`, `Client`, `Run`) is +> not stable until it ships. See "Open questions for DeVaris" below. + +## What this is + +- A **builder** for pipeline configs (`Pipeline(id).source(...).destination(...).process(...)`) + that produces the exact request payloads Conduit's `PipelineService`/ + `ConnectorService`/`ProcessorService` RPCs expect -- no YAML, no shelling out + to the `conduit` CLI. +- A **sync gRPC client** (`conduit.connect(addr)`) for an already-running, + independently deployed Conduit engine -- the production shape. +- A **local-engine supervisor** (`conduit.local(...)`) that downloads a + pinned `conduit` release binary on first use, spawns it with its API + enabled on loopback, and hands you a client bound to it -- for dev, + notebooks, and one-off jobs. **Not a production story** -- see "Deployment + modes" below. +- Errors are always `conduit.ConduitError` (a stable code, message, and + optional config path/suggestion) -- never a raw `grpc.RpcError` or + traceback. + +Why gRPC and not a C-ABI/FFI shared library: see +[`docs/design/20260724-embed-bindings-via-grpc.md`](docs/design/20260724-embed-bindings-via-grpc.md) +(the ADR) -- in short, the hot record-processing path never crosses the +embedding boundary, only low-frequency lifecycle/status calls do, so gRPC's +cost (a loopback network hop on those calls) is real but small, and it comes +with a working story for driving an already-deployed remote engine that a +C-ABI structurally cannot have. + +## Quickstart + +```python +import conduit + +pipeline = ( + conduit.Pipeline("orders-sync") + .source("generator", settings={"format.type": "structured"}, operations="create") + .destination("log", level="info") +) + +with conduit.local(state_dir="./conduit-state") as client: + run = client.run(pipeline) + run.wait_running() + print(run.status()) + run.stop() +``` + +First run downloads a pinned `conduit` release binary (see "Binary +provisioning" below); later runs reuse the cached copy. `state_dir` is +required to be a real, reused directory for anything beyond a one-off +experiment -- see "Deployment modes." + +## Requirements + +- Python 3.11+ +- [`uv`](https://docs.astral.sh/uv/) for dependency management (recommended; + `pip install -e .[dev]` also works) +- Network access on first `conduit.local()` call per version (binary + download), unless you pass `binary=` to point at an already-present + executable. + +## Repo layout + +```text +src/conduit/ + __init__.py # public API surface + pipeline.py # Pipeline builder -> BuildPlan (pure, no I/O) + client.py # Client (gRPC stubs), connect() + run.py # Run handle, RunStatus + errors.py # ConduitError, grpc.RpcError -> ConduitError translation + _local.py # local(): binary provisioning + subprocess supervision + _provision.py # download-on-first-use, checksum-verified binary provisioning + _grpc/ # generated protobuf/grpc stubs (buf generate output) +docs/design/ # design doc + ADR this repo implements (copied from ConduitIO/conduit) +tests/unit/ # builder/error/provisioning unit tests (no network, no subprocess) +tests/integration/ # spins up a real conduit via local() -- see "Testing" below +``` + +## The client API surface + +- **`conduit.Pipeline(id, *, name=None, description="")`** -- fluent builder. + `.source(plugin, *, name="", settings=None, **kwargs)`, `.destination(...)` + (same shape), `.process(plugin, *, condition="", workers=None, settings=None, + **kwargs)` (pipeline-level; per-connector processor attachment is a natural + follow-up, not yet exposed), `.dlq(plugin="builtin:log", *, window_size=None, + window_nack_threshold=None, settings=None, **kwargs)`. + Config values are coerced to strings -- `Connector.Config.settings`/ + `Processor.Config.settings` are a flat `map` on the wire + today. **Real connector config keys are often not valid Python identifiers** + (e.g. the builtin generator connector's `format.type`, `sdk.batch.size`) -- + use the explicit `settings={"format.type": "structured"}` dict for those; + plain identifier-shaped keys can use `**kwargs` (`operations="create"`) + instead, and both can be combined in the same call. **Typed per-connector + config** (`postgres.Source(url=..., tables=[...])` with real parameter + types, generated from each plugin's param spec) is the documented + fast-follow, not hand-written per connector. +- **`conduit.local(state_dir=None, *, version=None, binary=None, startup_timeout=30.0)`** + -- provisions (if needed), spawns, and waits for a `conduit` subprocess; + returns a client-like `LocalConduit` (context manager, or call `.close()` + yourself). +- **`conduit.connect(addr, *, credentials=None, check_version=True, timeout=5.0)`** + -- dials an already-running engine; returns a `Client`. +- **`client.run(pipeline, *, start=True, timeout=10.0) -> Run`** -- creates the + pipeline, its connectors and processors, applies the DLQ if configured, and + starts it (unless `start=False`). +- **`run.wait_running(timeout=30.0, poll_interval=0.2)`**, **`run.status()`** + (-> `RunStatus`: `status`, `error`, `stopped_reason`, `is_running`, + `is_degraded`), **`run.stop(force=False, timeout=10.0)`**. + +## RPCs this client drives + +Exactly the incremental CRUD-plus-lifecycle RPCs in `proto/api/v1/api.proto` +(not `PlanPipeline`/`ApplyPipeline`'s whole-document shape, which is a +different, YAML-provisioner-aligned surface): + +- `PipelineService`: `CreatePipeline`, `GetPipeline`, `StartPipeline`, + `StopPipeline`, `UpdateDLQ`. +- `ConnectorService`: `CreateConnector`. +- `ProcessorService`: `CreateProcessor`. +- `InformationService`: `GetInfo` (engine version check at connect-time). + +`UpdatePipeline`/`DeletePipeline`/`UpdateConnector`/`DeleteConnector`/ +`UpdateProcessor`/`DeleteProcessor`/`ListPipelines`/etc. are defined on the +generated stubs (nothing stops a caller reaching for `Client._pipelines` +directly) but have no builder-level convenience yet -- Slice 1 scope is +"define + run," not full lifecycle management. + +## Deployment modes -- framed honestly + +- **Mode 1, `conduit.local()`**: engine co-located with your process, tied to + its lifecycle. Fits dev, notebooks, one-off jobs. **Never sell this as a + production pipeline story** -- no independent lifecycle, no + restart-without-the-host-restarting, no fleet management. `state_dir` + defaults to `./.conduit/state` (stable across repeated runs in the same + directory) rather than an ephemeral temp dir, but a host-process crash still + takes the engine down with it. +- **Mode 2, `conduit.connect(addr)`**: a deployed, long-running Conduit + service, managed independently (systemd, Kubernetes, ...). This is what a + production pipeline should use. + +`inline_source`/`inline_destination` (Case B, driving a host-implemented +Python connector via a new engine-side "external connector" feature) is +**Slice 2**, not built here -- it needs its own engine-side design-doc sign-off +pass per the design doc (Tier 1, touches connector acquisition). + +## Binary provisioning + +`conduit.local()` never looks at `PATH`. It downloads a specific, +version-pinned GoReleaser release asset +(`conduit___.tar.gz`, `.zip` on Windows) from +[GitHub Releases](https://github.com/ConduitIO/conduit/releases), verifies its +SHA-256 against the release's published `checksums.txt`, extracts the +`conduit` binary, and caches it under a per-version directory in your user +cache dir (`platformdirs.user_cache_dir("conduit-client-python")`). A second +call with the same version reuses the cache with no network access. Pass +`binary=` to point at an already-present executable instead (skips +provisioning entirely) -- useful for CI images that pre-bake a specific build. + +The default version is pinned in `conduit._provision.DEFAULT_CONDUIT_VERSION` +(currently the latest stable release at the time this was written); override +with `local(version="0.19.0")` or the `CONDUIT_CLIENT_ENGINE_VERSION` env var. + +## Testing + +```bash +uv sync --all-extras +uv run pytest -v -m "not integration" # unit tests: no network, no subprocess +uv run pytest -v -m integration # spins up a real conduit binary end-to-end +``` + +The integration test (`tests/integration/test_local_generator_log.py`) runs +`conduit.local()` for real and drives a `generator -> log` pipeline through +`run()`/`wait_running()`/`stop()`. It downloads a real release binary on +first use -- **if the sandbox running these tests has no network access, it +is honestly marked `skip` with the reason stated** (see the test file), never +faked or mocked into looking like a pass. + +## Open questions for DeVaris + +See the design doc's "Open questions for DeVaris" for the full list; the ones +this repo's existence doesn't yet resolve: + +1. **PyPI distribution name.** This repo uses the working name + `conduit-client` in `pyproject.toml` -- not settled against + `conduit-embed`/bare `conduit`. +2. **Release target** (v0.19 fast-follow vs. v0.20 anchor). +3. Binary provisioning here is **download-on-first-use with checksum + verification** (not a bundled-per-platform wheel) -- lighter package, + needs network + GitHub Releases availability. Confirms the design doc's + open question 3 in the "lighter, needs network" direction; flag if that's + wrong for the intended distribution story. + +## License + +[Apache License 2.0](LICENSE). diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..7560659 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,20 @@ +version: v1 +# Generates Python protobuf/grpc stubs for the control-plane API +# (proto/api/v1/api.proto) plus its transitive proto dependencies, per +# docs/design/20260724-embed-grpc-client-libraries.md and +# src/conduit/_grpc/__init__.py's docstring for why the dependency trees are +# vendored too (descriptor-pool resolution of message/method options, not +# because this client uses those types directly). +# +# Output is vendored, generated code -- never hand-edited. Regenerate via +# ./tools/generate-stubs.sh, which issues one `buf generate` invocation per +# BSR module below, each scoped with `--path` (a bare `buf generate` against +# buf.build/conduitio/conduit would also pull in unrelated proto packages +# from the same module). +plugins: + - plugin: buf.build/protocolbuffers/python:v29.2 + out: src/conduit/_grpc + - plugin: buf.build/protocolbuffers/pyi:v29.2 + out: src/conduit/_grpc + - plugin: buf.build/grpc/python:v1.68.0 + out: src/conduit/_grpc diff --git a/docs/design/20260724-embed-bindings-via-grpc.md b/docs/design/20260724-embed-bindings-via-grpc.md new file mode 100644 index 0000000..c74d18a --- /dev/null +++ b/docs/design/20260724-embed-bindings-via-grpc.md @@ -0,0 +1,241 @@ +# Embed language bindings (Python, Node) are gRPC clients, not a C-ABI + +## Summary + +Conduit's non-Go embed bindings — Python first, Node next — will be **gRPC client libraries +driving the existing control-plane API, plus a new "external connector" engine feature**, not a +`libconduit` C-ABI shared library. This supersedes the C-ABI framing in +[`docs/design-documents/20260705-sdk-and-embedding-dx.md`](../design-documents/20260705-sdk-and-embedding-dx.md) +§B3 and the design-ahead doc drafted on PR #2675, originally +`docs/design-documents/20260723-libconduit-c-abi-bindings.md` — renamed and rewritten, in the +same change that introduces this ADR, to +`docs/design-documents/20260724-embed-grpc-client-libraries.md`. + +The load-bearing reason: **in an embedded pipeline, the data path never crosses the host-process +boundary.** Records flow source → processors → destination entirely inside the Conduit engine +(`pkg/connector`, `pkg/pipeline`, `pkg/lifecycle-poc/funnel`); the embedding host only issues +lifecycle and status calls (create/start/stop/get/list a pipeline, read its state) — traffic that +already exists as a control-plane RPC (`proto/api/v1/api.proto`). A C-ABI's central benefit — +avoiding a network hop on the hot data path — does not apply here, because the hot data path +never touches the ABI at all under either design. What a C-ABI would add is cost without a +matching benefit for this workload: FFI memory-ownership rules the host must honor exactly, a Go +panic that is fatal to the _entire host process_ if it ever crosses the cgo boundary uncaught +(unlike a Go-to-Go panic, which any Go caller can recover), a C-toolchain-per-target build matrix +for a codebase that otherwise cross-compiles for free, an ASAN gate on top of the Go race +detector, and JSON-string marshaling for every structured value crossing the boundary anyway +(the C-ABI draft's own D1.1: "cgo cannot safely pass Go pointers, slices, or interfaces"). + +gRPC already exists twice in this codebase for exactly this kind of boundary: the control-plane +API (`proto/api/v1/api.proto`, HTTP-gateway'd via `google.api.http` annotations) and the +connector protocol (`conduit-connector-protocol`'s `pconnector` gRPC services, which the Python +_connector_ SDK already speaks per `docs/design-documents/20260707-python-connector-sdk.md`). +A gRPC client library spans **both** deployment shapes a C-ABI cannot: a local subprocess +(`conduit.local()`, dialing a loopback address) and a remote, already-deployed Conduit service +(`conduit.connect(addr)`) — the same client code, a different address. A C-ABI is fundamentally +in-process-only; it has no story for "drive a Conduit that's already running somewhere else," +which is the production shape this project cares about most. + +## Context + +### The control-plane API is already a complete lifecycle/status surface + +`proto/api/v1/api.proto` defines three gRPC services with full CRUD-plus-lifecycle coverage, +each exposed over HTTP via `grpc-gateway` (`google.api.http` options, e.g. lines 320–325 for +`ListPipelines`/`CreatePipeline`): + +- **`PipelineService`** (lines 318–602): `ListPipelines`, `CreatePipeline`, `GetPipeline`, + `UpdatePipeline`, `DeletePipeline`, `StartPipeline`, `StopPipeline`, `GetDLQ`, `UpdateDLQ`, + `ExportPipeline`, `ImportPipeline`, `PlanPipeline`, `ApplyPipeline`. +- **`ConnectorService`** (lines 767–927): `ListConnectors`, `InspectConnector` (server-streaming), + `GetConnector`, `CreateConnector`, `ValidateConnector`, `UpdateConnector`, `DeleteConnector`, + `ListConnectorPlugins`. +- **`ProcessorService`** (lines 1003–1138): `ListProcessors`, `InspectProcessorIn`/ + `InspectProcessorOut` (server-streaming), `GetProcessor`, `CreateProcessor`, `UpdateProcessor`, + `DeleteProcessor`, `ListProcessorPlugins`. +- Plus `GetInfo` (line 1203) and `ListPlugins` (line 1225). + +Status is not a separate RPC: `message Pipeline` embeds `message State { Status status; +string error; StoppedReason stopped_reason; }` (lines 41–75), returned inline by +`GetPipeline`/`ListPipelines`. **Gap worth naming honestly:** there is no metrics RPC in this +proto. Metrics are a separate Prometheus `/metrics` HTTP endpoint wired outside the gRPC/OpenAPI +surface (`pkg/conduit/runtime.go:797–808`, mounted at `pkg/conduit/runtime.go:969`, alongside +`/healthz`/`/readyz` per `pkg/conduit/ui.go:30–41`). A client library's `status()` call maps to +`GetPipeline`; anything metrics-shaped means scraping `/metrics` separately, or nothing, in v1 — +not a new RPC invented for this doc. + +**Conclusion: the control-plane API RPC surface is sufficient for a lifecycle/status-driving +client library today, with the metrics gap noted above as an explicit non-goal for v1.** + +### The connector acquisition model is spawn-by-path today, and already supports dial-by-address underneath + +`pkg/plugin/connector/standalone/dispenser.go:39–62` (`NewDispenser`) calls +`conduit-connector-protocol`'s `pconnector/client.New(logger, path, opts...)` +(`pconnector/client/client.go:34–78`), which builds: + +```go +cmd := exec.CommandContext(context.Background(), path) +clientConfig := &plugin.ClientConfig{ + HandshakeConfig: pconnector.HandshakeConfig, + VersionedPlugins: map[int]plugin.PluginSet{ /* v1, v2 gRPC client stubs */ }, + Cmd: cmd, + AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, +} +return plugin.NewClient(clientConfig), nil +``` + +This is HashiCorp go-plugin's standard spawn model: Conduit execs the binary at `path`; the child +writes a handshake line (`CORE_PROTOCOL_VERSION|APP_PROTOCOL_VERSION|NETWORK|ADDRESS|PROTOCOL`) +to stdout; go-plugin's client parses that line and dials the advertised address over gRPC. +`pkg/plugin/connector/standalone/registry.go:37–56` stores each discovered plugin as a +`blueprint{FullName, Specification, Path}` — keyed by filesystem path, one dispenser per binary. + +Critically, go-plugin (`github.com/hashicorp/go-plugin@v1.8.0`, `client.go:299–315`) already +supports a second acquisition path that does **not** spawn a process at all: + +```go +type ReattachConfig struct { + Protocol Protocol + ProtocolVersion int + Addr net.Addr + Pid int + ReattachFunc runner.ReattachFunc + Test bool +} +``` + +`ClientConfig.Reattach`, wired at `client.go:596–616` and `973–1016`, lets a client dial a +**pre-known `Addr`** directly, skipping `Cmd`/spawn entirely, using the exact same +`VersionedPlugins` gRPC client stubs (`clientv1`/`clientv2`) as the spawn path. Nothing in +`conduit-connector-protocol`'s wire messages (`pconnector.SourceRunRequest`, +`SpecifierSpecifyRequest`, etc.) changes between the two paths — `Reattach` only changes how the +client _finds_ the server, not what it says to it once connected. The Python connector SDK design +(`docs/design-documents/20260707-python-connector-sdk.md:1–17, 163`) independently confirms the +handshake and gRPC server are not Go-specific: a Python-authored connector already writes the +handshake line and serves a plain gRPC server that any `clientv1`/`clientv2` stub can dial. + +**Conclusion, stated definitively: an "external connector" — Conduit dialing a pre-known address +instead of spawning a binary — requires no `conduit-connector-protocol` wire change.** It needs a +new dispenser variant (sibling to `standalone.Dispenser`) that constructs `plugin.ClientConfig{ +Reattach: &plugin.ReattachConfig{Addr: ...}}` instead of `{Cmd: cmd}`, and a registry entry keyed +by address instead of path. This is new engine-side code (Slice 2, Tier 1/Tier 2 depending on +final shape — see the design doc), not a protocol change. + +### The pipeline config model and the state layer already exist and already anticipate this + +`pkg/provisioning/config/parser.go:30–38` defines `Pipeline{ID, Status, Name, Description, +Connectors, Processors, DLQ}` — the exact structure a code-first builder targets. It is already +re-exported, unmodified, at the embed API's root: `conduit.go:37`, +`type PipelineConfig = provisioningconfig.Pipeline` — a type alias, not a copy, per B1 +(`github.com/conduitio/conduit`, merged, commit `e6a8b9f`). `parser.go:22–29`'s own doc comment +already states the deprecation discipline this ADR does not need to re-litigate: a field added, +removed, or renamed here is subject to the announce → warn → remove policy. + +The local KV store is Badger (`github.com/conduitio/conduit-commons/database/badger`), wired at +`pkg/conduit/runtime.go:336`: `badger.New(logger.Logger, cfg.DB.Badger.Path)`. At the embed API, +`Options.DB` (`conduit.go:82–104`) defaults to `DBTypeInMemory` when `Type == ""` — the doc +comment on `Options.DB` already warns "every pipeline configuration is lost once the Engine +stops." Positions persist inside `connector.Instance.State` (`pkg/connector/source.go:227`, +`s.Instance.State = SourceState{Position: p[len(p)-1]}`), written through +`pkg/connector/store.go`'s `Store`, which wraps the generic `database.DB` interface — Badger-file +durable, or in-memory and gone on process exit, depending on `Options.DB.Type`. This is not +theoretical: `tests/chaos` (added in commit `045f283`, the just-landed sev-0 fix for +`Source.Ack`'s ack-before-persist ordering) already exercises SIGKILL-mid-snapshot and +SIGKILL-mid-stream against a **real on-disk Badger DB** to verify recovery — the same store an +embedded `local()` engine uses. An embedder that runs `local()` with the default in-memory store, +or a temp directory it deletes on exit, does not violate any _engine_ invariant, but it does +forfeit Invariant 2 (crash-safe, monotonic positions) at the _embedding_ layer, silently, unless +the client library is explicit about requiring a persistent `Options.DB.Badger.Path` for anything +beyond a throwaway job. See the design doc's failure-modes section for the concrete guidance this +implies for `conduit.local()`. + +### What already ships, so this decision changes nothing about it + +B1 (`conduit.New`, `Engine.Run`/`Handle.Stop`/`Engine.Close`, `Engine.Import`, +`Engine.StartPipeline`/`StopPipeline`) is merged Go code at the repository root (`conduit.go`). +`Options.API` (`conduit.go:107–117`, `APIOptions{Enabled, GRPCAddress, HTTPAddress}`) already +turns on the exact gRPC/HTTP control-plane surface this ADR's client libraries will dial — meaning +`conduit.local()`'s "spawn a `conduit` process with its API enabled on a loopback address, then +speak gRPC to it" story requires **no new engine code** to stand up a first Python client. B2 +(the pipelines-in-code builder, `builder.go`) is in flight (`feat/embed-b2-pipeline-builder`, +not yet on `main`) and is unaffected by this decision — it is Go-side plumbing the Python/Node +builders will mirror in each language's own idiom, not something that crosses an ABI. + +## Decision + +1. **Python and Node embed bindings are gRPC client libraries**, not a `libconduit` C-ABI shared + library. They drive the existing control-plane API (`proto/api/v1/api.proto`) for pipeline/ + connector/processor lifecycle and status, and a new **external-connector** engine feature for + host-implemented inline sources/destinations. +2. **`conduit.local()`** spawns and supervises a `conduit` subprocess (binary provisioning + TBD — bundle vs. download-on-first-use, an open question for the design doc), with + `Options.API.Enabled = true` on a loopback address and an explicit, non-ephemeral + `Options.DB.Badger.Path`, then drives it exactly like `conduit.connect(addr)` drives a remote + instance — same client code, different process ownership. +3. **`conduit.connect(addr)`** dials an already-running, independently deployed Conduit service. + This is the production long-running shape; `local()` is dev/job/single-host shaped and must + never be sold as the production story (see the design doc's honest Mode 1 vs. Mode 2 framing). +4. **External connectors** (Slice 2) let Conduit dial a pre-known address for a connector instead + of spawning a binary, reusing go-plugin's existing `Reattach`/`Addr` path — zero + `conduit-connector-protocol` wire changes. This is the mechanism `inline_source`/ + `inline_destination` (a host-language connector server the client library runs in-process) is + built on. +5. **C-ABI / shared-memory transport is reserved as a demand-gated escape hatch**, not built now: + only revisited if two real, specific asks surface a use case gRPC structurally cannot serve — + a proven zero-copy requirement, or a single-binary-with-no-subprocess deployment constraint + that `local()` cannot meet. Absent that, per CLAUDE.md's no-speculative-generality rule, the + C-ABI does not get built on a hunch. + +## Consequences + +- **Positive.** No new build artifact class (`libconduit.{so,dylib,dll}`), no C toolchain per + target platform, no ASAN gate alongside the existing Go race detector, no FFI ownership/ + threading contract for the binding author to get right with weaker guardrails than Go's own + `-race` gives. The client library reuses two already-hardened, already-versioned surfaces + (control-plane API, connector protocol) instead of inventing a third wire encoding. It has an + honest story for both local and remote/production deployment, which the C-ABI design never did + (PR #2675's own alternative (b) analysis already conceded gRPC is the right answer for a + "managed sidecar" embedder — this ADR extends that concession to the primary path, not a + secondary one). +- **Negative / new costs.** Needs a real client library per language (not "for free" the way a Go + embedder importing `github.com/conduitio/conduit` is) and the external-connector engine feature + (Slice 2 — new dispenser variant, address-keyed registry entry, reachability requirements + documented). `local()` needs binary provisioning (a packaging problem the C-ABI draft's own D6 + already flagged as nontrivial, now inherited by a different artifact: the `conduit` executable + instead of a shared library). A deployed-remote `conduit.connect(addr)` with an + `inline_source`/`inline_destination` requires the remote engine to be able to reach back to the + host process running the Python/Node connector server — a real network-reachability + requirement absent in the local case, called out explicitly in the design doc rather than + glossed over. +- **Process.** This ADR supersedes the C-ABI framing in + `docs/design-documents/20260705-sdk-and-embedding-dx.md` §B3 (Phase-1/2 mapping corrected in + the same change) and replaces PR #2675's original design-ahead draft + (`docs/design-documents/20260723-libconduit-c-abi-bindings.md`) with the gRPC-embed design, + renamed and rewritten in place as `docs/design-documents/20260724-embed-grpc-client-libraries.md` + — reusing that draft's alternatives analysis (gRPC/UDS bridge, WASM) where it already reached + the right conclusion. + `docs/design-documents/20260722-embed-libconduit-v1.md` (B1/B2, merged/in-review) is unaffected: + its AC-8 C-ABI sketch is no longer the plan this doc's successor design builds toward, but + nothing in the shipped B1 Go API needs to change as a result — the constraints AC-8/D8 placed on + `PipelineConfig` (explicit JSON tags, etc.) become optional nice-to-haves for a future JSON- + emitting surface rather than load-bearing ABI requirements, and are re-evaluated, not assumed, + in the new design doc. + +## Related + +- `docs/design-documents/20260724-embed-grpc-client-libraries.md` — the design doc this ADR's + decision authorizes (client-library API, deployment modes, failure modes, build slices). It + replaces `docs/design-documents/20260723-libconduit-c-abi-bindings.md` (PR #2675's original + C-ABI design-ahead draft), renamed and rewritten in the same change that adds this ADR — the + old filename no longer exists in the tree. +- `docs/design-documents/20260705-sdk-and-embedding-dx.md` §B3 — the original embedding vision + this ADR reframes from C-ABI to gRPC. +- `docs/design-documents/20260722-embed-libconduit-v1.md` — the merged B1 (+ in-review B2) Go + embedding API this ADR's client libraries sit on top of via gRPC, not via cgo. +- `docs/design-documents/20260707-python-connector-sdk.md` — confirms the connector-protocol + handshake and gRPC server are not Go-specific; the precedent the external-connector feature and + `inline_source`/`inline_destination` build on. +- `proto/api/v1/api.proto` — the control-plane API surface this ADR's client libraries drive. +- `pkg/plugin/connector/standalone/dispenser.go`, `registry.go` — the spawn-by-path dispenser the + external-connector feature adds a dial-by-address sibling to. +- `pkg/connector/source.go:207–238`, `tests/chaos` (commit `045f283`) — the ack/position + durability behavior `conduit.local()`'s state-dir guidance is grounded in (Invariant 2). diff --git a/docs/design/20260724-embed-grpc-client-libraries.md b/docs/design/20260724-embed-grpc-client-libraries.md new file mode 100644 index 0000000..17bb243 --- /dev/null +++ b/docs/design/20260724-embed-grpc-client-libraries.md @@ -0,0 +1,384 @@ +# Embed client libraries (Python, Node): gRPC over the control-plane API + external connectors + +## Summary + +**This supersedes the C-ABI design previously drafted in this file** (see +[`docs/architecture-decision-records/20260724-embed-bindings-via-grpc.md`](../architecture-decision-records/20260724-embed-bindings-via-grpc.md) +for the decision record). Python and Node embed bindings will be **gRPC client libraries** driving +two already-real surfaces — the control-plane API (`proto/api/v1/api.proto`) and a new +**external-connector** engine feature — not a `libconduit` C-ABI shared library. + +The reason this flips, not just relabels, the earlier design: in an embedded pipeline, records +never cross the host-process boundary. Source → processor → destination all happens inside the +Conduit engine process; the embedding host only issues lifecycle and status calls. That traffic +already exists as gRPC — twice, in fact: the control-plane API (`ListPipelines`, `CreatePipeline`, +`StartPipeline`, `GetPipeline`, ...) and the connector protocol +(`conduit-connector-protocol`'s `pconnector` services, which the Python _connector_ SDK already +speaks per `docs/design-documents/20260707-python-connector-sdk.md`). A C-ABI's benefit — avoiding +a network hop on the hot data path — does not apply, because the hot data path never touches +either boundary. What a C-ABI would add is real cost with no matching benefit here: FFI +memory-ownership rules, a Go panic that is fatal to the _entire host process_ if it crosses the +cgo boundary uncaught, a C-toolchain-per-target build matrix, an ASAN gate, and JSON-marshaling +every structured value anyway. gRPC additionally spans a deployment shape the C-ABI structurally +cannot: driving an already-running, independently deployed Conduit service +(`conduit.connect(addr)`), not just an in-process one (`conduit.local()`). + +Risk tier: **Tier 1** (public contract — a new client-facing API surface and a new engine +connector-acquisition path). **Design-ahead only: no code ships from this doc.** DeVaris sign-off +is required before any Slice below starts implementation. + +## Problem + +Conduit has a merged Go embedding API (`github.com/conduitio/conduit`, B1) and an in-review +pipelines-in-code builder (B2, `feat/embed-b2-pipeline-builder`), but nothing for a non-Go host. +A Python or Node application that wants to run — or drive — a Conduit pipeline today has exactly +one option: shell out to the `conduit` CLI and parse its output, or hand-roll a gRPC client +against `proto/api/v1/api.proto` from scratch. Neither is a supported, documented, idiomatic +surface. The embedding vision doc (`docs/design-documents/20260705-sdk-and-embedding-dx.md` §B3) +and a design-ahead draft on PR #2675 previously proposed closing this gap with a `libconduit` +C-ABI shared library plus per-language FFI bindings. This doc rejects that mechanism and proposes +the alternative: idiomatic gRPC client libraries over surfaces that already exist and are already +versioned. + +## Alternatives considered + +**(a) gRPC client library over the control-plane API + external connectors — chosen.** No new +wire protocol: the control-plane API (`proto/api/v1/api.proto`) already has full CRUD-plus- +lifecycle coverage for pipelines, connectors, and processors (see Context), and the connector +protocol already supports dialing a pre-known address instead of spawning a binary (go-plugin's +`ReattachConfig`, see Context). A gRPC client is idiomatic per language (`grpcio`/`grpc.aio` for +Python, `@grpc/grpc-js` for Node), needs no C toolchain, and works identically whether the engine +is a local subprocess or a remote deployed service — the same client code, a different address. +Cost: engine lifecycle/status calls now cross a real IPC boundary (loopback or network) instead of +a direct in-process Go call, and `conduit.local()` needs a binary-provisioning story (a real +Conduit executable must exist on the host). + +**(b) `libconduit` C-ABI shared library — rejected, previously chosen in this doc's earlier +draft.** In-process, sharing the host's address space, no network hop on the lifecycle path. This +looked attractive when the design didn't distinguish the data path from the lifecycle path: the +whole point of "in-process" is avoiding a hop on the _hot_ path, but the hot path (record +movement) never crosses this boundary either way — only lifecycle calls do, and those are +low-frequency (create/start/stop/status, not per-record). Once that distinction is made, the +C-ABI's core justification evaporates and only its costs remain: cgo cannot pass Go pointers, +slices, or interfaces, so every structured value still crosses as a JSON string anyway (no +marshaling savings over gRPC's protobuf); a C-toolchain-per-target build (Go's +`-buildmode=c-shared` requires `CGO_ENABLED=1`) that the rest of Conduit's pure-Go, cross-compile- +for-free build never needed; a documented-but-compiler-unenforced memory-ownership and +single-writer-per-handle threading contract, strictly weaker than Go's own `-race`-detectable +guarantees; and a Go panic reaching the cgo boundary uncaught that is fatal to the _entire host +process_ — unlike a Go-to-Go panic, which any caller up the stack can `recover()`. It also has no +story for driving an already-deployed, remote Conduit service — a C-ABI is fundamentally +in-process-only, so a production "point our client library at our running Conduit fleet" use case +would need an entirely separate gRPC path anyway, at which point the C-ABI is solving only the +dev/local case while a second, unavoidable gRPC design solves production. Building two mechanisms +to cover what one gRPC client already covers is the opposite of CLAUDE.md's simplicity bar. + +**(c) A local gRPC/UDS bridge to an out-of-process engine.** This is not really a third +alternative — it _is_ what (a) becomes for the `conduit.local()` case (dialing a spawned +subprocess over loopback gRPC, previously enumerated as alternative (b) in the superseded C-ABI +draft and dismissed there as "not in-process embedding, so it doesn't compete with the C-ABI"). +That framing was backwards: once the C-ABI's in-process benefit is understood to not apply to the +lifecycle-only path this design covers, there is no separate "in-process" competitor to prefer +over it — this _is_ the chosen design, not a fallback. + +**(d) WASM (compile Conduit itself to a WASM module).** Still rejected, unchanged from the +superseded draft's reasoning: Conduit's own engine (goroutine scheduling, real file/network I/O, a +database driver, an HTTP/gRPC server) is a poor fit for any WASM sandbox today — WASI Preview 1 +has no engine-scale concurrency model, and the component model needed for anything richer is +NO-GO on a pure-Go host per `docs/architecture-decision-records/20260722-wasm-component-model-deferred.md`. +This doc's problem — "run the whole engine inside someone else's process" — is the opposite +direction from WASM's fit (a plugin running inside Conduit), and the two should never be +conflated. + +## Context + +### The control-plane API is already a complete lifecycle/status surface + +`proto/api/v1/api.proto` defines, with full CRUD-plus-lifecycle coverage, each exposed over HTTP +via `grpc-gateway` (`google.api.http` annotations, e.g. lines 320–325): + +- **`PipelineService`** (lines 318–602): `ListPipelines`, `CreatePipeline`, `GetPipeline`, + `UpdatePipeline`, `DeletePipeline`, `StartPipeline`, `StopPipeline`, `GetDLQ`, `UpdateDLQ`, + `ExportPipeline`, `ImportPipeline`, `PlanPipeline`, `ApplyPipeline`. +- **`ConnectorService`** (lines 767–927): `ListConnectors`, `InspectConnector` (server-streaming + record inspection), `GetConnector`, `CreateConnector`, `ValidateConnector`, `UpdateConnector`, + `DeleteConnector`, `ListConnectorPlugins`. +- **`ProcessorService`** (lines 1003–1138): `ListProcessors`, `InspectProcessorIn`/ + `InspectProcessorOut`, `GetProcessor`, `CreateProcessor`, `UpdateProcessor`, `DeleteProcessor`, + `ListProcessorPlugins`. +- Plus `GetInfo`/`ListPlugins` (lines 1203, 1225). + +`message Pipeline` embeds `message State { Status status; string error; StoppedReason +stopped_reason; }` (lines 41–75), returned inline by `GetPipeline`/`ListPipelines` — status is not +a separate call. **Gap, stated honestly:** there is no metrics RPC. Metrics are a separate +Prometheus `/metrics` HTTP endpoint outside the gRPC/OpenAPI surface +(`pkg/conduit/runtime.go:797–808`, mounted at `runtime.go:969`). The client library's `status()` +maps to `GetPipeline`; a metrics story is out of scope for v1 (see Observability). + +### The connector acquisition model already supports dial-by-address underneath + +`pkg/plugin/connector/standalone/dispenser.go:39–62` dispenses a connector by calling +`conduit-connector-protocol`'s `pconnector/client.New(logger, path, opts...)` +(`pconnector/client/client.go:34–78`), which builds `plugin.ClientConfig{Cmd: exec.CommandContext(..., +path), AllowedProtocols: [ProtocolGRPC], VersionedPlugins: {...clientv1/clientv2 gRPC stubs...}}` — +HashiCorp go-plugin's standard spawn model: Conduit execs the binary, the child writes a handshake +line to stdout, go-plugin parses the advertised address and dials it over gRPC. The registry +(`pkg/plugin/connector/standalone/registry.go:37–56`) keys each plugin by filesystem `Path`. + +go-plugin (`github.com/hashicorp/go-plugin@v1.8.0`, `client.go:299–315`, wired at `client.go:596–616` +and `973–1016`) already has a second, built-in acquisition path that skips spawning entirely: +`ClientConfig.Reattach *ReattachConfig{Addr net.Addr, Pid int, Protocol, ProtocolVersion}` dials a +**pre-known address** directly, using the identical `VersionedPlugins` gRPC client stubs as the +spawn path. **Nothing in `conduit-connector-protocol`'s wire messages changes between the two +paths** — `Reattach` only changes how the client _finds_ the server. The Python connector SDK +design independently confirms the handshake and gRPC server aren't Go-specific +(`docs/design-documents/20260707-python-connector-sdk.md:1–17, 163`): a Python-authored connector +already writes the handshake line and serves a plain gRPC server any `clientv1`/`clientv2` stub can +dial. + +**External connector = a new dispenser variant, sibling to `standalone.Dispenser`, that builds +`plugin.ClientConfig{Reattach: &plugin.ReattachConfig{Addr: ...}}` instead of `{Cmd: cmd}`, and a +registry entry keyed by address instead of path.** No protocol change. New engine-side code: +Slice 2 below. + +### The pipeline config model and state layer already exist + +`pkg/provisioning/config/parser.go:30–38` defines `Pipeline{ID, Status, Name, Description, +Connectors, Processors, DLQ}` — the target shape for any code-first builder — re-exported +unmodified at the embed API root: `conduit.go:37`, `type PipelineConfig = +provisioningconfig.Pipeline` (a type alias, not a copy; B1, merged, commit `e6a8b9f`). + +The local KV store is Badger (`github.com/conduitio/conduit-commons/database/badger`), wired at +`pkg/conduit/runtime.go:336`: `badger.New(logger.Logger, cfg.DB.Badger.Path)`. `Options.DB` +(`conduit.go:82–104`) defaults to `DBTypeInMemory` when `Type == ""` — its own doc comment already +warns "every pipeline configuration is lost once the Engine stops." Positions persist inside +`connector.Instance.State` (`pkg/connector/source.go:227`), written through +`pkg/connector/store.go`'s `Store`, itself a thin layer over the generic `database.DB` interface — +Badger-durable, or in-memory and gone on process exit, depending on `Options.DB.Type`. This is not +theoretical: `tests/chaos` (added in commit `045f283`, the just-landed sev-0 fix for `Source.Ack`'s +ack-before-persist ordering, `pkg/connector/source.go:207–238`) already SIGKILLs mid-snapshot and +mid-stream against a **real on-disk Badger DB** to verify recovery — the same store `local()` uses. + +### What already ships, unaffected by this design + +B1 (`conduit.New`, `Engine.Run`/`Handle.Stop`/`Engine.Close`, `Engine.Import`, +`Engine.StartPipeline`/`StopPipeline`) is merged at the repo root (`conduit.go`). `Options.API` +(`conduit.go:107–117`, `APIOptions{Enabled, GRPCAddress, HTTPAddress}`) already turns on the exact +gRPC/HTTP surface this design's client libraries dial — `conduit.local()`'s "spawn a `conduit` +process with its API enabled on loopback, then speak gRPC to it" needs **zero new engine code**. +B2 (`builder.go`, in flight, not yet merged) is unaffected — it's Go-side plumbing each language's +own builder mirrors in its own idiom, never crossing a boundary. + +## Decision — client-library API design + +### Pipeline builder + +Each binding exposes a fluent builder idiomatic to its language, producing the same +`config.Pipeline`/`PipelineConfig` shape the YAML provisioner parses (mirroring B2's `Build()` +contract — raw, unenriched output, validated the same way `conduit pipeline validate` validates): + +```python +# Python (illustrative) +pipeline = ( + conduit.Pipeline("orders-sync") + .source("postgres", url="postgres://...", tables=["orders"]) + .destination("kafka", brokers=["localhost:9092"], topic="orders") + .process("orders", "filter.field", condition="orders.deleted == false") +) +``` + +```javascript +// Node (illustrative) +const pipeline = new Pipeline("orders-sync") + .source("postgres", { url: "postgres://...", tables: ["orders"] }) + .destination("kafka", { brokers: ["localhost:9092"], topic: "orders" }); +``` + +`Settings` is a flat `map` today (`config.Connector.Settings`, +`config.Processor.Settings`) — v1 bindings pass and validate strings; typed config +(`postgres.Source(url=..., tables=[...])` with real parameter types) is a fast-follow (see Build +slices) generated from connector param specs, not hand-written per connector. + +### `conduit.local()` and `conduit.connect(addr)` + +Both return the same client object; only construction differs: + +- **`conduit.local(state_dir=..., binary=None)`** spawns and supervises a `conduit` subprocess: + `conduit run --api.enabled --api.grpc-address=127.0.0.1:0 --db.type=badger + --db.badger.path=` (or the equivalent `Options` if a future Go shim embeds B1 + directly instead of shelling to the released binary — an open question, see Open questions). + Discovers the OS-assigned port from the subprocess's structured startup log line, then dials it. + Owns the subprocess lifecycle: killed on client `close()`/`__exit__`, or on host-process exit via + an atexit/finalizer hook — best-effort, not guaranteed (see Failure modes). +- **`conduit.connect(addr)`** dials an already-running, independently deployed Conduit service at + `addr`. No process ownership — `close()` just closes the gRPC channel. + +Both expose the same async/idiomatic surface: `pipeline.deploy()` → `CreatePipeline`/`Import`, +returning a **run handle** — `run.status()` → `GetPipeline` (reads `Pipeline.State`), `run.stop()` +→ `StopPipeline`, `run.wait(timeout)` polling or streaming status until terminal. Errors surface +as the existing `conduiterr.ConduitError` shape (code, message, config path, suggestion, docs URL) +already crossing the CLI/API/MCP boundary today via `google.rpc.Status`/`ErrorInfo` — reused +as-is, not reinvented, translated into an idiomatic exception per language +(`conduit.ConduitError` in Python, `class ConduitError extends Error` in Node). + +### `inline_source` / `inline_destination` + +```python +pipeline.source(MyPythonSource()) # a conduit-connector-sdk-python Source, not a plugin name +``` + +The host process runs the Python-SDK connector's gRPC server in-process (the exact server shape +`docs/design-documents/20260707-python-connector-sdk.md` already designs), and the client library +registers its address with the (local or remote) engine as an **external connector** — see below. +This is Case B; plain named connectors (Case A) never touch this path. + +## External-connector feature (Slice 2) + +Address-based connector acquisition: the engine dials a pre-known `host:port` instead of spawning +a binary, reusing go-plugin's `ReattachConfig` path (Context) — no `conduit-connector-protocol` +wire change. Concretely: a new sibling to `pkg/plugin/connector/standalone.Dispenser` that accepts +an address instead of a path, and a pipeline-config extension so `config.Connector` can reference +`address: host:port` instead of `plugin: @`. **Reachability requirement, stated +plainly:** the engine process must be able to open a TCP connection to the given address. For +`conduit.local()` (Case A/B, engine co-located with the host on the same machine) this is +trivially loopback. For `conduit.connect(addr)` with an inline connector (Case B against a remote +engine), the **remote engine** must be able to reach back to the **host process** — a real +network-reachability requirement (open inbound port, no NAT/firewall in the way) that does not +exist in the local case and must never be glossed over in docs or error messages: a connector +registration should fail fast with an actionable error if the engine cannot reach the given +address at registration time, not time out silently mid-run. + +## Deployment modes — framed honestly + +**Mode 1 — `conduit.local()`: engine co-located, lifecycle tied to the host application.** Fits +dev, notebooks, one-off jobs, single-host batch/ETL. Requires an explicit, non-ephemeral +`state_dir` (backing `Options.DB.Badger.Path`) or at-least-once breaks the moment the host process +exits and the temp dir goes with it (Invariant 2, tied to the just-fixed sev-0 above). **Do not +sell Mode 1 as a production-pipeline story** — it has no independent lifecycle, no restart-without- +the-host-restarting, and no fleet management; a crash of the host application takes the engine +down with it (or leaves an orphaned subprocess if supervision is buggy — see Failure modes). + +**Mode 2 — `conduit.connect(addr)`: a deployed, long-running Conduit service.** Fits production. +The client library is a thin, stateless gRPC client; the engine's lifecycle, restart policy, and +persistence are managed independently (systemd, Kubernetes, the fleet console once it ships), not +by whatever process happens to import the client library. This is the mode a production pipeline +should use. + +**The Case-B host-reachability wrinkle applies only when combining Mode 2 with +`inline_source`/`inline_destination`**: a remote engine must be able to open a connection back to +the host process running the connector server. This is a real operational constraint (the host +needs a reachable, stable address — awkward behind NAT, in a serverless function, or in a +short-lived job) that Case A (named connectors only) never encounters. Docs must state this +plainly: **`inline_*` connectors are a Mode-1 (local) feature first; Mode-2 support is explicitly +gated on solving this reachability problem**, not assumed to work out of the box. + +## Failure modes + +1. **`conduit` subprocess crash/restart (Mode 1).** The client library's supervisor detects the + dead process (exit code / closed gRPC channel) and surfaces a `ConduitError` on the next call + rather than hanging — it does not auto-restart transparently (auto-restart would silently + re-run `Import` against a corrupted or partial view of the previous state; the caller decides). + Mitigation: `run.status()` distinguishes "engine unreachable" from "pipeline degraded." +2. **Ephemeral state dir → position loss (Mode 1), tied to Invariant 2 and the just-fixed sev-0 + (`Source.Ack` ordering, `pkg/connector/source.go:207–238`, commit `045f283`).** `conduit.local()` + without an explicit `state_dir` defaults to in-memory (`Options.DB` zero value, + `DBTypeInMemory`) — every position is lost on process exit, silently, unless the client library + makes this failure mode impossible to reach by accident: `local()` should require an explicit + `state_dir` argument (no silent in-memory default) for any pipeline expected to survive a + restart, and the docs must say so before the first code sample, not in a footnote. +3. **Binary/version mismatch between the client library and the `conduit` engine (Mode 1).** The + library bundles or downloads a specific `conduit` version; if a user's system has an + incompatible or stale binary on `PATH` that gets picked up instead, behavior drifts silently. + Mitigation: the library never relies on `PATH` lookup for the binary it spawns (same lesson + `20260707-python-connector-sdk.md` already learned for connector subprocesses — no inherited + `PATH`); it pins and verifies the exact binary/version it provisioned, and `GetInfo` (already in + the control-plane API, line 1203) is queried at connect-time to confirm the running engine's + version is compatible with the client library's minimum-supported API version, failing fast + with an actionable error on mismatch — never a version-skewed protobuf field silently ignored. +4. **External-connector disconnect (Slice 2, Case B).** The host process running an inline + connector's gRPC server crashes or the connection drops mid-run. The engine observes this as an + ordinary connector-plugin failure (the same path an ordinary standalone plugin crash already + takes — Conduit's existing plugin-failure handling, not new machinery) and the pipeline + transitions to `STATUS_DEGRADED` with the error surfaced in `Pipeline.State.error`, per the + existing at-least-once floor (Invariant 3) — it is a DLQ/halt/degrade situation, never a silent + drop. +5. **Host↔engine control-channel loss mid-run (either mode).** If the gRPC channel between the + client library and the engine drops (network blip, engine restart) while a pipeline is running, + the _pipeline itself_ is unaffected — it is the engine's responsibility, not the client + library's, and continues running (or fails per its own invariants) independent of whether + anyone is watching. The client library's `run` handle must reconnect and re-fetch `GetPipeline` + state rather than assume the pipeline stopped just because the channel did — conflating "I lost + the connection" with "the pipeline stopped" would be a client-library bug, not an engine one. + +## Upgrade / rollback + +No new serialized/persisted format: `PipelineConfig` on the wire is the same JSON/protobuf-derived +shape the control-plane API and YAML provisioning already use, governed by the same announce → +warn → remove policy `parser.go:22–29` already documents. A client-library version bump is an +ordinary package upgrade (`pip install --upgrade`, `npm update`); rollback is reinstalling the +prior version. The one real compatibility surface is **client-library-minimum-API-version vs. +engine-`GetInfo`-version** (Failure mode 3) — each client library release documents its minimum +supported engine version, checked at `connect()`/`local()` startup, not discovered as a runtime +protobuf-decode error later. + +## Observability + +`run.status()` (`GetPipeline`) and log streaming are the v1 story. Metrics are explicitly deferred +to the existing `/metrics` Prometheus endpoint (`pkg/conduit/runtime.go:797–808`) — scraped +directly by the host's own monitoring, or via the host's per-language Prometheus client — not a +new client-library API. `InspectConnector`/`InspectProcessorIn`/`InspectProcessorOut` (existing +server-streaming RPCs, lines 775, 1011, 1018) are directly usable by the client library for a +`pipeline.inspect()`-style debugging affordance without any new engine surface. + +## Build slices + +- **Slice 1 — Case-A Python client library. No core (Go) changes.** Uses only the existing + control-plane API and `Options.API` (already merged). Builder, `local()`/`connect()`, run handle, + `ConduitError` translation, named (non-inline) connectors only. +- **Slice 2 — External-connector engine feature → unlocks Case B.** New address-based dispenser + variant (Context), pipeline-config `address:` field, reachability validation at registration. + Tier 1 (touches connector acquisition, a data-path-adjacent surface) — needs its own design-doc + sign-off pass at implementation time, not rubber-stamped by this doc alone. +- **Slice 3 — Node client library.** Mirrors Slice 1 in `@grpc/grpc-js`/idiomatic JS/TS, once + Python has proven the pattern. +- **Fast-follow — typed-config codegen.** Generate typed builder methods + (`postgres.Source(url=..., tables=[...])`) from each connector's param spec + (`connector.yaml`/registry `Specification`), replacing the flat `Settings` map with real + per-connector types and IDE-visible parameters — not hand-maintained per connector. + +## Open questions for DeVaris + +1. **Repo/packaging home.** A new `conduit-sdk-python` (embedding), distinct from + `conduit-connector-sdk-python` (connector authoring, a different persona)? What PyPI name — the + superseded C-ABI draft proposed `conduit-embed`; does that still read right for a gRPC-based + client library, or does something like `conduit-client`/`conduit` (if unclaimed) fit better? +2. **Release target.** Does Slice 1 (Python client library, no core changes) target a v0.19 + fast-follow, or land as the v0.20 anchor alongside the Python connector SDK? +3. **Binary-provisioning mechanism for `conduit.local()`.** Bundle a `conduit` binary inside the + Python wheel per platform (heavier package, no network dependency, mirrors the C-ABI draft's + wheel-packaging discipline), or download-on-first-use with version pinning and a checksum + (lighter package, needs network access and a trust story)? Either needs the version-check + discipline in Failure mode 3 regardless of which is chosen. + +## Related + +- `docs/architecture-decision-records/20260724-embed-bindings-via-grpc.md` — the ADR this design + doc implements. +- `docs/design-documents/20260705-sdk-and-embedding-dx.md` §B3 — the original embedding vision, + reframed here from C-ABI to gRPC. +- `docs/design-documents/20260722-embed-libconduit-v1.md` — the merged B1 (+ in-review B2) Go + embedding API this design's client libraries sit on top of via gRPC. +- `docs/design-documents/20260707-python-connector-sdk.md` — confirms the connector-protocol + handshake and gRPC server aren't Go-specific; the precedent `inline_source`/`inline_destination` + and the external-connector feature build on; also the source of the "no inherited `PATH`" / + self-contained-artifact packaging lesson reused in Failure mode 3. +- `proto/api/v1/api.proto` — the control-plane API surface this design's client libraries drive. +- `pkg/plugin/connector/standalone/dispenser.go`, `registry.go` — the spawn-by-path dispenser + Slice 2's external-connector feature adds a dial-by-address sibling to. +- `pkg/connector/source.go:207–238`, `tests/chaos` (commit `045f283`) — the ack/position + durability behavior Failure mode 2's `state_dir` guidance is grounded in (Invariant 2). +- `pkg/foundation/cerrors/conduiterr/conduiterr.go` — the `ConduitError` shape this design's error + propagation reuses unchanged. +- `ROADMAP.md`, "Embedded v1" — Phase 1/Phase 2 mapping for B1/B2 (Go) vs. this design's client + libraries (Python/Node); the client-library work is a distinct workstream, not a Phase-2-only + gate the way the superseded C-ABI draft assumed. diff --git a/examples/quickstart.py b/examples/quickstart.py new file mode 100644 index 0000000..b938023 --- /dev/null +++ b/examples/quickstart.py @@ -0,0 +1,22 @@ +"""The README/design-doc quickstart, runnable as-is (``python examples/quickstart.py``). + +Downloads a pinned `conduit` binary on first use (see +`conduit._provision.DEFAULT_CONDUIT_VERSION`), spawns it, creates and runs a +`generator -> log` pipeline, and gracefully stops it. This exact block -- +minus this module docstring -- is the ≤15-line quickstart quoted in +`README.md` and `src/conduit/__init__.py`. +""" + +import conduit + +pipeline = ( + conduit.Pipeline("orders-sync") + .source("generator", settings={"format.type": "structured"}, operations="create") + .destination("log", level="info") +) + +with conduit.local(state_dir="./conduit-state") as client: + run = client.run(pipeline) + run.wait_running() + print(run.status()) + run.stop() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c9754f9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,194 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +# NOTE (open question for DeVaris, see README "Open questions"): final PyPI +# distribution name is not settled -- the design doc's ADR left "conduit-embed" +# vs. "conduit-client" vs. bare "conduit" (if unclaimed) open. Using +# "conduit-client" here as the working name; trivial to rename before a real +# release (no PyPI publish workflow is wired yet -- see .github/workflows/release.yml). +name = "conduit-client" +# NOTE: version is a placeholder until the first tagged release; the release +# workflow will drive this from git tags (matches conduit-connector-sdk-python's +# not-yet-automated release story). +version = "0.1.0.dev0" +description = "Python client library for defining and running Conduit pipelines over its control-plane gRPC API." +readme = "README.md" +license = "Apache-2.0" +requires-python = ">=3.11" +authors = [{ name = "Conduit contributors" }] +keywords = ["conduit", "client", "sdk", "grpc", "embed", "etl", "cdc"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: Libraries", +] + +dependencies = [ + "grpcio>=1.75,<2", + "protobuf>=6.31,<8", + # Decodes the rich google.rpc.Status / google.rpc.ErrorInfo detail Conduit's + # server attaches to every ConduitError crossing the gRPC boundary + # (pkg/foundation/cerrors/conduiterr/status.go's ToStatus) -- reason, + # configPath, suggestion, docsUrl. This is the standard companion package + # for grpc's rich-error-model API (grpc_status.rpc_status.from_call), not + # hand-rolled trailer parsing. See conduit/errors.py. + "grpcio-status>=1.75,<2", + # Provides google.rpc.error_details_pb2 (ErrorInfo) that errors.py unpacks + # directly -- a transitive dep of grpcio-status already, declared + # explicitly because this package imports it by name, not just relies on + # what grpcio-status happens to pull in. + "googleapis-common-protos>=1.65,<2", + # Cross-platform user cache dir for the provisioned `conduit` binary + # (conduit.local()'s download-on-first-use path, see conduit/_provision.py). + # Small, pure-Python, no transitive deps -- the standard tool for this + # rather than hand-rolling XDG/macOS/Windows cache-path logic per platform. + "platformdirs>=4,<5", +] + +[project.urls] +Homepage = "https://github.com/ConduitIO/conduit-client-python" +Repository = "https://github.com/ConduitIO/conduit-client-python" +Issues = "https://github.com/ConduitIO/conduit-client-python/issues" + +[project.optional-dependencies] +dev = [ + "grpcio-tools>=1.75,<2", + "ruff>=0.14,<1", + "mypy>=1.15,<2", + "pytest>=8.3,<9", + "grpc-stubs>=1.24,<2", + # Type stubs for google.protobuf -- without this, mypy reports "Library + # stubs not installed for google.protobuf" under strict mode. + "types-protobuf>=6.30,<7", + # Bounds the integration test's engine-startup wait so a genuine + # regression (engine never becomes reachable) fails CI promptly instead + # of hanging the run. + "pytest-timeout>=2.3,<3", +] + +[tool.hatch.build.targets.wheel] +packages = ["src/conduit"] + +[tool.hatch.build.targets.sdist] +include = ["src", "README.md", "LICENSE"] + +# --- ruff ------------------------------------------------------------------- +[tool.ruff] +target-version = "py311" +line-length = 100 +src = ["src", "tests", "examples"] +# Generated protobuf/grpc stubs (buf generate output, see tools/generate-stubs.sh) +# are vendored, not hand-written -- excluded from both lint and format. +extend-exclude = [ + "src/conduit/_grpc/**/*_pb2.py", + "src/conduit/_grpc/**/*_pb2_grpc.py", + "src/conduit/_grpc/**/*.pyi", +] + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "B", # bugbear + "C4", # comprehensions + "D", # pydocstyle (docstring coverage on the public API surface) + "RUF", # ruff-specific +] +ignore = [ + "D203", # conflicts with D211 (no-blank-line-before-class) + "D213", # conflicts with D212 (multi-line-summary-first-line) +] + +[tool.ruff.lint.per-file-ignores] +"src/conduit/_grpc/**/*_pb2.py" = ["ALL"] +"src/conduit/_grpc/**/*_pb2_grpc.py" = ["ALL"] +"src/conduit/_grpc/**/*.pyi" = ["ALL"] +"tests/**" = ["D"] +"examples/**" = ["D"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.isort] +# The generated stubs' top-level package names (api/config/opencdc/google/ +# protoc_gen_openapiv2) are reachable only because conduit._grpc's own +# __init__.py prepends its directory to sys.path at import time (see that +# module's docstring). Same reasoning and same fix as +# conduit-connector-sdk-python's identical isort config: without this, ruff's +# isort treats them as third-party and can reorder imports in a way that +# breaks the sys.path side-effect ordering -- a real correctness bug, not a +# style nit. +known-first-party = ["conduit", "api", "config", "opencdc"] + +[tool.ruff.format] +quote-style = "double" + +# --- mypy -------------------------------------------------------------------- +[tool.mypy] +python_version = "3.11" +strict = true +packages = ["conduit"] +# src/conduit/_grpc is also on mypy_path (not just src): protoc's Python +# codegen emits *absolute* imports rooted at each .proto file's own package +# path (e.g. `from config.v1 import parameter_pb2`), not imports nested under +# conduit._grpc -- see src/conduit/_grpc/__init__.py's docstring. mypy has no +# equivalent of the runtime sys.path mutation that module performs, so it +# needs the same directory on mypy_path statically to find `api.v1`/ +# `config.v1`/`opencdc.v1`/`google.api`/`protoc_gen_openapiv2` as modules. +mypy_path = ["src", "src/conduit/_grpc"] +exclude = [ + "src/conduit/_grpc/.*_pb2.*", + # These subtrees are reached exclusively via the top-level api/config/ + # opencdc/google/protoc_gen_openapiv2 names (the second mypy_path entry + # above) -- excluding them here too stops mypy's `packages` walk from + # also discovering them as conduit._grpc.api/.config/etc, which would + # otherwise collide ("Source file found twice under different module + # names") since both paths resolve to the same files. + "^src/conduit/_grpc/api/", + "^src/conduit/_grpc/config/", + "^src/conduit/_grpc/opencdc/", + "^src/conduit/_grpc/google/", + "^src/conduit/_grpc/protoc_gen_openapiv2/", +] + +[[tool.mypy.overrides]] +# Generated stubs are not held to strict mode; they're vendored, regenerated +# output. +module = [ + "conduit._grpc.*", + "api.*", + "config.*", + "opencdc.*", + "google.api.*", + "protoc_gen_openapiv2.*", +] +ignore_errors = true + +[[tool.mypy.overrides]] +# googleapis-common-protos ships .pyi files for google.rpc.* but no py.typed +# marker, so mypy treats it as untyped by default even though real stubs +# exist on disk; same for grpc_status (grpcio-status), which ships no stubs +# at all. Both are third-party, not vendored by this repo -- ignore-missing +# rather than ignore-errors (there's no local file to exclude). +module = ["google.rpc.*", "grpc_status.*"] +ignore_missing_imports = true + +# --- pytest ------------------------------------------------------------------ +[tool.pytest.ini_options] +testpaths = ["tests"] +markers = [ + "integration: requires a real `conduit` binary (network download or PATH); skipped by default in sandboxed/offline runs", +] +# A generous global ceiling so a genuine regression that hangs a test (e.g. +# conduit.local() never becoming reachable) fails CI with a clear timeout +# instead of hanging the whole run indefinitely. +timeout = 60 diff --git a/src/conduit/__init__.py b/src/conduit/__init__.py new file mode 100644 index 0000000..3c1fa47 --- /dev/null +++ b/src/conduit/__init__.py @@ -0,0 +1,58 @@ +"""Python client library for defining and running Conduit pipelines in code. + +Slice 1 (Case A: named connector plugins only -- see +``docs/design/20260724-embed-grpc-client-libraries.md``, "Build slices"), a +gRPC client over Conduit's existing control-plane API +(``proto/api/v1/api.proto``), per +``docs/design/20260724-embed-bindings-via-grpc.md`` (the ADR this design +implements). + +Quickstart:: + + import conduit + + pipeline = ( + conduit.Pipeline("orders-sync") + .source("generator", settings={"format.type": "structured"}, operations="create") + .destination("log", level="info") + ) + + with conduit.local(state_dir="./conduit-state") as client: + run = client.run(pipeline) + run.wait_running() + print(run.status()) + run.stop() + +Public surface: :class:`Pipeline` (builder), :func:`local`/:func:`connect` +(client construction), :class:`~conduit.client.Client`, :class:`~conduit.run.Run` +/ :class:`~conduit.run.RunStatus`, and :class:`ConduitError`. + +**Not yet public** (fast-follows, see the design doc's "Build slices"): +typed per-connector config (``Settings`` is a flat ``map`` +today -- every keyword argument to ``.source()``/``.destination()``/ +``.process()`` is coerced to a string), ``inline_source``/ +``inline_destination`` (Case B, needs the external-connector engine feature, +Slice 2), and an async client. +""" + +from __future__ import annotations + +from conduit._local import LocalConduit, local +from conduit.client import Client, connect +from conduit.errors import ConduitError +from conduit.pipeline import Pipeline +from conduit.run import Run, RunStatus + +__version__ = "0.1.0.dev0" + +__all__ = [ + "Client", + "ConduitError", + "LocalConduit", + "Pipeline", + "Run", + "RunStatus", + "__version__", + "connect", + "local", +] diff --git a/src/conduit/_grpc/__init__.py b/src/conduit/_grpc/__init__.py new file mode 100644 index 0000000..abbc746 --- /dev/null +++ b/src/conduit/_grpc/__init__.py @@ -0,0 +1,54 @@ +"""Generated protobuf/grpc stubs for the control-plane API (``proto/api/v1/api.proto``). + +**Generated, vendored code below this package -- never hand-edit.** Regenerate via +``./tools/generate-stubs.sh`` (requires ``buf`` on ``PATH``; see that script and +``buf.gen.yaml`` for the exact BSR modules/paths pulled). + +Layout note / known tradeoff (same one ``conduit-connector-sdk-python`` documents, +reused here verbatim because the cause is identical): protoc's Python codegen +emits *absolute* imports rooted at each ``.proto`` file's own package path -- +e.g. ``api/v1/api.proto`` imports ``config/v1/parameter.proto`` and becomes +``from config.v1 import parameter_pb2``, not an import nested under +``conduit._grpc``. Rewriting generated output to nest those imports would mean +hand-patching files explicitly marked "NO CHECKED-IN PROTOBUF GENCODE / DO NOT +EDIT", which is fragile across regenerations. Instead, this module prepends its +own directory to ``sys.path`` once, at import time, so the absolute imports the +generated code already contains resolve correctly as long as something has +imported ``conduit._grpc`` (directly or transitively) before importing e.g. +``api.v1.api_pb2``. + +**Why this package vendors more than just ``api/v1``:** ``api.proto`` imports +``config/v1/parameter.proto`` and ``opencdc/v1/opencdc.proto`` (from +``conduit-commons``), plus ``google/api/annotations.proto``, +``google/api/field_behavior.proto`` and +``protoc-gen-openapiv2/options/annotations.proto`` (used as message/method +options, e.g. ``google.api.http``) from ``googleapis``/``grpc-gateway``. The +Python protobuf runtime resolves a message's full descriptor -- including its +declared options -- through the descriptor pool at import time, so every +transitively imported ``.proto`` file needs a corresponding generated Python +module physically present, even though this client only ever constructs and +reads a handful of ``api.v1`` message types directly. ``api_pb2.py`` is the +only module here with request/response types this package's public API +touches; the ``config``, ``opencdc``, ``google.api`` and +``protoc_gen_openapiv2`` trees exist solely so ``import api.v1.api_pb2`` +succeeds. + +**Tradeoff, stated plainly:** this makes top-level names like ``api``, +``config``, ``opencdc``, ``google``, and ``protoc_gen_openapiv2`` resolvable as +importable modules process-wide once this package has been imported -- ``api``, +``config``, and ``google`` in particular are generic enough that a real (if +low-probability) collision risk exists with an unrelated third-party package of +the same name installed in the same environment. Acceptable for a Slice-1 +vendored-stub layer behind the internal ``_grpc/`` boundary (callers never +import ``conduit._grpc`` submodules directly -- only ``conduit``'s public +API does); revisit if this becomes a real collision in practice. +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +_STUB_ROOT = str(Path(__file__).parent) +if _STUB_ROOT not in sys.path: + sys.path.insert(0, _STUB_ROOT) diff --git a/src/conduit/_grpc/api/v1/api_pb2.py b/src/conduit/_grpc/api/v1/api_pb2.py new file mode 100644 index 0000000..a6844b4 --- /dev/null +++ b/src/conduit/_grpc/api/v1/api_pb2.py @@ -0,0 +1,381 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: api/v1/api.proto +# Protobuf Python Version: 5.29.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 2, + '', + 'api/v1/api.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from config.v1 import parameter_pb2 as config_dot_v1_dot_parameter__pb2 +from google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2 +from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2 +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +from opencdc.v1 import opencdc_pb2 as opencdc_dot_v1_dot_opencdc__pb2 +from protoc_gen_openapiv2.options import annotations_pb2 as protoc__gen__openapiv2_dot_options_dot_annotations__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x61pi/v1/api.proto\x12\x06\x61pi.v1\x1a\x19\x63onfig/v1/parameter.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x18opencdc/v1/opencdc.proto\x1a.protoc-gen-openapiv2/options/annotations.proto\"\xf3\x07\n\x08Pipeline\x12\x13\n\x02id\x18\x01 \x01(\tB\x03\xe0\x41\x03R\x02id\x12,\n\x05state\x18\x02 \x01(\x0b\x32\x16.api.v1.Pipeline.StateR\x05state\x12/\n\x06\x63onfig\x18\x03 \x01(\x0b\x32\x17.api.v1.Pipeline.ConfigR\x06\x63onfig\x12(\n\rconnector_ids\x18\x05 \x03(\tB\x03\xe0\x41\x03R\x0c\x63onnectorIds\x12(\n\rprocessor_ids\x18\x06 \x03(\tB\x03\xe0\x41\x03R\x0cprocessorIds\x12\x39\n\ncreated_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x39\n\nupdated_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\x1a\x80\x02\n\x05State\x12/\n\x06status\x18\x01 \x01(\x0e\x32\x17.api.v1.Pipeline.StatusR\x06status\x12\x14\n\x05\x65rror\x18\x02 \x01(\tR\x05\x65rror\x12K\n\x0estopped_reason\x18\x03 \x01(\x0e\x32$.api.v1.Pipeline.State.StoppedReasonR\rstoppedReason\"c\n\rStoppedReason\x12\x1e\n\x1aSTOPPED_REASON_UNSPECIFIED\x10\x00\x12\x17\n\x13STOPPED_REASON_USER\x10\x01\x12\x19\n\x15STOPPED_REASON_SYSTEM\x10\x02\x1a>\n\x06\x43onfig\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12 \n\x0b\x64\x65scription\x18\x02 \x01(\tR\x0b\x64\x65scription\x1a\xef\x01\n\x03\x44LQ\x12\x16\n\x06plugin\x18\x01 \x01(\tR\x06plugin\x12>\n\x08settings\x18\x02 \x03(\x0b\x32\".api.v1.Pipeline.DLQ.SettingsEntryR\x08settings\x12\x1f\n\x0bwindow_size\x18\x03 \x01(\x04R\nwindowSize\x12\x32\n\x15window_nack_threshold\x18\x04 \x01(\x04R\x13windowNackThreshold\x1a;\n\rSettingsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"t\n\x06Status\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\x12\n\x0eSTATUS_RUNNING\x10\x01\x12\x12\n\x0eSTATUS_STOPPED\x10\x02\x12\x13\n\x0fSTATUS_DEGRADED\x10\x03\x12\x15\n\x11STATUS_RECOVERING\x10\x04\"\xba\x07\n\tConnector\x12\x13\n\x02id\x18\x01 \x01(\tB\x03\xe0\x41\x03R\x02id\x12Q\n\x11\x64\x65stination_state\x18\x02 \x01(\x0b\x32\".api.v1.Connector.DestinationStateH\x00R\x10\x64\x65stinationState\x12\x42\n\x0csource_state\x18\x03 \x01(\x0b\x32\x1d.api.v1.Connector.SourceStateH\x00R\x0bsourceState\x12\x30\n\x06\x63onfig\x18\x04 \x01(\x0b\x32\x18.api.v1.Connector.ConfigR\x06\x63onfig\x12/\n\x04type\x18\x05 \x01(\x0e\x32\x16.api.v1.Connector.TypeB\x03\xe0\x41\x05R\x04type\x12\x1b\n\x06plugin\x18\x06 \x01(\tB\x03\xe0\x41\x05R\x06plugin\x12$\n\x0bpipeline_id\x18\x07 \x01(\tB\x03\xe0\x41\x05R\npipelineId\x12(\n\rprocessor_ids\x18\x08 \x03(\tB\x03\xe0\x41\x03R\x0cprocessorIds\x12\x39\n\ncreated_at\x18\t \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x39\n\nupdated_at\x18\n \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\x1a)\n\x0bSourceState\x12\x1a\n\x08position\x18\x01 \x01(\x0cR\x08position\x1a\xa1\x01\n\x10\x44\x65stinationState\x12O\n\tpositions\x18\x01 \x03(\x0b\x32\x31.api.v1.Connector.DestinationState.PositionsEntryR\tpositions\x1a<\n\x0ePositionsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\x0cR\x05value:\x02\x38\x01\x1a\x9d\x01\n\x06\x43onfig\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x42\n\x08settings\x18\x02 \x03(\x0b\x32&.api.v1.Connector.Config.SettingsEntryR\x08settings\x1a;\n\rSettingsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"C\n\x04Type\x12\x14\n\x10TYPE_UNSPECIFIED\x10\x00\x12\x0f\n\x0bTYPE_SOURCE\x10\x01\x12\x14\n\x10TYPE_DESTINATION\x10\x02\x42\x07\n\x05state\"\x8c\x05\n\tProcessor\x12\x13\n\x02id\x18\x01 \x01(\tB\x03\xe0\x41\x03R\x02id\x12\x30\n\x06\x63onfig\x18\x03 \x01(\x0b\x32\x18.api.v1.Processor.ConfigR\x06\x63onfig\x12\x1c\n\tcondition\x18\t \x01(\tR\tcondition\x12\x1b\n\x06plugin\x18\x05 \x01(\tB\x03\xe0\x41\x05R\x06plugin\x12\x35\n\x06parent\x18\x06 \x01(\x0b\x32\x18.api.v1.Processor.ParentB\x03\xe0\x41\x05R\x06parent\x12\x39\n\ncreated_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x39\n\nupdated_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\x1a\x90\x01\n\x06Parent\x12\x31\n\x04type\x18\x01 \x01(\x0e\x32\x1d.api.v1.Processor.Parent.TypeR\x04type\x12\x0e\n\x02id\x18\x02 \x01(\tR\x02id\"C\n\x04Type\x12\x14\n\x10TYPE_UNSPECIFIED\x10\x00\x12\x12\n\x0eTYPE_CONNECTOR\x10\x01\x12\x11\n\rTYPE_PIPELINE\x10\x02\x1a\xa3\x01\n\x06\x43onfig\x12\x42\n\x08settings\x18\x01 \x03(\x0b\x32&.api.v1.Processor.Config.SettingsEntryR\x08settings\x12\x18\n\x07workers\x18\x02 \x01(\x05R\x07workers\x1a;\n\rSettingsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01J\x04\x08\x02\x10\x03J\x04\x08\x04\x10\x05R\x05stateR\x04type\"\x9f\x04\n\x1d\x43onnectorPluginSpecifications\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07summary\x18\x02 \x01(\tR\x07summary\x12 \n\x0b\x64\x65scription\x18\x03 \x01(\tR\x0b\x64\x65scription\x12\x18\n\x07version\x18\x04 \x01(\tR\x07version\x12\x16\n\x06\x61uthor\x18\x05 \x01(\tR\x06\x61uthor\x12k\n\x12\x64\x65stination_params\x18\x06 \x03(\x0b\x32<.api.v1.ConnectorPluginSpecifications.DestinationParamsEntryR\x11\x64\x65stinationParams\x12\\\n\rsource_params\x18\x07 \x03(\x0b\x32\x37.api.v1.ConnectorPluginSpecifications.SourceParamsEntryR\x0csourceParams\x1aZ\n\x16\x44\x65stinationParamsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x14.config.v1.ParameterR\x05value:\x02\x38\x01\x1aU\n\x11SourceParamsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x14.config.v1.ParameterR\x05value:\x02\x38\x01\"\xcd\x02\n\x1dProcessorPluginSpecifications\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07summary\x18\x02 \x01(\tR\x07summary\x12 \n\x0b\x64\x65scription\x18\x03 \x01(\tR\x0b\x64\x65scription\x12\x18\n\x07version\x18\x04 \x01(\tR\x07version\x12\x16\n\x06\x61uthor\x18\x05 \x01(\tR\x06\x61uthor\x12U\n\nparameters\x18\x06 \x03(\x0b\x32\x35.api.v1.ProcessorPluginSpecifications.ParametersEntryR\nparameters\x1aS\n\x0fParametersEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x14.config.v1.ParameterR\x05value:\x02\x38\x01\"\xa1\t\n\x14PluginSpecifications\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07summary\x18\x02 \x01(\tR\x07summary\x12 \n\x0b\x64\x65scription\x18\x03 \x01(\tR\x0b\x64\x65scription\x12\x18\n\x07version\x18\x04 \x01(\tR\x07version\x12\x16\n\x06\x61uthor\x18\x05 \x01(\tR\x06\x61uthor\x12\x62\n\x12\x64\x65stination_params\x18\x06 \x03(\x0b\x32\x33.api.v1.PluginSpecifications.DestinationParamsEntryR\x11\x64\x65stinationParams\x12S\n\rsource_params\x18\x07 \x03(\x0b\x32..api.v1.PluginSpecifications.SourceParamsEntryR\x0csourceParams\x1a\xf2\x04\n\tParameter\x12 \n\x0b\x64\x65scription\x18\x01 \x01(\tR\x0b\x64\x65scription\x12\x18\n\x07\x64\x65\x66\x61ult\x18\x02 \x01(\tR\x07\x64\x65\x66\x61ult\x12?\n\x04type\x18\x03 \x01(\x0e\x32+.api.v1.PluginSpecifications.Parameter.TypeR\x04type\x12S\n\x0bvalidations\x18\x04 \x03(\x0b\x32\x31.api.v1.PluginSpecifications.Parameter.ValidationR\x0bvalidations\x1a\x8b\x02\n\nValidation\x12J\n\x04type\x18\x01 \x01(\x0e\x32\x36.api.v1.PluginSpecifications.Parameter.Validation.TypeR\x04type\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value\"\x96\x01\n\x04Type\x12\x14\n\x10TYPE_UNSPECIFIED\x10\x00\x12\x11\n\rTYPE_REQUIRED\x10\x01\x12\x15\n\x11TYPE_GREATER_THAN\x10\x02\x12\x12\n\x0eTYPE_LESS_THAN\x10\x03\x12\x12\n\x0eTYPE_INCLUSION\x10\x04\x12\x12\n\x0eTYPE_EXCLUSION\x10\x05\x12\x0e\n\nTYPE_REGEX\x10\x06\x1a\x02\x18\x01:\x02\x18\x01\"\x80\x01\n\x04Type\x12\x14\n\x10TYPE_UNSPECIFIED\x10\x00\x12\x0f\n\x0bTYPE_STRING\x10\x01\x12\x0c\n\x08TYPE_INT\x10\x02\x12\x0e\n\nTYPE_FLOAT\x10\x03\x12\r\n\tTYPE_BOOL\x10\x04\x12\r\n\tTYPE_FILE\x10\x05\x12\x11\n\rTYPE_DURATION\x10\x06\x1a\x02\x18\x01:\x02\x18\x01\x1al\n\x16\x44\x65stinationParamsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12<\n\x05value\x18\x02 \x01(\x0b\x32&.api.v1.PluginSpecifications.ParameterR\x05value:\x02\x38\x01\x1ag\n\x11SourceParamsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12<\n\x05value\x18\x02 \x01(\x0b\x32&.api.v1.PluginSpecifications.ParameterR\x05value:\x02\x38\x01:\x02\x18\x01\"*\n\x14ListPipelinesRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\"G\n\x15ListPipelinesResponse\x12.\n\tpipelines\x18\x01 \x03(\x0b\x32\x10.api.v1.PipelineR\tpipelines\"H\n\x15\x43reatePipelineRequest\x12/\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x17.api.v1.Pipeline.ConfigR\x06\x63onfig\"F\n\x16\x43reatePipelineResponse\x12,\n\x08pipeline\x18\x01 \x01(\x0b\x32\x10.api.v1.PipelineR\x08pipeline\"$\n\x12GetPipelineRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"C\n\x13GetPipelineResponse\x12,\n\x08pipeline\x18\x01 \x01(\x0b\x32\x10.api.v1.PipelineR\x08pipeline\"X\n\x15UpdatePipelineRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12/\n\x06\x63onfig\x18\x02 \x01(\x0b\x32\x17.api.v1.Pipeline.ConfigR\x06\x63onfig\"F\n\x16UpdatePipelineResponse\x12,\n\x08pipeline\x18\x01 \x01(\x0b\x32\x10.api.v1.PipelineR\x08pipeline\"\'\n\x15\x44\x65letePipelineRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"\x18\n\x16\x44\x65letePipelineResponse\"&\n\x14StartPipelineRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"\x17\n\x15StartPipelineResponse\";\n\x13StopPipelineRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n\x05\x66orce\x18\x02 \x01(\x08R\x05\x66orce\"\x16\n\x14StopPipelineResponse\"\x1f\n\rGetDLQRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"8\n\x0eGetDLQResponse\x12&\n\x03\x64lq\x18\x01 \x01(\x0b\x32\x14.api.v1.Pipeline.DLQR\x03\x64lq\"J\n\x10UpdateDLQRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12&\n\x03\x64lq\x18\x02 \x01(\x0b\x32\x14.api.v1.Pipeline.DLQR\x03\x64lq\";\n\x11UpdateDLQResponse\x12&\n\x03\x64lq\x18\x01 \x01(\x0b\x32\x14.api.v1.Pipeline.DLQR\x03\x64lq\"\'\n\x15\x45xportPipelineRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"F\n\x16\x45xportPipelineResponse\x12,\n\x08pipeline\x18\x01 \x01(\x0b\x32\x10.api.v1.PipelineR\x08pipeline\"E\n\x15ImportPipelineRequest\x12,\n\x08pipeline\x18\x01 \x01(\x0b\x32\x10.api.v1.PipelineR\x08pipeline\"F\n\x16ImportPipelineResponse\x12,\n\x08pipeline\x18\x01 \x01(\x0b\x32\x10.api.v1.PipelineR\x08pipeline\"\xc8\x08\n\x10PipelineDocument\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x16\n\x06status\x18\x02 \x01(\tR\x06status\x12\x12\n\x04name\x18\x03 \x01(\tR\x04name\x12 \n\x0b\x64\x65scription\x18\x04 \x01(\tR\x0b\x64\x65scription\x12\x42\n\nconnectors\x18\x05 \x03(\x0b\x32\".api.v1.PipelineDocument.ConnectorR\nconnectors\x12\x42\n\nprocessors\x18\x06 \x03(\x0b\x32\".api.v1.PipelineDocument.ProcessorR\nprocessors\x12.\n\x03\x64lq\x18\x07 \x01(\x0b\x32\x1c.api.v1.PipelineDocument.DLQR\x03\x64lq\x1a\xaa\x02\n\tConnector\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n\x04type\x18\x02 \x01(\tR\x04type\x12\x16\n\x06plugin\x18\x03 \x01(\tR\x06plugin\x12\x12\n\x04name\x18\x04 \x01(\tR\x04name\x12L\n\x08settings\x18\x05 \x03(\x0b\x32\x30.api.v1.PipelineDocument.Connector.SettingsEntryR\x08settings\x12\x42\n\nprocessors\x18\x06 \x03(\x0b\x32\".api.v1.PipelineDocument.ProcessorR\nprocessors\x1a;\n\rSettingsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\x1a\xf6\x01\n\tProcessor\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x16\n\x06plugin\x18\x02 \x01(\tR\x06plugin\x12L\n\x08settings\x18\x03 \x03(\x0b\x32\x30.api.v1.PipelineDocument.Processor.SettingsEntryR\x08settings\x12\x18\n\x07workers\x18\x04 \x01(\x05R\x07workers\x12\x1c\n\tcondition\x18\x05 \x01(\tR\tcondition\x1a;\n\rSettingsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\x1a\xf7\x01\n\x03\x44LQ\x12\x16\n\x06plugin\x18\x01 \x01(\tR\x06plugin\x12\x46\n\x08settings\x18\x02 \x03(\x0b\x32*.api.v1.PipelineDocument.DLQ.SettingsEntryR\x08settings\x12\x1f\n\x0bwindow_size\x18\x03 \x01(\x04R\nwindowSize\x12\x32\n\x15window_nack_threshold\x18\x04 \x01(\x04R\x13windowNackThreshold\x1a;\n\rSettingsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\x88\x02\n\x04\x44iff\x12\x1f\n\x0bpipeline_id\x18\x01 \x01(\tR\npipelineId\x12-\n\x07\x63hanges\x18\x02 \x03(\x0b\x32\x13.api.v1.Diff.ChangeR\x07\x63hanges\x12\x12\n\x04hash\x18\x03 \x01(\tR\x04hash\x1a\x9b\x01\n\x06\x43hange\x12\x1a\n\x08resource\x18\x01 \x01(\tR\x08resource\x12\x0e\n\x02id\x18\x02 \x01(\tR\x02id\x12\x16\n\x06\x61\x63tion\x18\x03 \x01(\tR\x06\x61\x63tion\x12\x16\n\x06\x65\x66\x66\x65\x63t\x18\x04 \x01(\tR\x06\x65\x66\x66\x65\x63t\x12!\n\x0c\x63onfig_paths\x18\x05 \x03(\tR\x0b\x63onfigPaths\x12\x12\n\x04\x63ode\x18\x06 \x01(\tR\x04\x63ode\"G\n\x13PlanPipelineRequest\x12\x30\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x18.api.v1.PipelineDocumentR\x06\x63onfig\"8\n\x14PlanPipelineResponse\x12 \n\x04\x64iff\x18\x01 \x01(\x0b\x32\x0c.api.v1.DiffR\x04\x64iff\"\\\n\x14\x41pplyPipelineRequest\x12\x30\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x18.api.v1.PipelineDocumentR\x06\x63onfig\x12\x12\n\x04hash\x18\x02 \x01(\tR\x04hash\"9\n\x15\x41pplyPipelineResponse\x12 \n\x04\x64iff\x18\x01 \x01(\x0b\x32\x0c.api.v1.DiffR\x04\x64iff\"\xaf\x01\n\x16\x43reateConnectorRequest\x12*\n\x04type\x18\x01 \x01(\x0e\x32\x16.api.v1.Connector.TypeR\x04type\x12\x16\n\x06plugin\x18\x02 \x01(\tR\x06plugin\x12\x1f\n\x0bpipeline_id\x18\x03 \x01(\tR\npipelineId\x12\x30\n\x06\x63onfig\x18\x04 \x01(\x0b\x32\x18.api.v1.Connector.ConfigR\x06\x63onfig\"J\n\x17\x43reateConnectorResponse\x12/\n\tconnector\x18\x01 \x01(\x0b\x32\x11.api.v1.ConnectorR\tconnector\"\x90\x01\n\x18ValidateConnectorRequest\x12*\n\x04type\x18\x01 \x01(\x0e\x32\x16.api.v1.Connector.TypeR\x04type\x12\x16\n\x06plugin\x18\x02 \x01(\tR\x06plugin\x12\x30\n\x06\x63onfig\x18\x04 \x01(\x0b\x32\x18.api.v1.Connector.ConfigR\x06\x63onfig\"\x1b\n\x19ValidateConnectorResponse\"8\n\x15ListConnectorsRequest\x12\x1f\n\x0bpipeline_id\x18\x01 \x01(\tR\npipelineId\"K\n\x16ListConnectorsResponse\x12\x31\n\nconnectors\x18\x01 \x03(\x0b\x32\x11.api.v1.ConnectorR\nconnectors\")\n\x17InspectConnectorRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"F\n\x18InspectConnectorResponse\x12*\n\x06record\x18\x01 \x01(\x0b\x32\x12.opencdc.v1.RecordR\x06record\"%\n\x13GetConnectorRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"G\n\x14GetConnectorResponse\x12/\n\tconnector\x18\x01 \x01(\x0b\x32\x11.api.v1.ConnectorR\tconnector\"r\n\x16UpdateConnectorRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x30\n\x06\x63onfig\x18\x02 \x01(\x0b\x32\x18.api.v1.Connector.ConfigR\x06\x63onfig\x12\x16\n\x06plugin\x18\x03 \x01(\tR\x06plugin\"J\n\x17UpdateConnectorResponse\x12/\n\tconnector\x18\x01 \x01(\x0b\x32\x11.api.v1.ConnectorR\tconnector\"(\n\x16\x44\x65leteConnectorRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"\x19\n\x17\x44\x65leteConnectorResponse\"1\n\x1bListConnectorPluginsRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\"_\n\x1cListConnectorPluginsResponse\x12?\n\x07plugins\x18\x01 \x03(\x0b\x32%.api.v1.ConnectorPluginSpecificationsR\x07plugins\"6\n\x15ListProcessorsRequest\x12\x1d\n\nparent_ids\x18\x01 \x03(\tR\tparentIds\"K\n\x16ListProcessorsResponse\x12\x31\n\nprocessors\x18\x01 \x03(\x0b\x32\x11.api.v1.ProcessorR\nprocessors\"+\n\x19InspectProcessorInRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"H\n\x1aInspectProcessorInResponse\x12*\n\x06record\x18\x01 \x01(\x0b\x32\x12.opencdc.v1.RecordR\x06record\",\n\x1aInspectProcessorOutRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"I\n\x1bInspectProcessorOutResponse\x12*\n\x06record\x18\x01 \x01(\x0b\x32\x12.opencdc.v1.RecordR\x06record\"\xca\x01\n\x16\x43reateProcessorRequest\x12\x16\n\x04type\x18\x01 \x01(\tB\x02\x18\x01R\x04type\x12\x30\n\x06parent\x18\x03 \x01(\x0b\x32\x18.api.v1.Processor.ParentR\x06parent\x12\x30\n\x06\x63onfig\x18\x04 \x01(\x0b\x32\x18.api.v1.Processor.ConfigR\x06\x63onfig\x12\x1c\n\tcondition\x18\x05 \x01(\tR\tcondition\x12\x16\n\x06plugin\x18\x06 \x01(\tR\x06plugin\"J\n\x17\x43reateProcessorResponse\x12/\n\tprocessor\x18\x01 \x01(\x0b\x32\x11.api.v1.ProcessorR\tprocessor\"%\n\x13GetProcessorRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"G\n\x14GetProcessorResponse\x12/\n\tprocessor\x18\x01 \x01(\x0b\x32\x11.api.v1.ProcessorR\tprocessor\"r\n\x16UpdateProcessorRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x30\n\x06\x63onfig\x18\x02 \x01(\x0b\x32\x18.api.v1.Processor.ConfigR\x06\x63onfig\x12\x16\n\x06plugin\x18\x03 \x01(\tR\x06plugin\"J\n\x17UpdateProcessorResponse\x12/\n\tprocessor\x18\x01 \x01(\x0b\x32\x11.api.v1.ProcessorR\tprocessor\"(\n\x16\x44\x65leteProcessorRequest\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"\x19\n\x17\x44\x65leteProcessorResponse\"1\n\x1bListProcessorPluginsRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\"_\n\x1cListProcessorPluginsResponse\x12?\n\x07plugins\x18\x01 \x03(\x0b\x32%.api.v1.ProcessorPluginSpecificationsR\x07plugins\"\x10\n\x0eGetInfoRequest\"3\n\x0fGetInfoResponse\x12 \n\x04info\x18\x01 \x01(\x0b\x32\x0c.api.v1.InfoR\x04info\"D\n\x04Info\x12\x18\n\x07version\x18\x01 \x01(\tR\x07version\x12\x0e\n\x02os\x18\x02 \x01(\tR\x02os\x12\x12\n\x04\x61rch\x18\x03 \x01(\tR\x04\x61rch\",\n\x12ListPluginsRequest\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name:\x02\x18\x01\"Q\n\x13ListPluginsResponse\x12\x36\n\x07plugins\x18\x01 \x03(\x0b\x32\x1c.api.v1.PluginSpecificationsR\x07plugins:\x02\x18\x01\x32\xc4\x19\n\x0fPipelineService\x12n\n\rListPipelines\x12\x1c.api.v1.ListPipelinesRequest\x1a\x1d.api.v1.ListPipelinesResponse\" \x82\xd3\xe4\x93\x02\x1a\x12\r/v1/pipelinesb\tpipelines\x12\xe7\x02\n\x0e\x43reatePipeline\x12\x1d.api.v1.CreatePipelineRequest\x1a\x1e.api.v1.CreatePipelineResponse\"\x95\x02\x92\x41\xef\x01Jw\n\x03\x34\x30\x30\x12p\x12\x16\n\x14\x1a\x12.google.rpc.Status\"V\n\x10\x61pplication/json\x12\x42{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }Jt\n\x03\x34\x30\x39\x12m\x12\x16\n\x14\x1a\x12.google.rpc.Status\"S\n\x10\x61pplication/json\x12?{ \"code\": 6, \"message\": \"already exists error\", \"details\": [] }\x82\xd3\xe4\x93\x02\x1c\"\r/v1/pipelines:\x01*b\x08pipeline\x12\xea\x01\n\x0bGetPipeline\x12\x1a.api.v1.GetPipelineRequest\x1a\x1b.api.v1.GetPipelineResponse\"\xa1\x01\x92\x41zJx\n\x03\x34\x30\x34\x12q\x12\x16\n\x14\x1a\x12.google.rpc.Status\"W\n\x10\x61pplication/json\x12\x43{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\x82\xd3\xe4\x93\x02\x1e\x12\x12/v1/pipelines/{id}b\x08pipeline\x12\xe6\x03\n\x0eUpdatePipeline\x12\x1d.api.v1.UpdatePipelineRequest\x1a\x1e.api.v1.UpdatePipelineResponse\"\x94\x03\x92\x41\xe9\x02Jw\n\x03\x34\x30\x30\x12p\x12\x16\n\x14\x1a\x12.google.rpc.Status\"V\n\x10\x61pplication/json\x12\x42{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }Jx\n\x03\x34\x30\x34\x12q\x12\x16\n\x14\x1a\x12.google.rpc.Status\"W\n\x10\x61pplication/json\x12\x43{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }Jt\n\x03\x34\x30\x39\x12m\x12\x16\n\x14\x1a\x12.google.rpc.Status\"S\n\x10\x61pplication/json\x12?{ \"code\": 6, \"message\": \"already exists error\", \"details\": [] }\x82\xd3\xe4\x93\x02!\x1a\x12/v1/pipelines/{id}:\x01*b\x08pipeline\x12\xe5\x02\n\x0e\x44\x65letePipeline\x12\x1d.api.v1.DeletePipelineRequest\x1a\x1e.api.v1.DeletePipelineResponse\"\x93\x02\x92\x41\xf5\x01Jy\n\x03\x34\x30\x30\x12r\x12\x16\n\x14\x1a\x12.google.rpc.Status\"X\n\x10\x61pplication/json\x12\x44{ \"code\": 9, \"message\": \"failed precondition error\", \"details\": [] }Jx\n\x03\x34\x30\x34\x12q\x12\x16\n\x14\x1a\x12.google.rpc.Status\"W\n\x10\x61pplication/json\x12\x43{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\x82\xd3\xe4\x93\x02\x14*\x12/v1/pipelines/{id}\x12\xe8\x02\n\rStartPipeline\x12\x1c.api.v1.StartPipelineRequest\x1a\x1d.api.v1.StartPipelineResponse\"\x99\x02\x92\x41\xf5\x01Jy\n\x03\x34\x30\x30\x12r\x12\x16\n\x14\x1a\x12.google.rpc.Status\"X\n\x10\x61pplication/json\x12\x44{ \"code\": 9, \"message\": \"failed precondition error\", \"details\": [] }Jx\n\x03\x34\x30\x34\x12q\x12\x16\n\x14\x1a\x12.google.rpc.Status\"W\n\x10\x61pplication/json\x12\x43{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\x82\xd3\xe4\x93\x02\x1a\"\x18/v1/pipelines/{id}/start\x12\xe7\x02\n\x0cStopPipeline\x12\x1b.api.v1.StopPipelineRequest\x1a\x1c.api.v1.StopPipelineResponse\"\x9b\x02\x92\x41\xf5\x01Jy\n\x03\x34\x30\x30\x12r\x12\x16\n\x14\x1a\x12.google.rpc.Status\"X\n\x10\x61pplication/json\x12\x44{ \"code\": 9, \"message\": \"failed precondition error\", \"details\": [] }Jx\n\x03\x34\x30\x34\x12q\x12\x16\n\x14\x1a\x12.google.rpc.Status\"W\n\x10\x61pplication/json\x12\x43{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\x82\xd3\xe4\x93\x02\x1c\"\x17/v1/pipelines/{id}/stop:\x01*\x12j\n\x06GetDLQ\x12\x15.api.v1.GetDLQRequest\x1a\x16.api.v1.GetDLQResponse\"1\x82\xd3\xe4\x93\x02+\x12$/v1/pipelines/{id}/dead-letter-queueb\x03\x64lq\x12x\n\tUpdateDLQ\x12\x18.api.v1.UpdateDLQRequest\x1a\x19.api.v1.UpdateDLQResponse\"6\x82\xd3\xe4\x93\x02\x30\x1a$/v1/pipelines/{id}/dead-letter-queue:\x03\x64lqb\x03\x64lq\x12|\n\x0e\x45xportPipeline\x12\x1d.api.v1.ExportPipelineRequest\x1a\x1e.api.v1.ExportPipelineResponse\"+\x82\xd3\xe4\x93\x02%\"\x19/v1/pipelines/{id}/exportb\x08pipeline\x12\x81\x01\n\x0eImportPipeline\x12\x1d.api.v1.ImportPipelineRequest\x1a\x1e.api.v1.ImportPipelineResponse\"0\x82\xd3\xe4\x93\x02*\"\x14/v1/pipelines/import:\x08pipelineb\x08pipeline\x12n\n\x0cPlanPipeline\x12\x1b.api.v1.PlanPipelineRequest\x1a\x1c.api.v1.PlanPipelineResponse\"#\x82\xd3\xe4\x93\x02\x1d\"\x12/v1/pipelines/plan:\x01*b\x04\x64iff\x12\xeb\x02\n\rApplyPipeline\x12\x1c.api.v1.ApplyPipelineRequest\x1a\x1d.api.v1.ApplyPipelineResponse\"\x9c\x02\x92\x41\xf4\x01Jw\n\x03\x34\x30\x30\x12p\x12\x16\n\x14\x1a\x12.google.rpc.Status\"V\n\x10\x61pplication/json\x12\x42{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }Jy\n\x03\x34\x31\x32\x12r\x12\x16\n\x14\x1a\x12.google.rpc.Status\"X\n\x10\x61pplication/json\x12\x44{ \"code\": 9, \"message\": \"failed precondition error\", \"details\": [] }\x82\xd3\xe4\x93\x02\x1e\"\x13/v1/pipelines/apply:\x01*b\x04\x64iff2\x8e\x10\n\x10\x43onnectorService\x12s\n\x0eListConnectors\x12\x1d.api.v1.ListConnectorsRequest\x1a\x1e.api.v1.ListConnectorsResponse\"\"\x82\xd3\xe4\x93\x02\x1c\x12\x0e/v1/connectorsb\nconnectors\x12\x84\x01\n\x10InspectConnector\x12\x1f.api.v1.InspectConnectorRequest\x1a .api.v1.InspectConnectorResponse\"+\x82\xd3\xe4\x93\x02%\x12\x1b/v1/connectors/{id}/inspectb\x06record0\x01\x12\xef\x01\n\x0cGetConnector\x12\x1b.api.v1.GetConnectorRequest\x1a\x1c.api.v1.GetConnectorResponse\"\xa3\x01\x92\x41zJx\n\x03\x34\x30\x34\x12q\x12\x16\n\x14\x1a\x12.google.rpc.Status\"W\n\x10\x61pplication/json\x12\x43{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\x82\xd3\xe4\x93\x02 \x12\x13/v1/connectors/{id}b\tconnector\x12\xf5\x01\n\x0f\x43reateConnector\x12\x1e.api.v1.CreateConnectorRequest\x1a\x1f.api.v1.CreateConnectorResponse\"\xa0\x01\x92\x41yJw\n\x03\x34\x30\x30\x12p\x12\x16\n\x14\x1a\x12.google.rpc.Status\"V\n\x10\x61pplication/json\x12\x42{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }\x82\xd3\xe4\x93\x02\x1e\"\x0e/v1/connectors:\x01*b\tconnector\x12\xa2\x03\n\x11ValidateConnector\x12 .api.v1.ValidateConnectorRequest\x1a!.api.v1.ValidateConnectorResponse\"\xc7\x02\x92\x41\xa1\x02J\x9d\x01\n\x03\x34\x30\x30\x12\x95\x01\x12\x16\n\x14\x1a\x12.google.rpc.Status\"{\n\x10\x61pplication/json\x12g{ \"code\": 9, \"message\": \"validation error: `aws.accessKeyId` config value must be set\", \"details\": [] }J\x7f\n\x03\x35\x30\x30\x12x\x12\x16\n\x14\x1a\x12.google.rpc.Status\"^\n\x10\x61pplication/json\x12J{ \"code\": 13, \"message\": \"could not dispense destination\", \"details\": [] }\x82\xd3\xe4\x93\x02\x1c\"\x17/v1/connectors/validate:\x01*\x12\xf5\x02\n\x0fUpdateConnector\x12\x1e.api.v1.UpdateConnectorRequest\x1a\x1f.api.v1.UpdateConnectorResponse\"\xa0\x02\x92\x41\xf3\x01Jw\n\x03\x34\x30\x30\x12p\x12\x16\n\x14\x1a\x12.google.rpc.Status\"V\n\x10\x61pplication/json\x12\x42{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }Jx\n\x03\x34\x30\x34\x12q\x12\x16\n\x14\x1a\x12.google.rpc.Status\"W\n\x10\x61pplication/json\x12\x43{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\x82\xd3\xe4\x93\x02#\x1a\x13/v1/connectors/{id}:\x01*b\tconnector\x12\xe9\x02\n\x0f\x44\x65leteConnector\x12\x1e.api.v1.DeleteConnectorRequest\x1a\x1f.api.v1.DeleteConnectorResponse\"\x94\x02\x92\x41\xf5\x01Jy\n\x03\x34\x30\x30\x12r\x12\x16\n\x14\x1a\x12.google.rpc.Status\"X\n\x10\x61pplication/json\x12\x44{ \"code\": 9, \"message\": \"failed precondition error\", \"details\": [] }Jx\n\x03\x34\x30\x34\x12q\x12\x16\n\x14\x1a\x12.google.rpc.Status\"W\n\x10\x61pplication/json\x12\x43{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\x82\xd3\xe4\x93\x02\x15*\x13/v1/connectors/{id}\x12\x8a\x01\n\x14ListConnectorPlugins\x12#.api.v1.ListConnectorPluginsRequest\x1a$.api.v1.ListConnectorPluginsResponse\"\'\x82\xd3\xe4\x93\x02!\x12\x16/v1/connectors/pluginsb\x07plugins2\x86\x0e\n\x10ProcessorService\x12s\n\x0eListProcessors\x12\x1d.api.v1.ListProcessorsRequest\x1a\x1e.api.v1.ListProcessorsResponse\"\"\x82\xd3\xe4\x93\x02\x1c\x12\x0e/v1/processorsb\nprocessors\x12\x8d\x01\n\x12InspectProcessorIn\x12!.api.v1.InspectProcessorInRequest\x1a\".api.v1.InspectProcessorInResponse\".\x82\xd3\xe4\x93\x02(\x12\x1e/v1/processors/{id}/inspect-inb\x06record0\x01\x12\x91\x01\n\x13InspectProcessorOut\x12\".api.v1.InspectProcessorOutRequest\x1a#.api.v1.InspectProcessorOutResponse\"/\x82\xd3\xe4\x93\x02)\x12\x1f/v1/processors/{id}/inspect-outb\x06record0\x01\x12\xef\x01\n\x0cGetProcessor\x12\x1b.api.v1.GetProcessorRequest\x1a\x1c.api.v1.GetProcessorResponse\"\xa3\x01\x92\x41zJx\n\x03\x34\x30\x34\x12q\x12\x16\n\x14\x1a\x12.google.rpc.Status\"W\n\x10\x61pplication/json\x12\x43{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\x82\xd3\xe4\x93\x02 \x12\x13/v1/processors/{id}b\tprocessor\x12\xf5\x01\n\x0f\x43reateProcessor\x12\x1e.api.v1.CreateProcessorRequest\x1a\x1f.api.v1.CreateProcessorResponse\"\xa0\x01\x92\x41yJw\n\x03\x34\x30\x30\x12p\x12\x16\n\x14\x1a\x12.google.rpc.Status\"V\n\x10\x61pplication/json\x12\x42{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }\x82\xd3\xe4\x93\x02\x1e\"\x0e/v1/processors:\x01*b\tprocessor\x12\xf5\x02\n\x0fUpdateProcessor\x12\x1e.api.v1.UpdateProcessorRequest\x1a\x1f.api.v1.UpdateProcessorResponse\"\xa0\x02\x92\x41\xf3\x01Jw\n\x03\x34\x30\x30\x12p\x12\x16\n\x14\x1a\x12.google.rpc.Status\"V\n\x10\x61pplication/json\x12\x42{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }Jx\n\x03\x34\x30\x34\x12q\x12\x16\n\x14\x1a\x12.google.rpc.Status\"W\n\x10\x61pplication/json\x12\x43{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\x82\xd3\xe4\x93\x02#\x1a\x13/v1/processors/{id}:\x01*b\tprocessor\x12\xe9\x02\n\x0f\x44\x65leteProcessor\x12\x1e.api.v1.DeleteProcessorRequest\x1a\x1f.api.v1.DeleteProcessorResponse\"\x94\x02\x92\x41\xf5\x01Jy\n\x03\x34\x30\x30\x12r\x12\x16\n\x14\x1a\x12.google.rpc.Status\"X\n\x10\x61pplication/json\x12\x44{ \"code\": 9, \"message\": \"failed precondition error\", \"details\": [] }Jx\n\x03\x34\x30\x34\x12q\x12\x16\n\x14\x1a\x12.google.rpc.Status\"W\n\x10\x61pplication/json\x12\x43{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\x82\xd3\xe4\x93\x02\x15*\x13/v1/processors/{id}\x12\x8a\x01\n\x14ListProcessorPlugins\x12#.api.v1.ListProcessorPluginsRequest\x1a$.api.v1.ListProcessorPluginsResponse\"\'\x82\xd3\xe4\x93\x02!\x12\x16/v1/processors/pluginsb\x07plugins2e\n\x12InformationService\x12O\n\x07GetInfo\x12\x16.api.v1.GetInfoRequest\x1a\x17.api.v1.GetInfoResponse\"\x13\x82\xd3\xe4\x93\x02\r\x12\x05/infob\x04info2x\n\rPluginService\x12g\n\x0bListPlugins\x12\x1a.api.v1.ListPluginsRequest\x1a\x1b.api.v1.ListPluginsResponse\"\x1f\x88\x02\x01\x82\xd3\xe4\x93\x02\x16\x12\x0b/v1/pluginsb\x07pluginsB\xa2\x02\x92\x41\x9e\x02\x12\xac\x01\n\x10\x43onduit REST API\"7\n\x0f\x43onduit project\x12$https://github.com/conduitio/conduit*W\n\x1a\x41pache License Version 2.0\x12\x39https://github.com/ConduitIO/conduit/blob/main/LICENSE.md2\x06v0.1.0Rm\n\x03\x35\x30\x30\x12\x66\x12\x16\n\x14\x1a\x12.google.rpc.Status\"L\n\x10\x61pplication/json\x12\x38{ \"code\": 13, \"message\": \"server error\", \"details\": [] }b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'api.v1.api_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\222A\236\002\022\254\001\n\020Conduit REST API\"7\n\017Conduit project\022$https://github.com/conduitio/conduit*W\n\032Apache License Version 2.0\0229https://github.com/ConduitIO/conduit/blob/main/LICENSE.md2\006v0.1.0Rm\n\003500\022f\022\026\n\024\032\022.google.rpc.Status\"L\n\020application/json\0228{ \"code\": 13, \"message\": \"server error\", \"details\": [] }' + _globals['_PIPELINE_DLQ_SETTINGSENTRY']._loaded_options = None + _globals['_PIPELINE_DLQ_SETTINGSENTRY']._serialized_options = b'8\001' + _globals['_PIPELINE'].fields_by_name['id']._loaded_options = None + _globals['_PIPELINE'].fields_by_name['id']._serialized_options = b'\340A\003' + _globals['_PIPELINE'].fields_by_name['connector_ids']._loaded_options = None + _globals['_PIPELINE'].fields_by_name['connector_ids']._serialized_options = b'\340A\003' + _globals['_PIPELINE'].fields_by_name['processor_ids']._loaded_options = None + _globals['_PIPELINE'].fields_by_name['processor_ids']._serialized_options = b'\340A\003' + _globals['_CONNECTOR_DESTINATIONSTATE_POSITIONSENTRY']._loaded_options = None + _globals['_CONNECTOR_DESTINATIONSTATE_POSITIONSENTRY']._serialized_options = b'8\001' + _globals['_CONNECTOR_CONFIG_SETTINGSENTRY']._loaded_options = None + _globals['_CONNECTOR_CONFIG_SETTINGSENTRY']._serialized_options = b'8\001' + _globals['_CONNECTOR'].fields_by_name['id']._loaded_options = None + _globals['_CONNECTOR'].fields_by_name['id']._serialized_options = b'\340A\003' + _globals['_CONNECTOR'].fields_by_name['type']._loaded_options = None + _globals['_CONNECTOR'].fields_by_name['type']._serialized_options = b'\340A\005' + _globals['_CONNECTOR'].fields_by_name['plugin']._loaded_options = None + _globals['_CONNECTOR'].fields_by_name['plugin']._serialized_options = b'\340A\005' + _globals['_CONNECTOR'].fields_by_name['pipeline_id']._loaded_options = None + _globals['_CONNECTOR'].fields_by_name['pipeline_id']._serialized_options = b'\340A\005' + _globals['_CONNECTOR'].fields_by_name['processor_ids']._loaded_options = None + _globals['_CONNECTOR'].fields_by_name['processor_ids']._serialized_options = b'\340A\003' + _globals['_PROCESSOR_CONFIG_SETTINGSENTRY']._loaded_options = None + _globals['_PROCESSOR_CONFIG_SETTINGSENTRY']._serialized_options = b'8\001' + _globals['_PROCESSOR'].fields_by_name['id']._loaded_options = None + _globals['_PROCESSOR'].fields_by_name['id']._serialized_options = b'\340A\003' + _globals['_PROCESSOR'].fields_by_name['plugin']._loaded_options = None + _globals['_PROCESSOR'].fields_by_name['plugin']._serialized_options = b'\340A\005' + _globals['_PROCESSOR'].fields_by_name['parent']._loaded_options = None + _globals['_PROCESSOR'].fields_by_name['parent']._serialized_options = b'\340A\005' + _globals['_CONNECTORPLUGINSPECIFICATIONS_DESTINATIONPARAMSENTRY']._loaded_options = None + _globals['_CONNECTORPLUGINSPECIFICATIONS_DESTINATIONPARAMSENTRY']._serialized_options = b'8\001' + _globals['_CONNECTORPLUGINSPECIFICATIONS_SOURCEPARAMSENTRY']._loaded_options = None + _globals['_CONNECTORPLUGINSPECIFICATIONS_SOURCEPARAMSENTRY']._serialized_options = b'8\001' + _globals['_PROCESSORPLUGINSPECIFICATIONS_PARAMETERSENTRY']._loaded_options = None + _globals['_PROCESSORPLUGINSPECIFICATIONS_PARAMETERSENTRY']._serialized_options = b'8\001' + _globals['_PLUGINSPECIFICATIONS_PARAMETER_VALIDATION_TYPE']._loaded_options = None + _globals['_PLUGINSPECIFICATIONS_PARAMETER_VALIDATION_TYPE']._serialized_options = b'\030\001' + _globals['_PLUGINSPECIFICATIONS_PARAMETER_VALIDATION']._loaded_options = None + _globals['_PLUGINSPECIFICATIONS_PARAMETER_VALIDATION']._serialized_options = b'\030\001' + _globals['_PLUGINSPECIFICATIONS_PARAMETER_TYPE']._loaded_options = None + _globals['_PLUGINSPECIFICATIONS_PARAMETER_TYPE']._serialized_options = b'\030\001' + _globals['_PLUGINSPECIFICATIONS_PARAMETER']._loaded_options = None + _globals['_PLUGINSPECIFICATIONS_PARAMETER']._serialized_options = b'\030\001' + _globals['_PLUGINSPECIFICATIONS_DESTINATIONPARAMSENTRY']._loaded_options = None + _globals['_PLUGINSPECIFICATIONS_DESTINATIONPARAMSENTRY']._serialized_options = b'8\001' + _globals['_PLUGINSPECIFICATIONS_SOURCEPARAMSENTRY']._loaded_options = None + _globals['_PLUGINSPECIFICATIONS_SOURCEPARAMSENTRY']._serialized_options = b'8\001' + _globals['_PLUGINSPECIFICATIONS']._loaded_options = None + _globals['_PLUGINSPECIFICATIONS']._serialized_options = b'\030\001' + _globals['_PIPELINEDOCUMENT_CONNECTOR_SETTINGSENTRY']._loaded_options = None + _globals['_PIPELINEDOCUMENT_CONNECTOR_SETTINGSENTRY']._serialized_options = b'8\001' + _globals['_PIPELINEDOCUMENT_PROCESSOR_SETTINGSENTRY']._loaded_options = None + _globals['_PIPELINEDOCUMENT_PROCESSOR_SETTINGSENTRY']._serialized_options = b'8\001' + _globals['_PIPELINEDOCUMENT_DLQ_SETTINGSENTRY']._loaded_options = None + _globals['_PIPELINEDOCUMENT_DLQ_SETTINGSENTRY']._serialized_options = b'8\001' + _globals['_CREATEPROCESSORREQUEST'].fields_by_name['type']._loaded_options = None + _globals['_CREATEPROCESSORREQUEST'].fields_by_name['type']._serialized_options = b'\030\001' + _globals['_LISTPLUGINSREQUEST']._loaded_options = None + _globals['_LISTPLUGINSREQUEST']._serialized_options = b'\030\001' + _globals['_LISTPLUGINSRESPONSE']._loaded_options = None + _globals['_LISTPLUGINSRESPONSE']._serialized_options = b'\030\001' + _globals['_PIPELINESERVICE'].methods_by_name['ListPipelines']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['ListPipelines']._serialized_options = b'\202\323\344\223\002\032\022\r/v1/pipelinesb\tpipelines' + _globals['_PIPELINESERVICE'].methods_by_name['CreatePipeline']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['CreatePipeline']._serialized_options = b'\222A\357\001Jw\n\003400\022p\022\026\n\024\032\022.google.rpc.Status\"V\n\020application/json\022B{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }Jt\n\003409\022m\022\026\n\024\032\022.google.rpc.Status\"S\n\020application/json\022?{ \"code\": 6, \"message\": \"already exists error\", \"details\": [] }\202\323\344\223\002\034\"\r/v1/pipelines:\001*b\010pipeline' + _globals['_PIPELINESERVICE'].methods_by_name['GetPipeline']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['GetPipeline']._serialized_options = b'\222AzJx\n\003404\022q\022\026\n\024\032\022.google.rpc.Status\"W\n\020application/json\022C{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\202\323\344\223\002\036\022\022/v1/pipelines/{id}b\010pipeline' + _globals['_PIPELINESERVICE'].methods_by_name['UpdatePipeline']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['UpdatePipeline']._serialized_options = b'\222A\351\002Jw\n\003400\022p\022\026\n\024\032\022.google.rpc.Status\"V\n\020application/json\022B{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }Jx\n\003404\022q\022\026\n\024\032\022.google.rpc.Status\"W\n\020application/json\022C{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }Jt\n\003409\022m\022\026\n\024\032\022.google.rpc.Status\"S\n\020application/json\022?{ \"code\": 6, \"message\": \"already exists error\", \"details\": [] }\202\323\344\223\002!\032\022/v1/pipelines/{id}:\001*b\010pipeline' + _globals['_PIPELINESERVICE'].methods_by_name['DeletePipeline']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['DeletePipeline']._serialized_options = b'\222A\365\001Jy\n\003400\022r\022\026\n\024\032\022.google.rpc.Status\"X\n\020application/json\022D{ \"code\": 9, \"message\": \"failed precondition error\", \"details\": [] }Jx\n\003404\022q\022\026\n\024\032\022.google.rpc.Status\"W\n\020application/json\022C{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\202\323\344\223\002\024*\022/v1/pipelines/{id}' + _globals['_PIPELINESERVICE'].methods_by_name['StartPipeline']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['StartPipeline']._serialized_options = b'\222A\365\001Jy\n\003400\022r\022\026\n\024\032\022.google.rpc.Status\"X\n\020application/json\022D{ \"code\": 9, \"message\": \"failed precondition error\", \"details\": [] }Jx\n\003404\022q\022\026\n\024\032\022.google.rpc.Status\"W\n\020application/json\022C{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\202\323\344\223\002\032\"\030/v1/pipelines/{id}/start' + _globals['_PIPELINESERVICE'].methods_by_name['StopPipeline']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['StopPipeline']._serialized_options = b'\222A\365\001Jy\n\003400\022r\022\026\n\024\032\022.google.rpc.Status\"X\n\020application/json\022D{ \"code\": 9, \"message\": \"failed precondition error\", \"details\": [] }Jx\n\003404\022q\022\026\n\024\032\022.google.rpc.Status\"W\n\020application/json\022C{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\202\323\344\223\002\034\"\027/v1/pipelines/{id}/stop:\001*' + _globals['_PIPELINESERVICE'].methods_by_name['GetDLQ']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['GetDLQ']._serialized_options = b'\202\323\344\223\002+\022$/v1/pipelines/{id}/dead-letter-queueb\003dlq' + _globals['_PIPELINESERVICE'].methods_by_name['UpdateDLQ']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['UpdateDLQ']._serialized_options = b'\202\323\344\223\0020\032$/v1/pipelines/{id}/dead-letter-queue:\003dlqb\003dlq' + _globals['_PIPELINESERVICE'].methods_by_name['ExportPipeline']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['ExportPipeline']._serialized_options = b'\202\323\344\223\002%\"\031/v1/pipelines/{id}/exportb\010pipeline' + _globals['_PIPELINESERVICE'].methods_by_name['ImportPipeline']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['ImportPipeline']._serialized_options = b'\202\323\344\223\002*\"\024/v1/pipelines/import:\010pipelineb\010pipeline' + _globals['_PIPELINESERVICE'].methods_by_name['PlanPipeline']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['PlanPipeline']._serialized_options = b'\202\323\344\223\002\035\"\022/v1/pipelines/plan:\001*b\004diff' + _globals['_PIPELINESERVICE'].methods_by_name['ApplyPipeline']._loaded_options = None + _globals['_PIPELINESERVICE'].methods_by_name['ApplyPipeline']._serialized_options = b'\222A\364\001Jw\n\003400\022p\022\026\n\024\032\022.google.rpc.Status\"V\n\020application/json\022B{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }Jy\n\003412\022r\022\026\n\024\032\022.google.rpc.Status\"X\n\020application/json\022D{ \"code\": 9, \"message\": \"failed precondition error\", \"details\": [] }\202\323\344\223\002\036\"\023/v1/pipelines/apply:\001*b\004diff' + _globals['_CONNECTORSERVICE'].methods_by_name['ListConnectors']._loaded_options = None + _globals['_CONNECTORSERVICE'].methods_by_name['ListConnectors']._serialized_options = b'\202\323\344\223\002\034\022\016/v1/connectorsb\nconnectors' + _globals['_CONNECTORSERVICE'].methods_by_name['InspectConnector']._loaded_options = None + _globals['_CONNECTORSERVICE'].methods_by_name['InspectConnector']._serialized_options = b'\202\323\344\223\002%\022\033/v1/connectors/{id}/inspectb\006record' + _globals['_CONNECTORSERVICE'].methods_by_name['GetConnector']._loaded_options = None + _globals['_CONNECTORSERVICE'].methods_by_name['GetConnector']._serialized_options = b'\222AzJx\n\003404\022q\022\026\n\024\032\022.google.rpc.Status\"W\n\020application/json\022C{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\202\323\344\223\002 \022\023/v1/connectors/{id}b\tconnector' + _globals['_CONNECTORSERVICE'].methods_by_name['CreateConnector']._loaded_options = None + _globals['_CONNECTORSERVICE'].methods_by_name['CreateConnector']._serialized_options = b'\222AyJw\n\003400\022p\022\026\n\024\032\022.google.rpc.Status\"V\n\020application/json\022B{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }\202\323\344\223\002\036\"\016/v1/connectors:\001*b\tconnector' + _globals['_CONNECTORSERVICE'].methods_by_name['ValidateConnector']._loaded_options = None + _globals['_CONNECTORSERVICE'].methods_by_name['ValidateConnector']._serialized_options = b'\222A\241\002J\235\001\n\003400\022\225\001\022\026\n\024\032\022.google.rpc.Status\"{\n\020application/json\022g{ \"code\": 9, \"message\": \"validation error: `aws.accessKeyId` config value must be set\", \"details\": [] }J\177\n\003500\022x\022\026\n\024\032\022.google.rpc.Status\"^\n\020application/json\022J{ \"code\": 13, \"message\": \"could not dispense destination\", \"details\": [] }\202\323\344\223\002\034\"\027/v1/connectors/validate:\001*' + _globals['_CONNECTORSERVICE'].methods_by_name['UpdateConnector']._loaded_options = None + _globals['_CONNECTORSERVICE'].methods_by_name['UpdateConnector']._serialized_options = b'\222A\363\001Jw\n\003400\022p\022\026\n\024\032\022.google.rpc.Status\"V\n\020application/json\022B{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }Jx\n\003404\022q\022\026\n\024\032\022.google.rpc.Status\"W\n\020application/json\022C{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\202\323\344\223\002#\032\023/v1/connectors/{id}:\001*b\tconnector' + _globals['_CONNECTORSERVICE'].methods_by_name['DeleteConnector']._loaded_options = None + _globals['_CONNECTORSERVICE'].methods_by_name['DeleteConnector']._serialized_options = b'\222A\365\001Jy\n\003400\022r\022\026\n\024\032\022.google.rpc.Status\"X\n\020application/json\022D{ \"code\": 9, \"message\": \"failed precondition error\", \"details\": [] }Jx\n\003404\022q\022\026\n\024\032\022.google.rpc.Status\"W\n\020application/json\022C{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\202\323\344\223\002\025*\023/v1/connectors/{id}' + _globals['_CONNECTORSERVICE'].methods_by_name['ListConnectorPlugins']._loaded_options = None + _globals['_CONNECTORSERVICE'].methods_by_name['ListConnectorPlugins']._serialized_options = b'\202\323\344\223\002!\022\026/v1/connectors/pluginsb\007plugins' + _globals['_PROCESSORSERVICE'].methods_by_name['ListProcessors']._loaded_options = None + _globals['_PROCESSORSERVICE'].methods_by_name['ListProcessors']._serialized_options = b'\202\323\344\223\002\034\022\016/v1/processorsb\nprocessors' + _globals['_PROCESSORSERVICE'].methods_by_name['InspectProcessorIn']._loaded_options = None + _globals['_PROCESSORSERVICE'].methods_by_name['InspectProcessorIn']._serialized_options = b'\202\323\344\223\002(\022\036/v1/processors/{id}/inspect-inb\006record' + _globals['_PROCESSORSERVICE'].methods_by_name['InspectProcessorOut']._loaded_options = None + _globals['_PROCESSORSERVICE'].methods_by_name['InspectProcessorOut']._serialized_options = b'\202\323\344\223\002)\022\037/v1/processors/{id}/inspect-outb\006record' + _globals['_PROCESSORSERVICE'].methods_by_name['GetProcessor']._loaded_options = None + _globals['_PROCESSORSERVICE'].methods_by_name['GetProcessor']._serialized_options = b'\222AzJx\n\003404\022q\022\026\n\024\032\022.google.rpc.Status\"W\n\020application/json\022C{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\202\323\344\223\002 \022\023/v1/processors/{id}b\tprocessor' + _globals['_PROCESSORSERVICE'].methods_by_name['CreateProcessor']._loaded_options = None + _globals['_PROCESSORSERVICE'].methods_by_name['CreateProcessor']._serialized_options = b'\222AyJw\n\003400\022p\022\026\n\024\032\022.google.rpc.Status\"V\n\020application/json\022B{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }\202\323\344\223\002\036\"\016/v1/processors:\001*b\tprocessor' + _globals['_PROCESSORSERVICE'].methods_by_name['UpdateProcessor']._loaded_options = None + _globals['_PROCESSORSERVICE'].methods_by_name['UpdateProcessor']._serialized_options = b'\222A\363\001Jw\n\003400\022p\022\026\n\024\032\022.google.rpc.Status\"V\n\020application/json\022B{ \"code\": 3, \"message\": \"invalid arguments error\", \"details\": [] }Jx\n\003404\022q\022\026\n\024\032\022.google.rpc.Status\"W\n\020application/json\022C{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\202\323\344\223\002#\032\023/v1/processors/{id}:\001*b\tprocessor' + _globals['_PROCESSORSERVICE'].methods_by_name['DeleteProcessor']._loaded_options = None + _globals['_PROCESSORSERVICE'].methods_by_name['DeleteProcessor']._serialized_options = b'\222A\365\001Jy\n\003400\022r\022\026\n\024\032\022.google.rpc.Status\"X\n\020application/json\022D{ \"code\": 9, \"message\": \"failed precondition error\", \"details\": [] }Jx\n\003404\022q\022\026\n\024\032\022.google.rpc.Status\"W\n\020application/json\022C{ \"code\": 5, \"message\": \"resource not found error\", \"details\": [] }\202\323\344\223\002\025*\023/v1/processors/{id}' + _globals['_PROCESSORSERVICE'].methods_by_name['ListProcessorPlugins']._loaded_options = None + _globals['_PROCESSORSERVICE'].methods_by_name['ListProcessorPlugins']._serialized_options = b'\202\323\344\223\002!\022\026/v1/processors/pluginsb\007plugins' + _globals['_INFORMATIONSERVICE'].methods_by_name['GetInfo']._loaded_options = None + _globals['_INFORMATIONSERVICE'].methods_by_name['GetInfo']._serialized_options = b'\202\323\344\223\002\r\022\005/infob\004info' + _globals['_PLUGINSERVICE'].methods_by_name['ListPlugins']._loaded_options = None + _globals['_PLUGINSERVICE'].methods_by_name['ListPlugins']._serialized_options = b'\210\002\001\202\323\344\223\002\026\022\013/v1/pluginsb\007plugins' + _globals['_PIPELINE']._serialized_start=226 + _globals['_PIPELINE']._serialized_end=1237 + _globals['_PIPELINE_STATE']._serialized_start=557 + _globals['_PIPELINE_STATE']._serialized_end=813 + _globals['_PIPELINE_STATE_STOPPEDREASON']._serialized_start=714 + _globals['_PIPELINE_STATE_STOPPEDREASON']._serialized_end=813 + _globals['_PIPELINE_CONFIG']._serialized_start=815 + _globals['_PIPELINE_CONFIG']._serialized_end=877 + _globals['_PIPELINE_DLQ']._serialized_start=880 + _globals['_PIPELINE_DLQ']._serialized_end=1119 + _globals['_PIPELINE_DLQ_SETTINGSENTRY']._serialized_start=1060 + _globals['_PIPELINE_DLQ_SETTINGSENTRY']._serialized_end=1119 + _globals['_PIPELINE_STATUS']._serialized_start=1121 + _globals['_PIPELINE_STATUS']._serialized_end=1237 + _globals['_CONNECTOR']._serialized_start=1240 + _globals['_CONNECTOR']._serialized_end=2194 + _globals['_CONNECTOR_SOURCESTATE']._serialized_start=1751 + _globals['_CONNECTOR_SOURCESTATE']._serialized_end=1792 + _globals['_CONNECTOR_DESTINATIONSTATE']._serialized_start=1795 + _globals['_CONNECTOR_DESTINATIONSTATE']._serialized_end=1956 + _globals['_CONNECTOR_DESTINATIONSTATE_POSITIONSENTRY']._serialized_start=1896 + _globals['_CONNECTOR_DESTINATIONSTATE_POSITIONSENTRY']._serialized_end=1956 + _globals['_CONNECTOR_CONFIG']._serialized_start=1959 + _globals['_CONNECTOR_CONFIG']._serialized_end=2116 + _globals['_CONNECTOR_CONFIG_SETTINGSENTRY']._serialized_start=1060 + _globals['_CONNECTOR_CONFIG_SETTINGSENTRY']._serialized_end=1119 + _globals['_CONNECTOR_TYPE']._serialized_start=2118 + _globals['_CONNECTOR_TYPE']._serialized_end=2185 + _globals['_PROCESSOR']._serialized_start=2197 + _globals['_PROCESSOR']._serialized_end=2849 + _globals['_PROCESSOR_PARENT']._serialized_start=2514 + _globals['_PROCESSOR_PARENT']._serialized_end=2658 + _globals['_PROCESSOR_PARENT_TYPE']._serialized_start=2591 + _globals['_PROCESSOR_PARENT_TYPE']._serialized_end=2658 + _globals['_PROCESSOR_CONFIG']._serialized_start=2661 + _globals['_PROCESSOR_CONFIG']._serialized_end=2824 + _globals['_PROCESSOR_CONFIG_SETTINGSENTRY']._serialized_start=1060 + _globals['_PROCESSOR_CONFIG_SETTINGSENTRY']._serialized_end=1119 + _globals['_CONNECTORPLUGINSPECIFICATIONS']._serialized_start=2852 + _globals['_CONNECTORPLUGINSPECIFICATIONS']._serialized_end=3395 + _globals['_CONNECTORPLUGINSPECIFICATIONS_DESTINATIONPARAMSENTRY']._serialized_start=3218 + _globals['_CONNECTORPLUGINSPECIFICATIONS_DESTINATIONPARAMSENTRY']._serialized_end=3308 + _globals['_CONNECTORPLUGINSPECIFICATIONS_SOURCEPARAMSENTRY']._serialized_start=3310 + _globals['_CONNECTORPLUGINSPECIFICATIONS_SOURCEPARAMSENTRY']._serialized_end=3395 + _globals['_PROCESSORPLUGINSPECIFICATIONS']._serialized_start=3398 + _globals['_PROCESSORPLUGINSPECIFICATIONS']._serialized_end=3731 + _globals['_PROCESSORPLUGINSPECIFICATIONS_PARAMETERSENTRY']._serialized_start=3648 + _globals['_PROCESSORPLUGINSPECIFICATIONS_PARAMETERSENTRY']._serialized_end=3731 + _globals['_PLUGINSPECIFICATIONS']._serialized_start=3734 + _globals['_PLUGINSPECIFICATIONS']._serialized_end=4919 + _globals['_PLUGINSPECIFICATIONS_PARAMETER']._serialized_start=4074 + _globals['_PLUGINSPECIFICATIONS_PARAMETER']._serialized_end=4700 + _globals['_PLUGINSPECIFICATIONS_PARAMETER_VALIDATION']._serialized_start=4298 + _globals['_PLUGINSPECIFICATIONS_PARAMETER_VALIDATION']._serialized_end=4565 + _globals['_PLUGINSPECIFICATIONS_PARAMETER_VALIDATION_TYPE']._serialized_start=4411 + _globals['_PLUGINSPECIFICATIONS_PARAMETER_VALIDATION_TYPE']._serialized_end=4561 + _globals['_PLUGINSPECIFICATIONS_PARAMETER_TYPE']._serialized_start=4568 + _globals['_PLUGINSPECIFICATIONS_PARAMETER_TYPE']._serialized_end=4696 + _globals['_PLUGINSPECIFICATIONS_DESTINATIONPARAMSENTRY']._serialized_start=4702 + _globals['_PLUGINSPECIFICATIONS_DESTINATIONPARAMSENTRY']._serialized_end=4810 + _globals['_PLUGINSPECIFICATIONS_SOURCEPARAMSENTRY']._serialized_start=4812 + _globals['_PLUGINSPECIFICATIONS_SOURCEPARAMSENTRY']._serialized_end=4915 + _globals['_LISTPIPELINESREQUEST']._serialized_start=4921 + _globals['_LISTPIPELINESREQUEST']._serialized_end=4963 + _globals['_LISTPIPELINESRESPONSE']._serialized_start=4965 + _globals['_LISTPIPELINESRESPONSE']._serialized_end=5036 + _globals['_CREATEPIPELINEREQUEST']._serialized_start=5038 + _globals['_CREATEPIPELINEREQUEST']._serialized_end=5110 + _globals['_CREATEPIPELINERESPONSE']._serialized_start=5112 + _globals['_CREATEPIPELINERESPONSE']._serialized_end=5182 + _globals['_GETPIPELINEREQUEST']._serialized_start=5184 + _globals['_GETPIPELINEREQUEST']._serialized_end=5220 + _globals['_GETPIPELINERESPONSE']._serialized_start=5222 + _globals['_GETPIPELINERESPONSE']._serialized_end=5289 + _globals['_UPDATEPIPELINEREQUEST']._serialized_start=5291 + _globals['_UPDATEPIPELINEREQUEST']._serialized_end=5379 + _globals['_UPDATEPIPELINERESPONSE']._serialized_start=5381 + _globals['_UPDATEPIPELINERESPONSE']._serialized_end=5451 + _globals['_DELETEPIPELINEREQUEST']._serialized_start=5453 + _globals['_DELETEPIPELINEREQUEST']._serialized_end=5492 + _globals['_DELETEPIPELINERESPONSE']._serialized_start=5494 + _globals['_DELETEPIPELINERESPONSE']._serialized_end=5518 + _globals['_STARTPIPELINEREQUEST']._serialized_start=5520 + _globals['_STARTPIPELINEREQUEST']._serialized_end=5558 + _globals['_STARTPIPELINERESPONSE']._serialized_start=5560 + _globals['_STARTPIPELINERESPONSE']._serialized_end=5583 + _globals['_STOPPIPELINEREQUEST']._serialized_start=5585 + _globals['_STOPPIPELINEREQUEST']._serialized_end=5644 + _globals['_STOPPIPELINERESPONSE']._serialized_start=5646 + _globals['_STOPPIPELINERESPONSE']._serialized_end=5668 + _globals['_GETDLQREQUEST']._serialized_start=5670 + _globals['_GETDLQREQUEST']._serialized_end=5701 + _globals['_GETDLQRESPONSE']._serialized_start=5703 + _globals['_GETDLQRESPONSE']._serialized_end=5759 + _globals['_UPDATEDLQREQUEST']._serialized_start=5761 + _globals['_UPDATEDLQREQUEST']._serialized_end=5835 + _globals['_UPDATEDLQRESPONSE']._serialized_start=5837 + _globals['_UPDATEDLQRESPONSE']._serialized_end=5896 + _globals['_EXPORTPIPELINEREQUEST']._serialized_start=5898 + _globals['_EXPORTPIPELINEREQUEST']._serialized_end=5937 + _globals['_EXPORTPIPELINERESPONSE']._serialized_start=5939 + _globals['_EXPORTPIPELINERESPONSE']._serialized_end=6009 + _globals['_IMPORTPIPELINEREQUEST']._serialized_start=6011 + _globals['_IMPORTPIPELINEREQUEST']._serialized_end=6080 + _globals['_IMPORTPIPELINERESPONSE']._serialized_start=6082 + _globals['_IMPORTPIPELINERESPONSE']._serialized_end=6152 + _globals['_PIPELINEDOCUMENT']._serialized_start=6155 + _globals['_PIPELINEDOCUMENT']._serialized_end=7251 + _globals['_PIPELINEDOCUMENT_CONNECTOR']._serialized_start=6454 + _globals['_PIPELINEDOCUMENT_CONNECTOR']._serialized_end=6752 + _globals['_PIPELINEDOCUMENT_CONNECTOR_SETTINGSENTRY']._serialized_start=1060 + _globals['_PIPELINEDOCUMENT_CONNECTOR_SETTINGSENTRY']._serialized_end=1119 + _globals['_PIPELINEDOCUMENT_PROCESSOR']._serialized_start=6755 + _globals['_PIPELINEDOCUMENT_PROCESSOR']._serialized_end=7001 + _globals['_PIPELINEDOCUMENT_PROCESSOR_SETTINGSENTRY']._serialized_start=1060 + _globals['_PIPELINEDOCUMENT_PROCESSOR_SETTINGSENTRY']._serialized_end=1119 + _globals['_PIPELINEDOCUMENT_DLQ']._serialized_start=7004 + _globals['_PIPELINEDOCUMENT_DLQ']._serialized_end=7251 + _globals['_PIPELINEDOCUMENT_DLQ_SETTINGSENTRY']._serialized_start=1060 + _globals['_PIPELINEDOCUMENT_DLQ_SETTINGSENTRY']._serialized_end=1119 + _globals['_DIFF']._serialized_start=7254 + _globals['_DIFF']._serialized_end=7518 + _globals['_DIFF_CHANGE']._serialized_start=7363 + _globals['_DIFF_CHANGE']._serialized_end=7518 + _globals['_PLANPIPELINEREQUEST']._serialized_start=7520 + _globals['_PLANPIPELINEREQUEST']._serialized_end=7591 + _globals['_PLANPIPELINERESPONSE']._serialized_start=7593 + _globals['_PLANPIPELINERESPONSE']._serialized_end=7649 + _globals['_APPLYPIPELINEREQUEST']._serialized_start=7651 + _globals['_APPLYPIPELINEREQUEST']._serialized_end=7743 + _globals['_APPLYPIPELINERESPONSE']._serialized_start=7745 + _globals['_APPLYPIPELINERESPONSE']._serialized_end=7802 + _globals['_CREATECONNECTORREQUEST']._serialized_start=7805 + _globals['_CREATECONNECTORREQUEST']._serialized_end=7980 + _globals['_CREATECONNECTORRESPONSE']._serialized_start=7982 + _globals['_CREATECONNECTORRESPONSE']._serialized_end=8056 + _globals['_VALIDATECONNECTORREQUEST']._serialized_start=8059 + _globals['_VALIDATECONNECTORREQUEST']._serialized_end=8203 + _globals['_VALIDATECONNECTORRESPONSE']._serialized_start=8205 + _globals['_VALIDATECONNECTORRESPONSE']._serialized_end=8232 + _globals['_LISTCONNECTORSREQUEST']._serialized_start=8234 + _globals['_LISTCONNECTORSREQUEST']._serialized_end=8290 + _globals['_LISTCONNECTORSRESPONSE']._serialized_start=8292 + _globals['_LISTCONNECTORSRESPONSE']._serialized_end=8367 + _globals['_INSPECTCONNECTORREQUEST']._serialized_start=8369 + _globals['_INSPECTCONNECTORREQUEST']._serialized_end=8410 + _globals['_INSPECTCONNECTORRESPONSE']._serialized_start=8412 + _globals['_INSPECTCONNECTORRESPONSE']._serialized_end=8482 + _globals['_GETCONNECTORREQUEST']._serialized_start=8484 + _globals['_GETCONNECTORREQUEST']._serialized_end=8521 + _globals['_GETCONNECTORRESPONSE']._serialized_start=8523 + _globals['_GETCONNECTORRESPONSE']._serialized_end=8594 + _globals['_UPDATECONNECTORREQUEST']._serialized_start=8596 + _globals['_UPDATECONNECTORREQUEST']._serialized_end=8710 + _globals['_UPDATECONNECTORRESPONSE']._serialized_start=8712 + _globals['_UPDATECONNECTORRESPONSE']._serialized_end=8786 + _globals['_DELETECONNECTORREQUEST']._serialized_start=8788 + _globals['_DELETECONNECTORREQUEST']._serialized_end=8828 + _globals['_DELETECONNECTORRESPONSE']._serialized_start=8830 + _globals['_DELETECONNECTORRESPONSE']._serialized_end=8855 + _globals['_LISTCONNECTORPLUGINSREQUEST']._serialized_start=8857 + _globals['_LISTCONNECTORPLUGINSREQUEST']._serialized_end=8906 + _globals['_LISTCONNECTORPLUGINSRESPONSE']._serialized_start=8908 + _globals['_LISTCONNECTORPLUGINSRESPONSE']._serialized_end=9003 + _globals['_LISTPROCESSORSREQUEST']._serialized_start=9005 + _globals['_LISTPROCESSORSREQUEST']._serialized_end=9059 + _globals['_LISTPROCESSORSRESPONSE']._serialized_start=9061 + _globals['_LISTPROCESSORSRESPONSE']._serialized_end=9136 + _globals['_INSPECTPROCESSORINREQUEST']._serialized_start=9138 + _globals['_INSPECTPROCESSORINREQUEST']._serialized_end=9181 + _globals['_INSPECTPROCESSORINRESPONSE']._serialized_start=9183 + _globals['_INSPECTPROCESSORINRESPONSE']._serialized_end=9255 + _globals['_INSPECTPROCESSOROUTREQUEST']._serialized_start=9257 + _globals['_INSPECTPROCESSOROUTREQUEST']._serialized_end=9301 + _globals['_INSPECTPROCESSOROUTRESPONSE']._serialized_start=9303 + _globals['_INSPECTPROCESSOROUTRESPONSE']._serialized_end=9376 + _globals['_CREATEPROCESSORREQUEST']._serialized_start=9379 + _globals['_CREATEPROCESSORREQUEST']._serialized_end=9581 + _globals['_CREATEPROCESSORRESPONSE']._serialized_start=9583 + _globals['_CREATEPROCESSORRESPONSE']._serialized_end=9657 + _globals['_GETPROCESSORREQUEST']._serialized_start=9659 + _globals['_GETPROCESSORREQUEST']._serialized_end=9696 + _globals['_GETPROCESSORRESPONSE']._serialized_start=9698 + _globals['_GETPROCESSORRESPONSE']._serialized_end=9769 + _globals['_UPDATEPROCESSORREQUEST']._serialized_start=9771 + _globals['_UPDATEPROCESSORREQUEST']._serialized_end=9885 + _globals['_UPDATEPROCESSORRESPONSE']._serialized_start=9887 + _globals['_UPDATEPROCESSORRESPONSE']._serialized_end=9961 + _globals['_DELETEPROCESSORREQUEST']._serialized_start=9963 + _globals['_DELETEPROCESSORREQUEST']._serialized_end=10003 + _globals['_DELETEPROCESSORRESPONSE']._serialized_start=10005 + _globals['_DELETEPROCESSORRESPONSE']._serialized_end=10030 + _globals['_LISTPROCESSORPLUGINSREQUEST']._serialized_start=10032 + _globals['_LISTPROCESSORPLUGINSREQUEST']._serialized_end=10081 + _globals['_LISTPROCESSORPLUGINSRESPONSE']._serialized_start=10083 + _globals['_LISTPROCESSORPLUGINSRESPONSE']._serialized_end=10178 + _globals['_GETINFOREQUEST']._serialized_start=10180 + _globals['_GETINFOREQUEST']._serialized_end=10196 + _globals['_GETINFORESPONSE']._serialized_start=10198 + _globals['_GETINFORESPONSE']._serialized_end=10249 + _globals['_INFO']._serialized_start=10251 + _globals['_INFO']._serialized_end=10319 + _globals['_LISTPLUGINSREQUEST']._serialized_start=10321 + _globals['_LISTPLUGINSREQUEST']._serialized_end=10365 + _globals['_LISTPLUGINSRESPONSE']._serialized_start=10367 + _globals['_LISTPLUGINSRESPONSE']._serialized_end=10448 + _globals['_PIPELINESERVICE']._serialized_start=10451 + _globals['_PIPELINESERVICE']._serialized_end=13719 + _globals['_CONNECTORSERVICE']._serialized_start=13722 + _globals['_CONNECTORSERVICE']._serialized_end=15784 + _globals['_PROCESSORSERVICE']._serialized_start=15787 + _globals['_PROCESSORSERVICE']._serialized_end=17585 + _globals['_INFORMATIONSERVICE']._serialized_start=17587 + _globals['_INFORMATIONSERVICE']._serialized_end=17688 + _globals['_PLUGINSERVICE']._serialized_start=17690 + _globals['_PLUGINSERVICE']._serialized_end=17810 +# @@protoc_insertion_point(module_scope) diff --git a/src/conduit/_grpc/api/v1/api_pb2.pyi b/src/conduit/_grpc/api/v1/api_pb2.pyi new file mode 100644 index 0000000..51d15cb --- /dev/null +++ b/src/conduit/_grpc/api/v1/api_pb2.pyi @@ -0,0 +1,837 @@ +from config.v1 import parameter_pb2 as _parameter_pb2 +from google.api import annotations_pb2 as _annotations_pb2 +from google.api import field_behavior_pb2 as _field_behavior_pb2 +from google.protobuf import timestamp_pb2 as _timestamp_pb2 +from opencdc.v1 import opencdc_pb2 as _opencdc_pb2 +from protoc_gen_openapiv2.options import annotations_pb2 as _annotations_pb2_1 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class Pipeline(_message.Message): + __slots__ = ("id", "state", "config", "connector_ids", "processor_ids", "created_at", "updated_at") + class Status(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + STATUS_UNSPECIFIED: _ClassVar[Pipeline.Status] + STATUS_RUNNING: _ClassVar[Pipeline.Status] + STATUS_STOPPED: _ClassVar[Pipeline.Status] + STATUS_DEGRADED: _ClassVar[Pipeline.Status] + STATUS_RECOVERING: _ClassVar[Pipeline.Status] + STATUS_UNSPECIFIED: Pipeline.Status + STATUS_RUNNING: Pipeline.Status + STATUS_STOPPED: Pipeline.Status + STATUS_DEGRADED: Pipeline.Status + STATUS_RECOVERING: Pipeline.Status + class State(_message.Message): + __slots__ = ("status", "error", "stopped_reason") + class StoppedReason(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + STOPPED_REASON_UNSPECIFIED: _ClassVar[Pipeline.State.StoppedReason] + STOPPED_REASON_USER: _ClassVar[Pipeline.State.StoppedReason] + STOPPED_REASON_SYSTEM: _ClassVar[Pipeline.State.StoppedReason] + STOPPED_REASON_UNSPECIFIED: Pipeline.State.StoppedReason + STOPPED_REASON_USER: Pipeline.State.StoppedReason + STOPPED_REASON_SYSTEM: Pipeline.State.StoppedReason + STATUS_FIELD_NUMBER: _ClassVar[int] + ERROR_FIELD_NUMBER: _ClassVar[int] + STOPPED_REASON_FIELD_NUMBER: _ClassVar[int] + status: Pipeline.Status + error: str + stopped_reason: Pipeline.State.StoppedReason + def __init__(self, status: _Optional[_Union[Pipeline.Status, str]] = ..., error: _Optional[str] = ..., stopped_reason: _Optional[_Union[Pipeline.State.StoppedReason, str]] = ...) -> None: ... + class Config(_message.Message): + __slots__ = ("name", "description") + NAME_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + name: str + description: str + def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ...) -> None: ... + class DLQ(_message.Message): + __slots__ = ("plugin", "settings", "window_size", "window_nack_threshold") + class SettingsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + PLUGIN_FIELD_NUMBER: _ClassVar[int] + SETTINGS_FIELD_NUMBER: _ClassVar[int] + WINDOW_SIZE_FIELD_NUMBER: _ClassVar[int] + WINDOW_NACK_THRESHOLD_FIELD_NUMBER: _ClassVar[int] + plugin: str + settings: _containers.ScalarMap[str, str] + window_size: int + window_nack_threshold: int + def __init__(self, plugin: _Optional[str] = ..., settings: _Optional[_Mapping[str, str]] = ..., window_size: _Optional[int] = ..., window_nack_threshold: _Optional[int] = ...) -> None: ... + ID_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + CONFIG_FIELD_NUMBER: _ClassVar[int] + CONNECTOR_IDS_FIELD_NUMBER: _ClassVar[int] + PROCESSOR_IDS_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + id: str + state: Pipeline.State + config: Pipeline.Config + connector_ids: _containers.RepeatedScalarFieldContainer[str] + processor_ids: _containers.RepeatedScalarFieldContainer[str] + created_at: _timestamp_pb2.Timestamp + updated_at: _timestamp_pb2.Timestamp + def __init__(self, id: _Optional[str] = ..., state: _Optional[_Union[Pipeline.State, _Mapping]] = ..., config: _Optional[_Union[Pipeline.Config, _Mapping]] = ..., connector_ids: _Optional[_Iterable[str]] = ..., processor_ids: _Optional[_Iterable[str]] = ..., created_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., updated_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + +class Connector(_message.Message): + __slots__ = ("id", "destination_state", "source_state", "config", "type", "plugin", "pipeline_id", "processor_ids", "created_at", "updated_at") + class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TYPE_UNSPECIFIED: _ClassVar[Connector.Type] + TYPE_SOURCE: _ClassVar[Connector.Type] + TYPE_DESTINATION: _ClassVar[Connector.Type] + TYPE_UNSPECIFIED: Connector.Type + TYPE_SOURCE: Connector.Type + TYPE_DESTINATION: Connector.Type + class SourceState(_message.Message): + __slots__ = ("position",) + POSITION_FIELD_NUMBER: _ClassVar[int] + position: bytes + def __init__(self, position: _Optional[bytes] = ...) -> None: ... + class DestinationState(_message.Message): + __slots__ = ("positions",) + class PositionsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: bytes + def __init__(self, key: _Optional[str] = ..., value: _Optional[bytes] = ...) -> None: ... + POSITIONS_FIELD_NUMBER: _ClassVar[int] + positions: _containers.ScalarMap[str, bytes] + def __init__(self, positions: _Optional[_Mapping[str, bytes]] = ...) -> None: ... + class Config(_message.Message): + __slots__ = ("name", "settings") + class SettingsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + NAME_FIELD_NUMBER: _ClassVar[int] + SETTINGS_FIELD_NUMBER: _ClassVar[int] + name: str + settings: _containers.ScalarMap[str, str] + def __init__(self, name: _Optional[str] = ..., settings: _Optional[_Mapping[str, str]] = ...) -> None: ... + ID_FIELD_NUMBER: _ClassVar[int] + DESTINATION_STATE_FIELD_NUMBER: _ClassVar[int] + SOURCE_STATE_FIELD_NUMBER: _ClassVar[int] + CONFIG_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + PLUGIN_FIELD_NUMBER: _ClassVar[int] + PIPELINE_ID_FIELD_NUMBER: _ClassVar[int] + PROCESSOR_IDS_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + id: str + destination_state: Connector.DestinationState + source_state: Connector.SourceState + config: Connector.Config + type: Connector.Type + plugin: str + pipeline_id: str + processor_ids: _containers.RepeatedScalarFieldContainer[str] + created_at: _timestamp_pb2.Timestamp + updated_at: _timestamp_pb2.Timestamp + def __init__(self, id: _Optional[str] = ..., destination_state: _Optional[_Union[Connector.DestinationState, _Mapping]] = ..., source_state: _Optional[_Union[Connector.SourceState, _Mapping]] = ..., config: _Optional[_Union[Connector.Config, _Mapping]] = ..., type: _Optional[_Union[Connector.Type, str]] = ..., plugin: _Optional[str] = ..., pipeline_id: _Optional[str] = ..., processor_ids: _Optional[_Iterable[str]] = ..., created_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., updated_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + +class Processor(_message.Message): + __slots__ = ("id", "config", "condition", "plugin", "parent", "created_at", "updated_at") + class Parent(_message.Message): + __slots__ = ("type", "id") + class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TYPE_UNSPECIFIED: _ClassVar[Processor.Parent.Type] + TYPE_CONNECTOR: _ClassVar[Processor.Parent.Type] + TYPE_PIPELINE: _ClassVar[Processor.Parent.Type] + TYPE_UNSPECIFIED: Processor.Parent.Type + TYPE_CONNECTOR: Processor.Parent.Type + TYPE_PIPELINE: Processor.Parent.Type + TYPE_FIELD_NUMBER: _ClassVar[int] + ID_FIELD_NUMBER: _ClassVar[int] + type: Processor.Parent.Type + id: str + def __init__(self, type: _Optional[_Union[Processor.Parent.Type, str]] = ..., id: _Optional[str] = ...) -> None: ... + class Config(_message.Message): + __slots__ = ("settings", "workers") + class SettingsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + SETTINGS_FIELD_NUMBER: _ClassVar[int] + WORKERS_FIELD_NUMBER: _ClassVar[int] + settings: _containers.ScalarMap[str, str] + workers: int + def __init__(self, settings: _Optional[_Mapping[str, str]] = ..., workers: _Optional[int] = ...) -> None: ... + ID_FIELD_NUMBER: _ClassVar[int] + CONFIG_FIELD_NUMBER: _ClassVar[int] + CONDITION_FIELD_NUMBER: _ClassVar[int] + PLUGIN_FIELD_NUMBER: _ClassVar[int] + PARENT_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + id: str + config: Processor.Config + condition: str + plugin: str + parent: Processor.Parent + created_at: _timestamp_pb2.Timestamp + updated_at: _timestamp_pb2.Timestamp + def __init__(self, id: _Optional[str] = ..., config: _Optional[_Union[Processor.Config, _Mapping]] = ..., condition: _Optional[str] = ..., plugin: _Optional[str] = ..., parent: _Optional[_Union[Processor.Parent, _Mapping]] = ..., created_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., updated_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + +class ConnectorPluginSpecifications(_message.Message): + __slots__ = ("name", "summary", "description", "version", "author", "destination_params", "source_params") + class DestinationParamsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: _parameter_pb2.Parameter + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_parameter_pb2.Parameter, _Mapping]] = ...) -> None: ... + class SourceParamsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: _parameter_pb2.Parameter + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_parameter_pb2.Parameter, _Mapping]] = ...) -> None: ... + NAME_FIELD_NUMBER: _ClassVar[int] + SUMMARY_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + VERSION_FIELD_NUMBER: _ClassVar[int] + AUTHOR_FIELD_NUMBER: _ClassVar[int] + DESTINATION_PARAMS_FIELD_NUMBER: _ClassVar[int] + SOURCE_PARAMS_FIELD_NUMBER: _ClassVar[int] + name: str + summary: str + description: str + version: str + author: str + destination_params: _containers.MessageMap[str, _parameter_pb2.Parameter] + source_params: _containers.MessageMap[str, _parameter_pb2.Parameter] + def __init__(self, name: _Optional[str] = ..., summary: _Optional[str] = ..., description: _Optional[str] = ..., version: _Optional[str] = ..., author: _Optional[str] = ..., destination_params: _Optional[_Mapping[str, _parameter_pb2.Parameter]] = ..., source_params: _Optional[_Mapping[str, _parameter_pb2.Parameter]] = ...) -> None: ... + +class ProcessorPluginSpecifications(_message.Message): + __slots__ = ("name", "summary", "description", "version", "author", "parameters") + class ParametersEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: _parameter_pb2.Parameter + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_parameter_pb2.Parameter, _Mapping]] = ...) -> None: ... + NAME_FIELD_NUMBER: _ClassVar[int] + SUMMARY_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + VERSION_FIELD_NUMBER: _ClassVar[int] + AUTHOR_FIELD_NUMBER: _ClassVar[int] + PARAMETERS_FIELD_NUMBER: _ClassVar[int] + name: str + summary: str + description: str + version: str + author: str + parameters: _containers.MessageMap[str, _parameter_pb2.Parameter] + def __init__(self, name: _Optional[str] = ..., summary: _Optional[str] = ..., description: _Optional[str] = ..., version: _Optional[str] = ..., author: _Optional[str] = ..., parameters: _Optional[_Mapping[str, _parameter_pb2.Parameter]] = ...) -> None: ... + +class PluginSpecifications(_message.Message): + __slots__ = ("name", "summary", "description", "version", "author", "destination_params", "source_params") + class Parameter(_message.Message): + __slots__ = ("description", "default", "type", "validations") + class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TYPE_UNSPECIFIED: _ClassVar[PluginSpecifications.Parameter.Type] + TYPE_STRING: _ClassVar[PluginSpecifications.Parameter.Type] + TYPE_INT: _ClassVar[PluginSpecifications.Parameter.Type] + TYPE_FLOAT: _ClassVar[PluginSpecifications.Parameter.Type] + TYPE_BOOL: _ClassVar[PluginSpecifications.Parameter.Type] + TYPE_FILE: _ClassVar[PluginSpecifications.Parameter.Type] + TYPE_DURATION: _ClassVar[PluginSpecifications.Parameter.Type] + TYPE_UNSPECIFIED: PluginSpecifications.Parameter.Type + TYPE_STRING: PluginSpecifications.Parameter.Type + TYPE_INT: PluginSpecifications.Parameter.Type + TYPE_FLOAT: PluginSpecifications.Parameter.Type + TYPE_BOOL: PluginSpecifications.Parameter.Type + TYPE_FILE: PluginSpecifications.Parameter.Type + TYPE_DURATION: PluginSpecifications.Parameter.Type + class Validation(_message.Message): + __slots__ = ("type", "value") + class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TYPE_UNSPECIFIED: _ClassVar[PluginSpecifications.Parameter.Validation.Type] + TYPE_REQUIRED: _ClassVar[PluginSpecifications.Parameter.Validation.Type] + TYPE_GREATER_THAN: _ClassVar[PluginSpecifications.Parameter.Validation.Type] + TYPE_LESS_THAN: _ClassVar[PluginSpecifications.Parameter.Validation.Type] + TYPE_INCLUSION: _ClassVar[PluginSpecifications.Parameter.Validation.Type] + TYPE_EXCLUSION: _ClassVar[PluginSpecifications.Parameter.Validation.Type] + TYPE_REGEX: _ClassVar[PluginSpecifications.Parameter.Validation.Type] + TYPE_UNSPECIFIED: PluginSpecifications.Parameter.Validation.Type + TYPE_REQUIRED: PluginSpecifications.Parameter.Validation.Type + TYPE_GREATER_THAN: PluginSpecifications.Parameter.Validation.Type + TYPE_LESS_THAN: PluginSpecifications.Parameter.Validation.Type + TYPE_INCLUSION: PluginSpecifications.Parameter.Validation.Type + TYPE_EXCLUSION: PluginSpecifications.Parameter.Validation.Type + TYPE_REGEX: PluginSpecifications.Parameter.Validation.Type + TYPE_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + type: PluginSpecifications.Parameter.Validation.Type + value: str + def __init__(self, type: _Optional[_Union[PluginSpecifications.Parameter.Validation.Type, str]] = ..., value: _Optional[str] = ...) -> None: ... + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + DEFAULT_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + VALIDATIONS_FIELD_NUMBER: _ClassVar[int] + description: str + default: str + type: PluginSpecifications.Parameter.Type + validations: _containers.RepeatedCompositeFieldContainer[PluginSpecifications.Parameter.Validation] + def __init__(self, description: _Optional[str] = ..., default: _Optional[str] = ..., type: _Optional[_Union[PluginSpecifications.Parameter.Type, str]] = ..., validations: _Optional[_Iterable[_Union[PluginSpecifications.Parameter.Validation, _Mapping]]] = ...) -> None: ... + class DestinationParamsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: PluginSpecifications.Parameter + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[PluginSpecifications.Parameter, _Mapping]] = ...) -> None: ... + class SourceParamsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: PluginSpecifications.Parameter + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[PluginSpecifications.Parameter, _Mapping]] = ...) -> None: ... + NAME_FIELD_NUMBER: _ClassVar[int] + SUMMARY_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + VERSION_FIELD_NUMBER: _ClassVar[int] + AUTHOR_FIELD_NUMBER: _ClassVar[int] + DESTINATION_PARAMS_FIELD_NUMBER: _ClassVar[int] + SOURCE_PARAMS_FIELD_NUMBER: _ClassVar[int] + name: str + summary: str + description: str + version: str + author: str + destination_params: _containers.MessageMap[str, PluginSpecifications.Parameter] + source_params: _containers.MessageMap[str, PluginSpecifications.Parameter] + def __init__(self, name: _Optional[str] = ..., summary: _Optional[str] = ..., description: _Optional[str] = ..., version: _Optional[str] = ..., author: _Optional[str] = ..., destination_params: _Optional[_Mapping[str, PluginSpecifications.Parameter]] = ..., source_params: _Optional[_Mapping[str, PluginSpecifications.Parameter]] = ...) -> None: ... + +class ListPipelinesRequest(_message.Message): + __slots__ = ("name",) + NAME_FIELD_NUMBER: _ClassVar[int] + name: str + def __init__(self, name: _Optional[str] = ...) -> None: ... + +class ListPipelinesResponse(_message.Message): + __slots__ = ("pipelines",) + PIPELINES_FIELD_NUMBER: _ClassVar[int] + pipelines: _containers.RepeatedCompositeFieldContainer[Pipeline] + def __init__(self, pipelines: _Optional[_Iterable[_Union[Pipeline, _Mapping]]] = ...) -> None: ... + +class CreatePipelineRequest(_message.Message): + __slots__ = ("config",) + CONFIG_FIELD_NUMBER: _ClassVar[int] + config: Pipeline.Config + def __init__(self, config: _Optional[_Union[Pipeline.Config, _Mapping]] = ...) -> None: ... + +class CreatePipelineResponse(_message.Message): + __slots__ = ("pipeline",) + PIPELINE_FIELD_NUMBER: _ClassVar[int] + pipeline: Pipeline + def __init__(self, pipeline: _Optional[_Union[Pipeline, _Mapping]] = ...) -> None: ... + +class GetPipelineRequest(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class GetPipelineResponse(_message.Message): + __slots__ = ("pipeline",) + PIPELINE_FIELD_NUMBER: _ClassVar[int] + pipeline: Pipeline + def __init__(self, pipeline: _Optional[_Union[Pipeline, _Mapping]] = ...) -> None: ... + +class UpdatePipelineRequest(_message.Message): + __slots__ = ("id", "config") + ID_FIELD_NUMBER: _ClassVar[int] + CONFIG_FIELD_NUMBER: _ClassVar[int] + id: str + config: Pipeline.Config + def __init__(self, id: _Optional[str] = ..., config: _Optional[_Union[Pipeline.Config, _Mapping]] = ...) -> None: ... + +class UpdatePipelineResponse(_message.Message): + __slots__ = ("pipeline",) + PIPELINE_FIELD_NUMBER: _ClassVar[int] + pipeline: Pipeline + def __init__(self, pipeline: _Optional[_Union[Pipeline, _Mapping]] = ...) -> None: ... + +class DeletePipelineRequest(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class DeletePipelineResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class StartPipelineRequest(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class StartPipelineResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class StopPipelineRequest(_message.Message): + __slots__ = ("id", "force") + ID_FIELD_NUMBER: _ClassVar[int] + FORCE_FIELD_NUMBER: _ClassVar[int] + id: str + force: bool + def __init__(self, id: _Optional[str] = ..., force: bool = ...) -> None: ... + +class StopPipelineResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetDLQRequest(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class GetDLQResponse(_message.Message): + __slots__ = ("dlq",) + DLQ_FIELD_NUMBER: _ClassVar[int] + dlq: Pipeline.DLQ + def __init__(self, dlq: _Optional[_Union[Pipeline.DLQ, _Mapping]] = ...) -> None: ... + +class UpdateDLQRequest(_message.Message): + __slots__ = ("id", "dlq") + ID_FIELD_NUMBER: _ClassVar[int] + DLQ_FIELD_NUMBER: _ClassVar[int] + id: str + dlq: Pipeline.DLQ + def __init__(self, id: _Optional[str] = ..., dlq: _Optional[_Union[Pipeline.DLQ, _Mapping]] = ...) -> None: ... + +class UpdateDLQResponse(_message.Message): + __slots__ = ("dlq",) + DLQ_FIELD_NUMBER: _ClassVar[int] + dlq: Pipeline.DLQ + def __init__(self, dlq: _Optional[_Union[Pipeline.DLQ, _Mapping]] = ...) -> None: ... + +class ExportPipelineRequest(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class ExportPipelineResponse(_message.Message): + __slots__ = ("pipeline",) + PIPELINE_FIELD_NUMBER: _ClassVar[int] + pipeline: Pipeline + def __init__(self, pipeline: _Optional[_Union[Pipeline, _Mapping]] = ...) -> None: ... + +class ImportPipelineRequest(_message.Message): + __slots__ = ("pipeline",) + PIPELINE_FIELD_NUMBER: _ClassVar[int] + pipeline: Pipeline + def __init__(self, pipeline: _Optional[_Union[Pipeline, _Mapping]] = ...) -> None: ... + +class ImportPipelineResponse(_message.Message): + __slots__ = ("pipeline",) + PIPELINE_FIELD_NUMBER: _ClassVar[int] + pipeline: Pipeline + def __init__(self, pipeline: _Optional[_Union[Pipeline, _Mapping]] = ...) -> None: ... + +class PipelineDocument(_message.Message): + __slots__ = ("id", "status", "name", "description", "connectors", "processors", "dlq") + class Connector(_message.Message): + __slots__ = ("id", "type", "plugin", "name", "settings", "processors") + class SettingsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + ID_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + PLUGIN_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + SETTINGS_FIELD_NUMBER: _ClassVar[int] + PROCESSORS_FIELD_NUMBER: _ClassVar[int] + id: str + type: str + plugin: str + name: str + settings: _containers.ScalarMap[str, str] + processors: _containers.RepeatedCompositeFieldContainer[PipelineDocument.Processor] + def __init__(self, id: _Optional[str] = ..., type: _Optional[str] = ..., plugin: _Optional[str] = ..., name: _Optional[str] = ..., settings: _Optional[_Mapping[str, str]] = ..., processors: _Optional[_Iterable[_Union[PipelineDocument.Processor, _Mapping]]] = ...) -> None: ... + class Processor(_message.Message): + __slots__ = ("id", "plugin", "settings", "workers", "condition") + class SettingsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + ID_FIELD_NUMBER: _ClassVar[int] + PLUGIN_FIELD_NUMBER: _ClassVar[int] + SETTINGS_FIELD_NUMBER: _ClassVar[int] + WORKERS_FIELD_NUMBER: _ClassVar[int] + CONDITION_FIELD_NUMBER: _ClassVar[int] + id: str + plugin: str + settings: _containers.ScalarMap[str, str] + workers: int + condition: str + def __init__(self, id: _Optional[str] = ..., plugin: _Optional[str] = ..., settings: _Optional[_Mapping[str, str]] = ..., workers: _Optional[int] = ..., condition: _Optional[str] = ...) -> None: ... + class DLQ(_message.Message): + __slots__ = ("plugin", "settings", "window_size", "window_nack_threshold") + class SettingsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + PLUGIN_FIELD_NUMBER: _ClassVar[int] + SETTINGS_FIELD_NUMBER: _ClassVar[int] + WINDOW_SIZE_FIELD_NUMBER: _ClassVar[int] + WINDOW_NACK_THRESHOLD_FIELD_NUMBER: _ClassVar[int] + plugin: str + settings: _containers.ScalarMap[str, str] + window_size: int + window_nack_threshold: int + def __init__(self, plugin: _Optional[str] = ..., settings: _Optional[_Mapping[str, str]] = ..., window_size: _Optional[int] = ..., window_nack_threshold: _Optional[int] = ...) -> None: ... + ID_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + CONNECTORS_FIELD_NUMBER: _ClassVar[int] + PROCESSORS_FIELD_NUMBER: _ClassVar[int] + DLQ_FIELD_NUMBER: _ClassVar[int] + id: str + status: str + name: str + description: str + connectors: _containers.RepeatedCompositeFieldContainer[PipelineDocument.Connector] + processors: _containers.RepeatedCompositeFieldContainer[PipelineDocument.Processor] + dlq: PipelineDocument.DLQ + def __init__(self, id: _Optional[str] = ..., status: _Optional[str] = ..., name: _Optional[str] = ..., description: _Optional[str] = ..., connectors: _Optional[_Iterable[_Union[PipelineDocument.Connector, _Mapping]]] = ..., processors: _Optional[_Iterable[_Union[PipelineDocument.Processor, _Mapping]]] = ..., dlq: _Optional[_Union[PipelineDocument.DLQ, _Mapping]] = ...) -> None: ... + +class Diff(_message.Message): + __slots__ = ("pipeline_id", "changes", "hash") + class Change(_message.Message): + __slots__ = ("resource", "id", "action", "effect", "config_paths", "code") + RESOURCE_FIELD_NUMBER: _ClassVar[int] + ID_FIELD_NUMBER: _ClassVar[int] + ACTION_FIELD_NUMBER: _ClassVar[int] + EFFECT_FIELD_NUMBER: _ClassVar[int] + CONFIG_PATHS_FIELD_NUMBER: _ClassVar[int] + CODE_FIELD_NUMBER: _ClassVar[int] + resource: str + id: str + action: str + effect: str + config_paths: _containers.RepeatedScalarFieldContainer[str] + code: str + def __init__(self, resource: _Optional[str] = ..., id: _Optional[str] = ..., action: _Optional[str] = ..., effect: _Optional[str] = ..., config_paths: _Optional[_Iterable[str]] = ..., code: _Optional[str] = ...) -> None: ... + PIPELINE_ID_FIELD_NUMBER: _ClassVar[int] + CHANGES_FIELD_NUMBER: _ClassVar[int] + HASH_FIELD_NUMBER: _ClassVar[int] + pipeline_id: str + changes: _containers.RepeatedCompositeFieldContainer[Diff.Change] + hash: str + def __init__(self, pipeline_id: _Optional[str] = ..., changes: _Optional[_Iterable[_Union[Diff.Change, _Mapping]]] = ..., hash: _Optional[str] = ...) -> None: ... + +class PlanPipelineRequest(_message.Message): + __slots__ = ("config",) + CONFIG_FIELD_NUMBER: _ClassVar[int] + config: PipelineDocument + def __init__(self, config: _Optional[_Union[PipelineDocument, _Mapping]] = ...) -> None: ... + +class PlanPipelineResponse(_message.Message): + __slots__ = ("diff",) + DIFF_FIELD_NUMBER: _ClassVar[int] + diff: Diff + def __init__(self, diff: _Optional[_Union[Diff, _Mapping]] = ...) -> None: ... + +class ApplyPipelineRequest(_message.Message): + __slots__ = ("config", "hash") + CONFIG_FIELD_NUMBER: _ClassVar[int] + HASH_FIELD_NUMBER: _ClassVar[int] + config: PipelineDocument + hash: str + def __init__(self, config: _Optional[_Union[PipelineDocument, _Mapping]] = ..., hash: _Optional[str] = ...) -> None: ... + +class ApplyPipelineResponse(_message.Message): + __slots__ = ("diff",) + DIFF_FIELD_NUMBER: _ClassVar[int] + diff: Diff + def __init__(self, diff: _Optional[_Union[Diff, _Mapping]] = ...) -> None: ... + +class CreateConnectorRequest(_message.Message): + __slots__ = ("type", "plugin", "pipeline_id", "config") + TYPE_FIELD_NUMBER: _ClassVar[int] + PLUGIN_FIELD_NUMBER: _ClassVar[int] + PIPELINE_ID_FIELD_NUMBER: _ClassVar[int] + CONFIG_FIELD_NUMBER: _ClassVar[int] + type: Connector.Type + plugin: str + pipeline_id: str + config: Connector.Config + def __init__(self, type: _Optional[_Union[Connector.Type, str]] = ..., plugin: _Optional[str] = ..., pipeline_id: _Optional[str] = ..., config: _Optional[_Union[Connector.Config, _Mapping]] = ...) -> None: ... + +class CreateConnectorResponse(_message.Message): + __slots__ = ("connector",) + CONNECTOR_FIELD_NUMBER: _ClassVar[int] + connector: Connector + def __init__(self, connector: _Optional[_Union[Connector, _Mapping]] = ...) -> None: ... + +class ValidateConnectorRequest(_message.Message): + __slots__ = ("type", "plugin", "config") + TYPE_FIELD_NUMBER: _ClassVar[int] + PLUGIN_FIELD_NUMBER: _ClassVar[int] + CONFIG_FIELD_NUMBER: _ClassVar[int] + type: Connector.Type + plugin: str + config: Connector.Config + def __init__(self, type: _Optional[_Union[Connector.Type, str]] = ..., plugin: _Optional[str] = ..., config: _Optional[_Union[Connector.Config, _Mapping]] = ...) -> None: ... + +class ValidateConnectorResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListConnectorsRequest(_message.Message): + __slots__ = ("pipeline_id",) + PIPELINE_ID_FIELD_NUMBER: _ClassVar[int] + pipeline_id: str + def __init__(self, pipeline_id: _Optional[str] = ...) -> None: ... + +class ListConnectorsResponse(_message.Message): + __slots__ = ("connectors",) + CONNECTORS_FIELD_NUMBER: _ClassVar[int] + connectors: _containers.RepeatedCompositeFieldContainer[Connector] + def __init__(self, connectors: _Optional[_Iterable[_Union[Connector, _Mapping]]] = ...) -> None: ... + +class InspectConnectorRequest(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class InspectConnectorResponse(_message.Message): + __slots__ = ("record",) + RECORD_FIELD_NUMBER: _ClassVar[int] + record: _opencdc_pb2.Record + def __init__(self, record: _Optional[_Union[_opencdc_pb2.Record, _Mapping]] = ...) -> None: ... + +class GetConnectorRequest(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class GetConnectorResponse(_message.Message): + __slots__ = ("connector",) + CONNECTOR_FIELD_NUMBER: _ClassVar[int] + connector: Connector + def __init__(self, connector: _Optional[_Union[Connector, _Mapping]] = ...) -> None: ... + +class UpdateConnectorRequest(_message.Message): + __slots__ = ("id", "config", "plugin") + ID_FIELD_NUMBER: _ClassVar[int] + CONFIG_FIELD_NUMBER: _ClassVar[int] + PLUGIN_FIELD_NUMBER: _ClassVar[int] + id: str + config: Connector.Config + plugin: str + def __init__(self, id: _Optional[str] = ..., config: _Optional[_Union[Connector.Config, _Mapping]] = ..., plugin: _Optional[str] = ...) -> None: ... + +class UpdateConnectorResponse(_message.Message): + __slots__ = ("connector",) + CONNECTOR_FIELD_NUMBER: _ClassVar[int] + connector: Connector + def __init__(self, connector: _Optional[_Union[Connector, _Mapping]] = ...) -> None: ... + +class DeleteConnectorRequest(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class DeleteConnectorResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListConnectorPluginsRequest(_message.Message): + __slots__ = ("name",) + NAME_FIELD_NUMBER: _ClassVar[int] + name: str + def __init__(self, name: _Optional[str] = ...) -> None: ... + +class ListConnectorPluginsResponse(_message.Message): + __slots__ = ("plugins",) + PLUGINS_FIELD_NUMBER: _ClassVar[int] + plugins: _containers.RepeatedCompositeFieldContainer[ConnectorPluginSpecifications] + def __init__(self, plugins: _Optional[_Iterable[_Union[ConnectorPluginSpecifications, _Mapping]]] = ...) -> None: ... + +class ListProcessorsRequest(_message.Message): + __slots__ = ("parent_ids",) + PARENT_IDS_FIELD_NUMBER: _ClassVar[int] + parent_ids: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, parent_ids: _Optional[_Iterable[str]] = ...) -> None: ... + +class ListProcessorsResponse(_message.Message): + __slots__ = ("processors",) + PROCESSORS_FIELD_NUMBER: _ClassVar[int] + processors: _containers.RepeatedCompositeFieldContainer[Processor] + def __init__(self, processors: _Optional[_Iterable[_Union[Processor, _Mapping]]] = ...) -> None: ... + +class InspectProcessorInRequest(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class InspectProcessorInResponse(_message.Message): + __slots__ = ("record",) + RECORD_FIELD_NUMBER: _ClassVar[int] + record: _opencdc_pb2.Record + def __init__(self, record: _Optional[_Union[_opencdc_pb2.Record, _Mapping]] = ...) -> None: ... + +class InspectProcessorOutRequest(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class InspectProcessorOutResponse(_message.Message): + __slots__ = ("record",) + RECORD_FIELD_NUMBER: _ClassVar[int] + record: _opencdc_pb2.Record + def __init__(self, record: _Optional[_Union[_opencdc_pb2.Record, _Mapping]] = ...) -> None: ... + +class CreateProcessorRequest(_message.Message): + __slots__ = ("type", "parent", "config", "condition", "plugin") + TYPE_FIELD_NUMBER: _ClassVar[int] + PARENT_FIELD_NUMBER: _ClassVar[int] + CONFIG_FIELD_NUMBER: _ClassVar[int] + CONDITION_FIELD_NUMBER: _ClassVar[int] + PLUGIN_FIELD_NUMBER: _ClassVar[int] + type: str + parent: Processor.Parent + config: Processor.Config + condition: str + plugin: str + def __init__(self, type: _Optional[str] = ..., parent: _Optional[_Union[Processor.Parent, _Mapping]] = ..., config: _Optional[_Union[Processor.Config, _Mapping]] = ..., condition: _Optional[str] = ..., plugin: _Optional[str] = ...) -> None: ... + +class CreateProcessorResponse(_message.Message): + __slots__ = ("processor",) + PROCESSOR_FIELD_NUMBER: _ClassVar[int] + processor: Processor + def __init__(self, processor: _Optional[_Union[Processor, _Mapping]] = ...) -> None: ... + +class GetProcessorRequest(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class GetProcessorResponse(_message.Message): + __slots__ = ("processor",) + PROCESSOR_FIELD_NUMBER: _ClassVar[int] + processor: Processor + def __init__(self, processor: _Optional[_Union[Processor, _Mapping]] = ...) -> None: ... + +class UpdateProcessorRequest(_message.Message): + __slots__ = ("id", "config", "plugin") + ID_FIELD_NUMBER: _ClassVar[int] + CONFIG_FIELD_NUMBER: _ClassVar[int] + PLUGIN_FIELD_NUMBER: _ClassVar[int] + id: str + config: Processor.Config + plugin: str + def __init__(self, id: _Optional[str] = ..., config: _Optional[_Union[Processor.Config, _Mapping]] = ..., plugin: _Optional[str] = ...) -> None: ... + +class UpdateProcessorResponse(_message.Message): + __slots__ = ("processor",) + PROCESSOR_FIELD_NUMBER: _ClassVar[int] + processor: Processor + def __init__(self, processor: _Optional[_Union[Processor, _Mapping]] = ...) -> None: ... + +class DeleteProcessorRequest(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class DeleteProcessorResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListProcessorPluginsRequest(_message.Message): + __slots__ = ("name",) + NAME_FIELD_NUMBER: _ClassVar[int] + name: str + def __init__(self, name: _Optional[str] = ...) -> None: ... + +class ListProcessorPluginsResponse(_message.Message): + __slots__ = ("plugins",) + PLUGINS_FIELD_NUMBER: _ClassVar[int] + plugins: _containers.RepeatedCompositeFieldContainer[ProcessorPluginSpecifications] + def __init__(self, plugins: _Optional[_Iterable[_Union[ProcessorPluginSpecifications, _Mapping]]] = ...) -> None: ... + +class GetInfoRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetInfoResponse(_message.Message): + __slots__ = ("info",) + INFO_FIELD_NUMBER: _ClassVar[int] + info: Info + def __init__(self, info: _Optional[_Union[Info, _Mapping]] = ...) -> None: ... + +class Info(_message.Message): + __slots__ = ("version", "os", "arch") + VERSION_FIELD_NUMBER: _ClassVar[int] + OS_FIELD_NUMBER: _ClassVar[int] + ARCH_FIELD_NUMBER: _ClassVar[int] + version: str + os: str + arch: str + def __init__(self, version: _Optional[str] = ..., os: _Optional[str] = ..., arch: _Optional[str] = ...) -> None: ... + +class ListPluginsRequest(_message.Message): + __slots__ = ("name",) + NAME_FIELD_NUMBER: _ClassVar[int] + name: str + def __init__(self, name: _Optional[str] = ...) -> None: ... + +class ListPluginsResponse(_message.Message): + __slots__ = ("plugins",) + PLUGINS_FIELD_NUMBER: _ClassVar[int] + plugins: _containers.RepeatedCompositeFieldContainer[PluginSpecifications] + def __init__(self, plugins: _Optional[_Iterable[_Union[PluginSpecifications, _Mapping]]] = ...) -> None: ... diff --git a/src/conduit/_grpc/api/v1/api_pb2_grpc.py b/src/conduit/_grpc/api/v1/api_pb2_grpc.py new file mode 100644 index 0000000..cf44986 --- /dev/null +++ b/src/conduit/_grpc/api/v1/api_pb2_grpc.py @@ -0,0 +1,1527 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from api.v1 import api_pb2 as api_dot_v1_dot_api__pb2 + + +class PipelineServiceStub(object): + """-- services ----------------------------------------------------------------- + + PipelineService exposes functionality for managing pipelines. + Endpoints in this service can be used to create, fetch, modify or delete a + pipeline. Entities connected to a pipeline (e.g. processors, connectors) can + be fetched together with a pipeline, although they can not be modified + through this service. Modifying these entities needs to be done through the + service responsible for managing the corresponding entity. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListPipelines = channel.unary_unary( + '/api.v1.PipelineService/ListPipelines', + request_serializer=api_dot_v1_dot_api__pb2.ListPipelinesRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.ListPipelinesResponse.FromString, + _registered_method=True) + self.CreatePipeline = channel.unary_unary( + '/api.v1.PipelineService/CreatePipeline', + request_serializer=api_dot_v1_dot_api__pb2.CreatePipelineRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.CreatePipelineResponse.FromString, + _registered_method=True) + self.GetPipeline = channel.unary_unary( + '/api.v1.PipelineService/GetPipeline', + request_serializer=api_dot_v1_dot_api__pb2.GetPipelineRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.GetPipelineResponse.FromString, + _registered_method=True) + self.UpdatePipeline = channel.unary_unary( + '/api.v1.PipelineService/UpdatePipeline', + request_serializer=api_dot_v1_dot_api__pb2.UpdatePipelineRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.UpdatePipelineResponse.FromString, + _registered_method=True) + self.DeletePipeline = channel.unary_unary( + '/api.v1.PipelineService/DeletePipeline', + request_serializer=api_dot_v1_dot_api__pb2.DeletePipelineRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.DeletePipelineResponse.FromString, + _registered_method=True) + self.StartPipeline = channel.unary_unary( + '/api.v1.PipelineService/StartPipeline', + request_serializer=api_dot_v1_dot_api__pb2.StartPipelineRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.StartPipelineResponse.FromString, + _registered_method=True) + self.StopPipeline = channel.unary_unary( + '/api.v1.PipelineService/StopPipeline', + request_serializer=api_dot_v1_dot_api__pb2.StopPipelineRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.StopPipelineResponse.FromString, + _registered_method=True) + self.GetDLQ = channel.unary_unary( + '/api.v1.PipelineService/GetDLQ', + request_serializer=api_dot_v1_dot_api__pb2.GetDLQRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.GetDLQResponse.FromString, + _registered_method=True) + self.UpdateDLQ = channel.unary_unary( + '/api.v1.PipelineService/UpdateDLQ', + request_serializer=api_dot_v1_dot_api__pb2.UpdateDLQRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.UpdateDLQResponse.FromString, + _registered_method=True) + self.ExportPipeline = channel.unary_unary( + '/api.v1.PipelineService/ExportPipeline', + request_serializer=api_dot_v1_dot_api__pb2.ExportPipelineRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.ExportPipelineResponse.FromString, + _registered_method=True) + self.ImportPipeline = channel.unary_unary( + '/api.v1.PipelineService/ImportPipeline', + request_serializer=api_dot_v1_dot_api__pb2.ImportPipelineRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.ImportPipelineResponse.FromString, + _registered_method=True) + self.PlanPipeline = channel.unary_unary( + '/api.v1.PipelineService/PlanPipeline', + request_serializer=api_dot_v1_dot_api__pb2.PlanPipelineRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.PlanPipelineResponse.FromString, + _registered_method=True) + self.ApplyPipeline = channel.unary_unary( + '/api.v1.PipelineService/ApplyPipeline', + request_serializer=api_dot_v1_dot_api__pb2.ApplyPipelineRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.ApplyPipelineResponse.FromString, + _registered_method=True) + + +class PipelineServiceServicer(object): + """-- services ----------------------------------------------------------------- + + PipelineService exposes functionality for managing pipelines. + Endpoints in this service can be used to create, fetch, modify or delete a + pipeline. Entities connected to a pipeline (e.g. processors, connectors) can + be fetched together with a pipeline, although they can not be modified + through this service. Modifying these entities needs to be done through the + service responsible for managing the corresponding entity. + """ + + def ListPipelines(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreatePipeline(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetPipeline(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdatePipeline(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeletePipeline(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StartPipeline(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StopPipeline(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetDLQ(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateDLQ(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ExportPipeline(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ImportPipeline(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PlanPipeline(self, request, context): + """PlanPipeline computes the diff needed to reconcile a pipeline's + currently stored state with the desired config, without applying + anything (read-only, safe to call against a running pipeline). See + docs/design-documents/20260708-live-server-deploy-apply.md. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ApplyPipeline(self, request, context): + """ApplyPipeline executes the plan for a desired pipeline config, gated on + the caller presenting the hash of the plan it reviewed (a stale hash is + refused, never partially applied). Against a running pipeline whose plan + includes a restart-class change, this requires the server to have been + started with the live-restart-apply operator flag — see + docs/design-documents/20260708-live-server-deploy-apply.md and + docs/operations/live-restart-apply.md. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_PipelineServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListPipelines': grpc.unary_unary_rpc_method_handler( + servicer.ListPipelines, + request_deserializer=api_dot_v1_dot_api__pb2.ListPipelinesRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.ListPipelinesResponse.SerializeToString, + ), + 'CreatePipeline': grpc.unary_unary_rpc_method_handler( + servicer.CreatePipeline, + request_deserializer=api_dot_v1_dot_api__pb2.CreatePipelineRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.CreatePipelineResponse.SerializeToString, + ), + 'GetPipeline': grpc.unary_unary_rpc_method_handler( + servicer.GetPipeline, + request_deserializer=api_dot_v1_dot_api__pb2.GetPipelineRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.GetPipelineResponse.SerializeToString, + ), + 'UpdatePipeline': grpc.unary_unary_rpc_method_handler( + servicer.UpdatePipeline, + request_deserializer=api_dot_v1_dot_api__pb2.UpdatePipelineRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.UpdatePipelineResponse.SerializeToString, + ), + 'DeletePipeline': grpc.unary_unary_rpc_method_handler( + servicer.DeletePipeline, + request_deserializer=api_dot_v1_dot_api__pb2.DeletePipelineRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.DeletePipelineResponse.SerializeToString, + ), + 'StartPipeline': grpc.unary_unary_rpc_method_handler( + servicer.StartPipeline, + request_deserializer=api_dot_v1_dot_api__pb2.StartPipelineRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.StartPipelineResponse.SerializeToString, + ), + 'StopPipeline': grpc.unary_unary_rpc_method_handler( + servicer.StopPipeline, + request_deserializer=api_dot_v1_dot_api__pb2.StopPipelineRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.StopPipelineResponse.SerializeToString, + ), + 'GetDLQ': grpc.unary_unary_rpc_method_handler( + servicer.GetDLQ, + request_deserializer=api_dot_v1_dot_api__pb2.GetDLQRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.GetDLQResponse.SerializeToString, + ), + 'UpdateDLQ': grpc.unary_unary_rpc_method_handler( + servicer.UpdateDLQ, + request_deserializer=api_dot_v1_dot_api__pb2.UpdateDLQRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.UpdateDLQResponse.SerializeToString, + ), + 'ExportPipeline': grpc.unary_unary_rpc_method_handler( + servicer.ExportPipeline, + request_deserializer=api_dot_v1_dot_api__pb2.ExportPipelineRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.ExportPipelineResponse.SerializeToString, + ), + 'ImportPipeline': grpc.unary_unary_rpc_method_handler( + servicer.ImportPipeline, + request_deserializer=api_dot_v1_dot_api__pb2.ImportPipelineRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.ImportPipelineResponse.SerializeToString, + ), + 'PlanPipeline': grpc.unary_unary_rpc_method_handler( + servicer.PlanPipeline, + request_deserializer=api_dot_v1_dot_api__pb2.PlanPipelineRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.PlanPipelineResponse.SerializeToString, + ), + 'ApplyPipeline': grpc.unary_unary_rpc_method_handler( + servicer.ApplyPipeline, + request_deserializer=api_dot_v1_dot_api__pb2.ApplyPipelineRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.ApplyPipelineResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'api.v1.PipelineService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('api.v1.PipelineService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class PipelineService(object): + """-- services ----------------------------------------------------------------- + + PipelineService exposes functionality for managing pipelines. + Endpoints in this service can be used to create, fetch, modify or delete a + pipeline. Entities connected to a pipeline (e.g. processors, connectors) can + be fetched together with a pipeline, although they can not be modified + through this service. Modifying these entities needs to be done through the + service responsible for managing the corresponding entity. + """ + + @staticmethod + def ListPipelines(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/ListPipelines', + api_dot_v1_dot_api__pb2.ListPipelinesRequest.SerializeToString, + api_dot_v1_dot_api__pb2.ListPipelinesResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def CreatePipeline(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/CreatePipeline', + api_dot_v1_dot_api__pb2.CreatePipelineRequest.SerializeToString, + api_dot_v1_dot_api__pb2.CreatePipelineResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetPipeline(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/GetPipeline', + api_dot_v1_dot_api__pb2.GetPipelineRequest.SerializeToString, + api_dot_v1_dot_api__pb2.GetPipelineResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpdatePipeline(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/UpdatePipeline', + api_dot_v1_dot_api__pb2.UpdatePipelineRequest.SerializeToString, + api_dot_v1_dot_api__pb2.UpdatePipelineResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeletePipeline(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/DeletePipeline', + api_dot_v1_dot_api__pb2.DeletePipelineRequest.SerializeToString, + api_dot_v1_dot_api__pb2.DeletePipelineResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StartPipeline(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/StartPipeline', + api_dot_v1_dot_api__pb2.StartPipelineRequest.SerializeToString, + api_dot_v1_dot_api__pb2.StartPipelineResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StopPipeline(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/StopPipeline', + api_dot_v1_dot_api__pb2.StopPipelineRequest.SerializeToString, + api_dot_v1_dot_api__pb2.StopPipelineResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetDLQ(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/GetDLQ', + api_dot_v1_dot_api__pb2.GetDLQRequest.SerializeToString, + api_dot_v1_dot_api__pb2.GetDLQResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpdateDLQ(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/UpdateDLQ', + api_dot_v1_dot_api__pb2.UpdateDLQRequest.SerializeToString, + api_dot_v1_dot_api__pb2.UpdateDLQResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ExportPipeline(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/ExportPipeline', + api_dot_v1_dot_api__pb2.ExportPipelineRequest.SerializeToString, + api_dot_v1_dot_api__pb2.ExportPipelineResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ImportPipeline(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/ImportPipeline', + api_dot_v1_dot_api__pb2.ImportPipelineRequest.SerializeToString, + api_dot_v1_dot_api__pb2.ImportPipelineResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def PlanPipeline(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/PlanPipeline', + api_dot_v1_dot_api__pb2.PlanPipelineRequest.SerializeToString, + api_dot_v1_dot_api__pb2.PlanPipelineResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ApplyPipeline(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PipelineService/ApplyPipeline', + api_dot_v1_dot_api__pb2.ApplyPipelineRequest.SerializeToString, + api_dot_v1_dot_api__pb2.ApplyPipelineResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class ConnectorServiceStub(object): + """ConnectorService exposes CRUD functionality for managing connectors. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListConnectors = channel.unary_unary( + '/api.v1.ConnectorService/ListConnectors', + request_serializer=api_dot_v1_dot_api__pb2.ListConnectorsRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.ListConnectorsResponse.FromString, + _registered_method=True) + self.InspectConnector = channel.unary_stream( + '/api.v1.ConnectorService/InspectConnector', + request_serializer=api_dot_v1_dot_api__pb2.InspectConnectorRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.InspectConnectorResponse.FromString, + _registered_method=True) + self.GetConnector = channel.unary_unary( + '/api.v1.ConnectorService/GetConnector', + request_serializer=api_dot_v1_dot_api__pb2.GetConnectorRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.GetConnectorResponse.FromString, + _registered_method=True) + self.CreateConnector = channel.unary_unary( + '/api.v1.ConnectorService/CreateConnector', + request_serializer=api_dot_v1_dot_api__pb2.CreateConnectorRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.CreateConnectorResponse.FromString, + _registered_method=True) + self.ValidateConnector = channel.unary_unary( + '/api.v1.ConnectorService/ValidateConnector', + request_serializer=api_dot_v1_dot_api__pb2.ValidateConnectorRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.ValidateConnectorResponse.FromString, + _registered_method=True) + self.UpdateConnector = channel.unary_unary( + '/api.v1.ConnectorService/UpdateConnector', + request_serializer=api_dot_v1_dot_api__pb2.UpdateConnectorRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.UpdateConnectorResponse.FromString, + _registered_method=True) + self.DeleteConnector = channel.unary_unary( + '/api.v1.ConnectorService/DeleteConnector', + request_serializer=api_dot_v1_dot_api__pb2.DeleteConnectorRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.DeleteConnectorResponse.FromString, + _registered_method=True) + self.ListConnectorPlugins = channel.unary_unary( + '/api.v1.ConnectorService/ListConnectorPlugins', + request_serializer=api_dot_v1_dot_api__pb2.ListConnectorPluginsRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.ListConnectorPluginsResponse.FromString, + _registered_method=True) + + +class ConnectorServiceServicer(object): + """ConnectorService exposes CRUD functionality for managing connectors. + """ + + def ListConnectors(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def InspectConnector(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetConnector(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateConnector(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ValidateConnector(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateConnector(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteConnector(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListConnectorPlugins(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ConnectorServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListConnectors': grpc.unary_unary_rpc_method_handler( + servicer.ListConnectors, + request_deserializer=api_dot_v1_dot_api__pb2.ListConnectorsRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.ListConnectorsResponse.SerializeToString, + ), + 'InspectConnector': grpc.unary_stream_rpc_method_handler( + servicer.InspectConnector, + request_deserializer=api_dot_v1_dot_api__pb2.InspectConnectorRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.InspectConnectorResponse.SerializeToString, + ), + 'GetConnector': grpc.unary_unary_rpc_method_handler( + servicer.GetConnector, + request_deserializer=api_dot_v1_dot_api__pb2.GetConnectorRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.GetConnectorResponse.SerializeToString, + ), + 'CreateConnector': grpc.unary_unary_rpc_method_handler( + servicer.CreateConnector, + request_deserializer=api_dot_v1_dot_api__pb2.CreateConnectorRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.CreateConnectorResponse.SerializeToString, + ), + 'ValidateConnector': grpc.unary_unary_rpc_method_handler( + servicer.ValidateConnector, + request_deserializer=api_dot_v1_dot_api__pb2.ValidateConnectorRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.ValidateConnectorResponse.SerializeToString, + ), + 'UpdateConnector': grpc.unary_unary_rpc_method_handler( + servicer.UpdateConnector, + request_deserializer=api_dot_v1_dot_api__pb2.UpdateConnectorRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.UpdateConnectorResponse.SerializeToString, + ), + 'DeleteConnector': grpc.unary_unary_rpc_method_handler( + servicer.DeleteConnector, + request_deserializer=api_dot_v1_dot_api__pb2.DeleteConnectorRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.DeleteConnectorResponse.SerializeToString, + ), + 'ListConnectorPlugins': grpc.unary_unary_rpc_method_handler( + servicer.ListConnectorPlugins, + request_deserializer=api_dot_v1_dot_api__pb2.ListConnectorPluginsRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.ListConnectorPluginsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'api.v1.ConnectorService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('api.v1.ConnectorService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class ConnectorService(object): + """ConnectorService exposes CRUD functionality for managing connectors. + """ + + @staticmethod + def ListConnectors(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ConnectorService/ListConnectors', + api_dot_v1_dot_api__pb2.ListConnectorsRequest.SerializeToString, + api_dot_v1_dot_api__pb2.ListConnectorsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def InspectConnector(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/api.v1.ConnectorService/InspectConnector', + api_dot_v1_dot_api__pb2.InspectConnectorRequest.SerializeToString, + api_dot_v1_dot_api__pb2.InspectConnectorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetConnector(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ConnectorService/GetConnector', + api_dot_v1_dot_api__pb2.GetConnectorRequest.SerializeToString, + api_dot_v1_dot_api__pb2.GetConnectorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def CreateConnector(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ConnectorService/CreateConnector', + api_dot_v1_dot_api__pb2.CreateConnectorRequest.SerializeToString, + api_dot_v1_dot_api__pb2.CreateConnectorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ValidateConnector(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ConnectorService/ValidateConnector', + api_dot_v1_dot_api__pb2.ValidateConnectorRequest.SerializeToString, + api_dot_v1_dot_api__pb2.ValidateConnectorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpdateConnector(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ConnectorService/UpdateConnector', + api_dot_v1_dot_api__pb2.UpdateConnectorRequest.SerializeToString, + api_dot_v1_dot_api__pb2.UpdateConnectorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeleteConnector(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ConnectorService/DeleteConnector', + api_dot_v1_dot_api__pb2.DeleteConnectorRequest.SerializeToString, + api_dot_v1_dot_api__pb2.DeleteConnectorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListConnectorPlugins(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ConnectorService/ListConnectorPlugins', + api_dot_v1_dot_api__pb2.ListConnectorPluginsRequest.SerializeToString, + api_dot_v1_dot_api__pb2.ListConnectorPluginsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class ProcessorServiceStub(object): + """ProcessorService exposes CRUD functionality for managing processors. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListProcessors = channel.unary_unary( + '/api.v1.ProcessorService/ListProcessors', + request_serializer=api_dot_v1_dot_api__pb2.ListProcessorsRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.ListProcessorsResponse.FromString, + _registered_method=True) + self.InspectProcessorIn = channel.unary_stream( + '/api.v1.ProcessorService/InspectProcessorIn', + request_serializer=api_dot_v1_dot_api__pb2.InspectProcessorInRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.InspectProcessorInResponse.FromString, + _registered_method=True) + self.InspectProcessorOut = channel.unary_stream( + '/api.v1.ProcessorService/InspectProcessorOut', + request_serializer=api_dot_v1_dot_api__pb2.InspectProcessorOutRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.InspectProcessorOutResponse.FromString, + _registered_method=True) + self.GetProcessor = channel.unary_unary( + '/api.v1.ProcessorService/GetProcessor', + request_serializer=api_dot_v1_dot_api__pb2.GetProcessorRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.GetProcessorResponse.FromString, + _registered_method=True) + self.CreateProcessor = channel.unary_unary( + '/api.v1.ProcessorService/CreateProcessor', + request_serializer=api_dot_v1_dot_api__pb2.CreateProcessorRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.CreateProcessorResponse.FromString, + _registered_method=True) + self.UpdateProcessor = channel.unary_unary( + '/api.v1.ProcessorService/UpdateProcessor', + request_serializer=api_dot_v1_dot_api__pb2.UpdateProcessorRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.UpdateProcessorResponse.FromString, + _registered_method=True) + self.DeleteProcessor = channel.unary_unary( + '/api.v1.ProcessorService/DeleteProcessor', + request_serializer=api_dot_v1_dot_api__pb2.DeleteProcessorRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.DeleteProcessorResponse.FromString, + _registered_method=True) + self.ListProcessorPlugins = channel.unary_unary( + '/api.v1.ProcessorService/ListProcessorPlugins', + request_serializer=api_dot_v1_dot_api__pb2.ListProcessorPluginsRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.ListProcessorPluginsResponse.FromString, + _registered_method=True) + + +class ProcessorServiceServicer(object): + """ProcessorService exposes CRUD functionality for managing processors. + """ + + def ListProcessors(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def InspectProcessorIn(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def InspectProcessorOut(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetProcessor(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateProcessor(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateProcessor(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteProcessor(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListProcessorPlugins(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ProcessorServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListProcessors': grpc.unary_unary_rpc_method_handler( + servicer.ListProcessors, + request_deserializer=api_dot_v1_dot_api__pb2.ListProcessorsRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.ListProcessorsResponse.SerializeToString, + ), + 'InspectProcessorIn': grpc.unary_stream_rpc_method_handler( + servicer.InspectProcessorIn, + request_deserializer=api_dot_v1_dot_api__pb2.InspectProcessorInRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.InspectProcessorInResponse.SerializeToString, + ), + 'InspectProcessorOut': grpc.unary_stream_rpc_method_handler( + servicer.InspectProcessorOut, + request_deserializer=api_dot_v1_dot_api__pb2.InspectProcessorOutRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.InspectProcessorOutResponse.SerializeToString, + ), + 'GetProcessor': grpc.unary_unary_rpc_method_handler( + servicer.GetProcessor, + request_deserializer=api_dot_v1_dot_api__pb2.GetProcessorRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.GetProcessorResponse.SerializeToString, + ), + 'CreateProcessor': grpc.unary_unary_rpc_method_handler( + servicer.CreateProcessor, + request_deserializer=api_dot_v1_dot_api__pb2.CreateProcessorRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.CreateProcessorResponse.SerializeToString, + ), + 'UpdateProcessor': grpc.unary_unary_rpc_method_handler( + servicer.UpdateProcessor, + request_deserializer=api_dot_v1_dot_api__pb2.UpdateProcessorRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.UpdateProcessorResponse.SerializeToString, + ), + 'DeleteProcessor': grpc.unary_unary_rpc_method_handler( + servicer.DeleteProcessor, + request_deserializer=api_dot_v1_dot_api__pb2.DeleteProcessorRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.DeleteProcessorResponse.SerializeToString, + ), + 'ListProcessorPlugins': grpc.unary_unary_rpc_method_handler( + servicer.ListProcessorPlugins, + request_deserializer=api_dot_v1_dot_api__pb2.ListProcessorPluginsRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.ListProcessorPluginsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'api.v1.ProcessorService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('api.v1.ProcessorService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class ProcessorService(object): + """ProcessorService exposes CRUD functionality for managing processors. + """ + + @staticmethod + def ListProcessors(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ProcessorService/ListProcessors', + api_dot_v1_dot_api__pb2.ListProcessorsRequest.SerializeToString, + api_dot_v1_dot_api__pb2.ListProcessorsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def InspectProcessorIn(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/api.v1.ProcessorService/InspectProcessorIn', + api_dot_v1_dot_api__pb2.InspectProcessorInRequest.SerializeToString, + api_dot_v1_dot_api__pb2.InspectProcessorInResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def InspectProcessorOut(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/api.v1.ProcessorService/InspectProcessorOut', + api_dot_v1_dot_api__pb2.InspectProcessorOutRequest.SerializeToString, + api_dot_v1_dot_api__pb2.InspectProcessorOutResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetProcessor(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ProcessorService/GetProcessor', + api_dot_v1_dot_api__pb2.GetProcessorRequest.SerializeToString, + api_dot_v1_dot_api__pb2.GetProcessorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def CreateProcessor(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ProcessorService/CreateProcessor', + api_dot_v1_dot_api__pb2.CreateProcessorRequest.SerializeToString, + api_dot_v1_dot_api__pb2.CreateProcessorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpdateProcessor(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ProcessorService/UpdateProcessor', + api_dot_v1_dot_api__pb2.UpdateProcessorRequest.SerializeToString, + api_dot_v1_dot_api__pb2.UpdateProcessorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeleteProcessor(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ProcessorService/DeleteProcessor', + api_dot_v1_dot_api__pb2.DeleteProcessorRequest.SerializeToString, + api_dot_v1_dot_api__pb2.DeleteProcessorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListProcessorPlugins(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.ProcessorService/ListProcessorPlugins', + api_dot_v1_dot_api__pb2.ListProcessorPluginsRequest.SerializeToString, + api_dot_v1_dot_api__pb2.ListProcessorPluginsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class InformationServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetInfo = channel.unary_unary( + '/api.v1.InformationService/GetInfo', + request_serializer=api_dot_v1_dot_api__pb2.GetInfoRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.GetInfoResponse.FromString, + _registered_method=True) + + +class InformationServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def GetInfo(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_InformationServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetInfo': grpc.unary_unary_rpc_method_handler( + servicer.GetInfo, + request_deserializer=api_dot_v1_dot_api__pb2.GetInfoRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.GetInfoResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'api.v1.InformationService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('api.v1.InformationService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class InformationService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def GetInfo(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.InformationService/GetInfo', + api_dot_v1_dot_api__pb2.GetInfoRequest.SerializeToString, + api_dot_v1_dot_api__pb2.GetInfoResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class PluginServiceStub(object): + """Deprecated: use ConnectorService and ProcessorService instead. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListPlugins = channel.unary_unary( + '/api.v1.PluginService/ListPlugins', + request_serializer=api_dot_v1_dot_api__pb2.ListPluginsRequest.SerializeToString, + response_deserializer=api_dot_v1_dot_api__pb2.ListPluginsResponse.FromString, + _registered_method=True) + + +class PluginServiceServicer(object): + """Deprecated: use ConnectorService and ProcessorService instead. + """ + + def ListPlugins(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_PluginServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListPlugins': grpc.unary_unary_rpc_method_handler( + servicer.ListPlugins, + request_deserializer=api_dot_v1_dot_api__pb2.ListPluginsRequest.FromString, + response_serializer=api_dot_v1_dot_api__pb2.ListPluginsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'api.v1.PluginService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('api.v1.PluginService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class PluginService(object): + """Deprecated: use ConnectorService and ProcessorService instead. + """ + + @staticmethod + def ListPlugins(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/api.v1.PluginService/ListPlugins', + api_dot_v1_dot_api__pb2.ListPluginsRequest.SerializeToString, + api_dot_v1_dot_api__pb2.ListPluginsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/src/conduit/_grpc/config/v1/parameter_pb2.py b/src/conduit/_grpc/config/v1/parameter_pb2.py new file mode 100644 index 0000000..4ccd7ac --- /dev/null +++ b/src/conduit/_grpc/config/v1/parameter_pb2.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: config/v1/parameter.proto +# Protobuf Python Version: 5.29.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 2, + '', + 'config/v1/parameter.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19\x63onfig/v1/parameter.proto\x12\tconfig.v1\"\xad\x02\n\tParameter\x12\x18\n\x07\x64\x65\x66\x61ult\x18\x01 \x01(\tR\x07\x64\x65\x66\x61ult\x12 \n\x0b\x64\x65scription\x18\x02 \x01(\tR\x0b\x64\x65scription\x12-\n\x04type\x18\x03 \x01(\x0e\x32\x19.config.v1.Parameter.TypeR\x04type\x12\x37\n\x0bvalidations\x18\x04 \x03(\x0b\x32\x15.config.v1.ValidationR\x0bvalidations\"|\n\x04Type\x12\x14\n\x10TYPE_UNSPECIFIED\x10\x00\x12\x0f\n\x0bTYPE_STRING\x10\x01\x12\x0c\n\x08TYPE_INT\x10\x02\x12\x0e\n\nTYPE_FLOAT\x10\x03\x12\r\n\tTYPE_BOOL\x10\x04\x12\r\n\tTYPE_FILE\x10\x05\x12\x11\n\rTYPE_DURATION\x10\x06\"\xe7\x01\n\nValidation\x12.\n\x04type\x18\x01 \x01(\x0e\x32\x1a.config.v1.Validation.TypeR\x04type\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value\"\x92\x01\n\x04Type\x12\x14\n\x10TYPE_UNSPECIFIED\x10\x00\x12\x11\n\rTYPE_REQUIRED\x10\x01\x12\x15\n\x11TYPE_GREATER_THAN\x10\x02\x12\x12\n\x0eTYPE_LESS_THAN\x10\x03\x12\x12\n\x0eTYPE_INCLUSION\x10\x04\x12\x12\n\x0eTYPE_EXCLUSION\x10\x05\x12\x0e\n\nTYPE_REGEX\x10\x06\x42\x36Z4github.com/conduitio/conduit-commons/proto/config/v1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'config.v1.parameter_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'Z4github.com/conduitio/conduit-commons/proto/config/v1' + _globals['_PARAMETER']._serialized_start=41 + _globals['_PARAMETER']._serialized_end=342 + _globals['_PARAMETER_TYPE']._serialized_start=218 + _globals['_PARAMETER_TYPE']._serialized_end=342 + _globals['_VALIDATION']._serialized_start=345 + _globals['_VALIDATION']._serialized_end=576 + _globals['_VALIDATION_TYPE']._serialized_start=430 + _globals['_VALIDATION_TYPE']._serialized_end=576 +# @@protoc_insertion_point(module_scope) diff --git a/src/conduit/_grpc/config/v1/parameter_pb2.pyi b/src/conduit/_grpc/config/v1/parameter_pb2.pyi new file mode 100644 index 0000000..3988851 --- /dev/null +++ b/src/conduit/_grpc/config/v1/parameter_pb2.pyi @@ -0,0 +1,59 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class Parameter(_message.Message): + __slots__ = ("default", "description", "type", "validations") + class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TYPE_UNSPECIFIED: _ClassVar[Parameter.Type] + TYPE_STRING: _ClassVar[Parameter.Type] + TYPE_INT: _ClassVar[Parameter.Type] + TYPE_FLOAT: _ClassVar[Parameter.Type] + TYPE_BOOL: _ClassVar[Parameter.Type] + TYPE_FILE: _ClassVar[Parameter.Type] + TYPE_DURATION: _ClassVar[Parameter.Type] + TYPE_UNSPECIFIED: Parameter.Type + TYPE_STRING: Parameter.Type + TYPE_INT: Parameter.Type + TYPE_FLOAT: Parameter.Type + TYPE_BOOL: Parameter.Type + TYPE_FILE: Parameter.Type + TYPE_DURATION: Parameter.Type + DEFAULT_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + VALIDATIONS_FIELD_NUMBER: _ClassVar[int] + default: str + description: str + type: Parameter.Type + validations: _containers.RepeatedCompositeFieldContainer[Validation] + def __init__(self, default: _Optional[str] = ..., description: _Optional[str] = ..., type: _Optional[_Union[Parameter.Type, str]] = ..., validations: _Optional[_Iterable[_Union[Validation, _Mapping]]] = ...) -> None: ... + +class Validation(_message.Message): + __slots__ = ("type", "value") + class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TYPE_UNSPECIFIED: _ClassVar[Validation.Type] + TYPE_REQUIRED: _ClassVar[Validation.Type] + TYPE_GREATER_THAN: _ClassVar[Validation.Type] + TYPE_LESS_THAN: _ClassVar[Validation.Type] + TYPE_INCLUSION: _ClassVar[Validation.Type] + TYPE_EXCLUSION: _ClassVar[Validation.Type] + TYPE_REGEX: _ClassVar[Validation.Type] + TYPE_UNSPECIFIED: Validation.Type + TYPE_REQUIRED: Validation.Type + TYPE_GREATER_THAN: Validation.Type + TYPE_LESS_THAN: Validation.Type + TYPE_INCLUSION: Validation.Type + TYPE_EXCLUSION: Validation.Type + TYPE_REGEX: Validation.Type + TYPE_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + type: Validation.Type + value: str + def __init__(self, type: _Optional[_Union[Validation.Type, str]] = ..., value: _Optional[str] = ...) -> None: ... diff --git a/src/conduit/_grpc/config/v1/parameter_pb2_grpc.py b/src/conduit/_grpc/config/v1/parameter_pb2_grpc.py new file mode 100644 index 0000000..2daafff --- /dev/null +++ b/src/conduit/_grpc/config/v1/parameter_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/src/conduit/_grpc/google/api/annotations_pb2.py b/src/conduit/_grpc/google/api/annotations_pb2.py new file mode 100644 index 0000000..e6596f8 --- /dev/null +++ b/src/conduit/_grpc/google/api/annotations_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/api/annotations.proto +# Protobuf Python Version: 5.29.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 2, + '', + 'google/api/annotations.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.api import http_pb2 as google_dot_api_dot_http__pb2 +from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cgoogle/api/annotations.proto\x12\ngoogle.api\x1a\x15google/api/http.proto\x1a google/protobuf/descriptor.proto:K\n\x04http\x12\x1e.google.protobuf.MethodOptions\x18\xb0\xca\xbc\" \x01(\x0b\x32\x14.google.api.HttpRuleR\x04httpBn\n\x0e\x63om.google.apiB\x10\x41nnotationsProtoP\x01ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\xa2\x02\x04GAPIb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.api.annotations_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\016com.google.apiB\020AnnotationsProtoP\001ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\242\002\004GAPI' +# @@protoc_insertion_point(module_scope) diff --git a/src/conduit/_grpc/google/api/annotations_pb2.pyi b/src/conduit/_grpc/google/api/annotations_pb2.pyi new file mode 100644 index 0000000..b818f18 --- /dev/null +++ b/src/conduit/_grpc/google/api/annotations_pb2.pyi @@ -0,0 +1,8 @@ +from google.api import http_pb2 as _http_pb2 +from google.protobuf import descriptor_pb2 as _descriptor_pb2 +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor +HTTP_FIELD_NUMBER: _ClassVar[int] +http: _descriptor.FieldDescriptor diff --git a/src/conduit/_grpc/google/api/annotations_pb2_grpc.py b/src/conduit/_grpc/google/api/annotations_pb2_grpc.py new file mode 100644 index 0000000..2daafff --- /dev/null +++ b/src/conduit/_grpc/google/api/annotations_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/src/conduit/_grpc/google/api/field_behavior_pb2.py b/src/conduit/_grpc/google/api/field_behavior_pb2.py new file mode 100644 index 0000000..4772ad6 --- /dev/null +++ b/src/conduit/_grpc/google/api/field_behavior_pb2.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/api/field_behavior.proto +# Protobuf Python Version: 5.29.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 2, + '', + 'google/api/field_behavior.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fgoogle/api/field_behavior.proto\x12\ngoogle.api\x1a google/protobuf/descriptor.proto*\xb6\x01\n\rFieldBehavior\x12\x1e\n\x1a\x46IELD_BEHAVIOR_UNSPECIFIED\x10\x00\x12\x0c\n\x08OPTIONAL\x10\x01\x12\x0c\n\x08REQUIRED\x10\x02\x12\x0f\n\x0bOUTPUT_ONLY\x10\x03\x12\x0e\n\nINPUT_ONLY\x10\x04\x12\r\n\tIMMUTABLE\x10\x05\x12\x12\n\x0eUNORDERED_LIST\x10\x06\x12\x15\n\x11NON_EMPTY_DEFAULT\x10\x07\x12\x0e\n\nIDENTIFIER\x10\x08:d\n\x0e\x66ield_behavior\x12\x1d.google.protobuf.FieldOptions\x18\x9c\x08 \x03(\x0e\x32\x19.google.api.FieldBehaviorB\x02\x10\x00R\rfieldBehaviorBp\n\x0e\x63om.google.apiB\x12\x46ieldBehaviorProtoP\x01ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\xa2\x02\x04GAPIb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.api.field_behavior_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\016com.google.apiB\022FieldBehaviorProtoP\001ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\242\002\004GAPI' + _globals['field_behavior']._loaded_options = None + _globals['field_behavior']._serialized_options = b'\020\000' + _globals['_FIELDBEHAVIOR']._serialized_start=82 + _globals['_FIELDBEHAVIOR']._serialized_end=264 +# @@protoc_insertion_point(module_scope) diff --git a/src/conduit/_grpc/google/api/field_behavior_pb2.pyi b/src/conduit/_grpc/google/api/field_behavior_pb2.pyi new file mode 100644 index 0000000..c090c5d --- /dev/null +++ b/src/conduit/_grpc/google/api/field_behavior_pb2.pyi @@ -0,0 +1,29 @@ +from google.protobuf import descriptor_pb2 as _descriptor_pb2 +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor + +class FieldBehavior(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + FIELD_BEHAVIOR_UNSPECIFIED: _ClassVar[FieldBehavior] + OPTIONAL: _ClassVar[FieldBehavior] + REQUIRED: _ClassVar[FieldBehavior] + OUTPUT_ONLY: _ClassVar[FieldBehavior] + INPUT_ONLY: _ClassVar[FieldBehavior] + IMMUTABLE: _ClassVar[FieldBehavior] + UNORDERED_LIST: _ClassVar[FieldBehavior] + NON_EMPTY_DEFAULT: _ClassVar[FieldBehavior] + IDENTIFIER: _ClassVar[FieldBehavior] +FIELD_BEHAVIOR_UNSPECIFIED: FieldBehavior +OPTIONAL: FieldBehavior +REQUIRED: FieldBehavior +OUTPUT_ONLY: FieldBehavior +INPUT_ONLY: FieldBehavior +IMMUTABLE: FieldBehavior +UNORDERED_LIST: FieldBehavior +NON_EMPTY_DEFAULT: FieldBehavior +IDENTIFIER: FieldBehavior +FIELD_BEHAVIOR_FIELD_NUMBER: _ClassVar[int] +field_behavior: _descriptor.FieldDescriptor diff --git a/src/conduit/_grpc/google/api/field_behavior_pb2_grpc.py b/src/conduit/_grpc/google/api/field_behavior_pb2_grpc.py new file mode 100644 index 0000000..2daafff --- /dev/null +++ b/src/conduit/_grpc/google/api/field_behavior_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/src/conduit/_grpc/google/api/http_pb2.py b/src/conduit/_grpc/google/api/http_pb2.py new file mode 100644 index 0000000..2163201 --- /dev/null +++ b/src/conduit/_grpc/google/api/http_pb2.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: google/api/http.proto +# Protobuf Python Version: 5.29.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 2, + '', + 'google/api/http.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15google/api/http.proto\x12\ngoogle.api\"y\n\x04Http\x12*\n\x05rules\x18\x01 \x03(\x0b\x32\x14.google.api.HttpRuleR\x05rules\x12\x45\n\x1f\x66ully_decode_reserved_expansion\x18\x02 \x01(\x08R\x1c\x66ullyDecodeReservedExpansion\"\xda\x02\n\x08HttpRule\x12\x1a\n\x08selector\x18\x01 \x01(\tR\x08selector\x12\x12\n\x03get\x18\x02 \x01(\tH\x00R\x03get\x12\x12\n\x03put\x18\x03 \x01(\tH\x00R\x03put\x12\x14\n\x04post\x18\x04 \x01(\tH\x00R\x04post\x12\x18\n\x06\x64\x65lete\x18\x05 \x01(\tH\x00R\x06\x64\x65lete\x12\x16\n\x05patch\x18\x06 \x01(\tH\x00R\x05patch\x12\x37\n\x06\x63ustom\x18\x08 \x01(\x0b\x32\x1d.google.api.CustomHttpPatternH\x00R\x06\x63ustom\x12\x12\n\x04\x62ody\x18\x07 \x01(\tR\x04\x62ody\x12#\n\rresponse_body\x18\x0c \x01(\tR\x0cresponseBody\x12\x45\n\x13\x61\x64\x64itional_bindings\x18\x0b \x03(\x0b\x32\x14.google.api.HttpRuleR\x12\x61\x64\x64itionalBindingsB\t\n\x07pattern\";\n\x11\x43ustomHttpPattern\x12\x12\n\x04kind\x18\x01 \x01(\tR\x04kind\x12\x12\n\x04path\x18\x02 \x01(\tR\x04pathBg\n\x0e\x63om.google.apiB\tHttpProtoP\x01ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\xa2\x02\x04GAPIb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.api.http_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\016com.google.apiB\tHttpProtoP\001ZAgoogle.golang.org/genproto/googleapis/api/annotations;annotations\242\002\004GAPI' + _globals['_HTTP']._serialized_start=37 + _globals['_HTTP']._serialized_end=158 + _globals['_HTTPRULE']._serialized_start=161 + _globals['_HTTPRULE']._serialized_end=507 + _globals['_CUSTOMHTTPPATTERN']._serialized_start=509 + _globals['_CUSTOMHTTPPATTERN']._serialized_end=568 +# @@protoc_insertion_point(module_scope) diff --git a/src/conduit/_grpc/google/api/http_pb2.pyi b/src/conduit/_grpc/google/api/http_pb2.pyi new file mode 100644 index 0000000..902f96e --- /dev/null +++ b/src/conduit/_grpc/google/api/http_pb2.pyi @@ -0,0 +1,46 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class Http(_message.Message): + __slots__ = ("rules", "fully_decode_reserved_expansion") + RULES_FIELD_NUMBER: _ClassVar[int] + FULLY_DECODE_RESERVED_EXPANSION_FIELD_NUMBER: _ClassVar[int] + rules: _containers.RepeatedCompositeFieldContainer[HttpRule] + fully_decode_reserved_expansion: bool + def __init__(self, rules: _Optional[_Iterable[_Union[HttpRule, _Mapping]]] = ..., fully_decode_reserved_expansion: bool = ...) -> None: ... + +class HttpRule(_message.Message): + __slots__ = ("selector", "get", "put", "post", "delete", "patch", "custom", "body", "response_body", "additional_bindings") + SELECTOR_FIELD_NUMBER: _ClassVar[int] + GET_FIELD_NUMBER: _ClassVar[int] + PUT_FIELD_NUMBER: _ClassVar[int] + POST_FIELD_NUMBER: _ClassVar[int] + DELETE_FIELD_NUMBER: _ClassVar[int] + PATCH_FIELD_NUMBER: _ClassVar[int] + CUSTOM_FIELD_NUMBER: _ClassVar[int] + BODY_FIELD_NUMBER: _ClassVar[int] + RESPONSE_BODY_FIELD_NUMBER: _ClassVar[int] + ADDITIONAL_BINDINGS_FIELD_NUMBER: _ClassVar[int] + selector: str + get: str + put: str + post: str + delete: str + patch: str + custom: CustomHttpPattern + body: str + response_body: str + additional_bindings: _containers.RepeatedCompositeFieldContainer[HttpRule] + def __init__(self, selector: _Optional[str] = ..., get: _Optional[str] = ..., put: _Optional[str] = ..., post: _Optional[str] = ..., delete: _Optional[str] = ..., patch: _Optional[str] = ..., custom: _Optional[_Union[CustomHttpPattern, _Mapping]] = ..., body: _Optional[str] = ..., response_body: _Optional[str] = ..., additional_bindings: _Optional[_Iterable[_Union[HttpRule, _Mapping]]] = ...) -> None: ... + +class CustomHttpPattern(_message.Message): + __slots__ = ("kind", "path") + KIND_FIELD_NUMBER: _ClassVar[int] + PATH_FIELD_NUMBER: _ClassVar[int] + kind: str + path: str + def __init__(self, kind: _Optional[str] = ..., path: _Optional[str] = ...) -> None: ... diff --git a/src/conduit/_grpc/google/api/http_pb2_grpc.py b/src/conduit/_grpc/google/api/http_pb2_grpc.py new file mode 100644 index 0000000..2daafff --- /dev/null +++ b/src/conduit/_grpc/google/api/http_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/src/conduit/_grpc/opencdc/v1/opencdc_pb2.py b/src/conduit/_grpc/opencdc/v1/opencdc_pb2.py new file mode 100644 index 0000000..dab96f5 --- /dev/null +++ b/src/conduit/_grpc/opencdc/v1/opencdc_pb2.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: opencdc/v1/opencdc.proto +# Protobuf Python Version: 5.29.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 2, + '', + 'opencdc/v1/opencdc.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 +from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18opencdc/v1/opencdc.proto\x12\nopencdc.v1\x1a google/protobuf/descriptor.proto\x1a\x1cgoogle/protobuf/struct.proto\"\xa6\x02\n\x06Record\x12\x1a\n\x08position\x18\x01 \x01(\x0cR\x08position\x12\x33\n\toperation\x18\x02 \x01(\x0e\x32\x15.opencdc.v1.OperationR\toperation\x12<\n\x08metadata\x18\x03 \x03(\x0b\x32 .opencdc.v1.Record.MetadataEntryR\x08metadata\x12\"\n\x03key\x18\x04 \x01(\x0b\x32\x10.opencdc.v1.DataR\x03key\x12,\n\x07payload\x18\x05 \x01(\x0b\x32\x12.opencdc.v1.ChangeR\x07payload\x1a;\n\rMetadataEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"Z\n\x06\x43hange\x12(\n\x06\x62\x65\x66ore\x18\x01 \x01(\x0b\x32\x10.opencdc.v1.DataR\x06\x62\x65\x66ore\x12&\n\x05\x61\x66ter\x18\x02 \x01(\x0b\x32\x10.opencdc.v1.DataR\x05\x61\x66ter\"o\n\x04\x44\x61ta\x12\x1b\n\x08raw_data\x18\x01 \x01(\x0cH\x00R\x07rawData\x12\x42\n\x0fstructured_data\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructH\x00R\x0estructuredDataB\x06\n\x04\x64\x61ta*\x80\x01\n\tOperation\x12\x19\n\x15OPERATION_UNSPECIFIED\x10\x00\x12\x14\n\x10OPERATION_CREATE\x10\x01\x12\x14\n\x10OPERATION_UPDATE\x10\x02\x12\x14\n\x10OPERATION_DELETE\x10\x03\x12\x16\n\x12OPERATION_SNAPSHOT\x10\x04:F\n\x0fopencdc_version\x12\x1c.google.protobuf.FileOptions\x18\x8fN \x01(\tR\x0eopencdcVersion:H\n\x10metadata_version\x12\x1c.google.protobuf.FileOptions\x18\x90N \x01(\tR\x0fmetadataVersion:M\n\x13metadata_created_at\x12\x1c.google.protobuf.FileOptions\x18\x91N \x01(\tR\x11metadataCreatedAt:G\n\x10metadata_read_at\x12\x1c.google.protobuf.FileOptions\x18\x92N \x01(\tR\x0emetadataReadAt:N\n\x13metadata_collection\x12\x1c.google.protobuf.FileOptions\x18\x93N \x01(\tR\x12metadataCollection:\\\n\x1bmetadata_key_schema_subject\x12\x1c.google.protobuf.FileOptions\x18\x94N \x01(\tR\x18metadataKeySchemaSubject:\\\n\x1bmetadata_key_schema_version\x12\x1c.google.protobuf.FileOptions\x18\x95N \x01(\tR\x18metadataKeySchemaVersion:d\n\x1fmetadata_payload_schema_subject\x12\x1c.google.protobuf.FileOptions\x18\x96N \x01(\tR\x1cmetadataPayloadSchemaSubject:d\n\x1fmetadata_payload_schema_version\x12\x1c.google.protobuf.FileOptions\x18\x97N \x01(\tR\x1cmetadataPayloadSchemaVersion:K\n\x12metadata_file_name\x12\x1c.google.protobuf.FileOptions\x18\x98N \x01(\tR\x10metadataFileName:K\n\x12metadata_file_size\x12\x1c.google.protobuf.FileOptions\x18\x99N \x01(\tR\x10metadataFileSize:K\n\x12metadata_file_hash\x12\x1c.google.protobuf.FileOptions\x18\x9aN \x01(\tR\x10metadataFileHash:Q\n\x15metadata_file_chunked\x12\x1c.google.protobuf.FileOptions\x18\x9bN \x01(\tR\x13metadataFileChunked:X\n\x19metadata_file_chunk_index\x12\x1c.google.protobuf.FileOptions\x18\x9cN \x01(\tR\x16metadataFileChunkIndex:X\n\x19metadata_file_chunk_count\x12\x1c.google.protobuf.FileOptions\x18\x9dN \x01(\tR\x16metadataFileChunkCountB\x9c\x03Z5github.com/conduitio/conduit-commons/proto/opencdc/v1\xfa\xf0\x04\x02v1\x82\xf1\x04\x0fopencdc.version\x8a\xf1\x04\x11opencdc.createdAt\x92\xf1\x04\x0eopencdc.readAt\x9a\xf1\x04\x12opencdc.collection\xa2\xf1\x04\x1aopencdc.key.schema.subject\xaa\xf1\x04\x1aopencdc.key.schema.version\xb2\xf1\x04\x1eopencdc.payload.schema.subject\xba\xf1\x04\x1eopencdc.payload.schema.version\xc2\xf1\x04\x11opencdc.file.name\xca\xf1\x04\x11opencdc.file.size\xd2\xf1\x04\x11opencdc.file.hash\xda\xf1\x04\x14opencdc.file.chunked\xe2\xf1\x04\x18opencdc.file.chunk.index\xea\xf1\x04\x18opencdc.file.chunk.countb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opencdc.v1.opencdc_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'Z5github.com/conduitio/conduit-commons/proto/opencdc/v1\372\360\004\002v1\202\361\004\017opencdc.version\212\361\004\021opencdc.createdAt\222\361\004\016opencdc.readAt\232\361\004\022opencdc.collection\242\361\004\032opencdc.key.schema.subject\252\361\004\032opencdc.key.schema.version\262\361\004\036opencdc.payload.schema.subject\272\361\004\036opencdc.payload.schema.version\302\361\004\021opencdc.file.name\312\361\004\021opencdc.file.size\322\361\004\021opencdc.file.hash\332\361\004\024opencdc.file.chunked\342\361\004\030opencdc.file.chunk.index\352\361\004\030opencdc.file.chunk.count' + _globals['_RECORD_METADATAENTRY']._loaded_options = None + _globals['_RECORD_METADATAENTRY']._serialized_options = b'8\001' + _globals['_OPERATION']._serialized_start=607 + _globals['_OPERATION']._serialized_end=735 + _globals['_RECORD']._serialized_start=105 + _globals['_RECORD']._serialized_end=399 + _globals['_RECORD_METADATAENTRY']._serialized_start=340 + _globals['_RECORD_METADATAENTRY']._serialized_end=399 + _globals['_CHANGE']._serialized_start=401 + _globals['_CHANGE']._serialized_end=491 + _globals['_DATA']._serialized_start=493 + _globals['_DATA']._serialized_end=604 +# @@protoc_insertion_point(module_scope) diff --git a/src/conduit/_grpc/opencdc/v1/opencdc_pb2.pyi b/src/conduit/_grpc/opencdc/v1/opencdc_pb2.pyi new file mode 100644 index 0000000..0c9a0e9 --- /dev/null +++ b/src/conduit/_grpc/opencdc/v1/opencdc_pb2.pyi @@ -0,0 +1,89 @@ +from google.protobuf import descriptor_pb2 as _descriptor_pb2 +from google.protobuf import struct_pb2 as _struct_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class Operation(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + OPERATION_UNSPECIFIED: _ClassVar[Operation] + OPERATION_CREATE: _ClassVar[Operation] + OPERATION_UPDATE: _ClassVar[Operation] + OPERATION_DELETE: _ClassVar[Operation] + OPERATION_SNAPSHOT: _ClassVar[Operation] +OPERATION_UNSPECIFIED: Operation +OPERATION_CREATE: Operation +OPERATION_UPDATE: Operation +OPERATION_DELETE: Operation +OPERATION_SNAPSHOT: Operation +OPENCDC_VERSION_FIELD_NUMBER: _ClassVar[int] +opencdc_version: _descriptor.FieldDescriptor +METADATA_VERSION_FIELD_NUMBER: _ClassVar[int] +metadata_version: _descriptor.FieldDescriptor +METADATA_CREATED_AT_FIELD_NUMBER: _ClassVar[int] +metadata_created_at: _descriptor.FieldDescriptor +METADATA_READ_AT_FIELD_NUMBER: _ClassVar[int] +metadata_read_at: _descriptor.FieldDescriptor +METADATA_COLLECTION_FIELD_NUMBER: _ClassVar[int] +metadata_collection: _descriptor.FieldDescriptor +METADATA_KEY_SCHEMA_SUBJECT_FIELD_NUMBER: _ClassVar[int] +metadata_key_schema_subject: _descriptor.FieldDescriptor +METADATA_KEY_SCHEMA_VERSION_FIELD_NUMBER: _ClassVar[int] +metadata_key_schema_version: _descriptor.FieldDescriptor +METADATA_PAYLOAD_SCHEMA_SUBJECT_FIELD_NUMBER: _ClassVar[int] +metadata_payload_schema_subject: _descriptor.FieldDescriptor +METADATA_PAYLOAD_SCHEMA_VERSION_FIELD_NUMBER: _ClassVar[int] +metadata_payload_schema_version: _descriptor.FieldDescriptor +METADATA_FILE_NAME_FIELD_NUMBER: _ClassVar[int] +metadata_file_name: _descriptor.FieldDescriptor +METADATA_FILE_SIZE_FIELD_NUMBER: _ClassVar[int] +metadata_file_size: _descriptor.FieldDescriptor +METADATA_FILE_HASH_FIELD_NUMBER: _ClassVar[int] +metadata_file_hash: _descriptor.FieldDescriptor +METADATA_FILE_CHUNKED_FIELD_NUMBER: _ClassVar[int] +metadata_file_chunked: _descriptor.FieldDescriptor +METADATA_FILE_CHUNK_INDEX_FIELD_NUMBER: _ClassVar[int] +metadata_file_chunk_index: _descriptor.FieldDescriptor +METADATA_FILE_CHUNK_COUNT_FIELD_NUMBER: _ClassVar[int] +metadata_file_chunk_count: _descriptor.FieldDescriptor + +class Record(_message.Message): + __slots__ = ("position", "operation", "metadata", "key", "payload") + class MetadataEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + POSITION_FIELD_NUMBER: _ClassVar[int] + OPERATION_FIELD_NUMBER: _ClassVar[int] + METADATA_FIELD_NUMBER: _ClassVar[int] + KEY_FIELD_NUMBER: _ClassVar[int] + PAYLOAD_FIELD_NUMBER: _ClassVar[int] + position: bytes + operation: Operation + metadata: _containers.ScalarMap[str, str] + key: Data + payload: Change + def __init__(self, position: _Optional[bytes] = ..., operation: _Optional[_Union[Operation, str]] = ..., metadata: _Optional[_Mapping[str, str]] = ..., key: _Optional[_Union[Data, _Mapping]] = ..., payload: _Optional[_Union[Change, _Mapping]] = ...) -> None: ... + +class Change(_message.Message): + __slots__ = ("before", "after") + BEFORE_FIELD_NUMBER: _ClassVar[int] + AFTER_FIELD_NUMBER: _ClassVar[int] + before: Data + after: Data + def __init__(self, before: _Optional[_Union[Data, _Mapping]] = ..., after: _Optional[_Union[Data, _Mapping]] = ...) -> None: ... + +class Data(_message.Message): + __slots__ = ("raw_data", "structured_data") + RAW_DATA_FIELD_NUMBER: _ClassVar[int] + STRUCTURED_DATA_FIELD_NUMBER: _ClassVar[int] + raw_data: bytes + structured_data: _struct_pb2.Struct + def __init__(self, raw_data: _Optional[bytes] = ..., structured_data: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... diff --git a/src/conduit/_grpc/opencdc/v1/opencdc_pb2_grpc.py b/src/conduit/_grpc/opencdc/v1/opencdc_pb2_grpc.py new file mode 100644 index 0000000..2daafff --- /dev/null +++ b/src/conduit/_grpc/opencdc/v1/opencdc_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/src/conduit/_grpc/protoc_gen_openapiv2/options/annotations_pb2.py b/src/conduit/_grpc/protoc_gen_openapiv2/options/annotations_pb2.py new file mode 100644 index 0000000..08abd78 --- /dev/null +++ b/src/conduit/_grpc/protoc_gen_openapiv2/options/annotations_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: protoc-gen-openapiv2/options/annotations.proto +# Protobuf Python Version: 5.29.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 2, + '', + 'protoc-gen-openapiv2/options/annotations.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 +from protoc_gen_openapiv2.options import openapiv2_pb2 as protoc__gen__openapiv2_dot_options_dot_openapiv2__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n.protoc-gen-openapiv2/options/annotations.proto\x12)grpc.gateway.protoc_gen_openapiv2.options\x1a google/protobuf/descriptor.proto\x1a,protoc-gen-openapiv2/options/openapiv2.proto:~\n\x11openapiv2_swagger\x12\x1c.google.protobuf.FileOptions\x18\x92\x08 \x01(\x0b\x32\x32.grpc.gateway.protoc_gen_openapiv2.options.SwaggerR\x10openapiv2Swagger:\x86\x01\n\x13openapiv2_operation\x12\x1e.google.protobuf.MethodOptions\x18\x92\x08 \x01(\x0b\x32\x34.grpc.gateway.protoc_gen_openapiv2.options.OperationR\x12openapiv2Operation:~\n\x10openapiv2_schema\x12\x1f.google.protobuf.MessageOptions\x18\x92\x08 \x01(\x0b\x32\x31.grpc.gateway.protoc_gen_openapiv2.options.SchemaR\x0fopenapiv2Schema:{\n\x0eopenapiv2_enum\x12\x1c.google.protobuf.EnumOptions\x18\x92\x08 \x01(\x0b\x32\x35.grpc.gateway.protoc_gen_openapiv2.options.EnumSchemaR\ropenapiv2Enum:u\n\ropenapiv2_tag\x12\x1f.google.protobuf.ServiceOptions\x18\x92\x08 \x01(\x0b\x32..grpc.gateway.protoc_gen_openapiv2.options.TagR\x0copenapiv2Tag:~\n\x0fopenapiv2_field\x12\x1d.google.protobuf.FieldOptions\x18\x92\x08 \x01(\x0b\x32\x35.grpc.gateway.protoc_gen_openapiv2.options.JSONSchemaR\x0eopenapiv2FieldBHZFgithub.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/optionsb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'protoc_gen_openapiv2.options.annotations_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZFgithub.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options' +# @@protoc_insertion_point(module_scope) diff --git a/src/conduit/_grpc/protoc_gen_openapiv2/options/annotations_pb2.pyi b/src/conduit/_grpc/protoc_gen_openapiv2/options/annotations_pb2.pyi new file mode 100644 index 0000000..48b05b7 --- /dev/null +++ b/src/conduit/_grpc/protoc_gen_openapiv2/options/annotations_pb2.pyi @@ -0,0 +1,18 @@ +from google.protobuf import descriptor_pb2 as _descriptor_pb2 +from protoc_gen_openapiv2.options import openapiv2_pb2 as _openapiv2_pb2 +from google.protobuf import descriptor as _descriptor +from typing import ClassVar as _ClassVar + +DESCRIPTOR: _descriptor.FileDescriptor +OPENAPIV2_SWAGGER_FIELD_NUMBER: _ClassVar[int] +openapiv2_swagger: _descriptor.FieldDescriptor +OPENAPIV2_OPERATION_FIELD_NUMBER: _ClassVar[int] +openapiv2_operation: _descriptor.FieldDescriptor +OPENAPIV2_SCHEMA_FIELD_NUMBER: _ClassVar[int] +openapiv2_schema: _descriptor.FieldDescriptor +OPENAPIV2_ENUM_FIELD_NUMBER: _ClassVar[int] +openapiv2_enum: _descriptor.FieldDescriptor +OPENAPIV2_TAG_FIELD_NUMBER: _ClassVar[int] +openapiv2_tag: _descriptor.FieldDescriptor +OPENAPIV2_FIELD_FIELD_NUMBER: _ClassVar[int] +openapiv2_field: _descriptor.FieldDescriptor diff --git a/src/conduit/_grpc/protoc_gen_openapiv2/options/annotations_pb2_grpc.py b/src/conduit/_grpc/protoc_gen_openapiv2/options/annotations_pb2_grpc.py new file mode 100644 index 0000000..2daafff --- /dev/null +++ b/src/conduit/_grpc/protoc_gen_openapiv2/options/annotations_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/src/conduit/_grpc/protoc_gen_openapiv2/options/openapiv2_pb2.py b/src/conduit/_grpc/protoc_gen_openapiv2/options/openapiv2_pb2.py new file mode 100644 index 0000000..8ee0620 --- /dev/null +++ b/src/conduit/_grpc/protoc_gen_openapiv2/options/openapiv2_pb2.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: protoc-gen-openapiv2/options/openapiv2.proto +# Protobuf Python Version: 5.29.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 2, + '', + 'protoc-gen-openapiv2/options/openapiv2.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n,protoc-gen-openapiv2/options/openapiv2.proto\x12)grpc.gateway.protoc_gen_openapiv2.options\x1a\x1cgoogle/protobuf/struct.proto\"\xb3\x08\n\x07Swagger\x12\x18\n\x07swagger\x18\x01 \x01(\tR\x07swagger\x12\x43\n\x04info\x18\x02 \x01(\x0b\x32/.grpc.gateway.protoc_gen_openapiv2.options.InfoR\x04info\x12\x12\n\x04host\x18\x03 \x01(\tR\x04host\x12\x1b\n\tbase_path\x18\x04 \x01(\tR\x08\x62\x61sePath\x12K\n\x07schemes\x18\x05 \x03(\x0e\x32\x31.grpc.gateway.protoc_gen_openapiv2.options.SchemeR\x07schemes\x12\x1a\n\x08\x63onsumes\x18\x06 \x03(\tR\x08\x63onsumes\x12\x1a\n\x08produces\x18\x07 \x03(\tR\x08produces\x12_\n\tresponses\x18\n \x03(\x0b\x32\x41.grpc.gateway.protoc_gen_openapiv2.options.Swagger.ResponsesEntryR\tresponses\x12q\n\x14security_definitions\x18\x0b \x01(\x0b\x32>.grpc.gateway.protoc_gen_openapiv2.options.SecurityDefinitionsR\x13securityDefinitions\x12Z\n\x08security\x18\x0c \x03(\x0b\x32>.grpc.gateway.protoc_gen_openapiv2.options.SecurityRequirementR\x08security\x12\x42\n\x04tags\x18\r \x03(\x0b\x32..grpc.gateway.protoc_gen_openapiv2.options.TagR\x04tags\x12\x65\n\rexternal_docs\x18\x0e \x01(\x0b\x32@.grpc.gateway.protoc_gen_openapiv2.options.ExternalDocumentationR\x0c\x65xternalDocs\x12\x62\n\nextensions\x18\x0f \x03(\x0b\x32\x42.grpc.gateway.protoc_gen_openapiv2.options.Swagger.ExtensionsEntryR\nextensions\x1aq\n\x0eResponsesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12I\n\x05value\x18\x02 \x01(\x0b\x32\x33.grpc.gateway.protoc_gen_openapiv2.options.ResponseR\x05value:\x02\x38\x01\x1aU\n\x0f\x45xtensionsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x05value:\x02\x38\x01J\x04\x08\x08\x10\tJ\x04\x08\t\x10\n\"\xd6\x07\n\tOperation\x12\x12\n\x04tags\x18\x01 \x03(\tR\x04tags\x12\x18\n\x07summary\x18\x02 \x01(\tR\x07summary\x12 \n\x0b\x64\x65scription\x18\x03 \x01(\tR\x0b\x64\x65scription\x12\x65\n\rexternal_docs\x18\x04 \x01(\x0b\x32@.grpc.gateway.protoc_gen_openapiv2.options.ExternalDocumentationR\x0c\x65xternalDocs\x12!\n\x0coperation_id\x18\x05 \x01(\tR\x0boperationId\x12\x1a\n\x08\x63onsumes\x18\x06 \x03(\tR\x08\x63onsumes\x12\x1a\n\x08produces\x18\x07 \x03(\tR\x08produces\x12\x61\n\tresponses\x18\t \x03(\x0b\x32\x43.grpc.gateway.protoc_gen_openapiv2.options.Operation.ResponsesEntryR\tresponses\x12K\n\x07schemes\x18\n \x03(\x0e\x32\x31.grpc.gateway.protoc_gen_openapiv2.options.SchemeR\x07schemes\x12\x1e\n\ndeprecated\x18\x0b \x01(\x08R\ndeprecated\x12Z\n\x08security\x18\x0c \x03(\x0b\x32>.grpc.gateway.protoc_gen_openapiv2.options.SecurityRequirementR\x08security\x12\x64\n\nextensions\x18\r \x03(\x0b\x32\x44.grpc.gateway.protoc_gen_openapiv2.options.Operation.ExtensionsEntryR\nextensions\x12U\n\nparameters\x18\x0e \x01(\x0b\x32\x35.grpc.gateway.protoc_gen_openapiv2.options.ParametersR\nparameters\x1aq\n\x0eResponsesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12I\n\x05value\x18\x02 \x01(\x0b\x32\x33.grpc.gateway.protoc_gen_openapiv2.options.ResponseR\x05value:\x02\x38\x01\x1aU\n\x0f\x45xtensionsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x05value:\x02\x38\x01J\x04\x08\x08\x10\t\"b\n\nParameters\x12T\n\x07headers\x18\x01 \x03(\x0b\x32:.grpc.gateway.protoc_gen_openapiv2.options.HeaderParameterR\x07headers\"\xa3\x02\n\x0fHeaderParameter\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12 \n\x0b\x64\x65scription\x18\x02 \x01(\tR\x0b\x64\x65scription\x12S\n\x04type\x18\x03 \x01(\x0e\x32?.grpc.gateway.protoc_gen_openapiv2.options.HeaderParameter.TypeR\x04type\x12\x16\n\x06\x66ormat\x18\x04 \x01(\tR\x06\x66ormat\x12\x1a\n\x08required\x18\x05 \x01(\x08R\x08required\"E\n\x04Type\x12\x0b\n\x07UNKNOWN\x10\x00\x12\n\n\x06STRING\x10\x01\x12\n\n\x06NUMBER\x10\x02\x12\x0b\n\x07INTEGER\x10\x03\x12\x0b\n\x07\x42OOLEAN\x10\x04J\x04\x08\x06\x10\x07J\x04\x08\x07\x10\x08\"\xd8\x01\n\x06Header\x12 \n\x0b\x64\x65scription\x18\x01 \x01(\tR\x0b\x64\x65scription\x12\x12\n\x04type\x18\x02 \x01(\tR\x04type\x12\x16\n\x06\x66ormat\x18\x03 \x01(\tR\x06\x66ormat\x12\x18\n\x07\x64\x65\x66\x61ult\x18\x06 \x01(\tR\x07\x64\x65\x66\x61ult\x12\x18\n\x07pattern\x18\r \x01(\tR\x07patternJ\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06J\x04\x08\x07\x10\x08J\x04\x08\x08\x10\tJ\x04\x08\t\x10\nJ\x04\x08\n\x10\x0bJ\x04\x08\x0b\x10\x0cJ\x04\x08\x0c\x10\rJ\x04\x08\x0e\x10\x0fJ\x04\x08\x0f\x10\x10J\x04\x08\x10\x10\x11J\x04\x08\x11\x10\x12J\x04\x08\x12\x10\x13\"\x9a\x05\n\x08Response\x12 \n\x0b\x64\x65scription\x18\x01 \x01(\tR\x0b\x64\x65scription\x12I\n\x06schema\x18\x02 \x01(\x0b\x32\x31.grpc.gateway.protoc_gen_openapiv2.options.SchemaR\x06schema\x12Z\n\x07headers\x18\x03 \x03(\x0b\x32@.grpc.gateway.protoc_gen_openapiv2.options.Response.HeadersEntryR\x07headers\x12]\n\x08\x65xamples\x18\x04 \x03(\x0b\x32\x41.grpc.gateway.protoc_gen_openapiv2.options.Response.ExamplesEntryR\x08\x65xamples\x12\x63\n\nextensions\x18\x05 \x03(\x0b\x32\x43.grpc.gateway.protoc_gen_openapiv2.options.Response.ExtensionsEntryR\nextensions\x1am\n\x0cHeadersEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12G\n\x05value\x18\x02 \x01(\x0b\x32\x31.grpc.gateway.protoc_gen_openapiv2.options.HeaderR\x05value:\x02\x38\x01\x1a;\n\rExamplesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\x1aU\n\x0f\x45xtensionsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x05value:\x02\x38\x01\"\xd6\x03\n\x04Info\x12\x14\n\x05title\x18\x01 \x01(\tR\x05title\x12 \n\x0b\x64\x65scription\x18\x02 \x01(\tR\x0b\x64\x65scription\x12(\n\x10terms_of_service\x18\x03 \x01(\tR\x0etermsOfService\x12L\n\x07\x63ontact\x18\x04 \x01(\x0b\x32\x32.grpc.gateway.protoc_gen_openapiv2.options.ContactR\x07\x63ontact\x12L\n\x07license\x18\x05 \x01(\x0b\x32\x32.grpc.gateway.protoc_gen_openapiv2.options.LicenseR\x07license\x12\x18\n\x07version\x18\x06 \x01(\tR\x07version\x12_\n\nextensions\x18\x07 \x03(\x0b\x32?.grpc.gateway.protoc_gen_openapiv2.options.Info.ExtensionsEntryR\nextensions\x1aU\n\x0f\x45xtensionsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x05value:\x02\x38\x01\"E\n\x07\x43ontact\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n\x03url\x18\x02 \x01(\tR\x03url\x12\x14\n\x05\x65mail\x18\x03 \x01(\tR\x05\x65mail\"/\n\x07License\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n\x03url\x18\x02 \x01(\tR\x03url\"K\n\x15\x45xternalDocumentation\x12 \n\x0b\x64\x65scription\x18\x01 \x01(\tR\x0b\x64\x65scription\x12\x10\n\x03url\x18\x02 \x01(\tR\x03url\"\xaa\x02\n\x06Schema\x12V\n\x0bjson_schema\x18\x01 \x01(\x0b\x32\x35.grpc.gateway.protoc_gen_openapiv2.options.JSONSchemaR\njsonSchema\x12$\n\rdiscriminator\x18\x02 \x01(\tR\rdiscriminator\x12\x1b\n\tread_only\x18\x03 \x01(\x08R\x08readOnly\x12\x65\n\rexternal_docs\x18\x05 \x01(\x0b\x32@.grpc.gateway.protoc_gen_openapiv2.options.ExternalDocumentationR\x0c\x65xternalDocs\x12\x18\n\x07\x65xample\x18\x06 \x01(\tR\x07\x65xampleJ\x04\x08\x04\x10\x05\"\xe8\x03\n\nEnumSchema\x12 \n\x0b\x64\x65scription\x18\x01 \x01(\tR\x0b\x64\x65scription\x12\x18\n\x07\x64\x65\x66\x61ult\x18\x02 \x01(\tR\x07\x64\x65\x66\x61ult\x12\x14\n\x05title\x18\x03 \x01(\tR\x05title\x12\x1a\n\x08required\x18\x04 \x01(\x08R\x08required\x12\x1b\n\tread_only\x18\x05 \x01(\x08R\x08readOnly\x12\x65\n\rexternal_docs\x18\x06 \x01(\x0b\x32@.grpc.gateway.protoc_gen_openapiv2.options.ExternalDocumentationR\x0c\x65xternalDocs\x12\x18\n\x07\x65xample\x18\x07 \x01(\tR\x07\x65xample\x12\x10\n\x03ref\x18\x08 \x01(\tR\x03ref\x12\x65\n\nextensions\x18\t \x03(\x0b\x32\x45.grpc.gateway.protoc_gen_openapiv2.options.EnumSchema.ExtensionsEntryR\nextensions\x1aU\n\x0f\x45xtensionsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x05value:\x02\x38\x01\"\xf7\n\n\nJSONSchema\x12\x10\n\x03ref\x18\x03 \x01(\tR\x03ref\x12\x14\n\x05title\x18\x05 \x01(\tR\x05title\x12 \n\x0b\x64\x65scription\x18\x06 \x01(\tR\x0b\x64\x65scription\x12\x18\n\x07\x64\x65\x66\x61ult\x18\x07 \x01(\tR\x07\x64\x65\x66\x61ult\x12\x1b\n\tread_only\x18\x08 \x01(\x08R\x08readOnly\x12\x18\n\x07\x65xample\x18\t \x01(\tR\x07\x65xample\x12\x1f\n\x0bmultiple_of\x18\n \x01(\x01R\nmultipleOf\x12\x18\n\x07maximum\x18\x0b \x01(\x01R\x07maximum\x12+\n\x11\x65xclusive_maximum\x18\x0c \x01(\x08R\x10\x65xclusiveMaximum\x12\x18\n\x07minimum\x18\r \x01(\x01R\x07minimum\x12+\n\x11\x65xclusive_minimum\x18\x0e \x01(\x08R\x10\x65xclusiveMinimum\x12\x1d\n\nmax_length\x18\x0f \x01(\x04R\tmaxLength\x12\x1d\n\nmin_length\x18\x10 \x01(\x04R\tminLength\x12\x18\n\x07pattern\x18\x11 \x01(\tR\x07pattern\x12\x1b\n\tmax_items\x18\x14 \x01(\x04R\x08maxItems\x12\x1b\n\tmin_items\x18\x15 \x01(\x04R\x08minItems\x12!\n\x0cunique_items\x18\x16 \x01(\x08R\x0buniqueItems\x12%\n\x0emax_properties\x18\x18 \x01(\x04R\rmaxProperties\x12%\n\x0emin_properties\x18\x19 \x01(\x04R\rminProperties\x12\x1a\n\x08required\x18\x1a \x03(\tR\x08required\x12\x14\n\x05\x61rray\x18\" \x03(\tR\x05\x61rray\x12_\n\x04type\x18# \x03(\x0e\x32K.grpc.gateway.protoc_gen_openapiv2.options.JSONSchema.JSONSchemaSimpleTypesR\x04type\x12\x16\n\x06\x66ormat\x18$ \x01(\tR\x06\x66ormat\x12\x12\n\x04\x65num\x18. \x03(\tR\x04\x65num\x12z\n\x13\x66ield_configuration\x18\xe9\x07 \x01(\x0b\x32H.grpc.gateway.protoc_gen_openapiv2.options.JSONSchema.FieldConfigurationR\x12\x66ieldConfiguration\x12\x65\n\nextensions\x18\x30 \x03(\x0b\x32\x45.grpc.gateway.protoc_gen_openapiv2.options.JSONSchema.ExtensionsEntryR\nextensions\x1a\\\n\x12\x46ieldConfiguration\x12&\n\x0fpath_param_name\x18/ \x01(\tR\rpathParamName\x12\x1e\n\ndeprecated\x18\x31 \x01(\x08R\ndeprecated\x1aU\n\x0f\x45xtensionsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x05value:\x02\x38\x01\"w\n\x15JSONSchemaSimpleTypes\x12\x0b\n\x07UNKNOWN\x10\x00\x12\t\n\x05\x41RRAY\x10\x01\x12\x0b\n\x07\x42OOLEAN\x10\x02\x12\x0b\n\x07INTEGER\x10\x03\x12\x08\n\x04NULL\x10\x04\x12\n\n\x06NUMBER\x10\x05\x12\n\n\x06OBJECT\x10\x06\x12\n\n\x06STRING\x10\x07J\x04\x08\x01\x10\x02J\x04\x08\x02\x10\x03J\x04\x08\x04\x10\x05J\x04\x08\x12\x10\x13J\x04\x08\x13\x10\x14J\x04\x08\x17\x10\x18J\x04\x08\x1b\x10\x1cJ\x04\x08\x1c\x10\x1dJ\x04\x08\x1d\x10\x1eJ\x04\x08\x1e\x10\"J\x04\x08%\x10*J\x04\x08*\x10+J\x04\x08+\x10.\"\xd9\x02\n\x03Tag\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12 \n\x0b\x64\x65scription\x18\x02 \x01(\tR\x0b\x64\x65scription\x12\x65\n\rexternal_docs\x18\x03 \x01(\x0b\x32@.grpc.gateway.protoc_gen_openapiv2.options.ExternalDocumentationR\x0c\x65xternalDocs\x12^\n\nextensions\x18\x04 \x03(\x0b\x32>.grpc.gateway.protoc_gen_openapiv2.options.Tag.ExtensionsEntryR\nextensions\x1aU\n\x0f\x45xtensionsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x05value:\x02\x38\x01\"\xf7\x01\n\x13SecurityDefinitions\x12h\n\x08security\x18\x01 \x03(\x0b\x32L.grpc.gateway.protoc_gen_openapiv2.options.SecurityDefinitions.SecurityEntryR\x08security\x1av\n\rSecurityEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12O\n\x05value\x18\x02 \x01(\x0b\x32\x39.grpc.gateway.protoc_gen_openapiv2.options.SecuritySchemeR\x05value:\x02\x38\x01\"\xff\x06\n\x0eSecurityScheme\x12R\n\x04type\x18\x01 \x01(\x0e\x32>.grpc.gateway.protoc_gen_openapiv2.options.SecurityScheme.TypeR\x04type\x12 \n\x0b\x64\x65scription\x18\x02 \x01(\tR\x0b\x64\x65scription\x12\x12\n\x04name\x18\x03 \x01(\tR\x04name\x12L\n\x02in\x18\x04 \x01(\x0e\x32<.grpc.gateway.protoc_gen_openapiv2.options.SecurityScheme.InR\x02in\x12R\n\x04\x66low\x18\x05 \x01(\x0e\x32>.grpc.gateway.protoc_gen_openapiv2.options.SecurityScheme.FlowR\x04\x66low\x12+\n\x11\x61uthorization_url\x18\x06 \x01(\tR\x10\x61uthorizationUrl\x12\x1b\n\ttoken_url\x18\x07 \x01(\tR\x08tokenUrl\x12I\n\x06scopes\x18\x08 \x01(\x0b\x32\x31.grpc.gateway.protoc_gen_openapiv2.options.ScopesR\x06scopes\x12i\n\nextensions\x18\t \x03(\x0b\x32I.grpc.gateway.protoc_gen_openapiv2.options.SecurityScheme.ExtensionsEntryR\nextensions\x1aU\n\x0f\x45xtensionsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x05value:\x02\x38\x01\"K\n\x04Type\x12\x10\n\x0cTYPE_INVALID\x10\x00\x12\x0e\n\nTYPE_BASIC\x10\x01\x12\x10\n\x0cTYPE_API_KEY\x10\x02\x12\x0f\n\x0bTYPE_OAUTH2\x10\x03\"1\n\x02In\x12\x0e\n\nIN_INVALID\x10\x00\x12\x0c\n\x08IN_QUERY\x10\x01\x12\r\n\tIN_HEADER\x10\x02\"j\n\x04\x46low\x12\x10\n\x0c\x46LOW_INVALID\x10\x00\x12\x11\n\rFLOW_IMPLICIT\x10\x01\x12\x11\n\rFLOW_PASSWORD\x10\x02\x12\x14\n\x10\x46LOW_APPLICATION\x10\x03\x12\x14\n\x10\x46LOW_ACCESS_CODE\x10\x04\"\xf6\x02\n\x13SecurityRequirement\x12\x8a\x01\n\x14security_requirement\x18\x01 \x03(\x0b\x32W.grpc.gateway.protoc_gen_openapiv2.options.SecurityRequirement.SecurityRequirementEntryR\x13securityRequirement\x1a\x30\n\x18SecurityRequirementValue\x12\x14\n\x05scope\x18\x01 \x03(\tR\x05scope\x1a\x9f\x01\n\x18SecurityRequirementEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12m\n\x05value\x18\x02 \x01(\x0b\x32W.grpc.gateway.protoc_gen_openapiv2.options.SecurityRequirement.SecurityRequirementValueR\x05value:\x02\x38\x01\"\x96\x01\n\x06Scopes\x12R\n\x05scope\x18\x01 \x03(\x0b\x32<.grpc.gateway.protoc_gen_openapiv2.options.Scopes.ScopeEntryR\x05scope\x1a\x38\n\nScopeEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01*;\n\x06Scheme\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x08\n\x04HTTP\x10\x01\x12\t\n\x05HTTPS\x10\x02\x12\x06\n\x02WS\x10\x03\x12\x07\n\x03WSS\x10\x04\x42HZFgithub.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/optionsb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'protoc_gen_openapiv2.options.openapiv2_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZFgithub.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options' + _globals['_SWAGGER_RESPONSESENTRY']._loaded_options = None + _globals['_SWAGGER_RESPONSESENTRY']._serialized_options = b'8\001' + _globals['_SWAGGER_EXTENSIONSENTRY']._loaded_options = None + _globals['_SWAGGER_EXTENSIONSENTRY']._serialized_options = b'8\001' + _globals['_OPERATION_RESPONSESENTRY']._loaded_options = None + _globals['_OPERATION_RESPONSESENTRY']._serialized_options = b'8\001' + _globals['_OPERATION_EXTENSIONSENTRY']._loaded_options = None + _globals['_OPERATION_EXTENSIONSENTRY']._serialized_options = b'8\001' + _globals['_RESPONSE_HEADERSENTRY']._loaded_options = None + _globals['_RESPONSE_HEADERSENTRY']._serialized_options = b'8\001' + _globals['_RESPONSE_EXAMPLESENTRY']._loaded_options = None + _globals['_RESPONSE_EXAMPLESENTRY']._serialized_options = b'8\001' + _globals['_RESPONSE_EXTENSIONSENTRY']._loaded_options = None + _globals['_RESPONSE_EXTENSIONSENTRY']._serialized_options = b'8\001' + _globals['_INFO_EXTENSIONSENTRY']._loaded_options = None + _globals['_INFO_EXTENSIONSENTRY']._serialized_options = b'8\001' + _globals['_ENUMSCHEMA_EXTENSIONSENTRY']._loaded_options = None + _globals['_ENUMSCHEMA_EXTENSIONSENTRY']._serialized_options = b'8\001' + _globals['_JSONSCHEMA_EXTENSIONSENTRY']._loaded_options = None + _globals['_JSONSCHEMA_EXTENSIONSENTRY']._serialized_options = b'8\001' + _globals['_TAG_EXTENSIONSENTRY']._loaded_options = None + _globals['_TAG_EXTENSIONSENTRY']._serialized_options = b'8\001' + _globals['_SECURITYDEFINITIONS_SECURITYENTRY']._loaded_options = None + _globals['_SECURITYDEFINITIONS_SECURITYENTRY']._serialized_options = b'8\001' + _globals['_SECURITYSCHEME_EXTENSIONSENTRY']._loaded_options = None + _globals['_SECURITYSCHEME_EXTENSIONSENTRY']._serialized_options = b'8\001' + _globals['_SECURITYREQUIREMENT_SECURITYREQUIREMENTENTRY']._loaded_options = None + _globals['_SECURITYREQUIREMENT_SECURITYREQUIREMENTENTRY']._serialized_options = b'8\001' + _globals['_SCOPES_SCOPEENTRY']._loaded_options = None + _globals['_SCOPES_SCOPEENTRY']._serialized_options = b'8\001' + _globals['_SCHEME']._serialized_start=8356 + _globals['_SCHEME']._serialized_end=8415 + _globals['_SWAGGER']._serialized_start=122 + _globals['_SWAGGER']._serialized_end=1197 + _globals['_SWAGGER_RESPONSESENTRY']._serialized_start=985 + _globals['_SWAGGER_RESPONSESENTRY']._serialized_end=1098 + _globals['_SWAGGER_EXTENSIONSENTRY']._serialized_start=1100 + _globals['_SWAGGER_EXTENSIONSENTRY']._serialized_end=1185 + _globals['_OPERATION']._serialized_start=1200 + _globals['_OPERATION']._serialized_end=2182 + _globals['_OPERATION_RESPONSESENTRY']._serialized_start=985 + _globals['_OPERATION_RESPONSESENTRY']._serialized_end=1098 + _globals['_OPERATION_EXTENSIONSENTRY']._serialized_start=1100 + _globals['_OPERATION_EXTENSIONSENTRY']._serialized_end=1185 + _globals['_PARAMETERS']._serialized_start=2184 + _globals['_PARAMETERS']._serialized_end=2282 + _globals['_HEADERPARAMETER']._serialized_start=2285 + _globals['_HEADERPARAMETER']._serialized_end=2576 + _globals['_HEADERPARAMETER_TYPE']._serialized_start=2495 + _globals['_HEADERPARAMETER_TYPE']._serialized_end=2564 + _globals['_HEADER']._serialized_start=2579 + _globals['_HEADER']._serialized_end=2795 + _globals['_RESPONSE']._serialized_start=2798 + _globals['_RESPONSE']._serialized_end=3464 + _globals['_RESPONSE_HEADERSENTRY']._serialized_start=3207 + _globals['_RESPONSE_HEADERSENTRY']._serialized_end=3316 + _globals['_RESPONSE_EXAMPLESENTRY']._serialized_start=3318 + _globals['_RESPONSE_EXAMPLESENTRY']._serialized_end=3377 + _globals['_RESPONSE_EXTENSIONSENTRY']._serialized_start=1100 + _globals['_RESPONSE_EXTENSIONSENTRY']._serialized_end=1185 + _globals['_INFO']._serialized_start=3467 + _globals['_INFO']._serialized_end=3937 + _globals['_INFO_EXTENSIONSENTRY']._serialized_start=1100 + _globals['_INFO_EXTENSIONSENTRY']._serialized_end=1185 + _globals['_CONTACT']._serialized_start=3939 + _globals['_CONTACT']._serialized_end=4008 + _globals['_LICENSE']._serialized_start=4010 + _globals['_LICENSE']._serialized_end=4057 + _globals['_EXTERNALDOCUMENTATION']._serialized_start=4059 + _globals['_EXTERNALDOCUMENTATION']._serialized_end=4134 + _globals['_SCHEMA']._serialized_start=4137 + _globals['_SCHEMA']._serialized_end=4435 + _globals['_ENUMSCHEMA']._serialized_start=4438 + _globals['_ENUMSCHEMA']._serialized_end=4926 + _globals['_ENUMSCHEMA_EXTENSIONSENTRY']._serialized_start=1100 + _globals['_ENUMSCHEMA_EXTENSIONSENTRY']._serialized_end=1185 + _globals['_JSONSCHEMA']._serialized_start=4929 + _globals['_JSONSCHEMA']._serialized_end=6328 + _globals['_JSONSCHEMA_FIELDCONFIGURATION']._serialized_start=5950 + _globals['_JSONSCHEMA_FIELDCONFIGURATION']._serialized_end=6042 + _globals['_JSONSCHEMA_EXTENSIONSENTRY']._serialized_start=1100 + _globals['_JSONSCHEMA_EXTENSIONSENTRY']._serialized_end=1185 + _globals['_JSONSCHEMA_JSONSCHEMASIMPLETYPES']._serialized_start=6131 + _globals['_JSONSCHEMA_JSONSCHEMASIMPLETYPES']._serialized_end=6250 + _globals['_TAG']._serialized_start=6331 + _globals['_TAG']._serialized_end=6676 + _globals['_TAG_EXTENSIONSENTRY']._serialized_start=1100 + _globals['_TAG_EXTENSIONSENTRY']._serialized_end=1185 + _globals['_SECURITYDEFINITIONS']._serialized_start=6679 + _globals['_SECURITYDEFINITIONS']._serialized_end=6926 + _globals['_SECURITYDEFINITIONS_SECURITYENTRY']._serialized_start=6808 + _globals['_SECURITYDEFINITIONS_SECURITYENTRY']._serialized_end=6926 + _globals['_SECURITYSCHEME']._serialized_start=6929 + _globals['_SECURITYSCHEME']._serialized_end=7824 + _globals['_SECURITYSCHEME_EXTENSIONSENTRY']._serialized_start=1100 + _globals['_SECURITYSCHEME_EXTENSIONSENTRY']._serialized_end=1185 + _globals['_SECURITYSCHEME_TYPE']._serialized_start=7590 + _globals['_SECURITYSCHEME_TYPE']._serialized_end=7665 + _globals['_SECURITYSCHEME_IN']._serialized_start=7667 + _globals['_SECURITYSCHEME_IN']._serialized_end=7716 + _globals['_SECURITYSCHEME_FLOW']._serialized_start=7718 + _globals['_SECURITYSCHEME_FLOW']._serialized_end=7824 + _globals['_SECURITYREQUIREMENT']._serialized_start=7827 + _globals['_SECURITYREQUIREMENT']._serialized_end=8201 + _globals['_SECURITYREQUIREMENT_SECURITYREQUIREMENTVALUE']._serialized_start=7991 + _globals['_SECURITYREQUIREMENT_SECURITYREQUIREMENTVALUE']._serialized_end=8039 + _globals['_SECURITYREQUIREMENT_SECURITYREQUIREMENTENTRY']._serialized_start=8042 + _globals['_SECURITYREQUIREMENT_SECURITYREQUIREMENTENTRY']._serialized_end=8201 + _globals['_SCOPES']._serialized_start=8204 + _globals['_SCOPES']._serialized_end=8354 + _globals['_SCOPES_SCOPEENTRY']._serialized_start=8298 + _globals['_SCOPES_SCOPEENTRY']._serialized_end=8354 +# @@protoc_insertion_point(module_scope) diff --git a/src/conduit/_grpc/protoc_gen_openapiv2/options/openapiv2_pb2.pyi b/src/conduit/_grpc/protoc_gen_openapiv2/options/openapiv2_pb2.pyi new file mode 100644 index 0000000..38b013e --- /dev/null +++ b/src/conduit/_grpc/protoc_gen_openapiv2/options/openapiv2_pb2.pyi @@ -0,0 +1,493 @@ +from google.protobuf import struct_pb2 as _struct_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class Scheme(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + UNKNOWN: _ClassVar[Scheme] + HTTP: _ClassVar[Scheme] + HTTPS: _ClassVar[Scheme] + WS: _ClassVar[Scheme] + WSS: _ClassVar[Scheme] +UNKNOWN: Scheme +HTTP: Scheme +HTTPS: Scheme +WS: Scheme +WSS: Scheme + +class Swagger(_message.Message): + __slots__ = ("swagger", "info", "host", "base_path", "schemes", "consumes", "produces", "responses", "security_definitions", "security", "tags", "external_docs", "extensions") + class ResponsesEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: Response + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[Response, _Mapping]] = ...) -> None: ... + class ExtensionsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: _struct_pb2.Value + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_struct_pb2.Value, _Mapping]] = ...) -> None: ... + SWAGGER_FIELD_NUMBER: _ClassVar[int] + INFO_FIELD_NUMBER: _ClassVar[int] + HOST_FIELD_NUMBER: _ClassVar[int] + BASE_PATH_FIELD_NUMBER: _ClassVar[int] + SCHEMES_FIELD_NUMBER: _ClassVar[int] + CONSUMES_FIELD_NUMBER: _ClassVar[int] + PRODUCES_FIELD_NUMBER: _ClassVar[int] + RESPONSES_FIELD_NUMBER: _ClassVar[int] + SECURITY_DEFINITIONS_FIELD_NUMBER: _ClassVar[int] + SECURITY_FIELD_NUMBER: _ClassVar[int] + TAGS_FIELD_NUMBER: _ClassVar[int] + EXTERNAL_DOCS_FIELD_NUMBER: _ClassVar[int] + EXTENSIONS_FIELD_NUMBER: _ClassVar[int] + swagger: str + info: Info + host: str + base_path: str + schemes: _containers.RepeatedScalarFieldContainer[Scheme] + consumes: _containers.RepeatedScalarFieldContainer[str] + produces: _containers.RepeatedScalarFieldContainer[str] + responses: _containers.MessageMap[str, Response] + security_definitions: SecurityDefinitions + security: _containers.RepeatedCompositeFieldContainer[SecurityRequirement] + tags: _containers.RepeatedCompositeFieldContainer[Tag] + external_docs: ExternalDocumentation + extensions: _containers.MessageMap[str, _struct_pb2.Value] + def __init__(self, swagger: _Optional[str] = ..., info: _Optional[_Union[Info, _Mapping]] = ..., host: _Optional[str] = ..., base_path: _Optional[str] = ..., schemes: _Optional[_Iterable[_Union[Scheme, str]]] = ..., consumes: _Optional[_Iterable[str]] = ..., produces: _Optional[_Iterable[str]] = ..., responses: _Optional[_Mapping[str, Response]] = ..., security_definitions: _Optional[_Union[SecurityDefinitions, _Mapping]] = ..., security: _Optional[_Iterable[_Union[SecurityRequirement, _Mapping]]] = ..., tags: _Optional[_Iterable[_Union[Tag, _Mapping]]] = ..., external_docs: _Optional[_Union[ExternalDocumentation, _Mapping]] = ..., extensions: _Optional[_Mapping[str, _struct_pb2.Value]] = ...) -> None: ... + +class Operation(_message.Message): + __slots__ = ("tags", "summary", "description", "external_docs", "operation_id", "consumes", "produces", "responses", "schemes", "deprecated", "security", "extensions", "parameters") + class ResponsesEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: Response + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[Response, _Mapping]] = ...) -> None: ... + class ExtensionsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: _struct_pb2.Value + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_struct_pb2.Value, _Mapping]] = ...) -> None: ... + TAGS_FIELD_NUMBER: _ClassVar[int] + SUMMARY_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + EXTERNAL_DOCS_FIELD_NUMBER: _ClassVar[int] + OPERATION_ID_FIELD_NUMBER: _ClassVar[int] + CONSUMES_FIELD_NUMBER: _ClassVar[int] + PRODUCES_FIELD_NUMBER: _ClassVar[int] + RESPONSES_FIELD_NUMBER: _ClassVar[int] + SCHEMES_FIELD_NUMBER: _ClassVar[int] + DEPRECATED_FIELD_NUMBER: _ClassVar[int] + SECURITY_FIELD_NUMBER: _ClassVar[int] + EXTENSIONS_FIELD_NUMBER: _ClassVar[int] + PARAMETERS_FIELD_NUMBER: _ClassVar[int] + tags: _containers.RepeatedScalarFieldContainer[str] + summary: str + description: str + external_docs: ExternalDocumentation + operation_id: str + consumes: _containers.RepeatedScalarFieldContainer[str] + produces: _containers.RepeatedScalarFieldContainer[str] + responses: _containers.MessageMap[str, Response] + schemes: _containers.RepeatedScalarFieldContainer[Scheme] + deprecated: bool + security: _containers.RepeatedCompositeFieldContainer[SecurityRequirement] + extensions: _containers.MessageMap[str, _struct_pb2.Value] + parameters: Parameters + def __init__(self, tags: _Optional[_Iterable[str]] = ..., summary: _Optional[str] = ..., description: _Optional[str] = ..., external_docs: _Optional[_Union[ExternalDocumentation, _Mapping]] = ..., operation_id: _Optional[str] = ..., consumes: _Optional[_Iterable[str]] = ..., produces: _Optional[_Iterable[str]] = ..., responses: _Optional[_Mapping[str, Response]] = ..., schemes: _Optional[_Iterable[_Union[Scheme, str]]] = ..., deprecated: bool = ..., security: _Optional[_Iterable[_Union[SecurityRequirement, _Mapping]]] = ..., extensions: _Optional[_Mapping[str, _struct_pb2.Value]] = ..., parameters: _Optional[_Union[Parameters, _Mapping]] = ...) -> None: ... + +class Parameters(_message.Message): + __slots__ = ("headers",) + HEADERS_FIELD_NUMBER: _ClassVar[int] + headers: _containers.RepeatedCompositeFieldContainer[HeaderParameter] + def __init__(self, headers: _Optional[_Iterable[_Union[HeaderParameter, _Mapping]]] = ...) -> None: ... + +class HeaderParameter(_message.Message): + __slots__ = ("name", "description", "type", "format", "required") + class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + UNKNOWN: _ClassVar[HeaderParameter.Type] + STRING: _ClassVar[HeaderParameter.Type] + NUMBER: _ClassVar[HeaderParameter.Type] + INTEGER: _ClassVar[HeaderParameter.Type] + BOOLEAN: _ClassVar[HeaderParameter.Type] + UNKNOWN: HeaderParameter.Type + STRING: HeaderParameter.Type + NUMBER: HeaderParameter.Type + INTEGER: HeaderParameter.Type + BOOLEAN: HeaderParameter.Type + NAME_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + FORMAT_FIELD_NUMBER: _ClassVar[int] + REQUIRED_FIELD_NUMBER: _ClassVar[int] + name: str + description: str + type: HeaderParameter.Type + format: str + required: bool + def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., type: _Optional[_Union[HeaderParameter.Type, str]] = ..., format: _Optional[str] = ..., required: bool = ...) -> None: ... + +class Header(_message.Message): + __slots__ = ("description", "type", "format", "default", "pattern") + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + FORMAT_FIELD_NUMBER: _ClassVar[int] + DEFAULT_FIELD_NUMBER: _ClassVar[int] + PATTERN_FIELD_NUMBER: _ClassVar[int] + description: str + type: str + format: str + default: str + pattern: str + def __init__(self, description: _Optional[str] = ..., type: _Optional[str] = ..., format: _Optional[str] = ..., default: _Optional[str] = ..., pattern: _Optional[str] = ...) -> None: ... + +class Response(_message.Message): + __slots__ = ("description", "schema", "headers", "examples", "extensions") + class HeadersEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: Header + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[Header, _Mapping]] = ...) -> None: ... + class ExamplesEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + class ExtensionsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: _struct_pb2.Value + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_struct_pb2.Value, _Mapping]] = ...) -> None: ... + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + SCHEMA_FIELD_NUMBER: _ClassVar[int] + HEADERS_FIELD_NUMBER: _ClassVar[int] + EXAMPLES_FIELD_NUMBER: _ClassVar[int] + EXTENSIONS_FIELD_NUMBER: _ClassVar[int] + description: str + schema: Schema + headers: _containers.MessageMap[str, Header] + examples: _containers.ScalarMap[str, str] + extensions: _containers.MessageMap[str, _struct_pb2.Value] + def __init__(self, description: _Optional[str] = ..., schema: _Optional[_Union[Schema, _Mapping]] = ..., headers: _Optional[_Mapping[str, Header]] = ..., examples: _Optional[_Mapping[str, str]] = ..., extensions: _Optional[_Mapping[str, _struct_pb2.Value]] = ...) -> None: ... + +class Info(_message.Message): + __slots__ = ("title", "description", "terms_of_service", "contact", "license", "version", "extensions") + class ExtensionsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: _struct_pb2.Value + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_struct_pb2.Value, _Mapping]] = ...) -> None: ... + TITLE_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + TERMS_OF_SERVICE_FIELD_NUMBER: _ClassVar[int] + CONTACT_FIELD_NUMBER: _ClassVar[int] + LICENSE_FIELD_NUMBER: _ClassVar[int] + VERSION_FIELD_NUMBER: _ClassVar[int] + EXTENSIONS_FIELD_NUMBER: _ClassVar[int] + title: str + description: str + terms_of_service: str + contact: Contact + license: License + version: str + extensions: _containers.MessageMap[str, _struct_pb2.Value] + def __init__(self, title: _Optional[str] = ..., description: _Optional[str] = ..., terms_of_service: _Optional[str] = ..., contact: _Optional[_Union[Contact, _Mapping]] = ..., license: _Optional[_Union[License, _Mapping]] = ..., version: _Optional[str] = ..., extensions: _Optional[_Mapping[str, _struct_pb2.Value]] = ...) -> None: ... + +class Contact(_message.Message): + __slots__ = ("name", "url", "email") + NAME_FIELD_NUMBER: _ClassVar[int] + URL_FIELD_NUMBER: _ClassVar[int] + EMAIL_FIELD_NUMBER: _ClassVar[int] + name: str + url: str + email: str + def __init__(self, name: _Optional[str] = ..., url: _Optional[str] = ..., email: _Optional[str] = ...) -> None: ... + +class License(_message.Message): + __slots__ = ("name", "url") + NAME_FIELD_NUMBER: _ClassVar[int] + URL_FIELD_NUMBER: _ClassVar[int] + name: str + url: str + def __init__(self, name: _Optional[str] = ..., url: _Optional[str] = ...) -> None: ... + +class ExternalDocumentation(_message.Message): + __slots__ = ("description", "url") + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + URL_FIELD_NUMBER: _ClassVar[int] + description: str + url: str + def __init__(self, description: _Optional[str] = ..., url: _Optional[str] = ...) -> None: ... + +class Schema(_message.Message): + __slots__ = ("json_schema", "discriminator", "read_only", "external_docs", "example") + JSON_SCHEMA_FIELD_NUMBER: _ClassVar[int] + DISCRIMINATOR_FIELD_NUMBER: _ClassVar[int] + READ_ONLY_FIELD_NUMBER: _ClassVar[int] + EXTERNAL_DOCS_FIELD_NUMBER: _ClassVar[int] + EXAMPLE_FIELD_NUMBER: _ClassVar[int] + json_schema: JSONSchema + discriminator: str + read_only: bool + external_docs: ExternalDocumentation + example: str + def __init__(self, json_schema: _Optional[_Union[JSONSchema, _Mapping]] = ..., discriminator: _Optional[str] = ..., read_only: bool = ..., external_docs: _Optional[_Union[ExternalDocumentation, _Mapping]] = ..., example: _Optional[str] = ...) -> None: ... + +class EnumSchema(_message.Message): + __slots__ = ("description", "default", "title", "required", "read_only", "external_docs", "example", "ref", "extensions") + class ExtensionsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: _struct_pb2.Value + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_struct_pb2.Value, _Mapping]] = ...) -> None: ... + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + DEFAULT_FIELD_NUMBER: _ClassVar[int] + TITLE_FIELD_NUMBER: _ClassVar[int] + REQUIRED_FIELD_NUMBER: _ClassVar[int] + READ_ONLY_FIELD_NUMBER: _ClassVar[int] + EXTERNAL_DOCS_FIELD_NUMBER: _ClassVar[int] + EXAMPLE_FIELD_NUMBER: _ClassVar[int] + REF_FIELD_NUMBER: _ClassVar[int] + EXTENSIONS_FIELD_NUMBER: _ClassVar[int] + description: str + default: str + title: str + required: bool + read_only: bool + external_docs: ExternalDocumentation + example: str + ref: str + extensions: _containers.MessageMap[str, _struct_pb2.Value] + def __init__(self, description: _Optional[str] = ..., default: _Optional[str] = ..., title: _Optional[str] = ..., required: bool = ..., read_only: bool = ..., external_docs: _Optional[_Union[ExternalDocumentation, _Mapping]] = ..., example: _Optional[str] = ..., ref: _Optional[str] = ..., extensions: _Optional[_Mapping[str, _struct_pb2.Value]] = ...) -> None: ... + +class JSONSchema(_message.Message): + __slots__ = ("ref", "title", "description", "default", "read_only", "example", "multiple_of", "maximum", "exclusive_maximum", "minimum", "exclusive_minimum", "max_length", "min_length", "pattern", "max_items", "min_items", "unique_items", "max_properties", "min_properties", "required", "array", "type", "format", "enum", "field_configuration", "extensions") + class JSONSchemaSimpleTypes(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + UNKNOWN: _ClassVar[JSONSchema.JSONSchemaSimpleTypes] + ARRAY: _ClassVar[JSONSchema.JSONSchemaSimpleTypes] + BOOLEAN: _ClassVar[JSONSchema.JSONSchemaSimpleTypes] + INTEGER: _ClassVar[JSONSchema.JSONSchemaSimpleTypes] + NULL: _ClassVar[JSONSchema.JSONSchemaSimpleTypes] + NUMBER: _ClassVar[JSONSchema.JSONSchemaSimpleTypes] + OBJECT: _ClassVar[JSONSchema.JSONSchemaSimpleTypes] + STRING: _ClassVar[JSONSchema.JSONSchemaSimpleTypes] + UNKNOWN: JSONSchema.JSONSchemaSimpleTypes + ARRAY: JSONSchema.JSONSchemaSimpleTypes + BOOLEAN: JSONSchema.JSONSchemaSimpleTypes + INTEGER: JSONSchema.JSONSchemaSimpleTypes + NULL: JSONSchema.JSONSchemaSimpleTypes + NUMBER: JSONSchema.JSONSchemaSimpleTypes + OBJECT: JSONSchema.JSONSchemaSimpleTypes + STRING: JSONSchema.JSONSchemaSimpleTypes + class FieldConfiguration(_message.Message): + __slots__ = ("path_param_name", "deprecated") + PATH_PARAM_NAME_FIELD_NUMBER: _ClassVar[int] + DEPRECATED_FIELD_NUMBER: _ClassVar[int] + path_param_name: str + deprecated: bool + def __init__(self, path_param_name: _Optional[str] = ..., deprecated: bool = ...) -> None: ... + class ExtensionsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: _struct_pb2.Value + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_struct_pb2.Value, _Mapping]] = ...) -> None: ... + REF_FIELD_NUMBER: _ClassVar[int] + TITLE_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + DEFAULT_FIELD_NUMBER: _ClassVar[int] + READ_ONLY_FIELD_NUMBER: _ClassVar[int] + EXAMPLE_FIELD_NUMBER: _ClassVar[int] + MULTIPLE_OF_FIELD_NUMBER: _ClassVar[int] + MAXIMUM_FIELD_NUMBER: _ClassVar[int] + EXCLUSIVE_MAXIMUM_FIELD_NUMBER: _ClassVar[int] + MINIMUM_FIELD_NUMBER: _ClassVar[int] + EXCLUSIVE_MINIMUM_FIELD_NUMBER: _ClassVar[int] + MAX_LENGTH_FIELD_NUMBER: _ClassVar[int] + MIN_LENGTH_FIELD_NUMBER: _ClassVar[int] + PATTERN_FIELD_NUMBER: _ClassVar[int] + MAX_ITEMS_FIELD_NUMBER: _ClassVar[int] + MIN_ITEMS_FIELD_NUMBER: _ClassVar[int] + UNIQUE_ITEMS_FIELD_NUMBER: _ClassVar[int] + MAX_PROPERTIES_FIELD_NUMBER: _ClassVar[int] + MIN_PROPERTIES_FIELD_NUMBER: _ClassVar[int] + REQUIRED_FIELD_NUMBER: _ClassVar[int] + ARRAY_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + FORMAT_FIELD_NUMBER: _ClassVar[int] + ENUM_FIELD_NUMBER: _ClassVar[int] + FIELD_CONFIGURATION_FIELD_NUMBER: _ClassVar[int] + EXTENSIONS_FIELD_NUMBER: _ClassVar[int] + ref: str + title: str + description: str + default: str + read_only: bool + example: str + multiple_of: float + maximum: float + exclusive_maximum: bool + minimum: float + exclusive_minimum: bool + max_length: int + min_length: int + pattern: str + max_items: int + min_items: int + unique_items: bool + max_properties: int + min_properties: int + required: _containers.RepeatedScalarFieldContainer[str] + array: _containers.RepeatedScalarFieldContainer[str] + type: _containers.RepeatedScalarFieldContainer[JSONSchema.JSONSchemaSimpleTypes] + format: str + enum: _containers.RepeatedScalarFieldContainer[str] + field_configuration: JSONSchema.FieldConfiguration + extensions: _containers.MessageMap[str, _struct_pb2.Value] + def __init__(self, ref: _Optional[str] = ..., title: _Optional[str] = ..., description: _Optional[str] = ..., default: _Optional[str] = ..., read_only: bool = ..., example: _Optional[str] = ..., multiple_of: _Optional[float] = ..., maximum: _Optional[float] = ..., exclusive_maximum: bool = ..., minimum: _Optional[float] = ..., exclusive_minimum: bool = ..., max_length: _Optional[int] = ..., min_length: _Optional[int] = ..., pattern: _Optional[str] = ..., max_items: _Optional[int] = ..., min_items: _Optional[int] = ..., unique_items: bool = ..., max_properties: _Optional[int] = ..., min_properties: _Optional[int] = ..., required: _Optional[_Iterable[str]] = ..., array: _Optional[_Iterable[str]] = ..., type: _Optional[_Iterable[_Union[JSONSchema.JSONSchemaSimpleTypes, str]]] = ..., format: _Optional[str] = ..., enum: _Optional[_Iterable[str]] = ..., field_configuration: _Optional[_Union[JSONSchema.FieldConfiguration, _Mapping]] = ..., extensions: _Optional[_Mapping[str, _struct_pb2.Value]] = ...) -> None: ... + +class Tag(_message.Message): + __slots__ = ("name", "description", "external_docs", "extensions") + class ExtensionsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: _struct_pb2.Value + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_struct_pb2.Value, _Mapping]] = ...) -> None: ... + NAME_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + EXTERNAL_DOCS_FIELD_NUMBER: _ClassVar[int] + EXTENSIONS_FIELD_NUMBER: _ClassVar[int] + name: str + description: str + external_docs: ExternalDocumentation + extensions: _containers.MessageMap[str, _struct_pb2.Value] + def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., external_docs: _Optional[_Union[ExternalDocumentation, _Mapping]] = ..., extensions: _Optional[_Mapping[str, _struct_pb2.Value]] = ...) -> None: ... + +class SecurityDefinitions(_message.Message): + __slots__ = ("security",) + class SecurityEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: SecurityScheme + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[SecurityScheme, _Mapping]] = ...) -> None: ... + SECURITY_FIELD_NUMBER: _ClassVar[int] + security: _containers.MessageMap[str, SecurityScheme] + def __init__(self, security: _Optional[_Mapping[str, SecurityScheme]] = ...) -> None: ... + +class SecurityScheme(_message.Message): + __slots__ = ("type", "description", "name", "flow", "authorization_url", "token_url", "scopes", "extensions") + class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + TYPE_INVALID: _ClassVar[SecurityScheme.Type] + TYPE_BASIC: _ClassVar[SecurityScheme.Type] + TYPE_API_KEY: _ClassVar[SecurityScheme.Type] + TYPE_OAUTH2: _ClassVar[SecurityScheme.Type] + TYPE_INVALID: SecurityScheme.Type + TYPE_BASIC: SecurityScheme.Type + TYPE_API_KEY: SecurityScheme.Type + TYPE_OAUTH2: SecurityScheme.Type + class In(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + IN_INVALID: _ClassVar[SecurityScheme.In] + IN_QUERY: _ClassVar[SecurityScheme.In] + IN_HEADER: _ClassVar[SecurityScheme.In] + IN_INVALID: SecurityScheme.In + IN_QUERY: SecurityScheme.In + IN_HEADER: SecurityScheme.In + class Flow(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + FLOW_INVALID: _ClassVar[SecurityScheme.Flow] + FLOW_IMPLICIT: _ClassVar[SecurityScheme.Flow] + FLOW_PASSWORD: _ClassVar[SecurityScheme.Flow] + FLOW_APPLICATION: _ClassVar[SecurityScheme.Flow] + FLOW_ACCESS_CODE: _ClassVar[SecurityScheme.Flow] + FLOW_INVALID: SecurityScheme.Flow + FLOW_IMPLICIT: SecurityScheme.Flow + FLOW_PASSWORD: SecurityScheme.Flow + FLOW_APPLICATION: SecurityScheme.Flow + FLOW_ACCESS_CODE: SecurityScheme.Flow + class ExtensionsEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: _struct_pb2.Value + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[_struct_pb2.Value, _Mapping]] = ...) -> None: ... + TYPE_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + IN_FIELD_NUMBER: _ClassVar[int] + FLOW_FIELD_NUMBER: _ClassVar[int] + AUTHORIZATION_URL_FIELD_NUMBER: _ClassVar[int] + TOKEN_URL_FIELD_NUMBER: _ClassVar[int] + SCOPES_FIELD_NUMBER: _ClassVar[int] + EXTENSIONS_FIELD_NUMBER: _ClassVar[int] + type: SecurityScheme.Type + description: str + name: str + flow: SecurityScheme.Flow + authorization_url: str + token_url: str + scopes: Scopes + extensions: _containers.MessageMap[str, _struct_pb2.Value] + def __init__(self, type: _Optional[_Union[SecurityScheme.Type, str]] = ..., description: _Optional[str] = ..., name: _Optional[str] = ..., flow: _Optional[_Union[SecurityScheme.Flow, str]] = ..., authorization_url: _Optional[str] = ..., token_url: _Optional[str] = ..., scopes: _Optional[_Union[Scopes, _Mapping]] = ..., extensions: _Optional[_Mapping[str, _struct_pb2.Value]] = ..., **kwargs) -> None: ... + +class SecurityRequirement(_message.Message): + __slots__ = ("security_requirement",) + class SecurityRequirementValue(_message.Message): + __slots__ = ("scope",) + SCOPE_FIELD_NUMBER: _ClassVar[int] + scope: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, scope: _Optional[_Iterable[str]] = ...) -> None: ... + class SecurityRequirementEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: SecurityRequirement.SecurityRequirementValue + def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[SecurityRequirement.SecurityRequirementValue, _Mapping]] = ...) -> None: ... + SECURITY_REQUIREMENT_FIELD_NUMBER: _ClassVar[int] + security_requirement: _containers.MessageMap[str, SecurityRequirement.SecurityRequirementValue] + def __init__(self, security_requirement: _Optional[_Mapping[str, SecurityRequirement.SecurityRequirementValue]] = ...) -> None: ... + +class Scopes(_message.Message): + __slots__ = ("scope",) + class ScopeEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + SCOPE_FIELD_NUMBER: _ClassVar[int] + scope: _containers.ScalarMap[str, str] + def __init__(self, scope: _Optional[_Mapping[str, str]] = ...) -> None: ... diff --git a/src/conduit/_grpc/protoc_gen_openapiv2/options/openapiv2_pb2_grpc.py b/src/conduit/_grpc/protoc_gen_openapiv2/options/openapiv2_pb2_grpc.py new file mode 100644 index 0000000..2daafff --- /dev/null +++ b/src/conduit/_grpc/protoc_gen_openapiv2/options/openapiv2_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/src/conduit/_local.py b/src/conduit/_local.py new file mode 100644 index 0000000..dfdd0f0 --- /dev/null +++ b/src/conduit/_local.py @@ -0,0 +1,297 @@ +"""``conduit.local()``: spawn and supervise a `conduit` subprocess. + +Mode 1 in the design doc's "Deployment modes -- framed honestly": engine +co-located with the host application, lifecycle tied to it. **Not a +production story** -- no independent lifecycle, no restart-without-the-host- +restarting, no fleet management. Fits dev, notebooks, one-off jobs, +single-host batch/ETL. See :func:`conduit.local`'s docstring (re-exported at +the package root) for the user-facing contract; this module is the +implementation. +""" + +from __future__ import annotations + +import atexit +import contextlib +import socket +import subprocess +import sys +import time +import warnings +from pathlib import Path +from types import TracebackType + +import grpc + +import conduit._grpc # noqa: F401 -- sets up sys.path, see conduit._grpc.__init__ +from api.v1 import api_pb2 +from conduit._provision import ensure_binary, resolve_version +from conduit.client import Client +from conduit.errors import ConduitError +from conduit.pipeline import Pipeline +from conduit.run import Run + +#: How long a graceful SIGTERM/terminate() gets before force-kill, per +#: Invariant 7 ("Shutdown is graceful by default... kill -9 at any instant +#: must be recoverable without loss"). This is the *client's* grace window for +#: its *own* supervised subprocess -- the engine's own drain behavior on +#: SIGTERM is unaffected by this constant. +_GRACEFUL_STOP_TIMEOUT = 10.0 + + +def _free_port() -> int: + """Reserve an OS-assigned loopback port, then release it immediately. + + Standard bind-then-release technique (used by e.g. pytest-xdist, + testcontainers) to avoid parsing Conduit's startup log for its bound + address. **Known, accepted race window**: another process could claim the + port between release and the subprocess's own bind. If that happens, the + readiness poll in :class:`LocalConduit._wait_ready` fails with a clear + "engine did not become reachable" error rather than hanging -- it does + not silently connect to the wrong process. + """ + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("127.0.0.1", 0)) + return s.getsockname()[1] # type: ignore[no-any-return] + + +class LocalConduit: + """Supervises one `conduit` subprocess and the :class:`Client` bound to it. + + Not constructed directly -- use :func:`conduit.local`. + """ + + def __init__( + self, + *, + state_dir: Path, + version: str | None, + binary: Path | None, + startup_timeout: float, + ) -> None: + """Provision (if needed), spawn, and wait for a `conduit` engine to become ready. + + Raises: + ConduitError: if provisioning fails (see + :func:`conduit._provision.ensure_binary`), the subprocess + exits before becoming ready (code + ``"client.engine_crashed"``, with captured output), or the + engine never becomes reachable within ``startup_timeout`` + (code ``"client.engine_unreachable"``). + """ + self.state_dir = state_dir + self.binary_path = Path(binary) if binary is not None else ensure_binary(version) + self._grpc_port = _free_port() + self._http_port = _free_port() + self._log_path = state_dir / "conduit.log" + + self._log_file = self._log_path.open("ab") + self._process = subprocess.Popen( + [ + str(self.binary_path), + "run", + "--api.enabled", + f"--api.grpc.address=127.0.0.1:{self._grpc_port}", + f"--api.http.address=127.0.0.1:{self._http_port}", + "--db.type=badger", + f"--db.badger.path={state_dir / 'conduit.db'}", + ], + stdout=self._log_file, + stderr=subprocess.STDOUT, + stdin=subprocess.DEVNULL, + ) + self._stopped = False + atexit.register(self._atexit_kill) + + try: + self.client = self._wait_ready(startup_timeout) + except BaseException: + self._terminate() + raise + + @property + def addr(self) -> str: + """The engine's ``host:port`` gRPC address.""" + return f"127.0.0.1:{self._grpc_port}" + + def _wait_ready(self, timeout: float) -> Client: + """Poll until the engine's gRPC API answers ``GetInfo``, or fail fast. + + Distinguishes, per the design doc's failure mode 1, "the process died" + (surfaces the captured log tail immediately -- never waits out the + rest of ``timeout``) from "not ready yet" (keeps polling). + """ + deadline = time.monotonic() + timeout + channel = grpc.insecure_channel(self.addr) + client = Client(channel, on_close=self._terminate) + last_error: Exception | None = None + + while time.monotonic() < deadline: + exit_code = self._process.poll() + if exit_code is not None: + channel.close() + raise ConduitError( + f"conduit subprocess exited (code {exit_code}) before its API " + f"became ready. Log tail:\n{self._log_tail()}", + code="client.engine_crashed", + ) + try: + client.get_info(timeout=1.0) + except ConduitError as e: + last_error = e + time.sleep(0.1) + continue + return client + + channel.close() + raise ConduitError( + f"conduit subprocess did not become reachable at {self.addr} within " + f"{timeout}s (last error: {last_error}). Log tail:\n{self._log_tail()}", + code="client.engine_unreachable", + ) + + def _log_tail(self, max_bytes: int = 4096) -> str: + """Best-effort tail of the subprocess's combined stdout/stderr log.""" + try: + with self._log_path.open("rb") as f: + f.seek(0, 2) + size = f.tell() + f.seek(max(0, size - max_bytes)) + return f.read().decode("utf-8", errors="replace") + except OSError: + return "" + + def _terminate(self) -> None: + """Gracefully stop the subprocess (Invariant 7), force-killing if it won't.""" + if self._stopped: + return + self._stopped = True + if self._process.poll() is None: + self._process.terminate() # SIGTERM on POSIX, TerminateProcess on Windows + try: + self._process.wait(timeout=_GRACEFUL_STOP_TIMEOUT) + except subprocess.TimeoutExpired: + self._process.kill() + self._process.wait(timeout=_GRACEFUL_STOP_TIMEOUT) + with contextlib.suppress(OSError): + self._log_file.close() + + def _atexit_kill(self) -> None: + """Best-effort backup teardown if the host process exits without calling close(). + + Per the design doc: "killed on client close()/__exit__, or on host- + process exit via an atexit/finalizer hook -- best-effort, not + guaranteed." A hard `kill -9` of the *host* skips this entirely, same + as any atexit hook. + """ + with contextlib.suppress(Exception): + self._terminate() + + # -- Client-like passthrough ------------------------------------------------- + # `conduit.local(...)` is usable exactly like `conduit.connect(...)`'s return + # value -- `client.run(pipeline)`, `client.get_info()`, `client.close()` -- + # whether or not it's used as a context manager. Explicit passthrough + # methods (not `__getattr__` delegation) so mypy strict sees real + # signatures and a reader doesn't need to chase a magic-method proxy. + + def run(self, pipeline: Pipeline, *, start: bool = True, timeout: float = 10.0) -> Run: + """See :meth:`conduit.client.Client.run`.""" + return self.client.run(pipeline, start=start, timeout=timeout) + + def get_info(self, *, timeout: float = 5.0) -> api_pb2.Info: + """See :meth:`conduit.client.Client.get_info`.""" + return self.client.get_info(timeout=timeout) + + def close(self) -> None: + """Close the channel and stop the supervised subprocess (graceful, then force).""" + self.client.close() # closes the channel, then calls self._terminate via on_close + + def __enter__(self) -> LocalConduit: + """Return ``self`` for ``with conduit.local(...) as client: client.run(...)``.""" + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + tb: TracebackType | None, + ) -> None: + """Stop the subprocess and close the channel.""" + self.close() + + +def local( + state_dir: str | Path | None = None, + *, + version: str | None = None, + binary: str | Path | None = None, + startup_timeout: float = 30.0, +) -> LocalConduit: + """Spawn and supervise a `conduit` subprocess, returning a bound client. + + **Mode 1 -- dev/notebook/single-host shaped, not a production story** + (design doc, "Deployment modes"): the engine's lifecycle is tied to this + host process. Use :func:`conduit.connect` against an independently + deployed engine for production. + + **`state_dir` is never an ephemeral temp directory.** If omitted, this + defaults to ``./.conduit/state`` under the current working directory -- + stable across repeated runs in the same directory, not + ``tempfile.mkdtemp()`` (which vanishes with the process and silently + forfeits Invariant 2's crash-safe positions the moment the host exits). + A runtime warning is emitted when the default is used; pass ``state_dir`` + explicitly for anything beyond a quick, throwaway experiment. + + Args: + state_dir: directory Conduit's Badger state (positions, pipeline + metadata) lives in. Created if it doesn't exist. Reusing the same + path across runs resumes from where the last run left off. + version: exact `conduit` version to provision (see + :data:`conduit._provision.DEFAULT_CONDUIT_VERSION`); a leading + `v` is stripped if present. Never resolved to "latest" -- + reproducible by default. + binary: path to an already-present `conduit` executable, skipping + download-on-first-use entirely. Use this to pin an exact local + build/binary instead of what this library would otherwise + provision. + startup_timeout: seconds to wait for the engine's API to become + reachable before raising. + + Returns: + A :class:`LocalConduit` -- use as a context manager + (``with conduit.local(...) as client:``) or access ``.client`` + directly; either way, call ``client.close()`` (or exit the ``with`` + block) to stop the subprocess. + + Raises: + ConduitError: see :class:`LocalConduit`'s constructor. + """ + resolved_state_dir = ( + Path(state_dir) if state_dir is not None else Path.cwd() / ".conduit" / "state" + ) + if state_dir is None: + warnings.warn( + f"conduit.local(): no state_dir given, defaulting to " + f"{resolved_state_dir} (stable across runs in this working " + "directory -- never an ephemeral temp dir, which would silently " + "lose pipeline positions on exit; see the design doc's Mode-1 " + "caveat). Pass state_dir= explicitly for anything beyond a quick " + "local experiment.", + stacklevel=2, + ) + resolved_state_dir.mkdir(parents=True, exist_ok=True) + + print( + # real wall-clock time on first use (binary download) and this is a CLI-adjacent + # library, not a silent background service + f"conduit.local(): using engine v{resolve_version(version)}, " + f"state_dir={resolved_state_dir}", + file=sys.stderr, + ) + + return LocalConduit( + state_dir=resolved_state_dir, + version=version, + binary=Path(binary) if binary is not None else None, + startup_timeout=startup_timeout, + ) diff --git a/src/conduit/_provision.py b/src/conduit/_provision.py new file mode 100644 index 0000000..8e3f81b --- /dev/null +++ b/src/conduit/_provision.py @@ -0,0 +1,231 @@ +"""Download-on-first-use, version-pinned provisioning of the `conduit` binary. + +Per the design doc's failure mode 3 ("Binary/version mismatch between the +client library and the `conduit` engine") and open question 3 ("Binary- +provisioning mechanism for `conduit.local()`"), this module **never** relies +on a `conduit` found on `PATH` -- it downloads a specific, pinned GoReleaser +release asset from GitHub Releases, verifies it against the release's +published `checksums.txt`, and caches the extracted binary under a per-version +directory in the user's cache dir. A second `local()` call with the same +version reuses the cached binary; no network access happens if it's already +present. + +Artifact naming (confirmed against `.goreleaser.yml`, not guessed): +`conduit___.tar.gz` on macOS/Linux (`.zip` on Windows), +`Os` title-cased (`Darwin`/`Linux`/`Windows`), `Arch` as `x86_64` for amd64 or +`arm64` for arm64 -- e.g. `conduit_0.18.0_Darwin_arm64.tar.gz`. +""" + +from __future__ import annotations + +import hashlib +import io +import os +import platform +import stat +import tarfile +import tempfile +import urllib.request +import zipfile +from pathlib import Path +from typing import cast + +import platformdirs + +from conduit.errors import ConduitError + +#: Default pinned version, used when `local()`/`ensure_binary()` are not told +#: otherwise. Bump deliberately on a release, never auto-resolved to +#: "latest" -- reproducibility over convenience (design doc, "Upgrade / +#: rollback": "each client library release documents its minimum supported +#: engine version"). +DEFAULT_CONDUIT_VERSION = "0.18.0" + +#: Overrides DEFAULT_CONDUIT_VERSION when set, without touching call sites. +_VERSION_ENV_VAR = "CONDUIT_CLIENT_ENGINE_VERSION" + +_GITHUB_RELEASES = "https://github.com/ConduitIO/conduit/releases/download" + + +def resolve_version(version: str | None) -> str: + """Resolve the effective `conduit` version, stripping a leading `v` if present. + + Precedence: explicit `version=` argument > `CONDUIT_CLIENT_ENGINE_VERSION` + env var > :data:`DEFAULT_CONDUIT_VERSION`. Never resolves "latest" over + the network -- see module docstring. + """ + resolved = version or os.environ.get(_VERSION_ENV_VAR) or DEFAULT_CONDUIT_VERSION + return resolved.removeprefix("v") + + +def _platform_triplet() -> tuple[str, str, str]: + """Return (goreleaser_os, goreleaser_arch, archive_ext) for the current host. + + Raises: + ConduitError: for a platform/arch this module doesn't know a + published release asset name for, with code + ``"client.unsupported_platform"`` -- fails fast and named, never + a silent wrong download. + """ + system = platform.system() + machine = platform.machine().lower() + + os_map = {"Darwin": "Darwin", "Linux": "Linux", "Windows": "Windows"} + arch_map = {"x86_64": "x86_64", "amd64": "x86_64", "arm64": "arm64", "aarch64": "arm64"} + + goreleaser_os = os_map.get(system) + goreleaser_arch = arch_map.get(machine) + if goreleaser_os is None or goreleaser_arch is None: + raise ConduitError( + f"no published conduit release asset known for platform " + f"system={system!r} machine={machine!r}. Supported: " + f"{sorted(os_map)} x {sorted(set(arch_map.values()))}.", + code="client.unsupported_platform", + ) + ext = "zip" if goreleaser_os == "Windows" else "tar.gz" + return goreleaser_os, goreleaser_arch, ext + + +def _archive_name(version: str) -> str: + """The exact GoReleaser archive filename for `version` on this platform.""" + goreleaser_os, goreleaser_arch, ext = _platform_triplet() + return f"conduit_{version}_{goreleaser_os}_{goreleaser_arch}.{ext}" + + +def _binary_name() -> str: + return "conduit.exe" if platform.system() == "Windows" else "conduit" + + +def cache_dir(version: str) -> Path: + """The per-version cache directory the provisioned binary lives under.""" + return Path(platformdirs.user_cache_dir("conduit-client-python")) / "bin" / version + + +def cached_binary_path(version: str) -> Path: + """Where the extracted binary would live for `version`, whether or not it exists yet.""" + return cache_dir(version) / _binary_name() + + +def _http_get(url: str) -> bytes: + """Fetch `url`'s full response body. The sole network I/O in this module. + + Isolated into its own function so unit tests can monkeypatch + ``conduit._provision._http_get`` and exercise the rest of + :func:`ensure_binary` (version resolution, checksum verification, + archive extraction, caching) without any real network access -- per the + build task's gate: "provisioning version-resolution with a MOCKED + download." + """ + # Fixed https:// GitHub Releases URL built from this module's own constants, + # never user input -- not a dynamic-URL SSRF surface. + with urllib.request.urlopen(url, timeout=30) as resp: + return cast(bytes, resp.read()) + + +def _verify_checksum(archive_bytes: bytes, checksums_txt: bytes, archive_name: str) -> None: + """Verify `archive_bytes` against the ` ` line for `archive_name`. + + Raises: + ConduitError: (code ``"client.checksum_mismatch"``) if the archive's + filename isn't listed, or its digest doesn't match -- never + silently accepted. + """ + expected: str | None = None + for line in checksums_txt.decode("utf-8").splitlines(): + parts = line.split() + if len(parts) == 2 and parts[1] == archive_name: + expected = parts[0] + break + if expected is None: + raise ConduitError( + f"{archive_name!r} not listed in the release's checksums.txt -- " + "refusing to trust an unverifiable download.", + code="client.checksum_mismatch", + ) + actual = hashlib.sha256(archive_bytes).hexdigest() + if actual != expected: + raise ConduitError( + f"checksum mismatch for {archive_name!r}: expected {expected}, got {actual}. " + "Refusing to use a download that doesn't match the published release checksum.", + code="client.checksum_mismatch", + ) + + +def _extract_binary(archive_bytes: bytes, archive_ext: str, binary_name: str) -> bytes: + """Extract just `binary_name` from the archive, returning its raw bytes.""" + if archive_ext == "zip": + with zipfile.ZipFile(io.BytesIO(archive_bytes)) as zf: + for name in zf.namelist(): + if Path(name).name == binary_name: + return zf.read(name) + else: + with tarfile.open(fileobj=io.BytesIO(archive_bytes), mode="r:gz") as tf: + for member in tf.getmembers(): + if Path(member.name).name == binary_name and member.isfile(): + extracted = tf.extractfile(member) + if extracted is not None: + return extracted.read() + raise ConduitError( + f"archive did not contain a {binary_name!r} entry -- release asset layout may " + "have changed; this client's extraction logic needs updating.", + code="client.malformed_release_asset", + ) + + +def ensure_binary(version: str | None = None) -> Path: + """Ensure a verified `conduit` binary for `version` is cached locally, and return its path. + + Downloads nothing if already cached. Otherwise: downloads the platform's + release archive and `checksums.txt`, verifies the archive's SHA-256 + against the published checksum, extracts the single `conduit` + (`conduit.exe` on Windows) binary, marks it executable, and atomically + installs it into the per-version cache directory (download-then-rename, + never a partially-written file left at the final path). + + Args: + version: exact version to provision (a leading `v` is stripped if + present); resolved via :func:`resolve_version` if `None`. + + Returns: + Absolute path to the cached, executable `conduit` binary. + + Raises: + ConduitError: on an unsupported platform, a download failure, a + checksum mismatch, or a malformed archive -- never a silent + fallback to some other binary. + """ + resolved_version = resolve_version(version) + target = cached_binary_path(resolved_version) + if target.exists(): + return target + + archive_name = _archive_name(resolved_version) + base_url = f"{_GITHUB_RELEASES}/v{resolved_version}" + + try: + archive_bytes = _http_get(f"{base_url}/{archive_name}") + checksums_bytes = _http_get(f"{base_url}/checksums.txt") + except OSError as e: + raise ConduitError( + f"failed to download conduit v{resolved_version} release asset " + f"({archive_name}) from GitHub Releases: {e}", + code="client.download_failed", + ) from e + + _verify_checksum(archive_bytes, checksums_bytes, archive_name) + + _, _, archive_ext = _platform_triplet() + binary_bytes = _extract_binary(archive_bytes, archive_ext, _binary_name()) + + target.parent.mkdir(parents=True, exist_ok=True) + fd, tmp_path_str = tempfile.mkstemp(dir=target.parent, prefix=".conduit-download-") + tmp_path = Path(tmp_path_str) + try: + with os.fdopen(fd, "wb") as f: + f.write(binary_bytes) + tmp_path.chmod(tmp_path.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH) + tmp_path.replace(target) # atomic on the same filesystem + finally: + tmp_path.unlink(missing_ok=True) + + return target diff --git a/src/conduit/client.py b/src/conduit/client.py new file mode 100644 index 0000000..e0cc246 --- /dev/null +++ b/src/conduit/client.py @@ -0,0 +1,245 @@ +"""``Client``: a gRPC client bound to a Conduit engine's control-plane API. + +Constructed via :func:`conduit.connect` (an already-running, independently +deployed engine -- Mode 2) or :func:`conduit.local` (a supervised subprocess +-- Mode 1). Both return the same :class:`Client` type and expose the identical +surface; only how the underlying channel/process came to exist differs (design +doc, "``conduit.local()`` and ``conduit.connect(addr)``"). + +Every RPC call here goes through :meth:`Client._call`, which translates any +``grpc.RpcError`` into a :class:`conduit.errors.ConduitError` -- callers never +see a raw gRPC exception or stack trace (CLAUDE.md, "errors are API"). +""" + +from __future__ import annotations + +from collections.abc import Callable +from types import TracebackType +from typing import Protocol, TypeVar + +import grpc + +import conduit._grpc # noqa: F401 -- sets up sys.path, see conduit._grpc.__init__ +from api.v1 import api_pb2, api_pb2_grpc +from conduit.errors import ConduitError +from conduit.pipeline import Pipeline +from conduit.run import Run + +_Req = TypeVar("_Req") +_Resp = TypeVar("_Resp") +_ReqContra = TypeVar("_ReqContra", contravariant=True) +_RespCo = TypeVar("_RespCo", covariant=True) + + +class _UnaryUnary(Protocol[_ReqContra, _RespCo]): + """A gRPC unary-unary stub method's call signature. + + The generated stubs (``api_pb2_grpc.py``, vendored, unannotated) bind + each RPC as a plain callable at ``__init__`` time with no static type -- + this Protocol is hand-written just to give :meth:`Client._call` a real, + checkable signature (``request``, keyword-only ``timeout``) instead of + losing type safety at every call site. + """ + + def __call__(self, request: _ReqContra, *, timeout: float | None = ...) -> _RespCo: ... + + +class Client: + """Bound to one Conduit engine's control-plane gRPC API. + + Not constructed directly -- use :func:`conduit.connect` or + :func:`conduit.local`. + """ + + def __init__( + self, + channel: grpc.Channel, + *, + on_close: Callable[[], None] | None = None, + ) -> None: + """Wrap a gRPC channel with the control-plane stubs this client drives. + + Args: + channel: an already-constructed gRPC channel (insecure loopback + for :func:`conduit.local`, whatever :func:`conduit.connect` + was given). + on_close: called once, after the channel closes, by + :meth:`close`/``__exit__`` -- :func:`conduit.local` uses this + to tear down the supervised subprocess; :func:`conduit.connect` + leaves it ``None`` (no process to own). + """ + self._channel = channel + self._on_close = on_close + self._closed = False + self._pipelines = api_pb2_grpc.PipelineServiceStub(channel) # type: ignore[no-untyped-call] + self._connectors = api_pb2_grpc.ConnectorServiceStub(channel) # type: ignore[no-untyped-call] + self._processors = api_pb2_grpc.ProcessorServiceStub(channel) # type: ignore[no-untyped-call] + self._info = api_pb2_grpc.InformationServiceStub(channel) # type: ignore[no-untyped-call] + + def __enter__(self) -> Client: + """Support ``with conduit.connect(...) as client:``.""" + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + tb: TracebackType | None, + ) -> None: + """Close the channel (and, for ``local()``, tear down the subprocess).""" + self.close() + + def close(self) -> None: + """Close the gRPC channel and, if this client owns a subprocess, stop it. + + Idempotent -- safe to call more than once (a second call is a no-op). + """ + if self._closed: + return + self._closed = True + self._channel.close() + if self._on_close is not None: + self._on_close() + + @staticmethod + def _call(stub_method: _UnaryUnary[_Req, _Resp], request: _Req, *, timeout: float) -> _Resp: + """Invoke a unary gRPC stub method, translating failures to :class:`ConduitError`.""" + try: + return stub_method(request, timeout=timeout) + except grpc.RpcError as e: + raise ConduitError.from_rpc_error(e) from e + + def get_info(self, *, timeout: float = 5.0) -> api_pb2.Info: + """Call ``InformationService.GetInfo`` -- the running engine's version/os/arch. + + Used at connect-time by :func:`conduit.local`/:func:`conduit.connect` + to confirm engine-version compatibility (design doc, failure mode 3). + """ + resp: api_pb2.GetInfoResponse = self._call( + self._info.GetInfo, api_pb2.GetInfoRequest(), timeout=timeout + ) + return resp.info + + def run(self, pipeline: Pipeline, *, start: bool = True, timeout: float = 10.0) -> Run: + """Submit a :class:`Pipeline` and (by default) start it. + + Drives, in order: ``CreatePipeline`` (response carries the + server-assigned ``pipeline_id`` every subsequent call needs) -> + ``CreateConnector`` per declared source/destination -> + ``CreateProcessor`` per declared processor -> ``UpdateDLQ`` if + ``.dlq(...)`` was called -> ``StartPipeline`` unless ``start=False``. + + No partial-pipeline cleanup on a mid-sequence failure: an error here + raises immediately with whatever step failed named in the message, + leaving already-created resources in place for the caller to inspect + or delete -- silently rolling back would hide exactly the information + needed to fix the config that failed. Because there's no rollback, + any failure *after* ``CreatePipeline`` has already responded carries + the server-assigned id on :attr:`ConduitError.pipeline_id`, so the + caller can find and clean up the partially-created pipeline even + though this method didn't. + + Args: + pipeline: the built pipeline (see :class:`conduit.pipeline.Pipeline`). + start: whether to call ``StartPipeline`` after creating every + resource. ``False`` leaves the pipeline created-but-stopped + (``STATUS_STOPPED`` / ``STOPPED_REASON_USER``). + timeout: per-RPC timeout in seconds, applied to each call in the + sequence independently. + + Returns: + A :class:`conduit.run.Run` handle bound to the created pipeline's id. + + Raises: + ConduitError: on any RPC failure, including partial-creation + failures (see above). ``pipeline_id`` is set on the raised + error whenever ``CreatePipeline`` already succeeded. + """ + plan = pipeline.build_requests() + + create_resp = self._call( + self._pipelines.CreatePipeline, plan.create_pipeline, timeout=timeout + ) + pipeline_id = create_resp.pipeline.id + + try: + for connector_req in plan.create_connectors: + connector_req.pipeline_id = pipeline_id + self._call(self._connectors.CreateConnector, connector_req, timeout=timeout) + + for processor_req in plan.create_processors: + processor_req.parent.type = api_pb2.Processor.Parent.TYPE_PIPELINE + processor_req.parent.id = pipeline_id + self._call(self._processors.CreateProcessor, processor_req, timeout=timeout) + + if plan.dlq is not None: + plan.dlq.id = pipeline_id + self._call(self._pipelines.UpdateDLQ, plan.dlq, timeout=timeout) + + if start: + self._call( + self._pipelines.StartPipeline, + api_pb2.StartPipelineRequest(id=pipeline_id), + timeout=timeout, + ) + except ConduitError as e: + # The pipeline itself was already created (CreatePipeline returned + # above) -- this step's failure doesn't undo that, so surface the + # id here or the caller has no way to find/clean up the orphaned + # pipeline (see docstring: run() deliberately never rolls back). + e.pipeline_id = pipeline_id + raise + + return Run(self, pipeline_id) + + +def connect( + addr: str, + *, + credentials: grpc.ChannelCredentials | None = None, + check_version: bool = True, + timeout: float = 5.0, +) -> Client: + """Dial an already-running, independently deployed Conduit engine. + + This is Mode 2 (design doc, "Deployment modes -- framed honestly"): the + production shape. The returned :class:`Client` is a thin, stateless gRPC + client -- the engine's lifecycle, restart policy, and persistence are + managed independently (systemd, Kubernetes, ...), not by this process. + ``close()``/``__exit__`` only closes the channel; no subprocess is ever + involved. + + Args: + addr: ``host:port`` of the engine's gRPC API (``Options.API.GRPCAddress`` + on the server side). + credentials: gRPC channel credentials for a secure channel; ``None`` + uses an insecure channel (loopback dev default; pass real + credentials for anything crossing an untrusted network). + check_version: call ``GetInfo`` immediately and raise (via + :class:`ConduitError`) on outright unreachability. **Name is + aspirational, not descriptive**: despite the name, this only + checks *reachability*, not the engine's actual version -- no + version comparison happens in Slice 1. See the ``TODO`` on the + call site below; minimum-supported-version gating is a + fast-follow once this library has a real version to check + against (design doc, "Upgrade / rollback"). + timeout: seconds to wait for the initial ``GetInfo`` check. + + Returns: + A :class:`Client` bound to the given address. + + Raises: + ConduitError: if ``check_version`` is true and the engine is + unreachable within ``timeout``. + """ + channel = grpc.secure_channel(addr, credentials) if credentials else grpc.insecure_channel(addr) + client = Client(channel) + if check_version: + # TODO: this only confirms the engine is reachable via GetInfo -- it + # does not compare `Info.version` against a minimum-supported version, + # so `check_version` overpromises relative to its name. Tracked as a + # fast-follow (see the `check_version` docstring above); do not fix + # here without a design doc for the compatibility policy (min + # version, deprecation window) per CLAUDE.md's backward-compat rules. + client.get_info(timeout=timeout) + return client diff --git a/src/conduit/errors.py b/src/conduit/errors.py new file mode 100644 index 0000000..27dcce9 --- /dev/null +++ b/src/conduit/errors.py @@ -0,0 +1,152 @@ +"""``ConduitError``: the one exception type this client ever raises for a failed call. + +Per the design doc (``docs/design/20260724-embed-grpc-client-libraries.md``, +"``conduit.local()`` and ``conduit.connect(addr)``" section) and CLAUDE.md's +"errors are API" standard, a caller of this library should never see a raw +``grpc.RpcError`` or a bare stack trace from the wire. Every gRPC failure is +translated into a :class:`ConduitError` carrying the same structured fields +Conduit's server already attaches to the wire +(``pkg/foundation/cerrors/conduiterr/status.go``'s ``ToStatus``): a stable +dotted ``code`` (``google.rpc.ErrorInfo.reason``), the human ``message``, and +optional ``config_path``/``suggestion``/``docs_url`` metadata -- reused as-is, +not reinvented. +""" + +from __future__ import annotations + +import grpc +from google.rpc import error_details_pb2 +from grpc_status import rpc_status + + +class ConduitError(Exception): + """Uniform, machine-actionable error raised for any failed API call. + + Mirrors the Go-side ``conduiterr.ConduitError`` shape field-for-field + where the wire carries it: ``code``, ``message``, ``config_path``, + ``suggestion``, ``docs_url``. ``grpc_status`` is always populated (the + underlying gRPC status code) even when no ``ErrorInfo`` detail is + present -- e.g. for a raw transport failure (engine unreachable, timeout) + that never reached Conduit's error-mapping layer at all. + """ + + def __init__( + self, + message: str, + *, + code: str = "internal.unknown", + grpc_status_code: grpc.StatusCode = grpc.StatusCode.UNKNOWN, + config_path: str = "", + suggestion: str = "", + docs_url: str = "", + pipeline_id: str = "", + ) -> None: + """Initialize a structured, actionable error. + + Args: + message: human-readable description, becomes ``str(self)``. + code: stable dotted reason, e.g. ``"common.not_found"`` -- + Conduit's ``ErrorInfo.reason`` when present, else + ``"internal.unknown"``. + grpc_status_code: the underlying gRPC status category. + config_path: JSON-pointer to the offending config field, if any. + suggestion: human-readable fix hint, if any. + docs_url: link to further documentation, if any. + pipeline_id: server-assigned pipeline id already created before + this error occurred, if any. Set by :meth:`conduit.client.Client.run` + on a mid-sequence failure (pipeline created, then a connector/ + processor/DLQ/start call fails) -- ``run()`` deliberately does + not roll back already-created resources (see its docstring), + so this is the only way the caller learns the id needed to + clean them up. Empty when the error occurred before + ``CreatePipeline`` returned, or for errors unrelated to + ``run()`` (e.g. :meth:`Run.status`). + """ + super().__init__(message) + self.code = code + self.grpc_status_code = grpc_status_code + self.config_path = config_path + self.suggestion = suggestion + self.docs_url = docs_url + self.pipeline_id = pipeline_id + + def __str__(self) -> str: + """Render message plus any structured fields present, one per line.""" + lines = [f"[{self.code}] {super().__str__()}"] + if self.config_path: + lines.append(f" config path: {self.config_path}") + if self.suggestion: + lines.append(f" suggestion: {self.suggestion}") + if self.docs_url: + lines.append(f" docs: {self.docs_url}") + if self.pipeline_id: + lines.append(f" pipeline id (already created): {self.pipeline_id}") + return "\n".join(lines) + + @classmethod + def from_rpc_error(cls, exc: grpc.RpcError) -> ConduitError: + """Translate a caught ``grpc.RpcError`` into a :class:`ConduitError`. + + Attempts to unpack the ``google.rpc.ErrorInfo`` detail Conduit's + server attaches (domain ``"conduit"``); falls back to a bare + ``code``/``message`` synthesized from the gRPC status category alone + when no such detail is present (e.g. the call never reached + Conduit's handler -- connection refused, deadline exceeded). + + Args: + exc: the exception caught from a unary gRPC call. Expected to + also implement ``grpc.Call`` (true for every exception + ``grpc``'s synchronous stubs raise), so ``rpc_status`` can + read trailing metadata off it. + + Returns: + A populated :class:`ConduitError`, never raising itself. + """ + grpc_code = exc.code() if isinstance(exc, grpc.Call) else grpc.StatusCode.UNKNOWN + message = exc.details() if isinstance(exc, grpc.Call) else str(exc) + message = message or str(exc) + + status = None + if isinstance(exc, grpc.Call): + try: + status = rpc_status.from_call(exc) + except ValueError: + # Trailing metadata present but not a well-formed + # google.rpc.Status -- fall back below rather than propagate + # a decode error from *this* translation layer. + status = None + + if status is not None: + for detail in status.details: + if detail.Is(error_details_pb2.ErrorInfo.DESCRIPTOR): + info = error_details_pb2.ErrorInfo() + detail.Unpack(info) + if info.domain == "conduit": + md = info.metadata + return cls( + message, + code=info.reason or "internal.unknown", + grpc_status_code=grpc_code, + config_path=md.get("configPath", ""), + suggestion=md.get("suggestion", ""), + docs_url=md.get("docsUrl", ""), + ) + + # No Conduit ErrorInfo detail: the call failed before reaching + # Conduit's error-mapping layer (network failure, engine down, + # deadline exceeded) or against an older engine that predates + # structured errors. Still never a raw traceback -- just less detail. + return cls(message, code=_code_from_status(grpc_code), grpc_status_code=grpc_code) + + +def _code_from_status(grpc_code: grpc.StatusCode) -> str: + """Best-effort dotted code for a bare gRPC status with no ErrorInfo detail.""" + return { + grpc.StatusCode.UNAVAILABLE: "transport.unavailable", + grpc.StatusCode.DEADLINE_EXCEEDED: "transport.deadline_exceeded", + grpc.StatusCode.CANCELLED: "transport.cancelled", + grpc.StatusCode.NOT_FOUND: "common.not_found", + grpc.StatusCode.INVALID_ARGUMENT: "common.invalid_argument", + grpc.StatusCode.ALREADY_EXISTS: "common.already_exists", + grpc.StatusCode.FAILED_PRECONDITION: "common.failed_precondition", + }.get(grpc_code, "internal.unknown") diff --git a/src/conduit/pipeline.py b/src/conduit/pipeline.py new file mode 100644 index 0000000..c1a0343 --- /dev/null +++ b/src/conduit/pipeline.py @@ -0,0 +1,353 @@ +"""``Pipeline``: the fluent builder that produces control-plane API payloads. + +Per the design doc (``docs/design/20260724-embed-grpc-client-libraries.md``, +"Decision -- client-library API design" / "Pipeline builder") and the task +spec's frozen surface: + + pipeline = ( + conduit.Pipeline("orders-sync") + .source("postgres", url="postgres://...", tables="orders") + .destination("kafka", brokers="localhost:9092", topic="orders") + .process("filter.field", condition="orders.deleted == false") + ) + +This is **Slice 1, Case A only**: every ``source``/``destination``/``process`` +call takes a *plugin name string* (``"postgres"``, ``"builtin:log"``, ...), +never a Python object -- ``inline_source``/``inline_destination`` (Case B, +external connectors) is out of scope for this build (design doc, "Build +slices"). + +``Settings`` on the wire is a flat ``map`` +(``Connector.Config.settings``, ``Processor.Config.settings`` in +``proto/api/v1/api.proto``) -- every keyword argument is coerced to ``str`` +here. Typed, per-connector config (real parameter types, IDE-visible) is a +documented fast-follow generated from connector param specs, not hand-written +per connector (design doc, "Fast-follow -- typed-config codegen"); see the +README. + +**Real connector config keys are frequently not valid Python identifiers** +(e.g. the builtin generator connector's ``format.type``, ``sdk.batch.size``, +``collections.*.operations``) -- a dotted or wildcarded key can never be +written as a Python ``**kwargs`` keyword. Every ``.source()``/ +``.destination()``/``.process()``/``.dlq()`` call therefore also accepts an +explicit ``settings: Mapping[str, object]`` dict for exactly those keys, +merged with (and overridden by, on overlap) any ``**kwargs`` given at the +same call: ``.source("generator", settings={"format.type": "structured"}, +operations="create")``. Plain identifier-shaped keys can use whichever form +reads better at the call site. + +This module never touches gRPC. :meth:`Pipeline.build_requests` is a pure +function of the builder's accumulated state -- deliberately so it can be +unit-tested (builder -> exact RPC payload) without a mock channel or a live +server. :class:`conduit.client.Client` is the only thing that sends the +requests this module builds. +""" + +from __future__ import annotations + +from collections.abc import Mapping +from dataclasses import dataclass + +import conduit._grpc # noqa: F401 -- sets up sys.path, see conduit._grpc.__init__ +from api.v1 import api_pb2 + + +def _settings( + settings: Mapping[str, object] | None, kwargs: Mapping[str, object] +) -> dict[str, str]: + """Merge the explicit ``settings=`` mapping with ``**kwargs``, coerced to strings. + + ``kwargs`` wins on key overlap (it's the more specific, call-site-local + form). Booleans render as Conduit's config parser expects + (``"true"``/``"false"``, not Python's ``"True"``/``"False"``); everything + else uses ``str()``. + """ + merged: dict[str, object] = dict(settings or {}) + merged.update(kwargs) + out: dict[str, str] = {} + for k, v in merged.items(): + out[k] = "true" if v is True else "false" if v is False else str(v) + return out + + +@dataclass(frozen=True) +class _ConnectorSpec: + """One accumulated ``.source()``/``.destination()`` call.""" + + type: api_pb2.Connector.Type + plugin: str + name: str + settings: dict[str, str] + + +@dataclass(frozen=True) +class _ProcessorSpec: + """One accumulated ``.process()`` call. Attaches to the pipeline itself. + + Attaching a processor to a specific connector (``Processor.Parent.TYPE_CONNECTOR``) + is supported by the wire protocol but not exposed by this builder yet -- + Slice 1 only needs pipeline-level processors to cover the frozen quickstart + surface; a per-connector attachment point is a natural, additive follow-up. + """ + + plugin: str + condition: str + workers: int | None + settings: dict[str, str] + + +@dataclass(frozen=True) +class _DLQSpec: + """One ``.dlq()`` call -- pipeline-level dead-letter-queue config.""" + + plugin: str + settings: dict[str, str] + window_size: int | None + window_nack_threshold: int | None + + +@dataclass(frozen=True) +class BuildPlan: + """The exact, ordered set of RPC request payloads a :class:`Pipeline` produces. + + Pure data -- constructing one performs no I/O. :class:`conduit.client.Client` + sends ``create_pipeline`` first (the response carries the server-assigned + pipeline id every other request needs), then ``create_connectors`` and + ``create_processors`` in the order they were declared, then ``dlq`` (if + set) last, before starting the pipeline. + """ + + create_pipeline: api_pb2.CreatePipelineRequest + create_connectors: tuple[api_pb2.CreateConnectorRequest, ...] + create_processors: tuple[api_pb2.CreateProcessorRequest, ...] + dlq: api_pb2.UpdateDLQRequest | None + + +class Pipeline: + """Fluent builder for a pipeline's create-RPC payloads. + + ``id`` is a local, display-only label (Conduit assigns the real pipeline + ID server-side -- ``Pipeline.id`` is ``OUTPUT_ONLY`` on the wire, + ``proto/api/v1/api.proto:100``). It becomes the pipeline's ``name`` unless + ``name=`` is given explicitly. + """ + + def __init__(self, id: str, *, name: str | None = None, description: str = "") -> None: + """Start building a pipeline. + + Args: + id: local, display-only label; also the default ``name`` sent to + ``CreatePipeline`` if ``name`` is not given. + name: pipeline name on the wire (``Pipeline.Config.name``). + Defaults to ``id``. + description: pipeline description (``Pipeline.Config.description``). + """ + self.id = id + self.name = name if name is not None else id + self.description = description + self._connectors: list[_ConnectorSpec] = [] + self._processors: list[_ProcessorSpec] = [] + self._dlq: _DLQSpec | None = None + + def source( + self, + plugin: str, + *, + name: str = "", + settings: Mapping[str, object] | None = None, + **kwargs: object, + ) -> Pipeline: + """Add a source connector. + + Args: + plugin: connector plugin reference, e.g. ``"postgres"``, + ``"builtin:generator"``, ``"standalone:s3@v0.2.0"`` (see + ``proto/api/v1/api.proto``'s ``CreateConnectorRequest.plugin`` + doc comment for the full ``[TYPE:]NAME[@VERSION]`` grammar). + name: connector name (``Connector.Config.name``); defaults to + ``plugin`` if omitted. + settings: connector config for keys that aren't valid Python + identifiers (e.g. ``"format.type"``); merged with ``**kwargs`` + (see module docstring). + **kwargs: connector config for identifier-shaped keys, coerced to + the wire's flat ``map``. + + Returns: + ``self``, for chaining. + """ + self._connectors.append( + _ConnectorSpec( + type=api_pb2.Connector.TYPE_SOURCE, + plugin=plugin, + name=name or plugin, + settings=_settings(settings, kwargs), + ) + ) + return self + + def destination( + self, + plugin: str, + *, + name: str = "", + settings: Mapping[str, object] | None = None, + **kwargs: object, + ) -> Pipeline: + """Add a destination connector. See :meth:`source` for argument semantics.""" + self._connectors.append( + _ConnectorSpec( + type=api_pb2.Connector.TYPE_DESTINATION, + plugin=plugin, + name=name or plugin, + settings=_settings(settings, kwargs), + ) + ) + return self + + def process( + self, + plugin: str, + *, + condition: str = "", + workers: int | None = None, + settings: Mapping[str, object] | None = None, + **kwargs: object, + ) -> Pipeline: + """Add a pipeline-level processor. + + Args: + plugin: processor plugin reference, e.g. ``"builtin:field.filter"``. + condition: goTemplate-formatted condition string + (``Processor.condition``); empty means "always run". + workers: number of concurrent workers + (``Processor.Config.workers``); ``None`` leaves the field + unset (server default). + settings: processor config for keys that aren't valid Python + identifiers; merged with ``**kwargs`` (see module docstring). + **kwargs: processor config for identifier-shaped keys, coerced to + the wire's flat ``map``. + + Returns: + ``self``, for chaining. + """ + self._processors.append( + _ProcessorSpec( + plugin=plugin, + condition=condition, + workers=workers, + settings=_settings(settings, kwargs), + ) + ) + return self + + def dlq( + self, + plugin: str = "builtin:log", + *, + window_size: int | None = None, + window_nack_threshold: int | None = None, + settings: Mapping[str, object] | None = None, + **kwargs: object, + ) -> Pipeline: + """Configure the pipeline-level dead-letter queue (``Pipeline.DLQ``). + + **Defaulting note (data-integrity):** ``UpdateDLQ`` takes + ``window_size``/``window_nack_threshold`` at face value with no + server-side defaulting -- unlike pipeline *creation*, where the engine + applies ``DefaultDLQ`` (``window_size=1``) if ``.dlq()`` is never + called at all. Leaving these ``None`` here and sending the protobuf + zero value would silently *disable* the nack-window stop-safety + (``window_size=0`` means "don't monitor"), which is strictly worse + than never calling ``.dlq()``. So this builder mirrors the engine's + own ``DefaultDLQ`` client-side: omitting ``window_size``/ + ``window_nack_threshold`` defaults them to ``1``/``0`` on the emitted + ``UpdateDLQ`` payload; pass explicit values (including ``0``) to + override. + + Args: + plugin: DLQ connector plugin; Conduit's own default is + ``builtin:log`` at ``WARN`` level (``proto/api/v1/api.proto``'s + ``Pipeline.DLQ.plugin`` doc comment) if never configured. + window_size: how many recent acks/nacks are monitored + (``0`` disables the window). Defaults to ``1`` (matching the + engine's ``DefaultDLQ``) if not given -- see note above. + window_nack_threshold: nacks tolerated within the window before + the pipeline stops. Defaults to ``0`` if not given. + settings: DLQ connector config for keys that aren't valid Python + identifiers; merged with ``**kwargs`` (see module docstring). + **kwargs: DLQ connector config for identifier-shaped keys, coerced + to ``map``. + + Returns: + ``self``, for chaining. + """ + self._dlq = _DLQSpec( + plugin=plugin, + settings=_settings(settings, kwargs), + window_size=window_size if window_size is not None else 1, + window_nack_threshold=( + window_nack_threshold if window_nack_threshold is not None else 0 + ), + ) + return self + + def build_requests(self) -> BuildPlan: + """Render the accumulated builder state into exact RPC request payloads. + + Pure and side-effect-free -- this is the seam unit tests exercise + directly (builder calls in, exact protobuf messages out) without any + gRPC channel involved. + + Returns: + A :class:`BuildPlan` with every request :class:`conduit.client.Client` + needs to send, in send order (minus pipeline id, which only + exists after ``CreatePipeline`` responds -- connector/processor/ + DLQ requests are returned with ``pipeline_id``/``id`` left unset; + the client fills it in after creating the pipeline). + """ + create_pipeline = api_pb2.CreatePipelineRequest( + config=api_pb2.Pipeline.Config(name=self.name, description=self.description) + ) + create_connectors = tuple( + api_pb2.CreateConnectorRequest( + type=c.type, + plugin=c.plugin, + config=api_pb2.Connector.Config(name=c.name, settings=c.settings), + ) + for c in self._connectors + ) + create_processors = tuple( + api_pb2.CreateProcessorRequest( + plugin=p.plugin, + condition=p.condition, + config=api_pb2.Processor.Config( + settings=p.settings, + **({"workers": p.workers} if p.workers is not None else {}), + ), + ) + for p in self._processors + ) + dlq_request: api_pb2.UpdateDLQRequest | None = None + if self._dlq is not None: + dlq_request = api_pb2.UpdateDLQRequest( + dlq=api_pb2.Pipeline.DLQ( + plugin=self._dlq.plugin, + settings=self._dlq.settings, + **( + {"window_size": self._dlq.window_size} + if self._dlq.window_size is not None + else {} + ), + **( + {"window_nack_threshold": self._dlq.window_nack_threshold} + if self._dlq.window_nack_threshold is not None + else {} + ), + ) + ) + return BuildPlan( + create_pipeline=create_pipeline, + create_connectors=create_connectors, + create_processors=create_processors, + dlq=dlq_request, + ) diff --git a/src/conduit/py.typed b/src/conduit/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/conduit/run.py b/src/conduit/run.py new file mode 100644 index 0000000..fc543bc --- /dev/null +++ b/src/conduit/run.py @@ -0,0 +1,165 @@ +"""``Run``: a handle to a submitted, running (or stopped) pipeline. + +Returned by :meth:`conduit.client.Client.run`. Per the design doc's failure +mode 5 ("Host<->engine control-channel loss mid-run"), losing the gRPC +connection is never conflated with the pipeline itself stopping -- every +method here re-issues a fresh RPC rather than caching state client-side, so +a transient reconnect is invisible to the caller and a real pipeline state +change is always reflected. +""" + +from __future__ import annotations + +import time +from dataclasses import dataclass +from typing import TYPE_CHECKING + +import conduit._grpc # noqa: F401 -- sets up sys.path, see conduit._grpc.__init__ +from api.v1 import api_pb2 +from conduit.errors import ConduitError + +if TYPE_CHECKING: + from conduit.client import Client + +_STATUS_PREFIX = "STATUS_" +_STOPPED_REASON_PREFIX = "STOPPED_REASON_" + + +def _strip_enum_prefix(name: str, prefix: str) -> str: + """Render a proto enum's ``Name()`` as a short, lowercase string. + + E.g. ``"STATUS_RUNNING"`` -> ``"running"``, ``"STOPPED_REASON_USER"`` -> + ``"user"``. Kept as a plain string (not the raw enum) on + :class:`RunStatus` so callers don't need to import ``api_pb2`` themselves + for a simple status check. + """ + return name[len(prefix) :].lower() if name.startswith(prefix) else name.lower() + + +@dataclass(frozen=True) +class RunStatus: + """A pipeline's status, as of the ``GetPipeline`` call that produced it. + + ``status`` is one of ``"unspecified"``, ``"running"``, ``"stopped"``, + ``"degraded"``, ``"recovering"`` (``Pipeline.State.Status``, + ``proto/api/v1/api.proto``). ``error`` is non-empty only when + ``status == "degraded"``. ``stopped_reason`` is one of ``"unspecified"``, + ``"user"``, ``"system"`` -- only meaningful when ``status == "stopped"``. + + **No throughput/metrics field**: ``GetPipeline`` carries no such data + (design doc, "Observability" -- there is no metrics RPC in the + control-plane API). Scrape Conduit's own ``/metrics`` Prometheus endpoint + directly for throughput; this is a deliberate, documented v1 gap, not an + oversight. + """ + + status: str + error: str + stopped_reason: str + + @property + def is_running(self) -> bool: + """Shorthand for ``status == "running"``.""" + return self.status == "running" + + @property + def is_degraded(self) -> bool: + """Shorthand for ``status == "degraded"``.""" + return self.status == "degraded" + + +class Run: + """Handle to a pipeline this client submitted via :meth:`Client.run`.""" + + def __init__(self, client: Client, pipeline_id: str) -> None: + """Bind a run handle to an already-created pipeline id. + + Not constructed directly -- returned by :meth:`Client.run`. + """ + self._client = client + self.pipeline_id = pipeline_id + + def status(self, *, timeout: float = 5.0) -> RunStatus: + """Fetch the pipeline's current status via ``GetPipeline``. + + Args: + timeout: seconds to wait for the RPC. + + Returns: + The pipeline's current :class:`RunStatus`. + + Raises: + ConduitError: on RPC failure, e.g. ``common.not_found`` if the + pipeline was deleted out-of-band, or a transport error if the + engine is unreachable -- distinguishable from "pipeline + degraded" by :attr:`ConduitError.code` (``transport.*`` vs. a + Conduit-domain reason), per the design doc's failure mode 1. + """ + resp = self._client._call( + self._client._pipelines.GetPipeline, + api_pb2.GetPipelineRequest(id=self.pipeline_id), + timeout=timeout, + ) + state = resp.pipeline.state + return RunStatus( + status=_strip_enum_prefix(api_pb2.Pipeline.Status.Name(state.status), _STATUS_PREFIX), + error=state.error, + stopped_reason=_strip_enum_prefix( + api_pb2.Pipeline.State.StoppedReason.Name(state.stopped_reason), + _STOPPED_REASON_PREFIX, + ), + ) + + def wait_running(self, *, timeout: float = 30.0, poll_interval: float = 0.2) -> RunStatus: + """Poll ``status()`` until the pipeline reaches ``"running"``. + + Args: + timeout: total seconds to wait before giving up. + poll_interval: seconds between polls. + + Returns: + The ``"running"`` :class:`RunStatus` once reached. + + Raises: + ConduitError: with code ``"client.pipeline_degraded"`` if the + pipeline transitions to ``"degraded"`` before running (the + degraded status's own ``error`` field is included in the + message -- never silently swallowed), or + ``"client.wait_timeout"`` if ``timeout`` elapses first. + """ + deadline = time.monotonic() + timeout + last: RunStatus | None = None + while time.monotonic() < deadline: + last = self.status() + if last.is_running: + return last + if last.is_degraded: + raise ConduitError( + f"pipeline {self.pipeline_id!r} became degraded while waiting to " + f"run: {last.error}", + code="client.pipeline_degraded", + ) + time.sleep(poll_interval) + raise ConduitError( + f"pipeline {self.pipeline_id!r} did not reach running within {timeout}s " + f"(last observed status: {last.status if last else 'unknown'})", + code="client.wait_timeout", + ) + + def stop(self, *, force: bool = False, timeout: float = 10.0) -> None: + """Stop the pipeline via ``StopPipeline``. + + Args: + force: if ``False`` (default), a graceful stop -- drains in-flight + records per Invariant 7. ``True`` requests a forceful stop; + only pass this when you have already decided graceful + shutdown isn't an option, not as a default. + timeout: seconds to wait for the RPC to return (the RPC itself + returns once the stop is *initiated*, not once the pipeline + has fully drained -- poll :meth:`status` for that). + """ + self._client._call( + self._client._pipelines.StopPipeline, + api_pb2.StopPipelineRequest(id=self.pipeline_id, force=force), + timeout=timeout, + ) diff --git a/tests/integration/test_local_generator_log.py b/tests/integration/test_local_generator_log.py new file mode 100644 index 0000000..f87078e --- /dev/null +++ b/tests/integration/test_local_generator_log.py @@ -0,0 +1,89 @@ +"""End-to-end: `conduit.local()` running a real `generator -> log` pipeline. + +This is the one test in this repo that touches a real `conduit` binary -- +downloaded for real (no mocking) via `conduit._provision.ensure_binary`, +spawned for real, driven over a real loopback gRPC channel. Marked +`integration` (excluded from the default `pytest` run, see +`pyproject.toml`'s `testpaths`/`markers` and the root `README.md`'s +"Testing" section). + +**Honesty requirement (build task gate):** if this sandbox has no network +access to GitHub Releases, this test is skipped with the real reason stated +-- never faked, never silently passed by mocking away the exact thing it +exists to prove. +""" + +from __future__ import annotations + +import time +import urllib.error +import urllib.request + +import pytest + +import conduit +from conduit._provision import DEFAULT_CONDUIT_VERSION, _archive_name + +pytestmark = pytest.mark.integration + + +def _github_releases_reachable() -> bool: + """Best-effort, fast check for network access to GitHub Releases. + + Not a substitute for the download itself succeeding (e.g. this version's + asset could 404 even if GitHub is reachable) -- just the cheap, honest + "do we even have a network" gate so a sandboxed/offline run skips with a + clear reason instead of hanging on a DNS/connect timeout. + """ + try: + urllib.request.urlopen("https://github.com", timeout=3) + except (TimeoutError, urllib.error.URLError, OSError): + return False + return True + + +@pytest.fixture(scope="module") +def _network_available() -> bool: + return _github_releases_reachable() + + +def test_generator_to_log_pipeline_runs_end_to_end( + tmp_path_factory: pytest.TempPathFactory, _network_available: bool +) -> None: + if not _network_available: + pytest.skip( + "no network access to github.com in this sandbox -- " + f"conduit.local() needs to download {_archive_name(DEFAULT_CONDUIT_VERSION)} " + "from GitHub Releases on first use, and this environment cannot reach it. " + "Honestly skipped, not faked: this is the one test that exercises a real " + "conduit binary end-to-end." + ) + + state_dir = tmp_path_factory.mktemp("conduit-state") + pipeline = ( + conduit.Pipeline("integration-generator-log") + .source("generator", settings={"format.type": "structured"}, operations="create") + .destination("log", level="info") + ) + + with conduit.local(state_dir=state_dir, startup_timeout=60.0) as client: + run = client.run(pipeline) + status = run.wait_running(timeout=30.0) + assert status.is_running + + settled = run.status() + assert settled.status in ("running", "degraded") + if settled.status == "degraded": + pytest.fail(f"pipeline degraded after starting: {settled.error}") + + run.stop() + # StopPipeline returns once the stop is *initiated*, not once the + # pipeline has fully drained (Run.stop()'s own docstring) -- poll + # status() rather than asserting immediately. + deadline = time.monotonic() + 10.0 + final = run.status() + while final.status != "stopped" and time.monotonic() < deadline: + time.sleep(0.1) + final = run.status() + assert final.status == "stopped" + assert final.stopped_reason == "user" diff --git a/tests/unit/test_client_run.py b/tests/unit/test_client_run.py new file mode 100644 index 0000000..d61feff --- /dev/null +++ b/tests/unit/test_client_run.py @@ -0,0 +1,275 @@ +"""``Client.run()``/``Run`` against a small fake control-plane server. + +An in-process fake `PipelineService`/`ConnectorService`/`ProcessorService` +(loopback gRPC, no external process) lets these tests verify the *sequence +and shape* of RPCs `Client.run()` issues, and `Run.status()`/`wait_running()`/ +`stop()`'s behavior, without a real `conduit` binary. The integration test +(`tests/integration/test_local_generator_log.py`) covers the real binary. +""" + +from __future__ import annotations + +from collections.abc import Iterator +from concurrent import futures + +import grpc +import pytest + +import conduit._grpc # noqa: F401 -- sets up sys.path, see conduit._grpc.__init__ +from api.v1 import api_pb2, api_pb2_grpc +from conduit.client import Client +from conduit.errors import ConduitError +from conduit.pipeline import Pipeline + + +class _FakeControlPlane( + api_pb2_grpc.PipelineServiceServicer, + api_pb2_grpc.ConnectorServiceServicer, + api_pb2_grpc.ProcessorServiceServicer, +): + """Enough of the control-plane API to exercise `Client.run()`/`Run` end-to-end. + + Tracks every request it receives (`self.calls`) so tests can assert on + the exact sequence `Client.run()` issues, and lets a test script a + sequence of `Pipeline.State.Status` values `GetPipeline` returns (for + `wait_running()`'s polling behavior). + """ + + def __init__(self) -> None: + self.calls: list[str] = [] + self._next_connector_id = 0 + self._next_processor_id = 0 + self.status_sequence: list[api_pb2.Pipeline.Status.ValueType] = [ + api_pb2.Pipeline.STATUS_RUNNING + ] + self.error_message = "" + self.stopped_reason: api_pb2.Pipeline.State.StoppedReason.ValueType = ( + api_pb2.Pipeline.State.STOPPED_REASON_UNSPECIFIED + ) + # When set, CreateConnector aborts instead of succeeding -- lets tests + # simulate the mid-sequence failure Client.run() deliberately doesn't + # roll back from (pipeline already created, a later step fails). + self.fail_create_connector = False + + def CreatePipeline( + self, request: api_pb2.CreatePipelineRequest, context: grpc.ServicerContext + ) -> api_pb2.CreatePipelineResponse: + self.calls.append("CreatePipeline") + pipeline = api_pb2.Pipeline(id="pipeline-1", config=request.config) + return api_pb2.CreatePipelineResponse(pipeline=pipeline) + + def CreateConnector( + self, request: api_pb2.CreateConnectorRequest, context: grpc.ServicerContext + ) -> api_pb2.CreateConnectorResponse: + self.calls.append(f"CreateConnector({request.plugin}, pipeline_id={request.pipeline_id})") + if self.fail_create_connector: + context.abort(grpc.StatusCode.INTERNAL, "simulated connector creation failure") + self._next_connector_id += 1 + connector = api_pb2.Connector( + id=f"connector-{self._next_connector_id}", + type=request.type, + plugin=request.plugin, + pipeline_id=request.pipeline_id, + config=request.config, + ) + return api_pb2.CreateConnectorResponse(connector=connector) + + def CreateProcessor( + self, request: api_pb2.CreateProcessorRequest, context: grpc.ServicerContext + ) -> api_pb2.CreateProcessorResponse: + self.calls.append( + f"CreateProcessor({request.plugin}, parent={request.parent.type}/{request.parent.id})" + ) + self._next_processor_id += 1 + processor = api_pb2.Processor( + id=f"processor-{self._next_processor_id}", + plugin=request.plugin, + parent=request.parent, + config=request.config, + condition=request.condition, + ) + return api_pb2.CreateProcessorResponse(processor=processor) + + def UpdateDLQ( + self, request: api_pb2.UpdateDLQRequest, context: grpc.ServicerContext + ) -> api_pb2.UpdateDLQResponse: + self.calls.append(f"UpdateDLQ(id={request.id})") + return api_pb2.UpdateDLQResponse(dlq=request.dlq) + + def StartPipeline( + self, request: api_pb2.StartPipelineRequest, context: grpc.ServicerContext + ) -> api_pb2.StartPipelineResponse: + self.calls.append(f"StartPipeline(id={request.id})") + return api_pb2.StartPipelineResponse() + + def StopPipeline( + self, request: api_pb2.StopPipelineRequest, context: grpc.ServicerContext + ) -> api_pb2.StopPipelineResponse: + self.calls.append(f"StopPipeline(id={request.id}, force={request.force})") + return api_pb2.StopPipelineResponse() + + def GetPipeline( + self, request: api_pb2.GetPipelineRequest, context: grpc.ServicerContext + ) -> api_pb2.GetPipelineResponse: + status = ( + self.status_sequence.pop(0) + if len(self.status_sequence) > 1 + else self.status_sequence[0] + ) + state = api_pb2.Pipeline.State( + status=status, error=self.error_message, stopped_reason=self.stopped_reason + ) + return api_pb2.GetPipelineResponse(pipeline=api_pb2.Pipeline(id=request.id, state=state)) + + +@pytest.fixture +def fake_server() -> Iterator[tuple[_FakeControlPlane, Client]]: + servicer = _FakeControlPlane() + server = grpc.server(futures.ThreadPoolExecutor(max_workers=4)) + api_pb2_grpc.add_PipelineServiceServicer_to_server(servicer, server) + api_pb2_grpc.add_ConnectorServiceServicer_to_server(servicer, server) + api_pb2_grpc.add_ProcessorServiceServicer_to_server(servicer, server) + port = server.add_insecure_port("127.0.0.1:0") + server.start() + channel = grpc.insecure_channel(f"127.0.0.1:{port}") + client = Client(channel) + try: + yield servicer, client + finally: + client.close() + server.stop(None) + + +def test_run_creates_pipeline_connectors_processors_dlq_then_starts( + fake_server: tuple[_FakeControlPlane, Client], +) -> None: + servicer, client = fake_server + pipeline = ( + Pipeline("orders-sync") + .source("generator", format_type="structured") + .destination("log", level="info") + .process("filter.field", condition="x") + .dlq("builtin:log", window_size=1) + ) + + run = client.run(pipeline) + + assert run.pipeline_id == "pipeline-1" + assert servicer.calls == [ + "CreatePipeline", + "CreateConnector(generator, pipeline_id=pipeline-1)", + "CreateConnector(log, pipeline_id=pipeline-1)", + "CreateProcessor(filter.field, parent=2/pipeline-1)", # Parent.TYPE_PIPELINE == 2 + "UpdateDLQ(id=pipeline-1)", + "StartPipeline(id=pipeline-1)", + ] + + +def test_run_with_start_false_skips_start_pipeline( + fake_server: tuple[_FakeControlPlane, Client], +) -> None: + servicer, client = fake_server + pipeline = Pipeline("p").source("generator").destination("log") + + client.run(pipeline, start=False) + + assert "StartPipeline(id=pipeline-1)" not in servicer.calls + assert servicer.calls[0] == "CreatePipeline" + + +def test_run_without_dlq_skips_update_dlq(fake_server: tuple[_FakeControlPlane, Client]) -> None: + servicer, client = fake_server + client.run(Pipeline("p").source("generator").destination("log")) + assert not any(call.startswith("UpdateDLQ") for call in servicer.calls) + + +def test_status_maps_enum_to_lowercase_strings( + fake_server: tuple[_FakeControlPlane, Client], +) -> None: + servicer, client = fake_server + servicer.status_sequence = [api_pb2.Pipeline.STATUS_DEGRADED] + servicer.error_message = "destination unreachable" + run = client.run(Pipeline("p").source("generator").destination("log"), start=False) + + status = run.status() + + assert status.status == "degraded" + assert status.error == "destination unreachable" + assert status.is_degraded is True + assert status.is_running is False + + +def test_wait_running_polls_until_running(fake_server: tuple[_FakeControlPlane, Client]) -> None: + servicer, client = fake_server + servicer.status_sequence = [ + api_pb2.Pipeline.STATUS_RECOVERING, + api_pb2.Pipeline.STATUS_RECOVERING, + api_pb2.Pipeline.STATUS_RUNNING, + ] + run = client.run(Pipeline("p").source("generator").destination("log"), start=False) + + status = run.wait_running(timeout=5.0, poll_interval=0.01) + + assert status.is_running is True + + +def test_wait_running_raises_on_degraded(fake_server: tuple[_FakeControlPlane, Client]) -> None: + servicer, client = fake_server + servicer.status_sequence = [api_pb2.Pipeline.STATUS_DEGRADED] + servicer.error_message = "boom" + run = client.run(Pipeline("p").source("generator").destination("log"), start=False) + + with pytest.raises(ConduitError) as exc_info: + run.wait_running(timeout=5.0, poll_interval=0.01) + assert exc_info.value.code == "client.pipeline_degraded" + assert "boom" in str(exc_info.value) + + +def test_wait_running_times_out(fake_server: tuple[_FakeControlPlane, Client]) -> None: + servicer, client = fake_server + servicer.status_sequence = [api_pb2.Pipeline.STATUS_RECOVERING] + run = client.run(Pipeline("p").source("generator").destination("log"), start=False) + + with pytest.raises(ConduitError) as exc_info: + run.wait_running(timeout=0.2, poll_interval=0.05) + assert exc_info.value.code == "client.wait_timeout" + + +def test_stop_issues_stop_pipeline_graceful_by_default( + fake_server: tuple[_FakeControlPlane, Client], +) -> None: + servicer, client = fake_server + run = client.run(Pipeline("p").source("generator").destination("log"), start=False) + + run.stop() + + assert "StopPipeline(id=pipeline-1, force=False)" in servicer.calls + + +def test_run_mid_sequence_failure_carries_pipeline_id( + fake_server: tuple[_FakeControlPlane, Client], +) -> None: + """Regression test: run() deliberately never rolls back a partially + created pipeline, but the raised ConduitError must carry the + already-created pipeline_id -- otherwise the caller has no way to find + and clean up the orphaned pipeline. + """ + servicer, client = fake_server + servicer.fail_create_connector = True + pipeline = Pipeline("orders-sync").source("generator").destination("log") + + with pytest.raises(ConduitError) as exc_info: + client.run(pipeline) + + assert exc_info.value.pipeline_id == "pipeline-1" + assert "pipeline-1" in str(exc_info.value) + + +def test_stop_force_true_is_explicit() -> None: + """force=True is available but never the default -- graceful is (Invariant 7).""" + import inspect + + from conduit.run import Run + + sig = inspect.signature(Run.stop) + assert sig.parameters["force"].default is False diff --git a/tests/unit/test_errors.py b/tests/unit/test_errors.py new file mode 100644 index 0000000..56f3af7 --- /dev/null +++ b/tests/unit/test_errors.py @@ -0,0 +1,147 @@ +"""grpc.RpcError -> ConduitError translation. + +Spins up a tiny real in-process gRPC server/channel (loopback, no external +process) so the translation is exercised against a genuine wire-encoded +``google.rpc.Status``/``ErrorInfo`` detail -- the exact shape Conduit's Go +server emits (``pkg/foundation/cerrors/conduiterr/status.go``'s ``ToStatus``), +not a hand-constructed fake. +""" + +from __future__ import annotations + +from collections.abc import Iterator +from concurrent import futures + +import grpc +import pytest +from google.rpc import error_details_pb2, status_pb2 +from grpc_status import rpc_status + +import conduit._grpc # noqa: F401 -- sets up sys.path, see conduit._grpc.__init__ +from api.v1 import api_pb2, api_pb2_grpc +from conduit.errors import ConduitError + + +class _Servicer(api_pb2_grpc.PipelineServiceServicer): + """Serves exactly one canned failure per test, mirroring conduiterr's wire shape.""" + + def __init__( + self, status: status_pb2.Status | None, plain_code: grpc.StatusCode | None + ) -> None: + self._status = status + self._plain_code = plain_code + + def GetPipeline( + self, request: api_pb2.GetPipelineRequest, context: grpc.ServicerContext + ) -> api_pb2.GetPipelineResponse: + if self._status is not None: + context.abort_with_status(rpc_status.to_status(self._status)) + assert self._plain_code is not None + context.abort(self._plain_code, "not found, no structured detail") + raise AssertionError("unreachable -- context.abort always raises") + + +def _serve( + status: status_pb2.Status | None = None, + plain_code: grpc.StatusCode | None = None, +) -> Iterator[api_pb2_grpc.PipelineServiceStub]: + server = grpc.server(futures.ThreadPoolExecutor(max_workers=2)) + api_pb2_grpc.add_PipelineServiceServicer_to_server(_Servicer(status, plain_code), server) + port = server.add_insecure_port("127.0.0.1:0") + server.start() + channel = grpc.insecure_channel(f"127.0.0.1:{port}") + try: + yield api_pb2_grpc.PipelineServiceStub(channel) + finally: + channel.close() + server.stop(None) + + +@pytest.fixture +def conduit_error_info_stub() -> Iterator[api_pb2_grpc.PipelineServiceStub]: + """A server that fails GetPipeline with a full conduiterr-shaped ErrorInfo detail.""" + info = error_details_pb2.ErrorInfo( + reason="common.not_found", + domain="conduit", + metadata={"configPath": "/id", "suggestion": "check the pipeline id"}, + ) + status = status_pb2.Status( + code=grpc.StatusCode.NOT_FOUND.value[0], message="pipeline not found: xyz" + ) + status.details.add().Pack(info) + yield from _serve(status=status) + + +@pytest.fixture +def plain_not_found_stub() -> Iterator[api_pb2_grpc.PipelineServiceStub]: + """A server that fails with a bare gRPC status, no ErrorInfo detail at all.""" + yield from _serve(plain_code=grpc.StatusCode.NOT_FOUND) + + +def test_decodes_conduit_error_info_detail( + conduit_error_info_stub: api_pb2_grpc.PipelineServiceStub, +) -> None: + with pytest.raises(grpc.RpcError) as exc_info: + conduit_error_info_stub.GetPipeline(api_pb2.GetPipelineRequest(id="xyz")) + + err = ConduitError.from_rpc_error(exc_info.value) + + assert err.code == "common.not_found" + assert "pipeline not found: xyz" in str(err) + assert err.config_path == "/id" + assert err.suggestion == "check the pipeline id" + assert err.grpc_status_code == grpc.StatusCode.NOT_FOUND + + +def test_falls_back_to_bare_status_without_error_info( + plain_not_found_stub: api_pb2_grpc.PipelineServiceStub, +) -> None: + with pytest.raises(grpc.RpcError) as exc_info: + plain_not_found_stub.GetPipeline(api_pb2.GetPipelineRequest(id="xyz")) + + err = ConduitError.from_rpc_error(exc_info.value) + + assert err.code == "common.not_found" # synthesized from the gRPC category + assert err.grpc_status_code == grpc.StatusCode.NOT_FOUND + assert err.config_path == "" + assert err.suggestion == "" + + +def test_unreachable_server_never_leaks_raw_traceback() -> None: + # Deliberately no server listening on this port. + channel = grpc.insecure_channel("127.0.0.1:1") + stub = api_pb2_grpc.PipelineServiceStub(channel) + try: + with pytest.raises(grpc.RpcError) as exc_info: + stub.GetPipeline(api_pb2.GetPipelineRequest(id="xyz"), timeout=2.0) + err = ConduitError.from_rpc_error(exc_info.value) + assert isinstance(err, ConduitError) + assert err.grpc_status_code in ( + grpc.StatusCode.UNAVAILABLE, + grpc.StatusCode.DEADLINE_EXCEEDED, + ) + finally: + channel.close() + + +def test_str_renders_structured_fields() -> None: + err = ConduitError( + "boom", + code="common.invalid_argument", + config_path="/settings/url", + suggestion="set a valid URL", + docs_url="https://conduit.io/docs/errors/common.invalid_argument", + ) + rendered = str(err) + assert "[common.invalid_argument] boom" in rendered + assert "config path: /settings/url" in rendered + assert "suggestion: set a valid URL" in rendered + assert "docs: https://conduit.io/docs/errors/common.invalid_argument" in rendered + + +def test_pipeline_id_defaults_empty_and_renders_when_set() -> None: + assert ConduitError("boom").pipeline_id == "" + + err = ConduitError("boom", pipeline_id="pipeline-1") + assert err.pipeline_id == "pipeline-1" + assert "pipeline-1" in str(err) diff --git a/tests/unit/test_pipeline_builder.py b/tests/unit/test_pipeline_builder.py new file mode 100644 index 0000000..bdb3ec0 --- /dev/null +++ b/tests/unit/test_pipeline_builder.py @@ -0,0 +1,210 @@ +"""Builder -> exact RPC payload mapping. + +Exercises :meth:`conduit.pipeline.Pipeline.build_requests` directly -- pure, +no gRPC channel, no mock server -- per the build task's unit-test gate. +""" + +from __future__ import annotations + +import conduit._grpc # noqa: F401 -- sets up sys.path, see conduit._grpc.__init__ +from api.v1 import api_pb2 +from conduit.pipeline import Pipeline + + +def test_id_becomes_default_name() -> None: + plan = Pipeline("orders-sync").build_requests() + assert plan.create_pipeline == api_pb2.CreatePipelineRequest( + config=api_pb2.Pipeline.Config(name="orders-sync", description="") + ) + + +def test_explicit_name_overrides_id() -> None: + plan = Pipeline("orders-sync", name="Orders Sync", description="syncs orders").build_requests() + assert plan.create_pipeline.config.name == "Orders Sync" + assert plan.create_pipeline.config.description == "syncs orders" + + +def test_source_produces_create_connector_request() -> None: + plan = Pipeline("p").source("postgres", url="postgres://x", tables="orders").build_requests() + assert len(plan.create_connectors) == 1 + req = plan.create_connectors[0] + assert req.type == api_pb2.Connector.TYPE_SOURCE + assert req.plugin == "postgres" + assert req.config.name == "postgres" + assert dict(req.config.settings) == {"url": "postgres://x", "tables": "orders"} + # pipeline_id is filled in by Client.run() after CreatePipeline responds, + # not by the builder -- it doesn't exist yet at build_requests() time. + assert req.pipeline_id == "" + + +def test_destination_produces_create_connector_request() -> None: + plan = ( + Pipeline("p") + .destination("kafka", brokers="localhost:9092", topic="orders") + .build_requests() + ) + assert len(plan.create_connectors) == 1 + req = plan.create_connectors[0] + assert req.type == api_pb2.Connector.TYPE_DESTINATION + assert req.plugin == "kafka" + assert dict(req.config.settings) == {"brokers": "localhost:9092", "topic": "orders"} + + +def test_source_and_destination_order_preserved() -> None: + plan = ( + Pipeline("p") + .source("generator", format_type="structured") + .destination("log", level="info") + .build_requests() + ) + assert [c.plugin for c in plan.create_connectors] == ["generator", "log"] + assert [c.type for c in plan.create_connectors] == [ + api_pb2.Connector.TYPE_SOURCE, + api_pb2.Connector.TYPE_DESTINATION, + ] + + +def test_connector_name_override() -> None: + plan = Pipeline("p").source("postgres", name="orders-db", url="x").build_requests() + assert plan.create_connectors[0].config.name == "orders-db" + + +def test_process_produces_create_processor_request() -> None: + plan = ( + Pipeline("p") + .process("filter.field", condition="orders.deleted == false", workers=4, some_key="v") + .build_requests() + ) + assert len(plan.create_processors) == 1 + req = plan.create_processors[0] + assert req.plugin == "filter.field" + assert req.condition == "orders.deleted == false" + assert req.config.workers == 4 + assert dict(req.config.settings) == {"some_key": "v"} + # parent is filled in by Client.run() (pipeline id doesn't exist yet). + assert req.parent.id == "" + + +def test_process_without_workers_leaves_field_unset() -> None: + plan = Pipeline("p").process("filter.field").build_requests() + assert plan.create_processors[0].config.workers == 0 + + +def test_multiple_processors_preserve_order() -> None: + plan = Pipeline("p").process("a").process("b").process("c").build_requests() + assert [p.plugin for p in plan.create_processors] == ["a", "b", "c"] + + +def test_no_dlq_by_default() -> None: + plan = Pipeline("p").build_requests() + assert plan.dlq is None + + +def test_dlq_produces_update_dlq_request() -> None: + plan = ( + Pipeline("p") + .dlq("builtin:log", window_size=5, window_nack_threshold=2, level="warn") + .build_requests() + ) + assert plan.dlq is not None + assert plan.dlq.dlq.plugin == "builtin:log" + assert plan.dlq.dlq.window_size == 5 + assert plan.dlq.dlq.window_nack_threshold == 2 + assert dict(plan.dlq.dlq.settings) == {"level": "warn"} + # id is filled in by Client.run() after CreatePipeline responds. + assert plan.dlq.id == "" + + +def test_dlq_defaults_plugin_to_builtin_log() -> None: + plan = Pipeline("p").dlq().build_requests() + assert plan.dlq is not None + assert plan.dlq.dlq.plugin == "builtin:log" + + +def test_dlq_defaults_window_size_to_one_when_not_given() -> None: + """Regression test: `UpdateDLQ` has no server-side defaulting, unlike + pipeline creation's `DefaultDLQ` (window_size=1). Leaving `window_size` + unset on the wire sends the protobuf zero value, which *disables* the + nack-window stop-safety -- worse than never calling `.dlq()` at all. The + builder must default `window_size=1`/`window_nack_threshold=0` itself + whenever `.dlq()` is called without explicit window args, matching the + engine's own `DefaultDLQ`. This bug shipped invisibly once (no test + covered the DLQ payload's window fields) -- do not regress it. + """ + plan = Pipeline("p").dlq("builtin:log").build_requests() + assert plan.dlq is not None + assert plan.dlq.dlq.window_size == 1 + assert plan.dlq.dlq.window_nack_threshold == 0 + + +def test_dlq_explicit_window_size_zero_is_respected() -> None: + """An explicit `window_size=0` is a deliberate override, not the same as + omitting the argument -- the builder must not clobber an intentional + opt-out of the nack window. + """ + plan = Pipeline("p").dlq("builtin:log", window_size=0).build_requests() + assert plan.dlq is not None + assert plan.dlq.dlq.window_size == 0 + + +def test_settings_coerce_bool_to_lowercase_string() -> None: + plan = Pipeline("p").source("x", recreate=True, dry_run=False).build_requests() + assert dict(plan.create_connectors[0].config.settings) == { + "recreate": "true", + "dry_run": "false", + } + + +def test_settings_coerce_int_to_string() -> None: + plan = Pipeline("p").source("x", batch_size=100).build_requests() + assert dict(plan.create_connectors[0].config.settings) == {"batch_size": "100"} + + +def test_settings_dict_supports_dotted_keys_not_valid_as_kwargs() -> None: + """Real connector config (e.g. generator's `format.type`) can't be a **kwarg.""" + plan = ( + Pipeline("p") + .source( + "generator", + settings={"format.type": "structured", "sdk.batch.size": 10}, + operations="create", + ) + .build_requests() + ) + assert dict(plan.create_connectors[0].config.settings) == { + "format.type": "structured", + "sdk.batch.size": "10", + "operations": "create", + } + + +def test_settings_dict_and_kwargs_kwargs_wins_on_overlap() -> None: + plan = Pipeline("p").source("x", settings={"level": "warn"}, level="info").build_requests() + assert dict(plan.create_connectors[0].config.settings) == {"level": "info"} + + +def test_chaining_returns_same_builder() -> None: + pipeline = Pipeline("p") + assert pipeline.source("a") is pipeline + assert pipeline.destination("b") is pipeline + assert pipeline.process("c") is pipeline + assert pipeline.dlq() is pipeline + + +def test_full_quickstart_shape_matches_design_doc() -> None: + """The frozen ≤15-line quickstart shape end-to-end (builder side only).""" + pipeline = ( + Pipeline("orders-sync") + .source("generator", format_type="structured", operations="create") + .destination("log", level="info") + ) + plan = pipeline.build_requests() + + assert plan.create_pipeline.config.name == "orders-sync" + assert len(plan.create_connectors) == 2 + assert plan.create_connectors[0].type == api_pb2.Connector.TYPE_SOURCE + assert plan.create_connectors[0].plugin == "generator" + assert plan.create_connectors[1].type == api_pb2.Connector.TYPE_DESTINATION + assert plan.create_connectors[1].plugin == "log" + assert len(plan.create_processors) == 0 + assert plan.dlq is None diff --git a/tests/unit/test_provision.py b/tests/unit/test_provision.py new file mode 100644 index 0000000..b245327 --- /dev/null +++ b/tests/unit/test_provision.py @@ -0,0 +1,207 @@ +"""Binary provisioning: version resolution, checksum verification, extraction. + +All network I/O is mocked (``conduit._provision._http_get`` monkeypatched) -- +per the build task's gate: "provisioning version-resolution with a MOCKED +download." :mod:`tests.integration.test_local_generator_log` covers the real, +unmocked download-and-launch path and is honestly skipped where there's no +network. +""" + +from __future__ import annotations + +import hashlib +import io +import os +import platform +import tarfile +from pathlib import Path + +import pytest + +from conduit import _provision +from conduit.errors import ConduitError + + +def test_resolve_version_strips_leading_v() -> None: + assert _provision.resolve_version("v0.19.0") == "0.19.0" + + +def test_resolve_version_explicit_wins_over_default() -> None: + assert _provision.resolve_version("0.20.0") == "0.20.0" + + +def test_resolve_version_falls_back_to_default() -> None: + assert _provision.resolve_version(None) == _provision.DEFAULT_CONDUIT_VERSION + + +def test_resolve_version_env_var_overrides_default(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("CONDUIT_CLIENT_ENGINE_VERSION", "v0.21.0") + assert _provision.resolve_version(None) == "0.21.0" + + +def test_resolve_version_explicit_arg_beats_env_var(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("CONDUIT_CLIENT_ENGINE_VERSION", "0.21.0") + assert _provision.resolve_version("0.22.0") == "0.22.0" + + +def test_platform_triplet_known_platform(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(platform, "system", lambda: "Darwin") + monkeypatch.setattr(platform, "machine", lambda: "arm64") + assert _provision._platform_triplet() == ("Darwin", "arm64", "tar.gz") + + +def test_platform_triplet_linux_amd64(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(platform, "system", lambda: "Linux") + monkeypatch.setattr(platform, "machine", lambda: "x86_64") + assert _provision._platform_triplet() == ("Linux", "x86_64", "tar.gz") + + +def test_platform_triplet_windows_uses_zip(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(platform, "system", lambda: "Windows") + monkeypatch.setattr(platform, "machine", lambda: "amd64") + assert _provision._platform_triplet() == ("Windows", "x86_64", "zip") + + +def test_platform_triplet_unsupported_raises_conduit_error(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(platform, "system", lambda: "Plan9") + monkeypatch.setattr(platform, "machine", lambda: "risc-v") + with pytest.raises(ConduitError) as exc_info: + _provision._platform_triplet() + assert exc_info.value.code == "client.unsupported_platform" + + +def _make_tar_gz(binary_name: str, binary_content: bytes) -> bytes: + buf = io.BytesIO() + with tarfile.open(fileobj=buf, mode="w:gz") as tf: + info = tarfile.TarInfo(name=binary_name) + info.size = len(binary_content) + tf.addfile(info, io.BytesIO(binary_content)) + return buf.getvalue() + + +def test_ensure_binary_downloads_verifies_and_caches( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + monkeypatch.setattr(platform, "system", lambda: "Darwin") + monkeypatch.setattr(platform, "machine", lambda: "arm64") + monkeypatch.setattr(_provision.platformdirs, "user_cache_dir", lambda _name: str(tmp_path)) + + binary_bytes = b"#!/bin/sh\necho fake-conduit\n" + archive_bytes = _make_tar_gz("conduit", binary_bytes) + archive_name = "conduit_1.2.3_Darwin_arm64.tar.gz" + checksum = hashlib.sha256(archive_bytes).hexdigest() + checksums_txt = f"{checksum} {archive_name}\ndeadbeef some_other_file.tar.gz\n".encode() + + requested_urls: list[str] = [] + + def fake_http_get(url: str) -> bytes: + requested_urls.append(url) + if url.endswith("checksums.txt"): + return checksums_txt + assert url.endswith(archive_name) + return archive_bytes + + monkeypatch.setattr(_provision, "_http_get", fake_http_get) + + path = _provision.ensure_binary("1.2.3") + + assert path == _provision.cached_binary_path("1.2.3") + assert path.exists() + assert path.read_bytes() == binary_bytes + assert os.access(path, os.X_OK) + assert any(archive_name in u for u in requested_urls) + assert any(u.endswith("checksums.txt") for u in requested_urls) + + # Second call: cached, no network access at all. + monkeypatch.setattr( + _provision, + "_http_get", + lambda _url: (_ for _ in ()).throw(AssertionError("should not re-download")), + ) + cached_path = _provision.ensure_binary("1.2.3") + assert cached_path == path + + +def test_ensure_binary_rejects_checksum_mismatch( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + monkeypatch.setattr(platform, "system", lambda: "Linux") + monkeypatch.setattr(platform, "machine", lambda: "x86_64") + monkeypatch.setattr(_provision.platformdirs, "user_cache_dir", lambda _name: str(tmp_path)) + + archive_bytes = _make_tar_gz("conduit", b"binary-content") + archive_name = "conduit_9.9.9_Linux_x86_64.tar.gz" + wrong_digest = "0" * 64 + checksums_txt = f"{wrong_digest} {archive_name}\n".encode() + + def fake_http_get(url: str) -> bytes: + return checksums_txt if url.endswith("checksums.txt") else archive_bytes + + monkeypatch.setattr(_provision, "_http_get", fake_http_get) + + with pytest.raises(ConduitError) as exc_info: + _provision.ensure_binary("9.9.9") + assert exc_info.value.code == "client.checksum_mismatch" + assert not _provision.cached_binary_path("9.9.9").exists() + + +def test_ensure_binary_rejects_unlisted_archive( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + monkeypatch.setattr(platform, "system", lambda: "Linux") + monkeypatch.setattr(platform, "machine", lambda: "x86_64") + monkeypatch.setattr(_provision.platformdirs, "user_cache_dir", lambda _name: str(tmp_path)) + + archive_bytes = _make_tar_gz("conduit", b"binary-content") + checksums_txt = b"deadbeef some_totally_different_file.tar.gz\n" + + monkeypatch.setattr( + _provision, + "_http_get", + lambda url: checksums_txt if url.endswith("checksums.txt") else archive_bytes, + ) + + with pytest.raises(ConduitError) as exc_info: + _provision.ensure_binary("5.5.5") + assert exc_info.value.code == "client.checksum_mismatch" + + +def test_ensure_binary_download_failure_wrapped_as_conduit_error( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + monkeypatch.setattr(platform, "system", lambda: "Linux") + monkeypatch.setattr(platform, "machine", lambda: "x86_64") + monkeypatch.setattr(_provision.platformdirs, "user_cache_dir", lambda _name: str(tmp_path)) + + def fake_http_get(_url: str) -> bytes: + raise OSError("network unreachable (simulated)") + + monkeypatch.setattr(_provision, "_http_get", fake_http_get) + + with pytest.raises(ConduitError) as exc_info: + _provision.ensure_binary("1.0.0") + assert exc_info.value.code == "client.download_failed" + + +def test_ensure_binary_rejects_malformed_archive( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + monkeypatch.setattr(platform, "system", lambda: "Linux") + monkeypatch.setattr(platform, "machine", lambda: "x86_64") + monkeypatch.setattr(_provision.platformdirs, "user_cache_dir", lambda _name: str(tmp_path)) + + # A tar.gz with no `conduit` entry at all. + archive_bytes = _make_tar_gz("some-other-file", b"not the binary") + archive_name = "conduit_2.0.0_Linux_x86_64.tar.gz" + checksum = hashlib.sha256(archive_bytes).hexdigest() + checksums_txt = f"{checksum} {archive_name}\n".encode() + + monkeypatch.setattr( + _provision, + "_http_get", + lambda url: checksums_txt if url.endswith("checksums.txt") else archive_bytes, + ) + + with pytest.raises(ConduitError) as exc_info: + _provision.ensure_binary("2.0.0") + assert exc_info.value.code == "client.malformed_release_asset" diff --git a/tools/generate-stubs.sh b/tools/generate-stubs.sh new file mode 100755 index 0000000..ed42636 --- /dev/null +++ b/tools/generate-stubs.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Regenerates the vendored gRPC/protobuf stubs in src/conduit/_grpc/ from the +# current ConduitIO/conduit control-plane API (proto/api/v1/api.proto) plus its +# transitive dependencies (conduit-commons opencdc/config, googleapis +# annotations, grpc-gateway openapiv2 options). See +# docs/design/20260724-embed-grpc-client-libraries.md and +# src/conduit/_grpc/__init__.py for why each of these is generated (api.proto +# imports all of them; the Python protobuf runtime needs a generated module +# for every transitively imported .proto file to resolve descriptors, even +# for types this client never constructs directly). +# +# Usage: ./tools/generate-stubs.sh +# Requires: buf (https://buf.build/docs/installation) on PATH. +set -euo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +echo "==> api/v1 (PipelineService/ConnectorService/ProcessorService/InformationService)" +buf generate buf.build/conduitio/conduit --path api/v1 + +echo "==> config/v1 + opencdc/v1 (parameter + record types, from conduit-commons)" +buf generate buf.build/conduitio/conduit-commons --path config/v1 --path opencdc/v1 + +echo "==> google/api (annotations/field_behavior/http, used as message/method options)" +buf generate buf.build/googleapis/googleapis \ + --path google/api/annotations.proto \ + --path google/api/field_behavior.proto \ + --path google/api/http.proto + +echo "==> protoc-gen-openapiv2/options (grpc-gateway OpenAPI annotations)" +buf generate buf.build/grpc-ecosystem/grpc-gateway --path protoc-gen-openapiv2/options + +echo "==> done. Review the diff in src/conduit/_grpc/ before committing."