shipthisapi-python 3.0.4__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.4 → shipthisapi_python-3.1.0}/PKG-INFO +1 -1
- {shipthisapi_python-3.0.4 → shipthisapi_python-3.1.0}/ShipthisAPI/__init__.py +1 -1
- {shipthisapi_python-3.0.4 → shipthisapi_python-3.1.0}/ShipthisAPI/shipthisapi.py +135 -41
- {shipthisapi_python-3.0.4 → shipthisapi_python-3.1.0}/setup.py +1 -1
- {shipthisapi_python-3.0.4 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/PKG-INFO +1 -1
- {shipthisapi_python-3.0.4 → shipthisapi_python-3.1.0}/README.md +0 -0
- {shipthisapi_python-3.0.4 → shipthisapi_python-3.1.0}/setup.cfg +0 -0
- {shipthisapi_python-3.0.4 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/SOURCES.txt +0 -0
- {shipthisapi_python-3.0.4 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/dependency_links.txt +0 -0
- {shipthisapi_python-3.0.4 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/requires.txt +0 -0
- {shipthisapi_python-3.0.4 → 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 ====================
|
|
@@ -321,14 +339,18 @@ class ShipthisAPI:
|
|
|
321
339
|
params = {}
|
|
322
340
|
if only_fields:
|
|
323
341
|
params["only"] = only_fields
|
|
324
|
-
return await self._make_request(
|
|
342
|
+
return await self._make_request(
|
|
343
|
+
"GET", path, query_params=params if params else None
|
|
344
|
+
)
|
|
325
345
|
else:
|
|
326
346
|
params = {}
|
|
327
347
|
if filters:
|
|
328
348
|
params["query_filter_v2"] = json.dumps(filters)
|
|
329
349
|
if only_fields:
|
|
330
350
|
params["only"] = only_fields
|
|
331
|
-
resp = await self._make_request(
|
|
351
|
+
resp = await self._make_request(
|
|
352
|
+
"GET", f"incollection/{collection_name}", params
|
|
353
|
+
)
|
|
332
354
|
if isinstance(resp, dict) and resp.get("items"):
|
|
333
355
|
return resp.get("items")[0]
|
|
334
356
|
return None
|
|
@@ -378,7 +400,9 @@ class ShipthisAPI:
|
|
|
378
400
|
if not meta:
|
|
379
401
|
params["meta"] = "false"
|
|
380
402
|
|
|
381
|
-
response = await self._make_request(
|
|
403
|
+
response = await self._make_request(
|
|
404
|
+
"GET", f"incollection/{collection_name}", params
|
|
405
|
+
)
|
|
382
406
|
|
|
383
407
|
if isinstance(response, dict):
|
|
384
408
|
return response.get("items", [])
|
|
@@ -416,13 +440,25 @@ class ShipthisAPI:
|
|
|
416
440
|
)
|
|
417
441
|
|
|
418
442
|
async def create_item(
|
|
419
|
-
self,
|
|
443
|
+
self,
|
|
444
|
+
collection_name: str,
|
|
445
|
+
data: Dict[str, Any],
|
|
446
|
+
ignore_new_required: bool = False,
|
|
447
|
+
skip_sequence_if_exists: bool = False,
|
|
448
|
+
replicate_count: int = 0,
|
|
449
|
+
input_filters: Optional[Dict[str, Any]] = None,
|
|
450
|
+
action_op_data: Optional[Dict[str, Any]] = None,
|
|
420
451
|
) -> Dict[str, Any]:
|
|
421
|
-
"""Create a new item in a collection.
|
|
452
|
+
"""Create a new item in a collection with all advanced Shipthis features.
|
|
422
453
|
|
|
423
454
|
Args:
|
|
424
455
|
collection_name: Name of the collection.
|
|
425
456
|
data: Document data.
|
|
457
|
+
ignore_new_required: Ignore new required fields (default: False).
|
|
458
|
+
skip_sequence_if_exists: Skip sequence if exists (default: False).
|
|
459
|
+
replicate_count: Number of times to replicate the item (default: 0).
|
|
460
|
+
input_filters: Input filters (optional).
|
|
461
|
+
action_op_data: Action operation data (optional).
|
|
426
462
|
|
|
427
463
|
Returns:
|
|
428
464
|
Created document data.
|
|
@@ -430,10 +466,26 @@ class ShipthisAPI:
|
|
|
430
466
|
Raises:
|
|
431
467
|
ShipthisAPIError: If the request fails.
|
|
432
468
|
"""
|
|
469
|
+
params = {}
|
|
470
|
+
if replicate_count > 0:
|
|
471
|
+
params["replicate_count"] = min(replicate_count, 100)
|
|
472
|
+
if input_filters:
|
|
473
|
+
params["input_filters"] = json.dumps(input_filters)
|
|
474
|
+
|
|
475
|
+
request_payload = {
|
|
476
|
+
"reqbody": data,
|
|
477
|
+
"ignore_new_required": ignore_new_required,
|
|
478
|
+
"skip_sequence_if_exists": skip_sequence_if_exists,
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if action_op_data:
|
|
482
|
+
request_payload["action_op_data"] = action_op_data
|
|
483
|
+
|
|
433
484
|
resp = await self._make_request(
|
|
434
485
|
"POST",
|
|
435
486
|
f"incollection/{collection_name}",
|
|
436
|
-
|
|
487
|
+
query_params=params,
|
|
488
|
+
request_data=request_payload,
|
|
437
489
|
)
|
|
438
490
|
if isinstance(resp, dict) and resp.get("data"):
|
|
439
491
|
return resp.get("data")
|
|
@@ -471,9 +523,10 @@ class ShipthisAPI:
|
|
|
471
523
|
self,
|
|
472
524
|
collection_name: str,
|
|
473
525
|
object_id: str,
|
|
474
|
-
update_fields: Dict[str, Any],
|
|
526
|
+
update_fields: Dict[str, Any] = None,
|
|
527
|
+
workflow: List[Dict[str, Any]] = None,
|
|
475
528
|
) -> Dict[str, Any]:
|
|
476
|
-
"""Patch specific fields of an item.
|
|
529
|
+
"""Patch specific fields of an item and/or trigger workflow transitions.
|
|
477
530
|
|
|
478
531
|
This is the recommended way to update document fields. It goes through
|
|
479
532
|
full field validation, workflow triggers, audit logging, and business logic.
|
|
@@ -482,24 +535,57 @@ class ShipthisAPI:
|
|
|
482
535
|
collection_name: Name of the collection (e.g., "sea_shipment", "fcl_load").
|
|
483
536
|
object_id: Document ID.
|
|
484
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.
|
|
485
543
|
|
|
486
544
|
Returns:
|
|
487
|
-
Updated document data.
|
|
545
|
+
Updated document data and/or workflow results.
|
|
488
546
|
|
|
489
547
|
Raises:
|
|
490
548
|
ShipthisAPIError: If the request fails.
|
|
491
549
|
|
|
492
550
|
Example:
|
|
551
|
+
# Update fields only
|
|
493
552
|
await client.patch_item(
|
|
494
553
|
"fcl_load",
|
|
495
554
|
"68a4f906743189ad061429a7",
|
|
496
555
|
update_fields={"container_no": "CONT123", "seal_no": "SEAL456"}
|
|
497
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
|
+
)
|
|
498
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
|
|
499
585
|
return await self._make_request(
|
|
500
586
|
"PATCH",
|
|
501
587
|
f"incollection/{collection_name}/{object_id}",
|
|
502
|
-
request_data=
|
|
588
|
+
request_data=request_data,
|
|
503
589
|
)
|
|
504
590
|
|
|
505
591
|
async def delete_item(self, collection_name: str, object_id: str) -> Dict[str, Any]:
|
|
@@ -646,12 +732,18 @@ class ShipthisAPI:
|
|
|
646
732
|
ShipthisAPIError: If the request fails.
|
|
647
733
|
"""
|
|
648
734
|
import time
|
|
735
|
+
|
|
649
736
|
if date is None:
|
|
650
737
|
date = int(time.time() * 1000)
|
|
651
738
|
|
|
652
739
|
return await self._make_request(
|
|
653
740
|
"GET",
|
|
654
|
-
|
|
741
|
+
"thirdparty/currency",
|
|
742
|
+
query_params={
|
|
743
|
+
"source": source_currency,
|
|
744
|
+
"target": target_currency,
|
|
745
|
+
"date": date,
|
|
746
|
+
},
|
|
655
747
|
)
|
|
656
748
|
|
|
657
749
|
async def autocomplete(
|
|
@@ -696,7 +788,8 @@ class ShipthisAPI:
|
|
|
696
788
|
"""
|
|
697
789
|
return await self._make_request(
|
|
698
790
|
"GET",
|
|
699
|
-
|
|
791
|
+
"thirdparty/search-place-autocomplete",
|
|
792
|
+
query_params={"query": query},
|
|
700
793
|
)
|
|
701
794
|
|
|
702
795
|
async def get_place_details(
|
|
@@ -718,7 +811,8 @@ class ShipthisAPI:
|
|
|
718
811
|
"""
|
|
719
812
|
return await self._make_request(
|
|
720
813
|
"GET",
|
|
721
|
-
|
|
814
|
+
"thirdparty/select-google-place",
|
|
815
|
+
query_params={"query": place_id, "description": description},
|
|
722
816
|
)
|
|
723
817
|
|
|
724
818
|
# ==================== Conversations ====================
|
|
@@ -952,8 +1046,8 @@ class ShipthisAPI:
|
|
|
952
1046
|
try:
|
|
953
1047
|
with open(file_path, "rb") as f:
|
|
954
1048
|
files = {"file": (file_name, f)}
|
|
955
|
-
async with httpx.AsyncClient(timeout=self.timeout * 2) as
|
|
956
|
-
response = await
|
|
1049
|
+
async with httpx.AsyncClient(timeout=self.timeout * 2) as upload_client:
|
|
1050
|
+
response = await upload_client.post(
|
|
957
1051
|
upload_url,
|
|
958
1052
|
headers=headers,
|
|
959
1053
|
files=files,
|
|
File without changes
|
|
File without changes
|
{shipthisapi_python-3.0.4 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{shipthisapi_python-3.0.4 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{shipthisapi_python-3.0.4 → shipthisapi_python-3.1.0}/shipthisapi_python.egg-info/top_level.txt
RENAMED
|
File without changes
|