shipthisapi-python 2.1.0__tar.gz → 3.0.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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shipthisapi-python
3
- Version: 2.1.0
4
- Summary: ShipthisAPI utility package
3
+ Version: 3.0.0
4
+ Summary: ShipthisAPI async utility package
5
5
  Home-page: https://github.com/shipthisco/shipthisapi-python
6
6
  Author: Mayur Rawte
7
7
  Author-email: mayur@shipthis.co
@@ -9,12 +9,14 @@ Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Description-Content-Type: text/markdown
12
+ Requires-Dist: httpx>=0.24.0
12
13
  Dynamic: author
13
14
  Dynamic: author-email
14
15
  Dynamic: classifier
15
16
  Dynamic: description
16
17
  Dynamic: description-content-type
17
18
  Dynamic: home-page
19
+ Dynamic: requires-dist
18
20
  Dynamic: summary
19
21
 
20
22
  # ShipthisAPI Python
@@ -1,5 +1,5 @@
1
1
  # __variables__ with double-quoted values will be available in setup.py
2
- __version__ = "2.1.0"
2
+ __version__ = "3.0.0"
3
3
 
4
4
  from .shipthisapi import (
5
5
  ShipthisAPI,
@@ -1,30 +1,35 @@
1
1
  """Shipthis API Client.
2
2
 
3
- A Python client for the Shipthis public API.
3
+ An async Python client for the Shipthis public API.
4
4
 
5
5
  Usage:
6
+ import asyncio
6
7
  from ShipthisAPI import ShipthisAPI
7
8
 
8
- client = ShipthisAPI(
9
- organisation="your_org_id",
10
- x_api_key="your_api_key",
11
- region_id="your_region",
12
- location_id="your_location"
13
- )
9
+ async def main():
10
+ # Initialize the client
11
+ client = ShipthisAPI(
12
+ organisation="your_org_id",
13
+ x_api_key="your_api_key",
14
+ region_id="your_region",
15
+ location_id="your_location"
16
+ )
17
+
18
+ # Connect and validate
19
+ await client.connect()
14
20
 
15
- # Connect and validate
16
- client.connect()
21
+ # Get items from a collection
22
+ items = await client.get_list("shipment")
17
23
 
18
- # Get items from a collection
19
- items = client.get_list("shipment")
24
+ # Patch document fields
25
+ await client.patch_item("fcl_load", doc_id, {"status": "completed"})
20
26
 
21
- # Get a single item
22
- item = client.get_one_item("shipment", doc_id="abc123")
27
+ asyncio.run(main())
23
28
  """
24
29
 
25
- from typing import Any, Dict, List, Optional, Union
30
+ from typing import Any, Dict, List, Optional
26
31
  import json
27
- import requests
32
+ import httpx
28
33
 
29
34
 
30
35
  class ShipthisAPIError(Exception):
@@ -50,7 +55,7 @@ class ShipthisRequestError(ShipthisAPIError):
50
55
 
51
56
 
52
57
  class ShipthisAPI:
53
- """Shipthis API client for public API access.
58
+ """Async Shipthis API client for public API access.
54
59
 
55
60
  Attributes:
56
61
  base_api_endpoint: The base URL for the API.
@@ -60,6 +65,7 @@ class ShipthisAPI:
60
65
  region_id: Region ID for requests.
61
66
  location_id: Location ID for requests.
62
67
  timeout: Request timeout in seconds.
68
+ custom_headers: Custom headers to override defaults.
63
69
  """
64
70
 
65
71
  DEFAULT_TIMEOUT = 30
@@ -68,28 +74,28 @@ class ShipthisAPI:
68
74
  def __init__(
69
75
  self,
70
76
  organisation: str,
71
- x_api_key: str,
77
+ x_api_key: str = None,
72
78
  user_type: str = "employee",
73
79
  region_id: str = None,
74
80
  location_id: str = None,
75
81
  timeout: int = None,
76
82
  base_url: str = None,
83
+ custom_headers: Dict[str, str] = None,
77
84
  ) -> None:
78
85
  """Initialize the Shipthis API client.
79
86
 
80
87
  Args:
81
88
  organisation: Your organisation ID.
82
- x_api_key: Your API key.
89
+ x_api_key: Your API key (optional if using custom_headers with auth).
83
90
  user_type: User type for requests (default: "employee").
84
91
  region_id: Region ID for requests.
85
92
  location_id: Location ID for requests.
86
93
  timeout: Request timeout in seconds (default: 30).
87
94
  base_url: Custom base URL (optional, for testing).
95
+ custom_headers: Custom headers that override defaults.
88
96
  """
89
97
  if not organisation:
90
98
  raise ValueError("organisation is required")
91
- if not x_api_key:
92
- raise ValueError("x_api_key is required")
93
99
 
94
100
  self.x_api_key = x_api_key
95
101
  self.organisation_id = organisation
@@ -98,6 +104,7 @@ class ShipthisAPI:
98
104
  self.location_id = location_id
99
105
  self.timeout = timeout or self.DEFAULT_TIMEOUT
100
106
  self.base_api_endpoint = base_url or self.BASE_API_ENDPOINT
107
+ self.custom_headers = custom_headers or {}
101
108
  self.organisation_info = None
102
109
  self.is_connected = False
103
110
 
@@ -111,39 +118,50 @@ class ShipthisAPI:
111
118
  self.region_id = region_id
112
119
  self.location_id = location_id
113
120
 
114
- def _get_headers(self) -> Dict[str, str]:
121
+ def _get_headers(self, override_headers: Dict[str, str] = None) -> Dict[str, str]:
115
122
  """Build request headers.
116
123
 
124
+ Args:
125
+ override_headers: Headers to override for this specific request.
126
+
117
127
  Returns:
118
128
  Dictionary of headers.
119
129
  """
120
130
  headers = {
121
- "x-api-key": self.x_api_key,
122
131
  "organisation": self.organisation_id,
123
132
  "usertype": self.user_type,
124
133
  "Content-Type": "application/json",
125
134
  "Accept": "application/json",
126
135
  }
136
+ if self.x_api_key:
137
+ headers["x-api-key"] = self.x_api_key
127
138
  if self.region_id:
128
139
  headers["region"] = self.region_id
129
140
  if self.location_id:
130
141
  headers["location"] = self.location_id
142
+ # Apply custom headers from init
143
+ headers.update(self.custom_headers)
144
+ # Apply per-request override headers
145
+ if override_headers:
146
+ headers.update(override_headers)
131
147
  return headers
132
148
 
133
- def _make_request(
149
+ async def _make_request(
134
150
  self,
135
151
  method: str,
136
152
  path: str,
137
153
  query_params: Dict[str, Any] = None,
138
154
  request_data: Dict[str, Any] = None,
155
+ headers: Dict[str, str] = None,
139
156
  ) -> Dict[str, Any]:
140
- """Make an HTTP request to the API.
157
+ """Make an async HTTP request to the API.
141
158
 
142
159
  Args:
143
160
  method: HTTP method (GET, POST, PUT, PATCH, DELETE).
144
161
  path: API endpoint path.
145
162
  query_params: Query parameters.
146
163
  request_data: Request body data.
164
+ headers: Headers to override for this request.
147
165
 
148
166
  Returns:
149
167
  API response data.
@@ -153,41 +171,32 @@ class ShipthisAPI:
153
171
  ShipthisRequestError: If the request fails.
154
172
  """
155
173
  url = self.base_api_endpoint + path
156
- headers = self._get_headers()
174
+ request_headers = self._get_headers(headers)
157
175
 
158
- try:
159
- if request_data:
160
- response = requests.request(
176
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
177
+ try:
178
+ response = await client.request(
161
179
  method,
162
180
  url,
163
- json=request_data,
164
- headers=headers,
181
+ headers=request_headers,
165
182
  params=query_params,
166
- timeout=self.timeout,
183
+ json=request_data,
167
184
  )
168
- else:
169
- response = requests.request(
170
- method,
171
- url,
172
- headers=headers,
173
- params=query_params,
174
- timeout=self.timeout,
185
+ except httpx.TimeoutException:
186
+ raise ShipthisRequestError(
187
+ message="Request timed out",
188
+ status_code=408,
189
+ )
190
+ except httpx.ConnectError as e:
191
+ raise ShipthisRequestError(
192
+ message=f"Connection error: {str(e)}",
193
+ status_code=0,
194
+ )
195
+ except httpx.RequestError as e:
196
+ raise ShipthisRequestError(
197
+ message=f"Request failed: {str(e)}",
198
+ status_code=0,
175
199
  )
176
- except requests.exceptions.Timeout:
177
- raise ShipthisRequestError(
178
- message="Request timed out",
179
- status_code=408,
180
- )
181
- except requests.exceptions.ConnectionError as e:
182
- raise ShipthisRequestError(
183
- message=f"Connection error: {str(e)}",
184
- status_code=0,
185
- )
186
- except requests.exceptions.RequestException as e:
187
- raise ShipthisRequestError(
188
- message=f"Request failed: {str(e)}",
189
- status_code=0,
190
- )
191
200
 
192
201
  # Handle authentication errors
193
202
  if response.status_code == 401:
@@ -235,7 +244,7 @@ class ShipthisAPI:
235
244
 
236
245
  # ==================== Connection ====================
237
246
 
238
- def connect(self) -> Dict[str, Any]:
247
+ async def connect(self) -> Dict[str, Any]:
239
248
  """Connect and validate the API connection.
240
249
 
241
250
  Fetches organisation info and validates region/location.
@@ -247,7 +256,7 @@ class ShipthisAPI:
247
256
  Raises:
248
257
  ShipthisAPIError: If connection fails.
249
258
  """
250
- info = self.info()
259
+ info = await self.info()
251
260
  self.organisation_info = info.get("organisation")
252
261
 
253
262
  if not self.region_id or not self.location_id:
@@ -273,7 +282,7 @@ class ShipthisAPI:
273
282
 
274
283
  # ==================== Info ====================
275
284
 
276
- def info(self) -> Dict[str, Any]:
285
+ async def info(self) -> Dict[str, Any]:
277
286
  """Get organisation and user info.
278
287
 
279
288
  Returns:
@@ -282,11 +291,11 @@ class ShipthisAPI:
282
291
  Raises:
283
292
  ShipthisAPIError: If the request fails.
284
293
  """
285
- return self._make_request("GET", "user-auth/info")
294
+ return await self._make_request("GET", "user-auth/info")
286
295
 
287
296
  # ==================== Collection CRUD ====================
288
297
 
289
- def get_one_item(
298
+ async def get_one_item(
290
299
  self,
291
300
  collection_name: str,
292
301
  doc_id: str = None,
@@ -312,19 +321,19 @@ class ShipthisAPI:
312
321
  params = {}
313
322
  if only_fields:
314
323
  params["only"] = only_fields
315
- return self._make_request("GET", path, query_params=params if params else None)
324
+ return await self._make_request("GET", path, query_params=params if params else None)
316
325
  else:
317
326
  params = {}
318
327
  if filters:
319
328
  params["query_filter_v2"] = json.dumps(filters)
320
329
  if only_fields:
321
330
  params["only"] = only_fields
322
- resp = self._make_request("GET", f"incollection/{collection_name}", params)
331
+ resp = await self._make_request("GET", f"incollection/{collection_name}", params)
323
332
  if isinstance(resp, dict) and resp.get("items"):
324
333
  return resp.get("items")[0]
325
334
  return None
326
335
 
327
- def get_list(
336
+ async def get_list(
328
337
  self,
329
338
  collection_name: str,
330
339
  filters: Dict[str, Any] = None,
@@ -369,13 +378,13 @@ class ShipthisAPI:
369
378
  if not meta:
370
379
  params["meta"] = "false"
371
380
 
372
- response = self._make_request("GET", f"incollection/{collection_name}", params)
381
+ response = await self._make_request("GET", f"incollection/{collection_name}", params)
373
382
 
374
383
  if isinstance(response, dict):
375
384
  return response.get("items", [])
376
385
  return []
377
386
 
378
- def search(
387
+ async def search(
379
388
  self,
380
389
  collection_name: str,
381
390
  query: str,
@@ -398,7 +407,7 @@ class ShipthisAPI:
398
407
  Raises:
399
408
  ShipthisAPIError: If the request fails.
400
409
  """
401
- return self.get_list(
410
+ return await self.get_list(
402
411
  collection_name,
403
412
  search_query=query,
404
413
  page=page,
@@ -406,7 +415,7 @@ class ShipthisAPI:
406
415
  only_fields=only_fields,
407
416
  )
408
417
 
409
- def create_item(
418
+ async def create_item(
410
419
  self, collection_name: str, data: Dict[str, Any]
411
420
  ) -> Dict[str, Any]:
412
421
  """Create a new item in a collection.
@@ -421,7 +430,7 @@ class ShipthisAPI:
421
430
  Raises:
422
431
  ShipthisAPIError: If the request fails.
423
432
  """
424
- resp = self._make_request(
433
+ resp = await self._make_request(
425
434
  "POST",
426
435
  f"incollection/{collection_name}",
427
436
  request_data={"reqbody": data},
@@ -430,7 +439,7 @@ class ShipthisAPI:
430
439
  return resp.get("data")
431
440
  return resp
432
441
 
433
- def update_item(
442
+ async def update_item(
434
443
  self,
435
444
  collection_name: str,
436
445
  object_id: str,
@@ -449,7 +458,7 @@ class ShipthisAPI:
449
458
  Raises:
450
459
  ShipthisAPIError: If the request fails.
451
460
  """
452
- resp = self._make_request(
461
+ resp = await self._make_request(
453
462
  "PUT",
454
463
  f"incollection/{collection_name}/{object_id}",
455
464
  request_data={"reqbody": updated_data},
@@ -458,7 +467,7 @@ class ShipthisAPI:
458
467
  return resp.get("data")
459
468
  return resp
460
469
 
461
- def patch_item(
470
+ async def patch_item(
462
471
  self,
463
472
  collection_name: str,
464
473
  object_id: str,
@@ -466,24 +475,34 @@ class ShipthisAPI:
466
475
  ) -> Dict[str, Any]:
467
476
  """Patch specific fields of an item.
468
477
 
478
+ This is the recommended way to update document fields. It goes through
479
+ full field validation, workflow triggers, audit logging, and business logic.
480
+
469
481
  Args:
470
- collection_name: Name of the collection.
482
+ collection_name: Name of the collection (e.g., "sea_shipment", "fcl_load").
471
483
  object_id: Document ID.
472
- update_fields: Fields to update.
484
+ update_fields: Dictionary of field_id to value mappings.
473
485
 
474
486
  Returns:
475
487
  Updated document data.
476
488
 
477
489
  Raises:
478
490
  ShipthisAPIError: If the request fails.
491
+
492
+ Example:
493
+ await client.patch_item(
494
+ "fcl_load",
495
+ "68a4f906743189ad061429a7",
496
+ update_fields={"container_no": "CONT123", "seal_no": "SEAL456"}
497
+ )
479
498
  """
480
- return self._make_request(
499
+ return await self._make_request(
481
500
  "PATCH",
482
501
  f"incollection/{collection_name}/{object_id}",
483
502
  request_data={"update_fields": update_fields},
484
503
  )
485
504
 
486
- def delete_item(self, collection_name: str, object_id: str) -> Dict[str, Any]:
505
+ async def delete_item(self, collection_name: str, object_id: str) -> Dict[str, Any]:
487
506
  """Delete an item.
488
507
 
489
508
  Args:
@@ -496,14 +515,14 @@ class ShipthisAPI:
496
515
  Raises:
497
516
  ShipthisAPIError: If the request fails.
498
517
  """
499
- return self._make_request(
518
+ return await self._make_request(
500
519
  "DELETE",
501
520
  f"incollection/{collection_name}/{object_id}",
502
521
  )
503
522
 
504
523
  # ==================== Workflow Operations ====================
505
524
 
506
- def get_job_status(
525
+ async def get_job_status(
507
526
  self, collection_name: str, object_id: str
508
527
  ) -> Dict[str, Any]:
509
528
  """Get the job status for a document.
@@ -518,12 +537,12 @@ class ShipthisAPI:
518
537
  Raises:
519
538
  ShipthisAPIError: If the request fails.
520
539
  """
521
- return self._make_request(
540
+ return await self._make_request(
522
541
  "GET",
523
542
  f"workflow/{collection_name}/job_status/{object_id}",
524
543
  )
525
544
 
526
- def set_job_status(
545
+ async def set_job_status(
527
546
  self,
528
547
  collection_name: str,
529
548
  object_id: str,
@@ -542,13 +561,13 @@ class ShipthisAPI:
542
561
  Raises:
543
562
  ShipthisAPIError: If the request fails.
544
563
  """
545
- return self._make_request(
564
+ return await self._make_request(
546
565
  "POST",
547
566
  f"workflow/{collection_name}/job_status/{object_id}",
548
567
  request_data={"action_index": action_index},
549
568
  )
550
569
 
551
- def get_workflow(self, object_id: str) -> Dict[str, Any]:
570
+ async def get_workflow(self, object_id: str) -> Dict[str, Any]:
552
571
  """Get a workflow configuration.
553
572
 
554
573
  Args:
@@ -560,11 +579,11 @@ class ShipthisAPI:
560
579
  Raises:
561
580
  ShipthisAPIError: If the request fails.
562
581
  """
563
- return self._make_request("GET", f"incollection/workflow/{object_id}")
582
+ return await self._make_request("GET", f"incollection/workflow/{object_id}")
564
583
 
565
584
  # ==================== Reports ====================
566
585
 
567
- def get_report_view(
586
+ async def get_report_view(
568
587
  self,
569
588
  report_name: str,
570
589
  start_date: str,
@@ -598,7 +617,7 @@ class ShipthisAPI:
598
617
  if self.location_id:
599
618
  params["location"] = self.location_id
600
619
 
601
- return self._make_request(
620
+ return await self._make_request(
602
621
  "POST",
603
622
  f"report-view/{report_name}",
604
623
  query_params=params,
@@ -607,7 +626,7 @@ class ShipthisAPI:
607
626
 
608
627
  # ==================== Third-party Services ====================
609
628
 
610
- def get_exchange_rate(
629
+ async def get_exchange_rate(
611
630
  self,
612
631
  source_currency: str,
613
632
  target_currency: str = "USD",
@@ -630,12 +649,12 @@ class ShipthisAPI:
630
649
  if date is None:
631
650
  date = int(time.time() * 1000)
632
651
 
633
- return self._make_request(
652
+ return await self._make_request(
634
653
  "GET",
635
654
  f"thirdparty/currency?source={source_currency}&target={target_currency}&date={date}",
636
655
  )
637
656
 
638
- def autocomplete(
657
+ async def autocomplete(
639
658
  self,
640
659
  reference_name: str,
641
660
  data: Dict[str, Any],
@@ -656,14 +675,14 @@ class ShipthisAPI:
656
675
  if self.location_id:
657
676
  params["location"] = self.location_id
658
677
 
659
- return self._make_request(
678
+ return await self._make_request(
660
679
  "POST",
661
680
  f"autocomplete-reference/{reference_name}",
662
681
  query_params=params if params else None,
663
682
  request_data=data,
664
683
  )
665
684
 
666
- def search_location(self, query: str) -> List[Dict[str, Any]]:
685
+ async def search_location(self, query: str) -> List[Dict[str, Any]]:
667
686
  """Search for locations using Google Places.
668
687
 
669
688
  Args:
@@ -675,12 +694,12 @@ class ShipthisAPI:
675
694
  Raises:
676
695
  ShipthisAPIError: If the request fails.
677
696
  """
678
- return self._make_request(
697
+ return await self._make_request(
679
698
  "GET",
680
699
  f"thirdparty/search-place-autocomplete?query={query}",
681
700
  )
682
701
 
683
- def get_place_details(
702
+ async def get_place_details(
684
703
  self,
685
704
  place_id: str,
686
705
  description: str = "",
@@ -697,14 +716,14 @@ class ShipthisAPI:
697
716
  Raises:
698
717
  ShipthisAPIError: If the request fails.
699
718
  """
700
- return self._make_request(
719
+ return await self._make_request(
701
720
  "GET",
702
721
  f"thirdparty/select-google-place?query={place_id}&description={description}",
703
722
  )
704
723
 
705
724
  # ==================== Conversations ====================
706
725
 
707
- def create_conversation(
726
+ async def create_conversation(
708
727
  self,
709
728
  view_name: str,
710
729
  document_id: str,
@@ -729,9 +748,9 @@ class ShipthisAPI:
729
748
  "view_name": view_name,
730
749
  "message_type": conversation_data.get("type", ""),
731
750
  }
732
- return self._make_request("POST", "conversation", request_data=payload)
751
+ return await self._make_request("POST", "conversation", request_data=payload)
733
752
 
734
- def get_conversations(
753
+ async def get_conversations(
735
754
  self,
736
755
  view_name: str,
737
756
  document_id: str,
@@ -762,11 +781,11 @@ class ShipthisAPI:
762
781
  "message_type": message_type,
763
782
  "version": "2",
764
783
  }
765
- return self._make_request("GET", "conversation", query_params=params)
784
+ return await self._make_request("GET", "conversation", query_params=params)
766
785
 
767
786
  # ==================== Bulk Operations ====================
768
787
 
769
- def bulk_edit(
788
+ async def bulk_edit(
770
789
  self,
771
790
  collection_name: str,
772
791
  ids: List[str],
@@ -788,7 +807,7 @@ class ShipthisAPI:
788
807
  ShipthisAPIError: If the request fails.
789
808
 
790
809
  Example:
791
- client.bulk_edit(
810
+ await client.bulk_edit(
792
811
  "customer",
793
812
  ids=["5fdc00487f7636c97b9fa064", "608fe19fc33215427867f34e"],
794
813
  update_data={"company.fax_no": "12323231", "address.state": "California"}
@@ -803,7 +822,7 @@ class ShipthisAPI:
803
822
  if external_update_data:
804
823
  payload["data"]["external_update_data"] = external_update_data
805
824
 
806
- return self._make_request(
825
+ return await self._make_request(
807
826
  "POST",
808
827
  f"incollection_group_edit/{collection_name}",
809
828
  request_data=payload,
@@ -811,7 +830,7 @@ class ShipthisAPI:
811
830
 
812
831
  # ==================== Workflow Actions ====================
813
832
 
814
- def primary_workflow_action(
833
+ async def primary_workflow_action(
815
834
  self,
816
835
  collection: str,
817
836
  workflow_id: str,
@@ -837,7 +856,7 @@ class ShipthisAPI:
837
856
  ShipthisAPIError: If the request fails.
838
857
 
839
858
  Example:
840
- client.primary_workflow_action(
859
+ await client.primary_workflow_action(
841
860
  collection="pickup_delivery",
842
861
  workflow_id="job_status",
843
862
  object_id="68a4f906743189ad061429a7",
@@ -853,13 +872,13 @@ class ShipthisAPI:
853
872
  if start_state_id:
854
873
  payload["start_state_id"] = start_state_id
855
874
 
856
- return self._make_request(
875
+ return await self._make_request(
857
876
  "POST",
858
877
  f"workflow/{collection}/{workflow_id}/{object_id}",
859
878
  request_data=payload,
860
879
  )
861
880
 
862
- def secondary_workflow_action(
881
+ async def secondary_workflow_action(
863
882
  self,
864
883
  collection: str,
865
884
  workflow_id: str,
@@ -883,7 +902,7 @@ class ShipthisAPI:
883
902
  ShipthisAPIError: If the request fails.
884
903
 
885
904
  Example:
886
- client.secondary_workflow_action(
905
+ await client.secondary_workflow_action(
887
906
  collection="pickup_delivery",
888
907
  workflow_id="driver_status",
889
908
  object_id="67ed10859b7cf551a19f813e",
@@ -892,7 +911,7 @@ class ShipthisAPI:
892
911
  """
893
912
  payload = additional_data or {}
894
913
 
895
- return self._make_request(
914
+ return await self._make_request(
896
915
  "POST",
897
916
  f"workflow/{collection}/{workflow_id}/{object_id}/{target_state}",
898
917
  request_data=payload,
@@ -900,7 +919,7 @@ class ShipthisAPI:
900
919
 
901
920
  # ==================== File Upload ====================
902
921
 
903
- def upload_file(
922
+ async def upload_file(
904
923
  self,
905
924
  file_path: str,
906
925
  file_name: str = None,
@@ -933,18 +952,18 @@ class ShipthisAPI:
933
952
  try:
934
953
  with open(file_path, "rb") as f:
935
954
  files = {"file": (file_name, f)}
936
- response = requests.post(
937
- upload_url,
938
- headers=headers,
939
- files=files,
940
- timeout=self.timeout * 2, # Double timeout for uploads
941
- )
955
+ async with httpx.AsyncClient(timeout=self.timeout * 2) as client:
956
+ response = await client.post(
957
+ upload_url,
958
+ headers=headers,
959
+ files=files,
960
+ )
942
961
  except FileNotFoundError:
943
962
  raise ShipthisRequestError(
944
963
  message=f"File not found: {file_path}",
945
964
  status_code=0,
946
965
  )
947
- except requests.exceptions.RequestException as e:
966
+ except httpx.RequestError as e:
948
967
  raise ShipthisRequestError(
949
968
  message=f"Upload failed: {str(e)}",
950
969
  status_code=0,
@@ -959,4 +978,38 @@ class ShipthisAPI:
959
978
  raise ShipthisRequestError(
960
979
  message=f"Upload failed with status {response.status_code}",
961
980
  status_code=response.status_code,
962
- )
981
+ )
982
+
983
+ # ==================== Reference Linked Fields ====================
984
+
985
+ async def create_reference_linked_field(
986
+ self,
987
+ collection_name: str,
988
+ doc_id: str,
989
+ payload: Dict[str, Any],
990
+ ) -> Dict[str, Any]:
991
+ """Create a reference-linked field on a document.
992
+
993
+ Args:
994
+ collection_name: Collection name.
995
+ doc_id: Document ID.
996
+ payload: Field data to create.
997
+
998
+ Returns:
999
+ API response.
1000
+
1001
+ Raises:
1002
+ ShipthisAPIError: If the request fails.
1003
+
1004
+ Example:
1005
+ await client.create_reference_linked_field(
1006
+ "sea_shipment",
1007
+ "68a4f906743189ad061429a7",
1008
+ payload={"field_name": "containers", "data": {...}}
1009
+ )
1010
+ """
1011
+ return await self._make_request(
1012
+ "POST",
1013
+ f"incollection/create-reference-linked-field/{collection_name}/{doc_id}",
1014
+ request_data=payload,
1015
+ )
@@ -5,15 +5,18 @@ with open("README.md", "r") as fh:
5
5
 
6
6
 
7
7
  setuptools.setup(
8
- name='shipthisapi-python',
9
- version='2.1.0',
8
+ name='shipthisapi-python',
9
+ version='3.0.0',
10
10
  author="Mayur Rawte",
11
11
  author_email="mayur@shipthis.co",
12
- description="ShipthisAPI utility package",
12
+ description="ShipthisAPI async utility package",
13
13
  long_description=long_description,
14
14
  long_description_content_type="text/markdown",
15
15
  url="https://github.com/shipthisco/shipthisapi-python",
16
16
  packages=setuptools.find_packages(),
17
+ install_requires=[
18
+ "httpx>=0.24.0",
19
+ ],
17
20
  classifiers=[
18
21
  "Programming Language :: Python :: 3",
19
22
  "License :: OSI Approved :: MIT License",
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shipthisapi-python
3
- Version: 2.1.0
4
- Summary: ShipthisAPI utility package
3
+ Version: 3.0.0
4
+ Summary: ShipthisAPI async utility package
5
5
  Home-page: https://github.com/shipthisco/shipthisapi-python
6
6
  Author: Mayur Rawte
7
7
  Author-email: mayur@shipthis.co
@@ -9,12 +9,14 @@ Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Description-Content-Type: text/markdown
12
+ Requires-Dist: httpx>=0.24.0
12
13
  Dynamic: author
13
14
  Dynamic: author-email
14
15
  Dynamic: classifier
15
16
  Dynamic: description
16
17
  Dynamic: description-content-type
17
18
  Dynamic: home-page
19
+ Dynamic: requires-dist
18
20
  Dynamic: summary
19
21
 
20
22
  # ShipthisAPI Python
@@ -5,4 +5,5 @@ ShipthisAPI/shipthisapi.py
5
5
  shipthisapi_python.egg-info/PKG-INFO
6
6
  shipthisapi_python.egg-info/SOURCES.txt
7
7
  shipthisapi_python.egg-info/dependency_links.txt
8
+ shipthisapi_python.egg-info/requires.txt
8
9
  shipthisapi_python.egg-info/top_level.txt