def PackRequestAsComplexType(request, version): """Pack (recursively) a Python tuple object into a SOAP data holder. Args: request: instance of Python tuple object. version: str the API version being used. Returns: ZSI.TCcompound.ComplexType packed Python data. """ if isinstance(request, dict): cpl = ZSI.TCcompound.ComplexType(ZSI.TCcompound.ComplexType, []) for key in request: value = request.get(key) data = PackRequestAsComplexType(value, version) if data and data != 'None': cpl.__dict__.__setitem__('_%s' % key, data) return cpl elif isinstance(request, list): if not SanityCheck.IsNewApi(version): request = CustomPackList(request) if isinstance(request, dict): return PackRequestAsComplexType(request, version) lst = [] for item in request: data = PackRequestAsComplexType(item, version) lst.append(data) return lst elif isinstance(request, tuple): return request else: return request
def __init__(self, headers, config, op_config, lock, logger): """Inits AccountService. Args: headers: dict dictionary object with populated authentication credentials. config: dict dictionary object with populated configuration values. op_config: dict dictionary object with additional configuration values for this operation. lock: thread.lock the thread lock logger: Logger the instance of Logger """ url = [ op_config['server'], 'api/adwords', op_config['version'], self.__class__.__name__ ] if glob_sanity_check.IsNewApi(op_config['version']): url.insert(2, 'account') if config['access']: url.insert(len(url) - 1, config['access']) self.__service = WebService(headers, config, op_config, '/'.join(url), lock, logger) self.__config = config self.__op_config = op_config if self.__config['soap_lib'] == SOAPPY: from aw_api.soappy_toolkit import MessageHandler from aw_api.soappy_toolkit import SanityCheck self.__web_services = None self.__message_handler = MessageHandler elif self.__config['soap_lib'] == ZSI: from aw_api import API_VERSIONS from aw_api.zsi_toolkit import SanityCheck if op_config['version'] in API_VERSIONS: module = '%s_services' % self.__class__.__name__ try: web_services = __import__( 'aw_api.zsi_toolkit.%s.%s' % (op_config['version'], module), globals(), locals(), ['']) except ImportError, e: # If one of library's required modules is missing, re raise exception. if str(e).find(module) < 0: raise ImportError(e) msg = ( 'The version \'%s\' is not compatible with \'%s\'.' % (op_config['version'], self.__class__.__name__)) raise ValidationError(msg) else: msg = 'Invalid API version, not one of %s.' % str( list(API_VERSIONS)) raise ValidationError(msg) self.__web_services = web_services self.__loc = eval('web_services.%sLocator()' % self.__class__.__name__)
def GetServiceConnection(headers, config, url, http_proxy, version): """Get SOAP service connection. Args: headers: dict dictionary object with populated authentication credentials. config: dict dictionary object with populated configuration values. url: str url of the web service to call. http_proxy: str HTTP proxy to use. version: str version of the API in use. Returns: instance SOAPpy.SOAPProxy with set headers. """ # Catch empty SOAP header elements and exclude them from request. full_headers = {} for key in headers: if headers[key]: full_headers[key] = headers[key] if glob_sanity_check.IsNewApi(version): headers = SOAPpy.Types.headerType( {config['ns_target'][1]: full_headers}) headers._setAttr('xmlns', config['ns_target'][0]) else: headers = SOAPpy.Types.headerType(full_headers) service = SOAPpy.SOAPProxy(url, http_proxy=http_proxy, header=headers) service.config.dumpHeadersIn = 1 service.config.dumpHeadersOut = 1 service.config.dumpSOAPIn = 1 service.config.dumpSOAPOut = 1 # Turn off type information, since SOAPpy usually gets the types wrong. service.config.typed = 0 # Turn on noroot, to skip including "SOAP-ENC:root" as part of the request. service.noroot = 1 # Explicitly set the style of the namespace, otherwise will default to 1999. service.config.namespaceStyle = '2001' return service
# Patch v2009*/*.py: # - In local instances, whenever we encounter 'get' or 'mutate', we use # 'getXxx' or 'mutateXxx' respectively. The Xxx refer to the name of the # service (i.e. Campaign, AdGroup). # # This patch allows Python to import generated types with out collision. # Otherwise, we end up with types like 'mutate_Dec', 'mutate', etc. which # show up in all services and cause invalid instance of a service to be # invoked (defaults to the first instance that is found). So, if you invoke # an instance of CampaignService and AdGroupService and then try to add an # ad group, it will invoke 'mutate' from wrong service (CampaignService in # this case) resulting in a failure. for f_name in os.listdir(os.path.abspath(target['location'])): f_path = os.path.join(os.path.abspath(target['location']), f_name) f_parts = f_name.split('Service_') if SanityCheck.IsNewApi(target['version']): if os.path.exists(f_path): fh = open(f_path, 'r') try: data = fh.read() ops = ['get', 'mutate'] if f_name.find('_services.py') > -1: for op in ops: data = data.replace('def %s(' % op, 'def %s%s(' % (op, f_parts[0])) data = data.replace('# op: %s' % op, '# %s: get%s' % (op, f_parts[0])) data = data.replace('%sRequest' % op, '%s%sRequest' % (op, f_parts[0])) data = data.replace('%sResponse' % op, '%s%sResponse' % (op, f_parts[0]))
def CallRawMethod(self, soap_message): """Make an API call by posting raw SOAP XML message. Args: soap_message: str SOAP XML message. Returns: tuple response from the API method. """ # Acquire thread lock. self.__lock.acquire() try: buf = SoapBuffer( xml_parser=self.__config['xml_parser'], pretty_xml=Utils.BoolTypeConvert(self.__config['use_pretty_xml'])) http_header = { 'post': '%s' % self.__url, 'host': 'sandbox.google.com', 'user_agent': '%s v%s; WebService.py' % (LIB_SHORT_NAME, LIB_VERSION), 'content_type': 'text/xml; charset=\"UTF-8\"', 'content_length': '%d' % len(soap_message), 'soap_action': '' } version = self.__url.split('/')[-2] if SanityCheck.IsNewApi(version): http_header['host'] = 'adwords-%s' % http_header['host'] index = self.__url.find('adwords.google.com') if index > -1: http_header['host'] = 'adwords.google.com' self.__url = ''.join(['https://', http_header['host'], self.__url]) start_time = time.strftime('%Y-%m-%d %H:%M:%S') buf.write( ('%s Outgoing HTTP headers %s\nPOST %s\nHost: %s\nUser-Agent: ' '%s\nContent-type: %s\nContent-length: %s\nSOAPAction: %s\n%s\n%s ' 'Outgoing SOAP %s\n%s\n%s\n' % ('*'*3, '*'*46, http_header['post'], http_header['host'], http_header['user_agent'], http_header['content_type'], http_header['content_length'], http_header['soap_action'], '*'*72, '*'*3, '*'*54, soap_message, '*'*72))) # Construct header and send SOAP message. web_service = httplib.HTTPS(http_header['host']) web_service.putrequest('POST', http_header['post']) web_service.putheader('Host', http_header['host']) web_service.putheader('User-Agent', http_header['user_agent']) web_service.putheader('Content-type', http_header['content_type']) web_service.putheader('Content-length', http_header['content_length']) web_service.putheader('SOAPAction', http_header['soap_action']) web_service.endheaders() web_service.send(soap_message) # Get response. status_code, status_message, header = web_service.getreply() response = web_service.getfile().read() header = str(header).replace('\r', '') buf.write(('%s Incoming HTTP headers %s\n%s %s\n%s\n%s\n%s Incoming SOAP' ' %s\n%s\n%s\n' % ('*'*3, '*'*46, status_code, status_message, header, '*'*72, '*'*3, '*'*54, response, '*'*72))) stop_time = time.strftime('%Y-%m-%d %H:%M:%S') # Catch local errors prior to going down to the SOAP layer, which may not # exist for this error instance. if not buf.IsHandshakeComplete() or not buf.IsSoap(): # The buffer contains non-XML data, most likely an HTML page. This # happens in the case of 502 errors. html_error = Utils.GetErrorFromHtml(buf.GetBufferAsStr()) if html_error: msg = '%s' % html_error else: msg = 'Unknown error.' raise Error(msg) self.__ManageSoap(buf, start_time, stop_time, {'data': buf.GetBufferAsStr()}) finally: # Release thread lock. if self.__lock.locked(): self.__lock.release() return (response,)
def CallMethod(self, method_name, params, service_name=None, loc=None, request=None): """Make an API call to specified method. Args: method_name: str API method name. params: list list of parameters to send to the API method. [optional] service_name: str API service name. loc: service locator. request: instance holder of the SOAP request. Returns: tuple/str response from the API method. If 'raw_response' flag enabled a string is returned, tuple otherwise. """ # Acquire thread lock. self.__lock.acquire() try: headers = self.__headers config = self.__config # Temporarily redirect HTTP headers and SOAP from STDOUT into a buffer. buf = SoapBuffer( xml_parser=config['xml_parser'], pretty_xml=Utils.BoolTypeConvert(config['use_pretty_xml'])) old_stdout = sys.stdout sys.stdout = buf start_time = time.strftime('%Y-%m-%d %H:%M:%S') response = () raw_response = '' error = {} try: if Utils.BoolTypeConvert(config['use_strict']): SanityCheck.ValidateHeadersForServer(headers, self.__op_config['server']) # Load/unload version specific authentication and configuration data. if SanityCheck.IsNewApi(self.__op_config['version']): # Set boolean to the format expected by the server, True => true. if 'validateOnly' in headers: headers['validateOnly'] = headers['validateOnly'].lower() # Load/set authentication token. If authentication token has expired, # regenerate it. now = time.time() if (Utils.BoolTypeConvert(config['use_auth_token']) and (('authToken' not in headers and 'auth_token_epoch' not in config) or int(now - config['auth_token_epoch']) >= AUTH_TOKEN_EXPIRE)): if ('email' not in headers or not headers['email'] or 'password' not in headers or not headers['password']): msg = ('Required authentication headers, \'email\' and ' '\'password\', are missing. Unable to regenerate ' 'authentication token.') raise ValidationError(msg) headers['authToken'] = Utils.GetAuthToken(headers['email'], headers['password']) config['auth_token_epoch'] = time.time() self.__headers = headers self.__config = config elif not Utils.BoolTypeConvert(config['use_auth_token']): msg = ('Requests via %s require use of authentication token.' % self.__op_config['version']) raise ValidationError(msg) headers = Utils.UnLoadDictKeys(Utils.CleanUpDict(headers), ['email', 'password']) name_space = '/'.join(['https://adwords.google.com/api/adwords', self.__op_config['group'], self.__op_config['version']]) config['ns_target'] = (name_space, 'RequestHeader') else: headers['useragent'] = headers['userAgent'] headers = Utils.UnLoadDictKeys(headers, ['authToken', 'userAgent']) config = Utils.UnLoadDictKeys(config, ['ns_target', 'auth_token_epoch']) # Fire off API request and handle the response. if config['soap_lib'] == SOAPPY: from aw_api.soappy_toolkit import MessageHandler service = MessageHandler.GetServiceConnection( headers, config, self.__url, self.__op_config['http_proxy'], self.__op_config['version']) if not SanityCheck.IsNewApi(self.__op_config['version']): response = MessageHandler.UnpackResponseAsDict( service.invoke(method_name, params)) else: response = MessageHandler.UnpackResponseAsDict( service._callWithBody(MessageHandler.SetRequestParams( config, method_name, params))) elif config['soap_lib'] == ZSI: from aw_api.zsi_toolkit import MessageHandler service = MessageHandler.GetServiceConnection( headers, config, self.__url, self.__op_config['http_proxy'], service_name, loc) request = MessageHandler.SetRequestParams(self.__op_config, request, params) response = MessageHandler.UnpackResponseAsTuple( eval('service.%s(request)' % method_name)) # The response should always be tuple. If it's not, there must be # something wrong with MessageHandler.UnpackResponseAsTuple(). if len(response) == 1 and isinstance(response[0], list): response = tuple(response[0]) if isinstance(response, list): response = tuple(response) elif isinstance(response, tuple): pass else: if response: response = (response,) else: response = () except Exception, e: error['data'] = e stop_time = time.strftime('%Y-%m-%d %H:%M:%S') # Restore STDOUT. sys.stdout = old_stdout # When debugging mode is ON, fetch last traceback. if Utils.BoolTypeConvert(self.__config['debug']): error['trace'] = Utils.LastStackTrace() # Catch local errors prior to going down to the SOAP layer, which may not # exist for this error instance. if 'data' in error and not buf.IsHandshakeComplete(): # Check if buffer contains non-XML data, most likely an HTML page. This # happens in the case of 502 errors (and similar). Otherwise, this is a # local error and API request was never made. html_error = Utils.GetErrorFromHtml(buf.GetBufferAsStr()) if html_error: msg = '%s' % html_error else: msg = str(error['data']) if Utils.BoolTypeConvert(self.__config['debug']): msg += '\n%s' % error['trace'] # When debugging mode is ON, store the raw content of the buffer. if Utils.BoolTypeConvert(self.__config['debug']): error['raw_data'] = buf.GetBufferAsStr() # Catch errors from AuthToken and ValidationError levels, raised during # try/except above. if isinstance(error['data'], AuthTokenError): raise AuthTokenError(msg) elif isinstance(error['data'], ValidationError): raise ValidationError(error['data']) if 'raw_data' in error: msg = '%s [RAW DATA: %s]' % (msg, error['raw_data']) raise Error(msg) if Utils.BoolTypeConvert(self.__config['raw_response']): raw_response = buf.GetRawSOAPIn() self.__ManageSoap(buf, start_time, stop_time, error)