3333 FileBasedStreamConfig ,
3434 ValidationPolicy ,
3535)
36+ from airbyte_cdk .sources .file_based .config .validate_config_transfer_modes import (
37+ include_identities_stream ,
38+ preserve_directory_structure ,
39+ use_file_transfer ,
40+ use_permissions_transfer ,
41+ )
3642from airbyte_cdk .sources .file_based .discovery_policy import (
3743 AbstractDiscoveryPolicy ,
3844 DefaultDiscoveryPolicy ,
4955 DEFAULT_SCHEMA_VALIDATION_POLICIES ,
5056 AbstractSchemaValidationPolicy ,
5157)
52- from airbyte_cdk .sources .file_based .stream import AbstractFileBasedStream , DefaultFileBasedStream
58+ from airbyte_cdk .sources .file_based .stream import (
59+ AbstractFileBasedStream ,
60+ DefaultFileBasedStream ,
61+ FileIdentitiesStream ,
62+ PermissionsFileBasedStream ,
63+ )
5364from airbyte_cdk .sources .file_based .stream .concurrent .adapters import FileBasedStreamFacade
5465from airbyte_cdk .sources .file_based .stream .concurrent .cursor import (
5566 AbstractConcurrentFileBasedCursor ,
6677DEFAULT_CONCURRENCY = 100
6778MAX_CONCURRENCY = 100
6879INITIAL_N_PARTITIONS = MAX_CONCURRENCY // 2
80+ IDENTITIES_STREAM = "identities"
6981
7082
7183class FileBasedSource (ConcurrentSourceAdapter , ABC ):
@@ -157,13 +169,20 @@ def check_connection(
157169 errors = []
158170 tracebacks = []
159171 for stream in streams :
172+ if isinstance (stream , FileIdentitiesStream ):
173+ identity = next (iter (stream .load_identity_groups ()))
174+ if not identity :
175+ errors .append (
176+ "Unable to get identities for current configuration, please check your credentials"
177+ )
178+ continue
160179 if not isinstance (stream , AbstractFileBasedStream ):
161180 raise ValueError (f"Stream { stream } is not a file-based stream." )
162181 try :
163182 parsed_config = self ._get_parsed_config (config )
164183 availability_method = (
165184 stream .availability_strategy .check_availability
166- if self . _use_file_transfer (parsed_config )
185+ if use_file_transfer ( parsed_config ) or use_permissions_transfer (parsed_config )
167186 else stream .availability_strategy .check_availability_and_parsability
168187 )
169188 (
@@ -239,7 +258,7 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]:
239258 message_repository = self .message_repository ,
240259 )
241260 stream = FileBasedStreamFacade .create_from_stream (
242- stream = self ._make_default_stream (
261+ stream = self ._make_file_based_stream (
243262 stream_config = stream_config ,
244263 cursor = cursor ,
245264 parsed_config = parsed_config ,
@@ -270,7 +289,7 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]:
270289 CursorField (DefaultFileBasedStream .ab_last_mod_col ),
271290 )
272291 stream = FileBasedStreamFacade .create_from_stream (
273- stream = self ._make_default_stream (
292+ stream = self ._make_file_based_stream (
274293 stream_config = stream_config ,
275294 cursor = cursor ,
276295 parsed_config = parsed_config ,
@@ -282,13 +301,17 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]:
282301 )
283302 else :
284303 cursor = self .cursor_cls (stream_config )
285- stream = self ._make_default_stream (
304+ stream = self ._make_file_based_stream (
286305 stream_config = stream_config ,
287306 cursor = cursor ,
288307 parsed_config = parsed_config ,
289308 )
290309
291310 streams .append (stream )
311+
312+ if include_identities_stream (parsed_config ):
313+ identities_stream = self ._make_identities_stream ()
314+ streams .append (identities_stream )
292315 return streams
293316
294317 except ValidationError as exc :
@@ -310,8 +333,48 @@ def _make_default_stream(
310333 validation_policy = self ._validate_and_get_validation_policy (stream_config ),
311334 errors_collector = self .errors_collector ,
312335 cursor = cursor ,
313- use_file_transfer = self ._use_file_transfer (parsed_config ),
314- preserve_directory_structure = self ._preserve_directory_structure (parsed_config ),
336+ use_file_transfer = use_file_transfer (parsed_config ),
337+ preserve_directory_structure = preserve_directory_structure (parsed_config ),
338+ )
339+
340+ def _make_permissions_stream (
341+ self , stream_config : FileBasedStreamConfig , cursor : Optional [AbstractFileBasedCursor ]
342+ ) -> AbstractFileBasedStream :
343+ return PermissionsFileBasedStream (
344+ config = stream_config ,
345+ catalog_schema = self .stream_schemas .get (stream_config .name ),
346+ stream_reader = self .stream_reader ,
347+ availability_strategy = self .availability_strategy ,
348+ discovery_policy = self .discovery_policy ,
349+ parsers = self .parsers ,
350+ validation_policy = self ._validate_and_get_validation_policy (stream_config ),
351+ errors_collector = self .errors_collector ,
352+ cursor = cursor ,
353+ )
354+
355+ def _make_file_based_stream (
356+ self ,
357+ stream_config : FileBasedStreamConfig ,
358+ cursor : Optional [AbstractFileBasedCursor ],
359+ parsed_config : AbstractFileBasedSpec ,
360+ ) -> AbstractFileBasedStream :
361+ """
362+ Creates different streams depending on the type of the transfer mode selected
363+ """
364+ if use_permissions_transfer (parsed_config ):
365+ return self ._make_permissions_stream (stream_config , cursor )
366+ # we should have a stream for File transfer mode to decouple from DefaultFileBasedStream
367+ else :
368+ return self ._make_default_stream (stream_config , cursor , parsed_config )
369+
370+ def _make_identities_stream (
371+ self ,
372+ ) -> Stream :
373+ return FileIdentitiesStream (
374+ catalog_schema = self .stream_schemas .get (FileIdentitiesStream .IDENTITIES_STREAM_NAME ),
375+ stream_reader = self .stream_reader ,
376+ discovery_policy = self .discovery_policy ,
377+ errors_collector = self .errors_collector ,
315378 )
316379
317380 def _get_stream_from_catalog (
@@ -378,33 +441,3 @@ def _validate_input_schema(self, stream_config: FileBasedStreamConfig) -> None:
378441 "`input_schema` and `schemaless` options cannot both be set" ,
379442 model = FileBasedStreamConfig ,
380443 )
381-
382- @staticmethod
383- def _use_file_transfer (parsed_config : AbstractFileBasedSpec ) -> bool :
384- use_file_transfer = (
385- hasattr (parsed_config .delivery_method , "delivery_type" )
386- and parsed_config .delivery_method .delivery_type == "use_file_transfer"
387- )
388- return use_file_transfer
389-
390- @staticmethod
391- def _preserve_directory_structure (parsed_config : AbstractFileBasedSpec ) -> bool :
392- """
393- Determines whether to preserve directory structure during file transfer.
394-
395- When enabled, files maintain their subdirectory paths in the destination.
396- When disabled, files are flattened to the root of the destination.
397-
398- Args:
399- parsed_config: The parsed configuration containing delivery method settings
400-
401- Returns:
402- True if directory structure should be preserved (default), False otherwise
403- """
404- if (
405- FileBasedSource ._use_file_transfer (parsed_config )
406- and hasattr (parsed_config .delivery_method , "preserve_directory_structure" )
407- and parsed_config .delivery_method .preserve_directory_structure is not None
408- ):
409- return parsed_config .delivery_method .preserve_directory_structure
410- return True
0 commit comments