diff --git a/examples/batch_request.py b/examples/batch_request.py index 866daa2..43ebe54 100644 --- a/examples/batch_request.py +++ b/examples/batch_request.py @@ -46,7 +46,7 @@ line_item_1.name = 'my first ad' line_item_1.product_type = PRODUCT.PROMOTED_TWEETS line_item_1.placements = [PLACEMENT.ALL_ON_TWITTER] -line_item_1.objective = OBJECTIVE.TWEET_ENGAGEMENTS +line_item_1.objective = OBJECTIVE.ENGAGEMENTS line_item_1.bid_amount_local_micro = 10000 line_item_1.entity_status = ENTITY_STATUS.PAUSED @@ -55,7 +55,7 @@ line_item_2.name = 'my second ad' line_item_2.product_type = PRODUCT.PROMOTED_TWEETS line_item_2.placements = [PLACEMENT.ALL_ON_TWITTER] -line_item_2.objective = OBJECTIVE.TWEET_ENGAGEMENTS +line_item_2.objective = OBJECTIVE.ENGAGEMENTS line_item_2.bid_amount_local_micro = 20000 line_item_2.entity_status = ENTITY_STATUS.PAUSED diff --git a/examples/quick_start.py b/examples/quick_start.py index 58ffa3a..a3614f9 100644 --- a/examples/quick_start.py +++ b/examples/quick_start.py @@ -31,7 +31,7 @@ line_item.name = 'my first ad' line_item.product_type = PRODUCT.PROMOTED_TWEETS line_item.placements = [PLACEMENT.ALL_ON_TWITTER] -line_item.objective = OBJECTIVE.TWEET_ENGAGEMENTS +line_item.objective = OBJECTIVE.ENGAGEMENTS line_item.bid_amount_local_micro = 10000 line_item.entity_status = ENTITY_STATUS.PAUSED line_item.save() diff --git a/examples/video_tutorial.py b/examples/video_tutorial.py index d77db95..b11cb5c 100644 --- a/examples/video_tutorial.py +++ b/examples/video_tutorial.py @@ -48,10 +48,10 @@ campaign.start_time = datetime.utcnow() campaign.save() -# create a line item with the VIDEO_VIEWS_PREROLL +# create a line item with the PREROLL_VIEWS # objective and product_type MEDIA line_item = LineItem(account) -line_item.objective = OBJECTIVE.VIDEO_VIEWS_PREROLL +line_item.objective = OBJECTIVE.PREROLL_VIEWS line_item.campaign_id = campaign.id line_item.name = 'Video tutorial example' diff --git a/tests/fixtures/tailored_audiences_all.json b/tests/fixtures/tailored_audiences_all.json index 023c7b4..53ca653 100644 --- a/tests/fixtures/tailored_audiences_all.json +++ b/tests/fixtures/tailored_audiences_all.json @@ -14,6 +14,7 @@ ], "audience_type": "WEB", "id": "abc2", + "owner_account_id": "2iqph", "reasons_not_targetable": [ "TOO_SMALL" ], @@ -27,6 +28,7 @@ { "targetable": true, "name": "TA #1", + "owner_account_id": "2iqph", "targetable_types": [ "CRM", "EXCLUDED_CRM" @@ -44,6 +46,7 @@ { "targetable": false, "name": "TA #3", + "owner_account_id": "2iqph", "targetable_types": [ "CRM", "EXCLUDED_CRM" @@ -61,7 +64,6 @@ "audience_size": null } ], - "data_type": "tailored_audience", "total_count": 3, "next_cursor": null } diff --git a/tests/fixtures/targeted_audiences.json b/tests/fixtures/targeted_audiences.json new file mode 100644 index 0000000..0b71100 --- /dev/null +++ b/tests/fixtures/targeted_audiences.json @@ -0,0 +1,33 @@ +{ + "request": { + "params": { + "account_id": "2iqph", + "tailored_audience_id": "abc2" + } + }, + "next_cursor": null, + "data": [ + { + "campaign_id": "59hod", + "campaign_name": "test-campaign", + "line_items": [ + { + "id": "5gzog", + "name": "test-line-item", + "servable": true + } + ] + }, + { + "campaign_id": "arja7", + "campaign_name": "Untitled campaign", + "line_items": [ + { + "id": "bjw1q", + "name": null, + "servable": true + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/test_targeted_audiences.py b/tests/test_targeted_audiences.py new file mode 100644 index 0000000..bfd65d7 --- /dev/null +++ b/tests/test_targeted_audiences.py @@ -0,0 +1,47 @@ +import responses +import unittest + +from tests.support import with_resource, with_fixture, characters + +from twitter_ads.account import Account +from twitter_ads.client import Client +from twitter_ads.audience import TailoredAudience +from twitter_ads.cursor import Cursor +from twitter_ads import API_VERSION + + +@responses.activate +def test_targeted_audiences(): + responses.add(responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph'), + body=with_fixture('accounts_load')) + + responses.add(responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph/tailored_audiences/2906h'), + body=with_fixture('tailored_audiences_load')) + + responses.add(responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph/tailored_audiences/abc2/targeted?with_active=True'), + body=with_fixture('targeted_audiences')) + + client = Client( + characters(40), + characters(40), + characters(40), + characters(40) + ) + + account = Account.load(client, '2iqph') + + audience = TailoredAudience.load(account, '2906h') + targeted_audiences = audience.targeted( + with_active=True + ) + + assert isinstance(targeted_audiences, Cursor) + assert isinstance(targeted_audiences.first.line_items, list) + assert targeted_audiences.first.campaign_id == '59hod' + 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 diff --git a/twitter_ads/__init__.py b/twitter_ads/__init__.py index b5fe98f..93d614a 100644 --- a/twitter_ads/__init__.py +++ b/twitter_ads/__init__.py @@ -1,7 +1,7 @@ # Copyright (C) 2015 Twitter, Inc. -VERSION = (7, 0, 2) -API_VERSION = '7' +VERSION = (8, 0, 0) +API_VERSION = '8' from twitter_ads.utils import get_version diff --git a/twitter_ads/audience.py b/twitter_ads/audience.py index ae4dc6a..d0ea3ca 100644 --- a/twitter_ads/audience.py +++ b/twitter_ads/audience.py @@ -68,6 +68,13 @@ def permissions(self, **kwargs): self._validate_loaded() return TailoredAudiencePermission.all(self.account, self.id, **kwargs) + def targeted(self, **kwargs): + """ + Returns a collection of campaigns and line items targeting the curent tailored audience. + """ + self._validate_loaded() + return TailoredAudienceTargeted.all(self.account, self.id, **kwargs) + def __create_audience__(self, name): params = {'name': name} resource = self.RESOURCE_COLLECTION.format(account_id=self.account.id) @@ -83,14 +90,15 @@ def __create_audience__(self, name): 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, 'metadata', 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) + # writable resource_property(TailoredAudience, 'name') -resource_property(TailoredAudience, 'list_type') +resource_property(TailoredAudience, 'description') class TailoredAudiencePermission(Resource): @@ -149,3 +157,36 @@ def delete(self): resource_property(TailoredAudiencePermission, 'tailored_audience_id') resource_property(TailoredAudiencePermission, 'granted_account_id') resource_property(TailoredAudiencePermission, 'permission_level') + + +class TailoredAudienceTargeted(Resource): + + PROPERTIES = {} + + RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/tailored_audiences/\ +{tailored_audience_id}/targeted' + + @classmethod + def all(klass, account, tailored_audience_id, **kwargs): + """Returns a Cursor instance for the given targeted tailored audience resource.""" + + resource = klass.RESOURCE.format( + account_id=account.id, + tailored_audience_id=tailored_audience_id) + request = Request(account.client, 'get', resource, params=kwargs) + + return Cursor(klass, request, init_with=[account]) + + +# tailored 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) + +# writable +resource_property(TailoredAudienceTargeted, 'tailored_audience_id') +resource_property(TailoredAudienceTargeted, 'with_active', transform=TRANSFORM.BOOL) diff --git a/twitter_ads/enum.py b/twitter_ads/enum.py index 28f5567..e3f0ba4 100644 --- a/twitter_ads/enum.py +++ b/twitter_ads/enum.py @@ -128,11 +128,11 @@ def enum(**enums): OBJECTIVE = enum( APP_ENGAGEMENTS='APP_ENGAGEMENTS', APP_INSTALLS='APP_INSTALLS', - AWARENESS='AWARENESS', + ENGAGEMENTS='ENGAGEMENTS', FOLLOWERS='FOLLOWERS', LEAD_GENERATION='LEAD_GENERATION', - TWEET_ENGAGEMENTS='TWEET_ENGAGEMENTS', - VIDEO_VIEWS_PREROLL='VIDEO_VIEWS_PREROLL', + PREROLL_VIEWS='PREROLL_VIEWS', + REACH='REACH', VIDEO_VIEWS='VIDEO_VIEWS', WEBSITE_CLICKS='WEBSITE_CLICKS', WEBSITE_CONVERSIONS='WEBSITE_CONVERSIONS'