def remote_create( self, batch=None, failure=None, files=None, params=None, success=None, api_version=None, ): """Uploads filename and creates the AdImage object from it. It has same arguments as AbstractCrudObject.remote_create except it does not have the files argument but requires the 'filename' property to be defined. """ if not self[self.Field.filename]: raise FacebookBadObjectError( "AdImage required a filename to be defined.", ) filename = self[self.Field.filename] with open(filename, 'rb') as open_file: return_val = AbstractCrudObject.remote_create( self, files={filename: open_file}, batch=batch, failure=failure, params=params, success=success, api_version=api_version, ) return return_val
def parse_single(self, response): if self._custom_parse_method is not None: return self._custom_parse_method(response, self._api) data = response if 'data' in response and isinstance(response['data'], dict): data = response['data'] elif 'images' in response and not isinstance(data['images'], list): _, data = data['images'].popitem() if 'campaigns' in data: _, data = data['campaigns'].popitem() elif 'adsets' in data: _, data = data['adsets'].popitem() elif 'ads' in data: _, data = data['ads'].popitem() if 'success' in data: del data['success'] if self._reuse_object is not None: self._reuse_object._set_data(data) return self._reuse_object elif self._target_class is not None: return AbstractObject.create_object(self._api, data, self._target_class) else: raise FacebookBadObjectError( 'Must specify either target class calling object' + 'or custom parse method for parser')
def search(cls, params=None, api=None): api = api or FacebookAdsApi.get_default_api() if not api: raise FacebookBadObjectError( "An Api instance must be provided as an argument or set as " "the default Api in FacebookAdsApi.", ) params = {} if not params else params.copy() response = api.call( FacebookAdsApi.HTTP_METHOD_GET, "/".join( (FacebookSession.GRAPH, FacebookAdsApi.API_VERSION, 'search')), params, ).json() ret_val = [] if response: keys = response['data'] # The response object can be either a dictionary of dictionaries # or a dictionary of lists. if isinstance(keys, list): for item in keys: search_obj = TargetingSearch() search_obj.update(item) ret_val.append(search_obj) elif isinstance(keys, dict): for item in keys: search_obj = TargetingSearch() search_obj.update(keys[item]) if keys[item]: ret_val.append(search_obj) return ret_val
def prepare_request_params(self, path, params, headers, files, url_override, api_version): if not params: params = {} if not headers: headers = {} if not files: files = {} api_version = api_version or self._api_version if api_version and not re.search('v[0-9]+\.[0-9]+', api_version): raise FacebookBadObjectError( 'Please provide the API version in the following format: %s' % self.API_VERSION) if not isinstance(path, six.string_types): # Path is not a full path path = "/".join(( self._session.GRAPH or url_override, api_version or self.API_VERSION, '/'.join(map(str, path)), )) # Include api headers in http request headers = headers.copy() headers.update(FacebookAdsApi.HTTP_DEFAULT_HEADERS) if params: params = _top_level_param_json_encode(params) return path, params, headers, files
def remote_create( self, batch=None, failure=None, files=None, params=None, success=None, api_version=None ): """ Uploads filepath and creates the AdVideo object from it. It has same arguments as AbstractCrudObject.remote_create except it does not have the files argument but requires the 'filepath' property to be defined. """ if (self.Field.slideshow_spec in self and self[self.Field.slideshow_spec] is not None): request = VideoUploadRequest(self.get_api_assured()) request.setParams(params={'slideshow_spec': { 'images_urls': self[self.Field.slideshow_spec]['images_urls'], 'duration_ms': self[self.Field.slideshow_spec]['duration_ms'], 'transition_ms': self[self.Field.slideshow_spec]['transition_ms'], }}) response = request.send((self.get_parent_id_assured(), 'advideos')).json() elif not (self.Field.filepath in self): raise FacebookBadObjectError( "AdVideo requires a filepath or slideshow_spec to be defined.", ) else: video_uploader = VideoUploader() response = video_uploader.upload(self) self._set_data(response) return response
def __init__( self, api=None, target_class=None, reuse_object=None, custom_parse_method=None, ): """ Initialize an ObjectParser. To Initialize, you need to provide either a resuse_object, target_class, or an custom_parse_method. Args: api: FacebookAdsApi object. target_class (optional): The expected return object type. reuse_object (optional): Reuse existing object to populate response. custom_parse_method (optional): Custom parsing method. """ if not any( [target_class, reuse_object is not None, custom_parse_method]): raise FacebookBadObjectError( 'Must specify either target class calling object' + 'or custom parse method for parser') self._reuse_object = reuse_object self._target_class = target_class self._custom_parse_method = custom_parse_method self._api = api
def _set_data(self, data): if hasattr(data, 'items'): for key, value in data.items(): self[key] = value else: raise FacebookBadObjectError("Bad data to set object data") self._json = data
def __setitem__(self, key, value): if key not in self.Field.__dict__: raise FacebookBadObjectError( "\"%s\" is not a valid field of %s" % (key, self.__class__.__name__) ) else: super(ValidatesFields, self).__setitem__(key, value)
def get_parent_id_assured(self): """Returns the object's parent's fbid. Raises: FacebookBadObjectError if the object does not have a parent id. """ if not self.get_parent_id(): raise FacebookBadObjectError( "%s object needs a parent_id for this operation." % self.__class__.__name__, ) return self.get_parent_id()
def get_id_assured(self): """Returns the fbid of the object. Raises: FacebookBadObjectError if the object does not have an id. """ if not self.get(self.Field.id): raise FacebookBadObjectError( "%s object needs an id for this operation." % self.__class__.__name__, ) return self.get_id()
def get_parent_id_assured(self): """Returns the object's parent's fbid. Raises: FacebookBadObjectError if the object does not have a parent id. """ #warning_message = "parent_id is being deprecated." #logging.warning(warning_message) if not self.get_parent_id(): raise FacebookBadObjectError( "%s object needs a parent_id for this operation." % self.__class__.__name__, ) return self.get_parent_id()
def get_api_assured(self): """Returns the fbid of the object. Raises: FacebookBadObjectError if get_api returns None. """ api = self.get_api() if not api: raise FacebookBadObjectError( "%s does not yet have an associated api object.\n" "Did you forget to instantiate an API session with: " "FacebookAdsApi.init(app_id, app_secret, access_token)" % self.__class__.__name__, ) return api
def test_retries_on_bad_data(self): """`AdInsights.run_job()` calls a `facebook_business` method, `get_insights()`, to make a request to the API. We mock this method to raise a `FacebookBadObjectError` We expect the tap to retry this request up to 5 times, which is the current hard coded `max_tries` value. """ # Create the mock and force the function to throw an error mocked_account = Mock() mocked_account.get_insights = Mock() mocked_account.get_insights.side_effect = FacebookBadObjectError("Bad data to set object data") # Initialize the object and call `sync()` ad_creative_object = AdsInsights('', mocked_account, '', '', {}, {}) with self.assertRaises(FacebookBadObjectError): ad_creative_object.run_job({}) # 5 is the max tries specified in the tap self.assertEquals(5, mocked_account.get_insights.call_count )
def format_params(cls, schema, users, is_raw=False, app_ids=None, pre_hashed=None, session=None): hashed_users = [] if schema in (cls.Schema.phone_hash, cls.Schema.email_hash, cls.Schema.mobile_advertiser_id): for user in users: if schema == cls.Schema.email_hash: user = user.strip(" \t\r\n\0\x0B.").lower() if isinstance(user, six.text_type) and not ( pre_hashed ) and schema != cls.Schema.mobile_advertiser_id: user = user.encode('utf8') # required for hashlib # for mobile_advertiser_id, don't hash it if pre_hashed or schema == cls.Schema.mobile_advertiser_id: hashed_users.append(user) else: hashed_users.append(hashlib.sha256(user).hexdigest()) elif isinstance(schema, list): # SDK will support only single PII if not is_raw: raise FacebookBadObjectError( "Please send single PIIs i.e. is_raw should be true. " + "The combining of the keys will be done internally.", ) # users is array of array for user in users: if len(schema) != len(user): raise FacebookBadObjectError( "Number of keys in each list in the data should " + "match the number of keys specified in scheme", ) break # If the keys are already hashed then send as it is if pre_hashed: hashed_users.append(user) else: counter = 0 hashed_user = [] for key in user: key = key.strip(" \t\r\n\0\x0B.").lower() key = cls.normalize_key(schema[counter], str(key)) if schema[counter] not in ( cls.Schema.MultiKeySchema.extern_id, cls.Schema.MultiKeySchema.appuid): if isinstance(key, six.text_type): key = key.encode('utf8') key = hashlib.sha256(key).hexdigest() counter = counter + 1 hashed_user.append(key) hashed_users.append(hashed_user) payload = { 'schema': schema, 'is_raw': is_raw, 'data': hashed_users or users, } if schema == cls.Schema.uid: if not app_ids: raise FacebookBadObjectError( "Custom Audiences with type " + cls.Schema.uid + "require at least one app_id", ) if app_ids: payload['app_ids'] = app_ids params = { 'payload': payload, } if session: params['session'] = session return params
def warning(message): if apiconfig.ads_api_config['STRICT_MODE']: raise FacebookBadObjectError(message) else: warnings.warn(message)
def call( self, method, path, params=None, headers=None, files=None, url_override=None, api_version=None, ): """Makes an API call. Args: method: The HTTP method name (e.g. 'GET'). path: A tuple of path tokens or a full URL string. A tuple will be translated to a url as follows: graph_url/tuple[0]/tuple[1]... It will be assumed that if the path is not a string, it will be iterable. params (optional): A mapping of request parameters where a key is the parameter name and its value is a string or an object which can be JSON-encoded. headers (optional): A mapping of request headers where a key is the header name and its value is the header value. files (optional): An optional mapping of file names to binary open file objects. These files will be attached to the request. Returns: A FacebookResponse object containing the response body, headers, http status, and summary of the call that was made. Raises: FacebookResponse.error() if the request failed. """ if not params: params = {} if not headers: headers = {} if not files: files = {} api_version = api_version or self._api_version if api_version and not re.search('v[0-9]+\.[0-9]+', api_version): raise FacebookBadObjectError( 'Please provide the API version in the following format: %s' % self.API_VERSION, ) self._num_requests_attempted += 1 if not isinstance(path, six.string_types): # Path is not a full path path = "/".join(( url_override or self._session.GRAPH, api_version, '/'.join(map(str, path)), )) # Include api headers in http request headers = headers.copy() headers.update(FacebookAdsApi.HTTP_DEFAULT_HEADERS) if params: params = _top_level_param_json_encode(params) # Get request response and encapsulate it in a FacebookResponse if method in ('GET', 'DELETE'): response = self._session.requests.request( method, path, params=params, headers=headers, files=files, timeout=self._session.timeout ) else: response = self._session.requests.request( method, path, data=params, headers=headers, files=files, timeout=self._session.timeout ) if self._enable_debug_logger: import curlify print(curlify.to_curl(response.request)) fb_response = FacebookResponse( body=response.text, headers=response.headers, http_status=response.status_code, call={ 'method': method, 'path': path, 'params': params, 'headers': headers, 'files': files, }, ) if fb_response.is_failure(): raise fb_response.error() self._num_requests_succeeded += 1 return fb_response
def assure_call(self): if not self._api: raise FacebookBadObjectError( 'Api call cannot be made if api is not set')
def remote_create( self, batch=None, failure=None, files=None, params=None, success=None, api_version=None, ): """Creates the object by calling the API. Args: batch (optional): A FacebookAdsApiBatch object. If specified, the call will be added to the batch. params (optional): A mapping of request parameters where a key is the parameter name and its value is a string or an object which can be JSON-encoded. files (optional): An optional mapping of file names to binary open file objects. These files will be attached to the request. success (optional): A callback function which will be called with the FacebookResponse of this call if the call succeeded. failure (optional): A callback function which will be called with the FacebookResponse of this call if the call failed. Returns: self if not a batch call. the return value of batch.add if a batch call. """ warning_message = "`remote_create` is being deprecated, please update your code with new function." logging.warning(warning_message) if self.get_id(): raise FacebookBadObjectError( "This %s object was already created." % self.__class__.__name__, ) if not 'get_endpoint' in dir(self): raise TypeError('Cannot create object of type %s.' % self.__class__.__name__) params = {} if not params else params.copy() params.update(self.export_all_data()) request = None if hasattr(self, 'api_create'): request = self.api_create(self.get_parent_id_assured(), pending=True) else: request = FacebookRequest( node_id=self.get_parent_id_assured(), method='POST', endpoint=self.get_endpoint(), api=self._api, target_class=self.__class__, response_parser=ObjectParser( reuse_object=self ), ) request.add_params(params) request.add_files(files) if batch is not None: def callback_success(response): self._set_data(response.json()) self._clear_history() if success: success(response) def callback_failure(response): if failure: failure(response) return batch.add_request( request=request, success=callback_success, failure=callback_failure, ) else: response = request.execute() self._set_data(response._json) self._clear_history() return self