Skip to content

Components nested under a custom component cannot receive required positional-or-keyword factory args #1186

Closed

Description

@bazarnov

Component

Low-code component factory: ModelToComponentFactory._create_nested_component

Description

A component nested directly under a custom component (any Custom* type with class_name) is built by _create_nested_component. It forwards only the keyword-only args of the child's factory, taken from the parent's kwargs or from the child's $parameters (airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py:2045-2058). A factory whose required arg is not keyword-only, or that only passes its kwargs on to its own children, cannot get that arg there. The build fails at stream creation, so check, discover and read all fail.

Line numbers are at main (0ca75ef4). #500 does not move them.

Two cases remain after #500 (#422):

  1. SelectiveAuthenticator with a session branch under a custom component, for example CustomRequester.authenticator. create_selective_authenticator (:3459-3472) has no keyword-only args, so nothing is forwarded to it, and it has nothing to pass on to create_session_token_authenticator (name, :1212-1213) or create_legacy_session_token_authenticator (url_base, :3475-3477). The error hint is wrong: it asks for SelectiveAuthenticator.$parameters.name (or .url_base), but that is filtered out too, because it is not a keyword-only arg of create_selective_authenticator.
  2. CursorPagination or OffsetIncrement under a custom component, for example a custom partition router field typed PaginationStrategy. create_cursor_pagination (:1831-1833) and create_offset_increment (:3126-3133) require a positional-or-keyword decoder. $parameters cannot hold a Decoder object, so there is no workaround. The raw TypeError also bypasses _extract_missing_parameters (:2020-2025), which only matches "keyword-only", so the user gets no hint.

create_legacy_to_per_partition_state_migration (:1170-1175, required positional-or-keyword declarative_stream) follows the same pattern, but I found no parent that would nest it under a custom component.

Repro

Run from the CDK repo root, which puts unit_tests.sources.declarative.parsers.testing_components on the path:

from airbyte_cdk.sources.declarative.models import CustomPartitionRouter, CustomRequester
from airbyte_cdk.sources.declarative.parsers.model_to_component_factory import ModelToComponentFactory

TESTING = "unit_tests.sources.declarative.parsers.testing_components"
SESSION = {
    "type": "SessionTokenAuthenticator",
    "login_requester": {"type": "HttpRequester", "url_base": "https://api.test.com", "path": "/login", "http_method": "POST"},
    "session_token_path": ["token"],
    "request_authentication": {"type": "ApiKey", "inject_into": {"type": "RequestOption", "inject_into": "header", "field_name": "X-Session"}},
}

# Case 1: SelectiveAuthenticator{SessionTokenAuthenticator} under a CustomRequester
ModelToComponentFactory().create_component(
    model_type=CustomRequester,
    component_definition={
        "type": "CustomRequester",
        "class_name": f"{TESTING}.TestingRequester",
        "url_base": "https://api.test.com",
        "path": "/items",
        "http_method": "GET",
        "authenticator": {
            "type": "SelectiveAuthenticator",
            "authenticator_selection_path": ["auth_type"],
            "authenticators": {"session": SESSION},
            "$parameters": {"name": "items"},  # the suggested fix, which is ignored
        },
    },
    config={"auth_type": "session"},
    name="items",
)

# Case 2: CursorPagination under a custom partition router
ModelToComponentFactory().create_component(
    model_type=CustomPartitionRouter,
    component_definition={
        "type": "CustomPartitionRouter",
        "class_name": f"{TESTING}.TestingCustomSubstreamPartitionRouter",
        "custom_field": "x",
        "custom_pagination_strategy": {"type": "CursorPagination", "cursor_value": "{{ response.next }}"},
        "parent_stream_configs": [],
    },
    config={},
    stream_name="items",
)

Output with #500 applied:

Case 1: ValueError: Error creating component 'SelectiveAuthenticator' with parent custom component unit_tests.sources.declarative.parsers.testing_components.TestingRequester: Please provide SelectiveAuthenticator.$parameters.name
        (same with and without $parameters.name; a LegacySessionTokenAuthenticator branch asks for $parameters.url_base, same result)
Case 2: TypeError: Error creating component 'CursorPagination' with parent custom component unit_tests.sources.declarative.parsers.testing_components.TestingCustomSubstreamPartitionRouter: ModelToComponentFactory.create_cursor_pagination() missing 1 required positional argument: 'decoder'
        (OffsetIncrement: same error from create_offset_increment(); PageIncrement, which has no required decoder, gets past the pagination strategy, and the snippet then stops on the empty parent_stream_configs)

Fleet impact

None found. I scanned 535 manifest.yaml files in the monorepo with $refs resolved. No custom component has a direct CursorPagination, OffsetIncrement, LegacyToPerPartitionStateMigration or session-bearing SelectiveAuthenticator child. 5 connectors (source-asana, source-hubspot, source-linkedin-ads, source-mixpanel, source-slack) put a SelectiveAuthenticator under a CustomRequester, but only with Bearer, OAuth, ApiKey or Basic branches, so they are not affected. Builder and Cloud manifests were not scanned.

Fix options

  1. Case 1: add keyword-only pass-through params to create_selective_authenticator (*, name: Optional[str] = None, url_base: Optional[str] = None, decoder: Optional[Decoder] = None) and forward only the non-None ones to the nested authenticators. _create_nested_component then fills them from the parent's kwargs or from SelectiveAuthenticator.$parameters, and the existing hint becomes correct.
  2. Case 2: default decoder in create_cursor_pagination and create_offset_increment to JsonDecoder, the fallback create_default_paginator already uses (:2453-2459).
  3. Not recommended: widen the kwonlyargs filter in _create_nested_component to positional-or-keyword args. It would start forwarding args such as create_http_requester's decoder to every component nested under a custom parent, which changes behavior across the fleet.

Precedent

#422, fixed by #500. There, the same filter dropped the positional name of create_session_token_authenticator. #500 made name keyword-only and made create_selective_authenticator forward the requester kwargs to its nested authenticators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    communityPRs and issues from community contributors

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions