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/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..45342e6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI + +on: + release: + types: [released] + +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 PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file 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..d04c757 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 ----- @@ -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 3012a3e..96fc6a4 100644 --- a/bigcommerce/connection.py +++ b/bigcommerce/connection.py @@ -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 @@ -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={}): + 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 + } + } + } + } + """) diff --git a/requirements.txt b/requirements.txt index 09a6bf3..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.1.0 \ No newline at end of file +requests==2.31.0 +pyjwt==2.4.0 diff --git a/setup.py b/setup.py index 4566daf..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.22.3' +VERSION = '0.23.4' setup( name='bigcommerce',