Skip to content

Commit 0291498

Browse files
authored
Merge pull request #57 from twistedFantasy/main
Support Amazon DSP API - Reports entity
2 parents 27f7eb1 + 167bca9 commit 0291498

20 files changed

Lines changed: 272 additions & 101 deletions

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,9 @@ curl \
305305
* Bid Recommendations
306306
* Creatives
307307

308+
### Modules Available DSP
309+
310+
* Reports
308311

309312
### Simple Example Usage Campaigns with Credentials
310313

ad_api/api/dsp/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from .reports import Reports
2+
from .client import DspClient
3+
from .access_token_client import DspAccessTokenClient
4+
from .credential_provider import DspCredentialProvider
5+
6+
__all__ = [
7+
"Reports",
8+
"DspClient",
9+
"DspAccessTokenClient",
10+
"DspCredentialProvider"
11+
]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from ad_api.api.dsp.credential_provider import DspCredentialProvider
2+
from ad_api.auth.access_token_client import AccessTokenClient
3+
4+
5+
class DspAccessTokenClient(AccessTokenClient):
6+
credential_provider_class = DspCredentialProvider

ad_api/api/dsp/client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from ad_api.base import Client
2+
from ad_api.auth.credentials import BaseCredentials
3+
from ad_api.api.dsp.credential_provider import DspCredentialProvider
4+
from ad_api.api.dsp.access_token_client import DspAccessTokenClient
5+
6+
7+
class DspClient(Client):
8+
access_token_client_class = DspAccessTokenClient
9+
credentials_class = BaseCredentials
10+
credential_provider_class = DspCredentialProvider
11+
12+
@property
13+
def headers(self):
14+
return {
15+
'User-Agent': self.user_agent,
16+
'Amazon-Advertising-API-ClientId': self.credentials.client_id,
17+
'Authorization': 'Bearer %s' % self.auth.access_token,
18+
'Content-Type': 'application/json',
19+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from ad_api.base.config import BaseConfig
2+
from ad_api.base.credential_provider import CredentialProvider
3+
4+
5+
class DspCredentialProvider(CredentialProvider):
6+
config_class = BaseConfig
7+
8+
def from_env(self):
9+
account_data = dict(
10+
refresh_token=self._get_env('AD_API_REFRESH_TOKEN'),
11+
client_id=self._get_env('AD_API_CLIENT_ID'),
12+
client_secret=self._get_env('AD_API_CLIENT_SECRET'),
13+
)
14+
self.credentials = self.config_class(**account_data)
15+
return len(self.credentials.check_config()) == 0

ad_api/api/dsp/reports.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from ad_api.api.dsp.client import DspClient as Client
2+
from ad_api.base import sp_endpoint, fill_query_params, ApiResponse
3+
4+
5+
class Reports(Client):
6+
"""Amazon DSP Reports
7+
8+
Documentation: https://advertising.amazon.com/API/docs/en-us/dsp-reports-beta-3p/#/Reports
9+
10+
Amazon DSP is a demand-side platform (DSP) that enables advertisers to programmatically buy display, video, and audio ads both on and off Amazon. Using Amazon's DSP, you can reach audiences across the web on both Amazon sites and apps as well as through our publishing partners and third-party exchanges. Amazon DSP is available to both advertisers who sell products on Amazon and those who do not.
11+
"""
12+
13+
@sp_endpoint('/accounts/{}/dsp/reports', method='POST')
14+
def post_report(self, dspAccountId, accept='application/vnd.dspcreatereports.v3+json', **kwargs) -> ApiResponse:
15+
r"""
16+
Request reports with performance metrics for DSP campaigns.
17+
18+
Request creation of a report that includes metrics about your Amazon DSP campaigns. Specify the type of report and the metrics you'd like to include. Note that the value specified for the dimensions field affects the metrics included in the report. See the dimensions field description for more information.
19+
20+
Keyword Args
21+
| path **dspAccountId** (string): Account Identifier you use to access the DSP. This is your DSP Entity ID if you have access to all DSP advertisers within that entity, or your DSP Advertiser ID if you only have access to a specific advertiser ID. [required]
22+
23+
Request body
24+
| **advertiserIds** (list > string): [optional] List of advertisers specified by identifier to include in the report. This should not be present if accountId is advertiser.
25+
| **endDate** (string): [required] Date in yyyy-MM-dd format. The report contains only metrics generated on the specified date range between startDate and endDate. The maximum date range between startDate and endDate is 31 days. The endDate can be up to 90 days older from today.
26+
| **format** (string): [optional] Enum: The report file format. [JSON]
27+
| **orderIds** (list > string): [optional] List of orders specified by identifier to include in the report.
28+
| **metrics** (list > string): [optional] Specify a list of metrics field names to include in the report. For example: ["impressions", "clickThroughs", "CTR", "eCPC", "totalCost", "eCPM"]. If no metric field names are specified, only the default fields and selected DIMENSION fields are included by default. Specifying default fields returns an error.
29+
| **type** (string): [optional] Enum: The report type. [CAMPAIGN]
30+
| **startDate** (string): [required] Date in yyyy-MM-dd format. The report contains only metrics generated on the specified date range between startDate and endDate. The maximum date range between startDate and endDate is 31 days. The startDate can be up to 90 days older from today.
31+
| **dimensions** (list > string): [optional] List of dimensions to include in the report. Specify one or many comma-delimited strings of dimensions. For example: ["ORDER", "LINE_ITEM", "CREATIVE"]. Adding a dimension in this array determines the aggregation level of the report data and also adds the fields for that dimension in the report. If the list is null or empty, the aggregation of the report data is at ORDER level. The allowed values can be used together in this array as an allowed value in which case the report aggregation will be at the lowest aggregation level and the report will contain the fields for all the dimensions included in the report.
32+
| **timeUnit** (string): [optional] Enum: Adding timeUnit determines the aggregation level (SUMMARY or DAILY) of the report data. If the timeUnit is null or empty, the aggregation of the report data is at the SUMMARY level and aggregated at the time period specified. DAILY timeUnit is not supported for AUDIENCE report type. The report will contain the fields based on timeUnit. [SUMMARY]
33+
34+
Returns:
35+
ApiResponse
36+
"""
37+
path = fill_query_params(kwargs.pop('path'), dspAccountId)
38+
return self._request(path, data=kwargs.pop('body'), headers={'Accept': accept}, params=kwargs)
39+
40+
@sp_endpoint('/accounts/{}/dsp/reports/{}', method='GET')
41+
def get_report(self, dspAccountId, reportId, accept='application/vnd.dspgetreports.v3+json', **kwargs) -> ApiResponse:
42+
r"""
43+
Gets a previously requested report specified by identifier.
44+
45+
Keyword Args
46+
| path **dspAccountId** (string): Account Identifier for DSP. Please input DSP entity ID if you want to retrieve reports for a group of advertisers, or input DSP advertiser ID if you want to retrieve reports for a single advertiser. [required]
47+
| path **reportId** (string): The identifier of the requested report. [required]
48+
49+
Returns:
50+
ApiResponse
51+
"""
52+
path = fill_query_params(kwargs.pop('path'), dspAccountId, reportId)
53+
return self._request(path, headers={'Accept': accept}, params=kwargs)
54+
55+
def download_report(self, **kwargs) -> ApiResponse:
56+
r"""
57+
Downloads the report previously get report specified by location (this is not part of the official Amazon Advertising API, is a helper method to download the report). Take in mind that a direct download of location returned in get_report will return 401 - Unauthorized.
58+
59+
kwarg parameter **file** if not provided will take the default amazon name from path download (add a path with slash / if you want a specific folder, do not add extension as the return will provide the right extension based on format choosed if needed)
60+
61+
kwarg parameter **format** if not provided a format will return a url to download the report (this url has a expiration time)
62+
63+
Keyword Args
64+
| **url** (string): The location obatined from get_report [required]
65+
| **file** (string): The path to save the file if mode is download json, zip or gzip. [optional]
66+
| **format** (string): The mode to download the report: data (list), raw, url, json, zip, gzip. Default (url) [optional]
67+
68+
Returns:
69+
ApiResponse
70+
"""
71+
return self._download(self, params=kwargs, headers={'User-Agent': self.user_agent})

ad_api/auth/access_token_client.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import json
2-
import os
31
import requests
42
import hashlib
53
import logging
@@ -21,9 +19,9 @@ class AccessTokenClient(BaseClient):
2119
grant_type = 'refresh_token'
2220
path = '/auth/o2/token'
2321

24-
def __init__(self, account='default', credentials=None):
22+
def __init__(self, account='default', credentials=None, credentials_class=Credentials):
2523
super().__init__(account, credentials)
26-
self.cred = Credentials(self.credentials)
24+
self.cred = credentials_class(self.credentials)
2725

2826
def _request(self, url, data, headers):
2927
response = requests.post(url, data=data, headers=headers)
@@ -57,7 +55,6 @@ def get_auth(self) -> AccessTokenResponse:
5755
cache[cache_key] = access_token
5856
return AccessTokenResponse(**access_token)
5957

60-
6158
def authorize_auth_code(self, auth_code):
6259
request_url = self.scheme + self.host + self.path
6360
res = self._request(

ad_api/auth/credentials.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import os
2-
3-
class Credentials:
1+
class BaseCredentials:
42
def __init__(self, credentials):
53
self.client_id = credentials.client_id
64
self.client_secret = credentials.client_secret
75
self.refresh_token = credentials.refresh_token
6+
7+
8+
class Credentials(BaseCredentials):
9+
def __init__(self, credentials):
10+
super().__init__(credentials)
811
self.profile_id = credentials.profile_id

ad_api/base/base_client.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
from ad_api.base.credential_provider import CredentialProvider
21
import ad_api.version as vd
2+
from ad_api.base.credential_provider import CredentialProvider
3+
34

45
class BaseClient:
56
scheme = 'https://'
67
method = 'GET'
78
content_type = 'application/x-www-form-urlencoded;charset=UTF-8'
89
user_agent = 'python-ad-api'
9-
10+
credential_provider_class = CredentialProvider
1011

1112
def __init__(self, account='default', credentials=None):
1213
try:
1314
version = vd.__version__
1415
self.user_agent += f'-{version}'
1516
except:
1617
pass
17-
self.credentials = CredentialProvider(account, credentials).credentials
18+
self.credentials = self.credential_provider_class(account, credentials).credentials

ad_api/base/client.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import json
2-
from datetime import datetime
32
import logging
43
from cachetools import TTLCache
54
from requests import request
5+
from ad_api.auth.credentials import Credentials
66
from ad_api.auth import AccessTokenClient, AccessTokenResponse
77
from .api_response import ApiResponse
88
from .base_client import BaseClient
9-
from .exceptions import get_exception_for_code, get_exception_for_content, AdvertisingApiBadRequestException
9+
from .exceptions import get_exception_for_content
1010
from .marketplaces import Marketplaces
1111
import sys
1212
import os
@@ -23,6 +23,8 @@
2323

2424

2525
class Client(BaseClient):
26+
access_token_client_class = AccessTokenClient
27+
credentials_class = Credentials
2628
grantless_scope = ''
2729

2830
def __init__(
@@ -37,7 +39,9 @@ def __init__(
3739
super().__init__(account, credentials)
3840
self.endpoint = marketplace.endpoint
3941
self.debug = debug
40-
self._auth = AccessTokenClient(account=account, credentials=credentials)
42+
self._auth = self.access_token_client_class(
43+
account=account, credentials=credentials, credentials_class=self.credentials_class
44+
)
4145

4246
@property
4347
def headers(self):

0 commit comments

Comments
 (0)