From 5c8fc80e4ac0cd0d048b5b56f29fecdbc667a1be Mon Sep 17 00:00:00 2001 From: "Austin G. Smith" Date: Wed, 2 Mar 2022 08:43:16 -0600 Subject: [PATCH 01/12] Modify api, connection, and base for v3 --- bigcommerce/api.py | 6 +++--- bigcommerce/connection.py | 36 +++++++++++++++++++++-------------- bigcommerce/resources/base.py | 10 ++++++++-- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/bigcommerce/api.py b/bigcommerce/api.py index 611aaf9..738080f 100644 --- a/bigcommerce/api.py +++ b/bigcommerce/api.py @@ -12,8 +12,8 @@ def __init__(self, host=None, basic_auth=None, 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)") @@ -70,4 +70,4 @@ def str_to_class(cls, str): Transforms a string class name into a class object Assumes that the class is already loaded. """ - return getattr(sys.modules[__name__], str) + return getattr(sys.modules[__name__], str) \ No newline at end of file diff --git a/bigcommerce/connection.py b/bigcommerce/connection.py index 3012a3e..cbc418f 100644 --- a/bigcommerce/connection.py +++ b/bigcommerce/connection.py @@ -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)) @@ -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: @@ -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: @@ -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): @@ -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): @@ -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 @@ -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): @@ -286,4 +294,4 @@ def _handle_response(self, url, res, suppress_empty=True): else: callback() - return result + return result \ No newline at end of file diff --git a/bigcommerce/resources/base.py b/bigcommerce/resources/base.py index 9dfec22..dd8d3fa 100644 --- a/bigcommerce/resources/base.py +++ b/bigcommerce/resources/base.py @@ -26,12 +26,16 @@ def __str__(self): """ return str({k: self.__dict__[k] for k in self.__dict__ if not k.startswith("_")}) + def __json__(self): + return {k: self.__dict__[k] for k in self.__dict__ if not k.startswith("_")} + def __repr__(self): return "<%s at %s, %s>" % (type(self).__name__, hex(id(self)), str(self)) class ApiResource(Mapping): resource_name = "" # The identifier which describes this resource in urls + resource_version = "v2" @classmethod def _create_object(cls, response, connection=None): @@ -42,7 +46,7 @@ def _create_object(cls, response, connection=None): @classmethod def _make_request(cls, method, url, connection, data=None, params=None, headers=None): - return connection.make_request(method, url, data, params, headers) + return connection.make_request(method, url, data, params, headers, version=cls.resource_version) @classmethod def _get_path(cls, id): @@ -183,6 +187,8 @@ def update(self, **updates): class DeleteableApiResource(ApiResource): def _delete_path(self): + if 'id' not in self and 'uuid' in self: # widgets have uuid not id + return "%s/%s" % (self.resource_name, self.uuid) return "%s/%s" % (self.resource_name, self.id) def delete(self): @@ -245,4 +251,4 @@ def _count_path(cls, parentid=None): @classmethod def count(cls, parentid=None, connection=None, **params): response = cls._make_request('GET', cls._count_path(parentid), connection, params=params) - return response['count'] + return response['count'] \ No newline at end of file From 065225865112cab59bf97ede979d066381368b31 Mon Sep 17 00:00:00 2001 From: "Austin G. Smith" Date: Wed, 2 Mar 2022 08:48:22 -0600 Subject: [PATCH 02/12] Add V3 Categories and ProductS --- bigcommerce/resources/__init__.py | 2 ++ bigcommerce/resources/categories_v3.py | 8 ++++++++ bigcommerce/resources/products_v3.py | 8 ++++++++ 3 files changed, 18 insertions(+) create mode 100644 bigcommerce/resources/categories_v3.py create mode 100644 bigcommerce/resources/products_v3.py diff --git a/bigcommerce/resources/__init__.py b/bigcommerce/resources/__init__.py index 541fc4f..24218d2 100644 --- a/bigcommerce/resources/__init__.py +++ b/bigcommerce/resources/__init__.py @@ -2,6 +2,7 @@ from .blog_posts import * from .brands import * from .categories import * +from .categories_v3 import * from .countries import * from .coupons import * from .currencies import * @@ -15,6 +16,7 @@ from .pages import * from .payments import * from .products import * +from .products_v3 import * from .redirects import * from .shipping import * from .store import * diff --git a/bigcommerce/resources/categories_v3.py b/bigcommerce/resources/categories_v3.py new file mode 100644 index 0000000..50bf8f7 --- /dev/null +++ b/bigcommerce/resources/categories_v3.py @@ -0,0 +1,8 @@ +from .base import * + + +class CategoriesV3(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource, + CollectionDeleteableApiResource, CountableApiResource): + resource_version = 'v3' + resource_name = 'catalog/categories' \ No newline at end of file diff --git a/bigcommerce/resources/products_v3.py b/bigcommerce/resources/products_v3.py new file mode 100644 index 0000000..661f012 --- /dev/null +++ b/bigcommerce/resources/products_v3.py @@ -0,0 +1,8 @@ +from .base import * + + +class ProductsV3(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource, + CollectionDeleteableApiResource, CountableApiResource): + resource_version = 'v3' + resource_name = 'catalog/products' \ No newline at end of file From e2db73d43b3609f89a4e096f75344135451c709f Mon Sep 17 00:00:00 2001 From: "Austin G. Smith" Date: Wed, 2 Mar 2022 15:19:42 -0600 Subject: [PATCH 03/12] before v2 folder --- bigcommerce/api.py | 36 +++++++++++++++++-- bigcommerce/resources/__init__.py | 4 +-- bigcommerce/resources/v3/__init__.py | 2 ++ .../{categories_v3.py => v3/categories.py} | 4 +-- .../{products_v3.py => v3/products.py} | 4 +-- 5 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 bigcommerce/resources/v3/__init__.py rename bigcommerce/resources/{categories_v3.py => v3/categories.py} (70%) rename bigcommerce/resources/{products_v3.py => v3/products.py} (71%) diff --git a/bigcommerce/api.py b/bigcommerce/api.py index 738080f..01ef354 100644 --- a/bigcommerce/api.py +++ b/bigcommerce/api.py @@ -2,13 +2,15 @@ import sys from bigcommerce import connection from bigcommerce.resources import * # Needed for ApiResourceWrapper dynamic loading +from bigcommerce.resources import v3 # Needed for V3ApiResourceWrapper 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) @@ -32,9 +34,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 @@ -70,4 +75,29 @@ def str_to_class(cls, str): Transforms a string class name into a class object Assumes that the class is already loaded. """ - return getattr(sys.modules[__name__], str) \ No newline at end of file + 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) diff --git a/bigcommerce/resources/__init__.py b/bigcommerce/resources/__init__.py index 24218d2..1d2cb4d 100644 --- a/bigcommerce/resources/__init__.py +++ b/bigcommerce/resources/__init__.py @@ -2,7 +2,6 @@ from .blog_posts import * from .brands import * from .categories import * -from .categories_v3 import * from .countries import * from .coupons import * from .currencies import * @@ -16,10 +15,9 @@ from .pages import * from .payments import * from .products import * -from .products_v3 import * from .redirects import * from .shipping import * from .store import * from .tax_classes import * from .time import * -from .webhooks import * +from .webhooks import * \ No newline at end of file diff --git a/bigcommerce/resources/v3/__init__.py b/bigcommerce/resources/v3/__init__.py new file mode 100644 index 0000000..0700371 --- /dev/null +++ b/bigcommerce/resources/v3/__init__.py @@ -0,0 +1,2 @@ +from .products import * +from .categories import * \ No newline at end of file diff --git a/bigcommerce/resources/categories_v3.py b/bigcommerce/resources/v3/categories.py similarity index 70% rename from bigcommerce/resources/categories_v3.py rename to bigcommerce/resources/v3/categories.py index 50bf8f7..61e8562 100644 --- a/bigcommerce/resources/categories_v3.py +++ b/bigcommerce/resources/v3/categories.py @@ -1,7 +1,7 @@ -from .base import * +from ..base import * -class CategoriesV3(ListableApiResource, CreateableApiResource, +class Categories(ListableApiResource, CreateableApiResource, UpdateableApiResource, DeleteableApiResource, CollectionDeleteableApiResource, CountableApiResource): resource_version = 'v3' diff --git a/bigcommerce/resources/products_v3.py b/bigcommerce/resources/v3/products.py similarity index 71% rename from bigcommerce/resources/products_v3.py rename to bigcommerce/resources/v3/products.py index 661f012..85a61af 100644 --- a/bigcommerce/resources/products_v3.py +++ b/bigcommerce/resources/v3/products.py @@ -1,7 +1,7 @@ -from .base import * +from ..base import * -class ProductsV3(ListableApiResource, CreateableApiResource, +class Products(ListableApiResource, CreateableApiResource, UpdateableApiResource, DeleteableApiResource, CollectionDeleteableApiResource, CountableApiResource): resource_version = 'v3' From 1883356f75aad1c87a73d870e8dcae8fbce6c1b1 Mon Sep 17 00:00:00 2001 From: "Austin G. Smith" Date: Wed, 2 Mar 2022 16:07:11 -0600 Subject: [PATCH 04/12] refactor v2 to v2 dir --- bigcommerce/api.py | 5 ++-- bigcommerce/resources/__init__.py | 24 +------------------ bigcommerce/resources/v2/__init__.py | 23 ++++++++++++++++++ bigcommerce/resources/{ => v2}/banners.py | 2 +- bigcommerce/resources/{ => v2}/blog_posts.py | 2 +- bigcommerce/resources/{ => v2}/brands.py | 2 +- bigcommerce/resources/{ => v2}/categories.py | 2 +- bigcommerce/resources/{ => v2}/countries.py | 2 +- bigcommerce/resources/{ => v2}/coupons.py | 2 +- bigcommerce/resources/{ => v2}/currencies.py | 2 +- .../resources/{ => v2}/customer_groups.py | 2 +- bigcommerce/resources/{ => v2}/customers.py | 2 +- .../resources/{ => v2}/gift_certificates.py | 2 +- bigcommerce/resources/{ => v2}/option_sets.py | 2 +- bigcommerce/resources/{ => v2}/options.py | 2 +- .../resources/{ => v2}/order_statuses.py | 2 +- bigcommerce/resources/{ => v2}/orders.py | 2 +- bigcommerce/resources/{ => v2}/pages.py | 2 +- bigcommerce/resources/{ => v2}/payments.py | 2 +- bigcommerce/resources/{ => v2}/products.py | 2 +- bigcommerce/resources/{ => v2}/redirects.py | 2 +- bigcommerce/resources/{ => v2}/shipping.py | 2 +- bigcommerce/resources/{ => v2}/store.py | 2 +- bigcommerce/resources/{ => v2}/tax_classes.py | 2 +- bigcommerce/resources/{ => v2}/time.py | 2 +- bigcommerce/resources/{ => v2}/webhooks.py | 2 +- 26 files changed, 49 insertions(+), 49 deletions(-) create mode 100644 bigcommerce/resources/v2/__init__.py rename bigcommerce/resources/{ => v2}/banners.py (90%) rename bigcommerce/resources/{ => v2}/blog_posts.py (91%) rename bigcommerce/resources/{ => v2}/brands.py (91%) rename bigcommerce/resources/{ => v2}/categories.py (91%) rename bigcommerce/resources/{ => v2}/countries.py (95%) rename bigcommerce/resources/{ => v2}/coupons.py (91%) rename bigcommerce/resources/{ => v2}/currencies.py (88%) rename bigcommerce/resources/{ => v2}/customer_groups.py (92%) rename bigcommerce/resources/{ => v2}/customers.py (97%) rename bigcommerce/resources/{ => v2}/gift_certificates.py (91%) rename bigcommerce/resources/{ => v2}/option_sets.py (97%) rename bigcommerce/resources/{ => v2}/options.py (97%) rename bigcommerce/resources/{ => v2}/order_statuses.py (79%) rename bigcommerce/resources/{ => v2}/orders.py (99%) rename bigcommerce/resources/{ => v2}/pages.py (91%) rename bigcommerce/resources/{ => v2}/payments.py (80%) rename bigcommerce/resources/{ => v2}/products.py (99%) rename bigcommerce/resources/{ => v2}/redirects.py (91%) rename bigcommerce/resources/{ => v2}/shipping.py (80%) rename bigcommerce/resources/{ => v2}/store.py (75%) rename bigcommerce/resources/{ => v2}/tax_classes.py (78%) rename bigcommerce/resources/{ => v2}/time.py (74%) rename bigcommerce/resources/{ => v2}/webhooks.py (87%) diff --git a/bigcommerce/api.py b/bigcommerce/api.py index 01ef354..224d4c7 100644 --- a/bigcommerce/api.py +++ b/bigcommerce/api.py @@ -1,9 +1,8 @@ import os import sys from bigcommerce import connection -from bigcommerce.resources import * # Needed for ApiResourceWrapper dynamic loading -from bigcommerce.resources import v3 # Needed for V3ApiResourceWrapper 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, diff --git a/bigcommerce/resources/__init__.py b/bigcommerce/resources/__init__.py index 1d2cb4d..466b1e2 100644 --- a/bigcommerce/resources/__init__.py +++ b/bigcommerce/resources/__init__.py @@ -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 * \ No newline at end of file +from .v2 import * \ No newline at end of file diff --git a/bigcommerce/resources/v2/__init__.py b/bigcommerce/resources/v2/__init__.py new file mode 100644 index 0000000..1d2cb4d --- /dev/null +++ b/bigcommerce/resources/v2/__init__.py @@ -0,0 +1,23 @@ +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 * \ No newline at end of file diff --git a/bigcommerce/resources/banners.py b/bigcommerce/resources/v2/banners.py similarity index 90% rename from bigcommerce/resources/banners.py rename to bigcommerce/resources/v2/banners.py index bbfd2fb..f0fcab4 100644 --- a/bigcommerce/resources/banners.py +++ b/bigcommerce/resources/v2/banners.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Banners(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/blog_posts.py b/bigcommerce/resources/v2/blog_posts.py similarity index 91% rename from bigcommerce/resources/blog_posts.py rename to bigcommerce/resources/v2/blog_posts.py index 11e0d31..072cd8e 100644 --- a/bigcommerce/resources/blog_posts.py +++ b/bigcommerce/resources/v2/blog_posts.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class BlogPosts(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/brands.py b/bigcommerce/resources/v2/brands.py similarity index 91% rename from bigcommerce/resources/brands.py rename to bigcommerce/resources/v2/brands.py index 91d7d9f..532d3da 100644 --- a/bigcommerce/resources/brands.py +++ b/bigcommerce/resources/v2/brands.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Brands(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/categories.py b/bigcommerce/resources/v2/categories.py similarity index 91% rename from bigcommerce/resources/categories.py rename to bigcommerce/resources/v2/categories.py index a96d634..7224d04 100644 --- a/bigcommerce/resources/categories.py +++ b/bigcommerce/resources/v2/categories.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Categories(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/countries.py b/bigcommerce/resources/v2/countries.py similarity index 95% rename from bigcommerce/resources/countries.py rename to bigcommerce/resources/v2/countries.py index db5baa1..dfc3e12 100644 --- a/bigcommerce/resources/countries.py +++ b/bigcommerce/resources/v2/countries.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Countries(ListableApiResource, CountableApiResource): diff --git a/bigcommerce/resources/coupons.py b/bigcommerce/resources/v2/coupons.py similarity index 91% rename from bigcommerce/resources/coupons.py rename to bigcommerce/resources/v2/coupons.py index 8c865ac..74025b2 100644 --- a/bigcommerce/resources/coupons.py +++ b/bigcommerce/resources/v2/coupons.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Coupons(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/currencies.py b/bigcommerce/resources/v2/currencies.py similarity index 88% rename from bigcommerce/resources/currencies.py rename to bigcommerce/resources/v2/currencies.py index 31627a5..b31725a 100644 --- a/bigcommerce/resources/currencies.py +++ b/bigcommerce/resources/v2/currencies.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Currencies(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/customer_groups.py b/bigcommerce/resources/v2/customer_groups.py similarity index 92% rename from bigcommerce/resources/customer_groups.py rename to bigcommerce/resources/v2/customer_groups.py index e574c6a..ff20971 100644 --- a/bigcommerce/resources/customer_groups.py +++ b/bigcommerce/resources/v2/customer_groups.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class CustomerGroups(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/customers.py b/bigcommerce/resources/v2/customers.py similarity index 97% rename from bigcommerce/resources/customers.py rename to bigcommerce/resources/v2/customers.py index d68ba4d..d5b7ed3 100644 --- a/bigcommerce/resources/customers.py +++ b/bigcommerce/resources/v2/customers.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Customers(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/gift_certificates.py b/bigcommerce/resources/v2/gift_certificates.py similarity index 91% rename from bigcommerce/resources/gift_certificates.py rename to bigcommerce/resources/v2/gift_certificates.py index 30938f6..2eb5187 100644 --- a/bigcommerce/resources/gift_certificates.py +++ b/bigcommerce/resources/v2/gift_certificates.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class GiftCertificates(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/option_sets.py b/bigcommerce/resources/v2/option_sets.py similarity index 97% rename from bigcommerce/resources/option_sets.py rename to bigcommerce/resources/v2/option_sets.py index 1da826b..ea02e9c 100644 --- a/bigcommerce/resources/option_sets.py +++ b/bigcommerce/resources/v2/option_sets.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class OptionSets(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/options.py b/bigcommerce/resources/v2/options.py similarity index 97% rename from bigcommerce/resources/options.py rename to bigcommerce/resources/v2/options.py index 5cd003b..617c2ff 100644 --- a/bigcommerce/resources/options.py +++ b/bigcommerce/resources/v2/options.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Options(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/order_statuses.py b/bigcommerce/resources/v2/order_statuses.py similarity index 79% rename from bigcommerce/resources/order_statuses.py rename to bigcommerce/resources/v2/order_statuses.py index 4179559..ab01fbe 100644 --- a/bigcommerce/resources/order_statuses.py +++ b/bigcommerce/resources/v2/order_statuses.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class OrderStatuses(ListableApiResource): diff --git a/bigcommerce/resources/orders.py b/bigcommerce/resources/v2/orders.py similarity index 99% rename from bigcommerce/resources/orders.py rename to bigcommerce/resources/v2/orders.py index 2badc9c..b0b1b96 100644 --- a/bigcommerce/resources/orders.py +++ b/bigcommerce/resources/v2/orders.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Orders(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/pages.py b/bigcommerce/resources/v2/pages.py similarity index 91% rename from bigcommerce/resources/pages.py rename to bigcommerce/resources/v2/pages.py index d0cdca1..cd007cf 100644 --- a/bigcommerce/resources/pages.py +++ b/bigcommerce/resources/v2/pages.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Pages(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/payments.py b/bigcommerce/resources/v2/payments.py similarity index 80% rename from bigcommerce/resources/payments.py rename to bigcommerce/resources/v2/payments.py index 10cd6b8..d694915 100644 --- a/bigcommerce/resources/payments.py +++ b/bigcommerce/resources/v2/payments.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class PaymentMethods(ListableApiResource): diff --git a/bigcommerce/resources/products.py b/bigcommerce/resources/v2/products.py similarity index 99% rename from bigcommerce/resources/products.py rename to bigcommerce/resources/v2/products.py index d569de9..fbfa2b1 100644 --- a/bigcommerce/resources/products.py +++ b/bigcommerce/resources/v2/products.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Products(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/redirects.py b/bigcommerce/resources/v2/redirects.py similarity index 91% rename from bigcommerce/resources/redirects.py rename to bigcommerce/resources/v2/redirects.py index ceed52c..8ba28c0 100644 --- a/bigcommerce/resources/redirects.py +++ b/bigcommerce/resources/v2/redirects.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Redirects(ListableApiResource, CreateableApiResource, diff --git a/bigcommerce/resources/shipping.py b/bigcommerce/resources/v2/shipping.py similarity index 80% rename from bigcommerce/resources/shipping.py rename to bigcommerce/resources/v2/shipping.py index 4df1b04..e59ce72 100644 --- a/bigcommerce/resources/shipping.py +++ b/bigcommerce/resources/v2/shipping.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class ShippingMethods(ListableApiResource): diff --git a/bigcommerce/resources/store.py b/bigcommerce/resources/v2/store.py similarity index 75% rename from bigcommerce/resources/store.py rename to bigcommerce/resources/v2/store.py index 1a272af..1cfe302 100644 --- a/bigcommerce/resources/store.py +++ b/bigcommerce/resources/v2/store.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Store(ListableApiResource): diff --git a/bigcommerce/resources/tax_classes.py b/bigcommerce/resources/v2/tax_classes.py similarity index 78% rename from bigcommerce/resources/tax_classes.py rename to bigcommerce/resources/v2/tax_classes.py index 48dc391..61285e2 100644 --- a/bigcommerce/resources/tax_classes.py +++ b/bigcommerce/resources/v2/tax_classes.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class TaxClasses(ListableApiResource): diff --git a/bigcommerce/resources/time.py b/bigcommerce/resources/v2/time.py similarity index 74% rename from bigcommerce/resources/time.py rename to bigcommerce/resources/v2/time.py index 3f92df9..35960d3 100644 --- a/bigcommerce/resources/time.py +++ b/bigcommerce/resources/v2/time.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Time(ListableApiResource): diff --git a/bigcommerce/resources/webhooks.py b/bigcommerce/resources/v2/webhooks.py similarity index 87% rename from bigcommerce/resources/webhooks.py rename to bigcommerce/resources/v2/webhooks.py index a8e5137..2437f2d 100644 --- a/bigcommerce/resources/webhooks.py +++ b/bigcommerce/resources/v2/webhooks.py @@ -1,4 +1,4 @@ -from .base import * +from ..base import * class Webhooks(ListableApiResource, CreateableApiResource, From 0c802cd91e382016f2c3dfeb1615fbe6ef9b8388 Mon Sep 17 00:00:00 2001 From: "Austin G. Smith" Date: Wed, 9 Mar 2022 08:53:39 -0600 Subject: [PATCH 05/12] Add SubSubResources & Products V3 --- bigcommerce/api.py | 2 + bigcommerce/resources/base.py | 86 +++++++++++++-- bigcommerce/resources/v3/products.py | 159 ++++++++++++++++++++++++++- tests/test_base.py | 53 ++++++--- 4 files changed, 275 insertions(+), 25 deletions(-) diff --git a/bigcommerce/api.py b/bigcommerce/api.py index 224d4c7..5323463 100644 --- a/bigcommerce/api.py +++ b/bigcommerce/api.py @@ -80,6 +80,7 @@ class BigCommerceLatestApi(BigcommerceApi): def __getattr__(self, item): return TryLatestApiResourceWrapper(item, self) + class V3ApiResourceWrapper(ApiResourceWrapper): @classmethod def str_to_class(cls, str): @@ -89,6 +90,7 @@ def str_to_class(cls, str): """ return getattr(getattr(sys.modules[__name__], 'v3'), str) + class TryLatestApiResourceWrapper(ApiResourceWrapper): @classmethod def str_to_class(cls, str): diff --git a/bigcommerce/resources/base.py b/bigcommerce/resources/base.py index dd8d3fa..d480813 100644 --- a/bigcommerce/resources/base.py +++ b/bigcommerce/resources/base.py @@ -59,21 +59,52 @@ def get(cls, id, connection=None, **params): class ApiSubResource(ApiResource): + gparent_resource = "" parent_resource = "" + gparent_key = "" parent_key = "" @classmethod - def _get_path(cls, id, parentid): - return "%s/%s/%s/%s" % (cls.parent_resource, parentid, cls.resource_name, id) + def _get_path(cls, parentid, id=None): + if id: + return "%s/%s/%s/%s" % (cls.parent_resource, parentid, cls.resource_name, id) + return "%s/%s/%s" % (cls.parent_resource, parentid, cls.resource_name) @classmethod - def get(cls, parentid, id, connection=None, **params): - response = cls._make_request('GET', cls._get_path(id, parentid), connection, params=params) + def get(cls, parentid, id=None, connection=None, **params): + response = cls._make_request('GET', cls._get_path(parentid, id), connection, params=params) return cls._create_object(response, connection=connection) def parent_id(self): return self[self.parent_key] + def gparent_id(self): + return self[self.gparent_key] + + +class ApiSubSubResource(ApiSubResource): + gparent_resource = "" + parent_resource = "" + gparent_key = "" + parent_key = "" + + @classmethod + def _get_path(cls, gparentid, parentid, id=None): + if id: + return "%s/%s/%s/%s/%s/%s" % (cls.gparent_resource, gparentid, cls.parent_resource, parentid, cls.resource_name, id) + return "%s/%s/%s/%s/%s" % (cls.gparent_resource, gparentid, cls.parent_resource, parentid, cls.resource_name) + + @classmethod + def get(cls, gparentid, parentid, id=None, connection=None, **params): + response = cls._make_request('GET', cls._get_path(gparentid, parentid, id), connection, params=params) + return cls._create_object(response, connection=connection) + + def parent_id(self): + return self[self.parent_key] + + def gparent_id(self): + return self[self.gparent_key] + class CreateableApiResource(ApiResource): @classmethod @@ -97,6 +128,17 @@ def create(cls, parentid, connection=None, **params): return cls._create_object(response, connection=connection) +class CreateableApiSubSubResource(ApiSubSubResource): + @classmethod + def _create_path(cls, gparentid, parentid): + return "%s/%s/%s/%s/%s" % (cls.gparent_resource, gparentid, cls.parent_resource, parentid, cls.resource_name) + + @classmethod + def create(cls, gparentid, parentid, connection=None, **params): + response = cls._make_request('POST', cls._create_path(gparentid, parentid), connection, data=params) + return cls._create_object(response, connection=connection) + + class ListableApiResource(ApiResource): @classmethod def _get_all_path(cls): @@ -107,7 +149,6 @@ def all(cls, connection=None, **params): """ Returns first page if no params passed in as a list. """ - request = cls._make_request('GET', cls._get_all_path(), connection, params=params) return cls._create_object(request, connection=connection) @@ -154,7 +195,7 @@ def _all_responses(): class ListableApiSubResource(ApiSubResource): @classmethod - def _get_all_path(cls, parentid=None): + def _get_all_path(cls, parentid): # Not all sub resources require a parent id. Eg: /api/v2/products/skus?sku= if (parentid): return "%s/%s/%s" % (cls.parent_resource, parentid, cls.resource_name) @@ -162,8 +203,20 @@ def _get_all_path(cls, parentid=None): return "%s/%s" % (cls.parent_resource, cls.resource_name) @classmethod - def all(cls, parentid=None, connection=None, **params): - response = cls._make_request('GET', cls._get_all_path(parentid), connection, params=params) + def all(cls, parentid=None, gparentid=None, connection=None, **params): + response = cls._make_request('GET', cls._get_all_path(parentid, gparentid), connection, params=params) + return cls._create_object(response, connection=connection) + + +class ListableApiSubSubResource(ApiSubSubResource): + @classmethod + def _get_all_path(cls, gparentid, parentid): + # Not all sub resources require a parent id. Eg: /api/v2/products/skus?sku= + return "%s/%s/%s/%s/%s" % (cls.gparent_resource, gparentid, cls.parent_resource, parentid, cls.resource_name) + + @classmethod + def all(cls, gparentid, parentid, connection=None, **params): + response = cls._make_request('GET', cls._get_all_path(parentid, gparentid), connection, params=params) return cls._create_object(response, connection=connection) @@ -185,6 +238,15 @@ def update(self, **updates): return self._create_object(response, connection=self._connection) +class UpdateableApiSubSubResource(ApiSubResource): + def _update_path(self): + return "%s/%s/%s/%s/%s/%s" % (self.gparent_resource, self.gparent_id(), self.parent_resource, self.parent_id(), self.resource_name, self.id) + + def update(self, **updates): + response = self._make_request('PUT', self._update_path(), self._connection, data=updates) + return self._create_object(response, connection=self._connection) + + class DeleteableApiResource(ApiResource): def _delete_path(self): if 'id' not in self and 'uuid' in self: # widgets have uuid not id @@ -203,6 +265,14 @@ def delete(self): return self._make_request('DELETE', self._delete_path(), self._connection) +class DeleteableApiSubSubResource(ApiSubSubResource): + def _delete_path(self): + return "%s/%s/%s/%s/%s/%s" % (self.gparent_resource, self.gparent_id(), self.parent_resource, self.parent_id(), self.resource_name, self.id) + + def delete(self): + return self._make_request('DELETE', self._delete_path(), self._connection) + + class CollectionDeleteableApiResource(ApiResource): @classmethod def _delete_all_path(cls): diff --git a/bigcommerce/resources/v3/products.py b/bigcommerce/resources/v3/products.py index 85a61af..d4bc01a 100644 --- a/bigcommerce/resources/v3/products.py +++ b/bigcommerce/resources/v3/products.py @@ -1,8 +1,165 @@ from ..base import * +""" +""" class Products(ListableApiResource, CreateableApiResource, UpdateableApiResource, DeleteableApiResource, CollectionDeleteableApiResource, CountableApiResource): resource_version = 'v3' - resource_name = 'catalog/products' \ No newline at end of file + resource_name = 'catalog/products' + + def bulk_pricing_rules(self, id=None): + if id: + return ProductBulkPricingRules.get(self.id, id, connection=self._connection) + else: + return ProductBulkPricingRules.all(self.id, connection=self._connection) + + def complex_rules(self, id=None): + if id: + return ProductComplexRules.get(self.id, id, connection=self._connection) + else: + return ProductComplexRules.all(self.id, connection=self._connection) + + def custom_fields(self, id=None): + if id: + return ProductCustomFields.get(self.id, id, connection=self._connection) + else: + return ProductCustomFields.all(self.id, connection=self._connection) + + def images(self, id=None): + if id: + return ProductImages.get(self.id, id, connection=self._connection) + else: + return ProductImages.all(self.id, connection=self._connection) + + def metafields(self, id=None): + if id: + return ProductMetafields.get(self.id, id, connection=self._connection) + else: + return ProductMetafields.all(self.id, connection=self._connection) + + def modifiers(self, id=None): + if id: + return ProductModifiers.get(self.id, id, connection=self._connection) + else: + return ProductModifiers.all(self.id, connection=self._connection) + + def videos(self, id=None): + if id: + return ProductVideos.get(self.id, id, connection=self._connection) + else: + return ProductVideos.all(self.id, connection=self._connection) + + +class ProductBulkPricingRules(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Products.resource_version + resource_name = 'XXX' + parent_resource = Products.resource_name + key = 'product_id' + + +class ProductComplexRules(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Products.resource_version + resource_name = 'XXX' + parent_resource = Products.resource_name + parent_key = 'product_id' + + +class ProductCustomFields(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Products.resource_version + resource_name = 'XXX' + parent_resource = Products.resource_name + parent_key = 'product_id' + + +class ProductImages(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Products.resource_version + resource_name = 'XXX' + parent_resource = Products.resource_name + parent_key = 'product_id' + + +class ProductMetafields(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Products.resource_version + resource_name = 'XXX' + parent_resource = Products.resource_name + parent_key = 'product_id' + + +class ProductModifiers(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Products.resource_version + resource_name = 'modifiers' + parent_resource = Products.resource_name + parent_key = 'product_id' + + def values(self, id=None): + if id: + return ProductModifiersValues.get(self.gparent_id(), self.parent_id(), id, connection=self._connection) + else: + return ProductModifiersValues.all(self.gparent_id(), self.parent_id(), connection=self._connection) + + +class ProductModifiersValues(ListableApiSubSubResource, CreateableApiSubSubResource, DeleteableApiSubSubResource): + resource_version = Products.resource_version + resource_name = 'values' + parent_resource = 'modifiers' + parent_key = 'modifier_id' + gparent_key = 'product_id' + gparent_resource=Products.resource_name + + +class ProductOptions(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Products.resource_version + resource_name = 'options' + parent_resource = Products.resource_name + parent_key = 'product_id' + + +class ProductOptionsValues(ListableApiSubSubResource, CreateableApiSubSubResource, DeleteableApiSubSubResource): + resource_version = Products.resource_version + resource_name = 'values' + parent_resource = 'options' + parent_key = 'option_id' + gparent_key = 'product_id' + gparent_resource=Products.resource_name + +class ProductReviews(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Products.resource_version + resource_name = 'XXX' + parent_resource = Products.resource_name + parent_key = 'product_id' + + +class ProductVariants(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Products.resource_version + resource_name = 'variants' + parent_resource = Products.resource_name + parent_key = 'product_id' + + +class ProductVariantsImages(ListableApiSubSubResource, CreateableApiSubSubResource, DeleteableApiSubSubResource): + resource_version = Products.resource_version + resource_name = 'images' + parent_resource = 'variants' + parent_key = 'variant_id' + gparent_key = 'product_id' + gparent_resource=Products.resource_name + + +class ProductVariantsMetafields(ListableApiSubSubResource, CreateableApiSubSubResource, DeleteableApiSubSubResource): + resource_version = Products.resource_version + resource_name = 'metafields' + parent_resource = 'variants' + parent_key = 'variant_id' + gparent_key = 'product_id' + gparent_resource=Products.resource_name + + +class ProductVideos(ListableApiSubResource, CountableApiSubResource, + CreateableApiSubResource, DeleteableApiSubResource): + resource_version = 'v3' + resource_name = 'videos' + parent_resource = 'catalog/products' + parent_key = 'product_id' + count_resource = 'products/videos' diff --git a/tests/test_base.py b/tests/test_base.py index 58b1ee0..b76df74 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -1,6 +1,7 @@ import unittest from bigcommerce.resources import Mapping, Orders, ApiResource, OrderShipments, Products, CountryStates,\ OrderCoupons, Webhooks, GoogleProductSearchMappings +from bigcommerce.resources.v3 import ProductModifiers, ProductModifiersValues from mock import MagicMock @@ -45,7 +46,7 @@ def test_get(self): self.assertIsInstance(result, Orders) self.assertEqual(result.id, 1) - connection.make_request.assert_called_once_with('GET', 'orders/1', None, {}, None) + connection.make_request.assert_called_once_with('GET', 'orders/1', None, {}, None, version='v2') class TestApiSubResource(unittest.TestCase): @@ -57,7 +58,27 @@ def test_get(self): self.assertIsInstance(result, OrderCoupons) self.assertEqual(result.id, 2) - connection.make_request.assert_called_once_with('GET', 'orders/1/coupons/2', None, {}, None) + connection.make_request.assert_called_once_with('GET', 'orders/1/coupons/2', None, {}, None, version='v2') + + def test_get_product_modifier(self): + connection = MagicMock() + connection.make_request.return_value = {'id': 2} + + result = ProductModifiers.get(1, 2, connection) + self.assertIsInstance(result, ProductModifiers) + self.assertEqual(result.id, 2) + + connection.make_request.assert_called_once_with('GET', 'catalog/products/1/modifiers/2', None, {}, None, version='v3') + + def test_get_product_modifier_value(self): + connection = MagicMock() + connection.make_request.return_value = {'id': 2} + + result = ProductModifiersValues.get(2, 3, connection, 1) + self.assertIsInstance(result, ProductModifiersValues) + self.assertEqual(result.id, 2) + + connection.make_request.assert_called_once_with('GET', 'catalog/products/1/modifiers/2/values/3', None, {}, None, version='v3') def test_parent_id(self): coupon = OrderCoupons({'id': 2, 'order_id': 1}) @@ -72,7 +93,7 @@ def test_create(self): result = Orders.create(connection, name="Hello") self.assertIsInstance(result, Orders) self.assertEqual(result.id, 1) - connection.make_request.assert_called_once_with('POST', 'orders', {'name': 'Hello'}, None, None) + connection.make_request.assert_called_once_with('POST', 'orders', {'name': 'Hello'}, None, None, version='v2') class TestCreateableApiSubResource(unittest.TestCase): @@ -83,7 +104,7 @@ def test_create(self): result = OrderShipments.create(1, connection, name="Hello") self.assertIsInstance(result, OrderShipments) self.assertEqual(result.id, 2) - connection.make_request.assert_called_once_with('POST', 'orders/1/shipments', {'name': 'Hello'}, None, None) + connection.make_request.assert_called_once_with('POST', 'orders/1/shipments', {'name': 'Hello'}, None, None, version='v2') class TestListableApiResource(unittest.TestCase): @@ -93,7 +114,7 @@ def test_all(self): result = Orders.all(connection, limit=3) self.assertEqual(len(list(result)), 3) - connection.make_request.assert_called_once_with('GET', 'orders', None, {'limit': 3}, None) + connection.make_request.assert_called_once_with('GET', 'orders', None, {'limit': 3}, None, version='v2') class TestListableApiSubResource(unittest.TestCase): @@ -103,7 +124,7 @@ def test_all(self): result = OrderCoupons.all(1, connection, limit=2) self.assertEqual(len(result), 2) - connection.make_request.assert_called_once_with('GET', 'orders/1/coupons', None, {'limit': 2}, None) + connection.make_request.assert_called_once_with('GET', 'orders/1/coupons', None, {'limit': 2}, None, version='v2') def test_google_mappings(self): connection = MagicMock() @@ -111,7 +132,7 @@ def test_google_mappings(self): result = GoogleProductSearchMappings.all(1, connection, limit=2) self.assertEqual(len(result), 2) - connection.make_request.assert_called_once_with('GET', 'products/1/googleproductsearch', None, {'limit': 2}, None) + connection.make_request.assert_called_once_with('GET', 'products/1/googleproductsearch', None, {'limit': 2}, None, version='v2') class TestUpdateableApiResource(unittest.TestCase): @@ -123,7 +144,7 @@ def test_update(self): new_order = order.update(name='order') self.assertIsInstance(new_order, Orders) - connection.make_request.assert_called_once_with('PUT', 'orders/1', {'name': 'order'}, None, None) + connection.make_request.assert_called_once_with('PUT', 'orders/1', {'name': 'order'}, None, None, version='v2') class TestUpdateableApiSubResource(unittest.TestCase): @@ -136,7 +157,7 @@ def test_update(self): self.assertIsInstance(new_order, OrderShipments) connection.make_request.assert_called_once_with('PUT', 'orders/2/shipments/1', {'tracking_number': '1234'}, - None, None) + None, None, version='v2') class TestDeleteableApiResource(unittest.TestCase): @@ -146,7 +167,7 @@ def test_delete_all(self): self.assertEqual(Orders.delete_all(connection), {}) - connection.make_request.assert_called_once_with('DELETE', 'orders', None, None, None) + connection.make_request.assert_called_once_with('DELETE', 'orders', None, None, None, version='v2') def test_delete(self): connection = MagicMock() @@ -156,7 +177,7 @@ def test_delete(self): self.assertEqual(order.delete(), {}) - connection.make_request.assert_called_once_with('DELETE', 'orders/1', None, None, None) + connection.make_request.assert_called_once_with('DELETE', 'orders/1', None, None, None, version='v2') class TestDeleteableApiSubResource(unittest.TestCase): @@ -166,7 +187,7 @@ def test_delete_all(self): self.assertEqual(OrderShipments.delete_all(1, connection=connection), {}) - connection.make_request.assert_called_once_with('DELETE', 'orders/1/shipments', None, None, None) + connection.make_request.assert_called_once_with('DELETE', 'orders/1/shipments', None, None, None, version='v2') def test_delete(self): connection = MagicMock() @@ -175,7 +196,7 @@ def test_delete(self): shipment = OrderShipments({'id': 1, 'order_id': 2, '_connection': connection}) self.assertEqual(shipment.delete(), {}) - connection.make_request.assert_called_once_with('DELETE', 'orders/2/shipments/1', None, None, None) + connection.make_request.assert_called_once_with('DELETE', 'orders/2/shipments/1', None, None, None, version='v2') class TestCountableApiResource(unittest.TestCase): @@ -184,7 +205,7 @@ def test_count(self): connection.make_request.return_value = {'count': 2} self.assertEqual(Products.count(connection, is_visible=True), 2) - connection.make_request.assert_called_once_with('GET', 'products/count', None, {'is_visible': True}, None) + connection.make_request.assert_called_once_with('GET', 'products/count', None, {'is_visible': True}, None, version='v2') class TestCountableApiSubResource(unittest.TestCase): @@ -194,7 +215,7 @@ def test_count(self): self.assertEqual(CountryStates.count(1, connection=connection, is_visible=True), 2) connection.make_request.assert_called_once_with('GET', 'countries/1/states/count', - None, {'is_visible': True}, None) + None, {'is_visible': True}, None, version='v2') def test_count_with_custom_count_path(self): connection = MagicMock() @@ -202,4 +223,4 @@ def test_count_with_custom_count_path(self): self.assertEqual(OrderShipments.count(connection=connection, is_visible=True), 2) connection.make_request.assert_called_once_with('GET', 'orders/shipments/count', - None, {'is_visible': True}, None) + None, {'is_visible': True}, None, version='v2') From 57b546ce24b7b4c4c5f49ba1b616d2fa4ee62928 Mon Sep 17 00:00:00 2001 From: "Austin G. Smith" Date: Wed, 9 Mar 2022 16:19:16 -0600 Subject: [PATCH 06/12] Add V3 Abandoned Carts --- .gitignore | 3 ++- bigcommerce/resources/v3/__init__.py | 1 + bigcommerce/resources/v3/abandoned_carts.py | 6 ++++++ bigcommerce/resources/v3/products.py | 2 -- 4 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 bigcommerce/resources/v3/abandoned_carts.py diff --git a/.gitignore b/.gitignore index db1cc1e..9d957be 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ Pipfile Pipfile.lock *.egg-info* -venv/ \ No newline at end of file +venv/ +build \ No newline at end of file diff --git a/bigcommerce/resources/v3/__init__.py b/bigcommerce/resources/v3/__init__.py index 0700371..a6dc64c 100644 --- a/bigcommerce/resources/v3/__init__.py +++ b/bigcommerce/resources/v3/__init__.py @@ -1,2 +1,3 @@ +from .abandoned_carts import * from .products import * from .categories import * \ No newline at end of file diff --git a/bigcommerce/resources/v3/abandoned_carts.py b/bigcommerce/resources/v3/abandoned_carts.py new file mode 100644 index 0000000..3cb4cdd --- /dev/null +++ b/bigcommerce/resources/v3/abandoned_carts.py @@ -0,0 +1,6 @@ +from ..base import * + + +class AbandonedCarts(ListableApiResource): + resource_version = 'v3' + resource_name = 'abandoned-carts' \ No newline at end of file diff --git a/bigcommerce/resources/v3/products.py b/bigcommerce/resources/v3/products.py index d4bc01a..fb4a1a5 100644 --- a/bigcommerce/resources/v3/products.py +++ b/bigcommerce/resources/v3/products.py @@ -1,7 +1,5 @@ from ..base import * -""" -""" class Products(ListableApiResource, CreateableApiResource, UpdateableApiResource, DeleteableApiResource, From 7ee89684a190422cf91b7eb8ac570546af15b9bf Mon Sep 17 00:00:00 2001 From: "Austin G. Smith" Date: Wed, 9 Mar 2022 16:50:36 -0600 Subject: [PATCH 07/12] Add AbandonedCartEmails --- bigcommerce/resources/base.py | 4 ++-- bigcommerce/resources/v3/abandoned_cart_emails.py | 15 +++++++++++++++ tests/test_base.py | 11 +++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 bigcommerce/resources/v3/abandoned_cart_emails.py diff --git a/bigcommerce/resources/base.py b/bigcommerce/resources/base.py index d480813..6cce342 100644 --- a/bigcommerce/resources/base.py +++ b/bigcommerce/resources/base.py @@ -203,8 +203,8 @@ def _get_all_path(cls, parentid): return "%s/%s" % (cls.parent_resource, cls.resource_name) @classmethod - def all(cls, parentid=None, gparentid=None, connection=None, **params): - response = cls._make_request('GET', cls._get_all_path(parentid, gparentid), connection, params=params) + def all(cls, parentid=None, connection=None, **params): + response = cls._make_request('GET', cls._get_all_path(parentid), connection, params=params) return cls._create_object(response, connection=connection) diff --git a/bigcommerce/resources/v3/abandoned_cart_emails.py b/bigcommerce/resources/v3/abandoned_cart_emails.py new file mode 100644 index 0000000..74a8669 --- /dev/null +++ b/bigcommerce/resources/v3/abandoned_cart_emails.py @@ -0,0 +1,15 @@ +from ..base import * + + +class AbandonedCartEmails(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'settings/marketing/abandoned-cart-emails' + + def default(self): + return AbandonedCartEmailsDefault.all(connection=self._connection) + +class AbandonedCartEmailsDefault(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'settings/marketing/abandoned-cart-emails/defaults' diff --git a/tests/test_base.py b/tests/test_base.py index b76df74..f67de5a 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -1,9 +1,12 @@ +from sqlite3 import connect import unittest from bigcommerce.resources import Mapping, Orders, ApiResource, OrderShipments, Products, CountryStates,\ OrderCoupons, Webhooks, GoogleProductSearchMappings from bigcommerce.resources.v3 import ProductModifiers, ProductModifiersValues from mock import MagicMock +from bigcommerce.resources.v3.abandoned_cart_emails import AbandonedCartEmails + class TestMapping(unittest.TestCase): def test_init(self): @@ -74,7 +77,7 @@ def test_get_product_modifier_value(self): connection = MagicMock() connection.make_request.return_value = {'id': 2} - result = ProductModifiersValues.get(2, 3, connection, 1) + result = ProductModifiersValues.get(1, 2, 3, connection) self.assertIsInstance(result, ProductModifiersValues) self.assertEqual(result.id, 2) @@ -116,6 +119,11 @@ def test_all(self): self.assertEqual(len(list(result)), 3) connection.make_request.assert_called_once_with('GET', 'orders', None, {'limit': 3}, None, version='v2') + def test_abandoned_cart_emails_templates(self): + connection = MagicMock() + result = AbandonedCartEmails.all(connection) + connection.make_request.assert_called_once_with('GET', 'settings/marketing/abandoned-cart-emails', None, {}, None, version='v3') + class TestListableApiSubResource(unittest.TestCase): def test_all(self): @@ -134,7 +142,6 @@ def test_google_mappings(self): self.assertEqual(len(result), 2) connection.make_request.assert_called_once_with('GET', 'products/1/googleproductsearch', None, {'limit': 2}, None, version='v2') - class TestUpdateableApiResource(unittest.TestCase): def test_update(self): connection = MagicMock() From e16d516e7b5e988b2f95d05ad3d1f7e7b7ca5c95 Mon Sep 17 00:00:00 2001 From: "Austin G. Smith" Date: Wed, 9 Mar 2022 16:52:00 -0600 Subject: [PATCH 08/12] Add AbandonedCartEmails to Init --- bigcommerce/resources/v3/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bigcommerce/resources/v3/__init__.py b/bigcommerce/resources/v3/__init__.py index a6dc64c..10e7908 100644 --- a/bigcommerce/resources/v3/__init__.py +++ b/bigcommerce/resources/v3/__init__.py @@ -1,3 +1,4 @@ +from .abandoned_cart_emails import * from .abandoned_carts import * from .products import * from .categories import * \ No newline at end of file From a74e524136cebf312244aa755d88bfade1ec6f78 Mon Sep 17 00:00:00 2001 From: "Austin G. Smith" Date: Sat, 12 Mar 2022 06:46:20 -0600 Subject: [PATCH 09/12] Add V3 Resources --- README.rst | 2 +- bigcommerce/resources/base.py | 17 +++++-- bigcommerce/resources/v2/blog_posts.py | 2 + bigcommerce/resources/v2/shipping.py | 3 ++ bigcommerce/resources/v3/__init__.py | 27 +++++++++- .../resources/v3/abandoned_cart_emails.py | 5 +- bigcommerce/resources/v3/api_tokens.py | 11 ++++ bigcommerce/resources/v3/brands.py | 34 +++++++++++++ bigcommerce/resources/v3/carts.py | 11 ++++ bigcommerce/resources/v3/catalog_summary.py | 1 + bigcommerce/resources/v3/categories.py | 7 ++- bigcommerce/resources/v3/channels.py | 13 +++++ bigcommerce/resources/v3/checkouts.py | 1 + .../v3/custom_template_associations.py | 6 +++ bigcommerce/resources/v3/customers.py | 3 ++ bigcommerce/resources/v3/email_templates.py | 8 +++ bigcommerce/resources/v3/orders.py | 50 +++++++++++++++++++ bigcommerce/resources/v3/pages.py | 9 ++++ bigcommerce/resources/v3/payments.py | 1 + bigcommerce/resources/v3/pricelists.py | 28 +++++++++++ bigcommerce/resources/v3/pricing.py | 7 +++ bigcommerce/resources/v3/redirects.py | 7 +++ bigcommerce/resources/v3/scripts.py | 7 +++ bigcommerce/resources/v3/settings.py | 7 +++ bigcommerce/resources/v3/shipping.py | 10 ++++ bigcommerce/resources/v3/sites.py | 18 +++++++ bigcommerce/resources/v3/tax.py | 1 + bigcommerce/resources/v3/themes.py | 31 ++++++++++++ bigcommerce/resources/v3/variants.py | 1 + bigcommerce/resources/v3/webhooks.py | 7 +++ bigcommerce/resources/v3/widgets.py | 24 +++++++++ bigcommerce/resources/v3/wishlists.py | 16 ++++++ 32 files changed, 366 insertions(+), 9 deletions(-) create mode 100644 bigcommerce/resources/v3/api_tokens.py create mode 100644 bigcommerce/resources/v3/brands.py create mode 100644 bigcommerce/resources/v3/carts.py create mode 100644 bigcommerce/resources/v3/catalog_summary.py create mode 100644 bigcommerce/resources/v3/channels.py create mode 100644 bigcommerce/resources/v3/checkouts.py create mode 100644 bigcommerce/resources/v3/custom_template_associations.py create mode 100644 bigcommerce/resources/v3/customers.py create mode 100644 bigcommerce/resources/v3/email_templates.py create mode 100644 bigcommerce/resources/v3/orders.py create mode 100644 bigcommerce/resources/v3/pages.py create mode 100644 bigcommerce/resources/v3/payments.py create mode 100644 bigcommerce/resources/v3/pricelists.py create mode 100644 bigcommerce/resources/v3/pricing.py create mode 100644 bigcommerce/resources/v3/redirects.py create mode 100644 bigcommerce/resources/v3/scripts.py create mode 100644 bigcommerce/resources/v3/settings.py create mode 100644 bigcommerce/resources/v3/shipping.py create mode 100644 bigcommerce/resources/v3/sites.py create mode 100644 bigcommerce/resources/v3/tax.py create mode 100644 bigcommerce/resources/v3/themes.py create mode 100644 bigcommerce/resources/v3/variants.py create mode 100644 bigcommerce/resources/v3/webhooks.py create mode 100644 bigcommerce/resources/v3/widgets.py create mode 100644 bigcommerce/resources/v3/wishlists.py diff --git a/README.rst b/README.rst index 2c9daca..84442e8 100644 --- a/README.rst +++ b/README.rst @@ -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 `__ 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``. diff --git a/bigcommerce/resources/base.py b/bigcommerce/resources/base.py index 6cce342..ee015e0 100644 --- a/bigcommerce/resources/base.py +++ b/bigcommerce/resources/base.py @@ -24,10 +24,10 @@ def __str__(self): """ Display as a normal dict, but filter out underscored items first """ - return str({k: self.__dict__[k] for k in self.__dict__ if not k.startswith("_")}) + return str({k: self.__dict__[k] for k in self.__dict__ if not k.startswith("_") and k.startswith('connection')}) def __json__(self): - return {k: self.__dict__[k] for k in self.__dict__ if not k.startswith("_")} + return {k: self.__dict__[k] for k in self.__dict__ if not k.startswith("_") and not k.startswith('connection')} def __repr__(self): return "<%s at %s, %s>" % (type(self).__name__, hex(id(self)), str(self)) @@ -50,10 +50,13 @@ def _make_request(cls, method, url, connection, data=None, params=None, headers= @classmethod def _get_path(cls, id): - return "%s/%s" % (cls.resource_name, id) + if id: + return "%s/%s" % (cls.resource_name, id) + else: + return "%s" % (cls.resource_name) @classmethod - def get(cls, id, connection=None, **params): + def get(cls, id=None, connection=None, **params): response = cls._make_request('GET', cls._get_path(id), connection, params=params) return cls._create_object(response, connection=connection) @@ -229,6 +232,9 @@ def update(self, **updates): return self._create_object(response, connection=self._connection) +# TODO: Add Upsertable? + + class UpdateableApiSubResource(ApiSubResource): def _update_path(self): return "%s/%s/%s/%s" % (self.parent_resource, self.parent_id(), self.resource_name, self.id) @@ -293,6 +299,9 @@ def delete_all(cls, parentid, connection=None): return cls._make_request('DELETE', cls._delete_all_path(parentid), connection) +# TODO: Add CollectionUpdateableApiResource + + class CountableApiResource(ApiResource): @classmethod def _count_path(cls): diff --git a/bigcommerce/resources/v2/blog_posts.py b/bigcommerce/resources/v2/blog_posts.py index 072cd8e..9c7cd04 100644 --- a/bigcommerce/resources/v2/blog_posts.py +++ b/bigcommerce/resources/v2/blog_posts.py @@ -5,3 +5,5 @@ class BlogPosts(ListableApiResource, CreateableApiResource, UpdateableApiResource, DeleteableApiResource, CountableApiResource, CollectionDeleteableApiResource): resource_name = 'blog/posts' + +# TODO: tags \ No newline at end of file diff --git a/bigcommerce/resources/v2/shipping.py b/bigcommerce/resources/v2/shipping.py index e59ce72..bdca8c0 100644 --- a/bigcommerce/resources/v2/shipping.py +++ b/bigcommerce/resources/v2/shipping.py @@ -3,3 +3,6 @@ class ShippingMethods(ListableApiResource): resource_name = 'shipping/methods' + + +# TODO: carriers, methods, zones \ No newline at end of file diff --git a/bigcommerce/resources/v3/__init__.py b/bigcommerce/resources/v3/__init__.py index 10e7908..6073602 100644 --- a/bigcommerce/resources/v3/__init__.py +++ b/bigcommerce/resources/v3/__init__.py @@ -1,4 +1,29 @@ from .abandoned_cart_emails import * from .abandoned_carts import * +from .api_tokens import * +from .brands import * +from .carts import * +from .catalog_summary import * +from .categories import * +from .channels import * +from .checkouts import * +from .customers import * +from .custom_template_associations import * +from .email_templates import * +from .orders import * +from .pages import * +from .payments import * +from .pricelists import * +from .pricing import * from .products import * -from .categories import * \ No newline at end of file +from .redirects import * +from .scripts import * +from .settings import * +from .sites import * +from .shipping import * +from .tax import * +from .themes import * +from .variants import * +from .webhooks import * +from .widgets import * +from .wishlists import * \ No newline at end of file diff --git a/bigcommerce/resources/v3/abandoned_cart_emails.py b/bigcommerce/resources/v3/abandoned_cart_emails.py index 74a8669..7bd5741 100644 --- a/bigcommerce/resources/v3/abandoned_cart_emails.py +++ b/bigcommerce/resources/v3/abandoned_cart_emails.py @@ -4,12 +4,13 @@ class AbandonedCartEmails(ListableApiResource, CreateableApiResource, UpdateableApiResource, DeleteableApiResource): resource_version = 'v3' - resource_name = 'settings/marketing/abandoned-cart-emails' + resource_name = 'marketing/email-templates/abandoned_cart_email' def default(self): return AbandonedCartEmailsDefault.all(connection=self._connection) + class AbandonedCartEmailsDefault(ListableApiResource, CreateableApiResource, UpdateableApiResource, DeleteableApiResource): resource_version = 'v3' - resource_name = 'settings/marketing/abandoned-cart-emails/defaults' + resource_name = 'marketing/abandoned-cart-emails/default' diff --git a/bigcommerce/resources/v3/api_tokens.py b/bigcommerce/resources/v3/api_tokens.py new file mode 100644 index 0000000..7430edb --- /dev/null +++ b/bigcommerce/resources/v3/api_tokens.py @@ -0,0 +1,11 @@ +from ..base import * + + +class ApiToken(CreateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'storefront/api-token' + + +class ApiTokenCustomerImpersonation(CreateableApiResource): + resource_version = 'v3' + resource_name = 'storefront/api-token-customer-impersonation' diff --git a/bigcommerce/resources/v3/brands.py b/bigcommerce/resources/v3/brands.py new file mode 100644 index 0000000..475d0f2 --- /dev/null +++ b/bigcommerce/resources/v3/brands.py @@ -0,0 +1,34 @@ +from ..base import * + + +class Brands(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource, + CollectionDeleteableApiResource, CountableApiResource): + resource_version = 'v3' + resource_name = 'catalog/brands' + + def images(self, id=None): + if id: + return BrandImages.get(self.id, id, connection=self._connection) + else: + return BrandImages.all(self.id, connection=self._connection) + + def metafields(self, id=None): + if id: + return BrandMetafields.get(self.id, id, connection=self._connection) + else: + return BrandMetafields.all(self.id, connection=self._connection) + + +class BrandImages(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Brands.resource_version + resource_name = 'XXX' + parent_resource = Brands.resource_name + parent_key = 'brand_id' + + +class BrandMetafields(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Brands.resource_version + resource_name = 'XXX' + parent_resource = Brands.resource_name + parent_key = 'brand_id' diff --git a/bigcommerce/resources/v3/carts.py b/bigcommerce/resources/v3/carts.py new file mode 100644 index 0000000..361f664 --- /dev/null +++ b/bigcommerce/resources/v3/carts.py @@ -0,0 +1,11 @@ +from ..base import * + +# TODO: test +class Carts(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource, + CollectionDeleteableApiResource, CountableApiResource): + resource_version = 'v3' + resource_name = 'carts' + +# TODO: carts/{cartId}/items, post put delete +# TODO: carts/{cartId}/redirect_urls, post diff --git a/bigcommerce/resources/v3/catalog_summary.py b/bigcommerce/resources/v3/catalog_summary.py new file mode 100644 index 0000000..64b395e --- /dev/null +++ b/bigcommerce/resources/v3/catalog_summary.py @@ -0,0 +1 @@ +# TODO: this \ No newline at end of file diff --git a/bigcommerce/resources/v3/categories.py b/bigcommerce/resources/v3/categories.py index 61e8562..f3a5bd9 100644 --- a/bigcommerce/resources/v3/categories.py +++ b/bigcommerce/resources/v3/categories.py @@ -5,4 +5,9 @@ class Categories(ListableApiResource, CreateableApiResource, UpdateableApiResource, DeleteableApiResource, CollectionDeleteableApiResource, CountableApiResource): resource_version = 'v3' - resource_name = 'catalog/categories' \ No newline at end of file + resource_name = 'catalog/categories' + + +# TODO: product sort order +# TODO: images +# TODO: metafields \ No newline at end of file diff --git a/bigcommerce/resources/v3/channels.py b/bigcommerce/resources/v3/channels.py new file mode 100644 index 0000000..b52adac --- /dev/null +++ b/bigcommerce/resources/v3/channels.py @@ -0,0 +1,13 @@ +from ..base import * + + +class Channels(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource, + CollectionDeleteableApiResource, CountableApiResource): + resource_version = 'v3' + resource_name = 'channels' + + +# TODO: channels/{channel_id}/active-theme +# channels/currency-assignments +# channels/{channel_id}/listings \ No newline at end of file diff --git a/bigcommerce/resources/v3/checkouts.py b/bigcommerce/resources/v3/checkouts.py new file mode 100644 index 0000000..64b395e --- /dev/null +++ b/bigcommerce/resources/v3/checkouts.py @@ -0,0 +1 @@ +# TODO: this \ No newline at end of file diff --git a/bigcommerce/resources/v3/custom_template_associations.py b/bigcommerce/resources/v3/custom_template_associations.py new file mode 100644 index 0000000..1ae4eda --- /dev/null +++ b/bigcommerce/resources/v3/custom_template_associations.py @@ -0,0 +1,6 @@ +from ..base import * + +# TODO: Test +class CustomTemplateAssociations(CreateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'storefront/custom-template-associations' \ No newline at end of file diff --git a/bigcommerce/resources/v3/customers.py b/bigcommerce/resources/v3/customers.py new file mode 100644 index 0000000..5745073 --- /dev/null +++ b/bigcommerce/resources/v3/customers.py @@ -0,0 +1,3 @@ +# TODO: this + +# TODO: subscribers \ No newline at end of file diff --git a/bigcommerce/resources/v3/email_templates.py b/bigcommerce/resources/v3/email_templates.py new file mode 100644 index 0000000..c755522 --- /dev/null +++ b/bigcommerce/resources/v3/email_templates.py @@ -0,0 +1,8 @@ +from ..base import * + + +# TODO: test +class EmailTemplates(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'marketing/email-templates' \ No newline at end of file diff --git a/bigcommerce/resources/v3/orders.py b/bigcommerce/resources/v3/orders.py new file mode 100644 index 0000000..ef3d73f --- /dev/null +++ b/bigcommerce/resources/v3/orders.py @@ -0,0 +1,50 @@ +from ..base import * + +# TODO: test +class Orders(ApiResource): + resource_version = 'v3' + resource_name = 'orders' + + # TODO: add functions for subresources + + +class OrderMetafields(ListableApiSubResource, CreateableApiSubResource, UpdateableApiSubResource, DeleteableApiSubResource): + resource_version = Orders.resource_version + resource_name = 'metafields' + parent_resource = Orders.resource_name + parent_key = 'order_id' + + +class OrderPaymentActionsCapture(CreateableApiSubResource): + resource_version = Orders.resource_version + resource_name = 'payment_actions/capture' + parent_resource = Orders.resource_name + parent_key = 'order_id' + + +class OrderPaymentActionsRefundQuotes(CreateableApiSubResource): + resource_version = Orders.resource_version + resource_name = 'payment_actions/refund_quotes' + parent_resource = Orders.resource_name + parent_key = 'order_id' + + +class OrderPaymentActionsRefunds(ListableApiSubResource, CreateableApiSubResource, UpdateableApiSubResource, DeleteableApiSubResource): + resource_version = Orders.resource_version + resource_name = 'payment_actions/refunds' + parent_resource = Orders.resource_name + parent_key = 'order_id' + + +class OrderPaymentActionsVoid(CreateableApiSubResource): + resource_version = Orders.resource_version + resource_name = 'payment_actions/void' + parent_resource = Orders.resource_name + parent_key = 'order_id' + + +class OrderTransactions(ListableApiSubResource): + resource_version = Orders.resource_version + resource_name = 'transactions' + parent_resource = Orders.resource_name + parent_key = 'order_id' \ No newline at end of file diff --git a/bigcommerce/resources/v3/pages.py b/bigcommerce/resources/v3/pages.py new file mode 100644 index 0000000..2ad0422 --- /dev/null +++ b/bigcommerce/resources/v3/pages.py @@ -0,0 +1,9 @@ +from ..base import * + + +# TODO: test +# TODO: add CollectionUpdateableApiResource +class Pages(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource, CollectionDeleteableApiResource): + resource_version = 'v3' + resource_name = 'content/pages' \ No newline at end of file diff --git a/bigcommerce/resources/v3/payments.py b/bigcommerce/resources/v3/payments.py new file mode 100644 index 0000000..64b395e --- /dev/null +++ b/bigcommerce/resources/v3/payments.py @@ -0,0 +1 @@ +# TODO: this \ No newline at end of file diff --git a/bigcommerce/resources/v3/pricelists.py b/bigcommerce/resources/v3/pricelists.py new file mode 100644 index 0000000..73ef806 --- /dev/null +++ b/bigcommerce/resources/v3/pricelists.py @@ -0,0 +1,28 @@ +from ..base import * + + +# TODO: test +class Pricelists(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'pricelists' + + +# TODO: test +class PricelistAssignments(ListableApiResource, CreateableApiResource, DeleteableApiResource): + resource_version = Pricelists.resource_version + resource_name = 'assignments' + parent_resource = Pricelists.resource_name + parent_key = 'pricelist_id' + + +# TODO: test +class PricelistRecords(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = Pricelists.resource_version + resource_name = 'records' + parent_resource = Pricelists.resource_name + parent_key = 'pricelist_id' + + # TODO: get by variant + # TODO: get by currency \ No newline at end of file diff --git a/bigcommerce/resources/v3/pricing.py b/bigcommerce/resources/v3/pricing.py new file mode 100644 index 0000000..4fa0007 --- /dev/null +++ b/bigcommerce/resources/v3/pricing.py @@ -0,0 +1,7 @@ +from ..base import * + + +# TODO: test +class PricingProducts(CreateableApiResource): + resource_version = 'v3' + resource_name = 'pricing/products' diff --git a/bigcommerce/resources/v3/redirects.py b/bigcommerce/resources/v3/redirects.py new file mode 100644 index 0000000..f7f9b87 --- /dev/null +++ b/bigcommerce/resources/v3/redirects.py @@ -0,0 +1,7 @@ +from ..base import * + + +class Redirects(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'storefront/redirects' \ No newline at end of file diff --git a/bigcommerce/resources/v3/scripts.py b/bigcommerce/resources/v3/scripts.py new file mode 100644 index 0000000..91dae5a --- /dev/null +++ b/bigcommerce/resources/v3/scripts.py @@ -0,0 +1,7 @@ +from ..base import * + + +class Scripts(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'content/scripts' \ No newline at end of file diff --git a/bigcommerce/resources/v3/settings.py b/bigcommerce/resources/v3/settings.py new file mode 100644 index 0000000..2210a8b --- /dev/null +++ b/bigcommerce/resources/v3/settings.py @@ -0,0 +1,7 @@ +from ..base import * + + +class EmailStatuses(ListableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'settings/email-statuses' \ No newline at end of file diff --git a/bigcommerce/resources/v3/shipping.py b/bigcommerce/resources/v3/shipping.py new file mode 100644 index 0000000..77bb99d --- /dev/null +++ b/bigcommerce/resources/v3/shipping.py @@ -0,0 +1,10 @@ +from ..base import * + + +# TODO: test +class ShippingProductsCustomsInformation(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'shipping/products/customs-information' + + # TODO: add subresource function diff --git a/bigcommerce/resources/v3/sites.py b/bigcommerce/resources/v3/sites.py new file mode 100644 index 0000000..dd10cbb --- /dev/null +++ b/bigcommerce/resources/v3/sites.py @@ -0,0 +1,18 @@ +from ..base import * + + +# TODO: test +class Sites(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'sites' + + # TODO: add subresource function + + +# TODO: test +class SiteRoutes(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Sites.resource_version + resource_name = 'routes' + parent_resource = Sites.resource_name + parent_key = 'site_id' diff --git a/bigcommerce/resources/v3/tax.py b/bigcommerce/resources/v3/tax.py new file mode 100644 index 0000000..64b395e --- /dev/null +++ b/bigcommerce/resources/v3/tax.py @@ -0,0 +1 @@ +# TODO: this \ No newline at end of file diff --git a/bigcommerce/resources/v3/themes.py b/bigcommerce/resources/v3/themes.py new file mode 100644 index 0000000..675376e --- /dev/null +++ b/bigcommerce/resources/v3/themes.py @@ -0,0 +1,31 @@ +from ..base import * + +# TODO: Test + +class Themes(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'themes' + + +class ThemeActions(CreateableApiSubResource): + resource_version = Themes.resource_version + resource_name = 'actions' + parent_resource = Themes.resource_name + key = 'theme_uuid' + + +# TODO: Test +class ThemeActionsDownload(CreateableApiSubResource): + resource_version = Themes.resource_version + resource_name = 'actions/download' + parent_resource = Themes.resource_name + key = 'theme_uuid' + + +# TODO: Test +class ThemeActionsActivate(CreateableApiSubResource): + resource_version = Themes.resource_version + resource_name = 'actions/activate' + parent_resource = Themes.resource_name + key = 'theme_uuid' \ No newline at end of file diff --git a/bigcommerce/resources/v3/variants.py b/bigcommerce/resources/v3/variants.py new file mode 100644 index 0000000..64b395e --- /dev/null +++ b/bigcommerce/resources/v3/variants.py @@ -0,0 +1 @@ +# TODO: this \ No newline at end of file diff --git a/bigcommerce/resources/v3/webhooks.py b/bigcommerce/resources/v3/webhooks.py new file mode 100644 index 0000000..2f4a7a3 --- /dev/null +++ b/bigcommerce/resources/v3/webhooks.py @@ -0,0 +1,7 @@ +from ..base import * + + +class Webhooks(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version='v3' + resource_name = 'hooks' \ No newline at end of file diff --git a/bigcommerce/resources/v3/widgets.py b/bigcommerce/resources/v3/widgets.py new file mode 100644 index 0000000..d3e669e --- /dev/null +++ b/bigcommerce/resources/v3/widgets.py @@ -0,0 +1,24 @@ +from ..base import * + + +class Widgets(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'content/widgets' + + +class WidgetTemplates(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'content/widget-templates' + + +class WidgetRegions(ListableApiResource): + resource_version = 'v3' + resource_name = 'content/regions' + + +class WidgetPlacements(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'content/placements' \ No newline at end of file diff --git a/bigcommerce/resources/v3/wishlists.py b/bigcommerce/resources/v3/wishlists.py new file mode 100644 index 0000000..896525e --- /dev/null +++ b/bigcommerce/resources/v3/wishlists.py @@ -0,0 +1,16 @@ +from ..base import * + + +# TODO: Test +class Wishlists(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'wishlists' + + +# TODO: Test +class WishlistItems(ListableApiSubResource, CreateableApiSubResource, DeleteableApiSubResource): + resource_version = Wishlists.resource_version + resource_name = 'items' + parent_resource = Wishlists.resource_name + parent_key = 'wishlist_id' \ No newline at end of file From 2f83ae30dbaa3cd9b7d465e40df2862a7f13795c Mon Sep 17 00:00:00 2001 From: "Austin G. Smith" Date: Fri, 20 May 2022 12:37:20 -0500 Subject: [PATCH 10/12] Add themes, settings, abandoned carts, other resources --- bigcommerce/resources/base.py | 24 ++++- bigcommerce/resources/v2/store.py | 2 +- bigcommerce/resources/v3/abandoned_carts.py | 11 ++- .../v3/custom_template_associations.py | 2 +- bigcommerce/resources/v3/orders.py | 33 +++---- bigcommerce/resources/v3/settings.py | 94 ++++++++++++++++++- bigcommerce/resources/v3/themes.py | 5 + 7 files changed, 140 insertions(+), 31 deletions(-) diff --git a/bigcommerce/resources/base.py b/bigcommerce/resources/base.py index ee015e0..f0cc793 100644 --- a/bigcommerce/resources/base.py +++ b/bigcommerce/resources/base.py @@ -231,9 +231,27 @@ def update(self, **updates): response = self._make_request('PUT', self._update_path(), self._connection, data=updates) return self._create_object(response, connection=self._connection) +class CollectionUpdateableApiResource(ApiResource): + @classmethod + def _update_path(cls): + return cls.resource_name + @classmethod + def update(cls, updates, connection=None): + response = cls._make_request('PUT', cls._update_path(), connection, updates) + return cls._create_object(response, connection=connection) -# TODO: Add Upsertable? +class CollectionCreatableApiSubResource(ApiResource): + @classmethod + def _create_path(cls, parentid): + return "%s/%s/%s" % (cls.parent_resource, parentid, cls.resource_name) + @classmethod + def create(cls, parentid, data, connection=None): + response = cls._make_request('POST', cls._create_path(parentid), connection, data) + return cls._create_object(response, connection=connection) + + +# TODO: Add Upsertable? class UpdateableApiSubResource(ApiSubResource): def _update_path(self): @@ -298,10 +316,6 @@ def _delete_all_path(cls, parentid): def delete_all(cls, parentid, connection=None): return cls._make_request('DELETE', cls._delete_all_path(parentid), connection) - -# TODO: Add CollectionUpdateableApiResource - - class CountableApiResource(ApiResource): @classmethod def _count_path(cls): diff --git a/bigcommerce/resources/v2/store.py b/bigcommerce/resources/v2/store.py index 1cfe302..49a2627 100644 --- a/bigcommerce/resources/v2/store.py +++ b/bigcommerce/resources/v2/store.py @@ -1,5 +1,5 @@ from ..base import * -class Store(ListableApiResource): +class Store(ApiResource): resource_name = 'store' diff --git a/bigcommerce/resources/v3/abandoned_carts.py b/bigcommerce/resources/v3/abandoned_carts.py index 3cb4cdd..1d7e163 100644 --- a/bigcommerce/resources/v3/abandoned_carts.py +++ b/bigcommerce/resources/v3/abandoned_carts.py @@ -3,4 +3,13 @@ class AbandonedCarts(ListableApiResource): resource_version = 'v3' - resource_name = 'abandoned-carts' \ No newline at end of file + resource_name = 'abandoned-carts' + +#TODO +""" +GET /stores/{store_hash}/v3/abandoned-carts/settings +PUT /stores/{store_hash}/v3/abandoned-carts/settings +GET /stores/{store_hash}/v3/abandoned-carts/settings/channels/{channel_id} +PUT /stores/{store_hash}/v3/abandoned-carts/settings/channels/{channel_id} +GET /stores/{store_hash}/v3/abandoned-carts/{token} +""" \ No newline at end of file diff --git a/bigcommerce/resources/v3/custom_template_associations.py b/bigcommerce/resources/v3/custom_template_associations.py index 1ae4eda..7f8bbd9 100644 --- a/bigcommerce/resources/v3/custom_template_associations.py +++ b/bigcommerce/resources/v3/custom_template_associations.py @@ -1,6 +1,6 @@ from ..base import * # TODO: Test -class CustomTemplateAssociations(CreateableApiResource, DeleteableApiResource): +class CustomTemplateAssociations(DeleteableApiResource, CollectionDeleteableApiResource, CollectionUpdateableApiResource): resource_version = 'v3' resource_name = 'storefront/custom-template-associations' \ No newline at end of file diff --git a/bigcommerce/resources/v3/orders.py b/bigcommerce/resources/v3/orders.py index ef3d73f..fccc9c7 100644 --- a/bigcommerce/resources/v3/orders.py +++ b/bigcommerce/resources/v3/orders.py @@ -1,50 +1,43 @@ from ..base import * # TODO: test -class Orders(ApiResource): - resource_version = 'v3' - resource_name = 'orders' - - # TODO: add functions for subresources - - class OrderMetafields(ListableApiSubResource, CreateableApiSubResource, UpdateableApiSubResource, DeleteableApiSubResource): - resource_version = Orders.resource_version + resource_version = 'v3' resource_name = 'metafields' - parent_resource = Orders.resource_name + parent_resource = 'orders' parent_key = 'order_id' class OrderPaymentActionsCapture(CreateableApiSubResource): - resource_version = Orders.resource_version + resource_version = 'v3' resource_name = 'payment_actions/capture' - parent_resource = Orders.resource_name + parent_resource = 'orders' parent_key = 'order_id' -class OrderPaymentActionsRefundQuotes(CreateableApiSubResource): - resource_version = Orders.resource_version +class OrderPaymentActionsRefundQuotes(CollectionCreatableApiSubResource): + resource_version = 'v3' resource_name = 'payment_actions/refund_quotes' - parent_resource = Orders.resource_name + parent_resource = 'orders' parent_key = 'order_id' class OrderPaymentActionsRefunds(ListableApiSubResource, CreateableApiSubResource, UpdateableApiSubResource, DeleteableApiSubResource): - resource_version = Orders.resource_version + resource_version = 'v3' resource_name = 'payment_actions/refunds' - parent_resource = Orders.resource_name + parent_resource = 'orders' parent_key = 'order_id' class OrderPaymentActionsVoid(CreateableApiSubResource): - resource_version = Orders.resource_version + resource_version = 'v3' resource_name = 'payment_actions/void' - parent_resource = Orders.resource_name + parent_resource = 'orders' parent_key = 'order_id' class OrderTransactions(ListableApiSubResource): - resource_version = Orders.resource_version + resource_version = 'v3' resource_name = 'transactions' - parent_resource = Orders.resource_name + parent_resource = 'orders' parent_key = 'order_id' \ No newline at end of file diff --git a/bigcommerce/resources/v3/settings.py b/bigcommerce/resources/v3/settings.py index 2210a8b..ec2d5a4 100644 --- a/bigcommerce/resources/v3/settings.py +++ b/bigcommerce/resources/v3/settings.py @@ -1,7 +1,95 @@ from ..base import * +class SettingsAnalytics(ListableApiResource, UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/analytics' + + +class SettingsEmailStatuses(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/email-statuses' + + +class SettingsFaviconImage(CreateableApiResource): + resource_version = 'v3' + resource_name = 'settings/favicon/image' + +class SettingsCatalog(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/catalog' + + +class SettingsInventoryNotifications(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/inventory/notifications' + + +class SettingsLogo(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/logo' + + +class SettingsLogoImage(CreateableApiResource): + resource_version = 'v3' + resource_name = 'settings/logo/image' + + +class SettingsStorefrontProduct(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/storefront/product' + + +class SettingsSearchFilters(ListableApiResource, + CollectionUpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'settings/search/filters' + + +class SettingsSearchFiltersAvailable(ListableApiResource): + resource_version = 'v3' + resource_name = 'settings/search/filters/available' + + +class SettingsSearchFiltersContexts(ListableApiResource, UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/search/filters/contexts' + + +class SettingsStoreLocale(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/store/locale' + + +class SettingsStorefrontCategory(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/storefront/category' + + +class SettingsStorefrontRobotstxt(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/storefront/robotstxt' + + +class SettingsStorefrontSearch(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/storefront/search' + + +class SettingsStorefrontSecurity(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/storefront/security' + + +class SettingsStorefrontSeo(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/storefront/seo' + + +class SettingsStorefrontStatus(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'settings/storefront/status' + -class EmailStatuses(ListableApiResource, - UpdateableApiResource, DeleteableApiResource): +class SettingsStoreProfile(UpdateableApiResource): resource_version = 'v3' - resource_name = 'settings/email-statuses' \ No newline at end of file + resource_name = 'settings/store/profile' \ No newline at end of file diff --git a/bigcommerce/resources/v3/themes.py b/bigcommerce/resources/v3/themes.py index 675376e..aa0cf7c 100644 --- a/bigcommerce/resources/v3/themes.py +++ b/bigcommerce/resources/v3/themes.py @@ -7,6 +7,11 @@ class Themes(ListableApiResource, CreateableApiResource, resource_version = 'v3' resource_name = 'themes' +class CustomTemplates(ListableApiResource, CreateableApiResource, + CollectionUpdateableApiResource, DeleteableApiResource): + resource_version = 'v3' + resource_name = 'themes/custom-templates' + key='version_uuid' class ThemeActions(CreateableApiSubResource): resource_version = Themes.resource_version From b7a00a5d5094d37e7f94f49e7af3362729c08f97 Mon Sep 17 00:00:00 2001 From: "Austin G. Smith" Date: Thu, 16 Jun 2022 11:25:47 -0500 Subject: [PATCH 11/12] update checkout and customers v3 --- bigcommerce/resources/v3/checkouts.py | 9 ++++++++- bigcommerce/resources/v3/customers.py | 20 +++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/bigcommerce/resources/v3/checkouts.py b/bigcommerce/resources/v3/checkouts.py index 64b395e..7a4cc25 100644 --- a/bigcommerce/resources/v3/checkouts.py +++ b/bigcommerce/resources/v3/checkouts.py @@ -1 +1,8 @@ -# TODO: this \ No newline at end of file +from ..base import * + + +class Checkouts(UpdateableApiResource): + resource_version = 'v3' + resource_name = 'checkouts' + +# 9139f12e-053f-4dfc-ba8b-0e6ec4a4e4a2 \ No newline at end of file diff --git a/bigcommerce/resources/v3/customers.py b/bigcommerce/resources/v3/customers.py index 5745073..bc14932 100644 --- a/bigcommerce/resources/v3/customers.py +++ b/bigcommerce/resources/v3/customers.py @@ -1,3 +1,21 @@ -# TODO: this +from ..base import * + + +class Customers(ListableApiResource, CreateableApiResource, + UpdateableApiResource, DeleteableApiResource, + CollectionDeleteableApiResource, CountableApiResource): + + resource_name = 'customers' + resource_version = 'v3' + +class CustomerAddresses(ListableApiResource, CreateableApiResource, CollectionUpdateableApiResource, DeleteableApiResource): + + resource_name = 'customers/addresses' + resource_version = 'v3' + +class CustomerFormFieldValues(ListableApiResource, UpdateableApiResource): + + resource_name = 'customers/form-field-values' + resource_version = 'v3' # TODO: subscribers \ No newline at end of file From 929565316170097a7a59a867505784e2f316522e Mon Sep 17 00:00:00 2001 From: Austin Smith Date: Thu, 25 Jan 2024 13:17:57 -0600 Subject: [PATCH 12/12] Add SQ GH Action --- .github/workflows/build.yml | 29 +++++++++++++++++++++++++++++ sonar-project.properties | 1 + 2 files changed, 30 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 sonar-project.properties diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..fa838bc --- /dev/null +++ b/.github/workflows/build.yml @@ -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 }} \ No newline at end of file diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..62032d1 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1 @@ +sonar.projectKey=aglensmith_bigcommerce-api-python_AY1CCzYcg9ty7uGlFgnb \ No newline at end of file