Skip to content

Commit

Permalink
First review comments, add adsets stream, start work on ads
Browse files Browse the repository at this point in the history
  • Loading branch information
bhtowles committed Oct 25, 2023
1 parent 5d6ff16 commit 05cfc4b
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 12 deletions.
6 changes: 4 additions & 2 deletions tests/base_new_frmwrk.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ def expected_metadata():
"ads": {
BaseCase.PRIMARY_KEYS: {"id", "updated_time"},
BaseCase.REPLICATION_METHOD: BaseCase.INCREMENTAL,
BaseCase.REPLICATION_KEYS: {"updated_time"}
BaseCase.REPLICATION_KEYS: {"updated_time"},
BaseCase.API_LIMIT: 100
},
"adcreative": {
BaseCase.PRIMARY_KEYS: {"id"},
Expand All @@ -81,7 +82,8 @@ def expected_metadata():
"adsets": {
BaseCase.PRIMARY_KEYS: {"id", "updated_time"},
BaseCase.REPLICATION_METHOD: BaseCase.INCREMENTAL,
BaseCase.REPLICATION_KEYS: {"updated_time"}
BaseCase.REPLICATION_KEYS: {"updated_time"},
BaseCase.API_LIMIT: 100
},
"campaigns": {
BaseCase.PRIMARY_KEYS: {"id", },
Expand Down
81 changes: 75 additions & 6 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ class TestClient():
# def __init__(self, config): # TODO move to dynamic config model?
def __init__(self):
# pass in config above and get() from it or hard code?
self.base_url = 'https://graph.facebook.com/'
self.base_url = 'https://graph.facebook.com'
self.api_version = 'v18.0'
self.account_id = os.getenv('TAP_FACEBOOK_ACCOUNT_ID')
self.access_token = os.getenv('TAP_FACEBOOK_ACCESS_TOKEN')
self.account_url = self.base_url + self.api_version +'/act_{}'.format(self.account_id)
self.account_url = f"{self.base_url}/{self.api_version}/act_{self.account_id}"

self.stream_endpoint_map = {'ads': '/ads',
'adsets': '/adsets',
Expand All @@ -37,6 +37,45 @@ def __init__(self):
# 'ISSUES_ELECTIONS_POLITICS', # acct unauthorized
'ONLINE_GAMBLING_AND_GAMING']

self.adset_billing_events = ['APP_INSTALLS',
'CLICKS',
'IMPRESSIONS',
'LINK_CLICKS',
'NONE',
'OFFER_CLAIMS',
'PAGE_LIKES',
'POST_ENGAGEMENT',
'THRUPLAY',
'PURCHASE',
'LISTING_INTERACTION']

self.adset_optimization_goals = ['NONE',
'APP_INSTALLS',
'AD_RECALL_LIFT',
'ENGAGED_USERS',
'EVENT_RESPONSES',
'IMPRESSIONS',
'LEAD_GENERATION',
'QUALITY_LEAD',
'LINK_CLICKS',
'OFFSITE_CONVERSIONS',
'PAGE_LIKES',
'POST_ENGAGEMENT',
'QUALITY_CALL',
'REACH',
'LANDING_PAGE_VIEWS',
'VISIT_INSTAGRAM_PROFILE',
'VALUE',
'THRUPLAY',
'DERIVED_EVENTS',
'APP_INSTALLS_AND_OFFSITE_CONVERSIONS',
'CONVERSATIONS',
'IN_APP_VALUE',
'MESSAGING_PURCHASE_CONVERSION',
'MESSAGING_APPOINTMENT_CONVERSION',
'SUBSCRIBERS',
'REMINDERS_SET']

# list of campaign objective values from fb docs below give "Invalid" error via api 18.0
# 'APP_INSTALLS', 'BRAND_AWARENESS', 'CONVERSIONS', 'EVENT_RESPONSES', 'LEAD_GENERATION',
# 'LINK_CLICKS', 'MESSAGES', 'OFFER_CLAIMS', 'PAGE_LIKES', 'POST_ENGAGEMENT',
Expand All @@ -54,13 +93,14 @@ def __init__(self):

def get_account_objects(self, stream):
assert stream in self.stream_endpoint_map.keys(), \
f'Endpoint undefiend for specified stream: {stream}'
f'Endpoint undefined for specified stream: {stream}'
endpoint = self.stream_endpoint_map[stream]
url = self.account_url + endpoint
params = {'access_token': self.access_token,
'limit': 100}
LOGGER.info(f"Getting url: {url}")
response = requests.get(url, params)
response.raise_for_status()
LOGGER.info(f"Returning get response: {response}")
return response.json()

Expand All @@ -72,17 +112,46 @@ def create_account_objects(self, stream):
LOGGER.info(f"Posting to url: {url}")
params = self.generate_post_params(stream)
response = requests.post(url, params)
response.raise_for_status()
LOGGER.info(f"Returning post response: {response}")
return response

def generate_post_params(self, stream):
if stream == 'campaigns':
if stream == 'ads':
params = {
'access_token': self.access_token,
'name': ''.join(random.choices(string.ascii_letters + string.digits, k=18)),
'adset_id': 23847656838230058, # TODO pick rand adset_id?
'creative': str({'creative_id': 23843561378450058}), # TODO pick rand creative_id?
'status': "PAUSED"}
return params

elif stream == 'adsets':
# TODO In order to randomize optimization_goal and billing_event the campaign_id
# would need to be examined to determine which goals were supported. Then an option
# could be selected from the available billing events supported by that goal.
params = {
'access_token': self.access_token,
'name': ''.join(random.choices(string.ascii_letters + string.digits, k=16)),
'optimization_goal': 'REACH',
'billing_event': 'IMPRESSIONS',
'bid_amount': 2, # TODO random?
'daily_budget': 1000, # TODO random? tie to parent campaign?
'campaign_id': 120203241386960059, # TODO pull from campaigns dynamically?
'targeting': str({'geo_locations': {'countries': ["US"]},
'facebook_positions': ["feed"]}),
'status': "PAUSED",
'promoted_object': str({'page_id': '453760455405317'})}
return params

elif stream == 'campaigns':
params = { # generate a campaign with random name, ojbective, and ad category
'access_token': self.access_token,
'name': ''.join(random.choices(string.ascii_letters + string.digits, k=15)),
'objective': random.choice(self.campaign_objectives),
'special_ad_categories': random.choice(self.campaign_special_ad_categories)}
return params

else:
assert False, f"Post params for stream {stream} not implemented / supported"

Expand Down Expand Up @@ -122,12 +191,12 @@ def generate_post_params(self, stream):
# Ad Insights TODO
# Empty data list for all 3 AdSet Ids

# AdSet Ids TODO
# AdSet Ids
# "data": [{"id": "23847656838230058"},
# {"id": "23847292383400058"},
# {"id": "23843561338600058"}],

# Campaign Ids TODO
# Campaign Ids
# "data": [{"id": "23847656838160058"},
# {"id": "23847292383380058"},
# {"id": "23843561338580058"},
Expand Down
10 changes: 6 additions & 4 deletions tests/test_facebook_pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def name():
return "tt_facebook_pagination"
def streams_to_test(self):
# return self.expected_stream_names()
return {'campaigns'} # TODO WIP, expand to all core streams
return {'adsets', 'campaigns'} # TODO WIP, expand to all core streams

def setUp(self): # pylint: disable=invalid-name
"""
Expand All @@ -42,20 +42,22 @@ def setUp(self): # pylint: disable=invalid-name

# ensure there is enough data to paginate
for stream in self.streams_to_test():
limit = self.expected_page_size(stream)

response = fb_client.get_account_objects(stream)
self.assertGreater(len(response['data']), 0,
msg='Failed HTTP get response for stream: {}'.format(stream))
number_of_records = len(response['data'])
if number_of_records > self.expected_page_size(stream):
if number_of_records >= limit and response.get('paging', {}).get('next'):
continue
LOGGER.info(f"Stream: {stream} - Record count is less than max page size: "
f"{self.expected_page_size(stream)}. Posting more records to setUp "
"the PaginationTest")
for i in range(self.expected_page_size(stream) - number_of_records + 1):
for i in range(limit - number_of_records + 1):
post_response = fb_client.create_account_objects(stream)
self.assertEqual(post_response.status_code, 200,
msg='Failed HTTP post response for stream: {}'.format(stream))
LOGGER.info(f"Posted {i + 1} new campaigns, new total: {number_of_records + i + 1}")
LOGGER.info(f"Posted {i + 1} new {stream}, new total: {number_of_records + i + 1}")
time.sleep(1)

# run initial sync
Expand Down

0 comments on commit 05cfc4b

Please sign in to comment.