From 17ffd57d288d8c92d159f1dff06b568d7f9e7b11 Mon Sep 17 00:00:00 2001 From: BJapac <70250690+BJapac@users.noreply.github.com> Date: Thu, 6 May 2021 17:13:57 +0900 Subject: [PATCH] v9 update for TA->CA (#278) * Update audience.py * Update test_targeted_audiences.py * Update __init__.py * Create custom_audience.py * Update account.py * Update campaign.py * Update custom_audience.py * Create custom_audience_load.json * Rename custom_audience_load.json to custom_audiences_load.json * Rename tailored_audiences_all.json to custom_audiences_all.json * Update and rename tailored_audiences_permissions_all.json to custom_audiences_permissions_all.json --- examples/custom_audience.py | 39 ++++++ ...ces_all.json => custom_audiences_all.json} | 0 tests/fixtures/custom_audiences_load.json | 29 +++++ ... => custom_audiences_permissions_all.json} | 8 +- tests/test_targeted_audiences.py | 12 +- twitter_ads/__init__.py | 4 +- twitter_ads/account.py | 8 +- twitter_ads/audience.py | 122 +++++++++--------- twitter_ads/campaign.py | 2 +- 9 files changed, 146 insertions(+), 78 deletions(-) create mode 100644 examples/custom_audience.py rename tests/fixtures/{tailored_audiences_all.json => custom_audiences_all.json} (100%) create mode 100644 tests/fixtures/custom_audiences_load.json rename tests/fixtures/{tailored_audiences_permissions_all.json => custom_audiences_permissions_all.json} (77%) diff --git a/examples/custom_audience.py b/examples/custom_audience.py new file mode 100644 index 0000000..1ddaf2e --- /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 CA') + +# 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)) 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 diff --git a/tests/fixtures/custom_audiences_load.json b/tests/fixtures/custom_audiences_load.json new file mode 100644 index 0000000..0d143ba --- /dev/null +++ b/tests/fixtures/custom_audiences_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" + } + } +} 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 } 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 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 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): """ 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) 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)