@@ -538,13 +538,57 @@ def get_source(
538538def delete_source (
539539 source_id : str ,
540540 * ,
541+ source_name : str | None = None ,
541542 api_root : str ,
542543 client_id : SecretString ,
543544 client_secret : SecretString ,
544545 workspace_id : str | None = None ,
546+ safe_mode : bool = True ,
545547) -> None :
546- """Delete a source."""
548+ """Delete a source.
549+
550+ Args:
551+ source_id: The source ID to delete
552+ source_name: Optional source name. If not provided and safe_mode is enabled,
553+ the source name will be fetched from the API to perform safety checks.
554+ api_root: The API root URL
555+ client_id: OAuth client ID
556+ client_secret: OAuth client secret
557+ workspace_id: The workspace ID (not currently used)
558+ safe_mode: If True, requires the source name to contain "delete-me" or "deleteme"
559+ (case insensitive) to prevent accidental deletion. Defaults to True.
560+
561+ Raises:
562+ PyAirbyteInputError: If safe_mode is True and the source name does not meet
563+ the safety requirements.
564+ """
547565 _ = workspace_id # Not used (yet)
566+
567+ if safe_mode :
568+ if source_name is None :
569+ source_info = get_source (
570+ source_id = source_id ,
571+ api_root = api_root ,
572+ client_id = client_id ,
573+ client_secret = client_secret ,
574+ )
575+ source_name = source_info .name
576+
577+ if not _is_safe_name_to_delete (source_name ):
578+ raise PyAirbyteInputError (
579+ message = (
580+ f"Cannot delete source '{ source_name } ' with safe_mode enabled. "
581+ "To authorize deletion, the source name must contain 'delete-me' or 'deleteme' "
582+ "(case insensitive).\n \n "
583+ "Please rename the source to meet this requirement before attempting deletion."
584+ ),
585+ context = {
586+ "source_id" : source_id ,
587+ "source_name" : source_name ,
588+ "safe_mode" : True ,
589+ },
590+ )
591+
548592 airbyte_instance = get_airbyte_server_instance (
549593 client_id = client_id ,
550594 client_secret = client_secret ,
@@ -700,7 +744,7 @@ def get_destination(
700744 # the destination API response is of the wrong type.
701745 # https://github.com/airbytehq/pyairbyte/issues/320
702746 raw_response : dict [str , Any ] = json .loads (response .raw_response .text )
703- raw_configuration : dict [str , Any ] = raw_response [ "configuration" ]
747+ raw_configuration : dict [str , Any ] | None = raw_response . get ( "configuration" )
704748
705749 destination_type = raw_response .get ("destinationType" )
706750 destination_mapping = {
@@ -710,7 +754,7 @@ def get_destination(
710754 "duckdb" : models .DestinationDuckdb ,
711755 }
712756
713- if destination_type in destination_mapping :
757+ if destination_type in destination_mapping and raw_configuration is not None :
714758 response .destination_response .configuration = destination_mapping [
715759 destination_type # pyrefly: ignore[index-error]
716760 ](** raw_configuration )
@@ -726,13 +770,58 @@ def get_destination(
726770def delete_destination (
727771 destination_id : str ,
728772 * ,
773+ destination_name : str | None = None ,
729774 api_root : str ,
730775 client_id : SecretString ,
731776 client_secret : SecretString ,
732777 workspace_id : str | None = None ,
778+ safe_mode : bool = True ,
733779) -> None :
734- """Delete a destination."""
780+ """Delete a destination.
781+
782+ Args:
783+ destination_id: The destination ID to delete
784+ destination_name: Optional destination name. If not provided and safe_mode is enabled,
785+ the destination name will be fetched from the API to perform safety checks.
786+ api_root: The API root URL
787+ client_id: OAuth client ID
788+ client_secret: OAuth client secret
789+ workspace_id: The workspace ID (not currently used)
790+ safe_mode: If True, requires the destination name to contain "delete-me" or "deleteme"
791+ (case insensitive) to prevent accidental deletion. Defaults to True.
792+
793+ Raises:
794+ PyAirbyteInputError: If safe_mode is True and the destination name does not meet
795+ the safety requirements.
796+ """
735797 _ = workspace_id # Not used (yet)
798+
799+ if safe_mode :
800+ if destination_name is None :
801+ destination_info = get_destination (
802+ destination_id = destination_id ,
803+ api_root = api_root ,
804+ client_id = client_id ,
805+ client_secret = client_secret ,
806+ )
807+ destination_name = destination_info .name
808+
809+ if not _is_safe_name_to_delete (destination_name ):
810+ raise PyAirbyteInputError (
811+ message = (
812+ f"Cannot delete destination '{ destination_name } ' with safe_mode enabled. "
813+ "To authorize deletion, the destination name must contain 'delete-me' or "
814+ "'deleteme' (case insensitive).\n \n "
815+ "Please rename the destination to meet this requirement "
816+ "before attempting deletion."
817+ ),
818+ context = {
819+ "destination_id" : destination_id ,
820+ "destination_name" : destination_name ,
821+ "safe_mode" : True ,
822+ },
823+ )
824+
736825 airbyte_instance = get_airbyte_server_instance (
737826 client_id = client_id ,
738827 client_secret = client_secret ,
@@ -902,13 +991,74 @@ def get_connection_by_name(
902991 return found [0 ]
903992
904993
994+ def _is_safe_name_to_delete (name : str ) -> bool :
995+ """Check if a name is safe to delete.
996+
997+ Requires the name to contain either "delete-me" or "deleteme" (case insensitive).
998+ """
999+ name_lower = name .lower ()
1000+ return any (
1001+ {
1002+ "delete-me" in name_lower ,
1003+ "deleteme" in name_lower ,
1004+ }
1005+ )
1006+
1007+
9051008def delete_connection (
9061009 connection_id : str ,
1010+ connection_name : str | None = None ,
1011+ * ,
9071012 api_root : str ,
9081013 workspace_id : str ,
9091014 client_id : SecretString ,
9101015 client_secret : SecretString ,
1016+ safe_mode : bool = True ,
9111017) -> None :
1018+ """Delete a connection.
1019+
1020+ Args:
1021+ connection_id: The connection ID to delete
1022+ connection_name: Optional connection name. If not provided and safe_mode is enabled,
1023+ the connection name will be fetched from the API to perform safety checks.
1024+ api_root: The API root URL
1025+ workspace_id: The workspace ID
1026+ client_id: OAuth client ID
1027+ client_secret: OAuth client secret
1028+ safe_mode: If True, requires the connection name to contain "delete-me" or "deleteme"
1029+ (case insensitive) to prevent accidental deletion. Defaults to True.
1030+
1031+ Raises:
1032+ PyAirbyteInputError: If safe_mode is True and the connection name does not meet
1033+ the safety requirements.
1034+ """
1035+ if safe_mode :
1036+ if connection_name is None :
1037+ connection_info = get_connection (
1038+ workspace_id = workspace_id ,
1039+ connection_id = connection_id ,
1040+ api_root = api_root ,
1041+ client_id = client_id ,
1042+ client_secret = client_secret ,
1043+ )
1044+ connection_name = connection_info .name
1045+
1046+ if not _is_safe_name_to_delete (connection_name ):
1047+ raise PyAirbyteInputError (
1048+ message = (
1049+ f"Cannot delete connection '{ connection_name } ' with safe_mode enabled. "
1050+ "To authorize deletion, the connection name must contain 'delete-me' or "
1051+ "'deleteme' (case insensitive).\n \n "
1052+ "Please rename the connection to meet this requirement "
1053+ "before attempting deletion."
1054+ ),
1055+ context = {
1056+ "connection_id" : connection_id ,
1057+ "connection_name" : connection_name ,
1058+ "safe_mode" : True ,
1059+ },
1060+ )
1061+
9121062 _ = workspace_id # Not used (yet)
9131063 airbyte_instance = get_airbyte_server_instance (
9141064 client_id = client_id ,
@@ -1317,9 +1467,8 @@ def delete_custom_yaml_source_definition(
13171467 api_root: The API root URL
13181468 client_id: OAuth client ID
13191469 client_secret: OAuth client secret
1320- safe_mode: If True, requires the connector name to either start with "delete:"
1321- or contain "delete-me" (case insensitive) to prevent accidental deletion.
1322- Defaults to True.
1470+ safe_mode: If True, requires the connector name to contain "delete-me" or "deleteme"
1471+ (case insensitive) to prevent accidental deletion. Defaults to True.
13231472
13241473 Raises:
13251474 PyAirbyteInputError: If safe_mode is True and the connector name does not meet
@@ -1335,19 +1484,14 @@ def delete_custom_yaml_source_definition(
13351484 )
13361485 connector_name = definition_info .name
13371486
1338- def is_safe_to_delete (name : str ) -> bool :
1339- name_lower = name .lower ()
1340- return name_lower .startswith ("delete:" ) or "delete-me" in name_lower
1341-
1342- if not is_safe_to_delete (definition_info .name ):
1487+ if not _is_safe_name_to_delete (definition_info .name ):
13431488 raise PyAirbyteInputError (
13441489 message = (
13451490 f"Cannot delete custom connector definition '{ connector_name } ' "
13461491 "with safe_mode enabled. "
1347- "To authorize deletion, the connector name must either:\n "
1348- " 1. Start with 'delete:' (case insensitive), OR\n "
1349- " 2. Contain 'delete-me' (case insensitive)\n \n "
1350- "Please rename the connector to meet one of these requirements "
1492+ "To authorize deletion, the connector name must contain 'delete-me' or "
1493+ "'deleteme' (case insensitive).\n \n "
1494+ "Please rename the connector to meet this requirement "
13511495 "before attempting deletion."
13521496 ),
13531497 context = {
0 commit comments