twitter_ads/creative.py
# Copyright (C) 2015 Twitter, Inc.
"""Container for all creative management logic used by the Ads API SDK."""
import json
from requests.exceptions import HTTPError
from twitter_ads import API_VERSION
from twitter_ads.cursor import Cursor
from twitter_ads.enum import TRANSFORM
from twitter_ads.http import Request
from twitter_ads.analytics import Analytics
from twitter_ads.resource import resource_property, Resource, Persistence
from twitter_ads.utils import Deprecated, FlattenParams
class PromotedAccount(Analytics, Resource, Persistence):
PROPERTIES = {}
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, 'approval_status', readonly=True)
resource_property(PromotedAccount, 'created_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(PromotedAccount, 'deleted', readonly=True, transform=TRANSFORM.BOOL)
resource_property(PromotedAccount, 'entity_status', readonly=True)
resource_property(PromotedAccount, 'id', readonly=True)
resource_property(PromotedAccount, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
# writable
resource_property(PromotedAccount, 'line_item_id')
resource_property(PromotedAccount, 'user_id')
class PromotedTweet(Analytics, Resource, Persistence):
PROPERTIES = {}
RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/promoted_tweets'
RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/promoted_tweets/{id}'
@Deprecated('This method has been deprecated and will no longer be available '
'in the next major version update. Please use PromotedTweet.attach() '
'method instead.')
def save(self):
"""
Saves or updates the current object instance depending on the
presence of `object.id`.
"""
params = self.to_params()
if 'tweet_id' in params:
params['tweet_ids'] = [params['tweet_id']]
del params['tweet_id']
if self.id:
raise HTTPError("Method PUT not allowed.")
resource = self.RESOURCE_COLLECTION.format(account_id=self.account.id)
response = Request(self.account.client, 'post', resource, params=params).perform()
return self.from_response(response.body['data'][0])
@classmethod
@FlattenParams
def attach(klass, account, **kwargs):
"""
Associate one or more Tweets with the specified line item.
"""
resource = klass.RESOURCE_COLLECTION.format(account_id=account.id)
request = Request(account.client, 'post', resource, params=kwargs)
return Cursor(klass, request, init_with=[account])
# promoted tweet properties
# read-only
resource_property(PromotedTweet, 'approval_status', readonly=True)
resource_property(PromotedTweet, 'created_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(PromotedTweet, 'deleted', readonly=True, transform=TRANSFORM.BOOL)
resource_property(PromotedTweet, 'entity_status', readonly=True)
resource_property(PromotedTweet, 'id', readonly=True)
resource_property(PromotedTweet, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(PromotedTweet, 'tweet_id')
resource_property(PromotedTweet, 'line_item_id')
class AccountMedia(Resource, Persistence):
PROPERTIES = {}
RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/account_media'
RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/account_media/{id}'
# Account Media properties
# read-only
resource_property(AccountMedia, 'created_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(AccountMedia, 'deleted', readonly=True, transform=TRANSFORM.BOOL)
resource_property(AccountMedia, 'id', readonly=True)
resource_property(AccountMedia, 'creative_type', readonly=True)
resource_property(AccountMedia, 'media_url', readonly=True)
resource_property(AccountMedia, 'media_key', readonly=True)
resource_property(AccountMedia, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
class MediaCreative(Analytics, Resource, Persistence):
PROPERTIES = {}
RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/media_creatives'
RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/media_creatives/{id}'
# Media Creative properties
# read-only
resource_property(MediaCreative, 'approval_status', readonly=True)
resource_property(MediaCreative, 'created_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(MediaCreative, 'deleted', readonly=True, transform=TRANSFORM.BOOL)
resource_property(MediaCreative, 'id', readonly=True)
resource_property(MediaCreative, 'entity_status', readonly=True)
resource_property(MediaCreative, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
# writable
resource_property(MediaCreative, 'account_media_id')
resource_property(MediaCreative, 'landing_url')
resource_property(MediaCreative, 'line_item_id')
class ImageConversationCard(Resource, Persistence):
PROPERTIES = {}
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, 'card_type', readonly=True)
resource_property(ImageConversationCard, 'card_uri', readonly=True)
resource_property(ImageConversationCard, 'created_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(ImageConversationCard, 'deleted', readonly=True, transform=TRANSFORM.BOOL)
resource_property(ImageConversationCard, 'id', readonly=True)
resource_property(ImageConversationCard, 'media_url', readonly=True)
resource_property(ImageConversationCard, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
# writable
resource_property(ImageConversationCard, 'unlocked_image_media_key')
resource_property(ImageConversationCard, 'fouth_cta')
resource_property(ImageConversationCard, 'fouth_cta_tweet')
resource_property(ImageConversationCard, 'media_key')
resource_property(ImageConversationCard, 'first_cta')
resource_property(ImageConversationCard, 'first_cta_tweet')
resource_property(ImageConversationCard, 'name')
resource_property(ImageConversationCard, 'second_cta')
resource_property(ImageConversationCard, 'second_cta_tweet')
resource_property(ImageConversationCard, 'thank_you_text')
resource_property(ImageConversationCard, 'thank_you_url')
resource_property(ImageConversationCard, 'third_cta')
resource_property(ImageConversationCard, 'third_cta_tweet')
resource_property(ImageConversationCard, 'title')
class VideoConversationCard(Resource, Persistence):
PROPERTIES = {}
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, 'card_uri', readonly=True)
resource_property(VideoConversationCard, 'card_type', readonly=True)
resource_property(VideoConversationCard, 'created_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(VideoConversationCard, 'deleted', readonly=True, transform=TRANSFORM.BOOL)
resource_property(VideoConversationCard, 'id', readonly=True)
resource_property(VideoConversationCard, 'media_url', readonly=True)
resource_property(VideoConversationCard, 'poster_media_url', readonly=True)
resource_property(VideoConversationCard, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
# writable
resource_property(VideoConversationCard, 'unlocked_image_media_key')
resource_property(VideoConversationCard, 'unlocked_video_media_key')
resource_property(VideoConversationCard, 'fouth_cta')
resource_property(VideoConversationCard, 'fouth_cta_tweet')
resource_property(VideoConversationCard, 'poster_media_key')
resource_property(VideoConversationCard, 'first_cta')
resource_property(VideoConversationCard, 'first_cta_tweet')
resource_property(VideoConversationCard, 'name')
resource_property(VideoConversationCard, 'second_cta')
resource_property(VideoConversationCard, 'second_cta_tweet')
resource_property(VideoConversationCard, 'thank_you_text')
resource_property(VideoConversationCard, 'thank_you_url')
resource_property(VideoConversationCard, 'third_cta')
resource_property(VideoConversationCard, 'third_cta_tweet')
resource_property(VideoConversationCard, 'title')
resource_property(VideoConversationCard, 'media_key')
class ScheduledTweet(Resource, Persistence):
PROPERTIES = {}
RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/scheduled_tweets'
RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/scheduled_tweets/{id}'
# scheduled tweet properties
# read-only
resource_property(ScheduledTweet, 'created_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(ScheduledTweet, 'completed_at', read_only=True, transform=TRANSFORM.TIME)
resource_property(ScheduledTweet, 'id', read_only=True)
resource_property(ScheduledTweet, 'scheduled_status', read_only=True)
resource_property(ScheduledTweet, 'tweet_id', readonly=True)
resource_property(ScheduledTweet, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(ScheduledTweet, 'user_id', read_only=True)
# writable
resource_property(ScheduledTweet, 'as_user_id')
resource_property(ScheduledTweet, 'card_uri')
resource_property(ScheduledTweet, 'media_keys', transform=TRANSFORM.LIST)
resource_property(ScheduledTweet, 'nullcast', transform=TRANSFORM.BOOL)
resource_property(ScheduledTweet, 'scheduled_at', transform=TRANSFORM.TIME)
resource_property(ScheduledTweet, 'text')
class DraftTweet(Resource, Persistence):
PROPERTIES = {}
RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/draft_tweets'
RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/draft_tweets/{id}'
# draft tweet properties
# read-only
resource_property(DraftTweet, 'id', read_only=True)
resource_property(DraftTweet, 'created_at', read_only=True, transform=TRANSFORM.TIME)
resource_property(DraftTweet, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(DraftTweet, 'user_id', read_only=True)
# writable
resource_property(DraftTweet, 'as_user_id')
resource_property(DraftTweet, 'card_uri')
resource_property(DraftTweet, 'media_keys', transform=TRANSFORM.LIST)
resource_property(DraftTweet, 'nullcast', transform=TRANSFORM.BOOL)
resource_property(DraftTweet, 'text')
class MediaLibrary(Resource, Persistence):
PROPERTIES = {}
RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/media_library'
RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/media_library/{id}'
def reload(self, **kwargs):
if not self.media_key:
return self
resource = self.RESOURCE.format(account_id=self.account.id, id=self.media_key)
response = Request(self.account.client, 'get', resource, params=kwargs).perform()
return self.from_response(response.body['data'])
def add(self):
resource = self.RESOURCE_COLLECTION.format(account_id=self.account.id)
response = Request(
self.account.client, 'post',
resource, params=self.to_params()).perform()
return self.from_response(response.body['data'])
def update(self):
resource = self.RESOURCE.format(account_id=self.account.id, id=self.media_key)
response = Request(
self.account.client, 'put',
resource, params=self.to_params()).perform()
return self.from_response(response.body['data'])
def delete(self):
resource = self.RESOURCE.format(account_id=self.account.id, id=self.media_key)
response = Request(self.account.client, 'delete', resource).perform()
self.from_response(response.body['data'])
# media library properties
# read-only
resource_property(MediaLibrary, 'aspect_ratio', readonly=True)
resource_property(MediaLibrary, 'created_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(MediaLibrary, 'deleted', readonly=True, transform=TRANSFORM.BOOL)
resource_property(MediaLibrary, 'duration', readonly=True, transform=TRANSFORM.INT)
resource_property(MediaLibrary, 'media_status', readonly=True)
resource_property(MediaLibrary, 'media_type', readonly=True)
resource_property(MediaLibrary, 'media_url', readonly=True)
resource_property(MediaLibrary, 'poster_media_url', readonly=True)
resource_property(MediaLibrary, 'tweeted', readonly=True, transform=TRANSFORM.BOOL)
resource_property(MediaLibrary, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
# writable
resource_property(MediaLibrary, 'media_key')
resource_property(MediaLibrary, 'description')
resource_property(MediaLibrary, 'file_name')
resource_property(MediaLibrary, 'name')
resource_property(MediaLibrary, 'poster_media_key')
resource_property(MediaLibrary, 'title')
class PollCard(Resource, Persistence):
PROPERTIES = {}
RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/poll'
RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/poll/{id}'
# poll card properties
# read-only
resource_property(PollCard, 'card_type', readonly=True)
resource_property(PollCard, 'card_uri', readonly=True)
resource_property(PollCard, 'content_duration_seconds', readonly=True)
resource_property(PollCard, 'created_at', readonly=True)
resource_property(PollCard, 'deleted', readonly=True, transform=TRANSFORM.BOOL)
resource_property(PollCard, 'end_time', readonly=True)
resource_property(PollCard, 'id', readonly=True)
resource_property(PollCard, 'image', readonly=True)
resource_property(PollCard, 'image_display_height', readonly=True)
resource_property(PollCard, 'image_display_width', readonly=True)
resource_property(PollCard, 'start_time', readonly=True)
resource_property(PollCard, 'updated_at', readonly=True)
resource_property(PollCard, 'video_height', readonly=True)
resource_property(PollCard, 'video_hls_url', readonly=True)
resource_property(PollCard, 'video_poster_height', readonly=True)
resource_property(PollCard, 'video_poster_url', readonly=True)
resource_property(PollCard, 'video_poster_width', readonly=True)
resource_property(PollCard, 'video_url', readonly=True)
resource_property(PollCard, 'video_width', readonly=True)
# writable
resource_property(PollCard, 'duration_in_minutes')
resource_property(PollCard, 'first_choice')
resource_property(PollCard, 'fourth_choice')
resource_property(PollCard, 'media_key')
resource_property(PollCard, 'name')
resource_property(PollCard, 'second_choice')
resource_property(PollCard, 'third_choice')
class CardsFetch(Resource):
PROPERTIES = {}
FETCH_URI = '/' + API_VERSION + '/accounts/{account_id}/cards/all'
FETCH_ID = '/' + API_VERSION + '/accounts/{account_id}/cards/all/{id}'
def all(klass):
raise AttributeError("'CardsFetch' object has no attribute 'all'")
@classmethod
@FlattenParams
def load(klass, account, **kwargs):
# check whether both are specified or neither are specified
if all([kwargs.get('card_uris'), kwargs.get('card_id')]) or \
not any([kwargs.get('card_uris'), kwargs.get('card_id')]):
raise ValueError('card_uris and card_id are exclusive parameters. ' +
'Please supply one or the other, but not both.')
if kwargs.get('card_uris'):
resource = klass.FETCH_URI.format(account_id=account.id)
request = Request(account.client, 'get', resource, params=kwargs)
return Cursor(klass, request, init_with=[account])
else:
resource = klass.FETCH_ID.format(account_id=account.id, id=kwargs.get('card_id'))
response = Request(account.client, 'get', resource, params=kwargs).perform()
return klass(account).from_response(response.body['data'])
def reload(self):
if self.id:
self.load(self.account, card_id=self.id)
# card properties
# read-only
resource_property(CardsFetch, 'country_code', readonly=True)
resource_property(CardsFetch, 'app_cta', readonly=True)
resource_property(CardsFetch, 'card_type', readonly=True)
resource_property(CardsFetch, 'card_uri', readonly=True)
resource_property(CardsFetch, 'content_duration_seconds', readonly=True)
resource_property(CardsFetch, 'created_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(CardsFetch, 'deleted', readonly=True, transform=TRANSFORM.BOOL)
resource_property(CardsFetch, 'duration_in_minutes', readonly=True)
resource_property(CardsFetch, 'end_time', readonly=True, transform=TRANSFORM.TIME)
resource_property(CardsFetch, 'first_choice', readonly=True)
resource_property(CardsFetch, 'first_cta', readonly=True)
resource_property(CardsFetch, 'first_cta_tweet', readonly=True)
resource_property(CardsFetch, 'first_cta_welcome_message_id', readonly=True)
resource_property(CardsFetch, 'fouth_choice', readonly=True)
resource_property(CardsFetch, 'fouth_cta', readonly=True)
resource_property(CardsFetch, 'fouth_cta_tweet', readonly=True)
resource_property(CardsFetch, 'fourth_cta_welcome_message_id', readonly=True)
resource_property(CardsFetch, 'googleplay_app_id', readonly=True)
resource_property(CardsFetch, 'googleplay_deep_link', readonly=True)
resource_property(CardsFetch, 'id', readonly=True)
resource_property(CardsFetch, 'image', readonly=True)
resource_property(CardsFetch, 'image_display_height', readonly=True)
resource_property(CardsFetch, 'image_display_width', readonly=True)
resource_property(CardsFetch, 'ios_app_store_identifier', readonly=True)
resource_property(CardsFetch, 'ios_deep_link', readonly=True)
resource_property(CardsFetch, 'name', readonly=True)
resource_property(CardsFetch, 'recipient_user_id', readonly=True)
resource_property(CardsFetch, 'second_choice', readonly=True)
resource_property(CardsFetch, 'second_cta', readonly=True)
resource_property(CardsFetch, 'second_cta_tweet', readonly=True)
resource_property(CardsFetch, 'second_cta_welcome_message_id', readonly=True)
resource_property(CardsFetch, 'start_time', readonly=True, transform=TRANSFORM.TIME)
resource_property(CardsFetch, 'thank_you_text', readonly=True)
resource_property(CardsFetch, 'thank_you_url', readonly=True)
resource_property(CardsFetch, 'third_choice', readonly=True)
resource_property(CardsFetch, 'third_cta', readonly=True)
resource_property(CardsFetch, 'third_cta_tweet', readonly=True)
resource_property(CardsFetch, 'third_cta_welcome_message_id', readonly=True)
resource_property(CardsFetch, 'title', readonly=True)
resource_property(CardsFetch, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(CardsFetch, 'video_content_id', readonly=True)
resource_property(CardsFetch, 'video_height', readonly=True)
resource_property(CardsFetch, 'video_hls_url', readonly=True)
resource_property(CardsFetch, 'video_owner_id', readonly=True)
resource_property(CardsFetch, 'video_poster_height', readonly=True)
resource_property(CardsFetch, 'video_poster_url', readonly=True)
resource_property(CardsFetch, 'video_poster_width', readonly=True)
resource_property(CardsFetch, 'video_width', readonly=True)
resource_property(CardsFetch, 'video_url', readonly=True)
resource_property(CardsFetch, 'website_dest_url', readonly=True)
resource_property(CardsFetch, 'website_display_url', readonly=True)
resource_property(CardsFetch, 'website_shortened_url', readonly=True)
resource_property(CardsFetch, 'website_title', readonly=True)
resource_property(CardsFetch, 'website_url', readonly=True)
resource_property(CardsFetch, 'wide_app_image', readonly=True)
class TweetPreview(Resource):
PROPERTIES = {}
RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/tweet_previews'
@classmethod
@FlattenParams
def load(klass, account, **kwargs):
resource = klass.RESOURCE_COLLECTION.format(account_id=account.id)
request = Request(account.client, 'get', resource, params=kwargs)
return Cursor(klass, request, init_with=[account])
# tweet preview properties
# read-only
resource_property(TweetPreview, 'preview', readonly=True)
resource_property(TweetPreview, 'tweet_id', readonly=True)
class Tweets(object):
RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/tweets'
@classmethod
@FlattenParams
def all(klass, account, **kwargs):
resource = klass.RESOURCE_COLLECTION.format(account_id=account.id)
request = Request(account.client, 'get', resource, params=kwargs)
return Cursor(None, request)
class Card(Resource):
PROPERTIES = {}
RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/{id}'
RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards'
def save(self):
if self.id:
method = 'put'
resource = self.RESOURCE.format(account_id=self.account.id, id=self.id)
else:
method = 'post'
resource = self.RESOURCE_COLLECTION.format(account_id=self.account.id)
headers = {'Content-Type': 'application/json'}
payload = {'name': self.name, 'components': self.components}
response = Request(self.account.client, method, resource, headers=headers,
body=json.dumps(payload)
).perform()
return self.from_response(response.body['data'])
@classmethod
@FlattenParams
def load(klass, account, id, **kwargs):
resource = klass.RESOURCE.format(account_id=account.id, id=id)
response = Request(account.client, 'get', resource, params=kwargs).perform()
return klass(account).from_response(response.body['data'])
def reload(self):
if self.id:
self.load(self.account, card_id=self.id)
# card properties
# read-only
resource_property(Card, 'id', readonly=True)
resource_property(Card, 'card_uri', readonly=True)
resource_property(Card, 'card_type', readonly=True)
resource_property(Card, 'created_at', readonly=True, transform=TRANSFORM.TIME)
resource_property(Card, 'deleted', readonly=True, transform=TRANSFORM.BOOL)
resource_property(Card, 'updated_at', readonly=True, transform=TRANSFORM.TIME)
# these are writable
resource_property(Card, 'name')
resource_property(Card, 'components', transform=TRANSFORM.LIST)