def __init__(self, provider_name, config_path=None, **kwargs): """ @param provider_name: ParselmouthProviders """ self.provider_name = provider_name self.provider_config = self.get_config_for_provider( self.provider_name, ) if config_path: try: self.provider_config.load_config_from_file( self.provider_name, config_path, ) except: raise ParselmouthException("Invalid credential file.") else: try: self.provider_config.load_config_from_external_source(**kwargs) except Exception: raise ParselmouthException("Invalid credential arguments.") self.provider_config.validate_credentials()
def remove_target(self, target): """ Remove the specified target from the current criterion. We assume that the criterion has a AND [] construction, and furthermore that the target lies within the top-level AND. @param criterion: TargetingCriterion @param target: TargetingCriterion @return: TargetingCriterion|None """ assert isinstance(target, TargetingCriterion) if self == target: return None op1, targets1 = self.get_data() op2, targets2 = target.get_data() if not op1 == self.OPERATOR.AND: raise ParselmouthException( "TargetingCriterion has wrong structure. Top-level operator not an AND." ) if not len(targets2) == 1 or isinstance(targets2[0], TargetingCriterion): raise ParselmouthException( "Target should be a simple TargetingCriterion with a single ObjectModel." ) target_object_model = targets2[0] target_index = None if target_object_model in targets1: target_index = targets1.index(target_object_model) if target in targets1: target_index = targets1.index(target) if target_index is None: raise ParselmouthException( "Target not found in top-level structure.") targets1 = deepcopy(targets1) targets1.pop(target_index) # Handle the following case which creates an extraneous AND: # AND: [TargetingCriterion] ---> TargetingCriterion if len(targets1) == 1 and isinstance(targets1[0], TargetingCriterion): return targets1[0] # Default to OR operator for single target: # AND: [ObjectModel] ---> OR: [ObjectModel] if len(targets1) == 1: op1 = self.OPERATOR.OR return TargetingCriterion( targets1, op1, )
def __init__(self, config=None, provider_name=None, network_timeout=60 * 10, **kwargs): """ Constructor Authenticates a provider client for the given domain. @param config: parselmouth.config.ParselmouthConfig @param provider_name: any(parselmouth.constants.ParseltoungProvider) @param network_timeout: int, number seconds before timing out a request the given ad provider service """ self._network_timeout = network_timeout self.provider_config = config # Load the provider configuration if self.provider_config and not isinstance(self.provider_config, ParselmouthConfig): raise ParselmouthException( "Invalid config. Config should be of type ParselmouthConfig", ) elif not self.provider_config: self.provider_config = ParselmouthConfig(provider_name, **kwargs) # Get interface for given provider self.provider_name = self.provider_config.provider_name provider_interface_class = self.get_ad_service_interface_for_provider( self.provider_name ) self.provider = provider_interface_class( **self.provider_config.get_credentials_arguments() ) self.tree_builder = TreeBuilder( self.provider_name, self.provider, ) # Attempt to access the network to check proper configuration try: self.get_network_timezone() except Exception as e: raise ParselmouthException( "Provider not configured correctly. Got error: '{}'".format( str(e) ) )
def get_line_item_available_inventory(self, line_item, use_start=False, preserve_id=False): """ Get number of impressions available for line item @param line_item: LineItem @param use_start: bool, if False, checks availability from right now @param preserve_id: bool, communicate the id of the line item being forecasted to DFP. This is used in the case where we want to get a forecast on a line item in flight. This way, a domain doesn't need to have enough spare inventory to accommodate the two line items simultaneously. NOTE: If this is true then use_start is necessarily true. @return: int|None, number of available impressions """ dfp_line_item = transform_forecast_line_item_to_dfp( line_item, use_start, preserve_id) logging.info("Obtaining forecast data for line item %s", line_item.id or line_item) try: dfp_forecast = self.dfp_client.forecast_line_item(dfp_line_item) except Exception as e: raise ParselmouthException(e) forecast = recursive_asdict(dfp_forecast) if forecast and forecast.get('availableUnits'): available_units = int(forecast['availableUnits']) else: available_units = None logging.info("%s available impressions", str(available_units)) return available_units
def validate_credentials(self): """ Make sure all credentials have been populated correctly """ for _key, _val in self._credentials.items(): if _val is None: raise ParselmouthException("{} field is missing".format(_key))
def _run_service_query(self, query, query_function): """ Run a series of chunked DFP queries until all results are acquired @param query: FilterStatement @param query_function: Dfp service method @return: list """ results = [] while True: try: response = query_function(query.ToStatement()) except Exception as e: raise ParselmouthException( "Error running query: {0}. Got Error: {1}".format( query.ToStatement(), str(e))) if 'results' in response: logging.info( 'Statement %s returned %d results', query.ToStatement(), len(response['results']), ) results += response['results'] query.offset += SUGGESTED_PAGE_LIMIT else: break return results
def __init__(self, target_list, operator=None): """ @param target_list: list(ObjectModel)|list(TargetingCriterion)|ObjectModel @param operator: self.OPERATOR """ if isinstance(target_list, ObjectModel): # Cast to list in case a single ObjectModel is inputed target_list = [target_list] operator = self.OPERATOR.OR if not operator in self.OPERATOR: raise ParselmouthException("Invalid operator") if not isinstance(target_list, list): raise ParselmouthException("Invalid target list") self._data = {operator: target_list}
def get_line_item(self, line_item_id): """ Return a line item object given an id @param line_item_id: str, id of the LineItem to return @return: parselmouth.delivery.LineItem """ attempt = 1 response = None while not response and attempt <= MAX_REQUEST_ATTEMPTS: try: with Timeout(self._network_timeout): response = self.provider.get_line_item(line_item_id) except ParselmouthNetworkError: logging.exception("Got network error on attempt %s" % attempt) attempt += 1 if not response: raise ParselmouthException( 'Could not fetch a line item in {0} attempts'.format( MAX_REQUEST_ATTEMPTS ) ) return response
def get_creative(self, creative_id): """ Return a creative object given an id @param creative_id: str, id of the campaign to return @return: parselmouth.delivery.Creative """ dfp_creative = self.dfp_client.get_creative(creative_id) creatives = self._convert_response_to_dict(dfp_creative) if len(creatives) == 0: raise ParselmouthException( "No results for creative with id: {0}".format(creative_id)) elif len(creatives) > 1: raise ParselmouthException( "More than one result for creative with id: {0}".format( creative_id)) return transform_creative_from_dfp(creatives[0])
def get_line_item(self, line_item_id): """ Return a line item object given an id @param line_item_id: str, id of the LineItem to return @return: parselmouth.delivery.LineItem """ dfp_line_item = self.dfp_client.get_line_item(line_item_id) results = self._convert_response_to_dict(dfp_line_item) if len(results) == 0: raise ParselmouthException( "No results for line item with id: {0}".format(line_item_id)) elif len(results) > 1: raise ParselmouthException( "More than one result for line item with id: {0}".format( line_item_id)) return transform_line_item_from_dfp(results[0])
def get_campaign(self, campaign_id): """ Return a campaign object given an id @param campaign_id: str, id of the campaign to return @return: parselmouth.delivery.Campaign """ # Fetch the SUDS object and convert to a proper dictionary dfp_order = self.dfp_client.get_order(campaign_id) results = self._convert_response_to_dict(dfp_order) if len(results) == 0: raise ParselmouthException( "No results for campaign with id: {0}".format(campaign_id)) elif len(results) > 1: raise ParselmouthException( "More than one result for campaign with id: {0}".format( campaign_id)) return transform_campaign_from_dfp(results[0])
def get_advertisers(self): """ Queries dfp for all advertisers within their account @return: list(dict), returns a list of company dictionaries """ brands = self.dfp_client.get_advertisers() if len(brands) == 0: raise ParselmouthException("No brands found") else: return brands
def _custom_target_list_to_child_list(custom_targets, is_operator): """ Convert a list of custom targets to a list of dictionaries formatted for use in DFP @param custom_targets: list(Custom) @param is_operator: str, 'IS' or 'IS_NOT' @return: list(dict) """ _children = defaultdict(lambda: defaultdict(list)) for value in custom_targets: _children[value.parent_id][is_operator].append(value) dfp_children = [] for _parent_id, _operations in _children.iteritems(): for _operator, _values in _operations.iteritems(): _value_target_ids = [] _node_type = None _node_value_type = None for _custom_value in _values: _this_node_type = _custom_value.node_key _this_node_value_type = _custom_value.id_key if not _node_type and not _node_value_type: _node_type = _this_node_type _node_value_type = _this_node_value_type if (_this_node_type != _custom_value.node_key or _this_node_value_type != _custom_value.id_key): raise ParselmouthException( "Could not serialize Custom Target: %s" % _custom_value) _value_target_ids.append(_custom_value.id) _doc = { 'operator': _operator, 'xsi_type': _node_type, _node_value_type: _value_target_ids, } if _parent_id: _doc['keyId'] = _parent_id dfp_children.append(_doc) return dfp_children
def get_line_item_creatives(self, line_item): """ Return the creatives associated with a given line item @param line_item: str|int|parselmouth.delivery.LineItem, either the id of the lineitem or an object with the id @return: list(parselmouth.delivery.Creative) """ if isinstance(line_item, LineItem): _id = line_item.id else: _id = int(line_item) creatives_to_fetch = self.dfp_client.get_line_item_creatives(_id) if len(creatives_to_fetch) == 0: raise ParselmouthException( "No creatives associated with line item: {0}".format(_id)) creatives = self.get_creatives(id=creatives_to_fetch, limit=len(creatives_to_fetch)) return creatives
def get_custom_target_by_name(self, name, parent_name): """ Get a custom target by its name @param name: name of custom target @param parent_name: name of parent target @return: Custom|None """ with Timeout(self._network_timeout): targets = self.provider.get_custom_targets( key_name=parent_name, value_name=name, ) if targets and len(targets) > 1: raise ParselmouthException('Given name is not unique') elif targets: return targets[0] else: return None
def create_custom_target(self, key_name, key_type, value_name, key_display_name=None, value_display_name=None, value_match_type=None): """ Add a custom target to DFP NOTE: Since DFP is responsible for creating the custom target key and value ids we must first create the key to get its id before creating the value DFP custom targeting keys are paraphrased here: { id: int- id of custom targeting value (assigned by DFP) name: str- name of the value, can be used for encoding displayName: str|None- descriptive name of the value type: ENUM(PREDEFINED, FREEFORM), see docs } The full spec can be found here: https://developers.google.com/doubleclick-publishers/docs/reference/v201408/CustomTargetingService.CustomTargetingKey DFP custom targeting values are paraphrased here: { customTargetingKeyId: int- id of custom targeting key id: int- id of custom targeting value (assigned by DFP) name: str- name of the value, can be used for encoding displayName: str|None- descriptive name of the value matchType: ENUM(EXACT, BROAD, PREFIX, BROAD_PREFIX, SUFFIX, CONTAINS, UNKNOWN)|None, see docs } The full outline can be found here: https://developers.google.com/doubleclick-publishers/docs/reference/v201408/CustomTargetingService.CustomTargetingValue @param key_name: str, encoded name for the key @param key_type: str, type of the custom targeting key @param value_name: str, encoded name for the value @param key_display_name: str|None, descriptive name for key @param value_display_name: str|None, descriptive name for value @return: list(dict) """ # Input checking if key_type not in DFP_CUSTOM_TARGETING_KEY_TYPES: raise ParselmouthException( "Provided key type ({0}) not one of a valid type ({1})".format( key_type, DFP_CUSTOM_TARGETING_KEY_TYPES)) if value_match_type and value_match_type not in DFP_VALUE_MATCH_TYPES: raise ParselmouthException( "Provided val type ({0}) not one of a valid type ({1})".format( value_match_type, DFP_VALUE_MATCH_TYPES)) existing_values = self.get_custom_targets(key_name=key_name, value_name=value_name) # Return if we already have a custom targeting keypair for key:value if existing_values: logging.info("Custom target key+value already exists in DFP") return existing_values service = self.native_dfp_client.GetService( 'CustomTargetingService', version=self.version, ) # Check if the key already exists in DFP, if not then create it # We need to do this first since DFP handles id assignment existing_keys = self.get_custom_targets(key_name=key_name) if not existing_keys: logging.info("Key ({0}) does not exist in DFP".format(key_name)) key = { 'name': key_name, 'type': key_type, } if key_display_name: key['displayName'] = key_display_name keys = service.createCustomTargetingKeys([key]) else: logging.info("Key ({0}) already exists in DFP".format(key_name)) keys = existing_keys # Extract the key we want if len(keys) == 0: raise ParselmouthException("No keys returned by DFP") elif len(keys) > 1: raise ParselmouthException("Too many keys returned by DFP") key = keys[0] # Create the new value in DFP logging.info("Value ({0}) does not exist in DFP".format(value_name)) value = { 'customTargetingKeyId': key['id'], 'name': value_name, } if value_display_name: value['displayName'] = value_display_name if value_match_type: value['matchType'] = value_match_type values = service.createCustomTargetingValues([value]) # Extract the value we want if len(values) == 0: raise ParselmouthException("No values returned by DFP") elif len(values) > 1: raise ParselmouthException("Too many values returned by DFP") value = values[0] if value['customTargetingKeyId'] != key['id']: raise ParselmouthException( ("Value custom target key id ({0}) does not equal the Key id " "({1})".format(value['customTargetingKeyId'], key['id']))) return [key, value]