diff --git a/.argo-ci/master.yaml b/.argo-ci/master.yaml
new file mode 100644
index 0000000..4d36d7a
--- /dev/null
+++ b/.argo-ci/master.yaml
@@ -0,0 +1,255 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+ generateName: amazon-advertising-api-python-master-
+spec:
+ # entrypoint is the name of the template used as the starting point of the workflow
+ entrypoint: run-check
+ onExit: exit-handler
+ arguments:
+ parameters:
+ - name: git_url
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+ - name: repo_name
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+ - name: commit_message
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+ - name: sha
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+ # a temporary volume, named workdir, will be used as a working directory
+ # for this workflow. This volume is passed around from step to step.
+ volumeClaimTemplates:
+ - metadata:
+ name: workdir
+ spec:
+ accessModes: [ "ReadWriteOnce" ]
+ resources:
+ requests:
+ storage: 1Gi
+ volumes:
+ - name: sshkey
+ secret:
+ secretName: argo-ci-git-ssh-key
+ items:
+ - key: id_rsa
+ mode: 0400
+ path: id_rsa
+ - name: known-hosts
+ secret:
+ secretName: known-github
+ - name: pypirc
+ secret:
+ secretName: nexus-pypi
+ items:
+ - key: .pypirc
+ path: .pypirc
+
+
+
+ templates:
+ - name: run-check
+ steps:
+ - - name: check-commit-messsage
+ template: check-message-script
+
+ - - name: run-build
+ template: run-build
+ when: "{{steps.check-commit-messsage.outputs.result}} != skip"
+
+
+ - name: run-build
+ steps:
+ - - name: set-pending
+ template: status-update
+ arguments:
+ parameters:
+ - name: status
+ value: pending
+ - name: message
+ value: Argo CI tests has been started
+
+ - - name: get-package-name
+ template: generate-name
+
+ - - name: checkout
+ template: git
+ arguments:
+ parameters:
+ - name: cmd
+ value: >
+ cd /workdir &&
+ git clone {{workflow.parameters.git_url}} &&
+ cd /workdir/{{workflow.parameters.repo_name}} &&
+ git checkout master
+
+ - - name: alpha-version-bump
+ template: python
+ arguments:
+ parameters:
+ - name: cmd
+ value: >
+ pip install bump2version &&
+ git config --global user.email 'argo-ci@pepsi.co' &&
+ git config --global user.name 'Argo CI' &&
+ bump2version patch
+ --verbose
+ --allow-dirty
+ --no-commit
+
+ - - name: pack-and-push
+ template: python
+ arguments:
+ parameters:
+ - name: cmd
+ value: >
+ pip install twine &&
+ python3 setup.py sdist bdist_wheel &&
+ twine upload --config-file /etc/pypirc/.pypirc -r pepsico-ecommerce-dev dist/*
+
+ - - name: version-bump
+ template: python
+ arguments:
+ parameters:
+ - name: cmd
+ value: >
+ pip install bump2version &&
+ git config --global user.email 'argo-ci@pepsi.co' &&
+ git config --global user.name 'Argo CI' &&
+ bump2version release
+ --serialize {major}.{minor}.{patch}
+ --verbose
+ --allow-dirty
+
+ - - name: git-push
+ template: git
+ arguments:
+ parameters:
+ - name: cmd
+ value: >
+ cd /workdir/{{workflow.parameters.repo_name}} &&
+ git push origin master
+
+
+
+ - name: exit-handler
+ steps:
+ - - name: success
+ template: status-update
+ arguments:
+ parameters:
+ - name: status
+ value: success
+ - name: message
+ value: Version bumped and package deployed.
+ when: "{{workflow.status}} == Succeeded"
+ - name: failure
+ template: status-update
+ arguments:
+ parameters:
+ - name: status
+ value: failure
+ - name: message
+ value: Something went wrong, check logs in details.
+ when: "{{workflow.status}} != Succeeded"
+
+ - name: git
+ inputs:
+ parameters:
+ - name: cmd
+ container:
+ image: "alpine/git"
+ command: ["sh", "-c"]
+ args:
+ - >
+ git config --global user.email 'argo-ci@pepsi.co' &&
+ git config --global user.name 'Argo CI' &&
+ {{inputs.parameters.cmd}}
+ volumeMounts:
+ - name: workdir
+ mountPath: /workdir
+ - name: sshkey
+ mountPath: /root/.ssh
+ - mountPath: /etc/ssh
+ name: known-hosts
+
+ - name: python
+ inputs:
+ parameters:
+ - name: cmd
+ container:
+ image: "python:3.7-buster"
+ command: ["sh", "-c"]
+ args:
+ - >
+ cd /workdir/{{workflow.parameters.repo_name}} &&
+ {{inputs.parameters.cmd}}
+ volumeMounts:
+ - name: workdir
+ mountPath: /workdir
+ - name: pypirc
+ mountPath: /etc/pypirc/
+ env:
+ - name: NEXUS_PYPI_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: nexus-pypi-ro-creds
+ key: password
+ - name: NEXUS_PYPI_USERNAME
+ valueFrom:
+ secretKeyRef:
+ name: nexus-pypi-ro-creds
+ key: username
+
+ - name: status-update
+ inputs:
+ parameters:
+ - name: status
+ - name: message
+ container:
+ image: cloudposse/github-status-updater
+ env:
+ - name: GITHUB_ACTION
+ value: update_state
+ - name: GITHUB_TOKEN
+ valueFrom:
+ secretKeyRef:
+ name: github-access-token
+ key: token
+ - name: GITHUB_OWNER
+ value: pepsico-ecommerce
+ - name: GITHUB_REPO
+ value: "{{workflow.parameters.repo_name}}"
+ - name: GITHUB_REF
+ value: "{{workflow.parameters.sha}}"
+ - name: GITHUB_CONTEXT
+ value: Dev package published
+ - name: GITHUB_STATE
+ value: "{{inputs.parameters.status}}"
+ - name: GITHUB_DESCRIPTION
+ value: "{{inputs.parameters.message}}"
+ - name: GITHUB_TARGET_URL
+ value: "https://argo-wf.pepstaging.com/workflows/argo-events/{{workflow.name}}"
+
+ - name: check-message-script
+ script:
+ image: python:alpine3.7
+ command: [python]
+ source: |
+ message = """
+ {{workflow.parameters.commit_message}}
+ """
+ if "Bump version:" in message:
+ print("skip")
+ else:
+ print("Building")
+
+ - name: generate-name
+ script:
+ image: python:alpine3.7
+ command: [python]
+ source: |
+ repo_name = """
+ {{workflow.parameters.repo_name}}
+ """
+ package_name = repo_name.strip().replace('"', '').replace("'", "").replace('-', '_')
+ print(package_name)
diff --git a/.argo-ci/pr.yaml b/.argo-ci/pr.yaml
new file mode 100644
index 0000000..60a510f
--- /dev/null
+++ b/.argo-ci/pr.yaml
@@ -0,0 +1,254 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+ generateName: amazon-advertising-api-python-pr-
+spec:
+ # entrypoint is the name of the template used as the starting point of the workflow
+ entrypoint: run-tests
+ onExit: exit-handler
+ arguments:
+ parameters:
+ - name: git_url
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+ - name: source_branch
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+ - name: target_branch
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+ - name: repo_name
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+ - name: source_sha
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+ - name: pr_number
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+ # a temporary volume, named workdir, will be used as a working directory
+ # for this workflow. This volume is passed around from step to step.
+ volumeClaimTemplates:
+ - metadata:
+ name: workdir
+ spec:
+ accessModes: [ "ReadWriteOnce" ]
+ resources:
+ requests:
+ storage: 1Gi
+ volumes:
+ - name: sshkey
+ secret:
+ secretName: argo-ci-git-ssh-key
+ items:
+ - key: id_rsa
+ mode: 0400
+ path: id_rsa
+ - name: known-hosts
+ secret:
+ secretName: known-github
+ - name: pypirc
+ secret:
+ secretName: nexus-pypi
+ items:
+ - key: .pypirc
+ path: .pypirc
+
+
+
+ templates:
+ - name: run-tests
+ steps:
+ - - name: set-pending
+ template: status-update
+ arguments:
+ parameters:
+ - name: status
+ value: pending
+ - name: message
+ value: Argo CI tests has been started
+
+ - - name: checkout
+ template: git
+ arguments:
+ parameters:
+ - name: cmd
+ value: >
+ cd /workdir &&
+ git clone {{workflow.parameters.git_url}} &&
+ cd {{workflow.parameters.repo_name}} &&
+ git fetch &&
+ git merge origin/{{workflow.parameters.source_branch}}
+
+ - - name: get-package-name
+ template: generate-name
+
+ - - name: version-bump
+ template: python
+ arguments:
+ parameters:
+ - name: cmd
+ value: >
+ pip install bump2version &&
+ git config --global user.email 'argo-ci@pepsi.co' &&
+ git config --global user.name 'Argo CI' &&
+ bump2version release --allow-dirty --verbose
+ --serialize 0.0.0-dev{{workflow.parameters.source_sha}} &&
+ python setup.py -V > /version
+
+ - - name: pack-and-push
+ template: python
+ arguments:
+ parameters:
+ - name: cmd
+ value: >
+ pip install twine &&
+ python3 setup.py sdist bdist_wheel &&
+ twine upload --verbose --config-file /etc/pypirc/.pypirc -r pepsico-ecommerce-dev dist/*
+
+ - - name: comment-pr
+ template: PR-comment
+ arguments:
+ parameters:
+ - name: message
+ value: "Looks good to me! and your package is here \
+ {{steps.version-bump.outputs.parameters.version}}"
+
+
+
+ - name: exit-handler
+ steps:
+ - - name: success
+ template: status-update
+ arguments:
+ parameters:
+ - name: status
+ value: success
+ - name: message
+ value: All tests has passed in argo ci
+ when: "{{workflow.status}} == Succeeded"
+ - name: failure
+ template: status-update
+ arguments:
+ parameters:
+ - name: status
+ value: failure
+ - name: message
+ value: Some tests has failed, check details for logs
+ when: "{{workflow.status}} != Succeeded"
+
+ - name: git
+ inputs:
+ parameters:
+ - name: cmd
+ container:
+ image: "alpine/git"
+ command: ["sh", "-c"]
+ args:
+ - >
+ git config --global user.email 'argo-ci@pepsi.co' &&
+ git config --global user.name 'Argo CI' &&
+ {{inputs.parameters.cmd}}
+ volumeMounts:
+ - name: workdir
+ mountPath: /workdir
+ - name: sshkey
+ mountPath: /root/.ssh
+ - mountPath: /etc/ssh
+ name: known-hosts
+
+ - name: python
+ inputs:
+ parameters:
+ - name: cmd
+ outputs:
+ parameters:
+ - name: version # name of output parameter
+ valueFrom:
+ path: /version
+ container:
+ image: "python:3.7-buster"
+ command: ["sh", "-c"]
+ args:
+ - >
+ touch /version &&
+ cd /workdir/{{workflow.parameters.repo_name}} &&
+ {{inputs.parameters.cmd}}
+ volumeMounts:
+ - name: workdir
+ mountPath: /workdir
+ - name: pypirc
+ mountPath: /etc/pypirc/
+ env:
+ - name: NEXUS_PYPI_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: nexus-pypi-ro-creds
+ key: password
+ - name: NEXUS_PYPI_USERNAME
+ valueFrom:
+ secretKeyRef:
+ name: nexus-pypi-ro-creds
+ key: username
+
+ - name: status-update
+ inputs:
+ parameters:
+ - name: status
+ - name: message
+ container:
+ image: cloudposse/github-status-updater
+ env:
+ - name: GITHUB_ACTION
+ value: update_state
+ - name: GITHUB_TOKEN
+ valueFrom:
+ secretKeyRef:
+ name: github-access-token
+ key: token
+ - name: GITHUB_OWNER
+ value: pepsico-ecommerce
+ - name: GITHUB_REPO
+ value: "{{workflow.parameters.repo_name}}"
+ - name: GITHUB_REF
+ value: "{{workflow.parameters.source_sha}}"
+ - name: GITHUB_CONTEXT
+ value: Argo CI Workflow
+ - name: GITHUB_STATE
+ value: "{{inputs.parameters.status}}"
+ - name: GITHUB_DESCRIPTION
+ value: "{{inputs.parameters.message}}"
+ - name: GITHUB_TARGET_URL
+ value: "https://argo-wf.pepstaging.com/workflows/argo-events/{{workflow.name}}"
+
+ - name: PR-comment
+ inputs:
+ parameters:
+ - name: message
+ container:
+ image: cloudposse/github-commenter
+ env:
+ - name: GITHUB_TOKEN
+ valueFrom:
+ secretKeyRef:
+ name: github-access-token
+ key: token
+ - name: GITHUB_OWNER
+ value: pepsico-ecommerce
+ - name: GITHUB_REPO
+ value: "{{workflow.parameters.repo_name}}"
+ - name: GITHUB_COMMENT_TYPE
+ value: pr
+ - name: GITHUB_PR_ISSUE_NUMBER
+ value: "{{workflow.parameters.pr_number}}"
+ - name: GITHUB_COMMENT_FORMAT
+ value: "Argo CI comment: {{.}}"
+ - name: GITHUB_COMMENT
+ value: "{{inputs.parameters.message}}"
+ - name: GITHUB_DELETE_COMMENT_REGEX
+ value: "^Argo CI comment"
+
+ - name: generate-name
+ script:
+ image: python:alpine3.7
+ command: [python]
+ source: |
+ repo_name = """
+ {{workflow.parameters.repo_name}}
+ """
+ package_name = repo_name.strip().replace('"', '').replace("'", "").replace('-', '_')
+ print(package_name)
diff --git a/.argo-ci/release.yaml b/.argo-ci/release.yaml
new file mode 100644
index 0000000..76a660b
--- /dev/null
+++ b/.argo-ci/release.yaml
@@ -0,0 +1,154 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+ generateName: amazon-advertising-api-python-release-
+spec:
+ # entrypoint is the name of the template used as the starting point of the workflow
+ entrypoint: run-build
+ # onExit: exit-handler
+ arguments:
+ parameters:
+ - name: git_url
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+ - name: tag_name
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+ - name: repo_name
+ value: HAS_TO_BE_PASSED_FROM_SENSOR
+
+ # a temporary volume, named workdir, will be used as a working directory
+ # for this workflow. This volume is passed around from step to step.
+ volumeClaimTemplates:
+ - metadata:
+ name: workdir
+ spec:
+ accessModes: [ "ReadWriteOnce" ]
+ resources:
+ requests:
+ storage: 1Gi
+ volumes:
+ - name: sshkey
+ secret:
+ secretName: argo-ci-git-ssh-key
+ items:
+ - key: id_rsa
+ mode: 0400
+ path: id_rsa
+ - name: known-hosts
+ secret:
+ secretName: known-github
+ - name: pypirc
+ secret:
+ secretName: nexus-pypi
+ items:
+ - key: .pypirc
+ path: .pypirc
+
+
+
+ templates:
+ - name: run-build
+ steps:
+ - - name: checkout
+ template: git
+ arguments:
+ parameters:
+ - name: cmd
+ value: >
+ cd /workdir &&
+ git clone {{workflow.parameters.git_url}} &&
+ cd /workdir/{{workflow.parameters.repo_name}} &&
+ git checkout tags/{{workflow.parameters.tag_name}}
+
+ - - name: compare-version
+ template: python
+ arguments:
+ parameters:
+ - name: cmd
+ value: >
+ if [ $(python setup.py --version) = {{workflow.parameters.tag_name}} ];
+ then
+ echo OK;
+ else
+ exit 1;
+ fi
+### add notification to slack if fail
+
+ - - name: get-package-name
+ template: generate-name
+
+ - - name: pack-and-push
+ template: python
+ arguments:
+ parameters:
+ - name: cmd
+ value: >
+ pip install twine &&
+ python3 setup.py sdist bdist_wheel &&
+ twine upload --config-file /etc/pypirc/.pypirc -r pepsico-ecommerce-release dist/*
+
+### slack notification about new version
+
+
+
+ - name: git
+ retryStrategy:
+ limit: 3
+ inputs:
+ parameters:
+ - name: cmd
+ container:
+ image: "alpine/git"
+ command: ["sh", "-c"]
+ args:
+ - >
+ git config --global user.email 'argo-ci@pepsi.co' &&
+ git config --global user.name 'Argo CI' &&
+ {{inputs.parameters.cmd}}
+ volumeMounts:
+ - name: workdir
+ mountPath: /workdir
+ - name: sshkey
+ mountPath: /root/.ssh
+ - mountPath: /etc/ssh
+ name: known-hosts
+
+ - name: python
+ retryStrategy:
+ limit: 3
+ inputs:
+ parameters:
+ - name: cmd
+ container:
+ image: "python:3.7-buster"
+ command: ["sh", "-c"]
+ args:
+ - >
+ cd /workdir/{{workflow.parameters.repo_name}} &&
+ {{inputs.parameters.cmd}}
+ volumeMounts:
+ - name: workdir
+ mountPath: /workdir
+ - name: pypirc
+ mountPath: /etc/pypirc/
+ env:
+ - name: NEXUS_PYPI_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: nexus-pypi-ro-creds
+ key: password
+ - name: NEXUS_PYPI_USERNAME
+ valueFrom:
+ secretKeyRef:
+ name: nexus-pypi-ro-creds
+ key: username
+
+ - name: generate-name
+ script:
+ image: python:alpine3.7
+ command: [python]
+ source: |
+ repo_name = """
+ {{workflow.parameters.repo_name}}
+ """
+ package_name = repo_name.strip().replace('"', '').replace("'", "").replace('-', '_')
+ print(package_name)
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
new file mode 100644
index 0000000..bc5e17b
--- /dev/null
+++ b/.bumpversion.cfg
@@ -0,0 +1,18 @@
+[bumpversion]
+current_version = 0.0.7
+commit = True
+tag = False
+message = "Bump version: {current_version} → {new_version}"
+parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z0-9]{1,10}))?
+serialize =
+ {major}.{minor}.{patch}-{release}
+ {major}.{minor}.{patch}
+
+[bumpversion:part:release]
+optional_value = dev
+first_value = alpha
+values =
+ dev
+ alpha
+
+[bumpversion:file:amazon_advertising_api/versions.py]
diff --git a/.gitignore b/.gitignore
index 772b156..ab7fb31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,3 +67,5 @@ localtest.py
# Pycharm
*.idea/
+
+venv/*
diff --git a/README.md b/README.md
index 159b813..3318beb 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
-Unofficial Amazon Sponsored Products Python client library.
+# Amazon Advertising API Client
+
+This library provides support for Amazon's Advertising API. It is a fork of [Amazon Advertising API Python](https://github.com/dbrent-amazon/amazon-advertising-api-python).
diff --git a/amazon_advertising_api/advertising_api.py b/amazon_advertising_api/advertising_api.py
index 8fb0657..0809454 100644
--- a/amazon_advertising_api/advertising_api.py
+++ b/amazon_advertising_api/advertising_api.py
@@ -1,5 +1,6 @@
-from amazon_advertising_api.regions import regions
+from amazon_advertising_api import versions as v
from amazon_advertising_api.versions import versions
+from amazon_advertising_api.regions import regions
from io import BytesIO
try:
# Python 3
@@ -13,6 +14,8 @@
import gzip
import json
+DSP_REPORT = "dsp_report"
+
class AdvertisingApi(object):
@@ -49,8 +52,10 @@ def __init__(self,
self.refresh_token = refresh_token
self.api_version = versions['api_version']
+
self.user_agent = 'AdvertisingAPI Python Client Library v{}'.format(
- versions['application_version'])
+ v.__version__)
+
self.profile_id = profile_id
self.token_url = None
@@ -174,42 +179,50 @@ def update_profiles(self, data):
interface = 'profiles'
return self._operation(interface, data, method='PUT')
- def get_campaign(self, campaign_id):
+ def get_campaign(self, campaign_id, campaign_type='sp'):
"""
Retrieves a campaign by Id. Note that this call returns the minimal
set of campaign fields, but is more efficient than **getCampaignEx**.
- :GET: /campaigns/{campaignId}
+ :GET: {campaignType}/campaigns/{campaignId}
:param campaign_id: The Id of the requested campaign.
:type campaign_id: string
+ :param campaign_type: The campaignType of the requested campaign ('sp' or 'sb')
+ Defaults to 'sp'
+ :type campaign_type: string
:returns:
:200: Campaign
:401: Unauthorized
:404: Campaign not found
"""
- interface = 'campaigns/{}'. format(campaign_id)
+ interface = '{}/campaigns/{}'. format(campaign_type, campaign_id)
+
return self._operation(interface)
- def get_campaign_ex(self, campaign_id):
+ def get_campaign_ex(self, campaign_id, campaign_type='sp'):
"""
Retrieves a campaign and its extended fields by ID. Note that this
call returns the complete set of campaign fields (including serving
status and other read-only fields), but is less efficient than
**getCampaign**.
- :GET: /campaigns/extended/{campaignId}
+ :GET: {campaignType}/campaigns/extended/{campaignId}
:param campaign_id: The Id of the requested campaign.
:type campaign_id: string
+ :param campaign_type: The campaignType of the requested campaign ('sp' or 'sb')
+ Defaults to 'sp'
+ :type campaign_type: string
:returns:
:200: Campaign
:401: Unauthorized
:404: Campaign not found
"""
- interface = 'campaigns/extended/{}'. format(campaign_id)
+ interface = '{}/campaigns/extended/{}'. format(campaign_type, campaign_id)
+
return self._operation(interface)
- def create_campaigns(self, data):
+ def create_campaigns(self, data, campaign_type='sp'):
"""
Creates one or more campaigns. Successfully created campaigns will be
assigned unique **campaignIds**.
@@ -224,10 +237,10 @@ def create_campaigns(self, data):
input.
:401: Unauthorized
"""
- interface = 'campaigns'
+ interface = '{}/campaigns'.format(campaign_type)
return self._operation(interface, data, method='POST')
- def update_campaigns(self, data):
+ def update_campaigns(self, data, campaign_type='sp'):
"""
Updates one or more campaigns. Campaigns are identified using their
**campaignIds**.
@@ -242,10 +255,22 @@ def update_campaigns(self, data):
input
:401: Unauthorized
"""
- interface = 'campaigns'
+ interface = '{}/campaigns'.format(campaign_type)
return self._operation(interface, data, method='PUT')
- def archive_campaign(self, campaign_id):
+ def get_campaigns(self, campaign_type='sp'):
+ """
+ Gets campaigns
+
+ :GET: /campaigns
+ :returns:
+ :207: List of **CampaignResponse**
+ :401: Unauthorized
+ """
+ interface = '{}/campaigns'.format(campaign_type)
+ return self._operation(interface)
+
+ def archive_campaign(self, campaign_id, campaign_type='sp'):
"""
Sets the campaign status to archived. This same operation can be
performed via an update, but is included for completeness.
@@ -258,62 +283,69 @@ def archive_campaign(self, campaign_id):
:401: Unauthorized
:404: Campaign not found
"""
- interface = 'campaigns/{}'.format(campaign_id)
+ interface = '{}/campaigns/{}'.format(campaign_type, campaign_id)
return self._operation(interface, method='DELETE')
- def list_campaigns(self, data=None):
+ def list_campaigns(self, data=None, campaign_type='sp'):
"""
Retrieves a list of campaigns satisfying optional criteria.
- :GET: /campaigns
+ :GET: /{campaignType}/campaigns
+ :param campaign_type: The campaignType to retrieve campaigns for ('sp' or 'hsa')
+ Defaults to 'sp'
+ :type campaign_type: string
:param data: Optional, search criteria containing the following
parameters.
data may contain the following optional parameters:
- :param startIndex: 0-indexed record offset for the result set.
+ :param startIndex: 0-indexed record offset for the result set.
Defaults to 0.
:type startIndex: Integer
- :param count: Number of records to include in the paged response.
+ :param count: Number of records to include in the paged response.
Defaults to max page size.
:type count: Integer
- :param campaignType: Restricts results to campaigns of a single
+ :param campaignType: Restricts results to campaigns of a single
campaign type. Must be **sponsoredProducts**.
:type campaignType: String
- :param stateFilter: Restricts results to campaigns with state within
- the specified comma-separatedlist. Must be one of **enabled**,
+ :param stateFilter: Restricts results to campaigns with state within
+ the specified comma-separatedlist. Must be one of **enabled**,
**paused**, **archived**. Default behavior is to include all.
:param name: Restricts results to campaigns with the specified name.
:type name: String
- :param campaignFilterId: Restricts results to campaigns specified in
+ :param campaignFilterId: Restricts results to campaigns specified in
comma-separated list.
:type campaignFilterId: String
:returns:
:200: Success. list of campaign
:401: Unauthorized
"""
- interface = 'campaigns'
+ interface = '{}/campaigns' .format(campaign_type)
+
return self._operation(interface, data)
- def list_campaigns_ex(self, data=None):
+ def list_campaigns_ex(self, data=None, campaign_type='sp'):
"""
Retrieves a list of campaigns with extended fields satisfying
optional filtering criteria.
- :GET: /campaigns/extended
+ :GET: /{campaignType}/campaigns/extended
+ :param campaign_type: campaignType of the requested campaigns ('sp' or 'hsa')
+ Defaults to 'sp'
+ :type campaign_type: string
:param data: Optional, search criteria containing the following
parameters.
:type data: JSON string
"""
- interface = 'campaigns/extended'
+ interface = '{}/campaigns/extended' .format(campaign_type)
return self._operation(interface, data)
- def get_ad_group(self, ad_group_id):
+ def get_ad_group(self, ad_group_id, campaign_type='sp'):
"""
Retrieves an ad group by Id. Note that this call returns the minimal
set of ad group fields, but is more efficient than getAdGroupEx.
- :GET: /adGroups/{adGroupId}
+ :GET: /sp/adGroups/{adGroupId}
:param ad_group_id: The Id of the requested ad group.
:type ad_group_id: string
@@ -322,17 +354,18 @@ def get_ad_group(self, ad_group_id):
:401: Unauthorized
:404: Ad group not found
"""
- interface = 'adGroups/{}'.format(ad_group_id)
+ interface = '{}/adGroups/{}'.format(campaign_type, ad_group_id)
+
return self._operation(interface)
- def get_ad_group_ex(self, ad_group_id):
+ def get_ad_group_ex(self, ad_group_id, campaign_type='sp'):
"""
Retrieves an ad group and its extended fields by ID. Note that this
call returns the complete set of ad group fields (including serving
status and other read-only fields), but is less efficient than
getAdGroup.
- :GET: /adGroups/extended/{adGroupId}
+ :GET: /sp/adGroups/extended/{adGroupId}
:param ad_group_id: The Id of the requested ad group.
:type ad_group_id: string
@@ -341,10 +374,10 @@ def get_ad_group_ex(self, ad_group_id):
:401: Unauthorized
:404: Ad group not found
"""
- interface = 'adGroups/extended/{}'.format(ad_group_id)
+ interface = '{}/adGroups/extended/{}'.format(campaign_type, ad_group_id)
return self._operation(interface)
- def create_ad_groups(self, data):
+ def create_ad_groups(self, data, campaign_type='sp'):
"""
Creates one or more ad groups. Successfully created ad groups will
be assigned unique adGroupIds.
@@ -360,10 +393,10 @@ def create_ad_groups(self, data):
order as the input
:401: Unauthorized
"""
- interface = 'adGroups'
+ interface = '{}/adGroups'.format(campaign_type)
return self._operation(interface, data, method='POST')
- def update_ad_groups(self, data):
+ def update_ad_groups(self, data, campaign_type='sp'):
"""
Updates one or more ad groups. Ad groups are identified using their
adGroupIds.
@@ -378,10 +411,10 @@ def update_ad_groups(self, data):
order as the input
:401: Unauthorized
"""
- interface = 'adGroups'
+ interface = '{}/adGroups'.format(campaign_type)
return self._operation(interface, data, method='PUT')
- def archive_ad_group(self, ad_group_id):
+ def archive_ad_group(self, ad_group_id, campaign_type="sp"):
"""
Sets the ad group status to archived. This same operation can be
performed via an update, but is included for completeness.
@@ -395,14 +428,14 @@ def archive_ad_group(self, ad_group_id):
:401: Unauthorized
:404: Ad group not found
"""
- interface = 'adGroups/{}'.format(ad_group_id)
+ interface = '{}/adGroups/{}'.format(campaign_type, ad_group_id)
return self._operation(interface, method='DELETE')
- def list_ad_groups(self, data=None):
+ def list_ad_groups(self, data=None, campaign_type="sp"):
"""
Retrieves a list of ad groups satisfying optional criteria.
- :GET: /adGroups
+ :GET: /sp/adGroups
:param data: Parameter list of criteria.
data may contain the following optional parameters:
@@ -434,14 +467,15 @@ def list_ad_groups(self, data=None):
:401: Unauthorized.
"""
- interface = 'adGroups'
+ interface = '{}/adGroups'.format(campaign_type)
+
return self._operation(interface, data)
- def list_ad_groups_ex(self, data=None):
+ def list_ad_groups_ex(self, data=None, campaign_type="sp"):
"""
Retrieves a list of ad groups satisfying optional criteria.
- :GET: /adGroups/extended
+ :GET: /sp/adGroups/extended
:param data: Parameter list of criteria.
data may contain the following optional parameters:
@@ -472,31 +506,403 @@ def list_ad_groups_ex(self, data=None):
:200: Success. List of adGroup.
:401: Unauthorized.
"""
- interface = 'adGroups/extended'
+ interface = '{}/adGroups/extended'.format(campaign_type)
return self._operation(interface, data)
- def get_biddable_keyword(self, keyword_id):
+ def get_target(self, target_id, campaign_type='sp'):
+ """
+ Retrieves an ad group by Id. Note that this call returns the minimal
+ set of ad group fields, but is more efficient than getAdGroupEx.
+
+ :GET: /sp/targets/{targetId}
+ :param target_id: The Id of the requested ad group.
+ :type target_id: string
+
+ :returns:
+ :200: Success, Target response
+ :401: Unauthorized
+ :404: Ad group not found
+ """
+ interface = '{}/targets/{}'.format(campaign_type, target_id)
+
+ return self._operation(interface)
+
+ def get_target_ex(self, target_id):
"""
- Retrieves a keyword by ID. Note that this call returns the minimal set
+ Retrieves a target and its extended fields by ID. Note that this
+ call returns the complete set of target fields (including serving
+ status and other read-only fields), but is less efficient than
+ getTarget.
+
+ :GET: /sp/targets/extended/{adGroupId}
+ :param target_id: The Id of the requested target.
+ :type target_id: string
+
+ :returns:
+ :200: Success, Target response
+ :401: Unauthorized
+ :404: Target not found
+ """
+ interface = 'sp/targets/extended/{}'.format(target_id)
+ return self._operation(interface)
+
+ def create_targets(self, data, campaign_type='sp'):
+ """
+ Creates one or more ad groups. Successfully created ad groups will
+ be assigned unique adGroupIds.
+
+ :POST: /targets
+ :param data: A list of up to 100 targets to be created.
+ :type data: List of **Target**
+
+ :returns:
+ :207: Multi-status. List of AdGroupResponse reflecting the same
+ order as the input
+ :401: Unauthorized
+ """
+ interface = '{}/targets'.format(campaign_type)
+
+ return self._operation(interface, data, method='POST')
+
+ def create_targets_list(self, data, campaign_type='sp'):
+ """
+ Creates many targets
+
+ :POST: /targets
+ :param data: A list of up to 100 targets to be created.
+ :type data: List of **Target**
+
+ :returns:
+ :207: Multi-status. List of AdGroupResponse reflecting the same
+ order as the input
+ :401: Unauthorized
+ """
+ interface = '{}/targets/list'.format(campaign_type)
+
+ # not tested
+ return self._operation(interface, data, method='POST')
+
+ def update_targets(self, data, campaign_type='sp'):
+ """
+ Updates one or more targets. Targets are identified using their
+ targetId.
+
+ :PUT: /targets
+ :param data: A list of up to 100 updates containing targetIds and the
+ mutable fields to be modified.
+ :type data: List of **Target**
+
+ :returns:
+ :207: Multi-status. List of Targets reflecting the same
+ order as the input
+ :401: Unauthorized
+ """
+ interface = '{}/targets'.format(campaign_type)
+
+ return self._operation(interface, data, method='PUT')
+
+ def archive_target(self, ad_group_id, campaign_type='sp'):
+ """
+ Sets the ad group status to archived. This same operation can be
+ performed via an update, but is included for completeness.
+
+ :DELETE: /targets/{targetId}
+ :param target_id: The Id of the ad group to be archived.
+ :type target_id: string
+
+ :returns:
+ :200: Success. TargetResponse
+ :401: Unauthorized
+ :404: Ad group not found
+ """
+ interface = '{}/targets/{}'.format(campaign_type, ad_group_id)
+ return self._operation(interface, method='DELETE')
+
+ def list_targets(self, data=None):
+ """
+ Retrieves a list of targets satisfying optional criteria.
+
+ :GET: /sp/targets
+ :param data: Parameter list of criteria.
+
+ data may contain the following optional parameters:
+
+ :param startIndex: 0-indexed record offset for the result
+ set. Defaults to 0.
+ :type startIndex: integer
+ :param count: Number of records to include in the paged response.
+ Defaults to max page size.
+ :type count: integer
+ :param expressionTypeFilter: Restricts results to targets
+ with expression types within the specified comma-separated list.
+ Possible filter types are: auto and manual
+ :type expressionTypeFilter: string
+ :param expressionTextFilter: Content of the targeting expression
+ :type expressionTextFilter: string
+ :param campaignIdFilter: Restricts results to ad groups within
+ campaigns specified in comma-separated list.
+ :type campaignIdFilter: string
+ :param adGroupIdFilter: Restricts results to ad groups specified in
+ comma-separated list.
+ :type adGroupIdFilter: string
+ :param stateFilter: Restricts results to targets with state within the
+ specified comma-separatedlist. Must be one of enabled, paused,
+ archived. Default behavior is to include all.
+ :type stateFilter: string
+ :returns:
+ :200: Success. List of Targets.
+ :401: Unauthorized.
+ """
+ interface = 'sp/targets'
+ return self._operation(interface, data)
+
+ def list_targets_ex(self, data=None):
+ """
+ Retrieves a list of targets satisfying optional criteria.
+
+ :GET: /sp/targets/extended
+ :param data: Parameter list of criteria.
+
+ data may contain the following optional parameters:
+
+ :param startIndex: 0-indexed record offset for the result
+ set. Defaults to 0.
+ :type startIndex: integer
+ :param count: Number of records to include in the paged response.
+ Defaults to max page size.
+ :type count: integer
+ :param expressionTypeFilter: Restricts results to targets
+ with expression types within the specified comma-separated list.
+ Possible filter types are: auto and manual
+ :type expressionTypeFilter: string
+ :param expressionTextFilter: Content of the targeting expression
+ :type expressionTextFilter: string
+ :param campaignIdFilter: Restricts results to ad groups within
+ campaigns specified in comma-separated list.
+ :type campaignIdFilter: string
+ :param adGroupIdFilter: Restricts results to ad groups specified in
+ comma-separated list.
+ :type adGroupIdFilter: string
+ :param stateFilter: Restricts results to targets with state within the
+ specified comma-separatedlist. Must be one of enabled, paused,
+ archived. Default behavior is to include all.
+ :type stateFilter: string
+ :returns:
+ :200: Success. List of Targets.
+ :401: Unauthorized.
+ """
+ interface = 'sp/targets/extended'
+ return self._operation(interface, data)
+
+ def get_negative_target(self, target_id, campaign_type='sb'):
+ """
+ Retrieves an ad group by Id. Note that this call returns the minimal
+ set of ad group fields, but is more efficient than getAdGroupEx.
+
+ :GET: /sp/negativeTargets/{targetId}
+ :param target_id: The Id of the requested ad group.
+ :type target_id: string
+
+ :returns:
+ :200: Success, Target response
+ :401: Unauthorized
+ :404: Ad group not found
+ """
+ interface = '{}/negativeTargets/{}'.format(campaign_type, target_id)
+
+ return self._operation(interface)
+
+ def get_negative_target_ex(self, target_id):
+ """
+ Retrieves a target and its extended fields by ID. Note that this
+ call returns the complete set of target fields (including serving
+ status and other read-only fields), but is less efficient than
+ getTarget.
+
+ :GET: /sp/negativeTargets/extended/{adGroupId}
+ :param target_id: The Id of the requested target.
+ :type target_id: string
+
+ :returns:
+ :200: Success, Target response
+ :401: Unauthorized
+ :404: Target not found
+ """
+ interface = 'sp/negativeTargets/extended/{}'.format(target_id)
+ return self._operation(interface)
+
+ def create_negative_targets(self, data, campaign_type='sp'):
+ """
+ Creates one or more ad groups. Successfully created ad groups will
+ be assigned unique adGroupIds.
+
+ :POST: /negativeTargets
+ :param data: A list of up to 100 negativeTargets to be created.
+ :type data: List of **Target**
+
+ :returns:
+ :207: Multi-status. List of AdGroupResponse reflecting the same
+ order as the input
+ :401: Unauthorized
+ """
+ interface = '{}/negativeTargets'.format(campaign_type)
+
+ return self._operation(interface, data, method='POST')
+
+ def create_negative_targets_list(self, data, campaign_type='sb'):
+ """
+ Creates list of targets
+
+ :POST: /negativeTargets/list
+ :param data: A list of negativeTargets to be created.
+ :type data: List of **Target**
+
+ :returns:
+ :207: Multi-status. List of AdGroupResponse reflecting the same
+ order as the input
+ :401: Unauthorized
+ """
+ interface = '{}/negativeTargets/list'.format(campaign_type)
+
+ return self._operation(interface, data, method='POST')
+
+ def update_negative_targets(self, data, campaign_type='sp'):
+ """
+ Updates one or more negativeTargets. negativeTargets are identified using their
+ targetId.
+
+ :PUT: /negativeTargets
+ :param data: A list of up to 100 updates containing targetIds and the
+ mutable fields to be modified.
+ :type data: List of **Target**
+
+ :returns:
+ :207: Multi-status. List of negativeTargets reflecting the same
+ order as the input
+ :401: Unauthorized
+ """
+ interface = '{}/negativeTargets'.format(campaign_type)
+ return self._operation(interface, data, method='PUT')
+
+ def archive_negative_target(self, ad_group_id, campaign_type='sp'):
+ """
+ Sets the ad group status to archived. This same operation can be
+ performed via an update, but is included for completeness.
+
+ :DELETE: /negativeTargets/{targetId}
+ :param target_id: The Id of the ad group to be archived.
+ :type target_id: string
+
+ :returns:
+ :200: Success. TargetResponse
+ :401: Unauthorized
+ :404: Ad group not found
+ """
+ interface = '{}/negativeTargets/{}'.format(campaign_type, ad_group_id)
+ return self._operation(interface, method='DELETE')
+
+ def list_negative_targets(self, data=None):
+ """
+ Retrieves a list of negativeTargets satisfying optional criteria.
+
+ :GET: /sp/negativeTargets
+ :param data: Parameter list of criteria.
+
+ data may contain the following optional parameters:
+
+ :param startIndex: 0-indexed record offset for the result
+ set. Defaults to 0.
+ :type startIndex: integer
+ :param count: Number of records to include in the paged response.
+ Defaults to max page size.
+ :type count: integer
+ :param expressionTypeFilter: Restricts results to negativeTargets
+ with expression types within the specified comma-separated list.
+ Possible filter types are: auto and manual
+ :type expressionTypeFilter: string
+ :param expressionTextFilter: Content of the targeting expression
+ :type expressionTextFilter: string
+ :param campaignIdFilter: Restricts results to ad groups within
+ campaigns specified in comma-separated list.
+ :type campaignIdFilter: string
+ :param adGroupIdFilter: Restricts results to ad groups specified in
+ comma-separated list.
+ :type adGroupIdFilter: string
+ :param stateFilter: Restricts results to negativeTargets with state within the
+ specified comma-separatedlist. Must be one of enabled, paused,
+ archived. Default behavior is to include all.
+ :type stateFilter: string
+ :returns:
+ :200: Success. List of negativeTargets.
+ :401: Unauthorized.
+ """
+ interface = 'sp/negativeTargets'
+ return self._operation(interface, data)
+
+ def list_negative_targets_ex(self, data=None):
+ """
+ Retrieves a list of negativeTargets satisfying optional criteria.
+
+ :GET: /sp/negativeTargets/extended
+ :param data: Parameter list of criteria.
+
+ data may contain the following optional parameters:
+
+ :param startIndex: 0-indexed record offset for the result
+ set. Defaults to 0.
+ :type startIndex: integer
+ :param count: Number of records to include in the paged response.
+ Defaults to max page size.
+ :type count: integer
+ :param expressionTypeFilter: Restricts results to negativeTargets
+ with expression types within the specified comma-separated list.
+ Possible filter types are: auto and manual
+ :type expressionTypeFilter: string
+ :param expressionTextFilter: Content of the targeting expression
+ :type expressionTextFilter: string
+ :param campaignIdFilter: Restricts results to ad groups within
+ campaigns specified in comma-separated list.
+ :type campaignIdFilter: string
+ :param adGroupIdFilter: Restricts results to ad groups specified in
+ comma-separated list.
+ :type adGroupIdFilter: string
+ :param stateFilter: Restricts results to negativeTargets with state within the
+ specified comma-separatedlist. Must be one of enabled, paused,
+ archived. Default behavior is to include all.
+ :type stateFilter: string
+ :returns:
+ :200: Success. List of negativeTargets.
+ :401: Unauthorized.
+ """
+ interface = 'sp/negativeTargets/extended'
+ return self._operation(interface, data)
+
+ def get_biddable_keyword(self, keyword_id, campaign_type='sp'):
+ """
+ Retrieves a keyword by ID. Note that this call returns the minimal set
of keyword fields, but is more efficient than getBiddableKeywordEx.
- :GET: /keywords/{keywordId}
+ :GET: /{campaignType}/keywords/{keywordId}
:param keyword_id: The Id of the requested keyword.
:type keyword_id: string
+ :param campaign_type: The campaignType for the requested keyword
+ Defaults to 'sp'
+ :type campaign_type: string
:returns:
:200: Success. Keyword.
:401: Unauthorized.
:404: Keyword not found.
"""
- interface = 'keywords/{}'.format(keyword_id)
+ interface = '{}/keywords/{}'.format(campaign_type, keyword_id)
+
return self._operation(interface)
def get_biddable_keyword_ex(self, keyword_id):
"""
- Retrieves a keyword and its extended fields by ID. Note that this call
- returns the complete set of keyword fields (including serving status
- and other read-only fields), but is less efficient than
+ Retrieves a keyword and its extended fields by ID. Note that this call
+ returns the complete set of keyword fields (including serving status
+ and other read-only fields), but is less efficient than
getBiddableKeyword.
:GET: /keywords/extended/{keywordId}
@@ -508,74 +914,76 @@ def get_biddable_keyword_ex(self, keyword_id):
:401: Unauthorized.
:404: Keyword not found.
"""
- interface = 'keywords/extended/{}'.format(keyword_id)
+ interface = 'sp/keywords/extended/{}'.format(keyword_id)
return self._operation(interface)
- def create_biddable_keywords(self, data):
+ def create_biddable_keywords(self, data, campaign_type='sp'):
"""
- Creates one or more keywords. Successfully created keywords will be
+ Creates one or more keywords. Successfully created keywords will be
assigned unique keywordIds.
:POST: /keywords
- :param data: A list of up to 1000 keywords to be created. Required
- fields for keyword creation are campaignId, adGroupId, keywordText,
+ :param data: A list of up to 1000 keywords to be created. Required
+ fields for keyword creation are campaignId, adGroupId, keywordText,
matchType and state.
:type data: List of **Keyword**
"""
- interface = 'keywords'
- return self._operation(interface, data, method='POST')
+ interface = '{}/keywords'.format(campaign_type)
+
+ return self._operation(interface, data)
- def update_biddable_keywords(self, data):
- interface = 'keywords'
+ def update_biddable_keywords(self, data, campaign_type='sp'):
+ interface = '{}/keywords'.format(campaign_type)
return self._operation(interface, data, method='PUT')
- def archive_biddable_keyword(self, keyword_id):
- interface = 'keywords/{}'.format(keyword_id)
+ def archive_biddable_keyword(self, keyword_id, campaign_type='sp'):
+ interface = '{}/keywords/{}'.format(campaign_type, keyword_id)
return self._operation(interface, method='DELETE')
- def list_biddable_keywords(self, data=None):
- interface = 'keywords'
+ def list_biddable_keywords(self, data=None, campaign_type='sp'):
+ interface = '{}/keywords'.format(campaign_type)
return self._operation(interface, data)
def list_biddable_keywords_ex(self, data=None):
- interface = 'keywords/extended'
+ interface = 'sp/keywords/extended'
return self._operation(interface, data)
- def get_negative_keyword(self, negative_keyword_id):
- interface = 'negativeKeywords/{}'.format(negative_keyword_id)
+ def get_negative_keyword(self, negative_keyword_id, campaign_type='sp'):
+ interface = '{}/negativeKeywords/{}'.format(campaign_type, negative_keyword_id)
return self._operation(interface)
def get_negative_keyword_ex(self, negative_keyword_id):
- interface = 'negativeKeywords/extended/{}'.format(negative_keyword_id)
+ interface = 'sp/negativeKeywords/extended/{}'.format(negative_keyword_id)
return self._operation(interface)
- def create_negative_keywords(self, data):
- interface = 'negativeKeywords'
+ def create_negative_keywords(self, data, campaign_type="sp"):
+ interface = '{}/negativeKeywords'.format(campaign_type)
+
return self._operation(interface, data, method='POST')
- def update_negative_keywords(self, data):
- interface = 'negativeKeywords'
+ def update_negative_keywords(self, data, campaign_type='sp'):
+ interface = '{}/negativeKeywords'.format(campaign_type)
return self._operation(interface, data, method='PUT')
- def archive_negative_keyword(self, negative_keyword_id):
- interface = 'negativeKeywords/{}'.format(negative_keyword_id)
+ def archive_negative_keyword(self, negative_keyword_id, campaign_type='sp'):
+ interface = '{}/negativeKeywords/{}'.format(campaign_type, negative_keyword_id)
return self._operation(interface, method='DELETE')
- def list_negative_keywords(self, data=None):
- interface = 'negativeKeywords'
+ def list_negative_keywords(self, data=None, campaign_type='sp'):
+ interface = '{}/negativeKeywords'.format(campaign_type)
return self._operation(interface, data)
def list_negative_keywords_ex(self, data=None):
- interface = 'negativeKeywords/extended'
+ interface = 'sp/negativeKeywords/extended'
return self._operation(interface, data)
def get_campaign_negative_keyword(self, campaign_negative_keyword_id):
- interface = 'campaignNegativeKeywords/{}'.format(
+ interface = 'sp/campaignNegativeKeywords/{}'.format(
campaign_negative_keyword_id)
return self._operation(interface)
def get_campaign_negative_keyword_ex(self, campaign_negative_keyword_id):
- interface = 'campaignNegativeKeywords/extended/{}'.format(
+ interface = 'sp/campaignNegativeKeywords/extended/{}'.format(
campaign_negative_keyword_id)
return self._operation(interface)
@@ -593,19 +1001,19 @@ def remove_campaign_negative_keyword(self, campaign_negative_keyword_id):
return self._operation(interface, method='DELETE')
def list_campaign_negative_keywords(self, data=None):
- interface = 'campaignNegativeKeywords'
+ interface = 'sp/campaignNegativeKeywords'
return self._operation(interface, data)
def list_campaign_negative_keywords_ex(self, data=None):
- interface = 'campaignNegativeKeywords/extended'
+ interface = 'sp/campaignNegativeKeywords/extended'
return self._operation(interface, data)
def get_product_ad(self, product_ad_id):
- interface = 'productAds/{}'.format(product_ad_id)
+ interface = 'sp/productAds/{}'.format(product_ad_id)
return self._operation(interface)
def get_product_ad_ex(self, product_ad_id):
- interface = 'productAds/extended/{}'.format(product_ad_id)
+ interface = 'sp/productAds/extended/{}'.format(product_ad_id)
return self._operation(interface)
def create_product_ads(self, data):
@@ -619,47 +1027,50 @@ def update_product_ads(self, data):
def archive_product_ads(self):
pass
- def list_product_ads(self, data=None):
- interface = 'productAds'
+ def list_product_ads(self, data=None, campaign_type="sp"):
+ interface = '{}/productAds'.format(campaign_type)
return self._operation(interface, data)
- def list_product_ads_ex(self, data=None):
- interface = 'productAds/extended'
+ def list_product_ads_ex(self, data=None, campaign_type="sp"):
+ interface = '{}/productAds/extended'.format(campaign_type)
return self._operation(interface, data)
- def request_snapshot(self, record_type=None, snapshot_id=None, data=None):
+ def create_keyword_recommendations(self, data, campaign_type='sp'):
+ interface = '{}/recommendations/keyword'.format(campaign_type)
+
+ return self._operation(interface, data, method='POST')
+
+ def request_snapshot(self, record_type=None, snapshot_id=None, data=None, campaign_type='sp'):
"""
+ :POST: /snapshots
+
Required data:
- * :campaignType: The type of campaign for which snapshot should be generated. Must be sponsoredProducts.
+ :campaign_type: Should be 'hsa' or 'sp'
"""
- if not data:
- data = {'campaignType': 'sponsoredProducts'}
- elif not data.get('campaignType'):
- data['campaignType'] = 'sponsoredProducts'
-
if record_type is not None:
- interface = '{}/snapshot'.format(record_type)
+ interface = '{}/{}/snapshot'.format(campaign_type, record_type)
return self._operation(interface, data, method='POST')
elif snapshot_id is not None:
- interface = 'snapshots/{}'.format(snapshot_id)
+ interface = '{}/snapshots/{}'.format(campaign_type, snapshot_id)
return self._operation(interface, data)
else:
return {'success': False,
'code': 0,
'response': 'record_type and snapshot_id are both empty.'}
- def request_report(self, record_type=None, report_id=None, data=None):
- """
- Required data:
- * :campaignType: The type of campaign for which report should be generated. Must be sponsoredProducts.
+ def request_report(self, record_type=None, report_id=None, data=None, campaign_type='sp'):
"""
- if not data:
- data = {'campaignType': 'sponsoredProducts'}
- elif not data.get('campaignType'):
- data['campaignType'] = 'sponsoredProducts'
+ :POST: /{campaignType}/reports
+ :param campaign_type: The campaignType to request the report for ('sp' or 'hsa')
+ Defaults to 'sp'
+ :type data: string
+ """
if record_type is not None:
- interface = '{}/report'.format(record_type)
+ if record_type == DSP_REPORT:
+ interface = 'dsp/reports'
+ else:
+ interface = '{}/{}/report'.format(campaign_type, record_type)
return self._operation(interface, data, method='POST')
elif report_id is not None:
interface = 'reports/{}'.format(report_id)
@@ -669,15 +1080,36 @@ def request_report(self, record_type=None, report_id=None, data=None):
'code': 0,
'response': 'record_type and report_id are both empty.'}
- def get_report(self, report_id):
+ def get_report(self, report_id, report_type=None):
interface = 'reports/{}'.format(report_id)
- res = self._operation(interface)
- if json.loads(res['response'])['status'] == 'SUCCESS':
- res = self._download(
- location=json.loads(res['response'])['location'])
- return res
+ if report_type is not None and report_type == DSP_REPORT:
+ interface = 'dsp/' + interface
+ res = self._operation(interface, report_id=report_id)
+ if res['success']:
+ body = json.loads(res['response'])
+ if body.get('status') == 'SUCCESS':
+ # for dsp report, return response with url for the report instead of the huge report data itself
+ if report_type == DSP_REPORT:
+ return res
+ res = self._download(location=body['location'])
+ return res
+
+ def get_dsp_entity(self, interface, media_type):
+ """
+ :GET: dsp/interface
+ :
+ :interface consists of entity_type and query_param, where
+ : entity_type: advertisers, orders, lineItems, creatives, lineItemCreativeAssociations
+ : query_param: startIndex=&count=[&=]
+ : filter: advertiserIdFilter, orderIdFilter, lineItemIdFilter
+ :media_type example: 'application/vnd.dspbasiclineitems.v3+json'
+ """
+ if interface is None or media_type is None:
+ return {'success': False,
+ 'code': 0,
+ 'response': 'interface and media_type are both empty.'}
else:
- return res
+ return self._operation(interface, method='GET', media_type=media_type)
def get_snapshot(self, snapshot_id):
interface = 'snapshots/{}'.format(snapshot_id)
@@ -734,6 +1166,7 @@ def _download(self, location):
data = f.read()
return {'success': True,
'code': res.code,
+ 'api_version': versions["api_version"],
'response': json.loads(data.decode('utf-8'))}
else:
return {'success': False,
@@ -748,7 +1181,7 @@ def _download(self, location):
'code': e.code,
'response': '{msg}: {details}'.format(msg=e.msg, details=e.read())}
- def _operation(self, interface, params=None, method='GET'):
+ def _operation(self, interface, params=None, method='GET', report_id=None, media_type=None):
"""
Makes that actual API call.
@@ -756,17 +1189,29 @@ def _operation(self, interface, params=None, method='GET'):
:type interface: string
:param params: Parameters associated with this call.
:type params: GET: string POST: dictionary
- :param method: Call method. Should be either 'GET', 'PUT', or 'POST'
+ :param method: Call method. Should be either 'GET', 'PUT', or 'POST'.
:type method: string
+ :param report_id: report_id obtained in previous 'POST' request.
+ :type report_id: string
+ :param media_type: Used for dsp entity 'GET' calls, e.g., 'application/vnd.dsporders.v2.2+json'
+ :type media_type: string
"""
+ api_v3 = interface.startswith('sb')
+ dsp_report = interface.startswith('dsp')
+
if self._access_token is None:
return {'success': False,
'code': 0,
'response': 'access_token is empty.'}
headers = {'Authorization': 'Bearer {}'.format(self._access_token),
+ 'Amazon-Advertising-API-ClientId': self.client_id,
'Content-Type': 'application/json',
'User-Agent': self.user_agent}
+ if report_id is not None:
+ headers['reportId'] = report_id
+ if media_type is not None:
+ headers['Accept'] = media_type
if self.profile_id is not None and self.profile_id != '':
headers['Amazon-Advertising-API-Scope'] = self.profile_id
@@ -778,26 +1223,22 @@ def _operation(self, interface, params=None, method='GET'):
data = None
+ if api_v3 or dsp_report:
+ url = f"https://{self.endpoint}/{interface}"
+ else:
+ url = f"https://{self.endpoint}/{self.api_version}/{interface}"
+
if method == 'GET':
if params is not None:
p = '?{}'.format(urllib.parse.urlencode(params))
else:
p = ''
- url = 'https://{host}/{api_version}/{interface}{params}'.format(
- host=self.endpoint,
- api_version=self.api_version,
- interface=interface,
- params=p)
+ url += '{params}'.format(params=p)
else:
if params is not None:
data = json.dumps(params).encode('utf-8')
- url = 'https://{host}/{api_version}/{interface}'.format(
- host=self.endpoint,
- api_version=self.api_version,
- interface=interface)
-
if PYTHON == 3:
req = urllib.request.Request(url=url, headers=headers, data=data)
else:
@@ -806,11 +1247,15 @@ def _operation(self, interface, params=None, method='GET'):
try:
f = urllib.request.urlopen(req)
- return {'success': True,
- 'code': f.code,
- 'response': f.read().decode('utf-8')}
+ return {
+ 'success': True,
+ 'api_version': self.api_version if not api_v3 else versions['api_version_sb'],
+ 'code': f.code,
+ 'response': f.read().decode('utf-8')}
+
except urllib.error.HTTPError as e:
return {'success': False,
+ 'api_version': self.api_version if not api_v3 else versions['api_version_sb'],
'code': e.code,
'response': '{msg}: {details}'.format(msg=e.msg, details=e.read())}
diff --git a/amazon_advertising_api/versions.py b/amazon_advertising_api/versions.py
index e1dccf0..b3940da 100644
--- a/amazon_advertising_api/versions.py
+++ b/amazon_advertising_api/versions.py
@@ -1,3 +1,5 @@
+__version__ = "0.0.7"
+
versions = {
- 'api_version': 'v1',
- 'application_version': '1.0'}
+ 'api_version': 'v2',
+ 'api_version_sb': 'v3'}
diff --git a/setup.py b/setup.py
index d2a341d..27ceb13 100644
--- a/setup.py
+++ b/setup.py
@@ -1,10 +1,10 @@
from setuptools import setup
-import amazon_advertising_api.versions as aa_versions
+from amazon_advertising_api import versions
setup(
name='amazon_advertising_api',
packages=['amazon_advertising_api'],
- version=aa_versions.versions['application_version'],
+ version=versions.__version__,
description='Unofficial Amazon Sponsored Products Python client library.',
- url='https://github.com/sguermond/amazon-advertising-api-python')
+ url='https://github.com/pepsico-ecommerce/amazon-advertising-api-python')