Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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