Skip to content

Commit 17c2e7c

Browse files
committed
Bing Ads Python SDK version 10.4.12 release
1 parent 2712f6d commit 17c2e7c

File tree

7 files changed

+279
-2
lines changed

7 files changed

+279
-2
lines changed

HISTORY.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
Release History
44
---------------
55

6+
10.4.12(2017-02-28)
7+
+++++++++++++++++++
8+
9+
* Support Remarketing list bulk upload
10+
* Add Remarketing Rule in bulk schema
11+
612
10.4.11(2016-12-30)
713
+++++++++++++++++++
814

bingads/internal/extensions.py

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@
2020
Webpage = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:Webpage')
2121
WebpageConditionOperand = _CAMPAIGN_OBJECT_FACTORY_V10.create('WebpageConditionOperand')
2222

23+
RemarketingRule = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:RemarketingRule')
24+
PageVisitorsRule = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:PageVisitorsRule')
25+
PageVisitorsWhoVisitedAnotherPageRule = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:PageVisitorsWhoVisitedAnotherPageRule')
26+
PageVisitorsWhoDidNotVisitAnotherPageRule = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:PageVisitorsWhoDidNotVisitAnotherPageRule')
27+
CustomEventsRule = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:CustomEventsRule')
28+
StringOperator = _CAMPAIGN_OBJECT_FACTORY_V10.create('StringOperator')
29+
NumberOperator = _CAMPAIGN_OBJECT_FACTORY_V10.create('NumberOperator')
30+
2331
def bulk_str(value):
2432
if value is None or (hasattr(value, 'value') and value.value is None):
2533
return None
@@ -692,6 +700,9 @@ def csv_to_entity_DSAWebpageParameter(row_values, entity):
692700
elif webpage_condition.lower() == 'pagecontent':
693701
condition.Operand = WebpageConditionOperand.PageContent
694702
else:
703+
# TODO wait bug 54825 to be fixed
704+
if webpage_condition.lower() == 'none':
705+
continue
695706
raise ValueError("Unknown WebpageConditionOperand value: {0}".format(webpage_condition))
696707

697708
condition.Argument = webpage_value
@@ -715,3 +726,256 @@ def parse_bool(value):
715726
return False
716727
else:
717728
raise ValueError('Unable to parse bool value: {0}.'.format(value))
729+
730+
731+
def field_to_csv_RemarketingRule(entity):
732+
"""
733+
convert remarketing rule to bulk string
734+
:param entity: remarketing list entity
735+
"""
736+
if entity.Rule == None:
737+
return None
738+
739+
rule = entity.Rule
740+
if (isinstance(rule, type(PageVisitorsRule))):
741+
return 'PageVisitors{0}'.format(rule_item_groups_str(rule.RuleItemGroups.RuleItemGroup))
742+
elif (isinstance(rule, type(PageVisitorsWhoVisitedAnotherPageRule))):
743+
return 'PageVisitorsWhoVisitedAnotherPage({0}) and ({1})'.format(
744+
rule_item_groups_str(rule.RuleItemGroups.RuleItemGroup),
745+
rule_item_groups_str(rule.AnotherRuleItemGroups.RuleItemGroup))
746+
elif (isinstance(rule, type(PageVisitorsWhoDidNotVisitAnotherPageRule))):
747+
return 'PageVisitorsWhoDidNotVisitAnotherPage({0}) and not ({1})'.format(
748+
rule_item_groups_str(rule.IncludeRuleItemGroups.RuleItemGroup),
749+
rule_item_groups_str(rule.ExcludeRuleItemGroups.RuleItemGroup))
750+
elif (isinstance(rule, type(CustomEventsRule))):
751+
return 'CustomEvents{0}'.format(custom_event_rule_str(rule))
752+
elif (isinstance(rule, type(RemarketingRule))):
753+
return None
754+
else:
755+
raise ValueError('Unsupported Remarketing Rule type: {0}'.format(type(entity.RemarketingRule)))
756+
757+
758+
def rule_item_groups_str(groups):
759+
if groups is None or len(groups) == 0:
760+
raise ValueError('Remarketing RuleItemGroups is None or empty.')
761+
762+
return ' or '.join(['({0})'.format(rule_items_str(group.Items.RuleItem)) for group in groups])
763+
764+
765+
def rule_items_str(items):
766+
if items is None or len(items) == 0:
767+
raise ValueError('Remarketing RuleItem list is None or empty.')
768+
769+
return ' and '.join(['({0} {1} {2})'.format(item.Operand, item.Operator, item.Value) for item in items])
770+
771+
772+
def custom_event_rule_str(rule):
773+
rule_items = []
774+
if rule.ActionOperator is not None and rule.Action is not None:
775+
rule_items.append('Action {0} {1}'.format(rule.ActionOperator, rule.Action))
776+
if rule.CategoryOperator is not None and rule.Category is not None:
777+
rule_items.append('Category {0} {1}'.format(rule.CategoryOperator, rule.Category))
778+
if rule.LabelOperator is not None and rule.Label is not None:
779+
rule_items.append('Label {0} {1}'.format(rule.LabelOperator, rule.Label))
780+
if rule.ValueOperator is not None and rule.Value is not None:
781+
rule_items.append('Value {0} {1}'.format(rule.ValueOperator, rule.Value))
782+
783+
if len(rule_items) == 0:
784+
raise ValueError('Remarketing CustomEvents RuleItem list is empty')
785+
786+
return ' and '.join('({0})'.format(item) for item in rule_items)
787+
788+
789+
def csv_to_field_RemarketingRule(entity, value):
790+
"""
791+
parse remarketing rule string and set remarketing rule attribute value
792+
:param entity: remarketing list entity
793+
:param value: bulk string value
794+
"""
795+
if value is None or value == '':
796+
return
797+
798+
type_end_pos = value.index('(')
799+
if type_end_pos <= 0:
800+
raise ValueError('Invalid Remarketing Rule: {0}'.format(value))
801+
802+
rule_type = value[:type_end_pos]
803+
rule = value[type_end_pos:]
804+
805+
if rule_type.lower() == 'pagevisitors':
806+
entity.Rule = parse_rule_PageVisitors(rule)
807+
elif rule_type.lower() == 'pagevisitorswhovisitedanotherpage':
808+
entity.Rule = parse_rule_PageVisitorsWhoVisitedAnotherPage(rule)
809+
elif rule_type.lower() == 'pagevisitorswhodidnotvisitanotherpage':
810+
entity.Rule = parse_rule_PageVisitorsWhoDidNotVisitAnotherPage(rule)
811+
elif rule_type.lower() == 'customevents':
812+
entity.Rule = parse_rule_CustomEvents(rule)
813+
else:
814+
raise ValueError('Invalid Remarketing Rule Type: {0}'.format(rule_type))
815+
816+
817+
def parse_rule_PageVisitors(rule_str):
818+
rule = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:PageVisitorsRule')
819+
rule.Type = 'PageVisitors'
820+
rule.RuleItemGroups = parse_rule_groups(rule_str)
821+
return rule
822+
823+
824+
def parse_rule_PageVisitorsWhoVisitedAnotherPage(rule_str):
825+
rule = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:PageVisitorsWhoVisitedAnotherPageRule')
826+
rule.Type = 'PageVisitorsWhoVisitedAnotherPage'
827+
828+
groups_split = '))) and ((('
829+
groups_string_list = rule_str.split(groups_split)
830+
831+
rule.RuleItemGroups = parse_rule_groups(groups_string_list[0])
832+
rule.AnotherRuleItemGroups = parse_rule_groups(groups_string_list[1])
833+
834+
return rule
835+
836+
837+
def parse_rule_PageVisitorsWhoDidNotVisitAnotherPage(rule_str):
838+
rule = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:PageVisitorsWhoDidNotVisitAnotherPageRule')
839+
rule.Type = 'PageVisitorsWhoDidNotVisitAnotherPage'
840+
841+
groups_split = '))) and not ((('
842+
groups_string_list = rule_str.split(groups_split)
843+
844+
rule.IncludeRuleItemGroups = parse_rule_groups(groups_string_list[0])
845+
rule.ExcludeRuleItemGroups = parse_rule_groups(groups_string_list[1])
846+
847+
return rule
848+
849+
850+
def parse_rule_CustomEvents(rule_str):
851+
rule = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:CustomEventsRule')
852+
rule.Type = 'CustomEvents'
853+
854+
item_split = ') and ('
855+
pattern_for_operand_str = '^(Category|Action|Label|Value) ([^()]*)$'
856+
pattern_for_operand = re.compile(pattern_for_operand_str)
857+
858+
pattern_number_item_str = '^(Equals|GreaterThan|LessThan|GreaterThanEqualTo|LessThanEqualTo) ([^()]*)$'
859+
pattern_number_item = re.compile(pattern_number_item_str)
860+
861+
pattern_string_item_str = '^(Equals|Contains|BeginsWith|EndsWith|NotEquals|DoesNotContain|DoesNotBeginWith|DoesNotEndWith) ([^()]*)$'
862+
pattern_string_item = re.compile(pattern_string_item_str)
863+
864+
item_string_list = rule_str.split(item_split)
865+
for item_string in item_string_list:
866+
item_string = item_string.strip('(').strip(')')
867+
match_for_operand = pattern_for_operand.match(item_string)
868+
869+
if not match_for_operand:
870+
raise ValueError('Invalid Custom Event rule item: {0}'.format(item_string))
871+
872+
operand = match_for_operand.group(1)
873+
operater_and_value_string = match_for_operand.group(2)
874+
875+
if operand.lower() == 'value':
876+
match_number_item = pattern_number_item.match(operater_and_value_string)
877+
878+
if not match_number_item:
879+
raise ValueError('Invalid Custom Event number rule item: {0}'.format(item_string))
880+
881+
rule.ValueOperator = parse_number_operator(match_number_item.group(1))
882+
rule.Value = float(match_number_item.group(2))
883+
else:
884+
match_string_item = pattern_string_item.match(operater_and_value_string)
885+
886+
if not match_string_item:
887+
raise ValueError('Invalid Custom Event string rule item: {0}'.format(item_string))
888+
889+
if operand.lower() == 'category':
890+
rule.CategoryOperator = parse_string_operator(match_string_item.group(1))
891+
rule.Category = match_string_item.group(2)
892+
elif operand.lower() == 'label':
893+
rule.LabelOperator = parse_string_operator(match_string_item.group(1))
894+
rule.Label = match_string_item.group(2)
895+
elif operand.lower() == 'action':
896+
rule.ActionOperator = parse_string_operator(match_string_item.group(1))
897+
rule.Action = match_string_item.group(2)
898+
else:
899+
raise ValueError('Invalid Custom Event string rule operator: {0}'.format(operand))
900+
901+
return rule
902+
903+
904+
def parse_rule_groups(groups_str):
905+
group_split = ')) or (('
906+
group_str_list = groups_str.split(group_split)
907+
908+
rule_item_groups = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:ArrayOfRuleItemGroup')
909+
for group_str in group_str_list:
910+
item_group = parse_rule_items(group_str)
911+
rule_item_groups.RuleItemGroup.append(item_group)
912+
913+
return rule_item_groups
914+
915+
916+
def parse_rule_items(items_str):
917+
item_split = ') and ('
918+
item_str_list = items_str.split(item_split)
919+
920+
rule_item_group = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:RuleItemGroup')
921+
for item_str in item_str_list:
922+
item = parse_string_rule_item(item_str)
923+
rule_item_group.Items.RuleItem.append(item)
924+
925+
return rule_item_group
926+
927+
928+
def parse_string_rule_item(item_str):
929+
item_str = item_str.strip('(').strip(')')
930+
pattern_str = '^(Url|ReferrerUrl|None) (Equals|Contains|BeginsWith|EndsWith|NotEquals|DoesNotContain|DoesNotBeginWith|DoesNotEndWith) ([^()]*)$'
931+
pattern = re.compile(pattern_str)
932+
933+
match = pattern.match(item_str)
934+
935+
if not match:
936+
ValueError('Invalid Rule Item:{0}'.format(item_str))
937+
938+
item = _CAMPAIGN_OBJECT_FACTORY_V10.create('ns0:StringRuleItem')
939+
item.Type = 'String'
940+
item.Operand = match.group(1)
941+
item.Operator = parse_string_operator(match.group(2))
942+
item.Value = match.group(3)
943+
944+
return item
945+
946+
947+
def parse_number_operator(operator):
948+
oper = operator.lower()
949+
if oper == 'equals':
950+
return NumberOperator.Equals
951+
if oper == 'greaterthan':
952+
return NumberOperator.GreaterThan
953+
if oper == 'lessthan':
954+
return NumberOperator.LessThan
955+
if oper == 'greaterthanequalto':
956+
return NumberOperator.GreaterThanEqualTo
957+
if oper == 'lessthanequalto':
958+
return NumberOperator.LessThanEqualTo
959+
raise ValueError('Invalid Number Rule Item operator:{0}'.format(operator))
960+
961+
962+
def parse_string_operator(operator):
963+
oper = operator.lower()
964+
if oper == 'equals':
965+
return StringOperator.Equals
966+
if oper == 'contains':
967+
return StringOperator.Contains
968+
if oper == 'beginswith':
969+
return StringOperator.BeginsWith
970+
if oper == 'endswith':
971+
return StringOperator.EndsWith
972+
if oper == 'notequals':
973+
return StringOperator.NotEquals
974+
if oper == 'doesnotcontain':
975+
return StringOperator.DoesNotContain
976+
if oper == 'doesnotbeginwith':
977+
return StringOperator.DoesNotBeginWith
978+
if oper == 'doesnotendwith':
979+
return StringOperator.DoesNotEndWith
980+
981+
raise ValueError('Invalid String Rule Item operator:{0}'.format(operator))

bingads/manifest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION = '10.4.11'
1+
VERSION = '10.4.12'
22
BULK_FORMAT_VERSION = '3.0'
33
BULK_FORMAT_VERSION_4 = '4.0'
44
WORKING_NAME = 'BingAdsSDKPython'

bingads/v10/bulk/entities/bulk_remarketing_list.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ def __init__(self,
7070
field_to_csv=lambda c: bulk_str(c.remarketing_list.TagId),
7171
csv_to_field=lambda c, v: setattr(c.remarketing_list, 'TagId', int(v) if v else None)
7272
),
73+
_SimpleBulkMapping(
74+
_StringTable.RemarketingRule,
75+
field_to_csv=lambda c: field_to_csv_RemarketingRule(c.remarketing_list),
76+
csv_to_field=lambda c, v: csv_to_field_RemarketingRule(c.remarketing_list, v)
77+
),
7378
]
7479

7580
@property

bingads/v10/internal/bulk/csv_headers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ class _CsvHeaders:
195195
_StringTable.TagId,
196196
_StringTable.RemarketingListId,
197197
_StringTable.RemarketingTargetingSetting,
198+
_StringTable.RemarketingRule,
198199

199200
# Expanded Text Ad
200201
_StringTable.TitlePart1,

bingads/v10/internal/bulk/string_table.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ class _StringTable:
252252
TagId = "UET Tag Id"
253253
RemarketingListId = "Remarketing List Id"
254254
RemarketingTargetingSetting = "Remarketing Targeting Setting"
255+
RemarketingRule = "Remarketing Rule"
255256

256257
# Expanded Text Ad
257258
TitlePart1 = "Title Part 1"

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
except ImportError:
44
from distutils.core import setup
55

6-
VERSION = '10.4.11'
6+
VERSION = '10.4.12'
77

88
with open('README.rst', 'r') as f:
99
readme = f.read()

0 commit comments

Comments
 (0)