ordercloud-python 2026.6.1__py3-none-any.whl → 2026.10.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ordercloud/http.py +40 -14
- ordercloud/middleware.py +7 -0
- ordercloud/models/api_client.py +0 -2
- ordercloud/models/discount.py +2 -0
- ordercloud/models/misc.py +1 -0
- ordercloud/resources/forgotten_credentials.py +4 -1
- ordercloud/resources/group_orders.py +6 -1
- ordercloud/resources/me.py +37 -8
- ordercloud/sync_client.py +1 -3
- {ordercloud_python-2026.6.1.dist-info → ordercloud_python-2026.10.2.dist-info}/METADATA +20 -15
- {ordercloud_python-2026.6.1.dist-info → ordercloud_python-2026.10.2.dist-info}/RECORD +13 -13
- {ordercloud_python-2026.6.1.dist-info → ordercloud_python-2026.10.2.dist-info}/WHEEL +1 -1
- {ordercloud_python-2026.6.1.dist-info → ordercloud_python-2026.10.2.dist-info}/licenses/LICENSE +0 -0
ordercloud/http.py
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
-
from
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Optional, Union
|
|
6
7
|
|
|
7
8
|
import httpx
|
|
8
9
|
|
|
@@ -11,13 +12,28 @@ from .config import OrderCloudConfig
|
|
|
11
12
|
from .errors import ApiError, AuthenticationError, OrderCloudError
|
|
12
13
|
from .middleware import AfterResponse, BeforeRequest, RequestContext, ResponseContext
|
|
13
14
|
|
|
14
|
-
__all__ = ["HttpClient"]
|
|
15
|
+
__all__ = ["HttpClient", "SensitivePath"]
|
|
15
16
|
|
|
16
17
|
logger = logging.getLogger("ordercloud")
|
|
17
18
|
|
|
18
19
|
_RETRYABLE_STATUSES = frozenset({429, 500, 502, 503, 504})
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class SensitivePath:
|
|
24
|
+
"""A request path that carries a sensitive value.
|
|
25
|
+
|
|
26
|
+
``wire`` is sent to the API; ``log_form`` is the only form the SDK may log.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
wire: str
|
|
30
|
+
log_form: str
|
|
31
|
+
|
|
32
|
+
def __repr__(self) -> str:
|
|
33
|
+
# str() falls back to this, so neither ever renders the wire form.
|
|
34
|
+
return f"SensitivePath({self.log_form!r})"
|
|
35
|
+
|
|
36
|
+
|
|
21
37
|
class HttpClient:
|
|
22
38
|
"""Async HTTP client with automatic authentication and error handling.
|
|
23
39
|
|
|
@@ -60,7 +76,7 @@ class HttpClient:
|
|
|
60
76
|
async def request(
|
|
61
77
|
self,
|
|
62
78
|
method: str,
|
|
63
|
-
path: str,
|
|
79
|
+
path: Union[str, SensitivePath],
|
|
64
80
|
*,
|
|
65
81
|
params: Optional[dict[str, Any]] = None,
|
|
66
82
|
json: Optional[dict[str, Any]] = None,
|
|
@@ -69,7 +85,9 @@ class HttpClient:
|
|
|
69
85
|
|
|
70
86
|
Args:
|
|
71
87
|
method: HTTP method (e.g. ``"GET"``, ``"POST"``).
|
|
72
|
-
path: API path relative to the base URL (e.g. ``"/products"``)
|
|
88
|
+
path: API path relative to the base URL (e.g. ``"/products"``),
|
|
89
|
+
or a ``SensitivePath`` whose ``log_form`` is logged in place
|
|
90
|
+
of the path that is sent. A plain string is logged as given.
|
|
73
91
|
params: Query parameters (``None`` values are stripped).
|
|
74
92
|
json: JSON request body.
|
|
75
93
|
|
|
@@ -80,7 +98,8 @@ class HttpClient:
|
|
|
80
98
|
AuthenticationError: On 401 or 403 responses.
|
|
81
99
|
OrderCloudError: On any other 4xx/5xx response.
|
|
82
100
|
"""
|
|
83
|
-
|
|
101
|
+
wire_path, log_path = self._split_path(path)
|
|
102
|
+
url = f"{self._config.base_url}{wire_path}"
|
|
84
103
|
|
|
85
104
|
if params:
|
|
86
105
|
params = {k: v for k, v in params.items() if v is not None}
|
|
@@ -93,7 +112,7 @@ class HttpClient:
|
|
|
93
112
|
|
|
94
113
|
ctx = RequestContext(
|
|
95
114
|
method=method,
|
|
96
|
-
path=
|
|
115
|
+
path=wire_path,
|
|
97
116
|
url=url,
|
|
98
117
|
headers=dict(headers),
|
|
99
118
|
params=dict(params) if params else None,
|
|
@@ -103,7 +122,7 @@ class HttpClient:
|
|
|
103
122
|
for hook in self._before_request:
|
|
104
123
|
await hook(ctx)
|
|
105
124
|
|
|
106
|
-
logger.debug("Request: %s %s", method,
|
|
125
|
+
logger.debug("Request: %s %s", method, log_path)
|
|
107
126
|
|
|
108
127
|
resp = await self._client.request(
|
|
109
128
|
ctx.method,
|
|
@@ -113,7 +132,7 @@ class HttpClient:
|
|
|
113
132
|
json=ctx.json,
|
|
114
133
|
)
|
|
115
134
|
|
|
116
|
-
logger.debug("Response: %s %s %d", method,
|
|
135
|
+
logger.debug("Response: %s %s %d", method, log_path, resp.status_code)
|
|
117
136
|
|
|
118
137
|
resp_ctx = ResponseContext(request=ctx, response=resp, attempt=attempt)
|
|
119
138
|
for after_hook in self._after_response:
|
|
@@ -129,7 +148,7 @@ class HttpClient:
|
|
|
129
148
|
attempt + 1,
|
|
130
149
|
self._config.max_retries,
|
|
131
150
|
method,
|
|
132
|
-
|
|
151
|
+
log_path,
|
|
133
152
|
resp.status_code,
|
|
134
153
|
delay,
|
|
135
154
|
)
|
|
@@ -141,6 +160,13 @@ class HttpClient:
|
|
|
141
160
|
# Unreachable — loop always returns or raises
|
|
142
161
|
raise AssertionError("unreachable") # pragma: no cover
|
|
143
162
|
|
|
163
|
+
@staticmethod
|
|
164
|
+
def _split_path(path: Union[str, SensitivePath]) -> tuple[str, str]:
|
|
165
|
+
"""Return ``(wire path, log path)``; a plain ``str`` is its own log form."""
|
|
166
|
+
if isinstance(path, SensitivePath):
|
|
167
|
+
return path.wire, path.log_form
|
|
168
|
+
return path, path
|
|
169
|
+
|
|
144
170
|
def _retry_delay(self, resp: httpx.Response, attempt: int) -> float:
|
|
145
171
|
"""Calculate the delay before the next retry attempt.
|
|
146
172
|
|
|
@@ -157,13 +183,13 @@ class HttpClient:
|
|
|
157
183
|
delay: float = self._config.retry_backoff * (2**attempt)
|
|
158
184
|
return min(delay, max_delay)
|
|
159
185
|
|
|
160
|
-
async def get(self, path: str, **params: Any) -> httpx.Response:
|
|
186
|
+
async def get(self, path: Union[str, SensitivePath], **params: Any) -> httpx.Response:
|
|
161
187
|
"""Send a GET request."""
|
|
162
188
|
return await self.request("GET", path, params=params or None)
|
|
163
189
|
|
|
164
190
|
async def post(
|
|
165
191
|
self,
|
|
166
|
-
path: str,
|
|
192
|
+
path: Union[str, SensitivePath],
|
|
167
193
|
json: Optional[dict[str, Any]] = None,
|
|
168
194
|
params: Optional[dict[str, Any]] = None,
|
|
169
195
|
) -> httpx.Response:
|
|
@@ -172,7 +198,7 @@ class HttpClient:
|
|
|
172
198
|
|
|
173
199
|
async def put(
|
|
174
200
|
self,
|
|
175
|
-
path: str,
|
|
201
|
+
path: Union[str, SensitivePath],
|
|
176
202
|
json: Optional[dict[str, Any]] = None,
|
|
177
203
|
params: Optional[dict[str, Any]] = None,
|
|
178
204
|
) -> httpx.Response:
|
|
@@ -181,14 +207,14 @@ class HttpClient:
|
|
|
181
207
|
|
|
182
208
|
async def patch(
|
|
183
209
|
self,
|
|
184
|
-
path: str,
|
|
210
|
+
path: Union[str, SensitivePath],
|
|
185
211
|
json: Optional[dict[str, Any]] = None,
|
|
186
212
|
params: Optional[dict[str, Any]] = None,
|
|
187
213
|
) -> httpx.Response:
|
|
188
214
|
"""Send a PATCH request."""
|
|
189
215
|
return await self.request("PATCH", path, json=json, params=params)
|
|
190
216
|
|
|
191
|
-
async def delete(self, path: str, **params: Any) -> httpx.Response:
|
|
217
|
+
async def delete(self, path: Union[str, SensitivePath], **params: Any) -> httpx.Response:
|
|
192
218
|
"""Send a DELETE request."""
|
|
193
219
|
return await self.request("DELETE", path, params=params or None)
|
|
194
220
|
|
ordercloud/middleware.py
CHANGED
|
@@ -20,6 +20,13 @@ class RequestContext:
|
|
|
20
20
|
Hooks can modify ``headers``, ``params``, or ``json`` in place
|
|
21
21
|
before the request is sent.
|
|
22
22
|
|
|
23
|
+
``path``, ``url`` and ``params`` are the concrete values sent on the
|
|
24
|
+
wire, and ``headers`` holds the ``Authorization`` bearer token. Any of
|
|
25
|
+
them may carry credentials — a password-reset verification code or a
|
|
26
|
+
group-order invitation ID in the path, for example — so redact them
|
|
27
|
+
before logging them from a hook. The SDK's own log lines redact such
|
|
28
|
+
path values; the context passed to hooks does not.
|
|
29
|
+
|
|
23
30
|
Attributes:
|
|
24
31
|
method: HTTP method (e.g. ``"GET"``).
|
|
25
32
|
path: API path relative to the base URL.
|
ordercloud/models/api_client.py
CHANGED
|
@@ -47,7 +47,6 @@ class ApiClient(OrderCloudModel, Generic[XP]):
|
|
|
47
47
|
|
|
48
48
|
Attributes:
|
|
49
49
|
id: Used for OAuth 2.0 workflows and impersonation. (read-only)
|
|
50
|
-
client_secret: Enables the OAuth 2.0 client credentials grant type. Required on all OAuth workflows when present.
|
|
51
50
|
access_token_duration:
|
|
52
51
|
active: All user authentication is prohibited if false.
|
|
53
52
|
app_name:
|
|
@@ -74,7 +73,6 @@ class ApiClient(OrderCloudModel, Generic[XP]):
|
|
|
74
73
|
"""
|
|
75
74
|
|
|
76
75
|
id: Optional[str] = Field(None, alias="ID")
|
|
77
|
-
client_secret: Optional[str] = Field(None, alias="ClientSecret")
|
|
78
76
|
access_token_duration: Optional[int] = Field(None, alias="AccessTokenDuration")
|
|
79
77
|
active: Optional[bool] = Field(None, alias="Active")
|
|
80
78
|
app_name: Optional[str] = Field(None, alias="AppName")
|
ordercloud/models/discount.py
CHANGED
|
@@ -52,6 +52,7 @@ class Discount(OrderCloudModel, Generic[XP]):
|
|
|
52
52
|
catalog_id:
|
|
53
53
|
category_id:
|
|
54
54
|
product_id:
|
|
55
|
+
priority: Controls precedence when multiple discounts apply to the same user and product. Lower number = higher precedence (1 beats 2). Null is applied last.
|
|
55
56
|
xp:
|
|
56
57
|
"""
|
|
57
58
|
|
|
@@ -62,4 +63,5 @@ class Discount(OrderCloudModel, Generic[XP]):
|
|
|
62
63
|
catalog_id: Optional[str] = Field(None, alias="CatalogID")
|
|
63
64
|
category_id: Optional[str] = Field(None, alias="CategoryID")
|
|
64
65
|
product_id: Optional[str] = Field(None, alias="ProductID")
|
|
66
|
+
priority: Optional[int] = Field(None, alias="Priority")
|
|
65
67
|
xp: Optional[XP] = Field(None, alias="xp")
|
ordercloud/models/misc.py
CHANGED
|
@@ -78,6 +78,7 @@ class ApiRole(OrderCloudEnum):
|
|
|
78
78
|
MeSubscriptionAdmin = "MeSubscriptionAdmin"
|
|
79
79
|
MeXpAdmin = "MeXpAdmin"
|
|
80
80
|
OrderAdmin = "OrderAdmin"
|
|
81
|
+
OrderEditAfterSubmit = "OrderEditAfterSubmit"
|
|
81
82
|
OrderReader = "OrderReader"
|
|
82
83
|
OverrideShipping = "OverrideShipping"
|
|
83
84
|
OverrideTax = "OverrideTax"
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
from typing import Any, Optional, Union
|
|
7
7
|
|
|
8
|
+
from ..http import SensitivePath
|
|
8
9
|
from ..models.auth_models import OneTimePasswordRequest, PasswordReset, PasswordResetRequest
|
|
9
10
|
from .base import BaseResource
|
|
10
11
|
|
|
@@ -50,7 +51,9 @@ class ForgottenCredentialsResource(BaseResource):
|
|
|
50
51
|
password_reset: A ``PasswordReset`` model or dict. Required fields: ClientID, Username.
|
|
51
52
|
"""
|
|
52
53
|
await self._http.put(
|
|
53
|
-
|
|
54
|
+
SensitivePath(
|
|
55
|
+
wire=f"/password/reset/{verification_code}", log_form="/password/reset/***"
|
|
56
|
+
),
|
|
54
57
|
json=self._serialize(password_reset),
|
|
55
58
|
)
|
|
56
59
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"""OrderCloud GroupOrders API resource."""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
|
+
from ..http import SensitivePath
|
|
6
7
|
from ..models.auth_models import AccessToken
|
|
7
8
|
from .base import BaseResource
|
|
8
9
|
|
|
@@ -24,5 +25,9 @@ class GroupOrdersResource(BaseResource):
|
|
|
24
25
|
Returns:
|
|
25
26
|
The AccessToken object.
|
|
26
27
|
"""
|
|
27
|
-
resp = await self._http.post(
|
|
28
|
+
resp = await self._http.post(
|
|
29
|
+
SensitivePath(
|
|
30
|
+
wire=f"/grouporders/{invitation_id}/token", log_form="/grouporders/***/token"
|
|
31
|
+
)
|
|
32
|
+
)
|
|
28
33
|
return AccessToken(**resp.json())
|
ordercloud/resources/me.py
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
from typing import Any, Optional, Union
|
|
7
7
|
|
|
8
|
+
from ..http import SensitivePath
|
|
8
9
|
from ..models.auth_models import AccessTokenBasic, TokenPasswordReset
|
|
9
10
|
from ..models.buyer import BuyerAddress, BuyerCreditCard, BuyerProduct, BuyerSupplier, ProductSeller
|
|
10
11
|
from ..models.catalog import Catalog
|
|
@@ -513,7 +514,11 @@ class MeResource(BaseResource):
|
|
|
513
514
|
Returns:
|
|
514
515
|
The GroupOrderInvitation object.
|
|
515
516
|
"""
|
|
516
|
-
resp = await self._http.get(
|
|
517
|
+
resp = await self._http.get(
|
|
518
|
+
SensitivePath(
|
|
519
|
+
wire=f"/me/orderinvitations/{invitation_id}", log_form="/me/orderinvitations/***"
|
|
520
|
+
)
|
|
521
|
+
)
|
|
517
522
|
return GroupOrderInvitation(**resp.json())
|
|
518
523
|
|
|
519
524
|
async def delete_group_order_invitation(
|
|
@@ -525,7 +530,11 @@ class MeResource(BaseResource):
|
|
|
525
530
|
Args:
|
|
526
531
|
invitation_id: ID of the invitation.
|
|
527
532
|
"""
|
|
528
|
-
await self._http.delete(
|
|
533
|
+
await self._http.delete(
|
|
534
|
+
SensitivePath(
|
|
535
|
+
wire=f"/me/orderinvitations/{invitation_id}", log_form="/me/orderinvitations/***"
|
|
536
|
+
)
|
|
537
|
+
)
|
|
529
538
|
|
|
530
539
|
async def patch_group_order_invitation(
|
|
531
540
|
self,
|
|
@@ -541,7 +550,12 @@ class MeResource(BaseResource):
|
|
|
541
550
|
Returns:
|
|
542
551
|
The GroupOrderInvitation object.
|
|
543
552
|
"""
|
|
544
|
-
resp = await self._http.patch(
|
|
553
|
+
resp = await self._http.patch(
|
|
554
|
+
SensitivePath(
|
|
555
|
+
wire=f"/me/orderinvitations/{invitation_id}", log_form="/me/orderinvitations/***"
|
|
556
|
+
),
|
|
557
|
+
json=partial,
|
|
558
|
+
)
|
|
545
559
|
return GroupOrderInvitation(**resp.json())
|
|
546
560
|
|
|
547
561
|
async def list_orders(
|
|
@@ -877,7 +891,10 @@ class MeResource(BaseResource):
|
|
|
877
891
|
The ProductCollectionInvitation object.
|
|
878
892
|
"""
|
|
879
893
|
resp = await self._http.get(
|
|
880
|
-
|
|
894
|
+
SensitivePath(
|
|
895
|
+
wire=f"/me/productcollections/{product_collection_id}/invitations/{invitation_id}",
|
|
896
|
+
log_form=f"/me/productcollections/{product_collection_id}/invitations/***",
|
|
897
|
+
)
|
|
881
898
|
)
|
|
882
899
|
return ProductCollectionInvitation(**resp.json())
|
|
883
900
|
|
|
@@ -893,7 +910,10 @@ class MeResource(BaseResource):
|
|
|
893
910
|
invitation_id: ID of the invitation.
|
|
894
911
|
"""
|
|
895
912
|
await self._http.delete(
|
|
896
|
-
|
|
913
|
+
SensitivePath(
|
|
914
|
+
wire=f"/me/productcollections/{product_collection_id}/invitations/{invitation_id}",
|
|
915
|
+
log_form=f"/me/productcollections/{product_collection_id}/invitations/***",
|
|
916
|
+
)
|
|
897
917
|
)
|
|
898
918
|
|
|
899
919
|
async def patch_product_collection_invitation(
|
|
@@ -913,7 +933,10 @@ class MeResource(BaseResource):
|
|
|
913
933
|
The ProductCollectionInvitation object.
|
|
914
934
|
"""
|
|
915
935
|
resp = await self._http.patch(
|
|
916
|
-
|
|
936
|
+
SensitivePath(
|
|
937
|
+
wire=f"/me/productcollections/{product_collection_id}/invitations/{invitation_id}",
|
|
938
|
+
log_form=f"/me/productcollections/{product_collection_id}/invitations/***",
|
|
939
|
+
),
|
|
917
940
|
json=partial,
|
|
918
941
|
)
|
|
919
942
|
return ProductCollectionInvitation(**resp.json())
|
|
@@ -930,7 +953,10 @@ class MeResource(BaseResource):
|
|
|
930
953
|
invitation_id: ID of the invitation.
|
|
931
954
|
"""
|
|
932
955
|
await self._http.post(
|
|
933
|
-
|
|
956
|
+
SensitivePath(
|
|
957
|
+
wire=f"/me/productcollections/{product_collection_id}/invitations/accept/{invitation_id}",
|
|
958
|
+
log_form=f"/me/productcollections/{product_collection_id}/invitations/accept/***",
|
|
959
|
+
)
|
|
934
960
|
)
|
|
935
961
|
|
|
936
962
|
async def decline_product_collection_invitation(
|
|
@@ -945,7 +971,10 @@ class MeResource(BaseResource):
|
|
|
945
971
|
invitation_id: ID of the invitation.
|
|
946
972
|
"""
|
|
947
973
|
await self._http.post(
|
|
948
|
-
|
|
974
|
+
SensitivePath(
|
|
975
|
+
wire=f"/me/productcollections/{product_collection_id}/invitations/decline/{invitation_id}",
|
|
976
|
+
log_form=f"/me/productcollections/{product_collection_id}/invitations/decline/***",
|
|
977
|
+
)
|
|
949
978
|
)
|
|
950
979
|
|
|
951
980
|
async def list_product_collection_entries(
|
ordercloud/sync_client.py
CHANGED
|
@@ -5,14 +5,12 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import functools
|
|
7
7
|
from collections.abc import Iterator
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any
|
|
9
9
|
|
|
10
10
|
from .config import OrderCloudConfig
|
|
11
11
|
from .middleware import AfterResponse, BeforeRequest
|
|
12
12
|
from .resources.base import BaseResource
|
|
13
13
|
|
|
14
|
-
T = TypeVar("T")
|
|
15
|
-
|
|
16
14
|
__all__ = ["SyncOrderCloudClient", "paginate_sync"]
|
|
17
15
|
|
|
18
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: ordercloud-python
|
|
3
|
-
Version: 2026.
|
|
3
|
+
Version: 2026.10.2
|
|
4
4
|
Summary: Idiomatic Python SDK for Sitecore OrderCloud
|
|
5
5
|
Project-URL: Homepage, https://github.com/markcassidyconsulting/ordercloud-python
|
|
6
6
|
Project-URL: Repository, https://github.com/markcassidyconsulting/ordercloud-python
|
|
@@ -60,7 +60,7 @@ A fully typed, async-first Python SDK for [Sitecore OrderCloud](https://orderclo
|
|
|
60
60
|
- **Middleware hooks** — intercept requests and responses for logging, metrics, or header injection.
|
|
61
61
|
- **Structured logging** — standard Python `logging` module, DEBUG/WARNING levels.
|
|
62
62
|
- **Full type annotations** — `py.typed` marker for downstream type checking with mypy, pyright, etc.
|
|
63
|
-
- **
|
|
63
|
+
- **803 tests, 97% coverage** — 778 unit tests (mocked HTTP) + 25 integration tests (live sandbox).
|
|
64
64
|
|
|
65
65
|
## Installation
|
|
66
66
|
|
|
@@ -212,9 +212,8 @@ The SDK logs via Python's standard `logging` module under the `ordercloud` logge
|
|
|
212
212
|
|
|
213
213
|
```python
|
|
214
214
|
import logging
|
|
215
|
-
logging.basicConfig(level=logging.DEBUG)
|
|
216
215
|
|
|
217
|
-
#
|
|
216
|
+
logging.basicConfig() # adds a handler; the root logger stays at WARNING
|
|
218
217
|
logging.getLogger("ordercloud").setLevel(logging.DEBUG)
|
|
219
218
|
```
|
|
220
219
|
|
|
@@ -223,6 +222,10 @@ logging.getLogger("ordercloud").setLevel(logging.DEBUG)
|
|
|
223
222
|
| `DEBUG` | Every request (`Request: GET /products`) and response (`Response: GET /products 200`) |
|
|
224
223
|
| `WARNING` | Retry attempts with status code and backoff delay |
|
|
225
224
|
|
|
225
|
+
Path values that work as credentials, a password-reset verification code and an invitation ID, are logged as `***`, for example `Request: PUT /password/reset/***`. Query parameters and request bodies are not logged.
|
|
226
|
+
|
|
227
|
+
Setting the root logger to `INFO` or `DEBUG` (for example `logging.basicConfig(level=logging.DEBUG)`) also turns on `httpx`'s own `HTTP Request:` line, which contains the full URL, unredacted, including the query string. The SDK does not control that logger. To keep it quiet, add `logging.getLogger("httpx").setLevel(logging.WARNING)`. See [SECURITY.md](SECURITY.md#logging).
|
|
228
|
+
|
|
226
229
|
## Middleware Hooks
|
|
227
230
|
|
|
228
231
|
Register hooks to intercept requests and responses:
|
|
@@ -242,9 +245,11 @@ client.add_after_response(log_timing)
|
|
|
242
245
|
|
|
243
246
|
Before-request hooks receive a mutable `RequestContext` — modify `headers`, `params`, or `json` before the request is sent. After-response hooks receive a `ResponseContext` with the request details and response. Hooks are called on every attempt, including retries.
|
|
244
247
|
|
|
248
|
+
Hooks see the concrete values sent to the API, including credential-bearing path values and the `Authorization` header; the redaction above applies only to the SDK's own log lines.
|
|
249
|
+
|
|
245
250
|
## API Coverage
|
|
246
251
|
|
|
247
|
-
The SDK covers **all 60 resources** and **639 operations** in the OrderCloud API. Models and resource clients are generated from the official OpenAPI v3 spec (version 1.0.
|
|
252
|
+
The SDK covers **all 60 resources** and **639 operations** in the OrderCloud API. Models and resource clients are generated from the official OpenAPI v3 spec (version 1.0.470).
|
|
248
253
|
|
|
249
254
|
### Core Commerce
|
|
250
255
|
|
|
@@ -504,18 +509,18 @@ The test suite is self-bootstrapping — it uses the SDK itself to create all te
|
|
|
504
509
|
|
|
505
510
|
### Test Suite
|
|
506
511
|
|
|
507
|
-
|
|
512
|
+
803 tests across 12 modules.
|
|
508
513
|
|
|
509
|
-
**Unit tests (
|
|
514
|
+
**Unit tests (778)** — mocked HTTP via [respx](https://lundberg.github.io/respx/), no network calls:
|
|
510
515
|
|
|
511
516
|
| Module | Tests | Purpose |
|
|
512
517
|
|--------|-------|---------|
|
|
513
518
|
| `test_auth.py` | 13 | OAuth2 token management |
|
|
514
|
-
| `test_http.py` |
|
|
515
|
-
| `test_models.py` |
|
|
516
|
-
| `test_resources.py` |
|
|
519
|
+
| `test_http.py` | 37 | HTTP client, error parsing, retries |
|
|
520
|
+
| `test_models.py` | 44 | Model round-trips, enums, xp, ListPage |
|
|
521
|
+
| `test_resources.py` | 33 | Representative resource operations |
|
|
517
522
|
| `test_resource_coverage.py` | 639 | All 60 resources, all 639 operations |
|
|
518
|
-
| `test_sync_client.py` |
|
|
523
|
+
| `test_sync_client.py` | 12 | Sync wrapper, pagination |
|
|
519
524
|
|
|
520
525
|
**Integration tests (25)** — live sandbox, skipped when credentials are absent:
|
|
521
526
|
|
|
@@ -532,11 +537,11 @@ The test suite is self-bootstrapping — it uses the SDK itself to create all te
|
|
|
532
537
|
|
|
533
538
|
| Module | Coverage |
|
|
534
539
|
|--------|----------|
|
|
535
|
-
| `auth.py` |
|
|
540
|
+
| `auth.py` | 93% |
|
|
536
541
|
| `client.py` | 100% |
|
|
537
|
-
| `config.py` |
|
|
542
|
+
| `config.py` | 94% |
|
|
538
543
|
| `errors.py` | 100% |
|
|
539
|
-
| `http.py` |
|
|
544
|
+
| `http.py` | 98% |
|
|
540
545
|
| `middleware.py` | 100% |
|
|
541
546
|
| `sync_client.py` | 100% |
|
|
542
547
|
| `resources/base.py` | 100% |
|
|
@@ -3,13 +3,13 @@ ordercloud/auth.py,sha256=GHMnOmT8EFu9pEHOtYokBw2eG1_Vqqq2LPe4cTplg-k,4977
|
|
|
3
3
|
ordercloud/client.py,sha256=QH_kDr9mlM9PVu74-29oZ4Whe-zQLoMdnJWRQXET22E,10250
|
|
4
4
|
ordercloud/config.py,sha256=IFJ_1LgK6gxq_W3GXeGgBRh2fkALHbvK0QIVGufDXQU,1460
|
|
5
5
|
ordercloud/errors.py,sha256=7AGWlqQIy5apGuseyYrDLqlkr5YxE8WgV3igQztm2VI,1291
|
|
6
|
-
ordercloud/http.py,sha256=
|
|
7
|
-
ordercloud/middleware.py,sha256=
|
|
6
|
+
ordercloud/http.py,sha256=gh4my4qpflzfwy6tVqChT_37hrzqMul0dJ72gumutp0,8605
|
|
7
|
+
ordercloud/middleware.py,sha256=hb-bzZynMAfqNw6-zd4xY57QVNyHOLzrva97Bp9BhRk,2233
|
|
8
8
|
ordercloud/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
ordercloud/sync_client.py,sha256=
|
|
9
|
+
ordercloud/sync_client.py,sha256=FvFjbzPPGDgzzf-Uu1iRFklEY2pzPbCH1_MkZRAlnpY,5492
|
|
10
10
|
ordercloud/models/__init__.py,sha256=b4xZvxsgCztIX6WfjbhaOR4Tezy9PdF_hKyoEQF5-48,5749
|
|
11
11
|
ordercloud/models/address.py,sha256=NDIJNDE_aPlCGtTypY2GfNn-xG4E4_GOgTst2oMAffM,1436
|
|
12
|
-
ordercloud/models/api_client.py,sha256=
|
|
12
|
+
ordercloud/models/api_client.py,sha256=i33IsC_02844InvkS-SHdxy6GdEk_0HEx-Ug2Gfc7Y0,5588
|
|
13
13
|
ordercloud/models/approval.py,sha256=kxD-Qgg5y-dYTi0mKQ656ou0Iszddhg1ZD-lH2KGWBE,2481
|
|
14
14
|
ordercloud/models/assignments.py,sha256=0Zk_nz8_4DSOeeKF5adeWLU4sozJ4MrYlTDfMbuoaLE,12446
|
|
15
15
|
ordercloud/models/auth_models.py,sha256=-LuKaUG0SjVXiS5XwbjyBL4bAWT6hXuW2F2JRjGs7uc,2794
|
|
@@ -20,13 +20,13 @@ ordercloud/models/category.py,sha256=lYQR5hOplel8I1nh5siceTVe95y5Vcggaq5Iq5g33Go
|
|
|
20
20
|
ordercloud/models/cost_center.py,sha256=gtbYeT2qLu97x1QhSheYg8UERh2b1y5X8m4fVi8cEeY,666
|
|
21
21
|
ordercloud/models/credit_card.py,sha256=tMKifoKBsuDqEtg5yyhuMCYMsy_iq7DSUAu1FUwgcA8,1170
|
|
22
22
|
ordercloud/models/delivery.py,sha256=tHnw9V0QEVgtpHKESrxsW64DMroB_qR3jKnFveRG79E,8041
|
|
23
|
-
ordercloud/models/discount.py,sha256=
|
|
23
|
+
ordercloud/models/discount.py,sha256=MjRN_dpqRBWPI0wtk_VGleaspTij_riNTETTyOdf6KE,2571
|
|
24
24
|
ordercloud/models/integration.py,sha256=TeTcUmrS49xJvP2JkNA8d1gbUEpMGIlIi6xqfV9DhG0,2806
|
|
25
25
|
ordercloud/models/inventory_record.py,sha256=zq0-sKk4Vox6Kp5lHKM1I4jayj7y9ibFy2LcYSlLZd8,2262
|
|
26
26
|
ordercloud/models/line_item.py,sha256=lep7bKF-5fgqoaoSR0Ck8E9bkSX8bcSiahfilSs5NFk,5045
|
|
27
27
|
ordercloud/models/line_item_types.py,sha256=xlFO3Xxt2wnD_OzIVGRNqsazvRTVsJ52M8kGLXJroBs,2913
|
|
28
28
|
ordercloud/models/message_sender.py,sha256=t-E8dJC00_4mgKMgx2eMcsF6ysnHTIirXwy860IlTzo,3181
|
|
29
|
-
ordercloud/models/misc.py,sha256=
|
|
29
|
+
ordercloud/models/misc.py,sha256=eLyzrTALWrsMR3lwK9iQ9xedZd6atr8tTrjIJ0NGapw,9292
|
|
30
30
|
ordercloud/models/open_id_connect.py,sha256=G_IAYeouhgg4tEbt3mv9X6byPDHooYpcAW9c6GwH99U,3212
|
|
31
31
|
ordercloud/models/order.py,sha256=XIdfjvHZ4pit3_wigDHQLdqOU4IJGvzf84L2VUSiNCE,23879
|
|
32
32
|
ordercloud/models/order_return.py,sha256=XWXolExpfrnnnGQXNHO3b-NbTGei4CBZWp8Kr05LOk0,3714
|
|
@@ -68,8 +68,8 @@ ordercloud/resources/delivery_configurations.py,sha256=AKhH-MAPR5KBCCNxPB3UktGxN
|
|
|
68
68
|
ordercloud/resources/discounts.py,sha256=TWZPSic_r3sZ4g5CrXUQGHzy5o1MemUFakndDieCy-A,6551
|
|
69
69
|
ordercloud/resources/entity_syncs.py,sha256=hU0LCckBKVQKfexDiptdtCiVw2GP_7qxjX6XoKWH_jk,19543
|
|
70
70
|
ordercloud/resources/error_configs.py,sha256=wNM0gQOLexgMBb9wHP39TzPSNd6WAViUE76xKHCTVtc,1872
|
|
71
|
-
ordercloud/resources/forgotten_credentials.py,sha256=
|
|
72
|
-
ordercloud/resources/group_orders.py,sha256=
|
|
71
|
+
ordercloud/resources/forgotten_credentials.py,sha256=Dkl1RSeBrDDpr7_aBwqjeD8s-B5NndlZkQK2k3HZgQQ,2580
|
|
72
|
+
ordercloud/resources/group_orders.py,sha256=OeEZEMCYre-DwBxZ2se2ynjAoVZpuLKR6PF39g-xWn8,881
|
|
73
73
|
ordercloud/resources/impersonation_configs.py,sha256=5BZuM2YZH5fa5htL2m8Nc03NM7v7G3mTquT4ehJNrLE,4524
|
|
74
74
|
ordercloud/resources/incrementors.py,sha256=K3HFvVM1bb4ysAMXP8uaG642roRoLR9h6aQiiXHMdEw,3992
|
|
75
75
|
ordercloud/resources/integration_events.py,sha256=XjuSC_TLszYmu0Q6cve1J8e3VU905s3Y2LPAm3pTMaw,6698
|
|
@@ -77,7 +77,7 @@ ordercloud/resources/inventory_integrations.py,sha256=CGcv8kldBh8mZpDzHwKJGKkJuI
|
|
|
77
77
|
ordercloud/resources/inventory_records.py,sha256=RJz98GOEDa6eVOZb66zmmInNTwYsmQ32qOIpf_QSvas,17312
|
|
78
78
|
ordercloud/resources/line_items.py,sha256=OdzdHK2Reb40KfSWeLTYnf1xblcW4JQdDibSv2zP7Qg,9162
|
|
79
79
|
ordercloud/resources/locales.py,sha256=kIiTO7SE1M-9Z5KPbr-CgME0OwUdvPyUKs_WmC8c8vs,6395
|
|
80
|
-
ordercloud/resources/me.py,sha256=
|
|
80
|
+
ordercloud/resources/me.py,sha256=cs_PT3QFdtMFokTF8gcEehXUtpjkmlLSRN_x9PYZ0Qg,69774
|
|
81
81
|
ordercloud/resources/message_senders.py,sha256=oGqP5pCe_vXOwTqJKqRd5rAzEXZSswRtN5PGOZbGTxE,9567
|
|
82
82
|
ordercloud/resources/open_id_connects.py,sha256=EZx531s-1rT3QntH5skgUMz2vTMA8ny3SfW78UII7Ys,4301
|
|
83
83
|
ordercloud/resources/order_returns.py,sha256=hGdnuDivv8GpoY2sO9kkHcf6nTfwGvHMs1BGoHlN0BY,9805
|
|
@@ -108,7 +108,7 @@ ordercloud/resources/user_groups.py,sha256=VDbVFtce48Ar0uSlzNfFVRm-TM5j4youNzMnr
|
|
|
108
108
|
ordercloud/resources/users.py,sha256=viaOyufvl_iKQ9Txi9MZQyKcyooSsqi9IrQeWy_TQW0,7898
|
|
109
109
|
ordercloud/resources/webhooks.py,sha256=phzdD-oLerrHz1OeHoiBnAS_4dxBsiwXknjXkUoqL9E,3710
|
|
110
110
|
ordercloud/resources/xp_indices.py,sha256=h74UvkuR0HJahX_v5E6moUth1VwAo4cvnFvyhDrHAEg,2816
|
|
111
|
-
ordercloud_python-2026.
|
|
112
|
-
ordercloud_python-2026.
|
|
113
|
-
ordercloud_python-2026.
|
|
114
|
-
ordercloud_python-2026.
|
|
111
|
+
ordercloud_python-2026.10.2.dist-info/METADATA,sha256=znDw6Y2RBeAMeyDr1Rd3lim4Z9EhQYSwb-wL6UyzZx0,20389
|
|
112
|
+
ordercloud_python-2026.10.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
113
|
+
ordercloud_python-2026.10.2.dist-info/licenses/LICENSE,sha256=x9gaXR_jrxgfZU27dgI3kUXDp9twvsZh-ZIzazdv3sk,1080
|
|
114
|
+
ordercloud_python-2026.10.2.dist-info/RECORD,,
|
{ordercloud_python-2026.6.1.dist-info → ordercloud_python-2026.10.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|