Skip to content

fix: Generate Python Code from plugin-pb - #250

Merged
kodiakhq[bot] merged 1 commit into
mainfrom
fix/gen_proto
Oct 16, 2026
Merged

kodiakhq[bot] merged 1 commit into
mainfrom
fix/gen_proto

Conversation

@cloudquery-ci

@cloudquery-ci cloudquery-ci Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

This PR was created by a scheduled workflow to regenerate the Python code from plugin-pb.

@cloudquery-ci cloudquery-ci Bot added the automerge Add to automerge PRs once requirements are met label Oct 16, 2026
@cloudquery-ci
cloudquery-ci Bot requested a review from a team as a code owner October 16, 2026 09:43
@cloudquery-ci
cloudquery-ci Bot requested review from erezrokah and removed request for a team October 16, 2026 09:43
@kodiakhq
kodiakhq Bot merged commit 1cdd1cc into main Oct 16, 2026
6 checks passed
@kodiakhq
kodiakhq Bot deleted the fix/gen_proto branch October 16, 2026 09:44
kodiakhq Bot pushed a commit that referenced this pull request Oct 20, 2026
🤖 I have created a release *beep* *boop*
---


## [0.0.55](v0.0.54...v0.0.55) (2026-10-16)


### Bug Fixes

* **deps:** Update dependency pytest to v9.0.3 [SECURITY] ([#224](#224)) ([c8f4360](c8f4360))
* **deps:** Update dependency pytest to v9.0.3 [SECURITY] ([#226](#226)) ([9e4f23c](9e4f23c))
* **deps:** Update dependency pytest to v9.0.3 [SECURITY] ([#227](#227)) ([77d029f](77d029f))
* **deps:** Update dependency pytest to v9.0.3 [SECURITY] ([#228](#228)) ([08c3834](08c3834))
* **deps:** Update dependency pytest to v9.0.3 [SECURITY] ([#229](#229)) ([c24e6cf](c24e6cf))
* **deps:** Update dependency pytest to v9.0.3 [SECURITY] ([#230](#230)) ([d7ace83](d7ace83))
* **deps:** Update dependency pytest to v9.0.3 [SECURITY] ([#231](#231)) ([177a661](177a661))
* **deps:** Update dependency pytest to v9.0.3 [SECURITY] ([#232](#232)) ([ee48f41](ee48f41))
* **deps:** Update dependency pytest to v9.0.3 [SECURITY] ([#234](#234)) ([444b631](444b631))
* **deps:** Update dependency pytest to v9.0.3 [SECURITY] ([#235](#235)) ([3dcfeb6](3dcfeb6))
* **deps:** Update dependency pytest to v9.0.3 [SECURITY] ([#236](#236)) ([0f1033a](0f1033a))
* Generate Python Code from `plugin-pb` ([#248](#248)) ([5ec1920](5ec1920))
* Generate Python Code from `plugin-pb` ([#249](#249)) ([dacf7b6](dacf7b6))
* Generate Python Code from `plugin-pb` ([#250](#250)) ([1cdd1cc](1cdd1cc))

---
This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
kodiakhq Bot pushed a commit to cloudquery/plugin-sdk-python that referenced this pull request Oct 20, 2026
#### Summary

Brings the source side of state checkpoints (cloudquery/plugin-pb#42, cloudquery/plugin-sdk#2632, cloudquery/cloudquery#23546) to the Python SDK. When the CLI sets `Sync.Request.supports_checkpoints`, flushed state goes on the sync stream as `Sync.MessageCheckpoint` instead of being written straight to the backend. The CLI then persists it only after every destination commits the records synced before it.

Why we want this: some of our Python syncs run for many hours, often limited by provider rate limits. A transient failure late in the sync currently throws away all the progress, because state is only safe to write once the sync ends. With checkpoints, a failed sync keeps the cursors whose records were committed, and the next run resumes from there.

Opt-in, and unchanged unless both sides opt in:

```python
self._scheduler = Scheduler(concurrency=...)
self._state_client = StateClientBuilder.build(
    backend_options=options.backend_options,
    checkpoints=self._scheduler.checkpoints,  # new, optional
)
self._scheduler.set_post_sync_hook(self._state_client.flush)

# in a table resolver, with one cursor per form
key = state_key("responses", client_id=client.id(), form_id=form["id"])
for item in api.list_responses(form["id"], since=state_client.get_key(key)):
    yield item
    state_client.set_key(key, item["updated_at"])
    state_client.flush()  # a checkpoint, sent once this item and its relations are on the stream
```

Without `checkpoints`, or when the CLI doesn't support them, `flush()` writes to the backend as it does today.

#### Ordering

This follows the Go SDK's semantics:
- A state change made inside a table resolver is tied to that resolver call's scope. The scheduler records the scope in a thread-local while the resolver runs.
- On `flush()`, each group of keys becomes a checkpoint. It goes on the scheduler's queue once every resource those scopes produced before the flush is on it, including the relations those resources spawned.
  - Child resolvers run asynchronously on the depth pools, so the checkpoint waits for every child scope that was still open at flush time.
  - Unlike Go, the Python scheduler has no batcher: resources reach the stream in queue order, so nothing needs flushing first.
- A checkpoint also waits for earlier unsent checkpoints of the same keys. The state table has no `version` column, so the CLI relies on stream order.
- Changes made outside table resolvers wait for nothing. Flush them after the resources they summarize, for example in the post-sync hook. Checkpoints flushed by the post-sync hook are now sent before `Scheduler.sync` returns.
- Flushing in checkpoint mode while no sync is running raises an error instead of dropping the state.

#### Granularity

Two things set how much a checkpointing sync writes, and today plugin authors and users control only one of them:

- **How many keys change between commits.** That's the plugin's choice: one cursor per table, or one per parent, account or other partition. This PR adds `state_key(table, *, client_id=None, **partition)`, so plugins choose the granularity without each inventing a key format. Keys are deterministic, partition fields are sorted, and separators are escaped, so tables, clients and partitions never share a key. Each key is one state row, and a partition's checkpoint waits only for the resolver that set it. Finer keys mean a resumed sync re-fetches less, at the cost of more rows per commit.
- **How often the CLI commits.** The CLI merges checkpoints and commits at most every 30 seconds (`checkpointInterval` in `cli/cmd/sync_v3_checkpoints.go`). Each commit flushes every destination, then writes every key changed since the last commit to the state backend. The interval is hard-coded.

We'd like the commit interval to be configurable per sync, for example in the source spec next to `backend_options`, including an option to commit only at the end. The right value depends on the destination and the sync, not on the plugin:
- **Destination batches:** each flush ends the destination's batch, so a 30-second interval caps batch size by time. For S3 it also writes a new object per table every 30 seconds.
- **State backend load:** a sync with per-partition keys writes up to one row per partition per commit to a relational state backend.
- **Sync length:** for a sync that runs many hours against rate-limited APIs, committing every few minutes loses little on failure and writes far less. A short sync gains nothing from mid-sync commits.

Keeping the plugin-side flush cheap matters too, and it is: `flush()` in checkpoint mode only puts a message on the sync stream, and nothing touches a database until the CLI commits.

#### Other changes

- **State client:** now thread-safe. It clears changed keys after a successful flush (previously every flush rewrote every key changed since the start), and it keeps them after a failed one so a later flush retries them.
- **Backend options:** `BackendOptions` gains `supports_checkpoints`. The server now builds `BackendOptions` from the request instead of passing the protobuf message through.
- **Server guard:** the server refuses to send a checkpoint when the CLI didn't ask for them.
- **Key helper:** `state_key` is exported from `cloudquery.sdk.stateclient`.

#### Depends on

The `plugin-pb-python` bindings with the new messages (cloudquery/plugin-pb-python#250), which aren't released yet (release PR cloudquery/plugin-pb-python#225). `pyproject.toml` requires `>=0.0.55`, but `uv.lock` still needs updating after that release, so CI will fail until then. That's why this is a draft.

#### Open questions

1. **Failed relations.** Like Go, a parent's checkpoint still goes out when one of its child resolvers fails. For a parent cursor such as `updated_since`, that can skip the failed parent's children for good. Would you take an opt-in to hold back a scope's checkpoints when anything in its subtree errored?
2. **`version` column.** Should the Python state table get the `version` column, to match the Go client?
3. **Python destinations.** Python destination plugins don't implement `Write.MessageFlush` yet. I left that for a separate PR, since the CLI only enables checkpoints when every destination supports flushing.
4. **Full syncs.** Checkpoints only help tables that keep cursors. Resuming long full (non-incremental) syncs, and the interaction with `delete-stale`, is a bigger question. I'd like to raise it in a separate issue.

#### Testing

- New tests in `tests/scheduler/checkpoints.py`, `tests/serve/checkpoints.py` and `tests/stateclient/keys.py`. They cover:
  - parent checkpoints following all child resources of the parents they cover, with randomized child latency
  - child checkpoints
  - per-key order under concurrent flushes
  - post-sync hook checkpoints
  - backend writes without checkpoints
  - failed-flush retry
  - the builder and server gating
  - `state_key` ordering, escaping and uniqueness
- Removing either the wait on child scopes or the per-key ordering makes the parent-checkpoint test fail.
- The full suite passes locally against `plugin-pb-python` main, except `test_plugin_package`, which builds a Docker image and fails the same way locally on `main`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge Add to automerge PRs once requirements are met

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants