2222import traceback
2323from collections .abc import Mapping
2424from pathlib import Path
25- from typing import Any , cast
25+ from typing import Any , Dict , cast
2626
2727import orjson
28+ import yaml
2829
2930from airbyte_cdk .entrypoint import AirbyteEntrypoint , launch
3031from airbyte_cdk .models import (
@@ -211,12 +212,59 @@ def _parse_inputs_into_config_catalog_state(
211212 ConfiguredAirbyteCatalog | None ,
212213 list [AirbyteStateMessage ],
213214]:
214- parsed_args = AirbyteEntrypoint .parse_args (args )
215- config = (
216- ConcurrentDeclarativeSource .read_config (parsed_args .config )
217- if hasattr (parsed_args , "config" )
218- else None
219- )
215+ # Extract the --manifest-path argument if present
216+ manifest_path = None
217+ modified_args = []
218+ i = 0
219+ while i < len (args ):
220+ if args [i ] == "--manifest-path" :
221+ if i + 1 < len (args ):
222+ manifest_path = args [i + 1 ]
223+ i += 2 # Skip both the option and its value
224+ else :
225+ raise ValueError ("--manifest-path option requires a path value" )
226+ else :
227+ modified_args .append (args [i ])
228+ i += 1
229+
230+ # Parse the modified arguments
231+ parsed_args = AirbyteEntrypoint .parse_args (modified_args )
232+
233+ # For spec command, we don't need config or manifest
234+ is_spec_command = len (modified_args ) > 0 and modified_args [0 ] == "spec"
235+
236+ # Read config from file if provided
237+ config = None
238+ if hasattr (parsed_args , "config" ):
239+ config = ConcurrentDeclarativeSource .read_config (parsed_args .config )
240+
241+ # If manifest_path is provided, read the manifest and inject it into the config
242+ if manifest_path :
243+ try :
244+ with open (manifest_path , "r" ) as manifest_file :
245+ manifest_content = yaml .safe_load (manifest_file )
246+
247+ # For commands other than spec, a config must be provided
248+ if not is_spec_command and config is None :
249+ raise ValueError (
250+ "When using --manifest-path with commands other than 'spec', "
251+ "a valid --config must also be provided."
252+ )
253+
254+ # For spec command, we can create an empty config if needed
255+ if config is None :
256+ config = {}
257+
258+ # Convert to a mutable dictionary if it's not already
259+ if not isinstance (config , dict ):
260+ config = dict (config )
261+
262+ # Inject the manifest into the config
263+ config ["__injected_declarative_manifest" ] = manifest_content
264+ except Exception as error :
265+ raise ValueError (f"Failed to load manifest file from { manifest_path } : { error } " )
266+
267+ # Read catalog and state if provided
220268 catalog = (
221269 ConcurrentDeclarativeSource .read_catalog (parsed_args .catalog )
222270 if hasattr (parsed_args , "catalog" )
@@ -233,4 +281,71 @@ def _parse_inputs_into_config_catalog_state(
233281
234282def run () -> None :
235283 args : list [str ] = sys .argv [1 :]
236- handle_command (args )
284+
285+ # First check if this is a local manifest command - if so, proceed with the standard flow
286+ if _is_local_manifest_command (args ):
287+ handle_command (args )
288+ return
289+
290+ # Check for --manifest-path argument
291+ try :
292+ manifest_path_index = args .index ("--manifest-path" )
293+ # Ensure there's a value after --manifest-path
294+ if manifest_path_index + 1 >= len (args ):
295+ print ("Error: --manifest-path option requires a path value" )
296+ sys .exit (1 )
297+
298+ # Extract the manifest path and remove both the option and its value from args
299+ manifest_path = args [manifest_path_index + 1 ]
300+ filtered_args = args .copy ()
301+ filtered_args .pop (manifest_path_index + 1 ) # Remove the path value first
302+ filtered_args .pop (manifest_path_index ) # Then remove the --manifest-path option
303+
304+ # For non-spec commands, we need to inject the manifest into the config
305+ if filtered_args and filtered_args [0 ] != "spec" :
306+ # Check for config argument
307+ if "--config" not in filtered_args :
308+ print ("Error: When using --manifest-path with commands other than 'spec', --config must also be provided" )
309+ sys .exit (1 )
310+
311+ config_index = filtered_args .index ("--config" )
312+ if config_index + 1 >= len (filtered_args ):
313+ print ("Error: --config option requires a value" )
314+ sys .exit (1 )
315+
316+ config_path = filtered_args [config_index + 1 ]
317+
318+ # Read and modify the config file
319+ with open (config_path , "r" ) as f :
320+ config = json .load (f )
321+
322+ with open (manifest_path , "r" ) as f :
323+ manifest = yaml .safe_load (f )
324+
325+ # Inject the manifest
326+ config ["__injected_declarative_manifest" ] = manifest
327+
328+ # Write to a temporary file
329+ temp_config_path = f"{ config_path } .temp"
330+ with open (temp_config_path , "w" ) as f :
331+ json .dump (config , f )
332+
333+ # Replace the config path
334+ filtered_args [config_index + 1 ] = temp_config_path
335+
336+ # Process the command with the modified arguments
337+ handle_remote_manifest_command (filtered_args )
338+
339+ except ValueError : # --manifest-path not found in args
340+ # For spec command, it's fine to proceed without manifest
341+ if args and args [0 ] == "spec" :
342+ handle_remote_manifest_command (args )
343+ else :
344+ # For other commands, provide a helpful error message
345+ print ("Error: When using the source-declarative-manifest command locally, you must either:" )
346+ print (" 1. Provide the --manifest-path option pointing to your YAML manifest file, or" )
347+ print (" 2. Include the '__injected_declarative_manifest' key in your config JSON with the manifest content" )
348+ sys .exit (1 )
349+ except Exception as e :
350+ print (f"Error processing arguments: { e } " )
351+ sys .exit (1 )
0 commit comments