From 87e25b244fbcc3a1f30253f4e30511a927090dfd Mon Sep 17 00:00:00 2001 From: BJapac <70250690+BJapac@users.noreply.github.com> Date: Mon, 26 Apr 2021 10:48:17 +0900 Subject: [PATCH 01/11] Update audience.py --- twitter_ads/audience.py | 122 ++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/twitter_ads/audience.py b/twitter_ads/audience.py index d0ea3ca..3b339b6 100644 --- a/twitter_ads/audience.py +++ b/twitter_ads/audience.py @@ -12,20 +12,20 @@ import json -class TailoredAudience(Resource): +class CustomAudience(Resource): PROPERTIES = {} - RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/tailored_audiences' - RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/tailored_audiences/{id}' - RESOURCE_USERS = '/' + API_VERSION + '/accounts/{account_id}/tailored_audiences/\ + RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/custom_audiences' + RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/custom_audiences/{id}' + RESOURCE_USERS = '/' + API_VERSION + '/accounts/{account_id}/custom_audiences/\ {id}/users' - RESOURCE_PERMISSIONS = '/' + API_VERSION + '/accounts/{account_id}/tailored_audiences/\ + RESOURCE_PERMISSIONS = '/' + API_VERSION + '/accounts/{account_id}/custom_audiences/\ {id}/permissions' @classmethod def create(klass, account, name): """ - Creates a new tailored audience. + Creates a new custom audience. """ audience = klass(account) getattr(audience, '__create_audience__')(name) @@ -39,7 +39,7 @@ def users(self, params): """ This is a private API and requires whitelisting from Twitter. This endpoint will allow partners to add, update and remove users from a given - tailored_audience_id. + custom_audience_id. The endpoint will also accept multiple user identifier types per user as well. """ resource = self.RESOURCE_USERS.format(account_id=self.account.id, id=self.id) @@ -55,7 +55,7 @@ def users(self, params): def delete(self): """ - Deletes the current tailored audience instance. + Deletes the current custom audience instance. """ resource = self.RESOURCE.format(account_id=self.account.id, id=self.id) response = Request(self.account.client, 'delete', resource).perform() @@ -63,17 +63,17 @@ def delete(self): def permissions(self, **kwargs): """ - Returns a collection of permissions for the curent tailored audience. + Returns a collection of permissions for the curent custom audience. """ self._validate_loaded() - return TailoredAudiencePermission.all(self.account, self.id, **kwargs) + return CustomAudiencePermission.all(self.account, self.id, **kwargs) def targeted(self, **kwargs): """ - Returns a collection of campaigns and line items targeting the curent tailored audience. + Returns a collection of campaigns and line items targeting the curent custom audience. """ self._validate_loaded() - return TailoredAudienceTargeted.all(self.account, self.id, **kwargs) + return CustomAudienceTargeted.all(self.account, self.id, **kwargs) def __create_audience__(self, name): params = {'name': name} @@ -82,52 +82,52 @@ def __create_audience__(self, name): return self.from_response(response.body['data']) -# tailored audience properties +# Custom audience properties # read-only -resource_property(TailoredAudience, 'id', readonly=True) -resource_property(TailoredAudience, 'created_at', readonly=True, transform=TRANSFORM.TIME) -resource_property(TailoredAudience, 'updated_at', readonly=True, transform=TRANSFORM.TIME) -resource_property(TailoredAudience, 'deleted', readonly=True, transform=TRANSFORM.BOOL) -resource_property(TailoredAudience, 'audience_size', readonly=True) -resource_property(TailoredAudience, 'audience_type', readonly=True) -resource_property(TailoredAudience, 'partner_source', readonly=True) -resource_property(TailoredAudience, 'reasons_not_targetable', readonly=True) -resource_property(TailoredAudience, 'targetable', readonly=True) -resource_property(TailoredAudience, 'targetable_types', readonly=True) -resource_property(TailoredAudience, 'owner_account_id', readonly=True) +resource_property(CustomAudience, 'id', readonly=True) +resource_property(CustomAudience, 'created_at', readonly=True, transform=TRANSFORM.TIME) +resource_property(CustomAudience, 'updated_at', readonly=True, transform=TRANSFORM.TIME) +resource_property(CustomAudience, 'deleted', readonly=True, transform=TRANSFORM.BOOL) +resource_property(CustomAudience, 'audience_size', readonly=True) +resource_property(CustomAudience, 'audience_type', readonly=True) +resource_property(CustomAudience, 'partner_source', readonly=True) +resource_property(CustomAudience, 'reasons_not_targetable', readonly=True) +resource_property(CustomAudience, 'targetable', readonly=True) +resource_property(CustomAudience, 'targetable_types', readonly=True) +resource_property(CustomAudience, 'owner_account_id', readonly=True) # writable -resource_property(TailoredAudience, 'name') -resource_property(TailoredAudience, 'description') +resource_property(CustomAudience, 'name') +resource_property(CustomAudience, 'description') -class TailoredAudiencePermission(Resource): +class CustomAudiencePermission(Resource): PROPERTIES = {} - RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/tailored_audiences/' - RESOURCE_COLLECTION += '{tailored_audience_id}/permissions' - RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/tailored_audiences/\ -{tailored_audience_id}/permissions/{id}' + RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/custom_audiences/' + RESOURCE_COLLECTION += '{custom_audience_id}/permissions' + RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/custom_audiences/\ +{custom_audience_id}/permissions/{id}' @classmethod - def all(klass, account, tailored_audience_id, **kwargs): - """Returns a Cursor instance for the given tailored audience permission resource.""" + def all(klass, account, custom_audience_id, **kwargs): + """Returns a Cursor instance for the given custom audience permission resource.""" resource = klass.RESOURCE_COLLECTION.format( account_id=account.id, - tailored_audience_id=tailored_audience_id) + custom_audience_id=custom_audience_id) request = Request(account.client, 'get', resource, params=kwargs) return Cursor(klass, request, init_with=[account]) def save(self): """ - Saves or updates the current tailored audience permission. + Saves or updates the current custom audience permission. """ resource = self.RESOURCE_COLLECTION.format( account_id=self.account.id, - tailored_audience_id=self.tailored_audience_id) + custom_audience_id=self.custom_audience_id) response = Request( self.account.client, 'post', @@ -137,56 +137,56 @@ def save(self): def delete(self): """ - Deletes the current tailored audience permission. + Deletes the current custom audience permission. """ resource = self.RESOURCE.format( account_id=self.account.id, - tailored_audience_id=self.tailored_audience_id, + custom_audience_id=self.custom_audience_id, id=self.id) response = Request(self.account.client, 'delete', resource).perform() return self.from_response(response.body['data']) -# tailored audience permission properties +# custom audience permission properties # read-only -resource_property(TailoredAudiencePermission, 'id', readonly=True) -resource_property(TailoredAudiencePermission, 'created_at', readonly=True, transform=TRANSFORM.TIME) -resource_property(TailoredAudiencePermission, 'updated_at', readonly=True, transform=TRANSFORM.TIME) -resource_property(TailoredAudiencePermission, 'deleted', readonly=True, transform=TRANSFORM.BOOL) +resource_property(CustomAudiencePermission, 'id', readonly=True) +resource_property(CustomAudiencePermission, 'created_at', readonly=True, transform=TRANSFORM.TIME) +resource_property(CustomAudiencePermission, 'updated_at', readonly=True, transform=TRANSFORM.TIME) +resource_property(CustomAudiencePermission, 'deleted', readonly=True, transform=TRANSFORM.BOOL) # writable -resource_property(TailoredAudiencePermission, 'tailored_audience_id') -resource_property(TailoredAudiencePermission, 'granted_account_id') -resource_property(TailoredAudiencePermission, 'permission_level') +resource_property(CustomAudiencePermission, 'custom_audience_id') +resource_property(CustomAudiencePermission, 'granted_account_id') +resource_property(CustomAudiencePermission, 'permission_level') -class TailoredAudienceTargeted(Resource): +class CustomAudienceTargeted(Resource): PROPERTIES = {} - RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/tailored_audiences/\ -{tailored_audience_id}/targeted' + RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/custom_audiences/\ +{custom_audience_id}/targeted' @classmethod - def all(klass, account, tailored_audience_id, **kwargs): - """Returns a Cursor instance for the given targeted tailored audience resource.""" + def all(klass, account, custom_audience_id, **kwargs): + """Returns a Cursor instance for the given targeted custom audience resource.""" resource = klass.RESOURCE.format( account_id=account.id, - tailored_audience_id=tailored_audience_id) + custom_audience_id=custom_audience_id) request = Request(account.client, 'get', resource, params=kwargs) return Cursor(klass, request, init_with=[account]) -# tailored audience targeted properties +# custom audience targeted properties # read-only -resource_property(TailoredAudienceTargeted, 'campaign_id', readonly=True) -resource_property(TailoredAudienceTargeted, 'campaign_name', readonly=True) -resource_property(TailoredAudienceTargeted, 'line_items', readonly=True) -resource_property(TailoredAudienceTargeted, 'id', readonly=True) -resource_property(TailoredAudienceTargeted, 'name', readonly=True) -resource_property(TailoredAudienceTargeted, 'servable', readonly=True, transform=TRANSFORM.BOOL) +resource_property(CustomAudienceTargeted, 'campaign_id', readonly=True) +resource_property(CustomAudienceTargeted, 'campaign_name', readonly=True) +resource_property(CustomAudienceTargeted, 'line_items', readonly=True) +resource_property(CustomAudienceTargeted, 'id', readonly=True) +resource_property(CustomAudienceTargeted, 'name', readonly=True) +resource_property(CustomAudienceTargeted, 'servable', readonly=True, transform=TRANSFORM.BOOL) # writable -resource_property(TailoredAudienceTargeted, 'tailored_audience_id') -resource_property(TailoredAudienceTargeted, 'with_active', transform=TRANSFORM.BOOL) +resource_property(CustomAudienceTargeted, 'custom_audience_id') +resource_property(CustomAudienceTargeted, 'with_active', transform=TRANSFORM.BOOL) From 4362055f9e16d442c55315d4f0b1ed52a52406f9 Mon Sep 17 00:00:00 2001 From: BJapac <70250690+BJapac@users.noreply.github.com> Date: Mon, 26 Apr 2021 11:31:36 +0900 Subject: [PATCH 02/11] Update test_targeted_audiences.py --- tests/test_targeted_audiences.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_targeted_audiences.py b/tests/test_targeted_audiences.py index bfd65d7..26c77a3 100644 --- a/tests/test_targeted_audiences.py +++ b/tests/test_targeted_audiences.py @@ -5,7 +5,7 @@ from twitter_ads.account import Account from twitter_ads.client import Client -from twitter_ads.audience import TailoredAudience +from twitter_ads.audience import CustomAudience from twitter_ads.cursor import Cursor from twitter_ads import API_VERSION @@ -17,11 +17,11 @@ def test_targeted_audiences(): body=with_fixture('accounts_load')) responses.add(responses.GET, - with_resource('/' + API_VERSION + '/accounts/2iqph/tailored_audiences/2906h'), - body=with_fixture('tailored_audiences_load')) + with_resource('/' + API_VERSION + '/accounts/2iqph/custom_audiences/2906h'), + body=with_fixture('custom_audiences_load')) responses.add(responses.GET, - with_resource('/' + API_VERSION + '/accounts/2iqph/tailored_audiences/abc2/targeted?with_active=True'), + with_resource('/' + API_VERSION + '/accounts/2iqph/custom_audiences/abc2/targeted?with_active=True'), body=with_fixture('targeted_audiences')) client = Client( @@ -33,7 +33,7 @@ def test_targeted_audiences(): account = Account.load(client, '2iqph') - audience = TailoredAudience.load(account, '2906h') + audience = CustomAudience.load(account, '2906h') targeted_audiences = audience.targeted( with_active=True ) @@ -44,4 +44,4 @@ def test_targeted_audiences(): assert targeted_audiences.first.line_items[0]['id'] == '5gzog' assert targeted_audiences.first.line_items[0]['name'] == 'test-line-item' assert targeted_audiences.first.line_items[0]['servable'] == True - assert len(responses.calls) == 3 \ No newline at end of file + assert len(responses.calls) == 3 From 4be3b550fa518b02f93d249179c54e0df5afdcd0 Mon Sep 17 00:00:00 2001 From: BJapac <70250690+BJapac@users.noreply.github.com> Date: Mon, 26 Apr 2021 11:36:01 +0900 Subject: [PATCH 03/11] Update __init__.py --- twitter_ads/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twitter_ads/__init__.py b/twitter_ads/__init__.py index 93d614a..36a538d 100644 --- a/twitter_ads/__init__.py +++ b/twitter_ads/__init__.py @@ -1,7 +1,7 @@ # Copyright (C) 2015 Twitter, Inc. -VERSION = (8, 0, 0) -API_VERSION = '8' +VERSION = (9, 0, 0) +API_VERSION = '9' from twitter_ads.utils import get_version From 2c9c716ce33f4abc8bf42047be13968436b77678 Mon Sep 17 00:00:00 2001 From: BJapac <70250690+BJapac@users.noreply.github.com> Date: Mon, 26 Apr 2021 11:51:56 +0900 Subject: [PATCH 04/11] Create custom_audience.py --- examples/custom_audience.py | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 examples/custom_audience.py diff --git a/examples/custom_audience.py b/examples/custom_audience.py new file mode 100644 index 0000000..fdc726c --- /dev/null +++ b/examples/custom_audience.py @@ -0,0 +1,39 @@ +import hashlib +from twitter_ads.client import Client +from twitter_ads.audience import CustomAudience + +CONSUMER_KEY = 'your consumer key' +CONSUMER_SECRET = 'your consumer secret' +ACCESS_TOKEN = 'access token' +ACCESS_TOKEN_SECRET = 'access token secret' +ACCOUNT_ID = 'account id' + +# initialize the client +client = Client(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET) + +# load the advertiser account instance +account = client.accounts(ACCOUNT_ID) + +# create a new custom audience +audience = CustomAudience.create(account, 'test TA') + +# sample user +# all values musth be sha256 hashed +email_hash = hashlib.sha256("test-email@test.com").hexdigest() + +# create payload +user = [{ + "operation_type": "Update", + "params": { + "users": [{ + "email": [ + email_hash + ] + }] + } +}] + +# update the custom audience +success_count, total_count = audience.users(user) +if success_count == total_count: + print(("Successfully added {total_count} users").format(total_count=total_count)) From 829761bfe97b2fb7332499368275755e7b9bf370 Mon Sep 17 00:00:00 2001 From: BJapac <70250690+BJapac@users.noreply.github.com> Date: Mon, 26 Apr 2021 12:09:32 +0900 Subject: [PATCH 05/11] Update account.py --- twitter_ads/account.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/twitter_ads/account.py b/twitter_ads/account.py index 1b33dbf..5c11b1d 100644 --- a/twitter_ads/account.py +++ b/twitter_ads/account.py @@ -11,7 +11,7 @@ from twitter_ads.resource import resource_property, Resource from twitter_ads.creative import (AccountMedia, MediaCreative, ScheduledTweet, Card, PromotedTweet) -from twitter_ads.audience import TailoredAudience +from twitter_ads.audience import CustomAudience from twitter_ads.campaign import (AppList, Campaign, FundingInstrument, LineItem, PromotableUser, ScheduledPromotedTweet) @@ -111,12 +111,12 @@ def app_lists(self, id=None, **kwargs): """ return self._load_resource(AppList, id, **kwargs) - def tailored_audiences(self, id=None, **kwargs): + def custom_audiences(self, id=None, **kwargs): """ - Returns a collection of tailored audiences available to the + Returns a collection of custom audiences available to the current account. """ - return self._load_resource(TailoredAudience, id, **kwargs) + return self._load_resource(CustomAudience, id, **kwargs) def account_media(self, id=None, **kwargs): """ From 57aba9f12e33dd1ce3d1f9ef4680715463922d56 Mon Sep 17 00:00:00 2001 From: BJapac <70250690+BJapac@users.noreply.github.com> Date: Mon, 26 Apr 2021 12:10:55 +0900 Subject: [PATCH 06/11] Update campaign.py --- twitter_ads/campaign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twitter_ads/campaign.py b/twitter_ads/campaign.py index 5531589..fbf2717 100644 --- a/twitter_ads/campaign.py +++ b/twitter_ads/campaign.py @@ -141,7 +141,7 @@ def tv_shows(klass, account, **kwargs): resource_property(TargetingCriteria, 'operator_type') resource_property(TargetingCriteria, 'targeting_type') resource_property(TargetingCriteria, 'targeting_value') -resource_property(TargetingCriteria, 'tailored_audience_expansion') +resource_property(TargetingCriteria, 'custom_audience_expansion') # sdk-only resource_property(TargetingCriteria, 'to_delete', transform=TRANSFORM.BOOL) From f5eb1c561b0b34d667b9856f36a65bab66b81e78 Mon Sep 17 00:00:00 2001 From: BJapac <70250690+BJapac@users.noreply.github.com> Date: Mon, 26 Apr 2021 12:19:13 +0900 Subject: [PATCH 07/11] Update custom_audience.py --- examples/custom_audience.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/custom_audience.py b/examples/custom_audience.py index fdc726c..1ddaf2e 100644 --- a/examples/custom_audience.py +++ b/examples/custom_audience.py @@ -15,7 +15,7 @@ account = client.accounts(ACCOUNT_ID) # create a new custom audience -audience = CustomAudience.create(account, 'test TA') +audience = CustomAudience.create(account, 'test CA') # sample user # all values musth be sha256 hashed From 1e2924bab4d8674a0b7721862489b1ce503f4728 Mon Sep 17 00:00:00 2001 From: BJapac <70250690+BJapac@users.noreply.github.com> Date: Mon, 26 Apr 2021 23:09:47 +0900 Subject: [PATCH 08/11] Create custom_audience_load.json --- tests/fixtures/custom_audience_load.json | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/fixtures/custom_audience_load.json diff --git a/tests/fixtures/custom_audience_load.json b/tests/fixtures/custom_audience_load.json new file mode 100644 index 0000000..0d143ba --- /dev/null +++ b/tests/fixtures/custom_audience_load.json @@ -0,0 +1,29 @@ +{ + "data_type": "custom_audience", + "data": { + "targetable": false, + "name": "TA #2", + "targetable_types": [ + "WEB", + "EXCLUDED_WEB" + ], + "audience_type": "WEB", + "id": "abc2", + "reasons_not_targetable": [ + "TOO_SMALL" + ], + "list_type": null, + "created_at": "2014-03-09T20:35:41Z", + "updated_at": "2014-06-11T09:38:06Z", + "partner_source": "OTHER", + "deleted": false, + "audience_size": null + }, + "request": { + "params": { + "account_id": "2iqph", + "name": "TA #2", + "list_type": "EMAIL" + } + } +} From ecc058d0bf3367acd5643eafed3631d659c1a92e Mon Sep 17 00:00:00 2001 From: BJapac <70250690+BJapac@users.noreply.github.com> Date: Mon, 26 Apr 2021 23:12:57 +0900 Subject: [PATCH 09/11] Rename custom_audience_load.json to custom_audiences_load.json --- .../{custom_audience_load.json => custom_audiences_load.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/fixtures/{custom_audience_load.json => custom_audiences_load.json} (100%) diff --git a/tests/fixtures/custom_audience_load.json b/tests/fixtures/custom_audiences_load.json similarity index 100% rename from tests/fixtures/custom_audience_load.json rename to tests/fixtures/custom_audiences_load.json From ddf97179e630e3259df00f57fca5e7602f46555f Mon Sep 17 00:00:00 2001 From: BJapac <70250690+BJapac@users.noreply.github.com> Date: Mon, 26 Apr 2021 23:16:14 +0900 Subject: [PATCH 10/11] Rename tailored_audiences_all.json to custom_audiences_all.json --- .../{tailored_audiences_all.json => custom_audiences_all.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/fixtures/{tailored_audiences_all.json => custom_audiences_all.json} (100%) diff --git a/tests/fixtures/tailored_audiences_all.json b/tests/fixtures/custom_audiences_all.json similarity index 100% rename from tests/fixtures/tailored_audiences_all.json rename to tests/fixtures/custom_audiences_all.json From 709311402b21cb2037e7fc209c4fe3fd99513109 Mon Sep 17 00:00:00 2001 From: BJapac <70250690+BJapac@users.noreply.github.com> Date: Mon, 26 Apr 2021 23:17:42 +0900 Subject: [PATCH 11/11] Update and rename tailored_audiences_permissions_all.json to custom_audiences_permissions_all.json --- ...ons_all.json => custom_audiences_permissions_all.json} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename tests/fixtures/{tailored_audiences_permissions_all.json => custom_audiences_permissions_all.json} (77%) diff --git a/tests/fixtures/tailored_audiences_permissions_all.json b/tests/fixtures/custom_audiences_permissions_all.json similarity index 77% rename from tests/fixtures/tailored_audiences_permissions_all.json rename to tests/fixtures/custom_audiences_permissions_all.json index f7ea5d3..98dd7ed 100644 --- a/tests/fixtures/tailored_audiences_permissions_all.json +++ b/tests/fixtures/custom_audiences_permissions_all.json @@ -2,12 +2,12 @@ "request": { "params": { "account_id": "2iqph", - "tailored_audience_id": "abc2" + "custom_audience_id": "abc2" } }, "data": [ { - "tailored_audience_id": "abc2", + "custom_audience_id": "abc2", "permission_level": "READ_ONLY", "id": "k", "created_at": "2016-04-09T18:16:32Z", @@ -16,7 +16,7 @@ "deleted": false }, { - "tailored_audience_id": "abc2", + "custom_audience_id": "abc2", "permission_level": "READ_ONLY", "id": "l", "created_at": "2016-04-09T18:22:33Z", @@ -25,7 +25,7 @@ "deleted": false } ], - "data_type": "tailored_audience_permission", + "data_type": "custom_audience_permission", "total_count": 2, "next_cursor": null }