55import logging
66import traceback
77from dataclasses import InitVar , dataclass
8- from typing import Any , Dict , List , Mapping , Optional , Tuple
8+ from typing import Any , Dict , List , Mapping , Optional , Tuple , Union
99
1010from airbyte_cdk import AbstractSource
1111from airbyte_cdk .sources .declarative .checks .connection_checker import ConnectionChecker
12+ from airbyte_cdk .sources .declarative .models .declarative_component_schema import DeclarativeStream
1213from airbyte_cdk .sources .streams .http .availability_strategy import HttpAvailabilityStrategy
1314
1415
@@ -25,13 +26,17 @@ class DynamicStreamCheckConfig:
2526@dataclass
2627class CheckStream (ConnectionChecker ):
2728 """
28- Checks the connections by checking availability of one or many streams selected by the developer
29+ Checks the connection by checking the availability of one or more streams specified by the developer.
2930
3031 Attributes:
31- stream_name (List[str]): names of streams to check
32+ stream_names (List[Union[str, DeclarativeStream]]):
33+ Names of streams to check. Each item can be:
34+ - a string (referencing a stream in the manifest's streams block)
35+ - a dict (an inline DeclarativeStream definition from YAML)
36+ - a DeclarativeStream Pydantic model (from parsed manifest)
3237 """
3338
34- stream_names : List [str ]
39+ stream_names : List [Union [ str , DeclarativeStream ] ]
3540 parameters : InitVar [Mapping [str , Any ]]
3641 dynamic_streams_check_configs : Optional [List [DynamicStreamCheckConfig ]] = None
3742
@@ -49,37 +54,75 @@ def _log_error(self, logger: logging.Logger, action: str, error: Exception) -> T
4954 def check_connection (
5055 self , source : AbstractSource , logger : logging .Logger , config : Mapping [str , Any ]
5156 ) -> Tuple [bool , Any ]:
52- """Checks the connection to the source and its streams."""
57+ """
58+ Checks the connection to the source and its streams.
59+
60+ Handles both:
61+ - Referenced streams (by name)
62+ - Inline check-only streams (as dicts or DeclarativeStream models)
63+ """
5364 try :
5465 streams = source .streams (config = config )
55- if not streams :
56- return False , f"No streams to connect to from source { source } "
57- except Exception as error :
58- return self ._log_error (logger , "discovering streams" , error )
59-
60- stream_name_to_stream = {s .name : s for s in streams }
61- for stream_name in self .stream_names :
62- if stream_name not in stream_name_to_stream :
63- raise ValueError (
64- f"{ stream_name } is not part of the catalog. Expected one of { list (stream_name_to_stream .keys ())} ."
66+ stream_name_to_stream = {s .name : s for s in streams }
67+
68+ # Add inline check-only streams to the map
69+ for stream_def in self .stream_names :
70+ # Handle dicts (from YAML) and DeclarativeStream objects (from Pydantic)
71+ if isinstance (stream_def , dict ):
72+ if hasattr (source , "_instantiate_stream_from_dict" ):
73+ stream_obj = source ._instantiate_stream_from_dict (stream_def , config )
74+ stream_name_to_stream [stream_obj .name ] = stream_obj
75+ else :
76+ raise NotImplementedError (
77+ f"Source { type (source )} does not support inline stream definitions for check-only streams."
78+ )
79+ elif isinstance (stream_def , DeclarativeStream ):
80+ # Convert the Pydantic model to dict before passing to the factory
81+ if hasattr (source , "_instantiate_stream_from_dict" ):
82+ stream_obj = source ._instantiate_stream_from_dict (stream_def .dict (), config )
83+ stream_name_to_stream [stream_obj .name ] = stream_obj
84+ else :
85+ raise NotImplementedError (
86+ f"Source { type (source )} does not support inline stream definitions for check-only streams."
87+ )
88+ # Optionally: warn if stream_def is an unexpected type
89+ elif not isinstance (stream_def , str ):
90+ logger .warning (f"Unexpected stream definition type: { type (stream_def )} " )
91+
92+ # Now check availability
93+ for stream_def in self .stream_names :
94+ if isinstance (stream_def , dict ):
95+ stream_name = stream_def .get ("name" )
96+ elif hasattr (stream_def , "name" ): # DeclarativeStream object
97+ stream_name = stream_def .name
98+ else :
99+ stream_name = stream_def # string
100+
101+ if stream_name not in stream_name_to_stream :
102+ raise ValueError (
103+ f"{ stream_name } is not part of the catalog or check-only streams. Expected one of { list (stream_name_to_stream .keys ())} ."
104+ )
105+
106+ stream_availability , message = self ._check_stream_availability (
107+ stream_name_to_stream , stream_name , logger
65108 )
109+ if not stream_availability :
110+ return stream_availability , message
66111
67- stream_availability , message = self ._check_stream_availability (
68- stream_name_to_stream , stream_name , logger
112+ should_check_dynamic_streams = (
113+ hasattr (source , "resolved_manifest" )
114+ and hasattr (source , "dynamic_streams" )
115+ and self .dynamic_streams_check_configs
69116 )
70- if not stream_availability :
71- return stream_availability , message
72117
73- should_check_dynamic_streams = (
74- hasattr (source , "resolved_manifest" )
75- and hasattr (source , "dynamic_streams" )
76- and self .dynamic_streams_check_configs
77- )
78-
79- if should_check_dynamic_streams :
80- return self ._check_dynamic_streams_availability (source , stream_name_to_stream , logger )
118+ if should_check_dynamic_streams :
119+ return self ._check_dynamic_streams_availability (
120+ source , stream_name_to_stream , logger
121+ )
81122
82- return True , None
123+ return True , None
124+ except Exception as error :
125+ return self ._log_error (logger , "discovering streams" , error )
83126
84127 def _check_stream_availability (
85128 self , stream_name_to_stream : Dict [str , Any ], stream_name : str , logger : logging .Logger
0 commit comments