From d3a9474accc47f2b180e5e8271955ef7f7b455af Mon Sep 17 00:00:00 2001 From: Zachary Trabookis Date: Thu, 21 Oct 2021 18:39:44 -0400 Subject: [PATCH 01/12] Fix issue when decoding the Current Customer JWT Payload The token created from this endpoint uses algorithm "HS512". Added this algorithm to the array. https://developer.bigcommerce.com/api-docs/storefront/current-customer-api Also noticed that when using `verify_payload` it gave an error since there were three values returned. Put a `__` to handle the last value returned. --- bigcommerce/connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bigcommerce/connection.py b/bigcommerce/connection.py index 3012a3e..473c9f6 100644 --- a/bigcommerce/connection.py +++ b/bigcommerce/connection.py @@ -222,7 +222,7 @@ def verify_payload(signed_payload, client_secret): Uses constant-time str comparison to prevent vulnerability to timing attacks. """ - encoded_json, encoded_hmac = signed_payload.split('.') + encoded_json, encoded_hmac, __ = signed_payload.split('.') dc_json = base64.b64decode(encoded_json) signature = base64.b64decode(encoded_hmac) expected_sig = hmac.new(client_secret.encode(), base64.b64decode(encoded_json), hashlib.sha256).hexdigest() @@ -237,7 +237,7 @@ def verify_payload_jwt(signed_payload, client_secret, client_id): """ return jwt.decode(signed_payload, client_secret, - algorithms=["HS256"], + algorithms=["HS256", "HS512"], audience=client_id, options={ 'verify_iss': False From 73c977c4cb76dff37e44e30bc131448f296a798a Mon Sep 17 00:00:00 2001 From: Zachary Trabookis Date: Wed, 27 Oct 2021 16:36:47 -0400 Subject: [PATCH 02/12] Fixing issue with failed unit test `TestOAuthConnection.test_verify_payload`. This `, __` variable doesn't need to be there. --- bigcommerce/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigcommerce/connection.py b/bigcommerce/connection.py index 473c9f6..7de7916 100644 --- a/bigcommerce/connection.py +++ b/bigcommerce/connection.py @@ -222,7 +222,7 @@ def verify_payload(signed_payload, client_secret): Uses constant-time str comparison to prevent vulnerability to timing attacks. """ - encoded_json, encoded_hmac, __ = signed_payload.split('.') + encoded_json, encoded_hmac = signed_payload.split('.') dc_json = base64.b64decode(encoded_json) signature = base64.b64decode(encoded_hmac) expected_sig = hmac.new(client_secret.encode(), base64.b64decode(encoded_json), hashlib.sha256).hexdigest() From 098cc3cb3a7db4373d08727e408009593cbe881b Mon Sep 17 00:00:00 2001 From: bc-zachary Date: Thu, 7 Jul 2022 09:52:16 -0500 Subject: [PATCH 03/12] APPEX-328 update pyjwt to 2.4.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 09a6bf3..167197c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,4 @@ mock==4.0.3 nose==1.3.7 nose-cov==1.6 requests==2.25.1 -pyjwt==2.1.0 \ No newline at end of file +pyjwt==2.4.0 \ No newline at end of file From 713e2185b090b39cd81e9fd1f7a806940b838d9e Mon Sep 17 00:00:00 2001 From: Nathan Booker Date: Thu, 7 Jul 2022 11:14:55 -0500 Subject: [PATCH 04/12] Move tests to GH actions --- .github/workflows/ci.yml | 25 +++++++++++++++++++++++++ .travis.yml | 5 ----- README.rst | 2 +- 3 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8c83df1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: continuous-integration + +on: + pull_request: + branches: [master] + push: + branches: [master] + +jobs: + tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + python-version: [3.7, 3.8, 3.9] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install Python dependencies + uses: py-actions/py-dependency-install@v3 + - name: Run Tests + run: nosetests -a '!broken' diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6704df5..0000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: python -python: - - "3.8" - - "3.9" -script: nosetests -a '!broken' diff --git a/README.rst b/README.rst index 2c9daca..ce1ce9b 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ Bigcommerce API Python Client Wrapper over the ``requests`` library for communicating with the Bigcommerce v2 API. Install with ``pip install bigcommerce`` or ``easy_install bigcommerce``. Tested with -python 3.8, and only requires ``requests`` and ``pyjwt``. +python 3.7-3.9, and only requires ``requests`` and ``pyjwt``. Usage ----- From 36c136e4cbf3e175730638f9db30e0cc50437699 Mon Sep 17 00:00:00 2001 From: Nathan Booker Date: Sun, 22 Jan 2023 14:14:26 -0600 Subject: [PATCH 05/12] Add basic GQL API support --- README.rst | 25 +++++++- bigcommerce/connection.py | 117 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index ce1ce9b..d04c757 100644 --- a/README.rst +++ b/README.rst @@ -116,7 +116,30 @@ it can be used to access V3 APIs using the OAuthConnection object: api_path='/stores/{}/v3/{}') v3client.get('/catalog/products', include_fields='name,sku', limit=5, page=1) -Managing OAuth Rate Limits +Accessing GraphQL Admin API +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +There is a basic GraphQL client which allows you to submit GraphQL queries to the GraphQL Admin API. + +:: + + gql = bigcommerce.connection.GraphQLConnection( + client_id=client_id, + store_hash=store_hash, + access_token=access_token + ) + # Make a basic query + time_query_result = gql.query(""" + query { + system { + time + } + } + """) + # Fetch the schema + schema = gql.introspection_query() + + +Managing Rate Limits ~~~~~~~~~~~~~~~~~~~~~~~~~~ You can optionally pass a ``rate_limiting_management`` object into ``bigcommerce.api.BigcommerceApi`` or ``bigcommerce.connection.OAuthConnection`` for automatic rate limiting management, ex: diff --git a/bigcommerce/connection.py b/bigcommerce/connection.py index 7de7916..6b9df11 100644 --- a/bigcommerce/connection.py +++ b/bigcommerce/connection.py @@ -287,3 +287,120 @@ def _handle_response(self, url, res, suppress_empty=True): callback() return result + + +class GraphQLConnection(OAuthConnection): + def __init__(self, client_id, store_hash, access_token=None, host='api.bigcommerce.com', + api_path='/stores/{}/graphql', rate_limiting_management=None): + self.client_id = client_id + self.store_hash = store_hash + self.host = host + self.api_path = api_path + self.graphql_path = "https://" + self.host + self.api_path.format(self.store_hash) + self.timeout = 7.0 # can attach to session? + self.rate_limiting_management = rate_limiting_management + + self._session = requests.Session() + self._session.headers = {"Accept": "application/json", + "Accept-Encoding": "gzip"} + if access_token and store_hash: + self._session.headers.update(self._oauth_headers(client_id, access_token)) + + self._last_response = None # for debugging + + self.rate_limit = {} + + def query(self, query, variables=None): + return self.post(self.graphql_path, dict(query=query, variables=variables)) + + def introspection_query(self): + return self.query(""" + fragment FullType on __Type { + kind + name + fields(includeDeprecated: true) { + name + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } + } + fragment InputValue on __InputValue { + name + type { + ...TypeRef + } + defaultValue + } + fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } + } + query IntrospectionQuery { + __schema { + queryType { + name + } + mutationType { + name + } + types { + ...FullType + } + directives { + name + locations + args { + ...InputValue + } + } + } + } + """) From 7043eb3bb7ce9fc669c0830f3228e57ecaa5e905 Mon Sep 17 00:00:00 2001 From: Nathan Booker Date: Thu, 30 Mar 2023 17:08:11 -0500 Subject: [PATCH 06/12] Release 0.23.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4566daf..1da3e57 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -VERSION = '0.22.3' +VERSION = '0.23.0' setup( name='bigcommerce', From d1f8e1cbcf43bfd5e09fe742b2d85f8196eb8db2 Mon Sep 17 00:00:00 2001 From: Nathan Booker Date: Thu, 30 Mar 2023 17:53:06 -0500 Subject: [PATCH 07/12] Add GitHub action to automate publishing to PyPi --- .github/workflows/release.yml | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..32088e2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI + +on: + push: + branches: [master, main] + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + - name: Publish distribution 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository-url: https://test.pypi.org/legacy/ + - name: Publish distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file From 88f577952bfdd0e9df095c6db21041f117f0e852 Mon Sep 17 00:00:00 2001 From: Nathan Booker Date: Thu, 30 Mar 2023 19:25:42 -0500 Subject: [PATCH 08/12] Remove test PyPi from CD --- .github/workflows/release.yml | 5 ----- setup.py | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 32088e2..aef3285 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,11 +27,6 @@ jobs: --sdist --wheel --outdir dist/ - - name: Publish distribution 📦 to Test PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.TEST_PYPI_API_TOKEN }} - repository-url: https://test.pypi.org/legacy/ - name: Publish distribution 📦 to PyPI if: startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/setup.py b/setup.py index 1da3e57..49c18ed 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -VERSION = '0.23.0' +VERSION = '0.23.1' setup( name='bigcommerce', From 700516ae79f163146a5ca39f686e84878896ee16 Mon Sep 17 00:00:00 2001 From: Nathan Booker Date: Thu, 30 Mar 2023 19:34:43 -0500 Subject: [PATCH 09/12] Change CD GH action to be trigged on release instead of push --- .github/workflows/release.yml | 5 ++--- setup.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aef3285..45342e6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,8 @@ name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI on: - push: - branches: [master, main] + release: + types: [released] jobs: build-n-publish: @@ -28,7 +28,6 @@ jobs: --wheel --outdir dist/ - name: Publish distribution 📦 to PyPI - if: startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file diff --git a/setup.py b/setup.py index 49c18ed..2bfce79 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -VERSION = '0.23.1' +VERSION = '0.23.2' setup( name='bigcommerce', From 820c61dafc0cba6734b4c3128aa308971325ffa0 Mon Sep 17 00:00:00 2001 From: Nathan Booker Date: Mon, 16 Oct 2023 21:26:06 -0500 Subject: [PATCH 10/12] Bump requests to 2.31.0 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 167197c..15fc388 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,5 @@ coverage==5.5 mock==4.0.3 nose==1.3.7 nose-cov==1.6 -requests==2.25.1 -pyjwt==2.4.0 \ No newline at end of file +requests==2.31.0 +pyjwt==2.4.0 From b21fb66ca6bc6ae48384a783b72e8517dd4b71b9 Mon Sep 17 00:00:00 2001 From: Nathan Booker Date: Mon, 16 Oct 2023 21:26:52 -0500 Subject: [PATCH 11/12] Release 0.23.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2bfce79..a55eff5 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -VERSION = '0.23.2' +VERSION = '0.23.3' setup( name='bigcommerce', From 599b27e69710b7cc2028196d915995310c7ad6c0 Mon Sep 17 00:00:00 2001 From: Nathan Booker Date: Mon, 8 Jan 2024 14:42:32 -0600 Subject: [PATCH 12/12] Replace default null variables with empty object to fix 400 error from API when variables are not set explicitly, release 0.23.4 --- bigcommerce/connection.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bigcommerce/connection.py b/bigcommerce/connection.py index 6b9df11..96fc6a4 100644 --- a/bigcommerce/connection.py +++ b/bigcommerce/connection.py @@ -310,7 +310,7 @@ def __init__(self, client_id, store_hash, access_token=None, host='api.bigcommer self.rate_limit = {} - def query(self, query, variables=None): + def query(self, query, variables={}): return self.post(self.graphql_path, dict(query=query, variables=variables)) def introspection_query(self): diff --git a/setup.py b/setup.py index a55eff5..4764df5 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -VERSION = '0.23.3' +VERSION = '0.23.4' setup( name='bigcommerce',