From 1db560efdff775279af8c30cdc9e0ebd47692821 Mon Sep 17 00:00:00 2001 From: Qingjun Tian Date: Sun, 23 Jun 2019 19:58:33 -0700 Subject: [PATCH] update to version 12.13.3 --- HISTORY.rst | 6 + bingads/manifest.py | 2 +- bingads/v12/bulk/entities/bulk_ads.py | 5 + bingads/v12/internal/extensions.py | 2 +- bingads/v13/bulk/entities/__init__.py | 1 + bingads/v13/bulk/entities/bulk_ads.py | 5 + bingads/v13/bulk/entities/bulk_campaign.py | 27 ++ bingads/v13/bulk/entities/feeds/__init__.py | 2 + bingads/v13/bulk/entities/feeds/bulk_feed.py | 150 ++++++++ .../v13/bulk/entities/feeds/bulk_feed_item.py | 350 ++++++++++++++++++ .../v13/internal/bulk/bulk_object_factory.py | 2 + bingads/v13/internal/bulk/csv_headers.py | 7 +- bingads/v13/internal/bulk/string_table.py | 5 + bingads/v13/internal/extensions.py | 71 +++- setup.py | 2 +- 15 files changed, 631 insertions(+), 6 deletions(-) create mode 100644 bingads/v13/bulk/entities/feeds/__init__.py create mode 100644 bingads/v13/bulk/entities/feeds/bulk_feed.py create mode 100644 bingads/v13/bulk/entities/feeds/bulk_feed_item.py diff --git a/HISTORY.rst b/HISTORY.rst index 1f0459d0..14ed1293 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,12 @@ .. :changelog: Release History +12.13.3(2019-06-15) ++++++++++++++++++++++++++ +* Updated Bing Ads API version 12 and 13 service proxies to reflect recent interface changes. For more information please see the Bing Ads API Release Notes: https://docs.microsoft.com/en-us/advertising/guides/release-notes. +* For Bing Ads API version 13, added BulkFeed and BulkFeedItem for ad customizer feeds and page feeds. For more information please see the Feed: https://docs.microsoft.com/en-us/advertising/bulk-service/feed?view=bingads-13 and Feed Item: https://docs.microsoft.com/en-us/advertising/bulk-service/feed-item?view=bingads-13 reference documentation. +* For Bing Ads API version 13, added the mapping for PageFeedIds in BulkCampaign. For more information please see the Campaign: https://docs.microsoft.com/en-us/advertising/bulk-service/dynamic-search-ad?view=bingads-13#pagefeedids reference documentation. +* For Bing Ads API version 13, added the mapping for TextPart2 in BulkDynamicSearchAd. For more information please see the Dynamic Search Ad: https://docs.microsoft.com/en-us/advertising/bulk-service/dynamic-search-ad?view=bingads-13#textpart2 reference documentation. 12.13.2(2019-05-15) +++++++++++++++++++++++++ diff --git a/bingads/manifest.py b/bingads/manifest.py index 3ce9864b..f200f209 100644 --- a/bingads/manifest.py +++ b/bingads/manifest.py @@ -1,5 +1,5 @@ import sys -VERSION = '12.13.2' +VERSION = '12.13.3' BULK_FORMAT_VERSION_5 = '5.0' BULK_FORMAT_VERSION_6 = '6.0' WORKING_NAME = 'BingAdsSDKPython' diff --git a/bingads/v12/bulk/entities/bulk_ads.py b/bingads/v12/bulk/entities/bulk_ads.py index 486175b6..c6f76454 100644 --- a/bingads/v12/bulk/entities/bulk_ads.py +++ b/bingads/v12/bulk/entities/bulk_ads.py @@ -566,6 +566,11 @@ def dynamic_search_ad(self, dynamic_search_ad): field_to_csv=lambda c: c.dynamic_search_ad.Path2, csv_to_field=lambda c, v: setattr(c.dynamic_search_ad, 'Path2', v) ), + _SimpleBulkMapping( + header=_StringTable.TextPart2, + field_to_csv=lambda c: c.dynamic_search_ad.TextPart2, + csv_to_field=lambda c, v: setattr(c.dynamic_search_ad, 'TextPart2', v) + ), ] def process_mappings_from_row_values(self, row_values): diff --git a/bingads/v12/internal/extensions.py b/bingads/v12/internal/extensions.py index 62cef57b..0d378e40 100644 --- a/bingads/v12/internal/extensions.py +++ b/bingads/v12/internal/extensions.py @@ -627,7 +627,7 @@ def csv_to_field_AdSchedule(entity, value): if value is None or value.strip() == '': return daytime_strs = value.split(';') - ad_schedule_pattern = '\((Monday|Tuesday|Wednesday|ThursDay|Friday|Saturday|Sunday)\[(\d\d?):(\d\d)-(\d\d?):(\d\d)\]\)' + ad_schedule_pattern = '\((Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)\[(\d\d?):(\d\d)-(\d\d?):(\d\d)\]\)' pattern = re.compile(ad_schedule_pattern, re.IGNORECASE) daytimes = [] for daytime_str in daytime_strs: diff --git a/bingads/v13/bulk/entities/__init__.py b/bingads/v13/bulk/entities/__init__.py index afc5d3d8..6a03e316 100644 --- a/bingads/v13/bulk/entities/__init__.py +++ b/bingads/v13/bulk/entities/__init__.py @@ -27,3 +27,4 @@ from .labels import * from .bulk_offline_conversion import * from .bulk_experiment import * +from .feeds import * diff --git a/bingads/v13/bulk/entities/bulk_ads.py b/bingads/v13/bulk/entities/bulk_ads.py index 8c392f50..41074cea 100644 --- a/bingads/v13/bulk/entities/bulk_ads.py +++ b/bingads/v13/bulk/entities/bulk_ads.py @@ -553,6 +553,11 @@ def dynamic_search_ad(self, dynamic_search_ad): field_to_csv=lambda c: c.dynamic_search_ad.Path2, csv_to_field=lambda c, v: setattr(c.dynamic_search_ad, 'Path2', v) ), + _SimpleBulkMapping( + header=_StringTable.TextPart2, + field_to_csv=lambda c: c.dynamic_search_ad.TextPart2, + csv_to_field=lambda c, v: setattr(c.dynamic_search_ad, 'TextPart2', v) + ), ] def process_mappings_from_row_values(self, row_values): diff --git a/bingads/v13/bulk/entities/bulk_campaign.py b/bingads/v13/bulk/entities/bulk_campaign.py index bfbf026f..558c9e7a 100644 --- a/bingads/v13/bulk/entities/bulk_campaign.py +++ b/bingads/v13/bulk/entities/bulk_campaign.py @@ -262,6 +262,28 @@ def _write_domain_language(c): return None return bulk_str(dsa_setting.Language) + @staticmethod + def _read_page_feed_ids(c, v): + if not c.campaign.CampaignType: + return None + campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType] + if 'dynamicsearchads' in campgaign_types: + dsa_setting = c._get_dsa_setting() + if not dsa_setting: + return None + dsa_setting.PageFeedIds.long = csv_to_field_PageFeedIds(v) + + @staticmethod + def _write_page_feed_ids(c): + if not c.campaign.CampaignType: + return None + campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType] + if 'dynamicsearchads' in campgaign_types: + dsa_setting = c._get_dsa_setting() + if not dsa_setting: + return None + return field_to_csv_Ids(dsa_setting.PageFeedIds, c.campaign.Id) + @staticmethod def _read_website(c, v): if not c.campaign.CampaignType: @@ -420,6 +442,11 @@ def _write_website(c): field_to_csv=lambda c: bulk_optional_str(c.campaign.FinalUrlSuffix, c.campaign.Id), csv_to_field=lambda c, v: setattr(c.campaign, 'FinalUrlSuffix', v) ), + _SimpleBulkMapping( + header=_StringTable.PageFeedIds, + field_to_csv=lambda c: BulkCampaign._write_page_feed_ids(c), + csv_to_field=lambda c, v: BulkCampaign._read_page_feed_ids(c, v) + ), ] def read_additional_data(self, stream_reader): diff --git a/bingads/v13/bulk/entities/feeds/__init__.py b/bingads/v13/bulk/entities/feeds/__init__.py new file mode 100644 index 00000000..02ffef3a --- /dev/null +++ b/bingads/v13/bulk/entities/feeds/__init__.py @@ -0,0 +1,2 @@ +from .bulk_feed import * +from .bulk_feed_item import * \ No newline at end of file diff --git a/bingads/v13/bulk/entities/feeds/bulk_feed.py b/bingads/v13/bulk/entities/feeds/bulk_feed.py new file mode 100644 index 00000000..eee51551 --- /dev/null +++ b/bingads/v13/bulk/entities/feeds/bulk_feed.py @@ -0,0 +1,150 @@ +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 * + + +class BulkFeed(_SingleRecordBulkEntity): + """ Represents a feed. + + Properties of this class and of classes that it is derived from, correspond to fields of the Feed record in a bulk file. + For more information, see Feed at https://go.microsoft.com/fwlink/?linkid=846127 + + *See also:* + + * :class:`.BulkServiceManager` + * :class:`.BulkOperation` + * :class:`.BulkFileReader` + * :class:`.BulkFileWriter` + """ + + def __init__(self, id = None, status=None, account_id=None, sub_type=None, feed_name = None, custom_attr=None): + super(BulkFeed, self).__init__() + self._status = status + self._account_id = account_id + self._id = id + self._sub_type = sub_type + self._name = feed_name + self._custom_attributes = custom_attr + + + @property + def id(self): + """ the status of bulk record + Corresponds to the 'Id' field in the bulk file. + + :rtype: str + """ + return self._id + + @id.setter + def id(self, value): + self._id = value + + @property + def status(self): + """ the status of bulk record + Corresponds to the 'Status' field in the bulk file. + + :rtype: str + """ + return self._status + + @status.setter + def status(self, value): + self._status = value + + @property + def account_id(self): + """ the id of the account which contains the feed + Corresponds to the 'Parent Id' field in the bulk file. + + :rtype: long + """ + return self._account_id + + @account_id.setter + def account_id(self, value): + self._account_id = value + + @property + def name(self): + """ the id of the account which contains the feed + Corresponds to the 'Feed Name' field in the bulk file. + + :rtype: long + """ + return self._name + + @name.setter + def name(self, value): + self._name = value + + @property + def sub_type(self): + """ the id of the account which contains the feed + Corresponds to the 'Sub Type' field in the bulk file. + + :rtype: long + """ + return self._sub_type + + @sub_type.setter + def sub_type(self, value): + self._sub_type = value + + @property + def custom_attributes(self): + """ the id of the account which contains the feed + Corresponds to the 'Custom Attributes' field in the bulk file. + + :rtype: long + """ + return self._custom_attributes + + @custom_attributes.setter + def custom_attributes(self, value): + self._custom_attributes = value + + _MAPPINGS = [ + _SimpleBulkMapping( + header=_StringTable.Id, + field_to_csv=lambda c: bulk_str(c.id), + csv_to_field=lambda c, v: setattr(c, 'id', int(v) if v else None) + ), + _SimpleBulkMapping( + header=_StringTable.ParentId, + 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.Status, + field_to_csv=lambda c: c.status, + csv_to_field=lambda c, v: setattr(c, 'status', v) + ), + _SimpleBulkMapping( + header=_StringTable.FeedName, + field_to_csv=lambda c: bulk_str(c.name), + csv_to_field=lambda c, v: setattr(c, 'name', v) + ), + _SimpleBulkMapping( + header=_StringTable.SubType, + field_to_csv=lambda c: bulk_str(c.sub_type), + csv_to_field=lambda c, v: setattr(c, 'sub_type', v) + ), + _SimpleBulkMapping( + header=_StringTable.CustomAttributes, + field_to_csv=lambda c: field_to_csv_CustomAttributes(c.custom_attributes), + csv_to_field=lambda c, v: csv_to_field_CustomAttributes(c, v) + ), + ] + + def process_mappings_from_row_values(self, row_values): + row_values.convert_to_entity(self, BulkFeed._MAPPINGS) + + def process_mappings_to_row_values(self, row_values, exclude_readonly_data): + self.convert_to_values(row_values, BulkFeed._MAPPINGS) + + def read_additional_data(self, stream_reader): + super(BulkFeed, self).read_additional_data(stream_reader) diff --git a/bingads/v13/bulk/entities/feeds/bulk_feed_item.py b/bingads/v13/bulk/entities/feeds/bulk_feed_item.py new file mode 100644 index 00000000..b5b09aab --- /dev/null +++ b/bingads/v13/bulk/entities/feeds/bulk_feed_item.py @@ -0,0 +1,350 @@ +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 datetime import datetime + +class BulkFeedItem(_SingleRecordBulkEntity): + """ Represents a feed item. + + Properties of this class and of classes that it is derived from, correspond to fields of the Feed Item record in a bulk file. + For more information, see Feed at https://go.microsoft.com/fwlink/?linkid=846127 + + *See also:* + + * :class:`.BulkServiceManager` + * :class:`.BulkOperation` + * :class:`.BulkFileReader` + * :class:`.BulkFileWriter` + """ + + @staticmethod + def _bulk_datetime_optional_str(value, id): + if value is None: + return None + + if not value: + return "delete_value" if id > 0 else None + + return value.strftime('%Y/%m/%d %H:%M:%S') + + @staticmethod + def _parse_datetime(value): + + if not value: + return None + try: + if value == 'delete_value': + return None + else: + return datetime.strptime(value, '%Y/%m/%d %H:%M:%S') + except Exception: + return parse_datetime(value) + + + def __init__(self): + super(BulkFeedItem, self).__init__() + self._id = None + self._feed_id = None + self._campaign = None + self._ad_group = None + self._audience_id = None + self._custom_attributes = None + self._status = None + self._start_date = None + self._end_date = None + self._keyword = None + self._daytime_ranges = None + self._match_type = None + self._location_id = None + self._intent_option = None + self._device_preference = None + + + + _MAPPINGS = [ + _SimpleBulkMapping( + header=_StringTable.Id, + field_to_csv=lambda c: bulk_str(c.id), + csv_to_field=lambda c, v: setattr(c, 'id', int(v) if v else None) + ), + _SimpleBulkMapping( + header=_StringTable.ParentId, + field_to_csv=lambda c: bulk_str(c.feed_id), + csv_to_field=lambda c, v: setattr(c, 'feed_id', int(v) if v else None) + ), + _SimpleBulkMapping( + header=_StringTable.AudienceId, + field_to_csv=lambda c: bulk_str(c.audience_id), + csv_to_field=lambda c, v: setattr(c, 'audience_id', int(v) if v else None) + ), + _SimpleBulkMapping( + header=_StringTable.Target, + field_to_csv=lambda c: bulk_str(c.location_id), + csv_to_field=lambda c, v: setattr(c, 'location_id', int(v) if v else None) + ), + _SimpleBulkMapping( + header=_StringTable.Status, + field_to_csv=lambda c: c.status, + csv_to_field=lambda c, v: setattr(c, 'status', v) + ), + _SimpleBulkMapping( + header=_StringTable.MatchType, + field_to_csv=lambda c: c.match_type, + csv_to_field=lambda c, v: setattr(c, 'match_type', v) + ), + _SimpleBulkMapping( + header=_StringTable.PhysicalIntent, + field_to_csv=lambda c: c.intent_option, + csv_to_field=lambda c, v: setattr(c, 'intent_option', v) + ), + _SimpleBulkMapping( + header=_StringTable.Campaign, + field_to_csv=lambda c: bulk_str(c.campaign), + csv_to_field=lambda c, v: setattr(c, 'campaign', v) + ), + _SimpleBulkMapping( + header=_StringTable.AdGroup, + field_to_csv=lambda c: bulk_str(c.ad_group), + csv_to_field=lambda c, v: setattr(c, 'ad_group', v) + ), + _SimpleBulkMapping( + header=_StringTable.Keyword, + field_to_csv=lambda c: bulk_str(c.keyword), + csv_to_field=lambda c, v: setattr(c, 'keyword', v) + ), + _SimpleBulkMapping( + header=_StringTable.CustomAttributes, + field_to_csv=lambda c: bulk_str(c.custom_attributes), + csv_to_field=lambda c, v: setattr(c, 'custom_attributes', v) + ), + _SimpleBulkMapping( + header=_StringTable.DevicePreference, + field_to_csv=lambda c: bulk_device_preference_str(c.device_preference), + csv_to_field=lambda c, v: setattr(c, 'device_preference', parse_device_preference(v)) + ), + _SimpleBulkMapping( + header=_StringTable.StartDate, + field_to_csv=lambda c: BulkFeedItem._bulk_datetime_optional_str(c.start_date, c.id), + csv_to_field=lambda c, v: setattr(c, 'start_date', BulkFeedItem._parse_datetime(v)) + ), + _SimpleBulkMapping( + header=_StringTable.EndDate, + field_to_csv=lambda c: BulkFeedItem._bulk_datetime_optional_str(c.end_date, c.id), + csv_to_field=lambda c, v: setattr(c, 'end_date', BulkFeedItem._parse_datetime(v)) + ), + _SimpleBulkMapping( + header=_StringTable.AdSchedule, + field_to_csv=lambda c: field_to_csv_FeedItemAdSchedule(c, c.id), + csv_to_field=lambda c, v: csv_to_field_FeedItemAdSchedule(c, v) + ), + ] + + + @property + def device_preference(self): + """ the id of the account which contains the feed + Corresponds to the 'Device Preference' field in the bulk file. + + :rtype: long + """ + return self._device_preference + + @device_preference.setter + def device_preference(self, value): + self._device_preference = value + + @property + def intent_option(self): + """ the id of the account which contains the feed + Corresponds to the 'Physical Intent' field in the bulk file. + + :rtype: str + """ + return self._intent_option + + @intent_option.setter + def intent_option(self, value): + self._intent_option = value + + @property + def location_id(self): + """ the id of the account which contains the feed + Corresponds to the 'Target' field in the bulk file. + + :rtype: long + """ + return self._location_id + + @location_id.setter + def location_id(self, value): + self._location_id = value + + + @property + def match_type(self): + """ the match type of bulk record + Corresponds to the 'Match Type' field in the bulk file. + + :rtype: str + """ + return self._match_type + + @match_type.setter + def match_type(self, value): + self._match_type = value + + @property + def keyword(self): + """ the status of bulk record + Corresponds to the 'Keyword' field in the bulk file. + + :rtype: str + """ + return self._keyword + + @keyword.setter + def keyword(self, value): + self._keyword = value + + @property + def daytime_ranges(self): + """ the status of bulk record + Corresponds to the 'Ad Schedule' field in the bulk file. + + :rtype: str + """ + return self._daytime_ranges + + @daytime_ranges.setter + def daytime_ranges(self, value): + self._daytime_ranges = value + + @property + def end_date(self): + """ the status of bulk record + Corresponds to the 'End Date' field in the bulk file. + + :rtype: str + """ + return self._end_date + + @end_date.setter + def end_date(self, value): + self._end_date = value + + @property + def start_date(self): + """ the status of bulk record + Corresponds to the 'Start Date' field in the bulk file. + + :rtype: str + """ + return self._start_date + + @start_date.setter + def start_date(self, value): + self._start_date = value + + + @property + def custom_attributes(self): + """ the status of bulk record + Corresponds to the 'Custom Attributes' field in the bulk file. + + :rtype: str + """ + return self._custom_attributes + + @custom_attributes.setter + def custom_attributes(self, value): + self._custom_attributes = value + + @property + def audience_id(self): + """ the status of bulk record + Corresponds to the 'Audience Id' field in the bulk file. + + :rtype: long + """ + return self._audience_id + + @audience_id.setter + def audience_id(self, value): + self._audience_id = value + + @property + def ad_group(self): + """ the status of bulk record + Corresponds to the 'Ad Group' field in the bulk file. + + :rtype: str + """ + return self._ad_group + + @ad_group.setter + def ad_group(self, value): + self._ad_group = value + + @property + def id(self): + """ the status of bulk record + Corresponds to the 'Id' field in the bulk file. + + :rtype: long + """ + return self._id + + @id.setter + def id(self, value): + self._id = value + + + @property + def feed_id(self): + """ the status of bulk record + Corresponds to the 'Parent Id' field in the bulk file. + + :rtype: long + """ + return self._feed_id + + @feed_id.setter + def feed_id(self, value): + self._feed_id = value + + @property + def status(self): + """ the status of bulk record + Corresponds to the 'Status' field in the bulk file. + + :rtype: str + """ + return self._status + + @status.setter + def status(self, value): + self._status = value + + @property + def campaign(self): + """ the id of the account which contains the feed + Corresponds to the 'campaign' field in the bulk file. + + :rtype: str + """ + return self._campaign + + @campaign.setter + def campaign(self, value): + self._campaign = value + + def process_mappings_from_row_values(self, row_values): + row_values.convert_to_entity(self, BulkFeedItem._MAPPINGS) + + def process_mappings_to_row_values(self, row_values, exclude_readonly_data): + self.convert_to_values(row_values, BulkFeedItem._MAPPINGS) + + def read_additional_data(self, stream_reader): + super(BulkFeedItem, 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 21661aa8..56369fde 100644 --- a/bingads/v13/internal/bulk/bulk_object_factory.py +++ b/bingads/v13/internal/bulk/bulk_object_factory.py @@ -142,6 +142,8 @@ class _BulkObjectFactory(): _StringTable.AdGroupActionAdExtension: _EntityInfo(lambda: BulkAdGroupActionAdExtension()), _StringTable.CampaignActionAdExtension: _EntityInfo(lambda: BulkCampaignActionAdExtension()), _StringTable.Experiment: _EntityInfo(lambda: BulkExperiment()), + _StringTable.Feed: _EntityInfo(lambda: BulkFeed()), + _StringTable.FeedItem: _EntityInfo(lambda: BulkFeedItem()) } ADDITIONAL_OBJECT_MAP = { diff --git a/bingads/v13/internal/bulk/csv_headers.py b/bingads/v13/internal/bulk/csv_headers.py index 5a27ae10..e98a46c7 100644 --- a/bingads/v13/internal/bulk/csv_headers.py +++ b/bingads/v13/internal/bulk/csv_headers.py @@ -58,7 +58,6 @@ class _CsvHeaders: # Location Target _StringTable.Target, - _StringTable.PhysicalIntent, _StringTable.TargetAll, _StringTable.BidAdjustment, _StringTable.RadiusTargetId, @@ -319,6 +318,7 @@ class _CsvHeaders: _StringTable.DynamicAdTargetValue2, _StringTable.DynamicAdTargetCondition3, _StringTable.DynamicAdTargetValue3, + _StringTable.PageFeedIds, # Labels _StringTable.ColorCode, @@ -335,6 +335,11 @@ class _CsvHeaders: _StringTable.MSCLKIDAutoTaggingEnabled, _StringTable.FinalUrlSuffix, + + # Feeds + _StringTable.CustomAttributes, + _StringTable.FeedName, + _StringTable.PhysicalIntent, ] diff --git a/bingads/v13/internal/bulk/string_table.py b/bingads/v13/internal/bulk/string_table.py index f9cfc13d..f1726867 100644 --- a/bingads/v13/internal/bulk/string_table.py +++ b/bingads/v13/internal/bulk/string_table.py @@ -52,6 +52,11 @@ class _StringTable: TextPart2="Text Part 2" Website = "Website" Target = "Target" + Feed = "Feed" + FeedItem = "Feed Item" + FeedName = "Feed Name" + CustomAttributes = "Custom Attributes"; + PageFeedIds = "Page Feed Ids"; PhysicalIntent = "Physical Intent" Bid = "Bid" Profile = "Profile" diff --git a/bingads/v13/internal/extensions.py b/bingads/v13/internal/extensions.py index d00a824b..199d040f 100644 --- a/bingads/v13/internal/extensions.py +++ b/bingads/v13/internal/extensions.py @@ -624,10 +624,10 @@ def field_to_csv_AdSchedule(entity, id): def csv_to_field_AdSchedule(entity, value): - if value is None or value.strip() == '': + if value is None or value.strip() == '' or value == DELETE_VALUE: return daytime_strs = value.split(';') - ad_schedule_pattern = '\((Monday|Tuesday|Wednesday|ThursDay|Friday|Saturday|Sunday)\[(\d\d?):(\d\d)-(\d\d?):(\d\d)\]\)' + ad_schedule_pattern = '\((Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)\[(\d\d?):(\d\d)-(\d\d?):(\d\d)\]\)' pattern = re.compile(ad_schedule_pattern, re.IGNORECASE) daytimes = [] for daytime_str in daytime_strs: @@ -645,6 +645,44 @@ def csv_to_field_AdSchedule(entity, value): entity.DayTimeRanges.DayTime = daytimes +def field_to_csv_FeedItemAdSchedule(entity, id): + """ + get the bulk string for FeedItem DayTimeRanges + :param entity: Scheduling entity + :return: bulk str + """ + if entity is None: + return None + if entity.daytime_ranges is None: + return DELETE_VALUE if id and id > 0 else None + return ';'.join('({0}[{1:02d}:{2:02d}-{3:02d}:{4:02d}])' + .format(d.Day, d.StartHour, int(minute_bulk_str(d.StartMinute)), d.EndHour, int(minute_bulk_str(d.EndMinute))) + for d in entity.daytime_ranges + ) + + +def csv_to_field_FeedItemAdSchedule(entity, value): + if value is None or value.strip() == '' or value == DELETE_VALUE: + return + daytime_strs = value.split(';') + ad_schedule_pattern = '\((Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)\[(\d\d?):(\d\d)-(\d\d?):(\d\d)\]\)' + pattern = re.compile(ad_schedule_pattern, re.IGNORECASE) + daytimes = [] + for daytime_str in daytime_strs: + match = pattern.match(daytime_str) + if match: + daytime = _CAMPAIGN_OBJECT_FACTORY_V13.create('DayTime') + daytime.Day = format_Day(match.group(1)) + daytime.StartHour = int(match.group(2)) + daytime.StartMinute = parse_minute(match.group(3)) + daytime.EndHour = int(match.group(4)) + daytime.EndMinute = parse_minute(match.group(5)) + daytimes.append(daytime) + else: + raise ValueError('Unable to parse DayTime: {0}'.format(daytime_str)) + entity.daytime_ranges = daytimes + + def field_to_csv_SchedulingStartDate(entity, id): """ write scheduling StartDate to bulk string @@ -1408,3 +1446,32 @@ def field_to_csv_SupportedCampaignTypes(entity): if len(entity.string) == 0: return None return ';'.join(entity.string) + + +def field_to_csv_CustomAttributes(custom_attributes): + if custom_attributes is None: + return None + if len(custom_attributes) > 0: + return json.dumps(custom_attributes) + return None + +def csv_to_field_CustomAttributes(feed, value): + if value is None or value == '': + return + feed.custom_attributes = json.loads(value) + +def field_to_csv_Ids(ids, entity_id): + if ids is None and entity_id is not None and entity_id > 0: + return DELETE_VALUE + + if ids is None or len(ids.long) == 0: + return None + return ';'.join(str(id) for id in ids.long) + +def csv_to_field_PageFeedIds(value): + if value is None or value == DELETE_VALUE: + return None + if len(value) == 0: + return [] + return [int(i) for i in value.split(';')] + \ No newline at end of file diff --git a/setup.py b/setup.py index f282cf5a..7c072d4c 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ except ImportError: from distutils.core import setup -VERSION = '12.13.2' +VERSION = '12.13.3' with open('README.rst', 'r') as f: readme = f.read()