From 0afe9511a975894d309fd76e2510c49e3638d892 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 10 Feb 2020 11:49:12 -0800 Subject: [PATCH 01/27] Create pythonpublish.yml Testing CI/CD integration --- .github/workflows/pythonpublish.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/pythonpublish.yml diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml new file mode 100644 index 0000000..b143a53 --- /dev/null +++ b/.github/workflows/pythonpublish.yml @@ -0,0 +1,26 @@ +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* From 638267501bb0708eb1314f843e26b66c724af6eb Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 10 Feb 2020 13:55:01 -0800 Subject: [PATCH 02/27] Update pythonpublish.yml --- .github/workflows/pythonpublish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml index b143a53..33c8ebc 100644 --- a/.github/workflows/pythonpublish.yml +++ b/.github/workflows/pythonpublish.yml @@ -1,4 +1,4 @@ -name: Upload Python Package +name: Update PyPi on: release: From ef98d73cba6d10e1465978eeecad936e59bc9342 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 10 Feb 2020 13:58:09 -0800 Subject: [PATCH 03/27] Update __init__.py --- twitter_ads/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twitter_ads/__init__.py b/twitter_ads/__init__.py index 00f34fa..3c9d0a6 100644 --- a/twitter_ads/__init__.py +++ b/twitter_ads/__init__.py @@ -1,6 +1,6 @@ # Copyright (C) 2015 Twitter, Inc. -VERSION = (6, 1, 0) +VERSION = (6, 1, 1) API_VERSION = '6' from twitter_ads.utils import get_version From 7de97b5640ddde5cf3baf01677ec7ab8128475c5 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 10 Feb 2020 14:02:59 -0800 Subject: [PATCH 04/27] Update __init__.py --- twitter_ads/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twitter_ads/__init__.py b/twitter_ads/__init__.py index 3c9d0a6..00f34fa 100644 --- a/twitter_ads/__init__.py +++ b/twitter_ads/__init__.py @@ -1,6 +1,6 @@ # Copyright (C) 2015 Twitter, Inc. -VERSION = (6, 1, 1) +VERSION = (6, 1, 0) API_VERSION = '6' from twitter_ads.utils import get_version From a78076de67197566b777a72e36adea4bcb4c39d9 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 5 Oct 2020 13:10:38 -0700 Subject: [PATCH 05/27] Create python-package.yml --- .github/workflows/python-package.yml | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/python-package.yml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..f1abc2f --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,39 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python package + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5, 3.6, 3.7, 3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest From 4e0cabd1db1f1359efc525d7ff512e45edf82c1a Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 5 Oct 2020 13:15:19 -0700 Subject: [PATCH 06/27] Update python-package.yml --- .github/workflows/python-package.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index f1abc2f..f2dded5 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -31,9 +31,10 @@ jobs: - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + python setup.py flake8 - name: Test with pytest run: | - pytest + python setup.py test From 4206d23508dea19020a73efbb482801bc3b95c18 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 5 Oct 2020 13:24:15 -0700 Subject: [PATCH 07/27] Update python-package.yml --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index f2dded5..897b795 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8] + python-version: [pypy, pypy2, pypy3, 3.5, 3.6, 3.7, 3.8] steps: - uses: actions/checkout@v2 From 3739defcd72c2d22821e84d7ffab0b7bc884b0f1 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 5 Oct 2020 13:26:50 -0700 Subject: [PATCH 08/27] Update python-package.yml --- .github/workflows/python-package.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 897b795..a6eab83 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -12,10 +12,11 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [pypy, pypy2, pypy3, 3.5, 3.6, 3.7, 3.8] + python-version: [pypy2, pypy3, 3.5, 3.6, 3.7, 3.8] + os: [macos-latest, windows-latest, ubuntu-latest] steps: - uses: actions/checkout@v2 From 8f25cecaf51dd1053eabe6ee7095985b270b9f38 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 5 Oct 2020 13:32:00 -0700 Subject: [PATCH 09/27] removed windows --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index a6eab83..85eb065 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: python-version: [pypy2, pypy3, 3.5, 3.6, 3.7, 3.8] - os: [macos-latest, windows-latest, ubuntu-latest] + os: [macos-latest, ubuntu-latest] steps: - uses: actions/checkout@v2 From e6dda1d74cae2d7c75486118f4c98e3ff1808ee8 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 5 Oct 2020 13:51:52 -0700 Subject: [PATCH 10/27] removed python 3.8 --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 85eb065..bdfdda8 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -15,7 +15,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [pypy2, pypy3, 3.5, 3.6, 3.7, 3.8] + python-version: [pypy2, pypy3, 3.5, 3.6, 3.7] os: [macos-latest, ubuntu-latest] steps: From 80e1edb78f9f9a0f9dcc51e2526f7f421e3bdf0e Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 5 Oct 2020 14:18:53 -0700 Subject: [PATCH 11/27] Update python-package.yml --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index bdfdda8..b832bc3 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -15,7 +15,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [pypy2, pypy3, 3.5, 3.6, 3.7] + python-version: [pypy, pypy2, pypy3, 3.5, 3.6, 3.7] os: [macos-latest, ubuntu-latest] steps: From 7488d8488d4125920ab38f8e5357cbcd51b766ec Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 5 Oct 2020 14:32:53 -0700 Subject: [PATCH 12/27] remove pypy --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b832bc3..bdfdda8 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -15,7 +15,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [pypy, pypy2, pypy3, 3.5, 3.6, 3.7] + python-version: [pypy2, pypy3, 3.5, 3.6, 3.7] os: [macos-latest, ubuntu-latest] steps: From 30f6eaa6ea426e3f9b9209605aa862b31e3e8ca7 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 5 Oct 2020 14:39:03 -0700 Subject: [PATCH 13/27] Update python-package.yml --- .github/workflows/python-package.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index bdfdda8..2a7e337 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -1,7 +1,7 @@ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Python package +name: Python build on: push: @@ -31,10 +31,6 @@ jobs: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | - # stop the build if there are Python syntax errors or undefined names - # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics python setup.py flake8 - name: Test with pytest run: | From b8613dbef8f80a780f5ccf6e0fc6bbc3689c5f9d Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 10 Feb 2020 11:49:12 -0800 Subject: [PATCH 14/27] Create pythonpublish.yml Testing CI/CD integration Update pythonpublish.yml Update __init__.py Update __init__.py Add github actions (#249) * Added pythonpublish.yml to auto-deploy to PyPi on new release Ads API v7 (#251) * Removed scoped timeline endpoint * added granular tap placements * added advertiser business categories endpoint * updated serving_status to entity_status for media creatives * replaced reach estimate with audience summary * added audience summary and tests Add header for internal use (#252) * add internal header Bump version (#253) Merge across forks to get CI working (#265) (#266) * Add store identifiers to line item * bump version Create python-package.yml Update python-package.yml Update python-package.yml Update python-package.yml removed windows removed python 3.8 Update python-package.yml remove pypy Update python-package.yml --- .github/workflows/python-package.yml | 37 ++++++++++++++++ .github/workflows/pythonpublish.yml | 26 ++++++++++++ examples/audience_summary.py | 40 ++++++++++++++++++ requirements.txt | 3 ++ tests/fixtures/audience_summary.json | 14 +++++++ tests/test_audience_summary.py | 63 ++++++++++++++++++++++++++++ twitter_ads/__init__.py | 4 +- twitter_ads/account.py | 18 -------- twitter_ads/campaign.py | 13 ++++++ twitter_ads/client.py | 6 +++ twitter_ads/creative.py | 2 +- twitter_ads/enum.py | 7 +++- twitter_ads/http.py | 6 ++- twitter_ads/targeting.py | 25 +++++++---- 14 files changed, 232 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/python-package.yml create mode 100644 .github/workflows/pythonpublish.yml create mode 100644 examples/audience_summary.py create mode 100644 tests/fixtures/audience_summary.json create mode 100644 tests/test_audience_summary.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..2a7e337 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,37 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: [pypy2, pypy3, 3.5, 3.6, 3.7] + os: [macos-latest, ubuntu-latest] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + python setup.py flake8 + - name: Test with pytest + run: | + python setup.py test diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml new file mode 100644 index 0000000..33c8ebc --- /dev/null +++ b/.github/workflows/pythonpublish.yml @@ -0,0 +1,26 @@ +name: Update PyPi + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/examples/audience_summary.py b/examples/audience_summary.py new file mode 100644 index 0000000..5f0bdcd --- /dev/null +++ b/examples/audience_summary.py @@ -0,0 +1,40 @@ +from twitter_ads.client import Client +from twitter_ads.targeting import AudienceSummary + +CONSUMER_KEY = 'your consumer key' +CONSUMER_SECRET = 'your consumer secret' +ACCESS_TOKEN = 'access token' +ACCESS_TOKEN_SECRET = 'access token secret' +ACCOUNT_ID = 'account id' + +# initialize the client +client = Client(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET) + +# load the advertiser account instance +account = client.accounts(ACCOUNT_ID) + +# targeting criteria params +params = { + "targeting_criteria": [ + { + "targeting_type":"LOCATION", + "targeting_value":"96683cc9126741d1" + }, + { + "targeting_type":"BROAD_KEYWORD", + "targeting_value":"cats" + }, + { + "targeting_type":"SIMILAR_TO_FOLLOWERS_OF_USER", + "targeting_value": "14230524" + }, + { + "targeting_type":"SIMILAR_TO_FOLLOWERS_OF_USER", + "targeting_value": "90420314" + } + ] +} + +audience_summary = AudienceSummary.load(account=account, params=params) + +print (audience_summary.audience_size) diff --git a/requirements.txt b/requirements.txt index 3e9aab1..111c2e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,6 @@ python-dateutil responses mock setuptools_scm +MarkupSafe +setuptools>=40.0 +configparser>=3.5 \ No newline at end of file diff --git a/tests/fixtures/audience_summary.json b/tests/fixtures/audience_summary.json new file mode 100644 index 0000000..e8dd488 --- /dev/null +++ b/tests/fixtures/audience_summary.json @@ -0,0 +1,14 @@ +{ + "request": { + "params": { + "targeting_criteria": null, + "account_id": "2iqph" + } + }, + "data": { + "audience_size": { + "min": 41133600, + "max": 50274400 + } + } +} \ No newline at end of file diff --git a/tests/test_audience_summary.py b/tests/test_audience_summary.py new file mode 100644 index 0000000..20bfa67 --- /dev/null +++ b/tests/test_audience_summary.py @@ -0,0 +1,63 @@ +import responses +import unittest + +from tests.support import with_resource, with_fixture, characters + +from twitter_ads.account import Account +from twitter_ads.client import Client +from twitter_ads.targeting import AudienceSummary +from twitter_ads import API_VERSION + + +@responses.activate +def test_audience_summary(): + responses.add(responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph'), + body=with_fixture('accounts_load'), + content_type='application/json') + + responses.add(responses.POST, + with_resource('/' + API_VERSION + '/accounts/2iqph/audience_summary'), + body=with_fixture('audience_summary'), + content_type='application/json') + + client = Client( + characters(40), + characters(40), + characters(40), + characters(40) + ) + + account = Account.load(client, '2iqph') + + params = { + "targeting_criteria": [ + { + "targeting_type":"LOCATION", + "targeting_value":"96683cc9126741d1" + }, + { + "targeting_type":"BROAD_KEYWORD", + "targeting_value":"cats" + }, + { + "targeting_type":"SIMILAR_TO_FOLLOWERS_OF_USER", + "targeting_value": "14230524" + }, + { + "targeting_type":"SIMILAR_TO_FOLLOWERS_OF_USER", + "targeting_value": "90420314" + } + ] + } + + audience_summary = AudienceSummary.load( + account=account, + params=params + ) + + print (audience_summary) + assert audience_summary is not None + assert audience_summary.audience_size is not None + assert audience_summary.audience_size['min'] == 41133600 + assert audience_summary.audience_size['max'] == 50274400 diff --git a/twitter_ads/__init__.py b/twitter_ads/__init__.py index 00f34fa..b5fe98f 100644 --- a/twitter_ads/__init__.py +++ b/twitter_ads/__init__.py @@ -1,7 +1,7 @@ # Copyright (C) 2015 Twitter, Inc. -VERSION = (6, 1, 0) -API_VERSION = '6' +VERSION = (7, 0, 2) +API_VERSION = '7' from twitter_ads.utils import get_version diff --git a/twitter_ads/account.py b/twitter_ads/account.py index 3534979..0bd4344 100644 --- a/twitter_ads/account.py +++ b/twitter_ads/account.py @@ -6,7 +6,6 @@ from twitter_ads.enum import TRANSFORM from twitter_ads.http import Request from twitter_ads.cursor import Cursor -from twitter_ads.utils import Deprecated from twitter_ads import API_VERSION from twitter_ads.resource import resource_property, Resource @@ -28,7 +27,6 @@ class Account(Resource): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts' RESOURCE = '/' + API_VERSION + '/accounts/{id}' FEATURES = '/' + API_VERSION + '/accounts/{id}/features' - SCOPED_TIMELINE = '/5/accounts/{id}/scoped_timeline' def __init__(self, client): self._client = client @@ -156,22 +154,6 @@ def video_website_cards(self, id=None, **kwargs): """ return self._load_resource(VideoWebsiteCard, id, **kwargs) - @Deprecated('This method has been deprecated as of version 5' - 'and no longer works in the latest version.') - def scoped_timeline(self, *id, **kwargs): - """ - Returns the most recent promotable Tweets created by the specified Twitter user. - """ - self._validate_loaded() - - params = {'user_id': id} - params.update(kwargs) - - resource = self.SCOPED_TIMELINE.format(id=self.id) - response = Request(self.client, 'get', resource, params=params).perform() - - return response.body['data'] - # account properties resource_property(Account, 'id', readonly=True) diff --git a/twitter_ads/campaign.py b/twitter_ads/campaign.py index be934cc..20a88e5 100644 --- a/twitter_ads/campaign.py +++ b/twitter_ads/campaign.py @@ -298,6 +298,8 @@ def save(self): resource_property(LineItem, 'charge_by') resource_property(LineItem, 'end_time', transform=TRANSFORM.TIME) resource_property(LineItem, 'entity_status') +resource_property(LineItem, 'ios_app_store_identifier') +resource_property(LineItem, 'android_app_store_identifier') resource_property(LineItem, 'audience_expansion') resource_property(LineItem, 'name') resource_property(LineItem, 'objective') @@ -470,3 +472,14 @@ class IabCategories(Resource): resource_property(IabCategories, 'id', readonly=True) resource_property(IabCategories, 'name', readonly=True) resource_property(IabCategories, 'parent_id', readonly=True) + + +class AdvertiserBusinessCategories(Resource): + + PROPERTIES = {} + RESOURCE_COLLECTION = '/' + API_VERSION + '/advertiser_business_categories' + + +resource_property(ContentCategories, 'id', readonly=True) +resource_property(ContentCategories, 'name', readonly=True) +resource_property(ContentCategories, 'iab_categories', readonly=True) diff --git a/twitter_ads/client.py b/twitter_ads/client.py index 6b91a5c..47b625d 100644 --- a/twitter_ads/client.py +++ b/twitter_ads/client.py @@ -29,6 +29,7 @@ def __init__(self, self._access_token = access_token self._access_token_secret = access_token_secret self._options = kwargs.get('options', {}) + self._headers = kwargs.get('headers', {}) def __repr__(self): return '<{name} object at {mem} consumer_key={key}>'.format( @@ -42,6 +43,11 @@ def options(self): """Returns the options value.""" return self._options + @property + def headers(self): + """Returns the headers value.""" + return self._headers + @property def consumer_key(self): """Returns the consumer_key value.""" diff --git a/twitter_ads/creative.py b/twitter_ads/creative.py index 87023d7..aafd6de 100644 --- a/twitter_ads/creative.py +++ b/twitter_ads/creative.py @@ -117,7 +117,7 @@ class MediaCreative(Analytics, Resource, Persistence): resource_property(MediaCreative, 'created_at', readonly=True, transform=TRANSFORM.TIME) resource_property(MediaCreative, 'deleted', readonly=True, transform=TRANSFORM.BOOL) resource_property(MediaCreative, 'id', readonly=True) -resource_property(MediaCreative, 'serving_status', readonly=True) +resource_property(MediaCreative, 'entity_status', readonly=True) resource_property(MediaCreative, 'updated_at', readonly=True, transform=TRANSFORM.TIME) # writable resource_property(MediaCreative, 'account_media_id') diff --git a/twitter_ads/enum.py b/twitter_ads/enum.py index 722dd76..28f5567 100644 --- a/twitter_ads/enum.py +++ b/twitter_ads/enum.py @@ -155,7 +155,12 @@ def enum(**enums): ALL_ON_TWITTER='ALL_ON_TWITTER', TWITTER_SEARCH='TWITTER_SEARCH', TWITTER_TIMELINE='TWITTER_TIMELINE', - PUBLISHER_NETWORK='PUBLISHER_NETWORK' + PUBLISHER_NETWORK='PUBLISHER_NETWORK', + TAP_FULL='TAP_FULL', + TAP_FULL_LANDSCAPE='TAP_FULL_LANDSCAPE', + TAP_BANNER='TAP_BANNER', + TAP_NATIVE='TAP_NATIVE', + TAP_MRECT="TAP_MRECT" ) PRODUCT = enum( diff --git a/twitter_ads/http.py b/twitter_ads/http.py index 1931f12..c7e15b8 100644 --- a/twitter_ads/http.py +++ b/twitter_ads/http.py @@ -75,10 +75,12 @@ def __oauth_request(self): if 'headers' in self.options: headers.update(self.options['headers'].copy()) - # internal-only + # DEPRECATED: internal-only (Should pass a header to the client) if 'x-as-user' in self._client.options: headers['x-as-user'] = self._client.options.get('x-as-user') - + # Add headers from the client to the request (Client headers take priority) + for key, val in self._client.headers.items(): + headers[key] = val params = self.options.get('params', None) data = self.options.get('body', None) files = self.options.get('files', None) diff --git a/twitter_ads/targeting.py b/twitter_ads/targeting.py index 621425c..b185ba0 100644 --- a/twitter_ads/targeting.py +++ b/twitter_ads/targeting.py @@ -3,19 +3,28 @@ """Container for all targeting related logic used by the Ads API SDK.""" from twitter_ads.http import Request +from twitter_ads.resource import resource_property, Resource, Persistence from twitter_ads import API_VERSION +from twitter_ads.utils import FlattenParams +import json -class ReachEstimate(object): +class AudienceSummary(Resource, Persistence): + PROPERTIES = {} - RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/reach_estimate' + RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/audience_summary' @classmethod - def fetch(klass, account, product_type, objective, user_id, **kwargs): - params = {'product_type': product_type, 'objective': objective, 'user_id': user_id} - params.update(kwargs) - + @FlattenParams + def load(klass, account, params): resource = klass.RESOURCE.format(account_id=account.id) - response = Request(account.client, 'get', resource, params=params).perform() + headers = {'Content-Type': 'application/json'} + response = Request(account.client, + 'post', + resource, + headers=headers, + body=json.dumps(params)).perform() + return klass(account).from_response(response.body['data']) + - return response.body['data'] +resource_property(AudienceSummary, 'audience_size') From 863ef3283c0058e5b1728f342bd6b646b78f4e2e Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 10 Feb 2020 11:49:12 -0800 Subject: [PATCH 15/27] # This is a combination of 2 commits. # The first commit's message is: # This is a combination of 2 commits. # The first commit's message is: # This is a combination of 2 commits. # The first commit's message is: # This is a combination of 2 commits. # The first commit's message is: Create pythonpublish.yml Testing CI/CD integration Update pythonpublish.yml Update __init__.py Update __init__.py Add github actions (#249) * Added pythonpublish.yml to auto-deploy to PyPi on new release Ads API v7 (#251) * Removed scoped timeline endpoint * added granular tap placements * added advertiser business categories endpoint * updated serving_status to entity_status for media creatives * replaced reach estimate with audience summary * added audience summary and tests Add header for internal use (#252) * add internal header Bump version (#253) Merge across forks to get CI working (#265) (#266) * Add store identifiers to line item * bump version Create python-package.yml Update python-package.yml Update python-package.yml Update python-package.yml removed windows removed python 3.8 Update python-package.yml remove pypy Update python-package.yml # The 2nd commit message will be skipped: # Update pythonpublish.yml # The 2nd commit message will be skipped: # Add github actions (#249) # # * Added pythonpublish.yml to auto-deploy to PyPi on new release # The 2nd commit message will be skipped: # Add header for internal use (#252) # # * add internal header # The 2nd commit message will be skipped: # Merge across forks to get CI working (#265) (#266) # # * Add store identifiers to line item # # * bump version --- .github/workflows/python-package.yml | 37 ++++++++++++++++ .github/workflows/pythonpublish.yml | 26 ++++++++++++ examples/audience_summary.py | 40 ++++++++++++++++++ requirements.txt | 3 ++ tests/fixtures/audience_summary.json | 14 +++++++ tests/test_audience_summary.py | 63 ++++++++++++++++++++++++++++ twitter_ads/__init__.py | 4 +- twitter_ads/account.py | 18 -------- twitter_ads/campaign.py | 13 ++++++ twitter_ads/client.py | 6 +++ twitter_ads/creative.py | 2 +- twitter_ads/enum.py | 7 +++- twitter_ads/http.py | 6 ++- twitter_ads/targeting.py | 25 +++++++---- 14 files changed, 232 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/python-package.yml create mode 100644 .github/workflows/pythonpublish.yml create mode 100644 examples/audience_summary.py create mode 100644 tests/fixtures/audience_summary.json create mode 100644 tests/test_audience_summary.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..2a7e337 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,37 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: [pypy2, pypy3, 3.5, 3.6, 3.7] + os: [macos-latest, ubuntu-latest] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + python setup.py flake8 + - name: Test with pytest + run: | + python setup.py test diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml new file mode 100644 index 0000000..33c8ebc --- /dev/null +++ b/.github/workflows/pythonpublish.yml @@ -0,0 +1,26 @@ +name: Update PyPi + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/examples/audience_summary.py b/examples/audience_summary.py new file mode 100644 index 0000000..5f0bdcd --- /dev/null +++ b/examples/audience_summary.py @@ -0,0 +1,40 @@ +from twitter_ads.client import Client +from twitter_ads.targeting import AudienceSummary + +CONSUMER_KEY = 'your consumer key' +CONSUMER_SECRET = 'your consumer secret' +ACCESS_TOKEN = 'access token' +ACCESS_TOKEN_SECRET = 'access token secret' +ACCOUNT_ID = 'account id' + +# initialize the client +client = Client(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET) + +# load the advertiser account instance +account = client.accounts(ACCOUNT_ID) + +# targeting criteria params +params = { + "targeting_criteria": [ + { + "targeting_type":"LOCATION", + "targeting_value":"96683cc9126741d1" + }, + { + "targeting_type":"BROAD_KEYWORD", + "targeting_value":"cats" + }, + { + "targeting_type":"SIMILAR_TO_FOLLOWERS_OF_USER", + "targeting_value": "14230524" + }, + { + "targeting_type":"SIMILAR_TO_FOLLOWERS_OF_USER", + "targeting_value": "90420314" + } + ] +} + +audience_summary = AudienceSummary.load(account=account, params=params) + +print (audience_summary.audience_size) diff --git a/requirements.txt b/requirements.txt index 3e9aab1..111c2e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,6 @@ python-dateutil responses mock setuptools_scm +MarkupSafe +setuptools>=40.0 +configparser>=3.5 \ No newline at end of file diff --git a/tests/fixtures/audience_summary.json b/tests/fixtures/audience_summary.json new file mode 100644 index 0000000..e8dd488 --- /dev/null +++ b/tests/fixtures/audience_summary.json @@ -0,0 +1,14 @@ +{ + "request": { + "params": { + "targeting_criteria": null, + "account_id": "2iqph" + } + }, + "data": { + "audience_size": { + "min": 41133600, + "max": 50274400 + } + } +} \ No newline at end of file diff --git a/tests/test_audience_summary.py b/tests/test_audience_summary.py new file mode 100644 index 0000000..20bfa67 --- /dev/null +++ b/tests/test_audience_summary.py @@ -0,0 +1,63 @@ +import responses +import unittest + +from tests.support import with_resource, with_fixture, characters + +from twitter_ads.account import Account +from twitter_ads.client import Client +from twitter_ads.targeting import AudienceSummary +from twitter_ads import API_VERSION + + +@responses.activate +def test_audience_summary(): + responses.add(responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph'), + body=with_fixture('accounts_load'), + content_type='application/json') + + responses.add(responses.POST, + with_resource('/' + API_VERSION + '/accounts/2iqph/audience_summary'), + body=with_fixture('audience_summary'), + content_type='application/json') + + client = Client( + characters(40), + characters(40), + characters(40), + characters(40) + ) + + account = Account.load(client, '2iqph') + + params = { + "targeting_criteria": [ + { + "targeting_type":"LOCATION", + "targeting_value":"96683cc9126741d1" + }, + { + "targeting_type":"BROAD_KEYWORD", + "targeting_value":"cats" + }, + { + "targeting_type":"SIMILAR_TO_FOLLOWERS_OF_USER", + "targeting_value": "14230524" + }, + { + "targeting_type":"SIMILAR_TO_FOLLOWERS_OF_USER", + "targeting_value": "90420314" + } + ] + } + + audience_summary = AudienceSummary.load( + account=account, + params=params + ) + + print (audience_summary) + assert audience_summary is not None + assert audience_summary.audience_size is not None + assert audience_summary.audience_size['min'] == 41133600 + assert audience_summary.audience_size['max'] == 50274400 diff --git a/twitter_ads/__init__.py b/twitter_ads/__init__.py index 00f34fa..b5fe98f 100644 --- a/twitter_ads/__init__.py +++ b/twitter_ads/__init__.py @@ -1,7 +1,7 @@ # Copyright (C) 2015 Twitter, Inc. -VERSION = (6, 1, 0) -API_VERSION = '6' +VERSION = (7, 0, 2) +API_VERSION = '7' from twitter_ads.utils import get_version diff --git a/twitter_ads/account.py b/twitter_ads/account.py index 3534979..0bd4344 100644 --- a/twitter_ads/account.py +++ b/twitter_ads/account.py @@ -6,7 +6,6 @@ from twitter_ads.enum import TRANSFORM from twitter_ads.http import Request from twitter_ads.cursor import Cursor -from twitter_ads.utils import Deprecated from twitter_ads import API_VERSION from twitter_ads.resource import resource_property, Resource @@ -28,7 +27,6 @@ class Account(Resource): RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts' RESOURCE = '/' + API_VERSION + '/accounts/{id}' FEATURES = '/' + API_VERSION + '/accounts/{id}/features' - SCOPED_TIMELINE = '/5/accounts/{id}/scoped_timeline' def __init__(self, client): self._client = client @@ -156,22 +154,6 @@ def video_website_cards(self, id=None, **kwargs): """ return self._load_resource(VideoWebsiteCard, id, **kwargs) - @Deprecated('This method has been deprecated as of version 5' - 'and no longer works in the latest version.') - def scoped_timeline(self, *id, **kwargs): - """ - Returns the most recent promotable Tweets created by the specified Twitter user. - """ - self._validate_loaded() - - params = {'user_id': id} - params.update(kwargs) - - resource = self.SCOPED_TIMELINE.format(id=self.id) - response = Request(self.client, 'get', resource, params=params).perform() - - return response.body['data'] - # account properties resource_property(Account, 'id', readonly=True) diff --git a/twitter_ads/campaign.py b/twitter_ads/campaign.py index be934cc..20a88e5 100644 --- a/twitter_ads/campaign.py +++ b/twitter_ads/campaign.py @@ -298,6 +298,8 @@ def save(self): resource_property(LineItem, 'charge_by') resource_property(LineItem, 'end_time', transform=TRANSFORM.TIME) resource_property(LineItem, 'entity_status') +resource_property(LineItem, 'ios_app_store_identifier') +resource_property(LineItem, 'android_app_store_identifier') resource_property(LineItem, 'audience_expansion') resource_property(LineItem, 'name') resource_property(LineItem, 'objective') @@ -470,3 +472,14 @@ class IabCategories(Resource): resource_property(IabCategories, 'id', readonly=True) resource_property(IabCategories, 'name', readonly=True) resource_property(IabCategories, 'parent_id', readonly=True) + + +class AdvertiserBusinessCategories(Resource): + + PROPERTIES = {} + RESOURCE_COLLECTION = '/' + API_VERSION + '/advertiser_business_categories' + + +resource_property(ContentCategories, 'id', readonly=True) +resource_property(ContentCategories, 'name', readonly=True) +resource_property(ContentCategories, 'iab_categories', readonly=True) diff --git a/twitter_ads/client.py b/twitter_ads/client.py index 6b91a5c..47b625d 100644 --- a/twitter_ads/client.py +++ b/twitter_ads/client.py @@ -29,6 +29,7 @@ def __init__(self, self._access_token = access_token self._access_token_secret = access_token_secret self._options = kwargs.get('options', {}) + self._headers = kwargs.get('headers', {}) def __repr__(self): return '<{name} object at {mem} consumer_key={key}>'.format( @@ -42,6 +43,11 @@ def options(self): """Returns the options value.""" return self._options + @property + def headers(self): + """Returns the headers value.""" + return self._headers + @property def consumer_key(self): """Returns the consumer_key value.""" diff --git a/twitter_ads/creative.py b/twitter_ads/creative.py index 87023d7..aafd6de 100644 --- a/twitter_ads/creative.py +++ b/twitter_ads/creative.py @@ -117,7 +117,7 @@ class MediaCreative(Analytics, Resource, Persistence): resource_property(MediaCreative, 'created_at', readonly=True, transform=TRANSFORM.TIME) resource_property(MediaCreative, 'deleted', readonly=True, transform=TRANSFORM.BOOL) resource_property(MediaCreative, 'id', readonly=True) -resource_property(MediaCreative, 'serving_status', readonly=True) +resource_property(MediaCreative, 'entity_status', readonly=True) resource_property(MediaCreative, 'updated_at', readonly=True, transform=TRANSFORM.TIME) # writable resource_property(MediaCreative, 'account_media_id') diff --git a/twitter_ads/enum.py b/twitter_ads/enum.py index 722dd76..28f5567 100644 --- a/twitter_ads/enum.py +++ b/twitter_ads/enum.py @@ -155,7 +155,12 @@ def enum(**enums): ALL_ON_TWITTER='ALL_ON_TWITTER', TWITTER_SEARCH='TWITTER_SEARCH', TWITTER_TIMELINE='TWITTER_TIMELINE', - PUBLISHER_NETWORK='PUBLISHER_NETWORK' + PUBLISHER_NETWORK='PUBLISHER_NETWORK', + TAP_FULL='TAP_FULL', + TAP_FULL_LANDSCAPE='TAP_FULL_LANDSCAPE', + TAP_BANNER='TAP_BANNER', + TAP_NATIVE='TAP_NATIVE', + TAP_MRECT="TAP_MRECT" ) PRODUCT = enum( diff --git a/twitter_ads/http.py b/twitter_ads/http.py index 1931f12..c7e15b8 100644 --- a/twitter_ads/http.py +++ b/twitter_ads/http.py @@ -75,10 +75,12 @@ def __oauth_request(self): if 'headers' in self.options: headers.update(self.options['headers'].copy()) - # internal-only + # DEPRECATED: internal-only (Should pass a header to the client) if 'x-as-user' in self._client.options: headers['x-as-user'] = self._client.options.get('x-as-user') - + # Add headers from the client to the request (Client headers take priority) + for key, val in self._client.headers.items(): + headers[key] = val params = self.options.get('params', None) data = self.options.get('body', None) files = self.options.get('files', None) diff --git a/twitter_ads/targeting.py b/twitter_ads/targeting.py index 621425c..b185ba0 100644 --- a/twitter_ads/targeting.py +++ b/twitter_ads/targeting.py @@ -3,19 +3,28 @@ """Container for all targeting related logic used by the Ads API SDK.""" from twitter_ads.http import Request +from twitter_ads.resource import resource_property, Resource, Persistence from twitter_ads import API_VERSION +from twitter_ads.utils import FlattenParams +import json -class ReachEstimate(object): +class AudienceSummary(Resource, Persistence): + PROPERTIES = {} - RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/reach_estimate' + RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/audience_summary' @classmethod - def fetch(klass, account, product_type, objective, user_id, **kwargs): - params = {'product_type': product_type, 'objective': objective, 'user_id': user_id} - params.update(kwargs) - + @FlattenParams + def load(klass, account, params): resource = klass.RESOURCE.format(account_id=account.id) - response = Request(account.client, 'get', resource, params=params).perform() + headers = {'Content-Type': 'application/json'} + response = Request(account.client, + 'post', + resource, + headers=headers, + body=json.dumps(params)).perform() + return klass(account).from_response(response.body['data']) + - return response.body['data'] +resource_property(AudienceSummary, 'audience_size') From 421589e07d92d5a6ffda127e108d04af7c24155f Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Tue, 15 Mar 2022 13:39:05 -0700 Subject: [PATCH 16/27] updated cards class and examples --- examples/unified_cards.py | 82 +++++++++++++++++++++++++++++++++++++++ twitter_ads/creative.py | 44 +++++++++++++-------- 2 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 examples/unified_cards.py diff --git a/examples/unified_cards.py b/examples/unified_cards.py new file mode 100644 index 0000000..a835add --- /dev/null +++ b/examples/unified_cards.py @@ -0,0 +1,82 @@ +from twitter_ads.client import Client +from twitter_ads.creative import Card +from twitter_ads.http import Request + + +CONSUMER_KEY = 'your consumer key' +CONSUMER_SECRET = 'your consumer secret' +ACCESS_TOKEN = 'user access token' +ACCESS_TOKEN_SECRET = 'user access token secret' +ACCOUNT_ID = 'ads account id' + +# initialize the client +client = Client(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET) + +# load the advertiser account instance +account = client.accounts(ACCOUNT_ID) + +# fetch all +card = Card.all(account, card_ids="1502039998987587584").first + +# fetch by card-id +card = Card.load(account=account, id="1502039998987587584") + +# edit card destination.url +card.components= [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/newvalue", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ] + +card.save() +print(card.components) + +# create new card +newcard = Card(account=account) +newcard.name="my new card" +components= [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/login", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ] +newcard.components=components +newcard.save() +print(newcard.id) \ No newline at end of file diff --git a/twitter_ads/creative.py b/twitter_ads/creative.py index ab150f0..a34cd6c 100644 --- a/twitter_ads/creative.py +++ b/twitter_ads/creative.py @@ -596,34 +596,44 @@ class Card(Resource): PROPERTIES = {} + RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/{id}' RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards' - @classmethod - def create(klass, account, name, components): - method = 'post' - resource = klass.RESOURCE_COLLECTION.format(account_id=account.id) + def save(self): + if self.id: + method = 'put' + resource = self.RESOURCE.format(account_id=self.account.id, id=self.id) + else: + method = 'post' + resource = self.RESOURCE_COLLECTION.format(account_id=self.account.id) + headers = {'Content-Type': 'application/json'} - payload = {'name': name, 'components': components} - response = Request( - account.client, method, resource, - headers=headers, body=json.dumps(payload) + payload = {'name': self.name, 'components': self.components} + response = Request(self.account.client, method, resource, headers=headers, + body=json.dumps(payload) ).perform() - return klass(account).from_response(response.body['data']) - - def load(klass): - raise AttributeError("'Card' object has no attribute 'load'") + return self.from_response(response.body['data']) - def reload(klass): - raise AttributeError("'Card' object has no attribute 'reload'") + @classmethod + @FlattenParams + def load(klass, account, id, **kwargs): + resource = klass.RESOURCE.format(account_id=account.id, id=id) + response = Request(account.client, 'get', resource, params=kwargs).perform() + return klass(account).from_response(response.body['data']) + + def reload(self): + if self.id: + self.load(self.account, card_id=self.id) # card properties # read-only +resource_property(Card, 'id', readonly=True) resource_property(Card, 'card_uri', readonly=True) resource_property(Card, 'card_type', readonly=True) resource_property(Card, 'created_at', readonly=True, transform=TRANSFORM.TIME) resource_property(Card, 'deleted', readonly=True, transform=TRANSFORM.BOOL) resource_property(Card, 'updated_at', readonly=True, transform=TRANSFORM.TIME) -# these are writable, but not in the sense that they can be set on an object and then saved -resource_property(Card, 'name', readonly=True) -resource_property(Card, 'components', readonly=True, transform=TRANSFORM.LIST) +# these are writable +resource_property(Card, 'name') +resource_property(Card, 'components', transform=TRANSFORM.LIST) From 559bafeda3a7f4ab4aed51f55b1a9208b6ba0945 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Tue, 15 Mar 2022 14:19:48 -0700 Subject: [PATCH 17/27] added tests --- tests/fixtures/cards_all.json | 836 +++++++++++++++++++++++++++++++++ tests/fixtures/cards_load.json | 42 ++ tests/test_cards.py | 69 +++ 3 files changed, 947 insertions(+) create mode 100644 tests/fixtures/cards_all.json create mode 100644 tests/fixtures/cards_load.json create mode 100644 tests/test_cards.py diff --git a/tests/fixtures/cards_all.json b/tests/fixtures/cards_all.json new file mode 100644 index 0000000..1feec12 --- /dev/null +++ b/tests/fixtures/cards_all.json @@ -0,0 +1,836 @@ +{ + "request": { + "params": { + "account_id": "2iqph" + } + }, + "next_cursor": null, + "data": [ + { + "name": "website carousel", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "https://www.dell.de/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1340029888649076737", + "created_at": "2020-12-18T20:23:16Z", + "card_uri": "card://1340029888649076737", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410461519343591424", + "created_at": "2021-07-01T04:53:25Z", + "card_uri": "card://1410461519343591424", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410461595289853954", + "created_at": "2021-07-01T04:53:44Z", + "card_uri": "card://1410461595289853954", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410461798390591491", + "created_at": "2021-07-01T04:54:32Z", + "card_uri": "card://1410461798390591491", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410461877499359237", + "created_at": "2021-07-01T04:54:51Z", + "card_uri": "card://1410461877499359237", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410461997494276100", + "created_at": "2021-07-01T04:55:19Z", + "card_uri": "card://1410461997494276100", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410462120282521603", + "created_at": "2021-07-01T04:55:49Z", + "card_uri": "card://1410462120282521603", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410462217586102272", + "created_at": "2021-07-01T04:56:12Z", + "card_uri": "card://1410462217586102272", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410462382632042496", + "created_at": "2021-07-01T04:56:51Z", + "card_uri": "card://1410462382632042496", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410702122887237632", + "created_at": "2021-07-01T20:49:30Z", + "card_uri": "card://1410702122887237632", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410702325044301827", + "created_at": "2021-07-01T20:50:18Z", + "card_uri": "card://1410702325044301827", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410702487212883969", + "created_at": "2021-07-01T20:50:57Z", + "card_uri": "card://1410702487212883969", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410702742541148162", + "created_at": "2021-07-01T20:51:58Z", + "card_uri": "card://1410702742541148162", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410703177888849923", + "created_at": "2021-07-01T20:53:41Z", + "card_uri": "card://1410703177888849923", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "video website card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1410703377667821568", + "created_at": "2021-07-01T20:54:29Z", + "card_uri": "card://1410703377667821568", + "updated_at": "2021-08-26T19:09:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "new card pytest", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1502039535651196928", + "created_at": "2022-03-10T21:51:46Z", + "card_uri": "card://1502039535651196928", + "updated_at": "2022-03-10T21:51:46Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "new card pytest", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1502039980394254337", + "created_at": "2022-03-10T21:53:32Z", + "card_uri": "card://1502039980394254337", + "updated_at": "2022-03-10T21:53:32Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "new card pytest", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/newvalue", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1502039983049232388", + "created_at": "2022-03-10T21:53:33Z", + "card_uri": "card://1502039983049232388", + "updated_at": "2022-03-10T21:53:33Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "new card pytest", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1502039996441628673", + "created_at": "2022-03-10T21:53:36Z", + "card_uri": "card://1502039996441628673", + "updated_at": "2022-03-10T21:53:36Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "new card pytest", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/newvalue", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1502039998987587584", + "created_at": "2022-03-10T21:53:36Z", + "card_uri": "card://1502039998987587584", + "updated_at": "2022-03-10T21:53:36Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "my new card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/login", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1503829009708175360", + "created_at": "2022-03-15T20:22:30Z", + "card_uri": "card://1503829009708175360", + "updated_at": "2022-03-15T20:22:30Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "my new card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/login", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1503829523753697280", + "created_at": "2022-03-15T20:24:32Z", + "card_uri": "card://1503829523753697280", + "updated_at": "2022-03-15T20:24:32Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "my new card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/login", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1503829695233617920", + "created_at": "2022-03-15T20:25:13Z", + "card_uri": "card://1503829695233617920", + "updated_at": "2022-03-15T20:25:13Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "my new card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/login", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1503829883746598912", + "created_at": "2022-03-15T20:25:58Z", + "card_uri": "card://1503829883746598912", + "updated_at": "2022-03-15T20:25:58Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + }, + { + "name": "my new card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/login", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1503831318555086849", + "created_at": "2022-03-15T20:31:40Z", + "card_uri": "card://1503831318555086849", + "updated_at": "2022-03-15T20:31:40Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + } + ] + } + \ No newline at end of file diff --git a/tests/fixtures/cards_load.json b/tests/fixtures/cards_load.json new file mode 100644 index 0000000..502f874 --- /dev/null +++ b/tests/fixtures/cards_load.json @@ -0,0 +1,42 @@ +{ + "request": { + "params": { + "account_id": "2iqph", + "card_id": "1503831318555086849" + } + }, + "data": { + "name": "my new card", + "components": [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/login", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ], + "id": "1503831318555086849", + "created_at": "2022-03-15T20:31:40Z", + "card_uri": "card://1503831318555086849", + "updated_at": "2022-03-15T20:31:40Z", + "deleted": false, + "card_type": "VIDEO_WEBSITE" + } + } + \ No newline at end of file diff --git a/tests/test_cards.py b/tests/test_cards.py new file mode 100644 index 0000000..f168b62 --- /dev/null +++ b/tests/test_cards.py @@ -0,0 +1,69 @@ +import responses +import unittest + +from tests.support import with_resource, with_fixture, characters + +from twitter_ads.account import Account +from twitter_ads.campaign import Campaign +from twitter_ads.creative import Card +from twitter_ads.client import Client +from twitter_ads.cursor import Cursor +from twitter_ads import API_VERSION + + +@responses.activate +def test_cards_all(): + responses.add(responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph'), + body=with_fixture('accounts_load'), + content_type='application/json') + + responses.add(responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph/cards'), + body=with_fixture('cards_all'), + content_type='application/json') + + client = Client( + characters(40), + characters(40), + characters(40), + characters(40) + ) + + account = Account.load(client, '2iqph') + + cursor = Card.all(account) + assert cursor is not None + assert isinstance(cursor, Cursor) + + card = cursor.next() + assert card.id == '1340029888649076737' + assert card.card_type == 'VIDEO_WEBSITE' + assert len(card.components) == 2 + + +@responses.activate +def test_card_load(): + responses.add(responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph'), + body=with_fixture('accounts_load'), + content_type='application/json') + + responses.add(responses.GET, + with_resource('/' + API_VERSION + '/accounts/2iqph/cards/1503831318555086849'), + body=with_fixture('cards_load'), + content_type='application/json') + + client = Client( + characters(40), + characters(40), + characters(40), + characters(40) + ) + + account = Account.load(client, '2iqph') + + card = Card.load(account, '1503831318555086849') + assert card.id == '1503831318555086849' + assert card.card_type == 'VIDEO_WEBSITE' + assert card.card_uri == 'card://1503831318555086849' From d3cf0ae9aaea7f005b5bbb15f603f89c3663eeb1 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Tue, 15 Mar 2022 14:31:08 -0700 Subject: [PATCH 18/27] fix flake8 tests --- twitter_ads/creative.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/twitter_ads/creative.py b/twitter_ads/creative.py index a34cd6c..52cf24e 100644 --- a/twitter_ads/creative.py +++ b/twitter_ads/creative.py @@ -610,8 +610,8 @@ def save(self): headers = {'Content-Type': 'application/json'} payload = {'name': self.name, 'components': self.components} response = Request(self.account.client, method, resource, headers=headers, - body=json.dumps(payload) - ).perform() + body=json.dumps(payload) + ).perform() return self.from_response(response.body['data']) @classmethod @@ -620,7 +620,7 @@ def load(klass, account, id, **kwargs): resource = klass.RESOURCE.format(account_id=account.id, id=id) response = Request(account.client, 'get', resource, params=kwargs).perform() return klass(account).from_response(response.body['data']) - + def reload(self): if self.id: self.load(self.account, card_id=self.id) From 2c358cc977f5460fd0c5a298e79e5da5da19ad1b Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Wed, 30 Mar 2022 16:53:47 -0700 Subject: [PATCH 19/27] updated cards example --- examples/cards.py | 80 +++++++++++++++++++++++++++++++++----- examples/unified_cards.py | 82 --------------------------------------- 2 files changed, 70 insertions(+), 92 deletions(-) delete mode 100644 examples/unified_cards.py diff --git a/examples/cards.py b/examples/cards.py index 79acdcd..9b8aeae 100644 --- a/examples/cards.py +++ b/examples/cards.py @@ -1,14 +1,13 @@ from twitter_ads.client import Client from twitter_ads.creative import Card -from twitter_ads.campaign import Tweet -from twitter_ads.restapi import UserIdLookup +from twitter_ads.http import Request CONSUMER_KEY = 'your consumer key' CONSUMER_SECRET = 'your consumer secret' -ACCESS_TOKEN = 'access token' -ACCESS_TOKEN_SECRET = 'access token secret' -ACCOUNT_ID = 'account id' +ACCESS_TOKEN = 'user access token' +ACCESS_TOKEN_SECRET = 'user access token secret' +ACCOUNT_ID = 'ads account id' # initialize the client client = Client(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET) @@ -16,14 +15,75 @@ # load the advertiser account instance account = client.accounts(ACCOUNT_ID) -# create the card -name = 'video website card' -components = [{"type":"MEDIA","media_key":"13_1191948012077092867"},{"type":"DETAILS","title":"Twitter","destination":{"type":"WEBSITE", "url":"http://twitter.com/"}}] -video_website_card = Card.create(account, name=name, components=components) +# fetch all +card = Card.all(account, card_ids="1502039998987587584").first + +# fetch by card-id +card = Card.load(account=account, id="1502039998987587584") + +# edit card destination.url +card.components= [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/newvalue", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ] + +card.save() +print(card.components) + +# create new card +newcard = Card(account=account) +newcard.name="my new card" +components= [ + { + "media_key": "13_794652834998325248", + "media_metadata": { + "13_794652834998325248": { + "type": "VIDEO", + "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", + "width": 640, + "height": 360, + "video_duration": 7967, + "video_aspect_ratio": "16:9" + } + }, + "type": "MEDIA" + }, + { + "title": "Twitter", + "destination": { + "url": "http://twitter.com/login", + "type": "WEBSITE" + }, + "type": "DETAILS" + } + ] +newcard.components=components +newcard.save() +print(newcard.id) # get user_id for as_user_id parameter user_id = UserIdLookup.load(account, screen_name='your_twitter_handle_name').id # create a tweet using this new card -Tweet.create(account, text='Created from the SDK', as_user_id=user_id, card_uri=video_website_card.card_uri) +Tweet.create(account, text='Created from the SDK', as_user_id=user_id, card_uri=card.card_uri) # https://twitter.com/apimctestface/status/1372283476615958529 diff --git a/examples/unified_cards.py b/examples/unified_cards.py deleted file mode 100644 index a835add..0000000 --- a/examples/unified_cards.py +++ /dev/null @@ -1,82 +0,0 @@ -from twitter_ads.client import Client -from twitter_ads.creative import Card -from twitter_ads.http import Request - - -CONSUMER_KEY = 'your consumer key' -CONSUMER_SECRET = 'your consumer secret' -ACCESS_TOKEN = 'user access token' -ACCESS_TOKEN_SECRET = 'user access token secret' -ACCOUNT_ID = 'ads account id' - -# initialize the client -client = Client(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET) - -# load the advertiser account instance -account = client.accounts(ACCOUNT_ID) - -# fetch all -card = Card.all(account, card_ids="1502039998987587584").first - -# fetch by card-id -card = Card.load(account=account, id="1502039998987587584") - -# edit card destination.url -card.components= [ - { - "media_key": "13_794652834998325248", - "media_metadata": { - "13_794652834998325248": { - "type": "VIDEO", - "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", - "width": 640, - "height": 360, - "video_duration": 7967, - "video_aspect_ratio": "16:9" - } - }, - "type": "MEDIA" - }, - { - "title": "Twitter", - "destination": { - "url": "http://twitter.com/newvalue", - "type": "WEBSITE" - }, - "type": "DETAILS" - } - ] - -card.save() -print(card.components) - -# create new card -newcard = Card(account=account) -newcard.name="my new card" -components= [ - { - "media_key": "13_794652834998325248", - "media_metadata": { - "13_794652834998325248": { - "type": "VIDEO", - "url": "https://video.twimg.com/amplify_video/794652834998325248/vid/640x360/pUgE2UKcfPwF_5Uh.mp4", - "width": 640, - "height": 360, - "video_duration": 7967, - "video_aspect_ratio": "16:9" - } - }, - "type": "MEDIA" - }, - { - "title": "Twitter", - "destination": { - "url": "http://twitter.com/login", - "type": "WEBSITE" - }, - "type": "DETAILS" - } - ] -newcard.components=components -newcard.save() -print(newcard.id) \ No newline at end of file From bfab444d64125ddf8a2d3a1d3c51e1b03b2f6b52 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Thu, 31 Mar 2022 10:53:27 -0700 Subject: [PATCH 20/27] remove deprecated card types --- twitter_ads/creative.py | 122 ---------------------------------------- 1 file changed, 122 deletions(-) diff --git a/twitter_ads/creative.py b/twitter_ads/creative.py index 52cf24e..3133cd1 100644 --- a/twitter_ads/creative.py +++ b/twitter_ads/creative.py @@ -126,128 +126,6 @@ class MediaCreative(Analytics, Resource, Persistence): resource_property(MediaCreative, 'line_item_id') -class WebsiteCard(Resource, Persistence): - - PROPERTIES = {} - - RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/website' - RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/website/{id}' - - -# website card properties -# read-only -resource_property(WebsiteCard, 'card_type', readonly=True) -resource_property(WebsiteCard, 'card_uri', readonly=True) -resource_property(WebsiteCard, 'created_at', readonly=True, transform=TRANSFORM.TIME) -resource_property(WebsiteCard, 'id', readonly=True) -resource_property(WebsiteCard, 'media_url', readonly=True) -resource_property(WebsiteCard, 'image_display_height', readonly=True) -resource_property(WebsiteCard, 'image_display_width', readonly=True) -resource_property(WebsiteCard, 'deleted', readonly=True, transform=TRANSFORM.BOOL) -resource_property(WebsiteCard, 'website_dest_url', readonly=True) -resource_property(WebsiteCard, 'website_display_url', readonly=True) -resource_property(WebsiteCard, 'updated_at', readonly=True, transform=TRANSFORM.TIME) -# writable -resource_property(WebsiteCard, 'media_key') -resource_property(WebsiteCard, 'name') -resource_property(WebsiteCard, 'website_title') -resource_property(WebsiteCard, 'website_url') - - -class VideoWebsiteCard(Resource, Persistence): - - PROPERTIES = {} - - RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/video_website' - RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/video_website/{id}' - - -# video website card properties -# read-only -resource_property(VideoWebsiteCard, 'account_id', readonly=True) -resource_property(VideoWebsiteCard, 'card_type', readonly=True) -resource_property(VideoWebsiteCard, 'card_uri', readonly=True) -resource_property(VideoWebsiteCard, 'created_at', readonly=True, transform=TRANSFORM.TIME) -resource_property(VideoWebsiteCard, 'deleted', readonly=True, transform=TRANSFORM.BOOL) -resource_property(VideoWebsiteCard, 'id', readonly=True) -resource_property(VideoWebsiteCard, 'updated_at', readonly=True, transform=TRANSFORM.TIME) -resource_property(VideoWebsiteCard, 'video_height', readonly=True) -resource_property(VideoWebsiteCard, 'video_owner_id', readonly=True) -resource_property(VideoWebsiteCard, 'video_poster_height', readonly=True) -resource_property(VideoWebsiteCard, 'poster_media_url', readonly=True) -resource_property(VideoWebsiteCard, 'video_poster_width', readonly=True) -resource_property(VideoWebsiteCard, 'media_url', readonly=True) -resource_property(VideoWebsiteCard, 'video_width', readonly=True) -resource_property(VideoWebsiteCard, 'website_dest_url', readonly=True) -resource_property(VideoWebsiteCard, 'website_display_url', readonly=True) -# writable -resource_property(VideoWebsiteCard, 'name') -resource_property(VideoWebsiteCard, 'title') -resource_property(VideoWebsiteCard, 'media_key') -resource_property(VideoWebsiteCard, 'website_url') - - -class ImageAppDownloadCard(Resource, Persistence): - - PROPERTIES = {} - - RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/image_app_download' - RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/image_app_download/{id}' - - -# image app download card properties -# read-only -resource_property(ImageAppDownloadCard, 'id', readonly=True) -resource_property(ImageAppDownloadCard, 'image_display_height', readonly=True) -resource_property(ImageAppDownloadCard, 'image_display_width', readonly=True) -resource_property(ImageAppDownloadCard, 'media_url', readonly=True) -resource_property(ImageAppDownloadCard, 'card_uri', readonly=True) -resource_property(ImageAppDownloadCard, 'card_type', readonly=True) -resource_property(ImageAppDownloadCard, 'created_at', readonly=True, transform=TRANSFORM.TIME) -resource_property(ImageAppDownloadCard, 'updated_at', readonly=True, transform=TRANSFORM.TIME) -resource_property(ImageAppDownloadCard, 'deleted', readonly=True, transform=TRANSFORM.BOOL) -# writable -resource_property(ImageAppDownloadCard, 'country_code') -resource_property(ImageAppDownloadCard, 'app_cta') -resource_property(ImageAppDownloadCard, 'ios_app_store_identifier') -resource_property(ImageAppDownloadCard, 'ios_deep_link') -resource_property(ImageAppDownloadCard, 'googleplay_app_id') -resource_property(ImageAppDownloadCard, 'googleplay_deep_link') -resource_property(ImageAppDownloadCard, 'name') -resource_property(ImageAppDownloadCard, 'media_key') - - -class VideoAppDownloadCard(Resource, Persistence): - - PROPERTIES = {} - - RESOURCE_COLLECTION = '/' + API_VERSION + '/accounts/{account_id}/cards/video_app_download' - RESOURCE = '/' + API_VERSION + '/accounts/{account_id}/cards/video_app_download/{id}' - - -# video app download card properties -# read-only -resource_property(VideoAppDownloadCard, 'card_uri', readonly=True) -resource_property(VideoAppDownloadCard, 'card_type', readonly=True) -resource_property(VideoAppDownloadCard, 'created_at', readonly=True, transform=TRANSFORM.TIME) -resource_property(VideoAppDownloadCard, 'deleted', readonly=True, transform=TRANSFORM.BOOL) -resource_property(VideoAppDownloadCard, 'id', readonly=True) -resource_property(VideoAppDownloadCard, 'updated_at', readonly=True, transform=TRANSFORM.TIME) -resource_property(VideoAppDownloadCard, 'video_owner_id', readonly=True) -resource_property(VideoAppDownloadCard, 'poster_media_url', readonly=True) -resource_property(VideoAppDownloadCard, 'media_url', readonly=True) -# writable -resource_property(VideoAppDownloadCard, 'country_code') -resource_property(VideoAppDownloadCard, 'app_cta') -resource_property(VideoAppDownloadCard, 'poster_media_key') -resource_property(VideoAppDownloadCard, 'ios_app_store_identifier') -resource_property(VideoAppDownloadCard, 'ios_deep_link') -resource_property(VideoAppDownloadCard, 'googleplay_app_id') -resource_property(VideoAppDownloadCard, 'googleplay_deep_link') -resource_property(VideoAppDownloadCard, 'name') -resource_property(VideoAppDownloadCard, 'media_key') - - class ImageConversationCard(Resource, Persistence): PROPERTIES = {} From b26d36180baa55e8a3f729c414291f5c0928a416 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Thu, 31 Mar 2022 10:59:14 -0700 Subject: [PATCH 21/27] line item budget optimization changes --- twitter_ads/campaign.py | 2 ++ twitter_ads/enum.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/twitter_ads/campaign.py b/twitter_ads/campaign.py index 71ca4b9..c3d7fd2 100644 --- a/twitter_ads/campaign.py +++ b/twitter_ads/campaign.py @@ -243,6 +243,7 @@ class Campaign(Analytics, Resource, Persistence, Batch): resource_property(Campaign, 'standard_delivery', transform=TRANSFORM.BOOL) resource_property(Campaign, 'start_time', transform=TRANSFORM.TIME) resource_property(Campaign, 'total_budget_amount_local_micro') +resource_property(Campaign, 'budget_optimization') # sdk-only resource_property(Campaign, 'to_delete', transform=TRANSFORM.BOOL) @@ -301,6 +302,7 @@ def save(self): resource_property(LineItem, 'start_time', transform=TRANSFORM.TIME) resource_property(LineItem, 'total_budget_amount_local_micro') resource_property(LineItem, 'tracking_tags') +resource_property(Campaign, 'standard_delivery', transform=TRANSFORM.BOOL) # sdk-only resource_property(LineItem, 'to_delete', transform=TRANSFORM.BOOL) diff --git a/twitter_ads/enum.py b/twitter_ads/enum.py index 9ab2716..741fc45 100644 --- a/twitter_ads/enum.py +++ b/twitter_ads/enum.py @@ -226,3 +226,7 @@ def enum(**enums): SIMILAR_TO_FOLLOWERS_OF_USER='SIMILAR_TO_FOLLOWERS_OF_USER', TV_SHOWS='TV_SHOWS' ) + +BUDGET_OPTIMIZATION = enum( + CAMPAIGN='CAMPAIGN' +) From f1316a4c6d58848110fa73adb6009826f8ea9a56 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Thu, 31 Mar 2022 10:59:27 -0700 Subject: [PATCH 22/27] bump version --- twitter_ads/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twitter_ads/__init__.py b/twitter_ads/__init__.py index 4931818..d4fc7ad 100644 --- a/twitter_ads/__init__.py +++ b/twitter_ads/__init__.py @@ -1,8 +1,8 @@ # Copyright (C) 2015 Twitter, Inc. -VERSION = (10, 0, 0) -API_VERSION = '10' +VERSION = (11, 0, 0) +API_VERSION = '11' from twitter_ads.utils import get_version From 223975e2df233b7c5c46fe5f833574c40ac1c020 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Thu, 31 Mar 2022 11:05:09 -0700 Subject: [PATCH 23/27] removed references to video website cards --- twitter_ads/account.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/twitter_ads/account.py b/twitter_ads/account.py index c7c87a8..61443a1 100644 --- a/twitter_ads/account.py +++ b/twitter_ads/account.py @@ -10,7 +10,7 @@ from twitter_ads.resource import resource_property, Resource from twitter_ads.creative import (AccountMedia, MediaCreative, ScheduledTweet, - Card, VideoWebsiteCard, PromotedTweet) + Card, PromotedTweet) from twitter_ads.audience import CustomAudience from twitter_ads.campaign import (AppList, Campaign, FundingInstrument, LineItem, PromotableUser, TrackingTags, ScheduledPromotedTweet) @@ -154,12 +154,6 @@ def tracking_tags(self, id=None, **kwargs): """ return self._load_resource(TrackingTags, id, **kwargs) - def video_website_cards(self, id=None, **kwargs): - """ - Returns a collection of video website cards available to the current account. - """ - return self._load_resource(VideoWebsiteCard, id, **kwargs) - def cards(self, id=None, **kwargs): """ Returns a collection of Cards available to the current account. From c6ee7f19a918d09521d81b95eaca429618a7a49c Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Tue, 12 Apr 2022 10:18:46 -0700 Subject: [PATCH 24/27] removed start/end times from campaigns --- twitter_ads/campaign.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/twitter_ads/campaign.py b/twitter_ads/campaign.py index c3d7fd2..0df63dc 100644 --- a/twitter_ads/campaign.py +++ b/twitter_ads/campaign.py @@ -235,13 +235,11 @@ class Campaign(Analytics, Resource, Persistence, Batch): # writable resource_property(Campaign, 'daily_budget_amount_local_micro') resource_property(Campaign, 'duration_in_days', transform=TRANSFORM.INT) -resource_property(Campaign, 'end_time', transform=TRANSFORM.TIME) resource_property(Campaign, 'entity_status') resource_property(Campaign, 'frequency_cap', transform=TRANSFORM.INT) resource_property(Campaign, 'funding_instrument_id') resource_property(Campaign, 'name') resource_property(Campaign, 'standard_delivery', transform=TRANSFORM.BOOL) -resource_property(Campaign, 'start_time', transform=TRANSFORM.TIME) resource_property(Campaign, 'total_budget_amount_local_micro') resource_property(Campaign, 'budget_optimization') # sdk-only From 0521e8194086cc5a8d2f7cc9d6b60fff4c6ff393 Mon Sep 17 00:00:00 2001 From: Tom Osowski Date: Fri, 22 Apr 2022 09:17:27 -0700 Subject: [PATCH 25/27] Remove string versions of IDs --- examples/draft_tweet.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/draft_tweet.py b/examples/draft_tweet.py index 0c15e22..03150be 100644 --- a/examples/draft_tweet.py +++ b/examples/draft_tweet.py @@ -22,7 +22,7 @@ # fetch draft tweets from a given account tweets = DraftTweet.all(account) for tweet in tweets: - print(tweet.id_str) + print(tweet.id) print(tweet.text) # create a new draft tweet @@ -30,19 +30,19 @@ draft_tweet.text = 'draft tweet - new' draft_tweet.as_user_id = user_id draft_tweet = draft_tweet.save() -print(draft_tweet.id_str) +print(draft_tweet.id) print(draft_tweet.text) # fetch single draft tweet metadata -tweet_id = draft_tweet.id_str +tweet_id = draft_tweet.id draft_tweet = draft_tweet.load(account, tweet_id) -print(draft_tweet.id_str) +print(draft_tweet.id) print(draft_tweet.text) # update (PUT) metadata draft_tweet.text = 'draft tweet - update' draft_tweet = draft_tweet.save() -print(draft_tweet.id_str) +print(draft_tweet.id) print(draft_tweet.text) # create a nullcasted tweet using draft tweet metadata From fe1a3c05f3c9971f6f90415c818f2deefe794c66 Mon Sep 17 00:00:00 2001 From: Tom Osowski Date: Fri, 22 Apr 2022 09:18:39 -0700 Subject: [PATCH 26/27] Remove string versions of IDs --- twitter_ads/creative.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/twitter_ads/creative.py b/twitter_ads/creative.py index 3133cd1..6abd865 100644 --- a/twitter_ads/creative.py +++ b/twitter_ads/creative.py @@ -211,7 +211,6 @@ class ScheduledTweet(Resource, Persistence): resource_property(ScheduledTweet, 'created_at', readonly=True, transform=TRANSFORM.TIME) resource_property(ScheduledTweet, 'completed_at', read_only=True, transform=TRANSFORM.TIME) resource_property(ScheduledTweet, 'id', read_only=True) -resource_property(ScheduledTweet, 'id_str', read_only=True) resource_property(ScheduledTweet, 'scheduled_status', read_only=True) resource_property(ScheduledTweet, 'tweet_id', readonly=True) resource_property(ScheduledTweet, 'updated_at', readonly=True, transform=TRANSFORM.TIME) @@ -236,7 +235,6 @@ class DraftTweet(Resource, Persistence): # draft tweet properties # read-only resource_property(DraftTweet, 'id', read_only=True) -resource_property(DraftTweet, 'id_str', read_only=True) resource_property(DraftTweet, 'created_at', read_only=True, transform=TRANSFORM.TIME) resource_property(DraftTweet, 'updated_at', readonly=True, transform=TRANSFORM.TIME) resource_property(DraftTweet, 'user_id', read_only=True) From 22801f823d98e0f7248b4901db8dd05b3663cba4 Mon Sep 17 00:00:00 2001 From: Tom Osowski Date: Fri, 22 Apr 2022 09:38:05 -0700 Subject: [PATCH 27/27] Remove _str versions of parameters --- tests/fixtures/analytics_async_get.json | 2 +- tests/fixtures/analytics_async_post.json | 2 +- tests/fixtures/tweets_get.json | 8 +++----- tests/test_analytics_async.py | 2 +- twitter_ads/analytics.py | 1 - 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/fixtures/analytics_async_get.json b/tests/fixtures/analytics_async_get.json index 05f1fa9..d189c01 100644 --- a/tests/fixtures/analytics_async_get.json +++ b/tests/fixtures/analytics_async_get.json @@ -12,7 +12,7 @@ "start_time": "2019-01-01T00:00:00Z", "segmentation_type": null, "url": "https://ton.twimg.com/advertiser-api-async-analytics/stats.json.gz", - "id_str": "111111111111111111", + "id": "111111111111111111", "entity_ids": [ "aaaa" ], diff --git a/tests/fixtures/analytics_async_post.json b/tests/fixtures/analytics_async_post.json index 8140e70..ec5cd54 100644 --- a/tests/fixtures/analytics_async_post.json +++ b/tests/fixtures/analytics_async_post.json @@ -18,7 +18,7 @@ "start_time": "2019-01-01T00:00:00Z", "segmentation_type": null, "url": null, - "id_str": "111111111111111111", + "id": "111111111111111111", "entity_ids": [ "aaaa" ], diff --git a/tests/fixtures/tweets_get.json b/tests/fixtures/tweets_get.json index b51fe27..4760c6f 100644 --- a/tests/fixtures/tweets_get.json +++ b/tests/fixtures/tweets_get.json @@ -28,14 +28,13 @@ "favorite_count": 0, "in_reply_to_status_id_str": null, "geo": null, - "id_str": "1166476031668015104", + "id": "1166476031668015104", "scopes": { "followers": false }, "in_reply_to_user_id": null, "truncated": false, "retweet_count": 0, - "id": 1166476031668015104, "in_reply_to_status_id": null, "nullcast": true, "created_at": "Tue Aug 27 22:22:12 +0000 2019", @@ -51,10 +50,9 @@ "in_reply_to_screen_name": null, "in_reply_to_user_id_str": null, "user": { - "id": 756201191646691328, - "id_str": "756201191646691328" + "id": "756201191646691328" }, "tweet_id": "1166476031668015104" } ] -} \ No newline at end of file +} diff --git a/tests/test_analytics_async.py b/tests/test_analytics_async.py index 0b6e123..d051e94 100644 --- a/tests/test_analytics_async.py +++ b/tests/test_analytics_async.py @@ -72,7 +72,7 @@ def test_analytics_async(): assert stats2.concurrent_job_limit == '100' # call async_stats_job_result() through Campaign class (inheritance) - job_id = stats.id_str + job_id = stats.id job_result = Campaign.async_stats_job_result( account, job_ids=[job_id]).first diff --git a/twitter_ads/analytics.py b/twitter_ads/analytics.py index 1690b9f..3d6458e 100644 --- a/twitter_ads/analytics.py +++ b/twitter_ads/analytics.py @@ -147,7 +147,6 @@ def active_entities(klass, account, start_time, end_time, **kwargs): # Analytics properties # read-only resource_property(Analytics, 'id', readonly=True) -resource_property(Analytics, 'id_str', readonly=True) resource_property(Analytics, 'status', readonly=True) resource_property(Analytics, 'url', readonly=True) resource_property(Analytics, 'created_at', readonly=True, transform=TRANSFORM.TIME)