shipthisapi-python 3.0.5__tar.gz → 3.1.0__tar.gz
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.
- {shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/PKG-INFO +1 -1
- {shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/ShipthisAPI/shipthisapi.py +94 -35
- {shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/setup.py +1 -1
- {shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/PKG-INFO +1 -1
- {shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/README.md +0 -0
- {shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/ShipthisAPI/__init__.py +0 -0
- {shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/setup.cfg +0 -0
- {shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/SOURCES.txt +0 -0
- {shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/dependency_links.txt +0 -0
- {shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/requires.txt +0 -0
- {shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/top_level.txt +0 -0
|
@@ -107,6 +107,7 @@ class ShipthisAPI:
|
|
|
107
107
|
self.custom_headers = custom_headers or {}
|
|
108
108
|
self.organisation_info = None
|
|
109
109
|
self.is_connected = False
|
|
110
|
+
self._client: httpx.AsyncClient = None
|
|
110
111
|
|
|
111
112
|
def set_region_location(self, region_id: str, location_id: str) -> None:
|
|
112
113
|
"""Set the region and location for subsequent requests.
|
|
@@ -146,6 +147,19 @@ class ShipthisAPI:
|
|
|
146
147
|
headers.update(override_headers)
|
|
147
148
|
return headers
|
|
148
149
|
|
|
150
|
+
async def _ensure_client(self) -> httpx.AsyncClient:
|
|
151
|
+
"""Get or create the shared HTTP client."""
|
|
152
|
+
if self._client is None:
|
|
153
|
+
self._client = httpx.AsyncClient(timeout=self.timeout)
|
|
154
|
+
return self._client
|
|
155
|
+
|
|
156
|
+
async def __aenter__(self):
|
|
157
|
+
await self.connect()
|
|
158
|
+
return self
|
|
159
|
+
|
|
160
|
+
async def __aexit__(self, *args):
|
|
161
|
+
await self.disconnect()
|
|
162
|
+
|
|
149
163
|
async def _make_request(
|
|
150
164
|
self,
|
|
151
165
|
method: str,
|
|
@@ -172,31 +186,31 @@ class ShipthisAPI:
|
|
|
172
186
|
"""
|
|
173
187
|
url = self.base_api_endpoint + path
|
|
174
188
|
request_headers = self._get_headers(headers)
|
|
189
|
+
client = await self._ensure_client()
|
|
175
190
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
)
|
|
191
|
+
try:
|
|
192
|
+
response = await client.request(
|
|
193
|
+
method,
|
|
194
|
+
url,
|
|
195
|
+
headers=request_headers,
|
|
196
|
+
params=query_params,
|
|
197
|
+
json=request_data,
|
|
198
|
+
)
|
|
199
|
+
except httpx.TimeoutException:
|
|
200
|
+
raise ShipthisRequestError(
|
|
201
|
+
message="Request timed out",
|
|
202
|
+
status_code=408,
|
|
203
|
+
)
|
|
204
|
+
except httpx.ConnectError as e:
|
|
205
|
+
raise ShipthisRequestError(
|
|
206
|
+
message=f"Connection error: {str(e)}",
|
|
207
|
+
status_code=0,
|
|
208
|
+
)
|
|
209
|
+
except httpx.RequestError as e:
|
|
210
|
+
raise ShipthisRequestError(
|
|
211
|
+
message=f"Request failed: {str(e)}",
|
|
212
|
+
status_code=0,
|
|
213
|
+
)
|
|
200
214
|
|
|
201
215
|
# Handle authentication errors
|
|
202
216
|
if response.status_code == 401:
|
|
@@ -275,9 +289,13 @@ class ShipthisAPI:
|
|
|
275
289
|
"organisation": self.organisation_info,
|
|
276
290
|
}
|
|
277
291
|
|
|
278
|
-
def disconnect(self) -> None:
|
|
279
|
-
"""Disconnect and clear credentials."""
|
|
292
|
+
async def disconnect(self) -> None:
|
|
293
|
+
"""Disconnect, close the HTTP client, and clear credentials."""
|
|
294
|
+
if self._client is not None:
|
|
295
|
+
await self._client.aclose()
|
|
296
|
+
self._client = None
|
|
280
297
|
self.x_api_key = None
|
|
298
|
+
self.organisation_info = None
|
|
281
299
|
self.is_connected = False
|
|
282
300
|
|
|
283
301
|
# ==================== Info ====================
|
|
@@ -505,9 +523,10 @@ class ShipthisAPI:
|
|
|
505
523
|
self,
|
|
506
524
|
collection_name: str,
|
|
507
525
|
object_id: str,
|
|
508
|
-
update_fields: Dict[str, Any],
|
|
526
|
+
update_fields: Dict[str, Any] = None,
|
|
527
|
+
workflow: List[Dict[str, Any]] = None,
|
|
509
528
|
) -> Dict[str, Any]:
|
|
510
|
-
"""Patch specific fields of an item.
|
|
529
|
+
"""Patch specific fields of an item and/or trigger workflow transitions.
|
|
511
530
|
|
|
512
531
|
This is the recommended way to update document fields. It goes through
|
|
513
532
|
full field validation, workflow triggers, audit logging, and business logic.
|
|
@@ -516,24 +535,57 @@ class ShipthisAPI:
|
|
|
516
535
|
collection_name: Name of the collection (e.g., "sea_shipment", "fcl_load").
|
|
517
536
|
object_id: Document ID.
|
|
518
537
|
update_fields: Dictionary of field_id to value mappings.
|
|
538
|
+
workflow: List of workflow actions to execute. Each action is a dict with:
|
|
539
|
+
- workflow_id (str): The workflow identifier (e.g., "status").
|
|
540
|
+
- value (str, optional): Target state for direct-mode workflows.
|
|
541
|
+
- action_id (str, optional): Action ID for action-based workflows.
|
|
542
|
+
- payload (any, optional): Extra data for the action.
|
|
519
543
|
|
|
520
544
|
Returns:
|
|
521
|
-
Updated document data.
|
|
545
|
+
Updated document data and/or workflow results.
|
|
522
546
|
|
|
523
547
|
Raises:
|
|
524
548
|
ShipthisAPIError: If the request fails.
|
|
525
549
|
|
|
526
550
|
Example:
|
|
551
|
+
# Update fields only
|
|
527
552
|
await client.patch_item(
|
|
528
553
|
"fcl_load",
|
|
529
554
|
"68a4f906743189ad061429a7",
|
|
530
555
|
update_fields={"container_no": "CONT123", "seal_no": "SEAL456"}
|
|
531
556
|
)
|
|
557
|
+
|
|
558
|
+
# Workflow transition only (direct mode)
|
|
559
|
+
await client.patch_item(
|
|
560
|
+
"sea_shipment",
|
|
561
|
+
"68a4f906743189ad061429a7",
|
|
562
|
+
workflow=[{"workflow_id": "status", "value": "approved"}]
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
# Workflow transition only (action-based)
|
|
566
|
+
await client.patch_item(
|
|
567
|
+
"sea_shipment",
|
|
568
|
+
"68a4f906743189ad061429a7",
|
|
569
|
+
workflow=[{"workflow_id": "status", "action_id": "submit_review"}]
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
# Fields + workflow in one call
|
|
573
|
+
await client.patch_item(
|
|
574
|
+
"sea_shipment",
|
|
575
|
+
"68a4f906743189ad061429a7",
|
|
576
|
+
update_fields={"notes": "ready"},
|
|
577
|
+
workflow=[{"workflow_id": "status", "action_id": "submit_review"}]
|
|
578
|
+
)
|
|
532
579
|
"""
|
|
580
|
+
request_data = {}
|
|
581
|
+
if update_fields is not None:
|
|
582
|
+
request_data["update_fields"] = update_fields
|
|
583
|
+
if workflow is not None:
|
|
584
|
+
request_data["workflow"] = workflow
|
|
533
585
|
return await self._make_request(
|
|
534
586
|
"PATCH",
|
|
535
587
|
f"incollection/{collection_name}/{object_id}",
|
|
536
|
-
request_data=
|
|
588
|
+
request_data=request_data,
|
|
537
589
|
)
|
|
538
590
|
|
|
539
591
|
async def delete_item(self, collection_name: str, object_id: str) -> Dict[str, Any]:
|
|
@@ -686,7 +738,12 @@ class ShipthisAPI:
|
|
|
686
738
|
|
|
687
739
|
return await self._make_request(
|
|
688
740
|
"GET",
|
|
689
|
-
|
|
741
|
+
"thirdparty/currency",
|
|
742
|
+
query_params={
|
|
743
|
+
"source": source_currency,
|
|
744
|
+
"target": target_currency,
|
|
745
|
+
"date": date,
|
|
746
|
+
},
|
|
690
747
|
)
|
|
691
748
|
|
|
692
749
|
async def autocomplete(
|
|
@@ -731,7 +788,8 @@ class ShipthisAPI:
|
|
|
731
788
|
"""
|
|
732
789
|
return await self._make_request(
|
|
733
790
|
"GET",
|
|
734
|
-
|
|
791
|
+
"thirdparty/search-place-autocomplete",
|
|
792
|
+
query_params={"query": query},
|
|
735
793
|
)
|
|
736
794
|
|
|
737
795
|
async def get_place_details(
|
|
@@ -753,7 +811,8 @@ class ShipthisAPI:
|
|
|
753
811
|
"""
|
|
754
812
|
return await self._make_request(
|
|
755
813
|
"GET",
|
|
756
|
-
|
|
814
|
+
"thirdparty/select-google-place",
|
|
815
|
+
query_params={"query": place_id, "description": description},
|
|
757
816
|
)
|
|
758
817
|
|
|
759
818
|
# ==================== Conversations ====================
|
|
@@ -987,8 +1046,8 @@ class ShipthisAPI:
|
|
|
987
1046
|
try:
|
|
988
1047
|
with open(file_path, "rb") as f:
|
|
989
1048
|
files = {"file": (file_name, f)}
|
|
990
|
-
async with httpx.AsyncClient(timeout=self.timeout * 2) as
|
|
991
|
-
response = await
|
|
1049
|
+
async with httpx.AsyncClient(timeout=self.timeout * 2) as upload_client:
|
|
1050
|
+
response = await upload_client.post(
|
|
992
1051
|
upload_url,
|
|
993
1052
|
headers=headers,
|
|
994
1053
|
files=files,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{shipthisapi_python-3.0.5 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/top_level.txt
RENAMED
|
File without changes
|