Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Build

on:
push:
branches:
- bigcli
pull_request:
types: [opened, synchronize, reopened]

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
# If you wish to fail your job when the Quality Gate is red, uncomment the
# following lines. This would typically be used to fail a deployment.
# We do not recommend to use this in a pull request. Prefer using pull request
# decoration instead.
# - uses: sonarsource/sonarqube-quality-gate-action@master
# timeout-minutes: 5
# env:
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ Pipfile
Pipfile.lock

*.egg-info*
venv/
venv/
build
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Bigcommerce API Python Client

|Build Status| |Package Version|

Wrapper over the ``requests`` library for communicating with the Bigcommerce v2 API.
Experimental fork of `bigcommerce/bigcommerce-api-python <https://github.com/bigcommerce/bigcommerce-api-python>`__ that adds high-level modeling for V3 API objects.

Install with ``pip install bigcommerce`` or ``easy_install bigcommerce``. Tested with
python 3.8, and only requires ``requests`` and ``pyjwt``.
Expand Down
43 changes: 37 additions & 6 deletions bigcommerce/api.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import os
import sys
from bigcommerce import connection
from bigcommerce.resources import * # Needed for ApiResourceWrapper dynamic loading

from bigcommerce.resources.v2 import * # Needed for ApiResourceWrapper dynamic loading
from bigcommerce.resources import v3 # Needed for ApiResourceWrapper dynamic loading

class BigcommerceApi(object):
def __init__(self, host=None, basic_auth=None,
client_id=None, store_hash=None, access_token=None, rate_limiting_management=None):
client_id=None, store_hash=None, access_token=None, rate_limiting_management=None, version='v2'):
self.api_service = os.getenv('BC_API_ENDPOINT', 'api.bigcommerce.com')
self.auth_service = os.getenv('BC_AUTH_SERVICE', 'login.bigcommerce.com')
self.version = version

if host and basic_auth:
self.connection = connection.Connection(host, basic_auth)
elif client_id and store_hash:
self.connection = connection.OAuthConnection(client_id, store_hash, access_token, self.api_service,
elif (client_id or access_token) and store_hash:
self.connection = connection.OAuthConnection(client_id=client_id, store_hash=store_hash, access_token=access_token, host=self.api_service,
rate_limiting_management=rate_limiting_management)
else:
raise Exception("Must provide either (client_id and store_hash) or (host and basic_auth)")
Expand All @@ -32,9 +33,12 @@ def oauth_verify_payload_jwt(cls, signed_payload, client_secret, client_id):
return connection.OAuthConnection.verify_payload_jwt(signed_payload, client_secret, client_id)

def __getattr__(self, item):
if self.version == 'v3':
return V3ApiResourceWrapper(item, self)
if self.version == 'latest':
return TryLatestApiResourceWrapper(item, self)
return ApiResourceWrapper(item, self)


class ApiResourceWrapper(object):
"""
Provides dot access to each of the API resources
Expand Down Expand Up @@ -71,3 +75,30 @@ def str_to_class(cls, str):
Assumes that the class is already loaded.
"""
return getattr(sys.modules[__name__], str)

class BigCommerceLatestApi(BigcommerceApi):
def __getattr__(self, item):
return TryLatestApiResourceWrapper(item, self)


class V3ApiResourceWrapper(ApiResourceWrapper):
@classmethod
def str_to_class(cls, str):
"""
Transforms a string class name into a class object
Assumes that the class is already loaded.
"""
return getattr(getattr(sys.modules[__name__], 'v3'), str)


class TryLatestApiResourceWrapper(ApiResourceWrapper):
@classmethod
def str_to_class(cls, str):
"""
Transforms a string class name into a class object
Assumes that the class is already loaded.
"""
try:
return getattr(getattr(sys.modules[__name__], 'v3'), str)
except AttributeError:
return getattr(sys.modules[__name__], str)
36 changes: 22 additions & 14 deletions bigcommerce/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ class Connection(object):
Connection class manages the connection to the Bigcommerce REST API.
"""

def __init__(self, host, auth, api_path='/api/v2/{}'):
def __init__(self, host, auth, api_path='/api/{}/{}'):
self.host = host
self.api_path = api_path

self.timeout = 7.0 # need to catch timeout?

log.info("API Host: %s/%s" % (self.host, self.api_path))
Expand All @@ -45,10 +44,14 @@ def __init__(self, host, auth, api_path='/api/v2/{}'):

self._last_response = None # for debugging

def full_path(self, url):
return "https://" + self.host + self.api_path.format(url)
def full_path(self, url, version='v2'):
if '/api/{}/{}' in self.api_path:
return "https://" + self.host + self.api_path.format(version, url)
else:
return "https://" + self.host + self.api_path.format(url)


def _run_method(self, method, url, data=None, query=None, headers=None):
def _run_method(self, method, url, data=None, query=None, headers=None, version='v2'):
if query is None:
query = {}
if headers is None:
Expand All @@ -58,9 +61,9 @@ def _run_method(self, method, url, data=None, query=None, headers=None):
if url and url[:4] != "http":
if url[0] == '/': # can call with /resource if you want
url = url[1:]
url = self.full_path(url)
url = self.full_path(url, version)
elif not url: # blank path
url = self.full_path(url)
url = self.full_path(url, version)

qs = urlencode(query)
if qs:
Expand Down Expand Up @@ -127,8 +130,8 @@ def delete(self, resource, rid=None): # note that rid can't be 0 - problem?

# Raw-er stuff

def make_request(self, method, url, data=None, params=None, headers=None):
response = self._run_method(method, url, data, params, headers)
def make_request(self, method, url, data=None, params=None, headers=None, version='v2'):
response = self._run_method(method, url, data, params, headers, version=version)
return self._handle_response(url, response)

def put(self, url, data):
Expand Down Expand Up @@ -170,6 +173,9 @@ def _handle_response(self, url, res, suppress_empty=True):
raise ClientRequestException("%d %s @ %s: %s" % (res.status_code, res.reason, url, res.content), res)
elif res.status_code >= 300:
raise RedirectionException("%d %s @ %s: %s" % (res.status_code, res.reason, url, res.content), res)

if 'data' in result: # for v3
result = result['data']
return result

def __repr__(self):
Expand All @@ -187,8 +193,8 @@ class OAuthConnection(Connection):
The verify_payload method is also provided for authenticating signed payloads passed to an application's load url.
"""

def __init__(self, client_id, store_hash, access_token=None, host='api.bigcommerce.com',
api_path='/stores/{}/v2/{}', rate_limiting_management=None):
def __init__(self, client_id=None, store_hash=None, access_token=None, host='api.bigcommerce.com',
api_path='/stores/{}/{}/{}', rate_limiting_management=None):
self.client_id = client_id
self.store_hash = store_hash
self.host = host
Expand All @@ -206,8 +212,10 @@ def __init__(self, client_id, store_hash, access_token=None, host='api.bigcommer

self.rate_limit = {}

def full_path(self, url):
return "https://" + self.host + self.api_path.format(self.store_hash, url)
def full_path(self, url, version='v2'):
if '/api/{}/{}/{}' in self.api_path:
return "https://" + self.host + self.api_path.format(self.store_hash, version, url)
return "https://" + self.host + self.api_path.format(self.store_hash, version, url)

@staticmethod
def _oauth_headers(cid, atoken):
Expand Down Expand Up @@ -286,4 +294,4 @@ def _handle_response(self, url, res, suppress_empty=True):
else:
callback()

return result
return result
24 changes: 1 addition & 23 deletions bigcommerce/resources/__init__.py
Original file line number Diff line number Diff line change
@@ -1,23 +1 @@
from .banners import *
from .blog_posts import *
from .brands import *
from .categories import *
from .countries import *
from .coupons import *
from .currencies import *
from .customer_groups import *
from .customers import *
from .gift_certificates import *
from .option_sets import *
from .options import *
from .order_statuses import *
from .orders import *
from .pages import *
from .payments import *
from .products import *
from .redirects import *
from .shipping import *
from .store import *
from .tax_classes import *
from .time import *
from .webhooks import *
from .v2 import *
Loading