diff --git a/.pylintrc b/.pylintrc
index ba02b0f..f7d2ba9 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -48,7 +48,7 @@ ignore=CVS
# ignore-list. The regex matches against paths and can be in Posix or Windows
# format. Because '\' represents the directory delimiter on Windows systems, it
# can't be used as an escape character.
-ignore-paths=generated,.venv,venv,docs,samples,package_test,integration_tests
+ignore-paths=generated,.venv,venv,docs,samples,package_test,integration_tests,pinterest/utils/validations.py
# Files or directories matching the regular expression patterns are skipped.
# The regex matches against base names, not paths. The default value ignores
@@ -473,7 +473,7 @@ max-returns=6
max-statements=50
# Minimum number of public methods for a class (see R0903).
-min-public-methods=2
+min-public-methods=1
[STRING]
diff --git a/README.md b/README.md
index 5d2b6d9..f702ac1 100644
--- a/README.md
+++ b/README.md
@@ -181,7 +181,6 @@ campaign.set_lifetime_budget(
* Documentation is hosted on [Developer Site](https://developers.pinterest.com/docs/sdk/).
-
## Exceptions
See `pinterest.utils.sdk_exceptions` for a list of exceptions which may be thrown by the SDK.
diff --git a/integration_tests/ads/test_ad_accounts.py b/integration_tests/ads/test_ad_accounts.py
index c83f04c..6d1aeff 100644
--- a/integration_tests/ads/test_ad_accounts.py
+++ b/integration_tests/ads/test_ad_accounts.py
@@ -1,7 +1,9 @@
"""
Test Ad Account Model
"""
-
+from datetime import date
+from datetime import timedelta
+from parameterized import parameterized
from unittest.mock import patch
from pinterest.ads.ad_accounts import AdAccount
@@ -9,7 +11,8 @@
from pinterest.ads.audiences import Audience
from integration_tests.base_test import BaseTestCase
-from integration_tests.config import OWNER_USER_ID, DEFAULT_AD_ACCOUNT_ID
+from integration_tests.config import OWNER_USER_ID
+from integration_tests.config import DEFAULT_AD_ACCOUNT_ID
class TestAdAccount(BaseTestCase):
@@ -136,3 +139,59 @@ def test_list_customer_list_success(self):
get_all_customer_list_ids.add(getattr(customer_list, "_id"))
assert new_customer_list_ids == get_all_customer_list_ids
+
+
+class TestGetAnalytics(BaseTestCase):
+ """
+ Test getting Ad accounts analytics
+ """
+ DAYS_BACK = 2
+ FURTHEST_BACK_HOUR = 7 # Futhest allowed days back for granularity HOUR
+ MAX_RANGE_DAYS = 3 # Max time range in days for granularity HOUR
+ FURTHEST_BACK_NOT_HOUR = 90 # Futhest allowed days back for any granularity but HOUR
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL"),
+ ("granularity_day", "DAY"),
+ ("granularity_hour", "HOUR"),
+ ("granularity_week", "WEEK"),
+ ("granularity_month", "MONTH"),
+ ]
+ )
+ def test_get_ad_analytics_success(self, name, granularity):
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(self.DAYS_BACK),
+ 'end_date': date.today(),
+ 'columns': ["ADVERTISER_ID","PIN_PROMOTION_ID","SPEND_IN_DOLLAR"],
+ 'granularity': granularity,
+ }
+ ad_account = AdAccount(
+ ad_account_id=self.ad_account_utils.get_default_ad_account_id(),
+ client=self.test_client
+ )
+ ad_account_analytics = ad_account.get_analytics(**analytics_info_dict)
+ self.assertIsNotNone(ad_account_analytics)
+ self.assertIsNotNone(ad_account_analytics.raw_response)
+
+
+class TestGetTargetingAnalytics(BaseTestCase):
+
+ def test_get_ad_targeting_analytics_success(self):
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(2),
+ 'end_date': date.today(),
+ 'targeting_types':["GENDER"],
+ 'columns': ["ADVERTISER_ID","PIN_PROMOTION_ID","SPEND_IN_DOLLAR"],
+ 'granularity': 'DAY',
+ }
+ ad_account = AdAccount(
+ ad_account_id=self.ad_account_utils.get_default_ad_account_id(),
+ client=self.test_client
+ )
+ ad_analytics = ad_account.get_targeting_analytics(**analytics_info_dict)
+ self.assertIsNotNone(ad_analytics)
+ self.assertIsNotNone(ad_analytics.raw_response)
+ self.assertIsNotNone(ad_analytics.raw_response.get('data'))
diff --git a/integration_tests/ads/test_ad_groups.py b/integration_tests/ads/test_ad_groups.py
index d147e26..1c96df2 100644
--- a/integration_tests/ads/test_ad_groups.py
+++ b/integration_tests/ads/test_ad_groups.py
@@ -1,9 +1,15 @@
'''
Test AdGroup Model
'''
+from datetime import date
+from datetime import timedelta
+from parameterized import parameterized
+
+from openapi_generated.pinterest_client.exceptions import ApiException
+from openapi_generated.pinterest_client.model.targeting_spec import TargetingSpec
from integration_tests.base_test import BaseTestCase
-from integration_tests.config import DEFAULT_AD_ACCOUNT_ID
+from integration_tests.config import DEFAULT_AD_ACCOUNT_ID, DEFAULT_AD_GROUP_ID
from pinterest.ads.ad_groups import AdGroup
@@ -63,9 +69,7 @@ def test_update_success(self):
)
new_name = "SDK_AD_GROUP_NEW_NAME"
- new_spec = {
- "GENDER": ["male"]
- }
+ new_spec = TargetingSpec(gender=["male"])
ad_group.update_fields(
name=new_name,
@@ -221,3 +225,138 @@ def test_disable_auto_targeting(self):
ad_group_id=getattr(ad_group_0, "_id")
)
self.assertFalse(getattr(ad_group_1, "_auto_targeting_enabled"))
+
+
+class TestGetAnalytics(BaseTestCase):
+ """
+ Test getting Ad Group analytics
+ """
+ DAYS_BACK = 2
+ FURTHEST_DAYS_BACK_HOUR = 7 # Futhest allowed days back for granularity HOUR
+ FURTHEST_DAYS_BACK_NOT_HOUR = 90 # Futhest allowed days back for any granularity but HOUR
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL"),
+ ("granularity_day", "DAY"),
+ ("granularity_hour", "HOUR"),
+ ("granularity_week", "WEEK"),
+ ("granularity_month", "MONTH"),
+ ]
+ )
+ def test_get_ad_group_analytics_success(self, name, granularity):
+
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(self.DAYS_BACK),
+ 'end_date': date.today(),
+ 'columns': ["ADVERTISER_ID","TOTAL_ENGAGEMENT","SPEND_IN_DOLLAR"],
+ 'granularity': granularity,
+ }
+ ad_group = AdGroup(
+ ad_account_id=DEFAULT_AD_ACCOUNT_ID,
+ ad_group_id=DEFAULT_AD_GROUP_ID,
+ client=self.test_client,
+ )
+
+ ad_group_analytics = ad_group.get_analytics(**analytics_info_dict)
+ self.assertIsNotNone(ad_group_analytics)
+ self.assertIsNotNone(ad_group_analytics.raw_response)
+ analytics_response = ad_group_analytics.raw_response.get('value')
+ for dict_item in analytics_response:
+ for column in analytics_info_dict.get('columns'):
+ self.assertIn(column, dict_item)
+ if granularity != 'TOTAL':
+ self.assertIn('DATE', dict_item)
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_day", "DAY", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_hour", "HOUR", FURTHEST_DAYS_BACK_HOUR + 1),
+ ("granularity_week", "WEEK", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_month", "MONTH", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ]
+ )
+ def test_get_analytics_fail(self, name, granularity, days_back):
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(days_back),
+ 'end_date': date.today(),
+ 'columns': ["ADVERTISER_ID","PIN_PROMOTION_ID","SPEND_IN_DOLLAR"],
+ 'granularity': granularity,
+ }
+ ad_group = AdGroup(
+ ad_account_id=DEFAULT_AD_ACCOUNT_ID,
+ ad_group_id=DEFAULT_AD_GROUP_ID,
+ client=self.test_client,
+ )
+ with self.assertRaises(ApiException):
+ ad_group.get_analytics(**analytics_info_dict)
+
+class TestGetTargetingAnalytics(BaseTestCase):
+ """
+ Test getting targeting analytics for Ad Group
+ """
+ DAYS_BACK = 2
+ FURTHEST_DAYS_BACK_HOUR = 7 # Futhest allowed days back for granularity HOUR
+ FURTHEST_DAYS_BACK_NOT_HOUR = 90 # Futhest allowed days back for any granularity but HOUR
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL"),
+ ("granularity_day", "DAY"),
+ ("granularity_week", "WEEK"),
+ ("granularity_month", "MONTH"),
+ ]
+ )
+ def test_get_ad_group_targeting_analytics_success(self, name, granularity):
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(self.DAYS_BACK),
+ 'end_date': date.today(),
+ 'targeting_types':["GENDER"],
+ 'columns': ["SPEND_IN_MICRO_DOLLAR","SPEND_IN_DOLLAR", "TOTAL_ENGAGEMENT"],
+ 'granularity': granularity,
+ }
+ ad_group = AdGroup(
+ ad_account_id=DEFAULT_AD_ACCOUNT_ID,
+ ad_group_id=DEFAULT_AD_GROUP_ID,
+ client=self.test_client,
+ )
+ ad_group_analytics = ad_group.get_targeting_analytics(**analytics_info_dict)
+ self.assertIsNotNone(ad_group_analytics)
+ self.assertIsNotNone(ad_group_analytics.raw_response)
+ analytics_response = ad_group_analytics.raw_response.get('data')
+ for dict_item in analytics_response:
+ self.assertIsNotNone(dict_item.get('metrics'))
+ for column in analytics_info_dict.get('columns'):
+ self.assertIn(column, dict_item.get('metrics'))
+ if granularity != 'TOTAL':
+ self.assertIn('DATE', dict_item.get('metrics'))
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_day", "DAY", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_hour", "HOUR", FURTHEST_DAYS_BACK_HOUR + 1),
+ ("granularity_week", "WEEK", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_month", "MONTH", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ]
+ )
+ def test_get_ad_group_targeting_analytics_success(self, name, granularity, days_back):
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(days_back),
+ 'end_date': date.today(),
+ 'targeting_types':["GENDER"],
+ 'columns': ["SPEND_IN_MICRO_DOLLAR","SPEND_IN_DOLLAR", "TOTAL_ENGAGEMENT"],
+ 'granularity': granularity,
+ }
+ ad_group = AdGroup(
+ ad_account_id=DEFAULT_AD_ACCOUNT_ID,
+ ad_group_id=DEFAULT_AD_GROUP_ID,
+ client=self.test_client,
+ )
+ with self.assertRaises(ApiException):
+ ad_group.get_targeting_analytics(**analytics_info_dict)
diff --git a/integration_tests/ads/test_ads.py b/integration_tests/ads/test_ads.py
index 0032c29..8fe10e7 100644
--- a/integration_tests/ads/test_ads.py
+++ b/integration_tests/ads/test_ads.py
@@ -1,15 +1,23 @@
"""
Test Ad Model
"""
+from datetime import date
+from datetime import timedelta
+from unittest.mock import DEFAULT
+from parameterized import parameterized
from pinterest.ads.ads import Ad
+from openapi_generated.pinterest_client.exceptions import ApiException
from openapi_generated.pinterest_client.exceptions import ApiValueError
from openapi_generated.pinterest_client.exceptions import NotFoundException
from openapi_generated.pinterest_client.model.entity_status import EntityStatus
from integration_tests.base_test import BaseTestCase
-from integration_tests.config import DEFAULT_PIN_ID, DEFAULT_AD_ACCOUNT_ID
+from integration_tests.config import DEFAULT_PIN_ID
+from integration_tests.config import DEFAULT_AD_ACCOUNT_ID
+from integration_tests.config import DEFAULT_AD_ID
+from integration_tests.config import DEFAULT_AD_GROUP_ID
class TestCreateAd(BaseTestCase):
@@ -23,16 +31,15 @@ def test_create_ad_success(self):
"""
ad = Ad.create(
ad_account_id=DEFAULT_AD_ACCOUNT_ID,
- ad_group_id=self.ad_group_utils.get_ad_group_id(),
- creative_type="REGULAR",
+ ad_group_id=DEFAULT_AD_GROUP_ID,
+ creative_type="IDEA",
pin_id=DEFAULT_PIN_ID,
name="Test_create_ad",
- status="ACTIVE",
+ status="PAUSED",
is_pin_deleted=False,
is_removable=False,
client=self.test_client,
)
-
assert ad
assert getattr(ad, "_id")
assert getattr(ad, "_name") == "Test_create_ad"
@@ -43,7 +50,7 @@ def test_create_ad_failure_without_creative_type(self):
"""
ad_arguments = dict(
ad_account_id=DEFAULT_AD_ACCOUNT_ID,
- ad_group_id=self.ad_group_utils.get_ad_group_id(),
+ ad_group_id=DEFAULT_AD_GROUP_ID,
pin_id=DEFAULT_PIN_ID,
name="Test_create_ad",
status="ACTIVE",
@@ -59,7 +66,7 @@ def test_create_ad_failure_with_incorrect_creative_type(self):
"""
ad_arguments = dict(
ad_account_id=DEFAULT_AD_ACCOUNT_ID,
- ad_group_id=self.ad_group_utils.get_ad_group_id(),
+ ad_group_id=DEFAULT_AD_GROUP_ID,
creative_type="NOT",
pin_id=DEFAULT_PIN_ID,
name="Test_create_ad",
@@ -80,12 +87,12 @@ def test_get_ad_success(self):
"""
ad = Ad(
ad_account_id=DEFAULT_AD_ACCOUNT_ID,
- ad_id=self.ad_utils.get_ad_id(),
+ ad_id=DEFAULT_AD_ID,
client=self.test_client,
)
assert ad
- assert getattr(ad, "_id") == self.ad_utils.get_ad_id()
+ assert getattr(ad, "_id") == DEFAULT_AD_ID
def test_get_ad_fail_with_invalid_id(self):
"""
@@ -145,7 +152,7 @@ def test_update_ad_success(self):
"""
ad = Ad(
ad_account_id=DEFAULT_AD_ACCOUNT_ID,
- ad_id=self.ad_utils.get_ad_id(),
+ ad_id=DEFAULT_AD_ID,
client=self.test_client,
)
@@ -160,3 +167,138 @@ def test_update_ad_success(self):
assert ad
assert getattr(ad, "_name") == new_name
assert getattr(ad, "_status") == EntityStatus(new_status)
+
+class TestGetAnalytics(BaseTestCase):
+ """
+ Test getting Ad analytics
+ """
+ DAYS_BACK = 2
+ FURTHEST_DAYS_BACK_HOUR = 7 # Futhest allowed days back for granularity HOUR
+ FURTHEST_DAYS_BACK_NOT_HOUR = 90 # Futhest allowed days back for any granularity but HOUR
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL"),
+ ("granularity_day", "DAY"),
+ ("granularity_hour", "HOUR"),
+ ("granularity_week", "WEEK"),
+ ("granularity_month", "MONTH"),
+ ]
+ )
+ def test_get_ad_analytics_success(self, name, granularity):
+
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(self.DAYS_BACK),
+ 'end_date': date.today(),
+ 'columns': ["ADVERTISER_ID","PIN_PROMOTION_ID","SPEND_IN_DOLLAR"],
+ 'granularity': granularity,
+ }
+ ad = Ad(
+ ad_account_id=analytics_info_dict.pop('ad_account_id'),
+ ad_id=DEFAULT_AD_ID,
+ client=self.test_client,
+ )
+
+ ad_analytics = ad.get_analytics(**analytics_info_dict)
+ self.assertIsNotNone(ad_analytics)
+ self.assertIsNotNone(ad_analytics.raw_response)
+ analytics_response = ad_analytics.raw_response.get('value')
+ for dict_item in analytics_response:
+ for column in analytics_info_dict.get('columns'):
+ self.assertIn(column, dict_item)
+ if granularity != 'TOTAL':
+ self.assertIn('DATE', dict_item)
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_day", "DAY", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_hour", "HOUR", FURTHEST_DAYS_BACK_HOUR + 1),
+ ("granularity_week", "WEEK", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_month", "MONTH", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ]
+ )
+ def test_get_analytics_fail(self, name, granularity, days_back):
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(days_back),
+ 'end_date': date.today(),
+ 'columns': ["ADVERTISER_ID","PIN_PROMOTION_ID","SPEND_IN_DOLLAR"],
+ 'granularity': granularity,
+ }
+ ad = Ad(
+ ad_account_id=analytics_info_dict.pop('ad_account_id'),
+ ad_id=DEFAULT_AD_ID,
+ client=self.test_client,
+ )
+ with self.assertRaises(ApiException):
+ ad.get_analytics(**analytics_info_dict)
+
+
+class TestGetTargetingAnalytics(BaseTestCase):
+ """
+ Test getting targeting analytics
+ """
+ DAYS_BACK = 2
+ FURTHEST_DAYS_BACK_HOUR = 7 # Futhest allowed days back for granularity HOUR
+ FURTHEST_DAYS_BACK_NOT_HOUR = 90 # Futhest allowed days back for any granularity but HOUR
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL"),
+ ("granularity_day", "DAY"),
+ ("granularity_week", "WEEK"),
+ ("granularity_month", "MONTH"),
+ ]
+ )
+ def test_get_ad_targeting_analytics_success(self, name, granularity):
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(self.DAYS_BACK),
+ 'end_date': date.today(),
+ 'targeting_types':["GENDER"],
+ 'columns': ["SPEND_IN_MICRO_DOLLAR","SPEND_IN_DOLLAR", "TOTAL_ENGAGEMENT"],
+ 'granularity': granularity,
+ }
+ ad = Ad(
+ ad_account_id=analytics_info_dict.pop('ad_account_id'),
+ ad_id=DEFAULT_AD_ID,
+ client=self.test_client,
+ )
+ ad_analytics = ad.get_targeting_analytics(**analytics_info_dict)
+ self.assertIsNotNone(ad_analytics)
+ self.assertIsNotNone(ad_analytics.raw_response)
+ analytics_response = ad_analytics.raw_response.get('data')
+ for dict_item in analytics_response:
+ self.assertIsNotNone(dict_item.get('metrics'))
+ for column in analytics_info_dict.get('columns'):
+ self.assertIn(column, dict_item.get('metrics'))
+ if granularity != 'TOTAL':
+ self.assertIn('DATE', dict_item.get('metrics'))
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_day", "DAY", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_hour", "HOUR", FURTHEST_DAYS_BACK_HOUR + 1),
+ ("granularity_week", "WEEK", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_month", "MONTH", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ]
+ )
+ def test_get_ad_targeting_analytics_fail(self, name, granularity, days_back):
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(days_back),
+ 'end_date': date.today(),
+ 'targeting_types':["GENDER"],
+ 'columns': ["SPEND_IN_MICRO_DOLLAR","SPEND_IN_DOLLAR", "TOTAL_ENGAGEMENT"],
+ 'granularity': granularity,
+ }
+ ad = Ad(
+ ad_account_id=analytics_info_dict.pop('ad_account_id'),
+ ad_id=DEFAULT_AD_ID,
+ client=self.test_client,
+ )
+ with self.assertRaises(ApiException):
+ ad.get_targeting_analytics(**analytics_info_dict)
diff --git a/integration_tests/ads/test_campaigns.py b/integration_tests/ads/test_campaigns.py
index f56cb01..e76d8cf 100644
--- a/integration_tests/ads/test_campaigns.py
+++ b/integration_tests/ads/test_campaigns.py
@@ -1,10 +1,13 @@
"""
Test Campaign Model
"""
-
+from datetime import date
+from datetime import timedelta
+from parameterized import parameterized
from unittest.mock import patch
from openapi_generated.pinterest_client.model.objective_type import ObjectiveType
+from openapi_generated.pinterest_client.exceptions import ApiException
from openapi_generated.pinterest_client.exceptions import ApiValueError
from openapi_generated.pinterest_client.exceptions import NotFoundException
@@ -15,6 +18,7 @@
from integration_tests.base_test import BaseTestCase
from integration_tests.config import DEFAULT_AD_ACCOUNT_ID
+from integration_tests.config import DEFAULT_CAMPAIGN_ID
class TestCreateCampaign(BaseTestCase):
@@ -233,3 +237,138 @@ def test_get_next_page_of_campaigns(self):
assert isinstance(campaign, Campaign)
assert created_campaign_ids == get_all_campaigns_ids
+
+
+class TestGetAnalytics(BaseTestCase):
+ """
+ Test getting Campaign analytics
+ """
+ DAYS_BACK = 2
+ FURTHEST_DAYS_BACK_HOUR = 7 # Futhest allowed days back for granularity HOUR
+ FURTHEST_DAYS_BACK_NOT_HOUR = 90 # Futhest allowed days back for any granularity but HOUR
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL"),
+ ("granularity_day", "DAY"),
+ ("granularity_hour", "HOUR"),
+ ("granularity_week", "WEEK"),
+ ("granularity_month", "MONTH"),
+ ]
+ )
+ def test_get_campaign_analytics_success(self, name, granularity):
+
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(self.DAYS_BACK),
+ 'end_date': date.today(),
+ 'columns': ["ADVERTISER_ID","TOTAL_ENGAGEMENT","SPEND_IN_DOLLAR"],
+ 'granularity': granularity,
+ }
+ campaign = Campaign(
+ client=self.test_client,
+ ad_account_id=DEFAULT_AD_ACCOUNT_ID,
+ campaign_id=DEFAULT_CAMPAIGN_ID,
+ )
+
+ campaign_analytics = campaign.get_analytics(**analytics_info_dict)
+ self.assertIsNotNone(campaign_analytics)
+ self.assertIsNotNone(campaign_analytics.raw_response)
+ analytics_response = campaign_analytics.raw_response.get('value')
+ for dict_item in analytics_response:
+ for column in analytics_info_dict.get('columns'):
+ self.assertIn(column, dict_item)
+ if granularity != 'TOTAL':
+ self.assertIn('DATE', dict_item)
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_day", "DAY", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_hour", "HOUR", FURTHEST_DAYS_BACK_HOUR + 1),
+ ("granularity_week", "WEEK", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_month", "MONTH", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ]
+ )
+ def test_get_campaign_analytics_fail(self, name, granularity, days_back):
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(days_back),
+ 'end_date': date.today(),
+ 'columns': ["ADVERTISER_ID","PIN_PROMOTION_ID","SPEND_IN_DOLLAR"],
+ 'granularity': granularity,
+ }
+ campaign = Campaign(
+ client=self.test_client,
+ ad_account_id=DEFAULT_AD_ACCOUNT_ID,
+ campaign_id=DEFAULT_CAMPAIGN_ID,
+ )
+ with self.assertRaises(ApiException):
+ campaign.get_analytics(**analytics_info_dict)
+
+class TestGetTargetingAnalytics(BaseTestCase):
+ """
+ Test getting targeting analytics for Campaigns
+ """
+ DAYS_BACK = 2
+ FURTHEST_DAYS_BACK_HOUR = 7 # Futhest allowed days back for granularity HOUR
+ FURTHEST_DAYS_BACK_NOT_HOUR = 90 # Futhest allowed days back for any granularity but HOUR
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL"),
+ ("granularity_day", "DAY"),
+ ("granularity_week", "WEEK"),
+ ("granularity_month", "MONTH"),
+ ]
+ )
+ def test_get_campaign_targeting_analytics_success(self, name, granularity):
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(self.DAYS_BACK),
+ 'end_date': date.today(),
+ 'targeting_types':["GENDER"],
+ 'columns': ["SPEND_IN_MICRO_DOLLAR","SPEND_IN_DOLLAR", "TOTAL_ENGAGEMENT"],
+ 'granularity': granularity,
+ }
+ campaign = Campaign(
+ client=self.test_client,
+ ad_account_id=DEFAULT_AD_ACCOUNT_ID,
+ campaign_id=DEFAULT_CAMPAIGN_ID,
+ )
+ campaign_analytics = campaign.get_targeting_analytics(**analytics_info_dict)
+ self.assertIsNotNone(campaign_analytics)
+ self.assertIsNotNone(campaign_analytics.raw_response)
+ analytics_response = campaign_analytics.raw_response.get('data')
+ for dict_item in analytics_response:
+ self.assertIsNotNone(dict_item.get('metrics'))
+ for column in analytics_info_dict.get('columns'):
+ self.assertIn(column, dict_item.get('metrics'))
+ if granularity != 'TOTAL':
+ self.assertIn('DATE', dict_item.get('metrics'))
+
+ @parameterized.expand(
+ [
+ ("granularity_total","TOTAL", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_day", "DAY", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_hour", "HOUR", FURTHEST_DAYS_BACK_HOUR + 1),
+ ("granularity_week", "WEEK", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ("granularity_month", "MONTH", FURTHEST_DAYS_BACK_NOT_HOUR + 1),
+ ]
+ )
+ def test_get_campaign_targeting_analytics_fail(self, name, granularity, days_back):
+ analytics_info_dict = {
+ 'ad_account_id': DEFAULT_AD_ACCOUNT_ID,
+ 'start_date': date.today() - timedelta(days_back),
+ 'end_date': date.today(),
+ 'targeting_types':["GENDER"],
+ 'columns': ["SPEND_IN_MICRO_DOLLAR","SPEND_IN_DOLLAR", "TOTAL_ENGAGEMENT"],
+ 'granularity': granularity,
+ }
+ campaign = Campaign(
+ client=self.test_client,
+ ad_account_id=DEFAULT_AD_ACCOUNT_ID,
+ campaign_id=DEFAULT_CAMPAIGN_ID,
+ )
+ with self.assertRaises(ApiException):
+ campaign.get_targeting_analytics(**analytics_info_dict)
diff --git a/integration_tests/ads/test_conversion_events.py b/integration_tests/ads/test_conversion_events.py
index b3b9fba..0e203b1 100644
--- a/integration_tests/ads/test_conversion_events.py
+++ b/integration_tests/ads/test_conversion_events.py
@@ -8,6 +8,8 @@
from pinterest.client import PinterestSDKClient
from pinterest.ads.conversion_events import Conversion
+from openapi_generated.pinterest_client.exceptions import ApiException
+
class TestSendConversionEvent(BaseTestCase):
"""
Test send Conversion Event
@@ -79,17 +81,12 @@ def test_send_conversion_fail(self):
for _ in range(NUMBER_OF_CONVERSION_EVENTS)
]
- response = Conversion.send_conversion_events(
- client = client,
- ad_account_id = DEFAULT_AD_ACCOUNT_ID,
- conversion_events = conversion_events,
- test = True,
- )
+ with self.assertRaises(ApiException):
+ Conversion.send_conversion_events(
+ client = client,
+ ad_account_id = DEFAULT_AD_ACCOUNT_ID,
+ conversion_events = conversion_events,
+ test = True,
+ )
- assert response
- assert response.num_events_received == 2
- assert response.num_events_processed == 0
- assert len(response.events) == 2
- assert 'hashed format' in response.events[0].error_message
- assert 'hashed format' in response.events[0].error_message
diff --git a/integration_tests/clean_organic_data.py b/integration_tests/clean_organic_data.py
index 92339c0..a73dfcc 100644
--- a/integration_tests/clean_organic_data.py
+++ b/integration_tests/clean_organic_data.py
@@ -9,10 +9,5 @@ def test_delete_organic_data():
"""
Delete organic boards from default client
"""
- all_boards, _ = Board.get_all()
- for board in all_boards:
- if board.id == DEFAULT_BOARD_ID:
- continue
- Board.delete(board_id=board.id)
+ pass
- assert len(Board.get_all()[0]) == 1
diff --git a/integration_tests/config.py b/integration_tests/config.py
index 2aceb4c..51bc751 100644
--- a/integration_tests/config.py
+++ b/integration_tests/config.py
@@ -9,3 +9,6 @@
DEFAULT_BOARD_SECTION_ID = os.environ.get('DEFAULT_BOARD_SECTION_ID', "")
OWNER_USER_ID = os.environ.get('OWNER_USER_ID', "")
DEFAULT_AD_ACCOUNT_ID = os.environ.get('DEFAULT_AD_ACCOUNT_ID', "")
+DEFAULT_AD_ID = os.environ.get('DEFAULT_AD_ID', "")
+DEFAULT_AD_GROUP_ID = os.environ.get('DEFAULT_AD_GROUP_ID', "")
+DEFAULT_CAMPAIGN_ID = os.environ.get('DEFAULT_CAMPAIGN_ID', "")
diff --git a/integration_tests/organic/__init__.py b/integration_tests/organic/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/integration_tests/organic/test_pins.py b/integration_tests/organic/test_pins.py
index 7ce8379..e6364a4 100644
--- a/integration_tests/organic/test_pins.py
+++ b/integration_tests/organic/test_pins.py
@@ -3,9 +3,12 @@
*NOTE*: Do not forget to delete pin after the test.
"""
+from datetime import date
+from datetime import timedelta
from parameterized import parameterized
from openapi_generated.pinterest_client.exceptions import NotFoundException
+from openapi_generated.pinterest_client.exceptions import ApiException
from pinterest.organic.pins import Pin
@@ -102,6 +105,7 @@ def test_save_pin_success(self, pin_save_kwargs):
"""
pin = self.pin_utils.create_new_pin(title="Test Saving Pin")
assert pin
+ assert pin.title == "Test Saving Pin"
pin.save(**pin_save_kwargs)
@@ -117,3 +121,51 @@ def test_save_pin_success(self, pin_save_kwargs):
pin_id=pin.id,
client=self.test_client
)
+
+class TestGetAnalytics(BaseTestCase):
+ """
+ Test getting a Pin analytics
+ """
+ DAYS_BACK = 4
+ EXPECTED_NUM_OF_DAILY_METRICS = DAYS_BACK + 1
+ DAYS_BACK_OUT_OF_RANGE = 91
+ METRIC_TYPES = "IMPRESSION,OUTBOUND_CLICK,PIN_CLICK,SAVE,SAVE_RATE,TOTAL_COMMENTS,TOTAL_REACTIONS"
+
+ def validate_raw_response(self, raw_response):
+ self.assertIsNotNone(raw_response)
+ self.assertIsNotNone(raw_response.get('all'))
+ self.assertIsNotNone(raw_response.get('all').get('daily_metrics'))
+ self.assertIsNotNone(raw_response.get('all').get('lifetime_metrics'))
+ self.assertIsNotNone(raw_response.get('all').get('summary_metrics'))
+ self.assertEqual(len(raw_response.get('all').get('daily_metrics')), self.EXPECTED_NUM_OF_DAILY_METRICS)
+ for metric in self.METRIC_TYPES.split(','):
+ self.assertIn(metric, {**raw_response.get('all').get('summary_metrics'),**raw_response.get('all').get('lifetime_metrics')})
+
+
+ def test_get_analytics_success(self):
+ """
+ Test request the Pin analitycs
+ """
+ analytics_info_dict = {
+ 'pin_id': DEFAULT_PIN_ID,
+ 'start_date': date.today() - timedelta(self.DAYS_BACK),
+ 'end_date': date.today(),
+ 'metric_types': ["IMPRESSION,OUTBOUND_CLICK,PIN_CLICK,SAVE,SAVE_RATE,TOTAL_COMMENTS,TOTAL_REACTIONS"],
+ }
+ pin = Pin(pin_id=analytics_info_dict.pop('pin_id'))
+ analytics = pin.get_analytics(**analytics_info_dict)
+ self.assertIsNotNone(analytics)
+ self.validate_raw_response(analytics.raw_response)
+
+ def test_get_analytics_failure(self):
+ """Test request the Pin analytics from before 90 days ago.
+ """
+ analytics_info_dict = {
+ 'pin_id': DEFAULT_PIN_ID,
+ 'start_date': date.today() - timedelta(self.DAYS_BACK_OUT_OF_RANGE),
+ 'end_date': date.today(),
+ 'metric_types': ["IMPRESSION,OUTBOUND_CLICK,PIN_CLICK,SAVE,SAVE_RATE,TOTAL_COMMENTS,TOTAL_REACTIONS"],
+ }
+ pin = Pin(pin_id=analytics_info_dict.pop('pin_id'))
+ with self.assertRaises(ApiException):
+ pin.get_analytics(**analytics_info_dict)
\ No newline at end of file
diff --git a/integration_tests/utils/ads_utils.py b/integration_tests/utils/ads_utils.py
index 5aaa22d..c57de14 100644
--- a/integration_tests/utils/ads_utils.py
+++ b/integration_tests/utils/ads_utils.py
@@ -13,7 +13,7 @@
from pinterest.ads.conversion_tags import ConversionTag
-from integration_tests.config import DEFAULT_PIN_ID, OWNER_USER_ID, DEFAULT_AD_ACCOUNT_ID
+from integration_tests.config import DEFAULT_AD_GROUP_ID, DEFAULT_PIN_ID, OWNER_USER_ID, DEFAULT_AD_ACCOUNT_ID
def _merge_default_params_with_params(default_params, params):
@@ -212,7 +212,7 @@ def __init__(self, client=None):
self.ad = Ad.create(
ad_account_id=DEFAULT_AD_ACCOUNT_ID,
ad_group_id=getattr(self.ad_group, "_id"),
- creative_type="REGULAR",
+ creative_type="IDEA",
pin_id=DEFAULT_PIN_ID,
name="Test_create_ad",
status="ACTIVE",
@@ -232,7 +232,7 @@ def get_default_params(self):
return dict(
ad_account_id=DEFAULT_AD_ACCOUNT_ID,
ad_group_id=getattr(self.ad_group, "_id"),
- creative_type="REGULAR",
+ creative_type="IDEA",
pin_id=DEFAULT_PIN_ID,
name="Test_create_ad",
status="ACTIVE",
diff --git a/integration_tests/utils/organic_utils.py b/integration_tests/utils/organic_utils.py
index b1ef35b..d11bcdb 100644
--- a/integration_tests/utils/organic_utils.py
+++ b/integration_tests/utils/organic_utils.py
@@ -79,4 +79,5 @@ def create_new_pin(self, **kwargs):
return Pin.create(**_merge_default_params_with_params(self.get_default_params(), kwargs))
def delete_pin(self, pin_id):
- return Pin.delete(pin_id=pin_id, client=self.test_client)
+ if pin_id != DEFAULT_PIN_ID: # Make sure default pin is not being deleted
+ return Pin.delete(pin_id=pin_id, client=self.test_client)
diff --git a/pinterest/ads/ad_accounts.py b/pinterest/ads/ad_accounts.py
index 4c18707..d367e16 100644
--- a/pinterest/ads/ad_accounts.py
+++ b/pinterest/ads/ad_accounts.py
@@ -2,11 +2,13 @@
AdAccount Class for Pinterest Python SDK
"""
from __future__ import annotations
+from datetime import date
from openapi_generated.pinterest_client.model.country import Country
from openapi_generated.pinterest_client.model.ad_account_owner import AdAccountOwner
from openapi_generated.pinterest_client.model.currency import Currency
-
+from openapi_generated.pinterest_client.model.ads_analytics_targeting_type import AdsAnalyticsTargetingType
+from openapi_generated.pinterest_client.model.conversion_report_attribution_type import ConversionReportAttributionType
from openapi_generated.pinterest_client.api.ad_accounts_api import AdAccountsApi
from openapi_generated.pinterest_client.model.ad_account import AdAccount as GeneratedAdAccount
from openapi_generated.pinterest_client.model.ad_account_create_request import AdAccountCreateRequest
@@ -17,6 +19,7 @@
from pinterest.ads.customer_lists import CustomerList
from pinterest.utils.base_model import PinterestBaseModel
from pinterest.utils.bookmark import Bookmark
+from pinterest.utils.analytics import AnalyticsResponse, AnalyticsUtils
class AdAccount(PinterestBaseModel):
@@ -32,7 +35,6 @@ def __init__(
) -> None:
"""
Initialize an object of an AdAccount.
-
Args:
ad_account_id (str): Unique identifier of an ad account.
client (PinterestSDKClient, optional): PinterestSDKClient Object. Defaults to `default_api_client`.
@@ -116,16 +118,13 @@ def create(
For more, see \
Create an advertiser account.
-
Args:
name (str): Ad Account name
owner_user_id (str): Ad Account's owning user ID
country (str): Country ID from ISO 3166-1 alpha-2. Example: "US" or "RU".
client (PinterestSDKClient): PinterestSDKClient Object
-
Keyword Args:
Any valid keyword arguments or query parameters for endpoint.
-
Returns:
AdAccount: AdAccount Object
"""
@@ -160,7 +159,6 @@ def list_campaigns(
roles granted to them via\
\
Business Access: Admin, Analyst, Campaign Manager.
-
Args:
campaign_ids (list[str], optional): List of Campaign Ids to use to filter the results. Defaults to None.
entity_statuses (list[str], optional): Possible Entity Statuses "ACTIVE", "PAUSED" or "ARCHIVED". Defaults
@@ -172,10 +170,8 @@ def list_campaigns(
Note that higher-value IDs are associated with more-recently added items. Defaults to
"ASCENDING".
bookmark (str, optional): Cursor used to fetch the next page of items. Defaults to None.
-
Keyword Args:
Any valid keyword arguments or query parameters for endpoint.
-
Returns:
list[Campaign]: List of Campaign Objects
Bookmark: Bookmark for pagination if present, else None.
@@ -202,7 +198,6 @@ def list_audiences(
# pylint: disable=too-many-arguments
"""
Get a list of the audiences in the AdAccount, filtered by the specified arguments
-
Args:
entity_statuses (list[str], optional): Possible Entity Statuses "ACTIVE", "PAUSED" or "ARCHIVED". Defaults
to None.
@@ -213,7 +208,6 @@ def list_audiences(
Note that higher-value IDs are associated with more-recently added items. Defaults to
"ASCENDING".
bookmark (str, optional): Cursor used to fetch the next page of items. Defaults to None.
-
Returns:
list[Audience]: List of Audience Objects
Bookmark: Bookmark for pagination if present, else None.
@@ -238,7 +232,6 @@ def list_customer_lists(
# pylint: disable=too-many-arguments
"""
Get a list of customer lists in the AdAccount, filtered by the specified arguments
-
Args:
page_size (int[1..100], optional): Maximum number of items to include in a single page of the response.
See documentation on Pagination for more information. Defaults to None
@@ -247,7 +240,6 @@ def list_customer_lists(
Note that higher-value IDs are associated with more-recently added items. Defaults to
"ASCENDING".
bookmark (str, optional): Cursor used to fetch the next page of items. Defaults to None.
-
Returns:
list[CustomerList]: List of Audience Objects
Bookmark: Bookmark for pagination if present, else None.
@@ -260,3 +252,150 @@ def list_customer_lists(
client=self._client,
**kwargs
)
+
+ def get_analytics(
+ self,
+ start_date:date,
+ end_date:date,
+ columns:list[str],
+ granularity:str,
+ click_window_days:int=30,
+ engagement_window_days:int=30,
+ view_window_days:int=1,
+ conversion_report_time:str="TIME_OF_AD_ACTION",
+ **kwargs
+ ) -> AnalyticsResponse:
+ """
+ Get analytics for the specified ad_account_id, filtered by the specified options.
+ - The token's user_account must either be the Owner of the specified ad account, or have one of the necessary
+ roles granted to them via Business Access: Admin, Analyst, Campaign Manager.
+ Args:
+ start_date (date): Metric report start date (UTC).
+ end_date (date): Metric report end date (UTC).
+ columns (list[str]): Columns to retrieve, encoded as a comma-separated string. NOTE: Any metrics defined as
+ MICRO_DOLLARS returns a value based on the advertiser profile's currency field. For USD,($1/1,000,000,
+ or $0.000001 - one one-ten-thousandth of a cent). it's microdollars. Otherwise, it's in microunits of
+ the advertiser'scurrency. For example, if the advertiser's currency is GBP (British pound sterling), all
+ MICRO_DOLLARS fields will be in GBP microunits (1/1,000,000 British pound). If a column has no value,
+ it may not be returned
+ granularity (str): Enum: "TOTAL" "DAY" "HOUR" "WEEK" "MONTH"
+ TOTAL - metrics are aggregated over the specified date range.
+ DAY - metrics are broken down daily.
+ HOUR - metrics are broken down hourly.
+ WEEKLY - metrics are broken down weekly.
+ MONTHLY - metrics are broken down monthly
+ click_window_days (int, optional): Enum: 1 7 30 60. Number of days to use as the conversion attribution
+ window for a pin click action. Applies to Pinterest Tag conversion metrics. Prior conversion tags use
+ their defined attribution windows.. Defaults to 30.
+ engagement_window_days (int, optional): Enum: 1 7 30 60 Number of days to use as the conversion attribution
+ window for an engagement action. Engagements include saves, closeups, link clicks, and carousel card
+ swipes. Applies to Pinterest Tag conversion metrics. Prior conversion tags use their defined attribution
+ windows. Defaults to 30.
+ view_window_days (int, optional): Enum: 1 7 30 60. Number of days to use as the conversion attribution
+ window for a view action. Applies to Pinterest Tag conversion metrics. Prior conversion tags use their
+ defined attribution windows. Defaults to 1.
+ conversion_report_time (str, optional): Enum: "TIME_OF_AD_ACTION" "TIME_OF_CONVERSION". The date by which
+ the conversion metrics returned from this endpoint will be reported. There are two dates associated
+ with a conversion event: the date that the user interacted with the ad, and the date that the user
+ completed a conversion event. Defaults to "TIME_OF_AD_ACTION".
+ Returns:
+ AnalyticsResponse: AnalyticsResponse object.
+ """
+ kwargs['ad_account_id'] = self.id
+ kwargs['start_date'] = start_date
+ kwargs['end_date'] = end_date
+ kwargs['columns'] = columns
+ kwargs['granularity'] = granularity
+ kwargs['click_window_days'] = click_window_days
+ kwargs['engagement_window_days'] = engagement_window_days
+ kwargs['view_window_days'] = view_window_days
+ kwargs['conversion_report_time'] = conversion_report_time
+
+ ad_account_analytics_response = AnalyticsUtils.get_entity_analytics(
+ params=kwargs,
+ api=AdAccountsApi,
+ analytics_fn=AdAccountsApi.ad_account_analytics,
+ entity=AdAccount,
+ client=self._client
+ )
+
+ return ad_account_analytics_response
+
+ def get_targeting_analytics(
+ self,
+ start_date:date,
+ end_date:date,
+ targeting_types:list[str],
+ columns:list[str],
+ granularity:str,
+ click_window_days:int=30,
+ engagement_window_days:int=30,
+ view_window_days:int=1,
+ conversion_report_time:str = "TIME_OF_AD_ACTION",
+ attribution_types:str = None,
+ **kwargs
+ ) -> AnalyticsResponse:
+ """
+ Get analytics for the specified ad_account_id, filtered by the specified options.
+ - The token's user_account must either be the Owner of the specified ad account, or have one of the necessary
+ roles granted to them via Business Access: Admin, Analyst, Campaign Manager.
+ Args:
+ start_date (date): Metric report start date (UTC).
+ end_date (date): Metric report end date (UTC).
+ targeting_types (list[str]): Items Enum: "KEYWORD" "APPTYPE" "GENDER" "LOCATION" "PLACEMENT" "COUNTRY"
+ "TARGETED_INTEREST" "PINNER_INTEREST" "AUDIENCE_INCLUDE" "AUDIENCE_EXCLUDE" "GEO" "AGE_BUCKET" "REGION"
+ Targeting type breakdowns for the report. The reporting per targeting type
+ is independent from each other.
+ columns (list[str]): Columns to retrieve, encoded as a comma-separated string. NOTE: Any metrics defined as
+ MICRO_DOLLARS returns a value based on the advertiser profile's currency field. For USD,($1/1,000,000,
+ or $0.000001 - one one-ten-thousandth of a cent). it's microdollars. Otherwise, it's in microunits of
+ the advertiser'scurrency. For example, if the advertiser's currency is GBP (British pound sterling), all
+ MICRO_DOLLARS fields will be in GBP microunits (1/1,000,000 British pound). If a column has no value,
+ it may not be returned
+ granularity (str): Enum: "TOTAL" "DAY" "HOUR" "WEEK" "MONTH"
+ TOTAL - metrics are aggregated over the specified date range.
+ DAY - metrics are broken down daily.
+ HOUR - metrics are broken down hourly.
+ WEEKLY - metrics are broken down weekly.
+ MONTHLY - metrics are broken down monthly
+ click_window_days (int, optional): Enum: 1 7 30 60. Number of days to use as the conversion attribution
+ window for a pin click action. Applies to Pinterest Tag conversion metrics. Prior conversion tags use
+ their defined attribution windows.. Defaults to 30.
+ engagement_window_days (int, optional): Enum: 1 7 30 60 Number of days to use as the conversion attribution
+ window for an engagement action. Engagements include saves, closeups, link clicks, and carousel card
+ swipes. Applies to Pinterest Tag conversion metrics. Prior conversion tags use their defined attribution
+ windows. Defaults to 30.
+ view_window_days (int, optional): Enum: 1 7 30 60. Number of days to use as the conversion attribution
+ window for a view action. Applies to Pinterest Tag conversion metrics. Prior conversion tags use their
+ defined attribution windows. Defaults to 1.
+ conversion_report_time (str, optional): Enum: "TIME_OF_AD_ACTION" "TIME_OF_CONVERSION". The date by which
+ the conversion metrics returned from this endpoint will be reported. There are two dates associated
+ with a conversion event: the date that the user interacted with the ad, and the date that the user
+ completed a conversion event. Defaults to "TIME_OF_AD_ACTION".
+ attribution_types (str): Enum: "INDIVIDUAL" "HOUSEHOLD"
+ List of types of attribution for the conversion report
+ Returns:
+ AnalyticsResponse: AnalyticsResponse object.
+ """
+ kwargs['ad_account_id'] = self.id
+ kwargs['start_date'] = start_date
+ kwargs['end_date'] = end_date
+ kwargs['targeting_types'] = [AdsAnalyticsTargetingType(targeting_type) for targeting_type in targeting_types]
+ kwargs['columns'] = columns
+ kwargs['granularity'] = granularity
+ kwargs['click_window_days'] = click_window_days
+ kwargs['engagement_window_days'] = engagement_window_days
+ kwargs['view_window_days'] = view_window_days
+ kwargs['conversion_report_time'] = conversion_report_time
+ if attribution_types:
+ kwargs['attribution_types'] = ConversionReportAttributionType(attribution_types)
+
+ ad_account_analytics_response = AnalyticsUtils.get_entity_analytics(
+ params=kwargs,
+ api=AdAccountsApi,
+ analytics_fn=AdAccountsApi.ad_account_targeting_analytics_get,
+ entity=AdAccount,
+ client=self._client
+ )
+
+ return ad_account_analytics_response
diff --git a/pinterest/ads/ad_groups.py b/pinterest/ads/ad_groups.py
index 1df2599..d70e70f 100644
--- a/pinterest/ads/ad_groups.py
+++ b/pinterest/ads/ad_groups.py
@@ -3,8 +3,11 @@
"""
from __future__ import annotations
-from openapi_generated.pinterest_client.api.ad_groups_api import AdGroupsApi
+from datetime import date
+from openapi_generated.pinterest_client.model.conversion_report_attribution_type import ConversionReportAttributionType
+from openapi_generated.pinterest_client.model.ads_analytics_targeting_type import AdsAnalyticsTargetingType
+from openapi_generated.pinterest_client.api.ad_groups_api import AdGroupsApi
from openapi_generated.pinterest_client.model.action_type import ActionType
from openapi_generated.pinterest_client.model.budget_type import BudgetType
from openapi_generated.pinterest_client.model.ad_group_response import AdGroupResponse
@@ -15,6 +18,7 @@
from pinterest.utils.base_model import PinterestBaseModel
from pinterest.ads.ads import Ad
from pinterest.utils.bookmark import Bookmark
+from pinterest.utils.analytics import AnalyticsResponse, AnalyticsUtils
class AdGroup(PinterestBaseModel):
@@ -484,3 +488,173 @@ def disable_auto_targeting(self):
bool: true if ad group disable auto_targeting_enabled
"""
return self.update_fields(auto_targeting_enabled=False)
+
+ def get_targeting_analytics(
+ self,
+ start_date: date,
+ end_date: date,
+ targeting_types: list[str],
+ columns: list[str],
+ granularity: str,
+ click_window_days: int = 30,
+ engagement_window_days: int = 30,
+ view_window_days: int = 1,
+ conversion_report_time: str = "TIME_OF_AD_ACTION",
+ attribution_types: str = None,
+ **kwargs
+ ) -> AnalyticsResponse:
+ """
+ Get targeting analytics for one or more ad groups. For the requested ad group(s) and metrics, the response will
+ include the requested metric information (e.g. SPEND_IN_DOLLAR) for the requested target type
+ (e.g. "age_bucket") for applicable values (e.g. "45-49").
+
+ - The token's user_account must either be the Owner of the specified ad account, or have one of the
+ necessary roles granted to them via Business Access: Admin, Analyst, Campaign Manager.
+
+
+ Args:
+ start_date (date): Metric report start date (UTC). Format: YYYY-MM-DD
+ end_date (date): Metric report end date (UTC). Format: YYYY-MM-DD
+ targeting_types (list[str]): Example: targeting_types=APPTYPE
+ Targeting type breakdowns for the report. The reporting per targeting type is independent from
+ each other.
+ columns (list[str]): Example: columns=SPEND_IN_DOLLAR
+ Columns to retrieve, encoded as a comma-separated string. NOTE: Any metrics defined as MICRO_DOLLARS
+ returns a value based on the advertiser profile's currency field. For USD,($1/1,000,000, or $0.000001 -
+ one one-ten-thousandth of a cent). it's microdollars. Otherwise, it's in microunits of the advertiser's
+ currency.
+
+ For example, if the advertiser's currency is GBP (British pound sterling), all MICRO_DOLLARS fields will
+ be in GBP microunits (1/1,000,000 British pound).
+
+ If a column has no value, it may not be returned
+
+ granularity (str): Enum: "TOTAL" "DAY" "HOUR" "WEEK" "MONTH"
+ TOTAL - metrics are aggregated over the specified date range.
+ DAY - metrics are broken down daily.
+ HOUR - metrics are broken down hourly.
+ WEEKLY - metrics are broken down weekly.
+ MONTHLY - metrics are broken down monthly
+ click_window_days (int, optional): Default: 30
+ Enum: 1 7 30 60
+ Example: click_window_days=1
+ Number of days to use as the conversion attribution window for a pin click action. Applies to Pinterest
+ Tag conversion metrics. Prior conversion tags use their defined attribution windows. If not specified,
+ defaults to 30 days.
+ engagement_window_days (int, optional): Default: 30
+ Enum: 1 7 30 60
+ Number of days to use as the conversion attribution window for an engagement action. Engagements include
+ saves, closeups, link clicks, and carousel card swipes. Applies to Pinterest Tag conversion metrics.
+ Prior conversion tags use their defined attribution windows. If not specified, defaults to 30 days.
+ view_window_days (int, optional): Default: 1
+ Enum: 1 7 30 60
+ Number of days to use as the conversion attribution window for a view action. Applies to Pinterest Tag
+ conversion metrics. Prior conversion tags use their defined attribution windows. If not specified,
+ defaults to 1 day.
+ conversion_report_time (str, optional): Default: "TIME_OF_AD_ACTION"
+ Enum: "TIME_OF_AD_ACTION" "TIME_OF_CONVERSION"
+ Example: conversion_report_time=TIME_OF_AD_ACTION
+ The date by which the conversion metrics returned from this endpoint will be reported. There are two
+ dates associated with a conversion event: the date that the user interacted with the ad, and the date
+ that the user completed a conversion event.
+ attribution_types (str): Enum: "INDIVIDUAL" "HOUSEHOLD"
+ Example: attribution_types=INDIVIDUAL
+ List of types of attribution for the conversion report
+ Returns:
+ AnalyticsResponse: AnalyticsResponse object.
+ """
+ kwargs['ad_account_id'] = self.ad_account_id
+ kwargs['ad_group_ids'] = [self.id]
+ kwargs['start_date'] = start_date
+ kwargs['end_date'] = end_date
+ kwargs['targeting_types'] = [AdsAnalyticsTargetingType(targeting_type) for targeting_type in targeting_types]
+ kwargs['columns'] = columns
+ kwargs['granularity'] = granularity
+ kwargs['click_window_days'] = click_window_days
+ kwargs['engagement_window_days'] = engagement_window_days
+ kwargs['view_window_days'] = view_window_days
+ kwargs['conversion_report_time'] = conversion_report_time
+
+ if attribution_types:
+ kwargs['attribution_types'] = ConversionReportAttributionType(attribution_types)
+
+ return AnalyticsUtils.get_entity_analytics(
+ params=kwargs,
+ api=AdGroupsApi,
+ analytics_fn=AdGroupsApi.ad_groups_targeting_analytics_get,
+ entity=AdGroup,
+ client=self._client
+ )
+
+ def get_analytics(
+ self,
+ start_date: date,
+ end_date: date,
+ columns: list[str],
+ granularity: str,
+ click_window_days: int = 30,
+ engagement_window_days: int = 30,
+ view_window_days: int = 1,
+ conversion_report_time: str = "TIME_OF_AD_ACTION",
+ **kwargs
+ ) -> AnalyticsResponse:
+ """
+ Get analytics for the specified ad groups in the specified ad_account_id, filtered by the specified options.
+ - The token's user_account must either be the Owner of the specified ad account, or have one of the necessary
+ roles granted to them via Business Access: Admin, Analyst, Campaign Manager.
+ Args:
+ start_date (date): Metric report start date (UTC). Format: YYYY-MM-DD
+ end_date (date): Metric report end date (UTC). Format: YYYY-MM-DD
+ columns (list[str]): Columns to retrieve, encoded as a comma-separated string. NOTE: Any metrics defined as
+ MICRO_DOLLARS returns a value based on the advertiser profile's currency field. For USD,($1/1,000,000,
+ or $0.000001 - one one-ten-thousandth of a cent). it's microdollars. Otherwise, it's in microunits of
+ the advertiser's currency.For example, if the advertiser's currency is GBP (British pound sterling),
+ all MICRO_DOLLARS fields will be in GBP microunits (1/1,000,000 British pound). If a column has no
+ value, it may not be returned
+ granularity (str): Enum: "TOTAL" "DAY" "HOUR" "WEEK" "MONTH"
+ TOTAL - metrics are aggregated over the specified date range.
+ DAY - metrics are broken down daily.
+ HOUR - metrics are broken down hourly.
+ WEEKLY - metrics are broken down weekly.
+ MONTHLY - metrics are broken down monthly
+ click_window_days (int, optional): Default: 30
+ Enum: 1 7 30 60
+ Number of days to use as the conversion attribution window for an engagement action. Engagements include
+ saves, closeups, link clicks, and carousel card swipes. Applies to Pinterest Tag conversion metrics.
+ Prior conversion tags use their defined attribution windows. If not specified, defaults to 30 days.
+ engagement_window_days (int, optional): Default: 30
+ Enum: 1 7 30 60
+ Number of days to use as the conversion attribution window for an engagement action. Engagements include
+ saves, closeups, link clicks, and carousel card swipes. Applies to Pinterest Tag conversion metrics.
+ Prior conversion tags use their defined attribution windows. If not specified, defaults to 30 days.
+ view_window_days (int, optional): Default: 1
+ Enum: 1 7 30 60
+ Number of days to use as the conversion attribution window for a view action. Applies to Pinterest Tag
+ conversion metrics. Prior conversion tags use their defined attribution windows. If not specified,
+ defaults to 1 day.
+ conversion_report_time (str, optional): Default: "TIME_OF_AD_ACTION"
+ Enum: "TIME_OF_AD_ACTION" "TIME_OF_CONVERSION"
+ Example: conversion_report_time=TIME_OF_AD_ACTION
+ The date by which the conversion metrics returned from this endpoint will be reported. There are two
+ dates associated with a conversion event: the date that the user interacted with the ad, and the date
+ that the user completed a conversion event.
+ Returns:
+ AnalyticsResponse: AnalyticsResponse object.
+ """
+ kwargs['ad_group_ids'] = [self.id]
+ kwargs['start_date'] = start_date
+ kwargs['end_date'] = end_date
+ kwargs['columns'] = columns
+ kwargs['granularity'] = granularity
+ kwargs['click_window_days'] = click_window_days
+ kwargs['engagement_window_days'] = engagement_window_days
+ kwargs['view_window_days'] = view_window_days
+ kwargs['conversion_report_time'] = conversion_report_time
+
+ return AnalyticsUtils.get_entity_analytics(
+ params=kwargs,
+ api=AdGroupsApi,
+ analytics_fn=AdGroupsApi.ad_groups_analytics,
+ entity=AdGroup,
+ client=self._client
+ )
diff --git a/pinterest/ads/ads.py b/pinterest/ads/ads.py
index 1d98774..197a94b 100644
--- a/pinterest/ads/ads.py
+++ b/pinterest/ads/ads.py
@@ -3,8 +3,11 @@
"""
from __future__ import annotations
-from openapi_generated.pinterest_client.api.ads_api import AdsApi
+from datetime import date
+from openapi_generated.pinterest_client.model.ads_analytics_targeting_type import AdsAnalyticsTargetingType
+from openapi_generated.pinterest_client.model.conversion_report_attribution_type import ConversionReportAttributionType
+from openapi_generated.pinterest_client.api.ads_api import AdsApi
from openapi_generated.pinterest_client.model.ad_response import AdResponse
from openapi_generated.pinterest_client.model.ad_create_request import AdCreateRequest
from openapi_generated.pinterest_client.model.creative_type import CreativeType
@@ -14,6 +17,7 @@
from pinterest.client import PinterestSDKClient
from pinterest.utils.base_model import PinterestBaseModel
from pinterest.utils.bookmark import Bookmark
+from pinterest.utils.analytics import AnalyticsResponse, AnalyticsUtils
class Ad(PinterestBaseModel):
@@ -418,3 +422,175 @@ def update_fields(self, **kwargs) -> bool:
update_fn=AdsApi.ads_update,
**kwargs
)
+
+ def get_analytics(
+ self,
+ start_date: date,
+ end_date: date,
+ columns: list[str],
+ granularity: str,
+ click_window_days: int = 30,
+ engagement_window_days: int = 30,
+ view_window_days: int = 1,
+ conversion_report_time: str = "TIME_OF_AD_ACTION",
+ **kwargs
+ ) -> AnalyticsResponse:
+ """
+ Get analytics for the specified ads in the specified ad_account_id, filtered by the specified options.
+
+ - The token's user_account must either be the Owner of the specified ad account, or have one of the necessary
+ roles granted to them via Business Access: Admin, Analyst, Campaign Manager.
+
+
+ Args:
+ start_date (date): Metric report start date (UTC). Format: YYYY-MM-DD.
+ end_date (date): Metric report end date (UTC). Format: YYYY-MM-DD.
+ columns (list[str]): Columns to retrieve, encoded as a comma-separated string. NOTE: Any metrics defined as
+ MICRO_DOLLARS returns a value based on the advertiser profile's currency field. For USD,($1/1,000,000,
+ or $0.000001 - one one-ten-thousandth of a cent). it's microdollars. Otherwise, it's in microunits of
+ the advertiser's currency.For example, if the advertiser's currency is GBP (British pound sterling),
+ all MICRO_DOLLARS fields will be in GBP microunits (1/1,000,000 British pound).If a column has no value,
+ it may not be returned
+ granularity (str): Enum: "TOTAL" "DAY" "HOUR" "WEEK" "MONTH"
+ TOTAL - metrics are aggregated over the specified date range.
+ DAY - metrics are broken down daily.
+ HOUR - metrics are broken down hourly.
+ WEEKLY - metrics are broken down weekly.
+ MONTHLY - metrics are broken down monthly
+ click_window_days (int, optional):
+ Default: 30
+ Enum: 1 7 30 60
+ Example: click_window_days=1
+ Number of days to use as the conversion attribution window for a pin click action. Applies to Pinterest
+ Tag conversion metrics. Prior conversion tags use their defined attribution windows. If not specified,
+ defaults to 30 days.
+ engagement_window_days (int, optional):
+ Default: 30
+ Enum: 1 7 30 60
+ Number of days to use as the conversion attribution window for an engagement action. Engagements include
+ saves, closeups, link clicks, and carousel card swipes. Applies to Pinterest Tag conversion metrics.
+ Prior conversion tags use their defined attribution windows. If not specified, defaults to 30 days.
+ view_window_days (int, optional):
+ Default: 1
+ Enum: 1 7 30 60
+ Number of days to use as the conversion attribution window for a view action. Applies to Pinterest Tag
+ conversion metrics. Prior conversion tags use their defined attribution windows. If not specified,
+ defaults to 1 day.
+ conversion_report_time (str, optional):
+ Default: "TIME_OF_AD_ACTION"
+ Enum: "TIME_OF_AD_ACTION" "TIME_OF_CONVERSION"
+ Example: conversion_report_time=TIME_OF_AD_ACTION
+ The date by which the conversion metrics returned from this endpoint will be reported. There are two
+ dates associated with a conversion event: the date that the user interacted with the ad, and the date
+ that the user completed a conversion event.
+
+ Returns:
+ AnalyticsResponse: AnalyticsResponse object.
+ """
+ kwargs['ad_account_id'] = self.ad_account_id
+ kwargs['ad_ids'] = [self.id]
+ kwargs['start_date'] = start_date
+ kwargs['end_date'] = end_date
+ kwargs['columns'] = columns
+ kwargs['granularity'] = granularity
+ kwargs['click_window_days'] = click_window_days
+ kwargs['engagement_window_days'] = engagement_window_days
+ kwargs['view_window_days'] = view_window_days
+ kwargs['conversion_report_time'] = conversion_report_time
+
+ return AnalyticsUtils.get_entity_analytics(
+ params=kwargs,
+ api=AdsApi,
+ analytics_fn=AdsApi.ads_analytics,
+ entity=Ad,
+ client=self._client
+ )
+
+ def get_targeting_analytics(
+ self,
+ start_date: date,
+ end_date: date,
+ targeting_types: list[str],
+ columns: list[str],
+ granularity: str,
+ click_window_days: int = 30,
+ engagement_window_days: int = 30,
+ view_window_days: int = 1,
+ conversion_report_time: str = "TIME_OF_AD_ACTION",
+ attribution_types: str = None,
+ **kwargs
+ ) -> AnalyticsResponse:
+ """
+ Get targeting analytics for one or more ads. For the requested ad(s) and metrics, the response will include the
+ requested metric information (e.g. SPEND_IN_DOLLAR) for the requested target type (e.g. "age_bucket") for
+ applicable values (e.g. "45-49").
+ The token's user_account must either be the Owner of the specified ad account, or have one of the necessary
+ roles granted to them via Business Access: Admin, Analyst, Campaign Manager.
+ Args:
+ start_date (date): Metric report start date (UTC). Format: YYYY-MM-DD
+ end_date (date): Metric report end date (UTC). Format: YYYY-MM-DD
+ targeting_types (list[str]): Targeting type breakdowns for the report. The reporting per targeting type
+ is independent from each other.
+ columns (list[str]): Columns to retrieve, encoded as a comma-separated string. NOTE: Any metrics defined as
+ MICRO_DOLLARS returns a value based on the advertiser profile's currency field. For USD,($1/1,000,000,
+ or $0.000001 - one one-ten-thousandth of a cent). it's microdollars. Otherwise, it's in microunits of
+ the advertiser's currency. For example, if the advertiser's currency is GBP (British pound sterling),
+ all MICRO_DOLLARS fields will be in GBP microunits (1/1,000,000 British pound). If a column has no
+ value, it may not be returned
+ granularity (str): Enum: "TOTAL" "DAY" "HOUR" "WEEK" "MONTH"
+ TOTAL - metrics are aggregated over the specified date range.
+ DAY - metrics are broken down daily.
+ HOUR - metrics are broken down hourly.
+ WEEKLY - metrics are broken down weekly.
+ MONTHLY - metrics are broken down monthly
+ click_window_days (int, optional): Default: 30
+ Enum: 1 7 30 60
+ Example: click_window_days=1
+ Number of days to use as the conversion attribution window for a pin click action. Applies to Pinterest
+ Tag conversion metrics. Prior conversion tags use their defined attribution windows. If not specified,
+ defaults to 30 days.
+ engagement_window_days (int, optional): Default: 30
+ Enum: 1 7 30 60
+ Number of days to use as the conversion attribution window for an engagement action. Engagements
+ include saves, closeups, link clicks, and carousel card swipes. Applies to Pinterest Tag conversion
+ metrics. Prior conversion tags use their defined attribution windows. If not specified, defaults to
+ 30 days.
+ view_window_days (int, optional): Default: 1
+ Enum: 1 7 30 60
+ Number of days to use as the conversion attribution window for a view action. Applies to Pinterest Tag
+ conversion metrics. Prior conversion tags use their defined attribution windows. If not specified,
+ defaults to 1 day.
+ conversion_report_time (str, optional): Default: "TIME_OF_AD_ACTION"
+ Enum: "TIME_OF_AD_ACTION" "TIME_OF_CONVERSION"
+ Example: conversion_report_time=TIME_OF_AD_ACTION
+ The date by which the conversion metrics returned from this endpoint will be reported. There are two
+ dates associated with a conversion event: the date that the user interacted with the ad, and the date
+ that the user completed a conversion event.
+ attribution_types (str): Enum: "INDIVIDUAL" "HOUSEHOLD"
+ Example: attribution_types=INDIVIDUAL
+ List of types of attribution for the conversion report
+ Returns:
+ AnalyticsResponse: AnalyticsResponse object.
+ """
+ kwargs['ad_account_id'] = self.ad_account_id
+ kwargs['ad_ids'] = [self.id]
+ kwargs['start_date'] = start_date
+ kwargs['end_date'] = end_date
+ kwargs['targeting_types'] = [AdsAnalyticsTargetingType(targeting_type) for targeting_type in targeting_types]
+ kwargs['columns'] = columns
+ kwargs['granularity'] = granularity
+ kwargs['click_window_days'] = click_window_days
+ kwargs['engagement_window_days'] = engagement_window_days
+ kwargs['view_window_days'] = view_window_days
+ kwargs['conversion_report_time'] = conversion_report_time
+
+ if attribution_types:
+ kwargs['attribution_types'] = ConversionReportAttributionType(attribution_types)
+
+ return AnalyticsUtils.get_entity_analytics(
+ params=kwargs,
+ api=AdsApi,
+ analytics_fn=AdsApi.ad_targeting_analytics_get,
+ entity=Ad,
+ client=self._client
+ )
diff --git a/pinterest/ads/campaigns.py b/pinterest/ads/campaigns.py
index a67c25f..285682f 100644
--- a/pinterest/ads/campaigns.py
+++ b/pinterest/ads/campaigns.py
@@ -3,12 +3,14 @@
"""
from __future__ import annotations
-from openapi_generated.pinterest_client.api.campaigns_api import CampaignsApi
+from datetime import date
+from openapi_generated.pinterest_client.model.conversion_report_attribution_type import ConversionReportAttributionType
+from openapi_generated.pinterest_client.model.ads_analytics_targeting_type import AdsAnalyticsTargetingType
+from openapi_generated.pinterest_client.api.campaigns_api import CampaignsApi
from openapi_generated.pinterest_client.model.campaign_response import CampaignResponse
from openapi_generated.pinterest_client.model.campaign_create_request import CampaignCreateRequest
from openapi_generated.pinterest_client.model.campaign_update_request import CampaignUpdateRequest
-
from openapi_generated.pinterest_client.model.objective_type import ObjectiveType
from pinterest.ads.ad_groups import AdGroup
@@ -16,6 +18,7 @@
from pinterest.utils.error_handling import verify_api_response
from pinterest.utils.base_model import PinterestBaseModel
from pinterest.utils.bookmark import Bookmark
+from pinterest.utils.analytics import AnalyticsUtils, AnalyticsResponse
class Campaign(PinterestBaseModel):
@@ -539,3 +542,180 @@ def list_ad_groups(
order=order, bookmark=bookmark,
**kwargs
)
+
+ def get_analytics(
+ self,
+ start_date: date,
+ end_date: date,
+ columns: list[str],
+ granularity: str,
+ click_window_days: int = 30,
+ engagement_window_days: int = 30,
+ view_window_days: int = 1,
+ conversion_report_time: str = "TIME_OF_AD_ACTION",
+ **kwargs
+ ) -> AnalyticsResponse:
+ """
+ Get analytics for the specified campaigns in the specified ad_account_id, filtered by the specified options.
+
+ - The token's user_account must either be the Owner of the specified ad account, or have one of the necessary
+ roles granted to them via Business Access: Admin, Analyst, Campaign Manager.
+
+ - If granularity is not HOUR, the furthest back you can are allowed to pull data is 914 days before the current
+ date in UTC time and the max time range supported is 186 days.
+
+ - If granularity is HOUR, the furthest back you can are allowed to pull data is 8 days before the current date
+ in UTC time and the max time range supported is 3 days.
+
+ Args:
+ start_date (date): Metric report start date (UTC). Format: YYYY-MM-DD
+ end_date (date): Metric report end date (UTC). Format: YYYY-MM-DD
+ columns (list[str]): Example: columns=SPEND_IN_DOLLAR
+ Columns to retrieve, encoded as a comma-separated string. NOTE: Any metrics defined as MICRO_DOLLARS
+ returns a value based on the advertiser profile's currency field. For USD,($1/1,000,000, or $0.000001
+ - one one-ten-thousandth of a cent). it's microdollars. Otherwise, it's in microunits of the
+ advertiser's currency.
+
+ For example, if the advertiser's currency is GBP (British pound sterling), all MICRO_DOLLARS fields
+ will be in GBP microunits (1/1,000,000 British pound).
+
+ If a column has no value, it may not be returned
+ granularity (str): Enum: "TOTAL" "DAY" "HOUR" "WEEK" "MONTH"
+ TOTAL - metrics are aggregated over the specified date range.
+ DAY - metrics are broken down daily.
+ HOUR - metrics are broken down hourly.
+ WEEKLY - metrics are broken down weekly.
+ MONTHLY - metrics are broken down monthly
+ click_window_days (int, optional): Default: 30
+ Enum: 1 7 30 60
+ Number of days to use as the conversion attribution window for a pin click action. Applies to Pinterest
+ Tag conversion metrics. Prior conversion tags use their defined attribution windows. If not specified,
+ defaults to 30 days.
+ engagement_window_days (int, optional): Default: 30
+ Enum: 1 7 30 60
+ Number of days to use as the conversion attribution window for an engagement action. Engagements include
+ saves, closeups, link clicks, and carousel card swipes. Applies to Pinterest Tag conversion metrics.
+ Prior conversion tags use their defined attribution windows. If not specified, defaults to 30 days.
+ view_window_days (int, optional): Default: 1
+ Enum: 1 7 30 60
+ Number of days to use as the conversion attribution window for a view action. Applies to Pinterest Tag
+ conversion metrics. Prior conversion tags use their defined attribution windows. If not specified,
+ defaults to 1 day.
+ conversion_report_time (str, optional): Default: "TIME_OF_AD_ACTION"
+ Enum: "TIME_OF_AD_ACTION" "TIME_OF_CONVERSION"
+ Example: conversion_report_time=TIME_OF_AD_ACTION
+ The date by which the conversion metrics returned from this endpoint will be reported. There are two
+ dates associated with a conversion event: the date that the user interacted with the ad, and the date
+ that the user completed a conversion event.
+ Returns:
+ AnalyticsResponse: AnalyticsResponse object.
+ """
+ kwargs['ad_account_id'] = self.ad_account_id
+ kwargs['campaign_ids'] = [self.id]
+ kwargs['start_date'] = start_date
+ kwargs['end_date'] = end_date
+ kwargs['columns'] = columns
+ kwargs['granularity'] = granularity
+ kwargs['click_window_days'] = click_window_days
+ kwargs['engagement_window_days'] = engagement_window_days
+ kwargs['view_window_days'] = view_window_days
+ kwargs['conversion_report_time'] = conversion_report_time
+
+ return AnalyticsUtils.get_entity_analytics(
+ params=kwargs,
+ api=CampaignsApi,
+ analytics_fn=CampaignsApi.campaigns_analytics,
+ entity=Campaign,
+ client=self._client
+ )
+
+ def get_targeting_analytics(
+ self,
+ start_date: date,
+ end_date: date,
+ targeting_types: list[str],
+ columns: list[str],
+ granularity: str,
+ click_window_days: int=30,
+ engagement_window_days: int=30,
+ view_window_days: int=1,
+ conversion_report_time: str = "TIME_OF_AD_ACTION",
+ attribution_types: str = None,
+ **kwargs
+ ) -> AnalyticsResponse:
+ """
+ Get targeting analytics for one or more campaigns. For the requested account and metrics, the response will
+ include the requested metric information (e.g. SPEND_IN_DOLLAR) for the requested target type
+ (e.g. "age_bucket") for applicable values (e.g. "45-49").
+
+ - The token's user_account must either be the Owner of the specified ad account, or have one of the necessary
+ roles granted to them via Business Access: Admin, Analyst, Campaign Manager.
+
+ - If granularity is not HOUR, the furthest back you can are allowed to pull data is 914 days before the current
+ date in UTC time and the max time range supported is 186 days.
+
+ - If granularity is HOUR, the furthest back you can are allowed to pull data is 8 days before the current date
+ in UTC time and the max time range supported is 3 days.
+
+ Args:
+ start_date (date): Metric report start date (UTC).
+ end_date (date): Metric report end date (UTC).
+ targeting_types (list[str]): Items Enum: "KEYWORD" "APPTYPE" "GENDER" "LOCATION" "PLACEMENT" "COUNTRY"
+ "TARGETED_INTEREST" "PINNER_INTEREST" "AUDIENCE_INCLUDE" "AUDIENCE_EXCLUDE" "GEO" "AGE_BUCKET" "REGION"
+ Targeting type breakdowns for the report. The reporting per targeting type
+ is independent from each other.
+ columns (list[str]): Columns to retrieve, encoded as a comma-separated string. NOTE: Any metrics defined as
+ MICRO_DOLLARS returns a value based on the advertiser profile's currency field. For USD,($1/1,000,000,
+ or $0.000001 - one one-ten-thousandth of a cent). it's microdollars. Otherwise, it's in microunits of
+ the advertiser'scurrency. For example, if the advertiser's currency is GBP (British pound sterling), all
+ MICRO_DOLLARS fields will be in GBP microunits (1/1,000,000 British pound). If a column has no value,
+ it may not be returned
+ granularity (str): Enum: "TOTAL" "DAY" "HOUR" "WEEK" "MONTH"
+ TOTAL - metrics are aggregated over the specified date range.
+ DAY - metrics are broken down daily.
+ HOUR - metrics are broken down hourly.
+ WEEKLY - metrics are broken down weekly.
+ MONTHLY - metrics are broken down monthly
+ click_window_days (int, optional): Enum: 1 7 30 60. Number of days to use as the conversion attribution
+ window for a pin click action. Applies to Pinterest Tag conversion metrics. Prior conversion tags use
+ their defined attribution windows.. Defaults to 30.
+ engagement_window_days (int, optional): Enum: 1 7 30 60 Number of days to use as the conversion attribution
+ window for an engagement action. Engagements include saves, closeups, link clicks, and carousel card
+ swipes. Applies to Pinterest Tag conversion metrics. Prior conversion tags use their defined attribution
+ windows. Defaults to 30.
+ view_window_days (int, optional): Enum: 1 7 30 60. Number of days to use as the conversion attribution
+ window for a view action. Applies to Pinterest Tag conversion metrics. Prior conversion tags use their
+ defined attribution windows. Defaults to 1.
+ conversion_report_time (str, optional): Enum: "TIME_OF_AD_ACTION" "TIME_OF_CONVERSION". The date by which
+ the conversion metrics returned from this endpoint will be reported. There are two dates associated
+ with a conversion event: the date that the user interacted with the ad, and the date that the user
+ completed a conversion event. Defaults to "TIME_OF_AD_ACTION".
+ attribution_types (str): Enum: "INDIVIDUAL" "HOUSEHOLD"
+ List of types of attribution for the conversion report
+
+ Returns:
+ AnalyticsResponse: AnalyticsResponse object.
+ """
+ kwargs['ad_account_id'] = self.ad_account_id
+ kwargs['campaign_ids'] = [self.id]
+ kwargs['start_date'] = start_date
+ kwargs['end_date'] = end_date
+ kwargs['targeting_types'] = [AdsAnalyticsTargetingType(targeting_type) for targeting_type in targeting_types]
+ kwargs['columns'] = columns
+ kwargs['granularity'] = granularity
+ kwargs['click_window_days'] = click_window_days
+ kwargs['engagement_window_days'] = engagement_window_days
+ kwargs['view_window_days'] = view_window_days
+ kwargs['conversion_report_time'] = conversion_report_time
+ if attribution_types:
+ kwargs['attribution_types'] = ConversionReportAttributionType(attribution_types)
+
+ ad_account_analytics_response = AnalyticsUtils.get_entity_analytics(
+ params=kwargs,
+ api=CampaignsApi,
+ analytics_fn=CampaignsApi.campaign_targeting_analytics_get,
+ entity=Campaign,
+ client=self._client
+ )
+
+ return ad_account_analytics_response
diff --git a/pinterest/organic/boards.py b/pinterest/organic/boards.py
index 0b57eee..7821eb0 100644
--- a/pinterest/organic/boards.py
+++ b/pinterest/organic/boards.py
@@ -205,7 +205,14 @@ def __init__(
client (PinterestSDKClient, optional): PinterestSDKClient Object. Uses the default client, if not provided.
"""
self._id = None
+ self._created_at = None
+ self._board_pins_modified_at = None
self._name = None
+ self._collaborator_count = None
+ self._pin_count = None
+ self._follower_count = None
+ self._media = None
+ self._owner = None
self._description = None
self._owner = None
self._privacy = None
@@ -227,11 +234,41 @@ def id(self) -> str:
# pylint: disable=missing-function-docstring
return self._id
+ @property
+ def created_at(self) -> str:
+ # pylint: disable=missing-function-docstring
+ return self._created_at
+
+ @property
+ def board_pins_modified_at(self) -> str:
+ # pylint: disable=missing-function-docstring
+ return self._board_pins_modified_at
+
@property
def name(self) -> str:
# pylint: disable=missing-function-docstring
return self._name
+ @property
+ def collaborator_count(self) -> int:
+ # pylint: disable=missing-function-docstring
+ return self._collaborator_count
+
+ @property
+ def pin_count(self) -> int:
+ # pylint: disable=missing-function-docstring
+ return self._pin_count
+
+ @property
+ def follower_count(self) -> int:
+ # pylint: disable=missing-function-docstring
+ return self._follower_count
+
+ @property
+ def media(self):
+ # pylint: disable=missing-function-docstring
+ return self._media
+
@property
def description(self) -> str:
# pylint: disable=missing-function-docstring
diff --git a/pinterest/organic/pins.py b/pinterest/organic/pins.py
index 5e7829c..53c11c1 100644
--- a/pinterest/organic/pins.py
+++ b/pinterest/organic/pins.py
@@ -3,6 +3,8 @@
"""
from __future__ import annotations
+from datetime import date
+
from openapi_generated.pinterest_client.api.pins_api import PinsApi
from openapi_generated.pinterest_client.model.pin import Pin as GeneratedPin
from openapi_generated.pinterest_client.model.pin_create import PinCreate as GeneratedPinCreate
@@ -11,6 +13,8 @@
from pinterest.client import PinterestSDKClient
from pinterest.utils.base_model import PinterestBaseModel
from pinterest.utils.error_handling import verify_api_response
+from pinterest.utils.analytics import AnalyticsResponse, AnalyticsUtils
+
class Pin(PinterestBaseModel):
"""
@@ -41,8 +45,12 @@ def __init__(
client (PinterestSDKClient, optional): PinterestSDKClient Object. Uses the default client, if not provided.
"""
self._ad_account_id = None
-
self._id = None
+ self._is_owner = None
+ self._is_standard = None
+ self._note = None
+ self._has_been_promoted = None
+ self._creative_type = None
self._created_at = None
self._link = None
self._title = None
@@ -75,6 +83,31 @@ def id(self) -> str:
# pylint: disable=missing-function-docstring
return self._id
+ @property
+ def is_owner(self) -> str:
+ # pylint: disable=missing-function-docstring
+ return self._is_owner
+
+ @property
+ def is_standard(self) -> str:
+ # pylint: disable=missing-function-docstring
+ return self._is_standard
+
+ @property
+ def note(self) -> str:
+ # pylint: disable=missing-function-docstring
+ return self._note
+
+ @property
+ def has_been_promoted(self) -> str:
+ # pylint: disable=missing-function-docstring
+ return self._has_been_promoted
+
+ @property
+ def creative_type(self) -> str:
+ # pylint: disable=missing-function-docstring
+ return self._creative_type
+
@property
def created_at(self) -> str:
# pylint: disable=missing-function-docstring
@@ -276,3 +309,62 @@ def save(
verify_api_response(api_response)
self._populate_fields(_model_data=api_response.to_dict())
+
+ def get_analytics(
+ self,
+ start_date: date,
+ end_date: date,
+ app_types: str = "ALL",
+ metric_types: list[str] = None,
+ split_field: str = None,
+ **kwargs
+ ) -> AnalyticsResponse:
+ """
+ Get analytics for a Pin owned by the "operation user_account" - or on a group board that has been shared with
+ this account.
+
+ - By default, the "operation user_account" is the token user_account.
+
+ Optional: Business Access: Specify an ad_account_id (obtained via List ad accounts) to use the owner of that
+ ad_account as the "operation user_account". In order to do this, the token user_account must have one of the
+ following Business Access roles on the ad_account:
+
+ - For Pins on public or protected boards: Admin, Analyst.
+ - For Pins on secret boards: Admin.
+
+ Args:
+ start_date (date): Metric report start date (UTC). Format: YYYY-MM-DD
+ end_date (date): Metric report end date (UTC). Format: YYYY-MM-DD
+ app_types (str): Default: "ALL"
+ Enum: "ALL" "MOBILE" "TABLET" "WEB"
+ Apps or devices to get data for, default is all.
+ metric_types (list[str], str):
+ - Standard Pin metric types: "OUTBOUND_CLICK" "PIN_CLICK" "IMPRESSION" "SAVE" "SAVE_RATE"
+ - Video Pin metric types :"OUTBOUND_CLICK" "IMPRESSION" "SAVE" "QUARTILE_95_PERCENT_VIEW"
+ "VIDEO_10S_VIEW" "VIDEO_AVG_WATCH_TIME" "VIDEO_MRC_VIEW" "VIDEO_START" "VIDEO_V50_WATCH_TIME"
+ Pin metric types to get data for, default is all.
+ split_field (str): Default: "NO_SPLIT"
+ Enum: "NO_SPLIT" "APP_TYPE"
+ How to split the data into groups. Not including this param means data won't be split.
+ Returns:
+ AnalyticsResponse: AnalyticsResponse object.
+ """
+ kwargs['pin_id'] = self._id
+ if self._ad_account_id:
+ kwargs['ad_account_id'] = self._ad_account_id
+ if app_types:
+ kwargs['app_types'] = app_types
+ if split_field:
+ kwargs['split_field'] = split_field
+
+ kwargs['start_date'] = start_date
+ kwargs['end_date'] = end_date
+ kwargs['metric_types'] = metric_types
+
+ return AnalyticsUtils.get_entity_analytics(
+ params=kwargs,
+ api=PinsApi,
+ analytics_fn=PinsApi.pins_analytics,
+ entity=Pin,
+ client=self._client
+ )
diff --git a/pinterest/utils/analytics.py b/pinterest/utils/analytics.py
new file mode 100644
index 0000000..eb79342
--- /dev/null
+++ b/pinterest/utils/analytics.py
@@ -0,0 +1,82 @@
+"""
+Analytics Class for Pinterest Python SDK
+"""
+from __future__ import annotations
+from typing import Callable
+
+from pinterest.utils.base_model import PinterestBaseModel
+
+from pinterest.client import PinterestSDKClient
+
+
+class AnalyticsUtils:
+ """
+ Utility class with functions to make model specific analytics api calls.
+ """
+ @classmethod
+ def get_entity_analytics(
+ cls,
+ params: list,
+ api: type,
+ analytics_fn: Callable,
+ entity: PinterestBaseModel,
+ client: PinterestSDKClient = None,
+ **kwargs
+ ) -> AnalyticsResponse:
+ """
+ Helper function used to get ad entity analytics.
+ Args:
+ params (list): List of params
+ api (type):
+ analytics_fn (Callable):
+ entity (PinterestBaseModel):
+ client (PinterestSDKClient, optional):
+ Returns:
+ AnalyticsResponse:
+ """
+
+ return AnalyticsResponse(
+ entity_type=entity,
+ fields=params.get('columns', []),
+ raw_response=getattr(api(client), analytics_fn.__name__)(**params, **kwargs)
+ )
+
+
+class AnalyticsResponse():
+ """
+ AnalyticsResponse model
+ """
+ def __init__(
+ self,
+ entity_type:PinterestBaseModel,
+ fields:list[str],
+ raw_response:dict,
+ ) -> None:
+ """
+ Initialize an Ads Analytics object.
+ Args:
+ entity_type (PinterestBaseModel): Entity Type identifier. Enum: ad_account, campaign, ad_group, ad.
+ fields (list[str]): _description_
+ raw_response (dict): _description_
+ """
+ self._entity_type = entity_type.__name__.lower()
+ self._fields = fields
+ self._raw_response = raw_response
+
+ @property
+ def entity_type(self) -> str:
+ # pylint: disable=missing-function-docstring
+ return self._entity_type
+
+ @property
+ def fields(self) -> list[str]:
+ # pylint: disable=missing-function-docstring
+ return self._fields
+
+ @property
+ def raw_response(self) -> dict:
+ # pylint: disable=missing-function-docstring
+ return self._raw_response
+
+ def __str__(self) -> str:
+ return f"{self.raw_response}"
diff --git a/pinterest/utils/validations.py b/pinterest/utils/validations.py
new file mode 100644
index 0000000..d35315c
--- /dev/null
+++ b/pinterest/utils/validations.py
@@ -0,0 +1,7 @@
+from enum import Enum
+
+class AdsEntityType(Enum):
+ AD_ACCOUNT = "ad_account"
+ CAMPAIGN = "campaign"
+ ADGROUP = "ad_group"
+ AD = "ad"
diff --git a/requirements.txt b/requirements.txt
index 101fee2..b998c63 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-Pinterest-Generated-Client==0.1.7
+Pinterest-Generated-Client==0.1.8
python-dateutil==2.8.2
six==1.16.0
urllib3==1.26.12
diff --git a/setup.py b/setup.py
index edadc0c..1fc0703 100644
--- a/setup.py
+++ b/setup.py
@@ -25,10 +25,10 @@ def _get_prod_version():
"python-dateutil",
"python-dotenv==0.20.0",
"six==1.16.0",
- "Pinterest-Generated-Client==0.1.7"
+ "Pinterest-Generated-Client==0.1.8"
]
-long_description = (Path(__file__).parent / "README.md").read_text()
+long_description = (Path(__file__).parent / "README.md").read_text(encoding='UTF-8')
package_root = os.path.abspath(os.path.dirname(__file__))
__version__ = None
diff --git a/tests/src/pinterest/ads/test_ad_groups.py b/tests/src/pinterest/ads/test_ad_groups.py
index af61403..8e9935f 100644
--- a/tests/src/pinterest/ads/test_ad_groups.py
+++ b/tests/src/pinterest/ads/test_ad_groups.py
@@ -8,6 +8,7 @@
from openapi_generated.pinterest_client.model.ad_group_response import AdGroupResponse
from openapi_generated.pinterest_client.model.ad_group_array_response import AdGroupArrayResponse
from openapi_generated.pinterest_client.model.ad_group_array_response_element import AdGroupArrayResponseElement
+from openapi_generated.pinterest_client.model.targeting_spec import TargetingSpec
from pinterest.ads.ad_groups import AdGroup
from pinterest.ads.ads import Ad
@@ -101,9 +102,7 @@ def test_update_ad_group(self, get_mock, update_mock):
"""
update_mock.__name__ = "ad_groups_update"
new_name = "SDK_AD_GROUP_NEW_NAME"
- new_spec = {
- "GENDER": ["male"]
- }
+ new_spec = TargetingSpec(gender=["male"])
get_mock.return_value = AdGroupResponse(
id=self.test_ad_group_id,