예제 #1
0
    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()
예제 #2
0
    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,
        )
예제 #3
0
    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)
                )
            )
예제 #4
0
    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
예제 #5
0
 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))
예제 #6
0
    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
예제 #7
0
    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}
예제 #8
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
        """
        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
예제 #9
0
    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])
예제 #10
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])
예제 #11
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])
예제 #12
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
예제 #13
0
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
예제 #14
0
    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
예제 #15
0
    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
예제 #16
0
    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]