From da4337d8232da3c42e59073fb802d00f21ca45a7 Mon Sep 17 00:00:00 2001 From: Xinyu Wen Date: Thu, 20 Feb 2025 19:38:08 +0800 Subject: [PATCH] v13.0.24 --- HISTORY.rst | 8 + bingads/manifest.py | 2 +- bingads/v13/bulk/entities/__init__.py | 1 + bingads/v13/bulk/entities/bulk_budget.py | 21 +- bingads/v13/bulk/entities/bulk_campaign.py | 87 +++ .../entities/bulk_campaign_conversion_goal.py | 43 +- .../bulk_new_customer_acquisition_goal.py | 81 +++ .../v13/internal/bulk/bulk_object_factory.py | 1 + bingads/v13/internal/bulk/csv_headers.py | 7 + bingads/v13/internal/bulk/string_table.py | 7 + .../production/campaignmanagement_service.xml | 546 +++++++++++++++++- .../production/customermanagement_service.xml | 16 + .../proxies/production/reporting_service.xml | 2 + .../sandbox/campaignmanagement_service.xml | 546 +++++++++++++++++- .../sandbox/customermanagement_service.xml | 16 + .../v13/proxies/sandbox/reporting_service.xml | 2 + examples/v13/responsive_ad_recommendation.py | 198 +++++++ setup.py | 2 +- 18 files changed, 1521 insertions(+), 65 deletions(-) create mode 100644 bingads/v13/bulk/entities/bulk_new_customer_acquisition_goal.py create mode 100644 examples/v13/responsive_ad_recommendation.py diff --git a/HISTORY.rst b/HISTORY.rst index eb273b40..b4d138ca 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,14 @@ Release History +13.0.24(2025-02-20) ++++++++++++++++++++++++++ +* Update Bing Ads API Version 13 service proxies to reflect recent interface changes. For details please see the Bing Ads API Release Notes: https://learn.microsoft.com/en-us/advertising/guides/release-notes?view=bingads-13. +* Added NewCustomerAcquisitionGoalSetting in BulkCampaign mapping. +* Added SubType, ActionType in BulkCampaignConversionGoal mapping. +* Added CampaignId in BulkKeyword mapping. +* Added bulk mappings for NCA: BulkNewCustomerAcquisitionGoal. + 13.0.23.1(2025-01-23) +++++++++++++++++++++++++ * Update Bing Ads API Version 13 service proxies to reflect recent interface changes. For details please see the Bing Ads API Release Notes: https://learn.microsoft.com/en-us/advertising/guides/release-notes?view=bingads-13. diff --git a/bingads/manifest.py b/bingads/manifest.py index b5c38812..28bd4ee8 100644 --- a/bingads/manifest.py +++ b/bingads/manifest.py @@ -1,5 +1,5 @@ import sys -VERSION = '13.0.23.1' +VERSION = '13.0.24' BULK_FORMAT_VERSION_6 = '6.0' WORKING_NAME = 'BingAdsSDKPython' USER_AGENT = '{0} {1} {2}'.format(WORKING_NAME, VERSION, sys.version_info[0:3]) diff --git a/bingads/v13/bulk/entities/__init__.py b/bingads/v13/bulk/entities/__init__.py index 36cd5a06..4ed27c99 100644 --- a/bingads/v13/bulk/entities/__init__.py +++ b/bingads/v13/bulk/entities/__init__.py @@ -55,4 +55,5 @@ from .bulk_campaign_brand_list_association import * from .bulk_brand_item import * from .bulk_brand_list import * +from .bulk_new_customer_acquisition_goal import * from .goals import * diff --git a/bingads/v13/bulk/entities/bulk_budget.py b/bingads/v13/bulk/entities/bulk_budget.py index a9c585e7..91c49778 100644 --- a/bingads/v13/bulk/entities/bulk_budget.py +++ b/bingads/v13/bulk/entities/bulk_budget.py @@ -20,11 +20,12 @@ class BulkBudget(_SingleRecordBulkEntity): * :class:`.BulkFileWriter` """ - def __init__(self, budget=None, status=None, account_id=None): + def __init__(self, budget=None, status=None, account_id=None, campaign_id=None): super(BulkBudget, self).__init__() self._budget = budget self._status = status self._account_id = account_id + self._campaign_id = campaign_id @property def budget(self): @@ -63,6 +64,19 @@ def account_id(self): def account_id(self, value): self._account_id = value + @property + def campaign_id(self): + """ the id of the campaign which contains the budget + Corresponds to the 'Campaign Id' field in the bulk file. + + :rtype: long + """ + return self._campaign_id + + @campaign_id.setter + def campaign_id(self, value): + self._campaign_id = value + _MAPPINGS = [ _SimpleBulkMapping( @@ -75,6 +89,11 @@ def account_id(self, value): field_to_csv=lambda c: bulk_str(c.account_id), csv_to_field=lambda c, v: setattr(c, 'account_id', int(v) if v else None) ), + _SimpleBulkMapping( + header=_StringTable.CampaignId, + field_to_csv=lambda c: bulk_str(c.campaign_id), + csv_to_field=lambda c, v: setattr(c, 'campaign_id', int(v) if v else None) + ), _SimpleBulkMapping( header=_StringTable.Status, field_to_csv=lambda c: c.status, diff --git a/bingads/v13/bulk/entities/bulk_campaign.py b/bingads/v13/bulk/entities/bulk_campaign.py index e9d9bfcb..991474b5 100644 --- a/bingads/v13/bulk/entities/bulk_campaign.py +++ b/bingads/v13/bulk/entities/bulk_campaign.py @@ -4,6 +4,7 @@ from bingads.v13.internal.bulk.entities.single_record_bulk_entity import _SingleRecordBulkEntity from bingads.v13.internal.bulk.mappings import _SimpleBulkMapping, _ComplexBulkMapping from bingads.v13.internal.extensions import * +from decimal import Decimal _DynamicFeedSetting = type(_CAMPAIGN_OBJECT_FACTORY_V13.create('DynamicFeedSetting')) _TargetSetting = type(_CAMPAIGN_OBJECT_FACTORY_V13.create('TargetSetting')) @@ -12,6 +13,7 @@ _DisclaimerSetting = type(_CAMPAIGN_OBJECT_FACTORY_V13.create('DisclaimerSetting')) _VerifiedTrackingSetting = type(_CAMPAIGN_OBJECT_FACTORY_V13.create('VerifiedTrackingSetting')) _PerformanceMaxSetting = type(_CAMPAIGN_OBJECT_FACTORY_V13.create('PerformanceMaxSetting')) +_NewCustomerAcquisitionGoalSetting = type(_CAMPAIGN_OBJECT_FACTORY_V13.create('NewCustomerAcquisitionGoalSetting')) class BulkCampaign(_SingleRecordBulkEntity): """ Represents a campaign that can be read or written in a bulk file. @@ -167,6 +169,9 @@ def _get_verified_tracking_setting(self): def _get_performance_max_setting(self): return self._get_setting(_PerformanceMaxSetting, 'PerformanceMaxSetting') + def _get_new_customer_acquisition_goal_setting(self): + return self._get_setting(_NewCustomerAcquisitionGoalSetting, 'NewCustomerAcquisitionGoalSetting') + def _get_setting(self, setting_type, setting_name): if not self.campaign.Settings.Setting: return None @@ -205,6 +210,7 @@ def _read_campaign_type(c, v): if campaign_type.lower() == 'performancemax': BulkCampaign._create_campaign_setting(c.campaign, 'PerformanceMaxSetting') BulkCampaign._create_campaign_setting(c.campaign, 'ShoppingSetting') + BulkCampaign._create_campaign_setting(c.campaign, 'NewCustomerAcquisitionGoalSetting') @staticmethod def _create_campaign_setting(campaign, setting_type): @@ -521,6 +527,72 @@ def _write_image_opt_out(c): return None return bulk_str(performance_max_setting.AutoGeneratedImageOptOut) + @staticmethod + def _read_new_customer_acquisition_bid_only_mode(c, v): + if not c.campaign.CampaignType: + return None + campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType] + if 'performancemax' in campgaign_types: + new_customer_acquisition_goal_setting = c._get_new_customer_acquisition_goal_setting() + if not new_customer_acquisition_goal_setting: + return None + new_customer_acquisition_goal_setting.NewCustomerAcquisitionBidOnlyMode = parse_bool(v) + + @staticmethod + def _write_new_customer_acquisition_bid_only_mode(c): + if not c.campaign.CampaignType: + return None + campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType] + if 'performancemax' in campgaign_types: + new_customer_acquisition_goal_setting = c._get_new_customer_acquisition_goal_setting() + if not new_customer_acquisition_goal_setting: + return None + return bulk_str(new_customer_acquisition_goal_setting.NewCustomerAcquisitionBidOnlyMode) + + @staticmethod + def _read_new_customer_acquisition_goal_id(c, v): + if not c.campaign.CampaignType: + return None + campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType] + if 'performancemax' in campgaign_types: + new_customer_acquisition_goal_setting = c._get_new_customer_acquisition_goal_setting() + if not new_customer_acquisition_goal_setting: + return None + new_customer_acquisition_goal_setting.NewCustomerAcquisitionGoalId = int(v) if v else None + + @staticmethod + def _write_new_customer_acquisition_goal_id(c): + if not c.campaign.CampaignType: + return None + campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType] + if 'performancemax' in campgaign_types: + new_customer_acquisition_goal_setting = c._get_new_customer_acquisition_goal_setting() + if not new_customer_acquisition_goal_setting: + return None + return bulk_str(new_customer_acquisition_goal_setting.NewCustomerAcquisitionGoalId) + + @staticmethod + def _read_additional_conversion_value(c, v): + if not c.campaign.CampaignType: + return None + campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType] + if 'performancemax' in campgaign_types: + new_customer_acquisition_goal_setting = c._get_new_customer_acquisition_goal_setting() + if not new_customer_acquisition_goal_setting: + return None + new_customer_acquisition_goal_setting.AdditionalConversionValue = Decimal(v) if v else None + + @staticmethod + def _write_additional_conversion_value(c): + if not c.campaign.CampaignType: + return None + campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType] + if 'performancemax' in campgaign_types: + new_customer_acquisition_goal_setting = c._get_new_customer_acquisition_goal_setting() + if not new_customer_acquisition_goal_setting: + return None + return bulk_str(new_customer_acquisition_goal_setting.AdditionalConversionValue) + @staticmethod def _read_website(c, v): if not c.campaign.CampaignType: @@ -756,6 +828,21 @@ def _write_website(c): field_to_csv=lambda c: field_to_csv_bool(c.should_serve_on_msan), csv_to_field=lambda c, v: setattr(c, 'should_serve_on_msan', parse_bool(v)) ), + _SimpleBulkMapping( + header=_StringTable.NewCustomerAcquisitionGoalId, + field_to_csv=lambda c: BulkCampaign._write_new_customer_acquisition_goal_id(c), + csv_to_field=lambda c, v: BulkCampaign._read_new_customer_acquisition_goal_id(c, v) + ), + _SimpleBulkMapping( + header=_StringTable.NewCustomerAcquisitionBidOnlyMode, + field_to_csv=lambda c: BulkCampaign._write_new_customer_acquisition_bid_only_mode(c), + csv_to_field=lambda c, v: BulkCampaign._read_new_customer_acquisition_bid_only_mode(c, v) + ), + _SimpleBulkMapping( + header=_StringTable.AdditionalConversionValue, + field_to_csv=lambda c: BulkCampaign._write_additional_conversion_value(c), + csv_to_field=lambda c, v: BulkCampaign._read_additional_conversion_value(c, v) + ), ] def read_additional_data(self, stream_reader): diff --git a/bingads/v13/bulk/entities/bulk_campaign_conversion_goal.py b/bingads/v13/bulk/entities/bulk_campaign_conversion_goal.py index 7900c52c..0ae398fc 100644 --- a/bingads/v13/bulk/entities/bulk_campaign_conversion_goal.py +++ b/bingads/v13/bulk/entities/bulk_campaign_conversion_goal.py @@ -10,7 +10,7 @@ class BulkCampaignConversionGoal(_SingleRecordBulkEntity): Properties of this class and of classes that it is derived from, correspond to fields of the CampaignConversionGoal record in a bulk file. For more information, see CampaignConversionGoal at https://go.microsoft.com/fwlink/?linkid=846127 - + *See also:* * :class:`.BulkServiceManager` @@ -19,10 +19,11 @@ class BulkCampaignConversionGoal(_SingleRecordBulkEntity): * :class:`.BulkFileWriter` """ - def __init__(self, campaign_conversion_goal = None): + def __init__(self, campaign_conversion_goal = None, sub_type = None, action_type = None): super(BulkCampaignConversionGoal, self).__init__() self._campaign_conversion_goal = campaign_conversion_goal - + self._sub_type = sub_type + self._action_type = action_type @property def campaign_conversion_goal(self): @@ -34,7 +35,31 @@ def campaign_conversion_goal(self): @campaign_conversion_goal.setter def campaign_conversion_goal(self, value): - self._campaign_conversion_goal = value + self._campaign_conversion_goal = value + + @property + def sub_type(self): + """ Corresponds to the 'Sub Type' field in the bulk file. + + :rtype: str + """ + return self._sub_type + + @sub_type.setter + def sub_type(self, value): + self._sub_type = value + + @property + def action_type(self): + """ Corresponds to the 'Action Type' field in the bulk file. + + :rtype: str + """ + return self._action_type + + @action_type.setter + def action_type(self, value): + self._action_type = value _MAPPINGS = [ @@ -48,6 +73,16 @@ def campaign_conversion_goal(self, value): field_to_csv=lambda c: bulk_str(c.campaign_conversion_goal.GoalId), csv_to_field=lambda c, v: setattr(c.campaign_conversion_goal, 'GoalId', int(v) if v else None) ), + _SimpleBulkMapping( + header=_StringTable.ActionType, + field_to_csv=lambda c: c.action_type, + csv_to_field=lambda c, v: setattr(c, 'action_type', v) + ), + _SimpleBulkMapping( + header=_StringTable.SubType, + field_to_csv=lambda c: c.sub_type, + csv_to_field=lambda c, v: setattr(c, 'sub_type', v) + ), ] def process_mappings_from_row_values(self, row_values): diff --git a/bingads/v13/bulk/entities/bulk_new_customer_acquisition_goal.py b/bingads/v13/bulk/entities/bulk_new_customer_acquisition_goal.py new file mode 100644 index 00000000..816aed2c --- /dev/null +++ b/bingads/v13/bulk/entities/bulk_new_customer_acquisition_goal.py @@ -0,0 +1,81 @@ +from bingads.service_client import _CAMPAIGN_OBJECT_FACTORY_V13 +from bingads.v13.internal.bulk.string_table import _StringTable +from bingads.v13.internal.bulk.entities.single_record_bulk_entity import _SingleRecordBulkEntity +from bingads.v13.internal.bulk.mappings import _SimpleBulkMapping +from bingads.v13.internal.extensions import * +from decimal import Decimal + + +class BulkNewCustomerAcquisitionGoal (_SingleRecordBulkEntity): + """ Represents a new customer acquisition goal that can be read or written in a bulk file. + + Properties of this class and of classes that it is derived from, correspond to fields of the Budget record in a bulk file. + For more information, see Budget at https://go.microsoft.com/fwlink/?linkid=846127 + + *See also:* + + * :class:`.BulkServiceManager` + * :class:`.BulkOperation` + * :class:`.BulkFileReader` + * :class:`.BulkFileWriter` + """ + + def __init__(self, new_customer_acquisition_goal=None, target=None): + super(BulkNewCustomerAcquisitionGoal , self).__init__() + self._new_customer_acquisition_goal = new_customer_acquisition_goal + self._target = target + + @property + def new_customer_acquisition_goal (self): + """ + the NewCustomerAcquisitionGoal object, see more detail at: https://go.microsoft.com/fwlink/?linkid=846127 + """ + return self._new_customer_acquisition_goal + + @new_customer_acquisition_goal .setter + def new_customer_acquisition_goal (self, value): + self._new_customer_acquisition_goal = value + + @property + def target(self): + """ + The ids of audiences within the new customer acquisition. + It should be split by simicolon. example: "123;456;789" + Corresponds to 'Target' field in bulk file. + :rtype: str + """ + return self._target + + @target.setter + def target(self, value): + self._target = value + + + _MAPPINGS = [ + _SimpleBulkMapping( + header=_StringTable.Id, + field_to_csv=lambda c: bulk_str(c.new_customer_acquisition_goal .Id), + csv_to_field=lambda c, v: setattr(c.new_customer_acquisition_goal , 'Id', int(v) if v else None) + ), + _SimpleBulkMapping( + header=_StringTable.Target, + field_to_csv=lambda c: bulk_str(c.target), + csv_to_field=lambda c, v: setattr(c, 'target', v) + ), + _SimpleBulkMapping( + header=_StringTable.AdditionalConversionValue, + field_to_csv=lambda c: bulk_str(c.new_customer_acquisition_goal.AdditionalValue), + csv_to_field=lambda c, v: setattr(c.new_customer_acquisition_goal , 'AdditionalValue', Decimal(v) if v else None) + ), + ] + + def process_mappings_from_row_values(self, row_values): + self._new_customer_acquisition_goal = _CAMPAIGN_OBJECT_FACTORY_V13.create('NewCustomerAcquisitionGoal') + row_values.convert_to_entity(self, BulkNewCustomerAcquisitionGoal ._MAPPINGS) + + def process_mappings_to_row_values(self, row_values, exclude_readonly_data): + self._validate_property_not_null(self.new_customer_acquisition_goal , 'new_customer_acquisition_goal ') + self.convert_to_values(row_values, BulkNewCustomerAcquisitionGoal ._MAPPINGS) + + def read_additional_data(self, stream_reader): + super(BulkNewCustomerAcquisitionGoal , self).read_additional_data(stream_reader) diff --git a/bingads/v13/internal/bulk/bulk_object_factory.py b/bingads/v13/internal/bulk/bulk_object_factory.py index ca0a4c30..8cd63936 100644 --- a/bingads/v13/internal/bulk/bulk_object_factory.py +++ b/bingads/v13/internal/bulk/bulk_object_factory.py @@ -233,6 +233,7 @@ class _BulkObjectFactory(): _StringTable.BrandItem: _EntityInfo(lambda: BulkBrandItem()), _StringTable.CampaignBrandList: _EntityInfo(lambda: BulkCampaignBrandListAssociation()), _StringTable.AssetGroupUrlTarget: _EntityInfo(lambda: BulkAssetGroupUrlTarget()), + _StringTable.NewCustomerAcquisitionGoal: _EntityInfo(lambda: BulkNewCustomerAcquisitionGoal()), } ADDITIONAL_OBJECT_MAP = { diff --git a/bingads/v13/internal/bulk/csv_headers.py b/bingads/v13/internal/bulk/csv_headers.py index e5cef573..62c040d6 100644 --- a/bingads/v13/internal/bulk/csv_headers.py +++ b/bingads/v13/internal/bulk/csv_headers.py @@ -8,6 +8,7 @@ class _CsvHeaders: _StringTable.Status, _StringTable.Id, _StringTable.ParentId, + _StringTable.CampaignId, _StringTable.SubType, _StringTable.Campaign, _StringTable.AdGroup, @@ -528,6 +529,12 @@ class _CsvHeaders: _StringTable.AssetGroupTargetValue1, _StringTable.AssetGroupTargetValue2, _StringTable.AssetGroupTargetValue3, + + # New Customer Acquisition Goal + _StringTable.AdditionalConversionValue, + _StringTable.NewCustomerAcquisitionGoalId, + _StringTable.NewCustomerAcquisitionBidOnlyMode, + ] @staticmethod diff --git a/bingads/v13/internal/bulk/string_table.py b/bingads/v13/internal/bulk/string_table.py index 27a240b8..72134e26 100644 --- a/bingads/v13/internal/bulk/string_table.py +++ b/bingads/v13/internal/bulk/string_table.py @@ -7,6 +7,7 @@ class _StringTable: Id = "Id" BusinessId = "Business Id" ParentId = "Parent Id" + CampaignId = "Campaign Id" TimeZone = "Time Zone" Budget = "Budget" BudgetType = "Budget Type" @@ -731,3 +732,9 @@ class _StringTable: AssetGroupTargetValue1 = "Asset Group Target Value 1"; AssetGroupTargetValue2 = "Asset Group Target Value 2"; AssetGroupTargetValue3 = "Asset Group Target Value 3"; + + # New Customer Acquisition Goal + NewCustomerAcquisitionGoal = "New Customer Acquisition Goal"; + AdditionalConversionValue = "Additional Conversion Value"; + NewCustomerAcquisitionGoalId = "New Customer Acquisition Goal Id"; + NewCustomerAcquisitionBidOnlyMode = "New Customer Acquisition Bid Only Mode"; diff --git a/bingads/v13/proxies/production/campaignmanagement_service.xml b/bingads/v13/proxies/production/campaignmanagement_service.xml index 46fd11e8..de79c6e8 100644 --- a/bingads/v13/proxies/production/campaignmanagement_service.xml +++ b/bingads/v13/proxies/production/campaignmanagement_service.xml @@ -1180,6 +1180,13 @@ + + + + + + + @@ -1206,11 +1213,25 @@ + + + + + + + + + + + + + + @@ -1604,6 +1625,7 @@ + @@ -5092,6 +5114,17 @@ + + + + + + + + + + + @@ -5223,6 +5256,14 @@ + + + + + + + + @@ -8809,9 +8850,29 @@ + + + + + + + 1 + + + + + + + 2 + + + + + + @@ -8873,37 +8934,50 @@ - - - + + + + 1 + + + + + + + 2 + + + + + + + 3 + + + - + - + - + - - + + - + + - - - - - - - + @@ -8915,7 +8989,7 @@ - + @@ -8930,7 +9004,7 @@ - + @@ -9195,11 +9269,11 @@ - - + + - + @@ -9212,26 +9286,38 @@ - - + + - - + + - + - + - + - + + + + + + + + + + + + + @@ -9296,6 +9382,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -9328,6 +9545,12 @@ + + + + + + @@ -13480,6 +13703,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -14495,6 +14838,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -18386,6 +18759,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bingads/v13/proxies/production/customermanagement_service.xml b/bingads/v13/proxies/production/customermanagement_service.xml index 0ec8c375..a9abb138 100644 --- a/bingads/v13/proxies/production/customermanagement_service.xml +++ b/bingads/v13/proxies/production/customermanagement_service.xml @@ -881,6 +881,8 @@ + + @@ -1867,6 +1869,20 @@ + + + + 1060 + + + + + + + 1062 + + + diff --git a/bingads/v13/proxies/production/reporting_service.xml b/bingads/v13/proxies/production/reporting_service.xml index 94b5fcae..cb38a40f 100644 --- a/bingads/v13/proxies/production/reporting_service.xml +++ b/bingads/v13/proxies/production/reporting_service.xml @@ -4438,6 +4438,8 @@ + + diff --git a/bingads/v13/proxies/sandbox/campaignmanagement_service.xml b/bingads/v13/proxies/sandbox/campaignmanagement_service.xml index 1043af29..725d6028 100644 --- a/bingads/v13/proxies/sandbox/campaignmanagement_service.xml +++ b/bingads/v13/proxies/sandbox/campaignmanagement_service.xml @@ -1180,6 +1180,13 @@ + + + + + + + @@ -1206,11 +1213,25 @@ + + + + + + + + + + + + + + @@ -1604,6 +1625,7 @@ + @@ -5092,6 +5114,17 @@ + + + + + + + + + + + @@ -5223,6 +5256,14 @@ + + + + + + + + @@ -8809,9 +8850,29 @@ + + + + + + + 1 + + + + + + + 2 + + + + + + @@ -8873,37 +8934,50 @@ - - - + + + + 1 + + + + + + + 2 + + + + + + + 3 + + + - + - + - + - - + + - + + - - - - - - - + @@ -8915,7 +8989,7 @@ - + @@ -8930,7 +9004,7 @@ - + @@ -9195,11 +9269,11 @@ - - + + - + @@ -9212,26 +9286,38 @@ - - + + - - + + - + - + - + - + + + + + + + + + + + + + @@ -9296,6 +9382,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -9328,6 +9545,12 @@ + + + + + + @@ -13480,6 +13703,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -14495,6 +14838,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -18386,6 +18759,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bingads/v13/proxies/sandbox/customermanagement_service.xml b/bingads/v13/proxies/sandbox/customermanagement_service.xml index 026f0ef0..a5cebe75 100644 --- a/bingads/v13/proxies/sandbox/customermanagement_service.xml +++ b/bingads/v13/proxies/sandbox/customermanagement_service.xml @@ -881,6 +881,8 @@ + + @@ -1867,6 +1869,20 @@ + + + + 1060 + + + + + + + 1062 + + + diff --git a/bingads/v13/proxies/sandbox/reporting_service.xml b/bingads/v13/proxies/sandbox/reporting_service.xml index b9d856cd..864e0650 100644 --- a/bingads/v13/proxies/sandbox/reporting_service.xml +++ b/bingads/v13/proxies/sandbox/reporting_service.xml @@ -4438,6 +4438,8 @@ + + diff --git a/examples/v13/responsive_ad_recommendation.py b/examples/v13/responsive_ad_recommendation.py new file mode 100644 index 00000000..aaeda249 --- /dev/null +++ b/examples/v13/responsive_ad_recommendation.py @@ -0,0 +1,198 @@ +import base64 + +from auth_helper import * +from campaignmanagement_example_helper import * + +# You must provide credentials in auth_helper.py. + +def main(authorization_data): + + try: + # To run this example you'll need to provide a valid Ad Final URL + ad_final_url = "https://contoso.com" + # Set false to disable cleanup of created entities at the end + do_cleanup = True + + final_urls = campaign_service.factory.create('ns3:ArrayOfstring') + final_urls.string.append(ad_final_url) + + output_status_message("-----\nCreateResponsiveAdRecommendation:"); + output_status_message(f"-----\nGetting ad recommendation for URL {ad_final_url} ..."); + responsive_ad_recommendation_response = campaign_service.CreateResponsiveAdRecommendation( + FinalUrls=final_urls + ) + responsive_ad = responsive_ad_recommendation_response.ResponsiveAd + image_suggestions = responsive_ad_recommendation_response.ImageSuggestions['AdRecommendationImageSuggestion'] + + # Select a few images from the suggested list. This example picks first 5 images + selected_images = image_suggestions[:5] + + # Add selected images to your media library + save_images(selected_images) + + images = campaign_service.factory.create('ArrayOfAssetLink') + images.AssetLink = [obj.AssetLink for obj in selected_images] + responsive_ad.Images = images + + responsive_ad.BusinessName = "Contoso" + #responsive_ad.CallToAction = 'ShopNow' + responsive_ad.CallToActionLanguage = 'English' + + # Create an Audience campaign with one ad group and a responsive ad + campaigns = campaign_service.factory.create('ArrayOfCampaign') + campaign = set_elements_to_none(campaign_service.factory.create('Campaign')) + campaign.BudgetType = 'DailyBudgetStandard' + # CampaignType must be set for Audience campaigns + campaign.CampaignType = ['Audience'] + campaign.DailyBudget = 50.00 + languages = campaign_service.factory.create('ns3:ArrayOfstring') + languages.string.append('All') + campaign.Languages = languages + campaign.Name = "Ad recommendation test " + str(datetime.now()) + campaign.TimeZone = 'PacificTimeUSCanadaTijuana' + campaigns.Campaign.append(campaign) + + output_status_message("-----\nAddCampaigns:") + add_campaigns_response = campaign_service.AddCampaigns( + AccountId=authorization_data.account_id, + Campaigns=campaigns + ) + campaign_ids = { + 'long': add_campaigns_response.CampaignIds['long'] if add_campaigns_response.CampaignIds['long'] else None + } + output_status_message("CampaignIds:") + output_array_of_long(campaign_ids) + output_status_message("PartialErrors:") + output_array_of_batcherror(add_campaigns_response.PartialErrors) + + # Add an ad group within the campaign. + ad_groups = campaign_service.factory.create('ArrayOfAdGroup') + ad_group = set_elements_to_none(campaign_service.factory.create('AdGroup')) + ad_group.Name = "Holiday Sale" + ad_group.StartDate = None + end_date = campaign_service.factory.create('Date') + end_date.Day = 31 + end_date.Month = 12 + current_time = gmtime() + end_date.Year = current_time.tm_year + ad_group.EndDate = end_date + cpc_bid = campaign_service.factory.create('Bid') + cpc_bid.Amount = 0.09 + ad_group.CpcBid = cpc_bid + # Network cannot be set for ad groups in Audience campaigns + ad_group.Network = None + ad_groups.AdGroup.append(ad_group) + + output_status_message("-----\nAddAdGroups:") + add_ad_groups_response = campaign_service.AddAdGroups( + CampaignId=campaign_ids['long'][0], + AdGroups=ad_groups, + ReturnInheritedBidStrategyTypes=False + ) + ad_group_ids = { + 'long': add_ad_groups_response.AdGroupIds['long'] if add_ad_groups_response.AdGroupIds['long'] else None + } + output_status_message("AdGroupIds:") + output_array_of_long(ad_group_ids) + output_status_message("PartialErrors:") + output_array_of_batcherror(add_ad_groups_response.PartialErrors) + + # Add a responsive ad within the ad group + ads = campaign_service.factory.create('ArrayOfAd') + ads.Ad.append(responsive_ad) + + output_status_message("-----\nAddAds:") + add_ads_response = campaign_service.AddAds( + AdGroupId=ad_group_ids['long'][0], + Ads=ads + ) + ad_ids = { + 'long': add_ads_response.AdIds['long'] if add_ads_response.AdIds['long'] else None + } + output_status_message("AdIds:") + output_array_of_long(ad_ids) + output_status_message("PartialErrors:") + output_array_of_batcherror(add_ads_response.PartialErrors) + + output_status_message(f"-----\nCreated campaign: + {campaign.Name}") + + if not do_cleanup: + return + else: + # Delete the account's media + output_status_message("-----\nDeleteMedia:") + media_ids = campaign_service.factory.create('ns3:ArrayOflong') + media_ids['long'] = [obj.Asset.Id for obj in responsive_ad.Images.AssetLink] + delete_media_response = campaign_service.DeleteMedia( + AccountId=authorization_data.account_id, + MediaIds=media_ids + ) + + for media_id in media_ids['long']: + output_status_message("Deleted Media Id {0}".format(media_id)) + + # Delete the campaign and everything it contains e.g., ad groups and ads + output_status_message("-----\nDeleteCampaigns:") + campaign_service.DeleteCampaigns( + AccountId=authorization_data.account_id, + CampaignIds=campaign_ids + ) + output_status_message("Deleted Campaign Id {0}".format(campaign_ids['long'][0])) + + + except WebFault as ex: + output_webfault_errors(ex) + except Exception as ex: + output_status_message(ex) + +def save_images(image_suggestions): + medias_to_add = campaign_service.factory.create('ArrayOfMedia') + for item in image_suggestions: + image = item.Image + image_bytes = download_bytes(item.ImageUrl) + image_base64 = base64.b64encode(image_bytes).decode('utf-8') + image.Data = image_base64 + medias_to_add.Media.append(image) + + media_ids = campaign_service.AddMedia( + AccountId=authorization_data.account_id, + Media=medias_to_add + ) + + for i in range(len(media_ids['long'])): + image_suggestions[i].AssetLink.Asset.Id = media_ids['long'][i] + +def download_bytes(url): + response = requests.get(url, stream=True) + response.raise_for_status() + + output_stream = bytearray() + + for chunk in response.iter_content(chunk_size=4096): + if chunk: + output_stream.extend(chunk) + + return bytes(output_stream) + +# Main execution +if __name__ == '__main__': + + print("Loading the web service client proxies...") + + authorization_data=AuthorizationData( + account_id=None, + customer_id=None, + developer_token=DEVELOPER_TOKEN, + authentication=None, + ) + + campaign_service=ServiceClient( + service='CampaignManagementService', + version=13, + authorization_data=authorization_data, + environment=ENVIRONMENT, + ) + + authenticate(authorization_data) + + main(authorization_data) diff --git a/setup.py b/setup.py index d309d24f..7710251c 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ except ImportError: from distutils.core import setup -VERSION = '13.0.23.1' +VERSION = '13.0.24' with open('README.rst', 'r') as f: readme = f.read()