Skip to content

Commit

Permalink
Merge pull request #10 from groveco/retry-behaviour
Browse files Browse the repository at this point in the history
Retry behaviour
  • Loading branch information
Anton-Shutik authored Nov 15, 2024
2 parents e59fb7d + f537218 commit 277e6a8
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 97 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.5
0.0.6
86 changes: 67 additions & 19 deletions shopify_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from urllib.parse import urljoin

import requests

from shopify_client.hooks import rate_limit
from requests.adapters import HTTPAdapter
from urllib3 import Retry

from .endpoint import DraftOrdersEndpoint, Endpoint, OrdersEndpoint
from .graphql import GraphQL
Expand All @@ -16,19 +16,46 @@

class ShopifyClient(requests.Session):

def __init__(self, api_url, api_token, api_version=SHOPIFY_API_VERSION, graphql_queries_dir=None):
def __init__(
self,
api_url,
api_token,
api_version=SHOPIFY_API_VERSION,
graphql_queries_dir=None,
):
super().__init__()
self.api_url = api_url
self.api_version = api_version
self.headers.update({"X-Shopify-Access-Token": api_token, "Content-Type": "application/json"})
self.headers.update(
{"X-Shopify-Access-Token": api_token, "Content-Type": "application/json"}
)

retry_strategy = Retry(
total=10,
connect=3,
read=3,
status=5,
backoff_factor=0.5,
allowed_methods=frozenset(["GET", "POST", "PUT", "PATCH", "DELETE"]),
status_forcelist=frozenset([429, 500, 502, 503, 504]),
respect_retry_after_header=True,
)

adapter = HTTPAdapter(max_retries=retry_strategy)
self.mount("http://", adapter)
self.mount("https://", adapter)

# Access
self.storefront_access_tokens = Endpoint(client=self, endpoint="storefront_access_tokens")
self.storefront_access_tokens = Endpoint(
client=self, endpoint="storefront_access_tokens"
)

# Billing
self.application_charges = Endpoint(client=self, endpoint="application_charges")
self.application_credits = Endpoint(client=self, endpoint="application_credits")
self.recurring_application_charges = Endpoint(client=self, endpoint="recurring_application_charges")
self.recurring_application_charges = Endpoint(
client=self, endpoint="recurring_application_charges"
)

# Customers
self.customers = Endpoint(client=self, endpoint="customers", metafields=True)
Expand All @@ -53,7 +80,9 @@ def __init__(self, api_url, api_token, api_version=SHOPIFY_API_VERSION, graphql_
self.marketing_events = Endpoint(client=self, endpoint="marketing_events")

# Mobile Support
self.mobile_platform_applications = Endpoint(client=self, endpoint="mobile_platform_applications")
self.mobile_platform_applications = Endpoint(
client=self, endpoint="mobile_platform_applications"
)

# Online Store
self.articles = Endpoint(client=self, endpoint="articles", metafields=True)
Expand All @@ -62,35 +91,51 @@ def __init__(self, api_url, api_token, api_version=SHOPIFY_API_VERSION, graphql_

# Orders
self.checkouts = Endpoint(client=self, endpoint="checkouts")
self.draft_orders = DraftOrdersEndpoint(client=self, endpoint="draft_orders", metafields=True)
self.draft_orders = DraftOrdersEndpoint(
client=self, endpoint="draft_orders", metafields=True
)
self.orders = OrdersEndpoint(client=self, endpoint="orders")

# Plus
self.users = Endpoint(client=self, endpoint="users")

# Products
self.collects = Endpoint(client=self, endpoint="collects")
self.collections = Endpoint(client=self, endpoint="collections", metafields=True)
self.collections = Endpoint(
client=self, endpoint="collections", metafields=True
)
self.custom_collections = Endpoint(client=self, endpoint="custom_collections")
self.products = Endpoint(client=self, endpoint="products", metafields=True)
self.products.images = Endpoint(client=self, endpoint="products", sub_endpoint="images")
self.product_images = Endpoint(client=self, endpoint="product_images", metafields=True)
self.smart_collections = Endpoint(client=self, endpoint="smart_collections", metafields=True)
self.products.images = Endpoint(
client=self, endpoint="products", sub_endpoint="images"
)
self.product_images = Endpoint(
client=self, endpoint="product_images", metafields=True
)
self.smart_collections = Endpoint(
client=self, endpoint="smart_collections", metafields=True
)
self.variants = Endpoint(client=self, endpoint="variants", metafields=True)

# Sales Channels
self.collections_listings = Endpoint(client=self, endpoint="collections_listings")
self.collections_listings = Endpoint(
client=self, endpoint="collections_listings"
)
self.checkouts = Endpoint(client=self, endpoint="checkouts")
self.product_listings = Endpoint(client=self, endpoint="product_listings")
self.resource_feedback = Endpoint(client=self, endpoint="resource_feedback")

# Shipping and Fulfillment
self.assigned_fulfillment_orders = Endpoint(client=self, endpoint="assigned_fulfillment_orders")
self.assigned_fulfillment_orders = Endpoint(
client=self, endpoint="assigned_fulfillment_orders"
)
# TODO: Implement Fulfillment
self.fulfillment_orders = Endpoint(client=self, endpoint="fulfillment_orders")
self.carrier_services = Endpoint(client=self, endpoint="carrier_services")
self.fulfillments = Endpoint(client=self, endpoint="fulfillments")
self.fulfillment_services = Endpoint(client=self, endpoint="fulfillment_services")
self.fulfillment_services = Endpoint(
client=self, endpoint="fulfillment_services"
)

# Shopify Payments
self.shopify_payments = Endpoint(client=self, endpoint="shopify_payments")
Expand All @@ -111,10 +156,13 @@ def __init__(self, api_url, api_token, api_version=SHOPIFY_API_VERSION, graphql_
# GraphQL
self.query = GraphQL(client=self, graphql_queries_dir=graphql_queries_dir)

self.hooks["response"].append(rate_limit)

def request(self, method, url, *args, **kwargs):
response = super().request(method, urljoin(f"{self.api_url}/admin/api/{self.api_version}/", url), *args, **kwargs)
response = super().request(
method,
urljoin(f"{self.api_url}/admin/api/{self.api_version}/", url),
*args,
**kwargs,
)
logger.info(f"Requesting {method} {url}: {response.status_code}")
return response

Expand All @@ -124,4 +172,4 @@ def parse_response(self, response):
except requests.exceptions.HTTPError as e:
logger.warning(f"Failed to execute request: {response.text}")
raise e
return response.json()
return response.json()
18 changes: 0 additions & 18 deletions shopify_client/hooks.py

This file was deleted.

20 changes: 13 additions & 7 deletions tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
import requests
import pytest
from shopify_client import ShopifyClient
from tests.conftest import CopyingMock


def test_client_initialization(shopify_client):
assert shopify_client.headers["X-Shopify-Access-Token"] == "test-token"
assert shopify_client.headers["Content-Type"] == "application/json"


def test_request_with_versioning(shopify_client, mocker):
mock_request = mocker.patch("requests.Session.request", return_value=mocker.Mock(status_code=200, json=lambda: {"result": "success"}))
mock_request = mocker.patch(
"requests.Session.request",
return_value=mocker.Mock(status_code=200, json=lambda: {"result": "success"}),
)
response = shopify_client.request("GET", "products.json")
assert response.json() == {"result": "success"}
mock_request.assert_called_once_with(
"GET",
"https://test-shop.myshopify.com/admin/api/2024-10/products.json"
"GET", "https://test-shop.myshopify.com/admin/api/2024-10/products.json"
)


def test_parse_response_success(shopify_client, mocker):
mock_response = mocker.Mock(status_code=200, json=lambda: {"key": "value"})
parsed = shopify_client.parse_response(mock_response)
assert parsed == {"key": "value"}


def test_parse_response_error(shopify_client, mocker):
mock_response = mocker.Mock(status_code=404, text="Not Found")
mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("Not Found")

mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError(
"Not Found"
)

with pytest.raises(requests.exceptions.HTTPError):
shopify_client.parse_response(mock_response)
52 changes: 0 additions & 52 deletions tests/test_hooks.py

This file was deleted.

Loading

0 comments on commit 277e6a8

Please sign in to comment.