Skip to content

feat(declarative): allow DefaultErrorHandler.max_retries to be interpolated from config#1017

Open
devin-ai-integration[bot] wants to merge 1 commit intomainfrom
devin/1778162508-default-error-handler-interpolatable-max-retries
Open

feat(declarative): allow DefaultErrorHandler.max_retries to be interpolated from config#1017
devin-ai-integration[bot] wants to merge 1 commit intomainfrom
devin/1778162508-default-error-handler-interpolatable-max-retries

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

Summary

Widens DefaultErrorHandler.max_retries to accept a Jinja-interpolatable string in addition to a literal integer, so connector authors can drive retry counts from the connector config — for example:

error_handler:
  type: DefaultErrorHandler
  max_retries: "{{ config['max_retries_on_throttle'] }}"

Use case: connectors whose users share a single rate-limit budget across many connections (e.g. SP-API's per-(application, selling_partner_account) token bucket) want a "fail-fast" knob to stop burning the shared quota on retry chains. Today this requires a custom Python error handler subclass; with this change a plain manifest field works.

The string variant is resolved once in __post_init__ against the connector config and replaced in place with the resolved int, so consumers of error_handler.max_retries (notably HttpClient._max_retries, which does arithmetic on it) keep getting an int and don't need to change.

Files changed

  • declarative_component_schema.yamlmax_retries becomes anyOf: [integer, string] with interpolation_context: [config]. Mirrors the existing pattern used by ConcurrencyLevel.default_concurrency, CursorPagination.page_size, Rate.limit, etc.
  • models/declarative_component_schema.py — generated Pydantic model widened to Optional[Union[int, str]]. Hand-edited rather than re-run through bin/generate_component_manifest_files.py (Dagger-based codegen) — single-line change, but worth a sanity check that re-running codegen produces the same diff.
  • requesters/error_handlers/default_error_handler.py:
    • widen the dataclass field to Optional[Union[int, str]] (with a # type: ignore[assignment] and inline comment explaining the LSP widening relative to the abstract base class — post-__post_init__ the value is always int, matching the parent contract)
    • resolve string variants via InterpolatedString(...).eval(config=...) in __post_init__
    • remove two unused private fields (_max_retries, _max_time) that were declared on the dataclass but never read anywhere in the codebase
  • unit_tests/.../test_default_error_handler.py — 8 new tests covering: default unspecified, literal int, zero, config-interpolated, Jinja .get(..., default) fallback, interpolation resolving to zero, interpolation resolving to a numeric string, and the explicit ValueError path when the interpolation doesn't resolve to an int.

Backward-compatible: existing manifests passing a literal integer go through an unchanged code path.

Review & Testing Checklist for Human

  • Confirm the hand-edit of models/declarative_component_schema.py matches what the Dagger codegen would produce. I edited it directly (one field, one description, examples list) instead of re-running bin/generate_component_manifest_files.py. Easiest verification: run the codegen locally and confirm git diff is empty on that file.
  • Sanity-check the LSP widening. The dataclass field max_retries: Optional[Union[int, str]] is wider than the abstract base class ErrorHandler.max_retries (Optional[int]). I added # type: ignore[assignment] because the __post_init__ resolution makes the post-construction invariant match the parent contract — confirm this is the preferred handling vs. e.g. widening ErrorHandler.max_retries itself (which I avoided to keep the abstract contract clean for non-declarative implementers like HttpStatusErrorHandler).
  • Verify the Connector Builder UI still renders the max_retries form field correctly after the schema change to anyOf: [integer, string]. Other fields with this same shape (e.g. default_concurrency, page_size) already work in the Builder, so this should be a non-issue, but worth a 30-second visual check.
  • Confirm the removal of the unused _max_retries / _max_time private dataclass fields is safe. I grep'd for both and found no references, but if any external consumer constructed DefaultErrorHandler and read those, that's a (very minor) break.
  • Run the targeted tests: poetry run pytest unit_tests/sources/declarative/requesters/error_handlers/test_default_error_handler.py (34 pass locally, including the 8 new ones).

Notes

  • I deliberately resolve max_retries once at construction time rather than lazily on each property access (which is the pattern ConcurrencyLevel uses). HttpClient reads error_handler.max_retries on every retried request, so caching is the right call here, and the connector config is sync-static. Happy to revert to lazy resolution if reviewers prefer consistency with ConcurrencyLevel.
  • This PR is the CDK-side enabler for a source-amazon-seller-partner feature request (oncall #11837). Without this PR, the same use case can be solved connector-side with a small custom error handler subclass; with this PR, it's a one-line manifest change.
  • No breakingChanges entry needed — strictly additive at the wire format and ABI level.

Link to Devin session: https://app.devin.ai/sessions/9e16ef322dcd4c4fb40299b57494bd37

…olated from config

Co-Authored-By: bot_apk <apk@cognition.ai>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

💡 Show Tips and Tricks

Testing This CDK Version

You 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/1778162508-default-error-handler-interpolatable-max-retries#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/1778162508-default-error-handler-interpolatable-max-retries

PR Slash Commands

Airbyte Maintainers can execute the following slash commands on your PR:

  • /autofix - Fixes most formatting and linting issues
  • /poetry-lock - Updates poetry.lock file
  • /test - Runs connector tests with the updated CDK
  • /prerelease - Triggers a prerelease publish with default arguments
  • /poe build - Regenerate git-committed build artifacts, such as the pydantic models which are generated from the manifest JSON schema in YAML.
  • /poe <command> - Runs any poe command in the CDK environment
📚 Show Repo Guidance

Helpful Resources

📝 Edit this welcome message.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

PyTest Results (Fast)

4 051 tests  +8   4 040 ✅ +8   7m 17s ⏱️ -31s
    1 suites ±0      11 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit 56866f8. ± Comparison against base commit ccc185f.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

PyTest Results (Full)

4 054 tests  +8   4 042 ✅ +8   10m 54s ⏱️ -7s
    1 suites ±0      12 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit 56866f8. ± Comparison against base commit ccc185f.

@darynaishchenko Daryna Ishchenko (darynaishchenko) marked this pull request as ready for review May 7, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant