def __init__(self, headers, config, op_config, lock, logger, service_name, service_url, wrap_lists, buffer_class, namespace, namespace_extractor): """Inits GenericApiService. 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: mixed Thread lock to use to synchronize requests. May be a thread.lock or a threading.RLock logger: Logger Instance of Logger to use for logging. service_name: string The name of this service. service_url: string The URL pointing to this web service. wrap_lists: boolean Whether this service needs to wrap lists in an additional layer of XML element tags. buffer_class: class The SoapBuffer subclass to use as a buffer. namespace: string The namespace this service uses by default. namespace_extractor: function A function which takes a URL and returns the namespace prefix to use to represent it. Raises: Error: The WSDL for this service could not be found. Will also be raised if GenericApiService is initialized directly rather than by a subclass. """ if self.__class__ == GenericApiService: raise Error('GenericApiService cannot be instantiated directly.') self._headers = headers self._config = config self._op_config = op_config self._lock = lock self._logger = logger self._service_name = service_name self._service_url = service_url self._wrap_lists = wrap_lists self._buffer_class = buffer_class self._namespace = namespace self._namespace_extractor = namespace_extractor self._method_proxies = {} wsdl_url = service_url + '?wsdl' try: self._soappyservice = SOAPpy.WSDL.Proxy( wsdl_url, noroot=1, http_proxy=self._op_config['http_proxy'], config=self._GetSoapConfig()) except WSDLError: raise Error('Unable to locate WSDL at path \'%s\'' % wsdl_url) else: for method_key in self._soappyservice.methods: self._soappyservice.methods[method_key].location = service_url
def _VerifyHostName(self, hostname, certificate): """Checks the host name used against the host's SSL certificate. Args: hostname: string The name of the host this socket is connecting to. certificate: dictionary The SSL certificate returned by this host. Raises: Error: if the host name used for this connection is not listed as one of the names in the SSL certificate returned by this host. """ if 'subjectAltName' in certificate: names = [name for (name_type, name) in certificate['subjectAltName'] if name_type.lower() == 'dns'] else: names = [value for ((key, value),) in certificate['subject'] if key.lower() == 'commonname'] for name in names: if re.match(name.replace('.', '\.').replace('*', '[^.]*'), hostname, re.I) is not None: return raise Error('Host name "' + self.host + '" does not match any name listed ' 'in its SSL certificate!')
def connect(self): """Creates a socket and validates SSL certificates.""" sock = socket.create_connection((self.host, self.port)) try: self.sock = ssl.wrap_socket(sock, keyfile=self.key_file, certfile=self.cert_file, cert_reqs=self.cert_reqs, ca_certs=self.ca_certs) except ssl.SSLError, e: raise Error('Error validating SSL certificate for "' + self.host + '": ' + str(e))
def PurgeLog(log): """Clear content of a given log file. If the file cannot be opened, Error is raised. Args: log: str Absolute path to a log file. """ try: fh = open(log, 'w') try: fh.write('') finally: fh.close() except IOError, e: raise Error(e)
def DecodeNonASCIIText(text, encoding='utf-8'): """Decode a non-ASCII text into a unicode, using given encoding. A full list of supported encodings is available at http://docs.python.org/lib/standard-encodings.html. If unable to find given encoding, Error is raised. Args: text: str Text to encode. [optional] encoding: str Encoding format to use. Returns: tuple Decoded text with the text length, (text, len(text)). """ if isinstance(text, unicode): return (text, len(text)) dec_text = '' try: decoder = codecs.getdecoder(encoding) dec_text = decoder(text) except LookupError, e: msg = 'Unable to find \'%s\' encoding. %s.' % (encoding, e) raise Error(msg)
def main(client, places_email_address, places_access_token): # The placeholder type for location extensions. # See the Placeholder reference page for a list of all the placeholder types # and fields: # https://developers.google.com/adwords/api/docs/appendix/placeholders placeholder_location = 7 # The maximum number of CustomerFeed ADD operation attempts to make before # throwing an exception. max_customer_feed_add_attempts = 10 # Create a feed that will sync to the Google Places account specified by # places_email_address. Do not add FeedAttributes to this object, # as AdWords will add them automatically because this will be a # system generated feed. feed = { 'name': 'Places feed #%s' % Utils.GetUniqueName(), 'systemFeedGenerationData': { 'xsi_type': 'PlacesLocationFeedData', 'oAuthInfo': { 'httpMethod': 'GET', 'httpRequestUrl': 'https://www.google.com/local/add', 'httpAuthorizationHeader': 'Bearer %s' % places_access_token }, 'emailAddress': places_email_address, }, # Since this feed's feed items will be managed by AdWords, you must set # its origin to ADWORDS. 'origin': 'ADWORDS' } # Create an operation to add the feed. places_operations = [{'operator': 'ADD', 'operand': feed}] places_response = client.GetFeedService( version='v201409').Mutate(places_operations)[0] added_feed = places_response['value'][0] print 'Added places feed with ID: %d\n' % added_feed['id'] # Add a CustomerFeed that associates the feed with this customer for the # LOCATION placeholder type. customer_feed = { 'feedId': added_feed['id'], 'placeholderTypes': [placeholder_location], 'matchingFunction': { 'operator': 'IDENTITY', 'lhsOperand': { 'xsi_type': 'FunctionArgumentOperand', 'type': 'BOOLEAN', 'booleanValue': True } } } customer_feed_operation = { 'xsi_type': 'CustomerFeedOperation', 'operator': 'ADD', 'operand': customer_feed } customer_feed_service = client.GetCustomerFeedService(version='v201409') added_customer_feed = None i = 0 while i < max_customer_feed_add_attempts and added_customer_feed is None: try: added_customer_feed = customer_feed_service.Mutate( [customer_feed_operation])[0]['value'][0] except: # Wait using exponential backoff policy sleep_seconds = 2**i print( 'Attempt %d to add the CustomerFeed was not successful.' 'Waiting %d seconds before trying again.\n' % (i, sleep_seconds)) time.sleep(sleep_seconds) i += 1 if added_customer_feed is None: raise Error( 'Could not create the CustomerFeed after %s attempts. Please' ' retry the CustomerFeed ADD operation later.' % max_customer_feed_add_attempts) print('Added CustomerFeed for feed ID %d and placeholder type %d\n' % (added_customer_feed['id'], added_customer_feed['placeholderTypes']))
if code in ERRORS: raise ERRORS[code](fault) elif 'errors' in fault['detail']: type = fault['detail']['errors'][0]['type'] if type in ERRORS: raise ERRORS[str(type)](fault) if isinstance(fault, str): raise AdWordsError(fault) elif isinstance(fault, dict): raise AdWordsApiError(fault) except AdWordsApiError, e: raise e except AdWordsError, e: raise e except Error, e: if error: e = error raise Error(e) def _GetServiceName(self): return self.__service 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.
class WebService(object): """Implements WebService. Responsible for sending and recieving SOAP XML requests. """ def __init__(self, lib_sig, headers, config, op_config, url, lock, logger=None): """Inits WebService. Args: lib_sig: str Client library signature. 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. url: str URL of the web service to call. lock: thread.lock Thread lock. logger: Logger Instance of Logger. """ self._lib_sig = lib_sig self._headers = headers self._config = config self._op_config = op_config self._url = url self._lock = lock self._logger = logger self._start_time = 0 self._stop_time = 0 self._response = None if self._logger is None: self._logger = Logger(lib_sig, self._config['log_home']) def _ManageSoap(self, buf, log_handlers, lib_url, errors, start_time, stop_time, error={}): """Manage SOAP XML message. Args: buf: SoapBuffer SOAP buffer. log_handlers: list Log handlers. lib_url: str URL of the project's home. errors: dict Map of errors available for the API. start_time: str Time before service call was invoked. stop_time: str Time after service call was invoked. [optional] error: dict Error, if any. """ # Load trace errors, if any. if error and 'trace' in error: error_msg = error['trace'] else: error_msg = '' # Check if response was successful or not. if error and 'data' in error: is_fault = True else: is_fault = False # Forward SOAP XML, errors, and other debugging data to console, external # file, both, or ignore. Each handler supports the following elements, # tag: Config value for this handler. If left empty, will never write # data to file. # target: Target/destination represented by this handler (i.e. FILE, # CONSOLE, etc.). Initially, it should be set to Logger.NONE. # name: Name of the log file to use. # data: Data to write. for handler in log_handlers: if handler['tag'] == 'xml_log': handler['target'] = Logger.NONE handler['data'] += str( 'StartTime: %s\n%s\n%s\n%s\n%s\nEndTime: %s' % (start_time, buf.GetHeadersOut(), buf.GetSoapOut(), buf.GetHeadersIn(), buf.GetSoapIn(), stop_time)) elif handler['tag'] == 'request_log': handler['target'] = Logger.NONE handler['data'] += ' isFault=%s' % is_fault elif handler['tag'] == '': handler['target'] = Logger.NONE handler['data'] += 'DEBUG: %s' % error_msg for handler in log_handlers: if (handler['tag'] and Utils.BoolTypeConvert(self._config[handler['tag']])): handler['target'] = Logger.FILE # If debugging is On, raise handler's target two levels, # NONE -> CONSOLE # FILE -> FILE_AND_CONSOLE. if Utils.BoolTypeConvert(self._config['debug']): handler['target'] += 2 if (handler['target'] != Logger.NONE and handler['data'] and handler['data'] != 'None' and handler['data'] != 'DEBUG: '): self._logger.Log(handler['name'], handler['data'], log_level=Logger.DEBUG, log_handler=handler['target']) # If raw response is requested, no need to validate and throw appropriate # error. Up to the end user to handle successful or failed request. if Utils.BoolTypeConvert(self._config['raw_response']): return # Report SOAP fault. if is_fault: try: fault = buf.GetFaultAsDict() if not fault: msg = error['data'] except: fault = None # An error is not a SOAP fault, but check if some other error. if error_msg: msg = error_msg else: msg = ('Unable to parse incoming SOAP XML. Please, file ' 'a bug at %s/issues/list.' % lib_url) # Release thread lock. if self._lock.locked(): self._lock.release() if not fault and msg: return msg return fault return None def CallMethod(self, headers, config, method_name, params, buf, is_jaxb_api, lib_sig, lib_url, service_name=None, loc=None, request=None): """Make an API call to specified method. Args: headers: dict Dictionary object with populated authentication credentials. config: dict Dictionary object with populated configuration values. method_name: str API method name. params: list List of parameters to send to the API method. buf: SoapBuffer SOAP buffer. is_jaxb_api: str Whether API uses JAXB. lib_sig: str Signature of the client library. lib_url: str URL of the project's home. [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. """ # Temporarily redirect HTTP headers and SOAP from STDOUT into a buffer. if not Utils.BoolTypeConvert(self._config['raw_debug']): old_stdout = sys.stdout sys.stdout = buf response = () error = {} try: # Fire off API request and handle the response. if config['soap_lib'] == SOAPPY: from adspygoogle.common.soappy import MessageHandler service = MessageHandler.GetServiceConnection( headers, config, self._url, self._op_config['http_proxy'], is_jaxb_api) if not is_jaxb_api: response = MessageHandler.UnpackResponseAsDict( service.invoke(method_name, params)) else: if not params: params = ('') response = MessageHandler.UnpackResponseAsDict( service._callWithBody( MessageHandler.SetRequestParams( config, method_name, params))) elif config['soap_lib'] == ZSI: from adspygoogle.common.zsi import MessageHandler service = MessageHandler.GetServiceConnection( headers, config, self._op_config, self._url, service_name, loc) request = MessageHandler.SetRequestParams( request, params, is_jaxb_api) 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 # Restore STDOUT. if not Utils.BoolTypeConvert(self._config['raw_debug']): sys.stdout = old_stdout # When debugging mode is ON, fetch last traceback. if Utils.BoolTypeConvert(self._config['debug']): if Utils.LastStackTrace() and Utils.LastStackTrace() != 'None': 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']) return Error(msg) if Utils.BoolTypeConvert(self._config['raw_response']): response = buf.GetRawSOAPIn() if error: response = error return response
def CallRawMethod(self, buf, host, soap_message): """Make an API call by posting raw SOAP XML message. Args: buf: SoapBuffer SOAP buffer. host: str Host against which to make API request. soap_message: str SOAP XML message. Returns: tuple Response from the API method. """ http_header = { 'post': '%s' % self._url, 'host': host, 'user_agent': '%s; WebService.py' % self._lib_sig, 'content_type': 'text/xml; charset=\"UTF-8\"', 'content_length': '%d' % len(soap_message), 'soap_action': '' } self._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() self._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, self._response, '*' * 72))) self._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)
def main(client, campaign_id): # Initialize appropriate service. feed_service = client.GetFeedService(version='v201406') feed_item_service = client.GetFeedItemService(version='v201406') feed_mapping_service = client.GetFeedMappingService(version='v201406') campaign_feed_service = client.GetCampaignFeedService(version='v201406') sitelinks_data = {} # Create site links feed first. site_links_feed = { 'name': 'Feed For Site Links - %s' % Utils.GetUniqueName(), 'attributes': [ {'type': 'STRING', 'name': 'Link Text'}, {'type': 'URL', 'name': 'Link URL'}, {'type': 'STRING', 'name': 'Line 1 Description'}, {'type': 'STRING', 'name': 'Line 2 Description'} ] } response = feed_service.mutate([ {'operator': 'ADD', 'operand': site_links_feed} ])[0] if 'value' in response: feed = response['value'][0] link_text_feed_attribute_id = feed['attributes'][0]['id'] link_url_feed_attribute_id = feed['attributes'][1]['id'] line_1_feed_attribute_id = feed['attributes'][2]['id'] line_2_feed_attribute_id = feed['attributes'][3]['id'] print ('Feed with name \'%s\' and ID \'%s\' was added with' % (feed['name'], feed['id'])) print ('\tText attribute ID \'%s\' and URL attribute ID \'%s\'.' % (link_text_feed_attribute_id, link_url_feed_attribute_id)) print ('\tLine 1 attribute ID \'%s\' and Line 2 attribute ID \'%s\'.' % (line_1_feed_attribute_id, line_2_feed_attribute_id)) sitelinks_data['feedId'] = feed['id'] sitelinks_data['linkTextFeedId'] = link_text_feed_attribute_id sitelinks_data['linkUrlFeedId'] = link_url_feed_attribute_id sitelinks_data['line1FeedId'] = line_1_feed_attribute_id sitelinks_data['line2FeedId'] = line_2_feed_attribute_id else: raise Error('No feeds were added.') # Create site links feed items. items_data = [ {'text': 'Home', 'url': 'http://www.example.com', 'line1': 'Home line 1', 'line2': 'Home line 2'}, {'text': 'Stores', 'url': 'http://www.example.com/stores', 'line1': 'Stores line 1', 'line2': 'Stores line 2'}, {'text': 'On Sale', 'url': 'http://www.example.com/sale', 'line1': 'On Sale line 1', 'line2': 'On Sale line 2'}, {'text': 'Support', 'url': 'http://www.example.com/support', 'line1': 'Support line 1', 'line2': 'Support line 2'}, {'text': 'Products', 'url': 'http://www.example.com/products', 'line1': 'Products line 1', 'line2': 'Products line 2'}, {'text': 'About', 'url': 'http://www.example.com/about', 'line1': 'About line 1', 'line2': 'About line 2'} ] feed_items = [] for item in items_data: feed_items.append({ 'feedId': sitelinks_data['feedId'], 'attributeValues': [ { 'feedAttributeId': sitelinks_data['linkTextFeedId'], 'stringValue': item['text'] }, { 'feedAttributeId': sitelinks_data['linkUrlFeedId'], 'stringValue': item['url'] }, { 'feedAttributeId': sitelinks_data['line1FeedId'], 'stringValue': item['line1'] }, { 'feedAttributeId': sitelinks_data['line2FeedId'], 'stringValue': item['line2'] } ], # Optional: use the 'startTime' and 'endTime' keys to specify the time # period for the feed to deliver. The example below will make the feed # start now and stop in one month. # Make sure you specify the datetime in the customer's time zone. You # can retrieve this from customer['dateTimeZone']. # # ['startTime']: datetime.datetime.now().strftime('%Y%m%d %H%M%S') # ['endTime']: (datetime.datetime.now() + # relativedelta(months=1)).strftime('%Y%m%d %H%M%S') # Optional: use the 'scheduling' key to specify time and days of the # week for feed to deliver. This is a Beta feature. }) feed_items_operations = [{'operator': 'ADD', 'operand': item} for item in feed_items] response = feed_item_service.mutate(feed_items_operations)[0] if 'value' in response: sitelinks_data['feedItemIds'] = [] for feed_item in response['value']: print 'Feed item with ID %s was added.' % feed_item['feedItemId'] sitelinks_data['feedItemIds'].append(feed_item['feedItemId']) else: raise Error('No feed items were added.') # Create site links feed mapping. feed_mapping = { 'placeholderType': PLACEHOLDER_SITELINKS, 'feedId': sitelinks_data['feedId'], 'attributeFieldMappings': [ { 'feedAttributeId': sitelinks_data['linkTextFeedId'], 'fieldId': PLACEHOLDER_FIELD_SITELINK_LINK_TEXT }, { 'feedAttributeId': sitelinks_data['linkUrlFeedId'], 'fieldId': PLACEHOLDER_FIELD_SITELINK_LINK_URL }, { 'feedAttributeId': sitelinks_data['line1FeedId'], 'fieldId': PLACEHOLDER_FIELD_LINE_1_TEXT }, { 'feedAttributeId': sitelinks_data['line2FeedId'], 'fieldId': PLACEHOLDER_FIELD_LINE_2_TEXT } ] } response = feed_mapping_service.mutate([ {'operator': 'ADD', 'operand': feed_mapping} ])[0] if 'value' in response: feed_mapping = response['value'][0] print ('Feed mapping with ID %s and placeholder type %s was saved for feed' ' with ID %s.' % (feed_mapping['feedMappingId'], feed_mapping['placeholderType'], feed_mapping['feedId'])) else: raise Error('No feed mappings were added.') # Create site links campaign feed. operands = [] for feed_item_id in sitelinks_data['feedItemIds']: operands.append({ 'xsi_type': 'ConstantOperand', 'type': 'LONG', 'longValue': feed_item_id }) feed_item_function = { 'operator': 'IN', 'lhsOperand': [ {'xsi_type': 'RequestContextOperand', 'contextType': 'FEED_ITEM_ID'} ], 'rhsOperand': operands } # Optional: to target to a platform, define a function and 'AND' it with the # feed item ID link: platform_function = { 'operator': 'EQUALS', 'lhsOperand': [ { 'xsi_type': 'RequestContextOperand', 'contextType': 'DEVICE_PLATFORM' } ], 'rhsOperand': [ { 'xsi_type': 'ConstantOperand', 'type': 'STRING', 'stringValue': 'Mobile' } ] } combined_function = { 'operator': 'AND', 'lhsOperand': [ {'xsi_type': 'FunctionOperand', 'value': feed_item_function}, {'xsi_type': 'FunctionOperand', 'value': platform_function} ] } campaign_feed = { 'feedId': sitelinks_data['feedId'], 'campaignId': campaign_id, 'matchingFunction': combined_function, # Specifying placeholder types on the CampaignFeed allows the same feed # to be used for different placeholders in different Campaigns. 'placeholderTypes': [PLACEHOLDER_SITELINKS] } response = campaign_feed_service.mutate([ {'operator': 'ADD', 'operand': campaign_feed} ])[0] if 'value' in response: campaign_feed = response['value'][0] print ('Campaign with ID %s was associated with feed with ID %s.' % (campaign_feed['campaignId'], campaign_feed['feedId'])) else: raise Error('No campaign feeds were added.')
def main(client, campaign_id): # Initialize appropriate service. feed_service = client.GetFeedService(version='v201306') feed_item_service = client.GetFeedItemService(version='v201306') feed_mapping_service = client.GetFeedMappingService(version='v201306') campaign_feed_service = client.GetCampaignFeedService(version='v201306') sitelinks_data = {} # Create site links feed first. site_links_feed = { 'name': 'Feed For Site Links', 'attributes': [ {'type': 'STRING', 'name': 'Link Text'}, {'type': 'URL', 'name': 'Link URL'} ] } response = feed_service.mutate([ {'operator': 'ADD', 'operand': site_links_feed} ])[0] if 'value' in response: feed = response['value'][0] link_text_feed_attribute_id = feed['attributes'][0]['id'] link_url_feed_attribute_id = feed['attributes'][1]['id'] print ('Feed with name \'%s\' and ID \'%s\' was added with' % (feed['name'], feed['id'])) print ('\tText attribute ID \'%s\' and URL attribute ID \'%s\'.' % (link_text_feed_attribute_id, link_url_feed_attribute_id)) sitelinks_data['feedId'] = feed['id'] sitelinks_data['linkTextFeedId'] = link_text_feed_attribute_id sitelinks_data['linkUrlFeedId'] = link_url_feed_attribute_id else: raise Error('No feeds were added.') # Create site links feed items. items_data = [ {'text': 'Home', 'url': 'http://www.example.com'}, {'text': 'Stores', 'url': 'http://www.example.com/stores'}, {'text': 'On Sale', 'url': 'http://www.example.com/sale'}, {'text': 'Support', 'url': 'http://www.example.com/support'}, {'text': 'Products', 'url': 'http://www.example.com/products'}, {'text': 'About', 'url': 'http://www.example.com/about'} ] feed_items = [] for item in items_data: feed_items.append({ 'feedId': sitelinks_data['feedId'], 'attributeValues': [ { 'feedAttributeId': sitelinks_data['linkTextFeedId'], 'stringValue': item['text'] }, { 'feedAttributeId': sitelinks_data['linkUrlFeedId'], 'stringValue': item['url'] } ] }) feed_items_operations = [{'operator': 'ADD', 'operand': item} for item in feed_items] response = feed_item_service.mutate(feed_items_operations)[0] if 'value' in response: sitelinks_data['feedItemIds'] = [] for feed_item in response['value']: print 'Feed item with ID %s was added.' % feed_item['feedItemId'] sitelinks_data['feedItemIds'].append(feed_item['feedItemId']) else: raise Error('No feed items were added.') # Create site links feed mapping. feed_mapping = { 'placeholderType': PLACEHOLDER_SITELINKS, 'feedId': sitelinks_data['feedId'], 'attributeFieldMappings': [ { 'feedAttributeId': sitelinks_data['linkTextFeedId'], 'fieldId': PLACEHOLDER_FIELD_SITELINK_LINK_TEXT }, { 'feedAttributeId': sitelinks_data['linkUrlFeedId'], 'fieldId': PLACEHOLDER_FIELD_SITELINK_LINK_URL } ] } response = feed_mapping_service.mutate([ {'operator': 'ADD', 'operand': feed_mapping} ])[0] if 'value' in response: feed_mapping = response['value'][0] print ('Feed mapping with ID %s and placeholder type %s was saved for feed' ' with ID %s.' % (feed_mapping['feedMappingId'], feed_mapping['placeholderType'], feed_mapping['feedId'])) else: raise Error('No feed mappings were added.') # Create site links campaign feed. operands = [] for feed_item_id in sitelinks_data['feedItemIds']: operands.append({ 'xsi_type': 'ConstantOperand', 'type': 'LONG', 'longValue': feed_item_id }) feed_item_function = { 'operator': 'IN', 'lhsOperand': [ {'xsi_type': 'RequestContextOperand', 'contextType': 'FEED_ITEM_ID'} ], 'rhsOperand': operands } # Optional: to target to a platform, define a function and 'AND' it with the # feed item ID link: platform_function = { 'operator': 'EQUALS', 'lhsOperand': [ { 'xsi_type': 'RequestContextOperand', 'contextType': 'DEVICE_PLATFORM' } ], 'rhsOperand': [ { 'xsi_type': 'ConstantOperand', 'type': 'STRING', 'stringValue': 'Mobile' } ] } combined_function = { 'operator': 'AND', 'lhsOperand': [ {'xsi_type': 'FunctionOperand', 'value': feed_item_function}, {'xsi_type': 'FunctionOperand', 'value': platform_function} ] } campaign_feed = { 'feedId': sitelinks_data['feedId'], 'campaignId': campaign_id, 'matchingFunction': combined_function, # Specifying placeholder types on the CampaignFeed allows the same feed # to be used for different placeholders in different Campaigns. 'placeholderTypes': [PLACEHOLDER_SITELINKS] } response = campaign_feed_service.mutate([ {'operator': 'ADD', 'operand': campaign_feed} ])[0] if 'value' in response: campaign_feed = response['value'][0] print ('Campaign with ID %s was associated with feed with ID %s.' % (campaign_feed['campaignId'], campaign_feed['feedId'])) else: raise Error('No campaign feeds were added.')
def DownloadCsvReport(self, report_job_id=-1, report_xml=''): """Download and return report data in CSV format. Report consists of two parts: table and totals. Table contains column names and rows of data. The outgoing CSV will contain only columns and data rows. The totals are not included, but can be easily calculated from the CSV data. Args: [optional] report_job_id: str ID of the report job. report_xml: str Report in XML format. Used for testing and debugging. Returns: str Report data if all data is in ASCII. unicode Report data if report contains non-ASCII characters. None If report failed. """ # Get XML report data. if not report_xml and report_job_id > -1: report_xml = self.DownloadXmlReport(report_job_id) if report_xml is None: return None # Prepare XML for DOM construction. report_xml = report_xml.replace( '<?xml version="1.0" standalone="yes"?>', '') csv_rows = [] try: # Construct DOM object. dom = minidom.parseString(report_xml) # Get data columns. columns = [] column_dom = dom.getElementsByTagName('column') for column_item in column_dom: if column_item.hasAttributes(): columns.append(column_item.attributes.item(0).value) # Get data rows. rows = [] row_dom = dom.getElementsByTagName('row') for row_item in row_dom: if row_item.hasAttributes(): attrs = row_item.attributes row = {} for index in range(attrs.length): row[attrs.item(index).name] = attrs.item(index).value rows.append(row) # Combine columns and rows into CSV format. csv_rows = [[column for column in columns]] for row in rows: csv_rows.append([row[column] for column in columns]) except: msg = ('Unable to parse report\'s data. Please, file a bug at ' 'http://code.google.com/p/google-api-adwords-python-lib/' 'issues/list.') raise Error(msg) buffer = cStringIO.StringIO() # A workaround for reports that include non-ASCII characters (i.e. ø). try: csv.writer(buffer).writerows(csv_rows) except (UnicodeEncodeError, Exception): unicode_csv_rows = [] for row in csv_rows: unicode_csv_rows.append(','.join( [Utils.CsvEscape(item) for item in row])) return unicode('\n'.join(unicode_csv_rows)) return buffer.getvalue().rstrip()
def CallRawMethod(self, soap_message): """Makes an API call by POSTing a raw SOAP XML message to the server. Args: soap_message: str SOAP XML message. Returns: tuple Raw XML response from the API method. Raises: Error: if the SOAP call is not successful. Most likely this is the result of the server sending back an HTTP error, such as a 502. """ self._lock.acquire() try: buf = self._buffer_class(xml_parser=self._config['xml_parser'], pretty_xml=Utils.BoolTypeConvert( self._config['pretty_xml'])) http_header = { 'post': self._service_url, 'host': Utils.GetNetLocFromUrl(self._op_config['server']), 'user_agent': '%s; CallRawMethod' % self.__class__.__name__, 'content_type': 'text/xml; charset=\"UTF-8\"', 'content_length': '%d' % len(soap_message), 'soap_action': '' } if self._headers.get('oauth2credentials'): self._headers['oauth2credentials'].apply(http_header) self._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' % ('*' * 3, '*' * 46, http_header['post'], http_header['host'], http_header['user_agent'], http_header['content_type'], http_header['content_length'], http_header['soap_action'])) if self._headers.get('oauth2credentials'): buf.write('Authorization: ' + http_header['Authorization'] + '\n') buf.write('%s\n%s Outgoing SOAP %s\n%s\n%s\n' % ('*' * 72, '*' * 3, '*' * 54, soap_message, '*' * 72)) if self._op_config['http_proxy']: real_address = self._op_config['http_proxy'] else: real_address = http_header['host'] # Construct header and send SOAP message. web_service = httplib.HTTPS(real_address) 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']) if self._headers.get('oauth2credentials'): web_service.putheader('Authorization', http_header['Authorization']) 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))) self._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 = html_error else: msg = 'Unknown error.' raise Error(msg) self._HandleLogsAndErrors(buf, self._start_time, self._stop_time) finally: self._lock.release() if self._config['wrap_in_tuple']: response = MessageHandler.WrapInTuple(response) return response
def CallMethod(*args): """Perform a SOAP call.""" try: self._lock.acquire() self._ReadyOAuth() self._ReadyCompression() self._SetHeaders() args = self._TakeActionOnSoapCall(method_name, args) method_info = self._GetMethodInfo(method_name) method_attrs_holder = None if len(method_info[MethodInfoKeys.INPUTS]) > 1: self._ConfigureArgOrder(method_name, method_info[MethodInfoKeys.INPUTS]) if not method_info[MethodInfoKeys.INPUTS]: # Don't put any namespaces other than this service's namespace on # calls with no input params. method_attrs_holder = self._soappyservice.soapproxy.methodattrs self._soappyservice.soapproxy.methodattrs = { 'xmlns': self._namespace } if len(args) != len(method_info[MethodInfoKeys.INPUTS]): raise TypeError(''.join([ method_name + '() takes exactly ', str( len(self._soappyservice.methods[method_name]. inparams)), ' argument(s). (', str(len(args)), ' given)' ])) ksoap_args = {} for i in range(len(method_info[MethodInfoKeys.INPUTS])): if Utils.BoolTypeConvert(self._config['strict']): SanityCheck.SoappySanityCheck( self._soappyservice, args[i], method_info[ MethodInfoKeys.INPUTS][i][MethodInfoKeys.NS], method_info[MethodInfoKeys.INPUTS][i][ MethodInfoKeys.TYPE], method_info[MethodInfoKeys.INPUTS][i][ MethodInfoKeys.MAX_OCCURS]) element_name = str(method_info[MethodInfoKeys.INPUTS][i][ MethodInfoKeys.ELEMENT_NAME]) ksoap_args[element_name] = MessageHandler.PackForSoappy( args[i], method_info[MethodInfoKeys.INPUTS][i][ MethodInfoKeys.NS], method_info[ MethodInfoKeys.INPUTS][i][MethodInfoKeys.TYPE], self._soappyservice, self._wrap_lists, self._namespace_extractor) ksoap_args = self._TakeActionOnPackedArgs( method_name, ksoap_args) buf = self._buffer_class(xml_parser=self._config['xml_parser'], pretty_xml=Utils.BoolTypeConvert( self._config['pretty_xml'])) sys_stdout_monkey_lock.acquire() try: old_stdout = sys.stdout sys.stdout = buf error = {} response = None start_time = time.strftime('%Y-%m-%d %H:%M:%S') try: response = MessageHandler.UnpackResponseAsDict( soap_service_method(**ksoap_args)) except Exception, e: error['data'] = e stop_time = time.strftime('%Y-%m-%d %H:%M:%S') # Restore stdout sys.stdout = old_stdout finally: sys_stdout_monkey_lock.release() if isinstance(response, Error): error = response if not Utils.BoolTypeConvert(self._config['raw_debug']): self._HandleLogsAndErrors(buf, start_time, stop_time, error) # When debugging mode is ON, fetch last traceback. if Utils.BoolTypeConvert(self._config['debug']): if Utils.LastStackTrace( ) and Utils.LastStackTrace() != 'None': 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 = 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']) return Error(msg) if Utils.BoolTypeConvert(self._config['raw_response']): response = buf.GetRawSoapIn() elif error: response = error else: output_types = [ (out_param[MethodInfoKeys.NS], out_param[MethodInfoKeys.TYPE], out_param[MethodInfoKeys.MAX_OCCURS]) for out_param in method_info[MethodInfoKeys.OUTPUTS] ] response = MessageHandler.RestoreListTypeWithSoappy( response, self._soappyservice, output_types) if Utils.BoolTypeConvert(self._config['wrap_in_tuple']): response = MessageHandler.WrapInTuple(response) # Restore method_attrs if they were over-ridden if method_attrs_holder: self._soappyservice.soapproxy.methodattrs = method_attrs_holder return response