Repository navigation
feat(declarative): add SyncTokenCursor for server-issued sync tokens - #1234
devin-ai-integration[bot] wants to merge 7 commits into
Conversation
Co-Authored-By: bot_apk <apk@cognition.ai>
Co-Authored-By: bot_apk <apk@cognition.ai>
Co-Authored-By: bot_apk <apk@cognition.ai>
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
👋 Greetings, Airbyte Team Member!Here are some helpful tips and reminders for your convenience. 💡 Show Tips and TricksTesting This CDK VersionYou can test this version of the CDK using the following: # Run the CLI from this branch:
uvx 'git+https://github.com/airbytehq/airbyte-python-cdk.git@devin/1792189821-opaque-token-cursor#egg=airbyte-python-cdk[dev]' --help
# Update a connector to use the CDK from this branch ref:
cd airbyte-integrations/connectors/source-example
poe use-cdk-branch devin/1792189821-opaque-token-cursorPR Slash CommandsAirbyte Maintainers can execute the following slash commands on your PR:
|
Co-Authored-By: bot_apk <apk@cognition.ai>
Review: fix before mergeReviewed at Existing connectors are safe, and this is not a breaking change.
Four things need fixing before merge:
Five P2 items and four P3 items follow. At the end there is a patch for findings 1, 2, 5 and 6, plus 34 ready-to-use tests. With both applied, 614 tests pass. P1: must fix1. Streaming decoders break token capture in production; Builder test reads passWhere: Problem:
Reproduced at head. Without the cursor, every decoder below reads correctly in both modes.
The CDK already rejects the same hazard for Fix:
2.
|
|
👍 On it. I'm applying findings 1–12 on this branch as new commits on top of a merge of
No change for 13. After that I'll merge the updated branch into #1235. For finding 4, I'll change |
Co-Authored-By: bot_apk <apk@cognition.ai>
Co-Authored-By: bot_apk <apk@cognition.ai>
|
☑️ Resolved in 8ee8478 and c51857f, on top of a merge of
Locally, 771 tests pass across the incremental, factory, decoder, request-option, retriever and state-delegating suites; ruff and mypy are clean. The same changes are merged into #1235, now at |
Summary
Adds
SyncTokenCursor, a declarative incremental sync type for APIs that return an opaque, server-issued sync token in the response envelope (AshbysyncToken, Google/Todoist sync tokens, Box stream positions, Graph/Dataverse delta tokens). This PR adds capture and commit. It is the first of three stacked PRs: #1235 addsRESET_SYNC_TOKENrecovery and #1238 addsRequestPathtokens.Part of https://github.com/airbytehq/airbyte-internal-issues/issues/17213:
Close on merge: no. Close #17213 once its acceptance criteria are checked off after the whole stack ships.
Merge gate
Keep this PR in draft until all of these are done:
RequestPathtokens and first-request checks inSyncTokenCursor#1238, whose new checks assume this cursor is unreleased.request_parameterandbody_data.Changes
SyncTokenCursorwith five settings:token_path,token_option,completion_condition,initial_token,cursor_field. It is added toDeclarativeStream.incremental_sync.anyOf. The definition usesadditionalProperties: trueso that stream-level$parametersstill validate.SyncTokenCursor(incremental/sync_token_cursor.py):{"state_type": "sync_token", "token": "<token>"}.PerPartitionKeySerializer.completion_conditionand carries a valid string or int token. It is committed inclose_partition, so state is queued after every record of the traversal.SyncTokenRequestOptionsProvider: sends the stored token, otherwiseinitial_token, otherwise nothing, throughtoken_option. For partitioned streams it is wrapped inPerPartitionRequestOptionsProvider.SimpleRetriever: newpage_observerhook (PageCompleted(stream_slice, response, pagination_exhausted)). It is called after a page's records are yielded and after the next page token is computed. It is bound only forSyncTokenCursorand isNone(inert) for every other stream.DefaultStream: newsource_defined_incrementalflag. A token stream advertises[full_refresh, incremental],source_defined_cursor: true,is_resumable: true, anddefault_cursor_field: [](or[cursor_field]). It never advertisesnull.GroupingPartitionRouterAsyncRetrieverincremental_dependencyin either directiontokenpagination_reset.limitsCustomRetrieverwarningExample (Ashby):
Reproduction (before the change)
On
main(v7.37.0), a manifest whose stream usesincremental_sync: {type: SyncTokenCursor, ...}failsConcurrentDeclarativeSource(source_config=manifest)withjsonschema.exceptions.ValidationError: Validation against json schema defined in declarative_component_schema.yaml schema failed.incremental_synconly acceptsDatetimeBasedCursorandIncrementingCountCursor.Declarative-First Evaluation
This is a CDK feature, not a connector change.
DatetimeBasedCursorandIncrementingCountCursorboth derive state from record values, and no existing declarative component can capture a value from the response envelope and commit it only when the traversal completes. The new component is built-in and declarative, so adopters need no custom Python components.Test Coverage
unit_tests/sources/declarative/incremental/test_sync_token_cursor.py: unit tests for T-TOKEN, T-CAP, T-FOREIGN, T-SCHEMA, T-DISC and T-VALID, plus the cursor state rules.unit_tests/sources/declarative/incremental/test_sync_token_cursor_integration.py: end-to-end tests throughConcurrentDeclarativeSource.readwithHttpMocker, asserting exact request bodies. Covers T-BOOT, T-INC, T-NOECHO, T-EMPTY, T-FAIL, T-BUILDER, T-INIT, T-PAR (3 partitions), T-RESUME and T-INERT.main(schema validation error) and pass with this change.main: two date-only parsing tests in file-based, and a schema-test fixture race under-n auto.Breaking change
None. This is an additive, opt-in component. Existing cursors, streams and the retriever are unchanged when the cursor is not used:
page_observerisNone, andsource_defined_incrementaldefaults toFalse.Link to Devin session: https://app.devin.ai/sessions/94b41efe75db43e78eb5a4f8d5f58484
Open in Devin Desktop: https://app.devin.ai/desktop/session/94b41efe75db43e78eb5a4f8d5f58484?variant=devin