Skip to content

Commit

Permalink
Minor updates (#227)
Browse files Browse the repository at this point in the history
* Separate analytics class from resouce.py

* Add some missing endpoints

* bump version
  • Loading branch information
smaeda-ks committed Oct 7, 2019
1 parent 4cf6ae6 commit 598e86c
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 165 deletions.
2 changes: 1 addition & 1 deletion tests/test_analytics_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from twitter_ads.account import Account
from twitter_ads.client import Client
from twitter_ads.campaign import Campaign
from twitter_ads.resource import Analytics
from twitter_ads.analytics import Analytics
from twitter_ads.enum import ENTITY, METRIC_GROUP, GRANULARITY
from twitter_ads import API_VERSION

Expand Down
2 changes: 1 addition & 1 deletion twitter_ads/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Copyright (C) 2015 Twitter, Inc.

VERSION = (6, 0, 0)
VERSION = (6, 0, 1)
API_VERSION = '6'

from twitter_ads.utils import get_version
Expand Down
163 changes: 163 additions & 0 deletions twitter_ads/analytics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Copyright (C) 2015 Twitter, Inc.

"""Container for all plugable resource object logic used by the Ads API SDK."""

from datetime import datetime, timedelta
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse

from twitter_ads.utils import to_time, validate_whole_hours
from twitter_ads.enum import ENTITY, GRANULARITY, PLACEMENT, TRANSFORM
from twitter_ads.http import Request
from twitter_ads.cursor import Cursor
from twitter_ads.resource import Resource, resource_property
from twitter_ads import API_VERSION
from twitter_ads.utils import FlattenParams


class Analytics(Resource):
"""
Container for all analytics related logic used by API resource objects.
"""
PROPERTIES = {}

ANALYTICS_MAP = {
'Campaign': ENTITY.CAMPAIGN,
'FundingInstrument': ENTITY.FUNDING_INSTRUMENT,
'LineItem': ENTITY.LINE_ITEM,
'MediaCreative': ENTITY.MEDIA_CREATIVE,
'OrganicTweet': ENTITY.ORGANIC_TWEET,
'PromotedTweet': ENTITY.PROMOTED_TWEET,
'PromotedAccount': ENTITY.PROMOTED_ACCOUNT
}

RESOURCE_SYNC = '/' + API_VERSION + '/stats/accounts/{account_id}'
RESOURCE_ASYNC = '/' + API_VERSION + '/stats/jobs/accounts/{account_id}'
RESOURCE_ACTIVE_ENTITIES = '/' + API_VERSION + '/stats/accounts/{account_id}/active_entities'

def stats(self, metrics, **kwargs): # noqa
"""
Pulls a list of metrics for the current object instance.
"""
return self.__class__.all_stats(self.account, [self.id], metrics, **kwargs)

@classmethod
def _standard_params(klass, ids, metric_groups, **kwargs):
"""
Sets the standard params for a stats request
"""
end_time = kwargs.get('end_time', datetime.utcnow())
start_time = kwargs.get('start_time', end_time - timedelta(seconds=604800))
granularity = kwargs.get('granularity', GRANULARITY.HOUR)
placement = kwargs.get('placement', PLACEMENT.ALL_ON_TWITTER)
entity = kwargs.get('entity', None)

params = {
'metric_groups': ','.join(map(str, metric_groups)),
'start_time': to_time(start_time, granularity),
'end_time': to_time(end_time, granularity),
'granularity': granularity.upper(),
'entity': entity or klass.ANALYTICS_MAP[klass.__name__],
'placement': placement
}

params['entity_ids'] = ','.join(map(str, ids))

return params

@classmethod
def all_stats(klass, account, ids, metric_groups, **kwargs):
"""
Pulls a list of metrics for a specified set of object IDs.
"""
params = klass._standard_params(ids, metric_groups, **kwargs)

resource = klass.RESOURCE_SYNC.format(account_id=account.id)
response = Request(account.client, 'get', resource, params=params).perform()
return response.body['data']

@classmethod
def queue_async_stats_job(klass, account, ids, metric_groups, **kwargs):
"""
Queues a list of metrics for a specified set of object IDs asynchronously
"""
params = klass._standard_params(ids, metric_groups, **kwargs)

params['platform'] = kwargs.get('platform', None)
params['country'] = kwargs.get('country', None)
params['segmentation_type'] = kwargs.get('segmentation_type', None)

resource = klass.RESOURCE_ASYNC.format(account_id=account.id)
response = Request(account.client, 'post', resource, params=params).perform()
return Analytics(account).from_response(response.body['data'], headers=response.headers)

@classmethod
@FlattenParams
def async_stats_job_result(klass, account, **kwargs):
"""
Returns the results of the specified async job IDs
"""
resource = klass.RESOURCE_ASYNC.format(account_id=account.id)
request = Request(account.client, 'get', resource, params=kwargs)

return Cursor(Analytics, request, init_with=[account])

@classmethod
def async_stats_job_data(klass, account, url, **kwargs):
"""
Returns the results of the specified async job IDs
"""
resource = urlparse(url)
domain = '{0}://{1}'.format(resource.scheme, resource.netloc)

response = Request(account.client, 'get', resource.path, domain=domain,
raw_body=True, stream=True).perform()

return response.body

@classmethod
@FlattenParams
def active_entities(klass, account, start_time, end_time, **kwargs):
"""
Returns the details about which entities' analytics metrics
have changed in a given time period.
"""
entity = kwargs.get('entity') or klass.ANALYTICS_MAP[klass.__name__]
if entity == klass.ANALYTICS_MAP['OrganicTweet']:
raise ValueError("'OrganicTweet' not support with 'active_entities'")

# The start and end times must be expressed in whole hours
validate_whole_hours(start_time)
validate_whole_hours(end_time)

params = {
'entity': entity,
'start_time': to_time(start_time, None),
'end_time': to_time(end_time, None)
}
params.update(kwargs)

resource = klass.RESOURCE_ACTIVE_ENTITIES.format(account_id=account.id)
response = Request(account.client, 'get', resource, params=params).perform()
return response.body['data']


# Analytics properties
# read-only
resource_property(Analytics, 'id', readonly=True)
resource_property(Analytics, 'id_str', readonly=True)
resource_property(Analytics, 'status', readonly=True)
resource_property(Analytics, 'url', readonly=True)
resource_property(Analytics, 'created_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(Analytics, 'expires_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(Analytics, 'updated_at', readonly=True, transform=TRANSFORM.TIME)

resource_property(Analytics, 'start_time', readonly=True, transform=TRANSFORM.TIME)
resource_property(Analytics, 'end_time', readonly=True, transform=TRANSFORM.TIME)
resource_property(Analytics, 'entity', readonly=True)
resource_property(Analytics, 'entity_ids', readonly=True)
resource_property(Analytics, 'placement', readonly=True)
resource_property(Analytics, 'granularity', readonly=True)
resource_property(Analytics, 'metric_groups', readonly=True)
59 changes: 51 additions & 8 deletions twitter_ads/campaign.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"""Container for all campaign management logic used by the Ads API SDK."""

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

@classmethod
@FlattenParams
def create(klass, account, **kwargs):
resource = klass.RESOURCE_COLLECTION.format(account_id=account.id)
response = Request(account.client, 'post', resource, params=kwargs).perform()
return klass(account).from_response(response.body['data'])

def apps(self):
if self.id and not hasattr(self, '_apps'):
self.reload()
Expand Down Expand Up @@ -306,6 +300,33 @@ def targeting_criteria(self, id=None, **kwargs):
resource_property(LineItem, 'to_delete', transform=TRANSFORM.BOOL)


class LineItemApps(Resource, Persistence):

PROPERTIES = {}

RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/line_item_apps'
RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/line_item_apps/{id}'


resource_property(LineItemApps, 'created_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(LineItemApps, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(LineItemApps, 'deleted', readonly=True, transform=TRANSFORM.BOOL)
resource_property(LineItemApps, 'id', readonly=True)
resource_property(LineItemApps, 'os_type', readonly=True)
resource_property(LineItemApps, 'app_store_identifier', readonly=True)
resource_property(LineItemApps, 'line_item_id', readonly=True)


class LineItemPlacements(Resource):

PROPERTIES = {}
RESOURCE_COLLECTION = '/' + API_VERSION + '/line_items/placements'


resource_property(LineItemPlacements, 'product_type', readonly=True)
resource_property(LineItemPlacements, 'placements', readonly=True)


class ScheduledPromotedTweet(Resource, Persistence):

PROPERTIES = {}
Expand Down Expand Up @@ -415,3 +436,25 @@ def save(self):
resource_property(TaxSettings, 'tax_category')
resource_property(TaxSettings, 'tax_exemption_id')
resource_property(TaxSettings, 'tax_id')


class ContentCategories(Resource):

PROPERTIES = {}
RESOURCE_COLLECTION = '/' + API_VERSION + '/content_categories'


resource_property(ContentCategories, 'id', readonly=True)
resource_property(ContentCategories, 'name', readonly=True)
resource_property(ContentCategories, 'iab_categories', readonly=True)


class IabCategories(Resource):

PROPERTIES = {}
RESOURCE_COLLECTION = '/' + API_VERSION + '/iab_categories'


resource_property(IabCategories, 'id', readonly=True)
resource_property(IabCategories, 'name', readonly=True)
resource_property(IabCategories, 'parent_id', readonly=True)
3 changes: 2 additions & 1 deletion twitter_ads/creative.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from twitter_ads.cursor import Cursor
from twitter_ads.enum import TRANSFORM
from twitter_ads.http import Request
from twitter_ads.resource import resource_property, Resource, Persistence, Analytics
from twitter_ads.analytics import Analytics
from twitter_ads.resource import resource_property, Resource, Persistence
from twitter_ads.utils import Deprecated, FlattenParams


Expand Down
Loading

0 comments on commit 598e86c

Please sign in to comment.