diff --git a/setup.py b/setup.py index 5a9d7e0..9b9eb57 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ def get_version(version_tuple): return '.'.join(map(str, version_tuple[:-1])) + version_tuple[-1] return '.'.join(map(str, version_tuple)) + init = os.path.join(os.path.dirname(__file__), 'twitter_ads', '__init__.py') version_line = list(filter(lambda l: l.startswith('VERSION'), open(init)))[0] diff --git a/tests/fixtures/campaigns_all.json b/tests/fixtures/campaigns_all.json index 85370d7..80a63af 100644 --- a/tests/fixtures/campaigns_all.json +++ b/tests/fixtures/campaigns_all.json @@ -16,7 +16,7 @@ "standard_delivery": true, "total_budget_amount_local_micro": null, "id": "2wap7", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "currency": "USD", "created_at": "2015-08-12T20:26:24Z", @@ -36,7 +36,7 @@ "standard_delivery": true, "total_budget_amount_local_micro": null, "id": "2wamv", - "paused": true, + "entity_status": "PAUSED", "account_id": "2iqph", "currency": "USD", "created_at": "2015-08-12T20:17:39Z", @@ -57,7 +57,7 @@ "standard_delivery": true, "total_budget_amount_local_micro": null, "id": "2wai9", - "paused": true, + "entity_status": "PAUSED", "account_id": "2iqph", "currency": "USD", "created_at": "2015-08-12T19:56:10Z", @@ -77,7 +77,7 @@ "standard_delivery": true, "total_budget_amount_local_micro": 1000000, "id": "2of1n", - "paused": true, + "entity_status": "PAUSED", "account_id": "2iqph", "currency": "USD", "created_at": "2015-06-29T22:24:17Z", @@ -98,7 +98,7 @@ "standard_delivery": true, "total_budget_amount_local_micro": null, "id": "2w9n1", - "paused": true, + "entity_status": "PAUSED", "account_id": "2iqph", "currency": "USD", "created_at": "2015-08-12T18:09:28Z", @@ -118,7 +118,6 @@ "standard_delivery": true, "total_budget_amount_local_micro": 1000000, "id": "2vuug", - "paused": true, "account_id": "2iqph", "currency": "USD", "created_at": "2015-08-10T21:58:39Z", @@ -136,7 +135,7 @@ "standard_delivery": true, "total_budget_amount_local_micro": null, "id": "2vuj3", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "currency": "USD", "created_at": "2015-08-10T21:06:04Z", @@ -154,7 +153,7 @@ "standard_delivery": true, "total_budget_amount_local_micro": 10000, "id": "2v3c4", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "currency": "USD", "created_at": "2015-08-06T06:54:13Z", @@ -174,7 +173,7 @@ "standard_delivery": true, "total_budget_amount_local_micro": 100000000, "id": "2uubq", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "currency": "USD", "created_at": "2015-08-04T23:20:11Z", @@ -194,7 +193,7 @@ "standard_delivery": true, "total_budget_amount_local_micro": null, "id": "2ttv3", - "paused": true, + "entity_status": "PAUSED", "account_id": "2iqph", "currency": "USD", "created_at": "2015-07-29T23:05:56Z", diff --git a/tests/fixtures/campaigns_load.json b/tests/fixtures/campaigns_load.json index 47f868d..09f3314 100644 --- a/tests/fixtures/campaigns_load.json +++ b/tests/fixtures/campaigns_load.json @@ -11,7 +11,7 @@ "standard_delivery": true, "total_budget_amount_local_micro": null, "id": "2wap7", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "currency": "USD", "created_at": "2015-08-12T20:26:24Z", diff --git a/tests/fixtures/line_items_all.json b/tests/fixtures/line_items_all.json index 110c9be..684afad 100644 --- a/tests/fixtures/line_items_all.json +++ b/tests/fixtures/line_items_all.json @@ -22,7 +22,7 @@ "total_budget_amount_local_micro": null, "objective": "CUSTOM", "id": "bw2", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "optimization": "DEFAULT", "categories": [], @@ -50,7 +50,7 @@ "total_budget_amount_local_micro": null, "objective": "CUSTOM", "id": "c4m", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "optimization": "DEFAULT", "categories": [], @@ -78,7 +78,7 @@ "total_budget_amount_local_micro": null, "objective": "CUSTOM", "id": "c5c", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "optimization": "DEFAULT", "categories": [], @@ -106,7 +106,7 @@ "total_budget_amount_local_micro": null, "objective": "CUSTOM", "id": "fhu", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "optimization": "DEFAULT", "categories": [], @@ -134,7 +134,7 @@ "total_budget_amount_local_micro": null, "objective": "CUSTOM", "id": "fxd", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "optimization": "DEFAULT", "categories": [], @@ -162,7 +162,7 @@ "total_budget_amount_local_micro": null, "objective": "CUSTOM", "id": "fxt", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "optimization": "DEFAULT", "categories": [], @@ -190,7 +190,7 @@ "total_budget_amount_local_micro": null, "objective": "CUSTOM", "id": "fya", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "optimization": "DEFAULT", "categories": [], @@ -218,7 +218,7 @@ "total_budget_amount_local_micro": null, "objective": "CUSTOM", "id": "ghj", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "optimization": "DEFAULT", "categories": [], @@ -246,7 +246,7 @@ "total_budget_amount_local_micro": null, "objective": "CUSTOM", "id": "gra", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "optimization": "DEFAULT", "categories": [], @@ -274,7 +274,7 @@ "total_budget_amount_local_micro": null, "objective": "CUSTOM", "id": "gsw", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "optimization": "DEFAULT", "categories": [], diff --git a/tests/fixtures/line_items_load.json b/tests/fixtures/line_items_load.json index 0dab002..a0dea6d 100644 --- a/tests/fixtures/line_items_load.json +++ b/tests/fixtures/line_items_load.json @@ -17,7 +17,7 @@ "total_budget_amount_local_micro": null, "objective": "CUSTOM", "id": "bw2", - "paused": false, + "entity_status": "ACTIVE", "account_id": "2iqph", "optimization": "DEFAULT", "categories": [], diff --git a/tests/test_campaign.py b/tests/test_campaign.py new file mode 100644 index 0000000..6a5eb50 --- /dev/null +++ b/tests/test_campaign.py @@ -0,0 +1,105 @@ +import responses +import unittest + +from tests.support import with_resource, with_fixture, characters + +from twitter_ads.account import Account +from twitter_ads.campaign import Campaign +from twitter_ads.client import Client +from twitter_ads.cursor import Cursor +from twitter_ads import API_VERSION + + +@responses.activate +def test_campaigns_all(): + responses.add( + responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph'), + body=with_fixture('accounts_load'), + content_type='application/json', + ) + + responses.add( + responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph/campaigns'), + body=with_fixture('campaigns_all'), + content_type='application/json', + ) + + client = Client( + characters(40), + characters(40), + characters(40), + characters(40) + ) + + account = Account.load(client, '2iqph') + + cursor = account.campaigns() + + assert cursor is not None + assert isinstance(cursor, Cursor) + assert cursor.count == 10 + + +@responses.activate +def test_campaign_load(): + responses.add( + responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph'), + body=with_fixture('accounts_load'), + content_type='application/json', + ) + + responses.add( + responses.GET, + with_resource( + '/' + API_VERSION + '/accounts/2iqph/campaigns/2wap7'), + body=with_fixture('campaigns_load'), + content_type='application/json', + ) + + client = Client( + characters(40), + characters(40), + characters(40), + characters(40) + ) + + account = Account.load(client, '2iqph') + + campaign = Campaign.load(account, '2wap7') + + assert campaign + + +@responses.activate +def test_campaign_entity_status_exists(): + responses.add( + responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph'), + body=with_fixture('accounts_load'), + content_type='application/json', + ) + + responses.add( + responses.GET, + with_resource( + '/' + API_VERSION + '/accounts/2iqph/campaigns/2wap7'), + body=with_fixture('campaigns_load'), + content_type='application/json', + ) + + client = Client( + characters(40), + characters(40), + characters(40), + characters(40) + ) + + account = Account.load(client, '2iqph') + + campaign = Campaign.load(account, '2wap7') + + assert campaign.entity_status + assert campaign.entity_status == 'ACTIVE' diff --git a/tests/test_line_item.py b/tests/test_line_item.py new file mode 100644 index 0000000..c67a78d --- /dev/null +++ b/tests/test_line_item.py @@ -0,0 +1,105 @@ +import responses +import unittest + +from tests.support import with_resource, with_fixture, characters + +from twitter_ads.account import Account +from twitter_ads.campaign import LineItem +from twitter_ads.client import Client +from twitter_ads.cursor import Cursor +from twitter_ads import API_VERSION + + +@responses.activate +def test_line_items_all(): + responses.add( + responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph'), + body=with_fixture('accounts_load'), + content_type='application/json', + ) + + responses.add( + responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph/line_items'), + body=with_fixture('line_items_all'), + content_type='application/json', + ) + + client = Client( + characters(40), + characters(40), + characters(40), + characters(40) + ) + + account = Account.load(client, '2iqph') + + cursor = account.line_items() + + assert cursor is not None + assert isinstance(cursor, Cursor) + assert cursor.count == 10 + + +@responses.activate +def test_line_item_load(): + responses.add( + responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph'), + body=with_fixture('accounts_load'), + content_type='application/json', + ) + + responses.add( + responses.GET, + with_resource( + '/' + API_VERSION + '/accounts/2iqph/line_items/bw2'), + body=with_fixture('line_items_load'), + content_type='application/json', + ) + + client = Client( + characters(40), + characters(40), + characters(40), + characters(40) + ) + + account = Account.load(client, '2iqph') + + line_item = LineItem.load(account, 'bw2') + + assert line_item + + +@responses.activate +def test_line_item_entity_status_exists(): + responses.add( + responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph'), + body=with_fixture('accounts_load'), + content_type='application/json', + ) + + responses.add( + responses.GET, + with_resource( + '/' + API_VERSION + '/accounts/2iqph/line_items/bw2'), + body=with_fixture('line_items_load'), + content_type='application/json', + ) + + client = Client( + characters(40), + characters(40), + characters(40), + characters(40) + ) + + account = Account.load(client, '2iqph') + + line_item = LineItem.load(account, 'bw2') + + assert line_item.entity_status + assert line_item.entity_status == 'ACTIVE' diff --git a/twitter_ads/account.py b/twitter_ads/account.py index 385f185..8821b2e 100644 --- a/twitter_ads/account.py +++ b/twitter_ads/account.py @@ -169,6 +169,7 @@ def scoped_timeline(self, *id, **kwargs): return response.body['data'] + # account properties resource_property(Account, 'id', readonly=True) resource_property(Account, 'name', readonly=True) diff --git a/twitter_ads/audience.py b/twitter_ads/audience.py index 3315939..efcc412 100644 --- a/twitter_ads/audience.py +++ b/twitter_ads/audience.py @@ -100,6 +100,7 @@ def __update_audience__(self, location, list_type, operation): resource = self.RESOURCE_UPDATE.format(account_id=self.account.id) return Request(self.account.client, 'post', resource, params=params).perform() + # tailored audience properties # read-only resource_property(TailoredAudience, 'id', readonly=True) @@ -171,6 +172,7 @@ def delete(self): response = Request(self.account.client, 'delete', resource).perform() return self.from_response(response.body['data']) + # tailored audience permission properties # read-only resource_property(TailoredAudiencePermission, 'id', readonly=True) diff --git a/twitter_ads/campaign.py b/twitter_ads/campaign.py index 46c1c97..b8a2faa 100644 --- a/twitter_ads/campaign.py +++ b/twitter_ads/campaign.py @@ -29,6 +29,7 @@ def all(klass, account, line_item_id, **kwargs): return Cursor(klass, request, init_with=[account]) + # targeting criteria properties # read-only resource_property(TargetingCriteria, 'id', readonly=True) @@ -54,6 +55,7 @@ class FundingInstrument(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/funding_instruments' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/funding_instruments/{id}' + # funding instrument properties # read-only resource_property(FundingInstrument, 'id', readonly=True) @@ -83,6 +85,7 @@ class PromotableUser(Resource): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/promotable_users' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/promotable_users/{id}' + # promotable user properties # read-only resource_property(PromotableUser, 'id', readonly=True) @@ -115,6 +118,7 @@ def apps(self): self.reload() return self._apps + # app list properties # read-only resource_property(AppList, 'id', readonly=True) @@ -130,6 +134,7 @@ class Campaign(Resource, Persistence, Analytics, Batch): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/campaigns' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/campaigns/{id}' + # campaign properties # read-only resource_property(Campaign, 'id', readonly=True) @@ -143,11 +148,11 @@ class Campaign(Resource, Persistence, Analytics, Batch): resource_property(Campaign, 'funding_instrument_id') resource_property(Campaign, 'start_time', transform=TRANSFORM.TIME) resource_property(Campaign, 'end_time', transform=TRANSFORM.TIME) -resource_property(Campaign, 'paused', transform=TRANSFORM.BOOL) resource_property(Campaign, 'currency') resource_property(Campaign, 'standard_delivery') resource_property(Campaign, 'daily_budget_amount_local_micro') resource_property(Campaign, 'total_budget_amount_local_micro') +resource_property(Campaign, 'entity_status') # sdk-only resource_property(Campaign, 'to_delete', transform=TRANSFORM.BOOL) @@ -171,6 +176,7 @@ def targeting_criteria(self, id=None, **kwargs): else: return TargetingCriteria.load(self.account, id, **kwargs) + # line item properties # read-only resource_property(LineItem, 'id', readonly=True) @@ -186,7 +192,6 @@ def targeting_criteria(self, id=None, **kwargs): resource_property(LineItem, 'include_sentiment') resource_property(LineItem, 'objective') resource_property(LineItem, 'optimization') -resource_property(LineItem, 'paused', transform=TRANSFORM.BOOL) resource_property(LineItem, 'primary_web_event_tag') resource_property(LineItem, 'product_type') resource_property(LineItem, 'placements', transform=TRANSFORM.LIST) @@ -195,6 +200,7 @@ def targeting_criteria(self, id=None, **kwargs): resource_property(LineItem, 'bid_amount_local_micro') resource_property(LineItem, 'total_budget_amount_local_micro') resource_property(LineItem, 'bid_type') +resource_property(LineItem, 'entity_status') # sdk-only resource_property(LineItem, 'to_delete', transform=TRANSFORM.BOOL) @@ -206,6 +212,7 @@ class ScheduledPromotedTweet(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/scheduled_promoted_tweets' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/scheduled_promoted_tweets/{id}' + # scheduled promoted tweets properties # read-only resource_property(ScheduledPromotedTweet, 'created_at', readonly=True, transform=TRANSFORM.TIME) diff --git a/twitter_ads/creative.py b/twitter_ads/creative.py index 8bc5129..609ebe4 100644 --- a/twitter_ads/creative.py +++ b/twitter_ads/creative.py @@ -17,6 +17,7 @@ class PromotedAccount(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/promoted_accounts' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/promoted_accounts/{id}' + # promoted account properties # read-only resource_property(PromotedAccount, 'id', readonly=True) @@ -54,6 +55,7 @@ def save(self): response = Request(self.account.client, 'post', resource, params=params).perform() return self.from_response(response.body['data'][0]) + # promoted tweet properties # read-only resource_property(PromotedTweet, 'id', readonly=True) @@ -74,6 +76,7 @@ class Video(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/videos' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/videos/{id}' + # video properties # read-only resource_property(Video, 'id', readonly=True) @@ -98,6 +101,7 @@ class AccountMedia(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/account_media' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/account_media/{id}' + # video properties # read-only resource_property(AccountMedia, 'id', readonly=True) @@ -120,6 +124,7 @@ class MediaCreative(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/media_creatives' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/media_creatives/{id}' + # video properties # read-only resource_property(MediaCreative, 'id', readonly=True) @@ -142,6 +147,7 @@ class WebsiteCard(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/website' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/website/{id}' + # website card properties # read-only resource_property(WebsiteCard, 'id', readonly=True) @@ -164,6 +170,7 @@ class VideoWebsiteCard(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/video_website' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/video_website/{id}' + # video website card properties # read-only resource_property(VideoWebsiteCard, 'account_id', readonly=True) @@ -199,6 +206,7 @@ class LeadGenCard(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/lead_gen' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/lead_gen/{id}' + # lead gen card properties # read-only resource_property(LeadGenCard, 'id', readonly=True) @@ -229,6 +237,7 @@ class AppDownloadCard(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/app_download' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/app_download/{id}' + # app download card properties # read-only resource_property(AppDownloadCard, 'id', readonly=True) @@ -257,6 +266,7 @@ class ImageAppDownloadCard(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/image_app_download' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/image_app_download/{id}' + # image app download card properties # read-only resource_property(ImageAppDownloadCard, 'id', readonly=True) @@ -284,6 +294,7 @@ class VideoAppDownloadCard(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/video_app_download' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/video_app_download/{id}' + # video app download card properties # read-only resource_property(VideoAppDownloadCard, 'id', readonly=True) @@ -314,6 +325,7 @@ class ImageConversationCard(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/image_conversation' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/image_conversation/{id}' + # image conversation card properties # read-only resource_property(ImageConversationCard, 'id', readonly=True) @@ -340,6 +352,7 @@ class VideoConversationCard(Resource, Persistence): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/video_conversation' RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/video_conversation/{id}' + # video conversation card properties # read-only resource_property(VideoConversationCard, 'id', readonly=True) @@ -380,6 +393,7 @@ def preview(self): response = Request(self.account.client, 'get', resource).perform() return response.body['data'] + # scheduled tweet properties # read-only resource_property(ScheduledTweet, 'created_at', readonly=True, transform=TRANSFORM.TIME) diff --git a/twitter_ads/enum.py b/twitter_ads/enum.py index 3813281..5fdb88a 100644 --- a/twitter_ads/enum.py +++ b/twitter_ads/enum.py @@ -6,6 +6,7 @@ def enum(**enums): return type('Enum', (), enums) + TRANSFORM = enum( TIME=0, BOOL=1, diff --git a/twitter_ads/error.py b/twitter_ads/error.py index 2d95179..8d84205 100644 --- a/twitter_ads/error.py +++ b/twitter_ads/error.py @@ -99,6 +99,7 @@ def __init__(self, response, **kwargs): def retry_after(self): return self._retry_after + ERRORS = { 400: BadRequest, 401: NotAuthorized,