From 2393a262b31d2714c18554c8c0b192d60cf61bd0 Mon Sep 17 00:00:00 2001 From: drake Date: Wed, 17 Sep 2025 10:58:42 -0500 Subject: [PATCH 1/2] Allow passing v3-relative paths like v3/catalog/... --- bigcommerce/connection.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bigcommerce/connection.py b/bigcommerce/connection.py index 96fc6a4..23aa7a7 100644 --- a/bigcommerce/connection.py +++ b/bigcommerce/connection.py @@ -207,7 +207,9 @@ 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) + if url and isinstance(url, str) and url.startswith('v3/'): + return "https://" + self.host + "/stores/{}/".format(self.store_hash) + url + return "https://" + self.host + self.api_path.format(self.store_hash, url) @staticmethod def _oauth_headers(cid, atoken): From a14fb5fcd1f1ed59657b8f6ab9da86110e142b4d Mon Sep 17 00:00:00 2001 From: drake Date: Wed, 17 Sep 2025 11:00:55 -0500 Subject: [PATCH 2/2] Add product variant resource type interactions as product sub-type --- bigcommerce/resources/products.py | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/bigcommerce/resources/products.py b/bigcommerce/resources/products.py index d569de9..078d7e1 100644 --- a/bigcommerce/resources/products.py +++ b/bigcommerce/resources/products.py @@ -63,6 +63,11 @@ def videos(self, id=None): def google_mappings(self): return GoogleProductSearchMappings.all(self.id, connection=self._connection) + def variants(self, id=None): + if id: + return ProductVariants.get(self.id, id, connection=self._connection) + else: + return ProductVariants.all(self.id, connection=self._connection) class ProductConfigurableFields(ListableApiSubResource, DeleteableApiSubResource, CollectionDeleteableApiSubResource, CountableApiSubResource): @@ -145,3 +150,52 @@ class GoogleProductSearchMappings(ListableApiSubResource): resource_name = 'googleproductsearch' parent_resource = 'products' parent_key = 'product_id' + + +class ProductVariants(ApiSubResource): + """ + v3 Catalog variants resource + Endpoints: + - GET /v3/catalog/products/{product_id}/variants + - GET /v3/catalog/products/{product_id}/variants/{variant_id} + - POST /v3/catalog/products/{product_id}/variants + - PUT /v3/catalog/products/{product_id}/variants/{variant_id} + - DELETE /v3/catalog/products/{product_id}/variants/{variant_id} + Responses are wrapped in {'data': ...}; unwrap for callers. + """ + + parent_key = 'product_id' + + @classmethod + def _v3_path(cls, parentid, id=None): + base = "v3/catalog/products/{}/variants".format(parentid) + return base if id is None else base + "/{}".format(id) + + @classmethod + def all(cls, parentid, connection=None, **params): + rsp = cls._make_request('GET', cls._v3_path(parentid), connection, params=params) + items = (rsp or {}).get('data', []) if isinstance(rsp, dict) else (rsp or []) + return [cls(obj, _connection=connection) for obj in items] + + @classmethod + def get(cls, parentid, id, connection=None, **params): + rsp = cls._make_request('GET', cls._v3_path(parentid, id), connection, params=params) + data = rsp.get('data', rsp) if isinstance(rsp, dict) else rsp + return cls(data, _connection=connection) + + @classmethod + def create(cls, parentid, connection=None, **params): + rsp = cls._make_request('POST', cls._v3_path(parentid), connection, data=params) + data = rsp.get('data', rsp) if isinstance(rsp, dict) else rsp + return cls(data, _connection=connection) + + def _update_path_v3(self): + return self._v3_path(self.parent_id(), self.id) + + def update(self, **updates): + rsp = self._make_request('PUT', self._update_path_v3(), self._connection, data=updates) + data = rsp.get('data', rsp) if isinstance(rsp, dict) else rsp + return self.__class__(data, _connection=self._connection) + + def delete(self): + return self._make_request('DELETE', self._update_path_v3(), self._connection)