2121
2222import uuid
2323import re
24+ import os
2425from urllib .parse import urlparse
2526
2627from azure .cli .command_modules .apim ._params import ImportFormat
@@ -516,6 +517,140 @@ def apim_api_import(
516517 parameters = parameters )
517518
518519
520+ def apim_api_export (client , resource_group_name , service_name , api_id , export_format , file_path = None , file_name = None ,):
521+ """Gets the details of the API specified by its identifier in the format specified """
522+
523+ import json
524+ import yaml
525+ import xml .etree .ElementTree as ET
526+ import os
527+ import requests
528+
529+ def _determine_file_extension (mapped_format ):
530+ """Determine file extension based on the mapped format."""
531+ if mapped_format in ['swagger' , 'openapi+json' ]:
532+ return '.json'
533+ elif mapped_format in ['wsdl' , 'wadl' ]:
534+ return '.xml'
535+ elif mapped_format in ['openapi' ]:
536+ return '.yaml'
537+ else :
538+ return '.txt'
539+
540+
541+ def _extract_export_link_or_text (response ):
542+ """Extract link or exported text from the API export response."""
543+ link = None
544+ exported_text = None
545+ response_dict = api_export_result_to_dict (response )
546+ try :
547+ link = response_dict ['additional_properties' ]['properties' ]['value' ]['link' ]
548+ except KeyError :
549+ # No link present; try to use direct content
550+ try :
551+ exported_text = response .value if hasattr (response , 'value' ) else None
552+ except AttributeError : # defensive
553+ exported_text = None
554+ return link , exported_text
555+
556+
557+ def _fetch_content_from_link (link ):
558+ """Fetch content from a downloadable link."""
559+ import requests
560+ try :
561+ exported_results = requests .get (link , timeout = 30 )
562+ if not exported_results .ok :
563+ logger .warning ("Got bad status from API Management during API export: %s" , exported_results .status_code )
564+ return exported_results .text
565+ except requests .exceptions .ReadTimeout :
566+ logger .warning ("Timed out while exporting API from API Management." )
567+ return None
568+
569+
570+ def _parse_exported_content (exported_text ):
571+ """Parse content into appropriate format (JSON, YAML, XML, or raw text)."""
572+ import json
573+ import yaml
574+ import xml .etree .ElementTree as ET
575+
576+ if exported_text is None :
577+ return None
578+
579+ try :
580+ return json .loads (exported_text )
581+ except json .JSONDecodeError :
582+ try :
583+ return yaml .safe_load (exported_text )
584+ except yaml .YAMLError :
585+ try :
586+ return ET .fromstring (exported_text )
587+ except ET .ParseError :
588+ return exported_text # raw text
589+
590+
591+ def _write_content_to_file (full_path , exported_result_content , file_extension , response ):
592+ """Write parsed content to file in appropriate format."""
593+ import json
594+ import yaml
595+ import xml .etree .ElementTree as ET
596+
597+ try :
598+ os .makedirs (os .path .dirname (full_path ), exist_ok = True )
599+ with open (full_path , 'w' , encoding = 'utf-8' ) as f :
600+ if isinstance (exported_result_content , dict ) and file_extension == '.json' :
601+ json .dump (exported_result_content , f , indent = 4 )
602+ elif isinstance (exported_result_content , dict ) and file_extension == '.yaml' :
603+ yaml .dump (exported_result_content , f )
604+ elif isinstance (exported_result_content , ET .Element ):
605+ ET .register_namespace ('' , 'http://wadl.dev.java.net/2009/02' )
606+ xml_string = ET .tostring (exported_result_content , encoding = 'unicode' )
607+ f .write (xml_string )
608+ elif isinstance (exported_result_content , str ):
609+ f .write (exported_result_content )
610+ else :
611+ # Fallback: write the value attribute or stringified content
612+ fallback = getattr (response , 'value' , '' )
613+ f .write (fallback if isinstance (fallback , str ) else str (exported_result_content ))
614+ except OSError as e :
615+ logger .warning ("Error writing exported API to file: %s" , e )
616+
617+
618+ def _handle_playback_mode (export_format , file_path , file_name , api_id , format_mapping ):
619+ """Handle playback mode file export for testing."""
620+ if file_path is None :
621+ raise RequiredArgumentMissingError (
622+ "Please specify file path using '--file-path' argument." )
623+
624+ file_extension = _determine_file_extension_from_export_format (export_format , format_mapping )
625+ export_type = format_mapping .get (export_format , '' ).replace ('-link' , '' )
626+
627+ if file_name is None :
628+ file_name = f"{ api_id } _{ export_type } { file_extension } "
629+
630+ full_path = os .path .join (file_path , file_name )
631+ try :
632+ os .makedirs (os .path .dirname (full_path ), exist_ok = True )
633+ with open (full_path , 'w' , encoding = 'utf-8' ) as f :
634+ f .write ('' )
635+ except OSError as e :
636+ logger .warning ("Error writing exported API to file in playback mode: %s" , e )
637+
638+ logger .warning ("APIM export results written to file (playback stub): %s" , full_path )
639+ return None
640+
641+
642+ def _determine_file_extension_from_export_format (export_format , format_mapping ):
643+ """Determine file extension based on the export format."""
644+ if export_format in ['SwaggerFile' , 'OpenApiJsonFile' ]:
645+ return '.json'
646+ elif export_format in ['WsdlFile' , 'WadlFile' ]:
647+ return '.xml'
648+ elif export_format in ['OpenApiYamlFile' ]:
649+ return '.yaml'
650+ else :
651+ return '.txt'
652+
653+
519654def apim_api_export (client , resource_group_name , service_name , api_id , export_format , file_path = None , file_name = None ,):
520655 """Gets the details of the API specified by its identifier in the format specified """
521656
@@ -526,7 +661,6 @@ def apim_api_export(client, resource_group_name, service_name, api_id, export_fo
526661 import requests
527662
528663 # Define the mapping from old format values to new ones
529- # Use non-link formats for File exports to avoid duplicate identical GET requests
530664 format_mapping = {
531665 # File exports -> non-link formats
532666 "WadlFile" : "wadl" ,
@@ -541,41 +675,15 @@ def apim_api_export(client, resource_group_name, service_name, api_id, export_fo
541675 "OpenApiJsonUrl" : "openapi+json-link" ,
542676 "WsdlUrl" : "wsdl-link"
543677 }
544- mappedFormat = format_mapping .get (export_format )
678+ mapped_format = format_mapping .get (export_format )
545679
546- # Export the API from API Management
547680 # Optimization for playback mode: if exporting to a file during tests, avoid a second
548681 # management call which causes cassette mismatches. Instead, create the file directly.
549- import os as _os
550- is_live = _os .environ .get ('AZURE_TEST_RUN_LIVE' , '' ).lower () in ['true' , '1' , 'yes' ]
682+ is_live = os .environ .get ('AZURE_TEST_RUN_LIVE' , '' ).lower () in ['true' , '1' , 'yes' ]
551683 if export_format .endswith ('File' ) and not is_live :
552- # If file is requested in playback mode
553- if file_path is None :
554- raise RequiredArgumentMissingError (
555- "Please specify file path using '--file-path' argument." )
556- # Determine extension from format
557- if export_format in ['SwaggerFile' , 'OpenApiJsonFile' ]:
558- file_extension = '.json'
559- elif export_format in ['WsdlFile' , 'WadlFile' ]:
560- file_extension = '.xml'
561- elif export_format in ['OpenApiYamlFile' ]:
562- file_extension = '.yaml'
563- else :
564- file_extension = '.txt'
565- exportType = format_mapping .get (export_format , '' ).replace ('-link' , '' ) or mappedFormat
566- if file_name is None :
567- file_name = f"{ api_id } _{ exportType } { file_extension } "
568- full_path = os .path .join (file_path , file_name )
569- try :
570- os .makedirs (os .path .dirname (full_path ), exist_ok = True )
571- with open (full_path , 'w' , encoding = 'utf-8' ) as f :
572- f .write ('' )
573- except OSError as e :
574- logger .warning ("Error writing exported API to file in playback mode: %s" , e )
575- logger .warning ("APIM export results written to file (playback stub): %s" , full_path )
576- return None
684+ return _handle_playback_mode (export_format , file_path , file_name , api_id , format_mapping )
577685
578- response = client .api_export .get (resource_group_name , service_name , api_id , mappedFormat , True )
686+ response = client .api_export .get (resource_group_name , service_name , api_id , mapped_format , True )
579687
580688 # If url is requested, just return the export result (contains a link)
581689 if export_format in ['WadlUrl' , 'SwaggerUrl' , 'OpenApiYamlUrl' , 'OpenApiJsonUrl' , 'WsdlUrl' ]:
@@ -586,79 +694,28 @@ def apim_api_export(client, resource_group_name, service_name, api_id, export_fo
586694 raise RequiredArgumentMissingError (
587695 "Please specify file path using '--file-path' argument." )
588696
589- # Determine the file extension based on the mappedFormat
590- if mappedFormat in ['swagger' , 'openapi+json' ]:
591- file_extension = '.json'
592- elif mappedFormat in ['wsdl' , 'wadl' ]:
593- file_extension = '.xml'
594- elif mappedFormat in ['openapi' ]:
595- file_extension = '.yaml'
596- else :
597- file_extension = '.txt'
598-
599- exportType = mappedFormat
697+ file_extension = _determine_file_extension (mapped_format )
698+ export_type = mapped_format
699+
600700 if file_name is None :
601- file_name = f"{ api_id } _{ exportType } { file_extension } "
701+ file_name = f"{ api_id } _{ export_type } { file_extension } "
602702 full_path = os .path .join (file_path , file_name )
603703
604704 # Try to obtain a downloadable link first (in case service still returns link)
605- link = None
606- exported_text = None
607- response_dict = api_export_result_to_dict (response )
608- try :
609- link = response_dict ['additional_properties' ]['properties' ]['value' ]['link' ]
610- except KeyError :
611- # No link present; try to use direct content
612- try :
613- exported_text = response .value if hasattr (response , 'value' ) else None
614- except Exception : # defensive
615- exported_text = None
705+ link , exported_text = _extract_export_link_or_text (response )
616706
617707 # Fetch content if link is available
618708 if link :
619- try :
620- exportedResults = requests .get (link , timeout = 30 )
621- if not exportedResults .ok :
622- logger .warning ("Got bad status from API Management during API export: %s" , exportedResults .status_code )
623- exported_text = exportedResults .text
624- except requests .exceptions .ReadTimeout :
625- logger .warning ("Timed out while exporting API from API Management." )
709+ fetched_text = _fetch_content_from_link (link )
710+ if fetched_text :
711+ exported_text = fetched_text
626712
627713 # Parse content where possible for nicer formatting, otherwise write raw text
628- exportedResultContent = None
629- if exported_text is not None :
630- try :
631- exportedResultContent = json .loads (exported_text )
632- except Exception :
633- try :
634- exportedResultContent = yaml .safe_load (exported_text )
635- except Exception :
636- try :
637- exportedResultContent = ET .fromstring (exported_text )
638- except Exception :
639- exportedResultContent = exported_text # raw text
714+ exported_result_content = _parse_exported_content (exported_text )
640715
641716 # Write results to a file
642717 logger .warning ("Writing results to file: %s" , full_path )
643- try :
644- os .makedirs (os .path .dirname (full_path ), exist_ok = True )
645- with open (full_path , 'w' , encoding = 'utf-8' ) as f :
646- if isinstance (exportedResultContent , dict ) and file_extension == '.json' :
647- json .dump (exportedResultContent , f , indent = 4 )
648- elif isinstance (exportedResultContent , dict ) and file_extension == '.yaml' :
649- yaml .dump (exportedResultContent , f )
650- elif isinstance (exportedResultContent , ET .Element ):
651- ET .register_namespace ('' , 'http://wadl.dev.java.net/2009/02' )
652- xml_string = ET .tostring (exportedResultContent , encoding = 'unicode' )
653- f .write (xml_string )
654- elif isinstance (exportedResultContent , str ):
655- f .write (exportedResultContent )
656- else :
657- # Fallback: write the value attribute or stringified content
658- fallback = getattr (response , 'value' , '' )
659- f .write (fallback if isinstance (fallback , str ) else str (exportedResultContent ))
660- except OSError as e :
661- logger .warning ("Error writing exported API to file: %s" , e )
718+ _write_content_to_file (full_path , exported_result_content , file_extension , response )
662719
663720 logger .warning ("APIM export results written to file: %s" , full_path )
664721 return None
0 commit comments