def ValidateServer(server, version): """Sanity check for API server. Args: server: str API server to access for this API call. version: str API version being used to access the server. """ # Map of supported API servers and versions. prod = { 'v13': 'https://adwords.google.com', 'v200909': 'https://adwords.google.com', 'v201003': 'https://adwords.google.com' } sandbox = { 'v13': 'https://sandbox.google.com', 'v200909': 'https://adwords-sandbox.google.com', 'v201003': 'https://adwords-sandbox.google.com' } if server not in prod.values() and server not in sandbox.values(): msg = ('Given API server, \'%s\', is not valid. Expecting one of %s.' % (server, sorted(prod.values() + sandbox.values())[1:])) raise ValidationError(msg) if version not in prod.keys() and version not in sandbox.keys(): msg = ( 'Geven API version, \'%s\', is not valid. Expecting one of %s.' % (version, sorted(set(prod.keys() + sandbox.keys())))) raise ValidationError(msg) if server != prod[version] and server != sandbox[version]: msg = ( 'Given API version, \'%s\', is not compatible with given server, ' '\'%s\'.' % (version, server)) raise ValidationError(msg)
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 __init__(self, headers, config, op_config, lock, logger): """Inits BulkMutateJobService. 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 """ # NOTE(api.sgrinberg): Custom handling for BulkMutateJobService, whose # group in URL is 'job/' which is different from its namespace 'cm/'. url = [ op_config['server'], 'api/adwords', 'job', op_config['version'], self.__class__.__name__ ] 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 SanityCheck self.__web_services = None 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 ValidateCriterion(operator, criterion, web_services): """Validate Criterion object. A Criterion object is one of Keyword, Website, Placement. Args: operator: str operator to use. criterion: dict Criterion object. web_services: module for web services. Returns: dict/Criterion updated criterion object or Criterion instance. """ if IsPyClass(criterion): return criterion glob_sanity_check.ValidateTypes(((criterion, dict),)) if operator in ('ADD', ''): if 'criterionType' in criterion: new_criterion = GetPyClass(criterion['criterionType'], web_services) elif 'type' in criterion: new_criterion = GetPyClass(criterion['type'], web_services) elif 'Criterion_Type' in criterion: new_criterion = GetPyClass(criterion['Criterion_Type'], web_services) else: msg = 'The \'criterionType\' or \'type\' of the criterion is missing.' raise ValidationError(msg) elif operator in ('SET', 'REMOVE'): new_criterion = GetPyClass('Criterion', web_services) for key in criterion: if criterion[key] == 'None': continue glob_sanity_check.ValidateTypes(((criterion[key], (str, unicode)),)) new_criterion.__dict__.__setitem__('_%s' % key, criterion[key]) return new_criterion
def ValidateBiddingStrategy(strategy, web_services): """Validate BiddingStrategy object. A BiddingStrategy is one of BudgetOptimizer, ConversionOptimizer, ManualCPC, ManualCPM. Args: strategy: dict BiddingStrategy object. web_services: module for web services. Returns: BiddingStrategy instance. """ if IsPyClass(strategy): return strategy glob_sanity_check.ValidateTypes(((strategy, dict),)) if 'type' in strategy: new_strategy = GetPyClass(strategy['type'], web_services) elif 'BiddingStrategy_Type' in strategy: new_strategy = GetPyClass(strategy['BiddingStrategy_Type'], web_services) else: msg = 'The \'type\' of the bidding transition is missing.' raise ValidationError(msg) for key in strategy: if strategy[key] == 'None': continue if key in ('bidCeiling',): data = ValidateMoney(strategy[key], web_services) else: glob_sanity_check.ValidateTypes(((strategy[key], (str, unicode)),)) data = strategy[key] new_strategy.__dict__.__setitem__('_%s' % key, data) return new_strategy
def __init__(self, headers, config, op_config, url, lock, logger=None): """Inits WebService. 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. url: str url of the web service to call. lock: thread.lock the thread lock. logger: Logger the instance of Logger """ self.__headers = headers self.__config = config self.__op_config = op_config self.__url = url self.__lock = lock self.__logger = logger if self.__logger is None: self.__logger = Logger(self.__config['log_home']) if (self.__config['soap_lib'] == SOAPPY and self.__headers.values().count(None) < 2 and (len(self.__headers.values()) > len(set(self.__headers.values())) and not self.__headers.has_key('useragent'))): if self.__headers.values().count('') < 2: msg = ('Two (or more) values in \'headers\' dict can not be the same. ' 'See, %s/issues/detail?id=27.' % LIB_URL) raise ValidationError(msg) else: self.__headers = {} for key in headers: if headers[key]: self.__headers[key] = headers[key]
def ValidateAd(operator, ad, web_services): """Validate Ad object. An Ad object is one of DeprecatedAd, MobileAd, MobileImageAd, ImageAd, LocalBusinessAd, TemplateAd, TextAd, Args: operator: str operator to use. ad: dict ad object. web_services: module for web services. Returns: dict/Ad updated ad object or Ad instance. """ if IsPyClass(ad): return ad glob_sanity_check.ValidateTypes(((ad, dict),)) if operator in ('ADD', ''): if 'adType' in ad: new_ad = GetPyClass(ad['adType'], web_services) elif 'type' in ad: new_ad = GetPyClass(ad['type'], web_services) elif 'Ad_Type' in ad: new_ad = GetPyClass(ad['Ad_Type'], web_services) else: msg = 'The \'adType\' or \'type\' of the ad is missing.' raise ValidationError(msg) elif operator in ('SET', 'REMOVE'): new_ad = GetPyClass('Ad', web_services) for key in ad: if ad[key] == 'None': continue if key in ('productImage', 'image', 'businessImage', 'customIcon', 'icon'): data = ValidateImage(ad[key], web_services) elif key in ('video',): data = ValidateVideo(ad[key], web_services) elif key in ('markupLanguages', 'mobileCarriers'): glob_sanity_check.ValidateTypes(((ad[key], list),)) for item in ad[key]: glob_sanity_check.ValidateTypes(((item, (str, unicode)),)) data = ad[key] elif key in ('target',): data = ValidateProximityTarget(ad[key], web_services) elif key in ('adUnionId',): data = ValidateEntityId(ad[key], 'AdUnionId', web_services) elif key in ('templateElements',): glob_sanity_check.ValidateTypes(((ad[key], list),)) elements = [] for item in ad[key]: elements.append(ValidateTemplateElement(item, web_services)) data = elements elif key in ('dimensions',): data = ValidateDimensions(ad[key], web_services) else: glob_sanity_check.ValidateTypes(((ad[key], (str, unicode)),)) data = ad[key] new_ad.__dict__.__setitem__('_%s' % key, data) return new_ad
def ValidateBids(bids, web_services): """Validate Bids object. A Bids object is on of AdGroupBids, AdGroupCriterionBids, BudgetOptimizerAdGroupBids, BudgetOptimizerAdGroupCriterionBids, ConversionOptimizerAdGroupBids, ConversionOptimizerAdGroupCriterionBids, ManualCPCAdGroupBids, ManualCPCAdGroupCriterionBids, ManualCPMAdGroupBids, ManualCPMAdGroupCriterionBids, PositionPreferenceAdGroupCriterionBids. Args: bids: dict Bids object. web_services: module for web services. Returns: XxxBids instance. """ if IsPyClass(bids): return bids glob_sanity_check.ValidateTypes(((bids, dict),)) if 'type' in bids: new_bids = GetPyClass(bids['type'], web_services) elif 'AdGroupBids_Type' in bids: new_bids = GetPyClass(bids['AdGroupBids_Type'], web_services) elif 'AdGroupCriterionBids_Type' in bids: new_bids = GetPyClass(bids['AdGroupCriterionBids_Type'], web_services) else: msg = 'The \'type\' of the bid is missing.' raise ValidationError(msg) for key in bids: if bids[key] == 'None': continue if key in ('proxyBid', 'maxCpc', 'maxCpm', 'proxyMaxCpc', 'proxyKeywordMaxCpc', 'proxySiteMaxCpc', 'targetCpa', 'keywordMaxCpc', 'keywordContentMaxCpc', 'siteMaxCpc', 'targetCpa'): data = ValidateBid(bids[key], web_services) elif key in ('positionPreferenceBids',): glob_sanity_check.ValidateTypes(((bids[key], dict),)) new_bids = GetPyClass('PositionPreferenceAdGroupCriterionBids', web_services) for sub_key in bids[key]: if sub_key in ('proxyMaxCpc',): data = ValidateBid(bids[key][sub_key], web_services) else: glob_sanity_check.ValidateTypes(((bids[key][sub_key], (str, unicode)),)) data = bids[key][sub_key] new_bids.__dict__.__setitem__('_%s' % sub_key, data) data = new_bids else: glob_sanity_check.ValidateTypes(((bids[key], (str, unicode)),)) data = bids[key] new_bids.__dict__.__setitem__('_%s' % key, data) return new_bids
def ValidateHeadersForServer(headers, server): """Check if provided headers match the ones expected on the provided server. The SOAP headers on Sandbox server are different from production. See http://code.google.com/apis/adwords/docs/developer/adwords_api_sandbox.html. """ fits_sandbox = False # The clientEmail SOAP header in Sandbox has to be of specific format, with # "client_" prepended (e.g., [email protected]). if ('clientEmail' in headers and headers['clientEmail'] and headers['clientEmail'].find('client_', 0, 7) > -1): fits_sandbox = True # The developerToken SOAP header in Sandbox has to be same as email SOAP # header with appended "++" and the currency code. if ('email' in headers and headers['email'] and headers['developerToken'].find('%s++' % headers['email'], 0, len(headers['email']) + 2) > -1): fits_sandbox = True elif ('authToken' in headers and headers['authToken'] and headers['developerToken'].find('++') == len(headers['developerToken']) - 5): fits_sandbox = True else: fits_sandbox = False # Sandbox server is identifying by the "sandbox" part in the URL (e.g., # https://sandbox.google.com or https://adwords-sandbox.google.com). if server.find('sandbox') > -1: if not fits_sandbox: msg = ( 'Invalid headers for \'%s\', see http://code.google.com/apis/adwords/docs/developer/adwords_api_sandbox.html#requestheaders.' % server) raise ValidationError(msg) elif server.find('sandbox') < 0: if fits_sandbox: msg = ( 'Invalid headers for \'%s\', see http://code.google.com/apis/adwords/docs/developer/index.html#adwords_api_intro_request.' % server) raise ValidationError(msg)
def ValidateConfigSoapLib(soap_lib): """Sanity check for SOAP library. Args: soap_lib: str SOAP library to use. """ if (not isinstance(soap_lib, str) or not IsConfigUserInputValid(soap_lib, [SOAPPY, ZSI])): msg = ( 'Invalid input for %s \'%s\', expecting %s or %s of type <str>.' % (type(soap_lib), soap_lib, SOAPPY, ZSI)) raise ValidationError(msg)
def ValidateConfigXmlParser(xml_parser): """Sanity check for XML parser. Args: xml_parser: str XML parser to use. """ if (not isinstance(xml_parser, str) or not IsConfigUserInputValid(xml_parser, [PYXML, ETREE])): msg = ( 'Invalid input for %s \'%s\', expecting %s or %s of type <str>.' % (type(xml_parser), xml_parser, PYXML, ETREE)) raise ValidationError(msg)
def ValidateTypes(vars_tpl): """Check types for a set of variables. Args: vars_tpl: tuple set of variables to check. """ for var, var_types in vars_tpl: if not isinstance(var_types, tuple): var_types = (var_types, ) for var_type in var_types: if IsType(var, var_type): return msg = ('The \'%s\' is of type %s, expecting one of %s.' % (var, type(var), var_types)) raise ValidationError(msg)
def ValidateRequiredHeaders(headers): """Sanity check for required authentication elements. All required authentication headers have to be set in order to make successful API request. Args: headers: dict authentication headers. """ req_headers = ('email', 'password', 'userAgent', 'developerToken') if 'authToken' in headers and headers['authToken']: req_headers = ('authToken', 'userAgent', 'developerToken') for key in req_headers: if key not in headers or not headers[key]: msg = ('Required authentication header \'%s\' is missing.' % key) raise ValidationError(msg)
def ValidateTarget(target, web_services): """Validate Target object. A Target object is one of AdScheduleTarget, AgeTarget, CityTarget, CountryTarget, DemographicTarget, GenderTarget, GeoTarget, LanguageTarget, MetroTarget, NetworkTarget, PlatformTarget, PolygonTarget, ProvinceTarget, ProximityTarget, Target. Args: target: list a target object. web_services: module for web services. Returns: XxxTarget instance. """ if IsPyClass(target): return target glob_sanity_check.ValidateTypes(((target, dict),)) if 'type' in target: new_target = GetPyClass(target['type'], web_services) elif 'Target_Type' in target: new_target = GetPyClass(target['Target_Type'], web_services) else: msg = 'The \'type\' of the target is missing.' raise ValidationError(msg) for key in target: if target[key] == 'None': continue if key in ('vertices',): glob_sanity_check.ValidateTypes(((target[key], list),)) geo_points = [] for item in target[key]: geo_points.append(ValidateGeoPoint(item, web_services)) data = geo_points elif key in ('address',): data = ValidateAddress(target[key], web_services) elif key in ('geoPoint',): data = ValidateGeoPoint(target[key], web_services) else: glob_sanity_check.ValidateTypes(((target[key], (str, unicode)),)) data = target[key] new_target.__dict__.__setitem__('_%s' % key, data) return new_target
def __LoadAuthCredentials(self): """Load existing authentication credentials from auth.pkl. Returns: dict dictionary object with populated authentication credentials. """ auth = {} if os.path.exists(Client.auth_pkl): fh = open(Client.auth_pkl, 'r') try: auth = pickle.load(fh) finally: fh.close() if not auth: msg = 'Authentication data is missing.' raise ValidationError(msg) return auth
def GetPyClass(name, web_services): """Return Python class for a given class name. Args: name: str name of the Python class to return. web_services: module for web service. Returns: Python class. """ for index in xrange(MAX_TARGET_NAMESPACE): try: pyclass = eval('web_services.ns%s.%s_Def(\'%s\').pyclass' % (index, name, name)) break except AttributeError: if index == MAX_TARGET_NAMESPACE - 1: version = web_services.__dict__['__name__'].split('.')[2] msg = ('Given API version, %s, is not compatible with \'%s\' class.' % (version, name)) raise ValidationError(msg) return pyclass
def ValidateJobOperation(operation, web_services): """Validate JobOperation object. Args: operation: dict JobOperation object. web_services: module for web services. Returns: JobOperation instance. """ if IsPyClass(operation): return operation glob_sanity_check.ValidateTypes(((operation, dict),)) if 'type' not in operation: msg = 'A job operation type is missing.' raise ValidationError(msg) operation_type = '%sOperation' % operation['type'] new_operation = GetPyClass(operation_type, web_services) operation = ValidateOperation(operation, web_services) for key in operation: new_operation.__dict__.__setitem__('_%s' % key, operation[key]) return new_operation
def ValidateBidLandscapeSelector(selector, web_services): """Validate BidLandscapeSelector object. Args: selector: dict selector object. web_services: module for web services. """ if IsPyClass(selector): return selector glob_sanity_check.ValidateTypes(((selector, dict),)) if 'type' in selector: new_selector = GetPyClass(selector['type'], web_services) elif 'BidLandscapeSelector_Type' in selector: new_selector = GetPyClass(selector['BidLandscapeSelector_Type'], web_services) else: msg = 'The \'type\' of the bid landscape selector is missing.' raise ValidationError(msg) for key in selector: if key in ('idFilters',): glob_sanity_check.ValidateTypes(((selector[key], list),)) filters = [] for item in selector[key]: glob_sanity_check.ValidateTypes(((item, dict),)) filter = GetPyClass('BidLandscapeIdFilter', web_services) for sub_key in item: glob_sanity_check.ValidateTypes(((item[sub_key], (str, unicode)),)) filter.__dict__.__setitem__('_%s' % sub_key, item[sub_key]) filters.append(filter) data = filters else: data = selector[key] new_selector.__dict__.__setitem__('_%s' % key, data) return new_selector
from ZSI.version import Version as ZSI_VERSION except ImportError: msg = 'ZSI v%s or newer is required.' % MIN_ZSI_VERSION raise MissingPackageError(msg) else: if list(ZSI.version.Version) < (list(map(eval, MIN_ZSI_VERSION.split('.')))): msg = 'ZSI v%s or newer is required.' % MIN_ZSI_VERSION raise MissingPackageError(msg) # NOTE(api.sgrinberg): Keep this check until ZSI version higher than 2.0.0 # fixes known bug with bad NS (see issue# 84). elif list(ZSI.version.Version) > (list( map(eval, MAX_ZSI_VERSION.split('.')))): msg = ('ZSI v%s is not supported. Please use v%s.' % ('.'.join(map(str, ZSI.version.Version)), MIN_ZSI_VERSION)) raise ValidationError(msg) def GetServiceConnection(headers, config, url, http_proxy, service_name, loc): """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 for this API call. service_name: str API service name. loc: service locator. Returns:
def __init__(self, headers=None, config=None, path=None, use_mcc=False, soap_lib=None): """Inits Client. Args: [optional] headers: dict object with populated authentication credentials. config: dict object with client configuration values. path: str relative or absolute path to home directory (i.e. location of pickles and logs/). use_mcc: bool state of the API request, whether to use MCC account. soap_lib: str soap library to use. Ex: headers = { 'email': '*****@*****.**', 'password': '******', 'clientEmail': '*****@*****.**', 'clientCustomerId': '1234567890', 'userAgent': 'GoogleTest', 'developerToken': '[email protected]++USD', 'validateOnly': 'n' } config = { 'home': '/path/to/home', 'log_home': '/path/to/logs/home', 'soap_lib': ZSI, 'xml_parser': PYXML, 'debug': 'n', 'xml_log': 'y', 'request_log': 'y', 'raw_response': 'n', 'use_strict': 'y', 'use_auth_token': 'y', 'use_pretty_xml': 'y', 'access': '' } path = '/path/to/home' use_mcc = False soap_lib = SOAPPY """ self.__lock = thread.allocate_lock() self.__loc = None self.__is_mcc = use_mcc if path is not None: # Update absolute path for a given instance of Client, based on provided # relative path. if os.path.isabs(path): Client.home = path else: # NOTE(api.sgrinberg): Keep first parameter of join() as os.getcwd(), # do not change it to Client.home. Otherwise, may break when multiple # instances of Client exist during program run. Client.home = os.path.join(os.getcwd(), path) # Update location for both pickles. Client.auth_pkl = os.path.join(Client.home, 'auth.pkl') Client.config_pkl = os.path.join(Client.home, 'config.pkl') # Only load from the pickle if config wasn't specified. self.__config = config or self.__LoadConfigValues() self.__SetMissingDefaultConfigValues(self.__config) self.__config['home'] = Client.home # Load the SOAP library to use. if soap_lib is not None: SanityCheck.ValidateConfigSoapLib(soap_lib) self.__config['soap_lib'] = soap_lib # Validate XML parser to use. SanityCheck.ValidateConfigXmlParser(self.__config['xml_parser']) # Initialize units and operations for current instance of Client object # (using list to take advantage of Python's pass-by-reference). self.__config['units'] = [0] self.__config['operations'] = [0] self.__config['last_units'] = [0] self.__config['last_operations'] = [0] # Only load from the pickle if 'headers' wasn't specified. if headers is None: self.__headers = self.__LoadAuthCredentials() else: self.__headers = headers # Internally, store user agent as 'userAgent'. if 'useragent' in self.__headers: self.__headers['userAgent'] = self.__headers['useragent'] self.__headers = Utils.UnLoadDictKeys(self.__headers, ['useragent']) if Utils.BoolTypeConvert(self.__config['use_strict']): SanityCheck.ValidateRequiredHeaders(self.__headers) # Load validateOnly header, if one was set. if 'validateOnly' in self.__headers: self.__headers['validateOnly'] = str( Utils.BoolTypeConvert(self.__headers['validateOnly'])) # Load/set authentication token. try: if Utils.BoolTypeConvert(self.__config['use_auth_token']): if headers and 'authToken' in headers and headers['authToken']: self.__headers['authToken'] = headers['authToken'] elif 'email' in self.__headers and 'password' in self.__headers: self.__headers['authToken'] = Utils.GetAuthToken( self.__headers['email'], self.__headers['password']) else: msg = 'Authentication data, email or/and password, is missing.' raise ValidationError(msg) self.__config['auth_token_epoch'] = time.time() except AuthTokenError: # We would end up here if non-valid Google Account's credentials were # specified. This is useful for when dummy credentials are set in # unit tests and requests are being made against v13. If v200909 is being # used and invalid credentials specified, this will be caught in # aw_api.WebService.CallMethod(). self.__headers['authToken'] = None self.__config['auth_token_epoch'] = 0 # Insert library name and version into userAgent. if (self.__headers['userAgent'].rfind( '%s v%s' % (LIB_SHORT_NAME, LIB_VERSION)) == -1): # Make sure library name shows up only once. if (self.__headers['userAgent'].rfind(LIB_SHORT_NAME) > -1 or self.__headers['userAgent'].rfind(LIB_NAME) > -1): pattern = re.compile('.*?: ') self.__headers['userAgent'] = pattern.sub( '', self.__headers['userAgent'], 1) self.__headers['userAgent'] = ( '%s v%s: %s' % (LIB_SHORT_NAME, LIB_VERSION, self.__headers['userAgent'])) # Sync library's version in the new userAgent with the one in the pickle. if headers is None: self.__WriteUpdatedAuthValue('userAgent', self.__headers['userAgent']) # Initialize logger. self.__logger = Logger(self.__config['log_home'])
def ValidateSearchParameter(param, web_services): """Validate SearchParameter object. A SearchParameter is one of AdTypeSearchParameter, AverageTargetedMonthlySearchesSearchParameter, CompetitionSearchParameter, CountryTargetSearchParameter, ExcludedKeywordSearchParameter, GlobalMonthlySearchesSearchParameter, IncludeAdultContentSearchParameter, KeywordCategoryIdSearchParameter, KeywordMatchTypeSearchParameter, LanguageTargetSearchParameter, MobileSearchParameter, NgramGroupsSearchParameter, PlacementTypeSearchParameter, RelatedToKeywordSearchParameter, RelatedToUrlSearchParameter, SeedAdGroupIdSearchParameter Args: search_parameter: dict SearchParameter object. web_services: module for web services. Returns: XxxSearchParameter instance. """ if IsPyClass(param): return param glob_sanity_check.ValidateTypes(((param, dict),)) if 'type' in param: new_param = GetPyClass(param['type'], web_services) elif 'SearchParameter_Type' in param: new_param = GetPyClass('SearchParameter_Type', web_services) else: msg = 'The \'type\' of the search parameter is missing.' raise ValidationError(msg) for key in param: if param[key] == 'None': continue if key in ('adTypes', 'levels', 'keywordMatchTypes', 'ngramGroups', 'categoryIds', 'placementTypes', 'urls', 'included', 'excluded'): glob_sanity_check.ValidateTypes(((param[key], list),)) items = [] for item in param[key]: glob_sanity_check.ValidateTypes(((item, (str, unicode)),)) items.append(item) data = items elif key in ('operation',): data = ValidateLongComparisonOperation(param[key], web_services) elif key in ('keywords',): glob_sanity_check.ValidateTypes(((param[key], list),)) kws = [] for item in param[key]: kws.append(ValidateKeyword(item, web_services)) data = kws elif key in ('countryTargets',): glob_sanity_check.ValidateTypes(((param[key], list),)) targets = [] for item in param[key]: targets.append(ValidateCountryTarget(item, web_services)) data = targets elif key in ('languageTargets',): glob_sanity_check.ValidateTypes(((param[key], list),)) targets = [] for item in param[key]: targets.append(ValidateLanguageTarget(item, web_services)) data = targets else: glob_sanity_check.ValidateTypes(((param[key], (str, unicode)),)) data = param[key] new_param.__dict__.__setitem__('_%s' % key, data) return new_param
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)
def ValidateOperation(operation, web_services): """Validate Operation object. Args: operation: dict operation object. web_services: module for web services. Returns: dict updated Operation object. """ if IsPyClass(operation): return operation glob_sanity_check.ValidateTypes(((operation, dict),)) caller = { 'name': web_services.__name__.split('.')[-1].split('Service')[0], 'typed': False } if 'type' in operation: caller['name'] = operation['type'] # Custom handler for services that require concrete types for ADD and SET # operators. if caller['name'] in ('AdGroupCriterion', 'BulkMutateJob', 'CampaignAdExtension', 'CampaignCriterion', 'CampaignTarget'): caller['typed'] = True operator = '' for key in operation: if key in ('operator',): glob_sanity_check.ValidateTypes(((operation[key], (str, unicode)),)) operator = operation[key] elif key in ('operand',): glob_sanity_check.ValidateTypes(((operation[key], dict),)) operand = operation[key] if (caller['typed'] and 'type' in operand) or 'type' in operation: caller['typed'] = True if 'type' in operation and 'type' in operand: type = operand['type'] elif 'type' in operation: type = operation['type'] else: type = operand['type'] new_operand = GetPyClass(type, web_services) elif not caller['typed']: pass else: msg = 'The \'type\' of the operand is missing.' raise ValidationError(msg) for sub_key in operand: if sub_key in ('ad',): data = ValidateAd(operator, operand[sub_key], web_services) elif sub_key in ('bids',): data = ValidateBids(operand[sub_key], web_services) elif sub_key in ('criterion',): data = ValidateCriterion(operator, operand[sub_key], web_services) elif sub_key in ('minBids',): glob_sanity_check.ValidateTypes(((operand[sub_key], list),)) bids = [] for item in operand[sub_key]: bids.append(ValidateBid(item, web_services)) data = bids elif sub_key in ('budget',): data = ValidateBudget(operand[sub_key], web_services) elif sub_key in ('biddingStrategy',): data = ValidateBiddingStrategy(operand[sub_key], web_services) elif sub_key in ('frequencyCap',): data = ValidateFrequencyCap(operand[sub_key], web_services) elif sub_key in ('targets',): glob_sanity_check.ValidateTypes(((operand[sub_key], list),)) targets = [] for item in operand[sub_key]: targets.append(ValidateTarget(item, web_services)) data = targets elif sub_key in ('request',): data = ValidateBulkMutateRequest(operand[sub_key], web_services) elif sub_key in ('adExtension',): data = ValidateAdExtension(operand[sub_key], web_services) elif sub_key in ('overrideInfo',): data = ValidateOverrideInfo(operand[sub_key], web_services) elif sub_key in ('customerIds',): glob_sanity_check.ValidateTypes(((operand[sub_key], list),)) for item in operand[sub_key]: glob_sanity_check.ValidateTypes(((item, (str, unicode)),)) elif sub_key in ('selector',): ValidateSelector(operand[sub_key], web_services) data = operand[sub_key] else: data = operand[sub_key] if caller['typed']: new_operand.__dict__.__setitem__('_%s' % sub_key, data) else: operand[sub_key] = data if caller['typed']: new_operand.__dict__.__setitem__('_%s' % key, operand) operation[key] = new_operand elif key in ('exemptionRequests',): glob_sanity_check.ValidateTypes(((operation[key], list),)) for item in operation[key]: ValidateExemptionRequest(item) data = operation[key] elif key in ('biddingTransition',): glob_sanity_check.ValidateTypes(((operation[key], dict),)) for sub_key in operation[key]: if sub_key in ('targetBiddingStrategy',): operation[key][sub_key] = \ ValidateBiddingStrategy(operation[key][sub_key], web_services) elif sub_key in ('explicitAdGroupBids',): operation[key][sub_key] = \ ValidateBids(operation[key][sub_key], web_services) else: glob_sanity_check.ValidateTypes(((operation[key][sub_key], (str, unicode)),)) return operation
def CallMethod(self, url, method, params, http_proxy): """Call API method directly, using its service's URL. For API calls performed with this method, outgoing data is not run through library's validation logic. Args: url: str URL of the API service for the method to call. method: str name of the API method to call. params: list list of parameters to send to the API method. http_proxy: str HTTP proxy to use for this API call. Returns: tuple response from the API method. """ headers = self.__GetAuthCredentialsForAccessLevel() # Load additional configuration data. op_config = { 'server': Utils.GetServerFromUrl(url), 'version': Utils.GetVersionFromUrl(url), 'http_proxy': http_proxy } service = WebService(headers, self.__config, op_config, url, self.__lock, self.__logger) if self.__config['soap_lib'] == ZSI: # Check format of parameters. Example of valid formats, # - () # - ({'dummy': 0},) # - ({'campaignIds': ['11111']}, # {'startDay': '2008-07-01'}, # {'endDay': '2008-07-31'}) # # TODO(api.sgrinberg: Figure out how to match the order of params with # those in Holder object below. Then, we don't need to require client code # to provide key/value pairs, just values will be enough (see, issue# 31). try: SanityCheck.ValidateTypes(((params, tuple), )) for item in params: SanityCheck.ValidateTypes(((item, dict), )) except ValidationError: msg = 'Invalid format of parameters, expecting a tuple of dicts.' raise ValidationError(msg) # From the URL, get service being accessed and version used. url_parts = url.split('/') service_name = url_parts[len(url_parts) - 1].split('Service')[0] version = url_parts[len(url_parts) - 2] from aw_api import API_VERSIONS if version in API_VERSIONS: web_services = __import__( 'aw_api.zsi_toolkit.%s.%sService_services' % (version, service_name), globals(), locals(), ['']) else: msg = 'Invalid API version, not one of %s.' % str( list(API_VERSIONS)) raise ValidationError(msg) eval('%sService' % service_name).web_services = web_services self.__loc = eval(('%sService.web_services.%sServiceLocator()' % (service_name, service_name))) request = eval('%sService.web_services.%sRequest()' % (service_name, method)) return service.CallMethod(method, (params), service_name, self.__loc, request) else: return service.CallMethod(method, (params))