Skip to content

Commit 598e86c

Browse files
authored
Minor updates (#227)
* Separate analytics class from resouce.py * Add some missing endpoints * bump version
1 parent 4cf6ae6 commit 598e86c

File tree

6 files changed

+231
-165
lines changed

6 files changed

+231
-165
lines changed

tests/test_analytics_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from twitter_ads.account import Account
66
from twitter_ads.client import Client
77
from twitter_ads.campaign import Campaign
8-
from twitter_ads.resource import Analytics
8+
from twitter_ads.analytics import Analytics
99
from twitter_ads.enum import ENTITY, METRIC_GROUP, GRANULARITY
1010
from twitter_ads import API_VERSION
1111

twitter_ads/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Copyright (C) 2015 Twitter, Inc.
22

3-
VERSION = (6, 0, 0)
3+
VERSION = (6, 0, 1)
44
API_VERSION = '6'
55

66
from twitter_ads.utils import get_version

twitter_ads/analytics.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Copyright (C) 2015 Twitter, Inc.
2+
3+
"""Container for all plugable resource object logic used by the Ads API SDK."""
4+
5+
from datetime import datetime, timedelta
6+
try:
7+
from urllib.parse import urlparse
8+
except ImportError:
9+
from urlparse import urlparse
10+
11+
from twitter_ads.utils import to_time, validate_whole_hours
12+
from twitter_ads.enum import ENTITY, GRANULARITY, PLACEMENT, TRANSFORM
13+
from twitter_ads.http import Request
14+
from twitter_ads.cursor import Cursor
15+
from twitter_ads.resource import Resource, resource_property
16+
from twitter_ads import API_VERSION
17+
from twitter_ads.utils import FlattenParams
18+
19+
20+
class Analytics(Resource):
21+
"""
22+
Container for all analytics related logic used by API resource objects.
23+
"""
24+
PROPERTIES = {}
25+
26+
ANALYTICS_MAP = {
27+
'Campaign': ENTITY.CAMPAIGN,
28+
'FundingInstrument': ENTITY.FUNDING_INSTRUMENT,
29+
'LineItem': ENTITY.LINE_ITEM,
30+
'MediaCreative': ENTITY.MEDIA_CREATIVE,
31+
'OrganicTweet': ENTITY.ORGANIC_TWEET,
32+
'PromotedTweet': ENTITY.PROMOTED_TWEET,
33+
'PromotedAccount': ENTITY.PROMOTED_ACCOUNT
34+
}
35+
36+
RESOURCE_SYNC = '/' + API_VERSION + '/stats/accounts/{account_id}'
37+
RESOURCE_ASYNC = '/' + API_VERSION + '/stats/jobs/accounts/{account_id}'
38+
RESOURCE_ACTIVE_ENTITIES = '/' + API_VERSION + '/stats/accounts/{account_id}/active_entities'
39+
40+
def stats(self, metrics, **kwargs): # noqa
41+
"""
42+
Pulls a list of metrics for the current object instance.
43+
"""
44+
return self.__class__.all_stats(self.account, [self.id], metrics, **kwargs)
45+
46+
@classmethod
47+
def _standard_params(klass, ids, metric_groups, **kwargs):
48+
"""
49+
Sets the standard params for a stats request
50+
"""
51+
end_time = kwargs.get('end_time', datetime.utcnow())
52+
start_time = kwargs.get('start_time', end_time - timedelta(seconds=604800))
53+
granularity = kwargs.get('granularity', GRANULARITY.HOUR)
54+
placement = kwargs.get('placement', PLACEMENT.ALL_ON_TWITTER)
55+
entity = kwargs.get('entity', None)
56+
57+
params = {
58+
'metric_groups': ','.join(map(str, metric_groups)),
59+
'start_time': to_time(start_time, granularity),
60+
'end_time': to_time(end_time, granularity),
61+
'granularity': granularity.upper(),
62+
'entity': entity or klass.ANALYTICS_MAP[klass.__name__],
63+
'placement': placement
64+
}
65+
66+
params['entity_ids'] = ','.join(map(str, ids))
67+
68+
return params
69+
70+
@classmethod
71+
def all_stats(klass, account, ids, metric_groups, **kwargs):
72+
"""
73+
Pulls a list of metrics for a specified set of object IDs.
74+
"""
75+
params = klass._standard_params(ids, metric_groups, **kwargs)
76+
77+
resource = klass.RESOURCE_SYNC.format(account_id=account.id)
78+
response = Request(account.client, 'get', resource, params=params).perform()
79+
return response.body['data']
80+
81+
@classmethod
82+
def queue_async_stats_job(klass, account, ids, metric_groups, **kwargs):
83+
"""
84+
Queues a list of metrics for a specified set of object IDs asynchronously
85+
"""
86+
params = klass._standard_params(ids, metric_groups, **kwargs)
87+
88+
params['platform'] = kwargs.get('platform', None)
89+
params['country'] = kwargs.get('country', None)
90+
params['segmentation_type'] = kwargs.get('segmentation_type', None)
91+
92+
resource = klass.RESOURCE_ASYNC.format(account_id=account.id)
93+
response = Request(account.client, 'post', resource, params=params).perform()
94+
return Analytics(account).from_response(response.body['data'], headers=response.headers)
95+
96+
@classmethod
97+
@FlattenParams
98+
def async_stats_job_result(klass, account, **kwargs):
99+
"""
100+
Returns the results of the specified async job IDs
101+
"""
102+
resource = klass.RESOURCE_ASYNC.format(account_id=account.id)
103+
request = Request(account.client, 'get', resource, params=kwargs)
104+
105+
return Cursor(Analytics, request, init_with=[account])
106+
107+
@classmethod
108+
def async_stats_job_data(klass, account, url, **kwargs):
109+
"""
110+
Returns the results of the specified async job IDs
111+
"""
112+
resource = urlparse(url)
113+
domain = '{0}://{1}'.format(resource.scheme, resource.netloc)
114+
115+
response = Request(account.client, 'get', resource.path, domain=domain,
116+
raw_body=True, stream=True).perform()
117+
118+
return response.body
119+
120+
@classmethod
121+
@FlattenParams
122+
def active_entities(klass, account, start_time, end_time, **kwargs):
123+
"""
124+
Returns the details about which entities' analytics metrics
125+
have changed in a given time period.
126+
"""
127+
entity = kwargs.get('entity') or klass.ANALYTICS_MAP[klass.__name__]
128+
if entity == klass.ANALYTICS_MAP['OrganicTweet']:
129+
raise ValueError("'OrganicTweet' not support with 'active_entities'")
130+
131+
# The start and end times must be expressed in whole hours
132+
validate_whole_hours(start_time)
133+
validate_whole_hours(end_time)
134+
135+
params = {
136+
'entity': entity,
137+
'start_time': to_time(start_time, None),
138+
'end_time': to_time(end_time, None)
139+
}
140+
params.update(kwargs)
141+
142+
resource = klass.RESOURCE_ACTIVE_ENTITIES.format(account_id=account.id)
143+
response = Request(account.client, 'get', resource, params=params).perform()
144+
return response.body['data']
145+
146+
147+
# Analytics properties
148+
# read-only
149+
resource_property(Analytics, 'id', readonly=True)
150+
resource_property(Analytics, 'id_str', readonly=True)
151+
resource_property(Analytics, 'status', readonly=True)
152+
resource_property(Analytics, 'url', readonly=True)
153+
resource_property(Analytics, 'created_at', readonly=True, transform=TRANSFORM.TIME)
154+
resource_property(Analytics, 'expires_at', readonly=True, transform=TRANSFORM.TIME)
155+
resource_property(Analytics, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
156+
157+
resource_property(Analytics, 'start_time', readonly=True, transform=TRANSFORM.TIME)
158+
resource_property(Analytics, 'end_time', readonly=True, transform=TRANSFORM.TIME)
159+
resource_property(Analytics, 'entity', readonly=True)
160+
resource_property(Analytics, 'entity_ids', readonly=True)
161+
resource_property(Analytics, 'placement', readonly=True)
162+
resource_property(Analytics, 'granularity', readonly=True)
163+
resource_property(Analytics, 'metric_groups', readonly=True)

twitter_ads/campaign.py

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"""Container for all campaign management logic used by the Ads API SDK."""
44

55
from twitter_ads.enum import TRANSFORM
6-
from twitter_ads.resource import resource_property, Resource, Persistence, Batch, Analytics
6+
from twitter_ads.analytics import Analytics
7+
from twitter_ads.resource import resource_property, Resource, Persistence, Batch
78
from twitter_ads.http import Request
89
from twitter_ads.cursor import Cursor
910
from twitter_ads.utils import FlattenParams
@@ -200,13 +201,6 @@ class AppList(Resource, Persistence):
200201
RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/app_lists'
201202
RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/app_lists/{id}'
202203

203-
@classmethod
204-
@FlattenParams
205-
def create(klass, account, **kwargs):
206-
resource = klass.RESOURCE_COLLECTION.format(account_id=account.id)
207-
response = Request(account.client, 'post', resource, params=kwargs).perform()
208-
return klass(account).from_response(response.body['data'])
209-
210204
def apps(self):
211205
if self.id and not hasattr(self, '_apps'):
212206
self.reload()
@@ -306,6 +300,33 @@ def targeting_criteria(self, id=None, **kwargs):
306300
resource_property(LineItem, 'to_delete', transform=TRANSFORM.BOOL)
307301

308302

303+
class LineItemApps(Resource, Persistence):
304+
305+
PROPERTIES = {}
306+
307+
RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/line_item_apps'
308+
RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/line_item_apps/{id}'
309+
310+
311+
resource_property(LineItemApps, 'created_at', readonly=True, transform=TRANSFORM.TIME)
312+
resource_property(LineItemApps, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
313+
resource_property(LineItemApps, 'deleted', readonly=True, transform=TRANSFORM.BOOL)
314+
resource_property(LineItemApps, 'id', readonly=True)
315+
resource_property(LineItemApps, 'os_type', readonly=True)
316+
resource_property(LineItemApps, 'app_store_identifier', readonly=True)
317+
resource_property(LineItemApps, 'line_item_id', readonly=True)
318+
319+
320+
class LineItemPlacements(Resource):
321+
322+
PROPERTIES = {}
323+
RESOURCE_COLLECTION = '/' + API_VERSION + '/line_items/placements'
324+
325+
326+
resource_property(LineItemPlacements, 'product_type', readonly=True)
327+
resource_property(LineItemPlacements, 'placements', readonly=True)
328+
329+
309330
class ScheduledPromotedTweet(Resource, Persistence):
310331

311332
PROPERTIES = {}
@@ -415,3 +436,25 @@ def save(self):
415436
resource_property(TaxSettings, 'tax_category')
416437
resource_property(TaxSettings, 'tax_exemption_id')
417438
resource_property(TaxSettings, 'tax_id')
439+
440+
441+
class ContentCategories(Resource):
442+
443+
PROPERTIES = {}
444+
RESOURCE_COLLECTION = '/' + API_VERSION + '/content_categories'
445+
446+
447+
resource_property(ContentCategories, 'id', readonly=True)
448+
resource_property(ContentCategories, 'name', readonly=True)
449+
resource_property(ContentCategories, 'iab_categories', readonly=True)
450+
451+
452+
class IabCategories(Resource):
453+
454+
PROPERTIES = {}
455+
RESOURCE_COLLECTION = '/' + API_VERSION + '/iab_categories'
456+
457+
458+
resource_property(IabCategories, 'id', readonly=True)
459+
resource_property(IabCategories, 'name', readonly=True)
460+
resource_property(IabCategories, 'parent_id', readonly=True)

twitter_ads/creative.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from twitter_ads.cursor import Cursor
88
from twitter_ads.enum import TRANSFORM
99
from twitter_ads.http import Request
10-
from twitter_ads.resource import resource_property, Resource, Persistence, Analytics
10+
from twitter_ads.analytics import Analytics
11+
from twitter_ads.resource import resource_property, Resource, Persistence
1112
from twitter_ads.utils import Deprecated, FlattenParams
1213

1314

0 commit comments

Comments
 (0)