示例#1
0
  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)
示例#2
0
    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'])