From 7b95a108ff9575dda1102aca2043560b8c131fec Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 19:18:48 +0000 Subject: [PATCH 1/5] fix: treat stream_count=0 as 'check all streams' in DynamicStreamCheckConfig Previously, DynamicStreamCheckConfig.stream_count defaulted to 0, which caused _check_generated_streams_availability to slice generated_streams[:0], resulting in zero streams being checked. This meant connectors using DynamicStreamCheckConfig without explicitly setting stream_count would silently skip all dynamic stream availability checks. Now, stream_count <= 0 means 'check all generated streams', which is the expected behavior when no limit is specified. Co-Authored-By: bot_apk --- .../declarative/checks/check_stream.py | 8 +++++-- .../declarative/checks/test_check_stream.py | 24 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/airbyte_cdk/sources/declarative/checks/check_stream.py b/airbyte_cdk/sources/declarative/checks/check_stream.py index aa3298b53..4d5472d43 100644 --- a/airbyte_cdk/sources/declarative/checks/check_stream.py +++ b/airbyte_cdk/sources/declarative/checks/check_stream.py @@ -164,8 +164,12 @@ def _check_generated_streams_availability( logger: logging.Logger, max_count: int, ) -> Tuple[bool, Any]: - """Checks availability of generated dynamic streams.""" - for declarative_stream in generated_streams[: min(max_count, len(generated_streams))]: + """Checks availability of generated dynamic streams. + + If max_count is 0 or negative, all generated streams are checked. + """ + streams_to_check = generated_streams if max_count <= 0 else generated_streams[:max_count] + for declarative_stream in streams_to_check: stream = stream_name_to_stream[declarative_stream["name"]] try: stream_is_available, reason = evaluate_availability(stream, logger) diff --git a/unit_tests/sources/declarative/checks/test_check_stream.py b/unit_tests/sources/declarative/checks/test_check_stream.py index 317abb6c9..a13f8d063 100644 --- a/unit_tests/sources/declarative/checks/test_check_stream.py +++ b/unit_tests/sources/declarative/checks/test_check_stream.py @@ -439,7 +439,7 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp False, 200, [], - 0, + 1, id="test_check_http_dynamic_stream_and_config_dynamic_stream", ), pytest.param( @@ -464,7 +464,7 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp False, 200, [], - 0, + 1, id="test_check_static_streams_and_http_dynamic_stream_and_config_dynamic_stream", ), pytest.param( @@ -487,6 +487,26 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp 1, id="test_stream_count_gt_generated_streams", ), + pytest.param( + { + "check": { + "type": "CheckStream", + "dynamic_streams_check_configs": [ + { + "type": "DynamicStreamCheckConfig", + "dynamic_stream_name": "http_dynamic_stream", + "stream_count": 0, + }, + ], + } + }, + Status.SUCCEEDED, + False, + 200, + [], + 1, + id="test_stream_count_zero_checks_all_streams", + ), pytest.param( {"check": {"type": "CheckStream", "stream_names": ["non_existent_stream"]}}, Status.FAILED, From 3ae13204270e20d63d3b4fc7b5b85058f8c1b933 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 11:19:22 +0000 Subject: [PATCH 2/5] test: add stream_count=0 failure case Co-Authored-By: gl_anatolii.yatsuk --- .../declarative/checks/test_check_stream.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/unit_tests/sources/declarative/checks/test_check_stream.py b/unit_tests/sources/declarative/checks/test_check_stream.py index a13f8d063..e1215fa7c 100644 --- a/unit_tests/sources/declarative/checks/test_check_stream.py +++ b/unit_tests/sources/declarative/checks/test_check_stream.py @@ -507,6 +507,26 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp 1, id="test_stream_count_zero_checks_all_streams", ), + pytest.param( + { + "check": { + "type": "CheckStream", + "dynamic_streams_check_configs": [ + { + "type": "DynamicStreamCheckConfig", + "dynamic_stream_name": "http_dynamic_stream", + "stream_count": 0, + }, + ], + } + }, + Status.FAILED, + False, + 404, + ["Not found. The requested resource was not found on the server."], + 0, + id="test_stream_count_zero_failed", + ), pytest.param( {"check": {"type": "CheckStream", "stream_names": ["non_existent_stream"]}}, Status.FAILED, From 932539d038ef1f773b60a4c6fea59f5136d981da Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:44:05 +0000 Subject: [PATCH 3/5] Revise stream_count semantics: None=check all, 0=check none Co-Authored-By: gl_anatolii.yatsuk --- .../declarative/checks/check_stream.py | 11 +-- .../declarative_component_schema.yaml | 4 +- .../models/declarative_component_schema.py | 5 +- .../parsers/model_to_component_factory.py | 2 +- .../declarative/checks/test_check_stream.py | 70 ++++++++++++++++++- 5 files changed, 80 insertions(+), 12 deletions(-) diff --git a/airbyte_cdk/sources/declarative/checks/check_stream.py b/airbyte_cdk/sources/declarative/checks/check_stream.py index 4d5472d43..d7314ead9 100644 --- a/airbyte_cdk/sources/declarative/checks/check_stream.py +++ b/airbyte_cdk/sources/declarative/checks/check_stream.py @@ -36,7 +36,7 @@ class DynamicStreamCheckConfig: and type enforcement.""" dynamic_stream_name: str - stream_count: int = 0 + stream_count: Optional[int] = None @dataclass @@ -162,13 +162,16 @@ def _check_generated_streams_availability( generated_streams: List[Dict[str, Any]], stream_name_to_stream: Dict[str, Union[Stream, AbstractStream]], logger: logging.Logger, - max_count: int, + max_count: Optional[int], ) -> Tuple[bool, Any]: """Checks availability of generated dynamic streams. - If max_count is 0 or negative, all generated streams are checked. + If `max_count` is `None`, all generated streams are checked. If `max_count` is 0, + no streams are checked. Otherwise, the first `max_count` streams are checked. """ - streams_to_check = generated_streams if max_count <= 0 else generated_streams[:max_count] + streams_to_check = ( + generated_streams if max_count is None else generated_streams[:max_count] + ) for declarative_stream in streams_to_check: stream = stream_name_to_stream[declarative_stream["name"]] try: diff --git a/airbyte_cdk/sources/declarative/declarative_component_schema.yaml b/airbyte_cdk/sources/declarative/declarative_component_schema.yaml index 66c126d94..c96f8fe88 100644 --- a/airbyte_cdk/sources/declarative/declarative_component_schema.yaml +++ b/airbyte_cdk/sources/declarative/declarative_component_schema.yaml @@ -358,9 +358,9 @@ definitions: type: string stream_count: title: Stream Count - description: The number of streams to attempt reading from during a check operation. If `stream_count` exceeds the total number of available streams, the minimum of the two values will be used. + description: The number of streams to attempt reading from during a check operation. If unset, all generated streams are checked. If set to 0, no streams are checked. If set to a positive value greater than the total number of available streams, all streams are checked. type: integer - default: 0 + minimum: 0 CheckDynamicStream: title: Dynamic Streams to Check description: (This component is experimental. Use at your own risk.) Defines the dynamic streams to try reading when running a check operation. diff --git a/airbyte_cdk/sources/declarative/models/declarative_component_schema.py b/airbyte_cdk/sources/declarative/models/declarative_component_schema.py index 1ce38fec0..ce23696f6 100644 --- a/airbyte_cdk/sources/declarative/models/declarative_component_schema.py +++ b/airbyte_cdk/sources/declarative/models/declarative_component_schema.py @@ -58,8 +58,9 @@ class DynamicStreamCheckConfig(BaseModel): ..., description="The dynamic stream name.", title="Dynamic Stream Name" ) stream_count: Optional[int] = Field( - 0, - description="The number of streams to attempt reading from during a check operation. If `stream_count` exceeds the total number of available streams, the minimum of the two values will be used.", + None, + description="The number of streams to attempt reading from during a check operation. If unset, all generated streams are checked. If set to 0, no streams are checked. If set to a positive value greater than the total number of available streams, all streams are checked.", + ge=0, title="Stream Count", ) diff --git a/airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py b/airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py index aefe01364..f0b984e8e 100644 --- a/airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +++ b/airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py @@ -1246,7 +1246,7 @@ def create_dynamic_stream_check_config( ) -> DynamicStreamCheckConfig: return DynamicStreamCheckConfig( dynamic_stream_name=model.dynamic_stream_name, - stream_count=model.stream_count or 0, + stream_count=model.stream_count, ) def create_check_stream( diff --git a/unit_tests/sources/declarative/checks/test_check_stream.py b/unit_tests/sources/declarative/checks/test_check_stream.py index e1215fa7c..3f34ac3e8 100644 --- a/unit_tests/sources/declarative/checks/test_check_stream.py +++ b/unit_tests/sources/declarative/checks/test_check_stream.py @@ -504,8 +504,8 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp False, 200, [], - 1, - id="test_stream_count_zero_checks_all_streams", + 0, + id="test_stream_count_zero_checks_no_streams", ), pytest.param( { @@ -520,12 +520,50 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp ], } }, + Status.SUCCEEDED, + False, + 404, + ["Not found. The requested resource was not found on the server."], + 0, + id="test_stream_count_zero_skips_failing_streams", + ), + pytest.param( + { + "check": { + "type": "CheckStream", + "dynamic_streams_check_configs": [ + { + "type": "DynamicStreamCheckConfig", + "dynamic_stream_name": "http_dynamic_stream", + }, + ], + } + }, + Status.SUCCEEDED, + False, + 200, + [], + 1, + id="test_stream_count_unset_checks_all_streams", + ), + pytest.param( + { + "check": { + "type": "CheckStream", + "dynamic_streams_check_configs": [ + { + "type": "DynamicStreamCheckConfig", + "dynamic_stream_name": "http_dynamic_stream", + }, + ], + } + }, Status.FAILED, False, 404, ["Not found. The requested resource was not found on the server."], 0, - id="test_stream_count_zero_failed", + id="test_stream_count_unset_failed", ), pytest.param( {"check": {"type": "CheckStream", "stream_names": ["non_existent_stream"]}}, @@ -722,6 +760,32 @@ def test_check_stream_missing_fields(): ) +def test_check_stream_negative_stream_count(): + """Test that a ValidationError is raised when stream_count is negative.""" + manifest = { + **deepcopy(_MANIFEST_WITHOUT_CHECK_COMPONENT), + **{ + "check": { + "type": "CheckStream", + "dynamic_streams_check_configs": [ + { + "type": "DynamicStreamCheckConfig", + "dynamic_stream_name": "http_dynamic_stream", + "stream_count": -1, + } + ], + } + }, + } + with pytest.raises(ValidationError): + ConcurrentDeclarativeSource( + source_config=manifest, + config=_CONFIG, + catalog=None, + state=None, + ) + + def test_check_stream_only_type_provided(): manifest = {**deepcopy(_MANIFEST_WITHOUT_CHECK_COMPONENT), **{"check": {"type": "CheckStream"}}} source = ConcurrentDeclarativeSource( From 4a0801608f5220e37025c107b7e953ee9e7f1d96 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:47:33 +0000 Subject: [PATCH 4/5] Require stream_count >= 1; drop zero-means-skip behavior Co-Authored-By: gl_anatolii.yatsuk --- .../declarative/checks/check_stream.py | 4 +- .../declarative_component_schema.yaml | 4 +- .../models/declarative_component_schema.py | 4 +- .../declarative/checks/test_check_stream.py | 50 +++---------------- 4 files changed, 13 insertions(+), 49 deletions(-) diff --git a/airbyte_cdk/sources/declarative/checks/check_stream.py b/airbyte_cdk/sources/declarative/checks/check_stream.py index d7314ead9..6b9fc9039 100644 --- a/airbyte_cdk/sources/declarative/checks/check_stream.py +++ b/airbyte_cdk/sources/declarative/checks/check_stream.py @@ -166,8 +166,8 @@ def _check_generated_streams_availability( ) -> Tuple[bool, Any]: """Checks availability of generated dynamic streams. - If `max_count` is `None`, all generated streams are checked. If `max_count` is 0, - no streams are checked. Otherwise, the first `max_count` streams are checked. + If `max_count` is `None`, all generated streams are checked. Otherwise, the + first `max_count` streams are checked (capped at the number of available streams). """ streams_to_check = ( generated_streams if max_count is None else generated_streams[:max_count] diff --git a/airbyte_cdk/sources/declarative/declarative_component_schema.yaml b/airbyte_cdk/sources/declarative/declarative_component_schema.yaml index c96f8fe88..dda723c00 100644 --- a/airbyte_cdk/sources/declarative/declarative_component_schema.yaml +++ b/airbyte_cdk/sources/declarative/declarative_component_schema.yaml @@ -358,9 +358,9 @@ definitions: type: string stream_count: title: Stream Count - description: The number of streams to attempt reading from during a check operation. If unset, all generated streams are checked. If set to 0, no streams are checked. If set to a positive value greater than the total number of available streams, all streams are checked. + description: The number of streams to attempt reading from during a check operation. If unset, all generated streams are checked. Must be a positive integer; if it exceeds the total number of available streams, all streams are checked. type: integer - minimum: 0 + minimum: 1 CheckDynamicStream: title: Dynamic Streams to Check description: (This component is experimental. Use at your own risk.) Defines the dynamic streams to try reading when running a check operation. diff --git a/airbyte_cdk/sources/declarative/models/declarative_component_schema.py b/airbyte_cdk/sources/declarative/models/declarative_component_schema.py index ce23696f6..2fe33e672 100644 --- a/airbyte_cdk/sources/declarative/models/declarative_component_schema.py +++ b/airbyte_cdk/sources/declarative/models/declarative_component_schema.py @@ -59,8 +59,8 @@ class DynamicStreamCheckConfig(BaseModel): ) stream_count: Optional[int] = Field( None, - description="The number of streams to attempt reading from during a check operation. If unset, all generated streams are checked. If set to 0, no streams are checked. If set to a positive value greater than the total number of available streams, all streams are checked.", - ge=0, + description="The number of streams to attempt reading from during a check operation. If unset, all generated streams are checked. Must be a positive integer; if it exceeds the total number of available streams, all streams are checked.", + ge=1, title="Stream Count", ) diff --git a/unit_tests/sources/declarative/checks/test_check_stream.py b/unit_tests/sources/declarative/checks/test_check_stream.py index 3f34ac3e8..5821ae02e 100644 --- a/unit_tests/sources/declarative/checks/test_check_stream.py +++ b/unit_tests/sources/declarative/checks/test_check_stream.py @@ -487,46 +487,6 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp 1, id="test_stream_count_gt_generated_streams", ), - pytest.param( - { - "check": { - "type": "CheckStream", - "dynamic_streams_check_configs": [ - { - "type": "DynamicStreamCheckConfig", - "dynamic_stream_name": "http_dynamic_stream", - "stream_count": 0, - }, - ], - } - }, - Status.SUCCEEDED, - False, - 200, - [], - 0, - id="test_stream_count_zero_checks_no_streams", - ), - pytest.param( - { - "check": { - "type": "CheckStream", - "dynamic_streams_check_configs": [ - { - "type": "DynamicStreamCheckConfig", - "dynamic_stream_name": "http_dynamic_stream", - "stream_count": 0, - }, - ], - } - }, - Status.SUCCEEDED, - False, - 404, - ["Not found. The requested resource was not found on the server."], - 0, - id="test_stream_count_zero_skips_failing_streams", - ), pytest.param( { "check": { @@ -760,8 +720,12 @@ def test_check_stream_missing_fields(): ) -def test_check_stream_negative_stream_count(): - """Test that a ValidationError is raised when stream_count is negative.""" +@pytest.mark.parametrize( + "stream_count", + [pytest.param(0, id="zero"), pytest.param(-1, id="negative")], +) +def test_check_stream_non_positive_stream_count(stream_count: int) -> None: + """A ValidationError is raised when stream_count is less than 1.""" manifest = { **deepcopy(_MANIFEST_WITHOUT_CHECK_COMPONENT), **{ @@ -771,7 +735,7 @@ def test_check_stream_negative_stream_count(): { "type": "DynamicStreamCheckConfig", "dynamic_stream_name": "http_dynamic_stream", - "stream_count": -1, + "stream_count": stream_count, } ], } From 144386ef62f04eb71e38e40a6774bccf6ad0b6f4 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:08:04 +0000 Subject: [PATCH 5/5] ruff format Co-Authored-By: gl_anatolii.yatsuk --- airbyte_cdk/sources/declarative/checks/check_stream.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/airbyte_cdk/sources/declarative/checks/check_stream.py b/airbyte_cdk/sources/declarative/checks/check_stream.py index 6b9fc9039..540813a49 100644 --- a/airbyte_cdk/sources/declarative/checks/check_stream.py +++ b/airbyte_cdk/sources/declarative/checks/check_stream.py @@ -169,9 +169,7 @@ def _check_generated_streams_availability( If `max_count` is `None`, all generated streams are checked. Otherwise, the first `max_count` streams are checked (capped at the number of available streams). """ - streams_to_check = ( - generated_streams if max_count is None else generated_streams[:max_count] - ) + streams_to_check = generated_streams if max_count is None else generated_streams[:max_count] for declarative_stream in streams_to_check: stream = stream_name_to_stream[declarative_stream["name"]] try: