pdfdancer-client-python 0.2.22__py3-none-any.whl → 0.2.24__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.
- pdfdancer/__init__.py +2 -0
- pdfdancer/exceptions.py +17 -0
- pdfdancer/pdfdancer_v1.py +260 -40
- {pdfdancer_client_python-0.2.22.dist-info → pdfdancer_client_python-0.2.24.dist-info}/METADATA +4 -2
- {pdfdancer_client_python-0.2.22.dist-info → pdfdancer_client_python-0.2.24.dist-info}/RECORD +9 -9
- {pdfdancer_client_python-0.2.22.dist-info → pdfdancer_client_python-0.2.24.dist-info}/WHEEL +0 -0
- {pdfdancer_client_python-0.2.22.dist-info → pdfdancer_client_python-0.2.24.dist-info}/licenses/LICENSE +0 -0
- {pdfdancer_client_python-0.2.22.dist-info → pdfdancer_client_python-0.2.24.dist-info}/licenses/NOTICE +0 -0
- {pdfdancer_client_python-0.2.22.dist-info → pdfdancer_client_python-0.2.24.dist-info}/top_level.txt +0 -0
pdfdancer/__init__.py
CHANGED
|
@@ -10,6 +10,7 @@ from .exceptions import (
|
|
|
10
10
|
FontNotFoundException,
|
|
11
11
|
HttpClientException,
|
|
12
12
|
PdfDancerException,
|
|
13
|
+
RateLimitException,
|
|
13
14
|
SessionException,
|
|
14
15
|
ValidationException,
|
|
15
16
|
)
|
|
@@ -80,6 +81,7 @@ __all__ = [
|
|
|
80
81
|
"ValidationException",
|
|
81
82
|
"HttpClientException",
|
|
82
83
|
"SessionException",
|
|
84
|
+
"RateLimitException",
|
|
83
85
|
"set_ssl_verify",
|
|
84
86
|
]
|
|
85
87
|
|
pdfdancer/exceptions.py
CHANGED
|
@@ -62,3 +62,20 @@ class ValidationException(PdfDancerException):
|
|
|
62
62
|
"""
|
|
63
63
|
|
|
64
64
|
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class RateLimitException(PdfDancerException):
|
|
68
|
+
"""
|
|
69
|
+
Exception raised when the API rate limit is exceeded (HTTP 429).
|
|
70
|
+
Includes retry-after information if provided by the server.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
message: str,
|
|
76
|
+
retry_after: Optional[int] = None,
|
|
77
|
+
response: Optional[httpx.Response] = None,
|
|
78
|
+
):
|
|
79
|
+
super().__init__(message)
|
|
80
|
+
self.retry_after = retry_after
|
|
81
|
+
self.response = response
|
pdfdancer/pdfdancer_v1.py
CHANGED
|
@@ -11,6 +11,7 @@ import gzip
|
|
|
11
11
|
import json
|
|
12
12
|
import logging
|
|
13
13
|
import os
|
|
14
|
+
import sys
|
|
14
15
|
import time
|
|
15
16
|
from datetime import datetime, timezone
|
|
16
17
|
from pathlib import Path
|
|
@@ -24,6 +25,7 @@ from .exceptions import (
|
|
|
24
25
|
FontNotFoundException,
|
|
25
26
|
HttpClientException,
|
|
26
27
|
PdfDancerException,
|
|
28
|
+
RateLimitException,
|
|
27
29
|
SessionException,
|
|
28
30
|
ValidationException,
|
|
29
31
|
)
|
|
@@ -234,6 +236,30 @@ def _is_retryable_error(error: Exception) -> bool:
|
|
|
234
236
|
return any(msg in error_msg for msg in retryable_messages)
|
|
235
237
|
|
|
236
238
|
|
|
239
|
+
def _get_retry_after_delay(response: httpx.Response) -> Optional[int]:
|
|
240
|
+
"""
|
|
241
|
+
Extract Retry-After delay from response headers.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
response: HTTP response with potential Retry-After header
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
Delay in seconds, or None if header not present or invalid
|
|
248
|
+
"""
|
|
249
|
+
retry_after = response.headers.get("Retry-After")
|
|
250
|
+
if not retry_after:
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
# Retry-After can be either a number of seconds or an HTTP date
|
|
255
|
+
# Try parsing as integer first (seconds)
|
|
256
|
+
return int(retry_after)
|
|
257
|
+
except ValueError:
|
|
258
|
+
# If not a number, it might be an HTTP date - ignore for now
|
|
259
|
+
# Most rate limiting APIs use seconds
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
|
|
237
263
|
class PageClient:
|
|
238
264
|
def __init__(
|
|
239
265
|
self,
|
|
@@ -708,38 +734,102 @@ class PDFDancer:
|
|
|
708
734
|
|
|
709
735
|
Raises:
|
|
710
736
|
HttpClientException: If token request fails
|
|
737
|
+
RateLimitException: If rate limit is exceeded
|
|
711
738
|
"""
|
|
739
|
+
# Create temporary client without authentication
|
|
740
|
+
temp_client = httpx.Client(http2=True, verify=not DISABLE_SSL_VERIFY)
|
|
741
|
+
max_retries = 3
|
|
742
|
+
retry_backoff_factor = 1.0
|
|
743
|
+
|
|
712
744
|
try:
|
|
713
|
-
|
|
714
|
-
|
|
745
|
+
last_error: Optional[Exception] = None
|
|
746
|
+
attempt = 0
|
|
715
747
|
|
|
716
|
-
|
|
748
|
+
while attempt <= max_retries:
|
|
749
|
+
try:
|
|
750
|
+
headers = {"X-Fingerprint": Fingerprint.generate()}
|
|
717
751
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
752
|
+
response = temp_client.post(
|
|
753
|
+
cls._cleanup_url_path(base_url, "/keys/anon"),
|
|
754
|
+
headers=headers,
|
|
755
|
+
timeout=timeout if timeout > 0 else None,
|
|
756
|
+
)
|
|
723
757
|
|
|
724
|
-
|
|
725
|
-
|
|
758
|
+
response.raise_for_status()
|
|
759
|
+
token_data = response.json()
|
|
726
760
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
761
|
+
# Extract token from response (matches Java AnonTokenResponse structure)
|
|
762
|
+
if isinstance(token_data, dict) and "token" in token_data:
|
|
763
|
+
return token_data["token"]
|
|
764
|
+
else:
|
|
765
|
+
raise HttpClientException(
|
|
766
|
+
"Invalid anonymous token response format"
|
|
767
|
+
)
|
|
732
768
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
769
|
+
except httpx.HTTPStatusError as e:
|
|
770
|
+
# Handle 429 (rate limit) with retry
|
|
771
|
+
if e.response.status_code == 429 and attempt < max_retries:
|
|
772
|
+
retry_after = _get_retry_after_delay(e.response)
|
|
773
|
+
if retry_after is not None:
|
|
774
|
+
delay = retry_after
|
|
775
|
+
else:
|
|
776
|
+
# Use exponential backoff if no Retry-After header
|
|
777
|
+
delay = retry_backoff_factor * (2**attempt)
|
|
778
|
+
|
|
779
|
+
# Always log 429 to stderr for visibility
|
|
780
|
+
print(
|
|
781
|
+
f"Rate limit (429) on POST /keys/anon - retrying in {delay}s "
|
|
782
|
+
f"(attempt {attempt + 1}/{max_retries})",
|
|
783
|
+
file=sys.stderr,
|
|
784
|
+
)
|
|
785
|
+
if DEBUG:
|
|
786
|
+
print(
|
|
787
|
+
f"{time.time()}|POST /keys/anon - Rate limit exceeded (429), "
|
|
788
|
+
f"retrying in {delay}s (attempt {attempt + 1}/{max_retries})"
|
|
789
|
+
)
|
|
790
|
+
time.sleep(delay)
|
|
791
|
+
attempt += 1
|
|
792
|
+
continue
|
|
793
|
+
|
|
794
|
+
# Raise RateLimitException for 429 after exhausting retries
|
|
795
|
+
if e.response.status_code == 429:
|
|
796
|
+
retry_after = _get_retry_after_delay(e.response)
|
|
797
|
+
print(
|
|
798
|
+
"Rate limit (429) on POST /keys/anon - max retries exhausted",
|
|
799
|
+
file=sys.stderr,
|
|
800
|
+
)
|
|
801
|
+
raise RateLimitException(
|
|
802
|
+
"Rate limit exceeded when obtaining anonymous token",
|
|
803
|
+
retry_after=retry_after,
|
|
804
|
+
response=e.response,
|
|
805
|
+
) from None
|
|
806
|
+
|
|
807
|
+
# Other HTTP status errors
|
|
808
|
+
raise HttpClientException(
|
|
809
|
+
f"Failed to obtain anonymous token: HTTP {e.response.status_code}",
|
|
810
|
+
response=e.response,
|
|
811
|
+
cause=e,
|
|
812
|
+
) from None
|
|
813
|
+
except httpx.RequestError as e:
|
|
814
|
+
last_error = e
|
|
815
|
+
raise HttpClientException(
|
|
816
|
+
f"Failed to obtain anonymous token: {str(e)}",
|
|
817
|
+
response=None,
|
|
818
|
+
cause=e,
|
|
819
|
+
) from None
|
|
820
|
+
|
|
821
|
+
# Should not reach here, but handle just in case
|
|
822
|
+
if last_error:
|
|
823
|
+
raise HttpClientException(
|
|
824
|
+
f"Failed to obtain anonymous token after {max_retries + 1} attempts: {str(last_error)}",
|
|
825
|
+
response=None,
|
|
826
|
+
cause=last_error,
|
|
827
|
+
) from None
|
|
828
|
+
else:
|
|
829
|
+
raise HttpClientException(
|
|
830
|
+
f"Failed to obtain anonymous token after {max_retries + 1} attempts",
|
|
831
|
+
response=None,
|
|
832
|
+
)
|
|
743
833
|
finally:
|
|
744
834
|
temp_client.close()
|
|
745
835
|
|
|
@@ -1029,7 +1119,9 @@ class PDFDancer:
|
|
|
1029
1119
|
b'Content-Disposition: form-data; name="pdf"; filename="document.pdf"\r\n'
|
|
1030
1120
|
)
|
|
1031
1121
|
body_parts.append(b"Content-Type: application/pdf\r\n")
|
|
1032
|
-
body_parts.append(
|
|
1122
|
+
body_parts.append(
|
|
1123
|
+
b"\r\n"
|
|
1124
|
+
) # End of headers, no Content-Transfer-Encoding
|
|
1033
1125
|
body_parts.append(self._pdf_bytes)
|
|
1034
1126
|
body_parts.append(b"\r\n")
|
|
1035
1127
|
body_parts.append(f"--{boundary}--\r\n".encode("utf-8"))
|
|
@@ -1042,11 +1134,17 @@ class PDFDancer:
|
|
|
1042
1134
|
original_size = len(uncompressed_body)
|
|
1043
1135
|
compressed_size = len(compressed_body)
|
|
1044
1136
|
compression_ratio = (
|
|
1045
|
-
(1 - compressed_size / original_size) * 100
|
|
1137
|
+
(1 - compressed_size / original_size) * 100
|
|
1138
|
+
if original_size > 0
|
|
1139
|
+
else 0
|
|
1046
1140
|
)
|
|
1047
1141
|
|
|
1048
1142
|
if DEBUG:
|
|
1049
|
-
retry_info =
|
|
1143
|
+
retry_info = (
|
|
1144
|
+
f" (attempt {attempt + 1}/{self._max_retries + 1})"
|
|
1145
|
+
if attempt > 0
|
|
1146
|
+
else ""
|
|
1147
|
+
)
|
|
1050
1148
|
print(
|
|
1051
1149
|
f"{time.time()}|POST /session/create{retry_info} - original size: {original_size} bytes, "
|
|
1052
1150
|
f"compressed size: {compressed_size} bytes, "
|
|
@@ -1083,9 +1181,47 @@ class PDFDancer:
|
|
|
1083
1181
|
return session_id
|
|
1084
1182
|
|
|
1085
1183
|
except httpx.HTTPStatusError as e:
|
|
1086
|
-
#
|
|
1184
|
+
# Handle 429 (rate limit) with retry
|
|
1185
|
+
if e.response.status_code == 429 and attempt < self._max_retries:
|
|
1186
|
+
retry_after = _get_retry_after_delay(e.response)
|
|
1187
|
+
if retry_after is not None:
|
|
1188
|
+
delay = retry_after
|
|
1189
|
+
else:
|
|
1190
|
+
# Use exponential backoff if no Retry-After header
|
|
1191
|
+
delay = self._retry_backoff_factor * (2**attempt)
|
|
1192
|
+
|
|
1193
|
+
# Always log 429 to stderr for visibility
|
|
1194
|
+
print(
|
|
1195
|
+
f"Rate limit (429) on POST /session/create - retrying in {delay}s "
|
|
1196
|
+
f"(attempt {attempt + 1}/{self._max_retries})",
|
|
1197
|
+
file=sys.stderr,
|
|
1198
|
+
)
|
|
1199
|
+
if DEBUG:
|
|
1200
|
+
print(
|
|
1201
|
+
f"{time.time()}|POST /session/create - Rate limit exceeded (429), "
|
|
1202
|
+
f"retrying in {delay}s (attempt {attempt + 1}/{self._max_retries})"
|
|
1203
|
+
)
|
|
1204
|
+
time.sleep(delay)
|
|
1205
|
+
attempt += 1
|
|
1206
|
+
continue
|
|
1207
|
+
|
|
1208
|
+
# Other HTTP status errors are not retried (these are application-level errors)
|
|
1087
1209
|
self._handle_authentication_error(e.response)
|
|
1088
1210
|
error_message = self._extract_error_message(e.response)
|
|
1211
|
+
|
|
1212
|
+
# Raise RateLimitException for 429 after exhausting retries
|
|
1213
|
+
if e.response.status_code == 429:
|
|
1214
|
+
retry_after = _get_retry_after_delay(e.response)
|
|
1215
|
+
print(
|
|
1216
|
+
"Rate limit (429) on POST /session/create - max retries exhausted",
|
|
1217
|
+
file=sys.stderr,
|
|
1218
|
+
)
|
|
1219
|
+
raise RateLimitException(
|
|
1220
|
+
f"Rate limit exceeded: {error_message}",
|
|
1221
|
+
retry_after=retry_after,
|
|
1222
|
+
response=e.response,
|
|
1223
|
+
) from None
|
|
1224
|
+
|
|
1089
1225
|
raise HttpClientException(
|
|
1090
1226
|
f"Failed to create session: {error_message}",
|
|
1091
1227
|
response=e.response,
|
|
@@ -1097,7 +1233,7 @@ class PDFDancer:
|
|
|
1097
1233
|
# Check if this is a retryable error
|
|
1098
1234
|
if _is_retryable_error(e) and attempt < self._max_retries:
|
|
1099
1235
|
# Calculate exponential backoff delay
|
|
1100
|
-
delay = self._retry_backoff_factor * (2
|
|
1236
|
+
delay = self._retry_backoff_factor * (2**attempt)
|
|
1101
1237
|
if DEBUG:
|
|
1102
1238
|
print(
|
|
1103
1239
|
f"{time.time()}|POST /session/create - Retryable error: {str(e)}, "
|
|
@@ -1157,9 +1293,7 @@ class PDFDancer:
|
|
|
1157
1293
|
except ValueError as exc:
|
|
1158
1294
|
raise ValidationException(str(exc)) from exc
|
|
1159
1295
|
except TypeError:
|
|
1160
|
-
raise ValidationException(
|
|
1161
|
-
f"Invalid page_size type: {type(page_size)}"
|
|
1162
|
-
)
|
|
1296
|
+
raise ValidationException(f"Invalid page_size type: {type(page_size)}")
|
|
1163
1297
|
|
|
1164
1298
|
# Handle orientation
|
|
1165
1299
|
if orientation is not None:
|
|
@@ -1187,7 +1321,11 @@ class PDFDancer:
|
|
|
1187
1321
|
request_body = json.dumps(request_data)
|
|
1188
1322
|
request_size = len(request_body.encode("utf-8"))
|
|
1189
1323
|
if DEBUG:
|
|
1190
|
-
retry_info =
|
|
1324
|
+
retry_info = (
|
|
1325
|
+
f" (attempt {attempt + 1}/{self._max_retries + 1})"
|
|
1326
|
+
if attempt > 0
|
|
1327
|
+
else ""
|
|
1328
|
+
)
|
|
1191
1329
|
print(
|
|
1192
1330
|
f"{time.time()}|POST /session/new{retry_info} - request size: {request_size} bytes"
|
|
1193
1331
|
)
|
|
@@ -1220,9 +1358,47 @@ class PDFDancer:
|
|
|
1220
1358
|
return session_id
|
|
1221
1359
|
|
|
1222
1360
|
except httpx.HTTPStatusError as e:
|
|
1223
|
-
#
|
|
1361
|
+
# Handle 429 (rate limit) with retry
|
|
1362
|
+
if e.response.status_code == 429 and attempt < self._max_retries:
|
|
1363
|
+
retry_after = _get_retry_after_delay(e.response)
|
|
1364
|
+
if retry_after is not None:
|
|
1365
|
+
delay = retry_after
|
|
1366
|
+
else:
|
|
1367
|
+
# Use exponential backoff if no Retry-After header
|
|
1368
|
+
delay = self._retry_backoff_factor * (2**attempt)
|
|
1369
|
+
|
|
1370
|
+
# Always log 429 to stderr for visibility
|
|
1371
|
+
print(
|
|
1372
|
+
f"Rate limit (429) on POST /session/new - retrying in {delay}s "
|
|
1373
|
+
f"(attempt {attempt + 1}/{self._max_retries})",
|
|
1374
|
+
file=sys.stderr,
|
|
1375
|
+
)
|
|
1376
|
+
if DEBUG:
|
|
1377
|
+
print(
|
|
1378
|
+
f"{time.time()}|POST /session/new - Rate limit exceeded (429), "
|
|
1379
|
+
f"retrying in {delay}s (attempt {attempt + 1}/{self._max_retries})"
|
|
1380
|
+
)
|
|
1381
|
+
time.sleep(delay)
|
|
1382
|
+
attempt += 1
|
|
1383
|
+
continue
|
|
1384
|
+
|
|
1385
|
+
# Other HTTP status errors are not retried (these are application-level errors)
|
|
1224
1386
|
self._handle_authentication_error(e.response)
|
|
1225
1387
|
error_message = self._extract_error_message(e.response)
|
|
1388
|
+
|
|
1389
|
+
# Raise RateLimitException for 429 after exhausting retries
|
|
1390
|
+
if e.response.status_code == 429:
|
|
1391
|
+
retry_after = _get_retry_after_delay(e.response)
|
|
1392
|
+
print(
|
|
1393
|
+
"Rate limit (429) on POST /session/new - max retries exhausted",
|
|
1394
|
+
file=sys.stderr,
|
|
1395
|
+
)
|
|
1396
|
+
raise RateLimitException(
|
|
1397
|
+
f"Rate limit exceeded: {error_message}",
|
|
1398
|
+
retry_after=retry_after,
|
|
1399
|
+
response=e.response,
|
|
1400
|
+
) from None
|
|
1401
|
+
|
|
1226
1402
|
raise HttpClientException(
|
|
1227
1403
|
f"Failed to create blank PDF session: {error_message}",
|
|
1228
1404
|
response=e.response,
|
|
@@ -1234,7 +1410,7 @@ class PDFDancer:
|
|
|
1234
1410
|
# Check if this is a retryable error
|
|
1235
1411
|
if _is_retryable_error(e) and attempt < self._max_retries:
|
|
1236
1412
|
# Calculate exponential backoff delay
|
|
1237
|
-
delay = self._retry_backoff_factor * (2
|
|
1413
|
+
delay = self._retry_backoff_factor * (2**attempt)
|
|
1238
1414
|
if DEBUG:
|
|
1239
1415
|
print(
|
|
1240
1416
|
f"{time.time()}|POST /session/new - Retryable error: {str(e)}, "
|
|
@@ -1246,7 +1422,9 @@ class PDFDancer:
|
|
|
1246
1422
|
else:
|
|
1247
1423
|
# Non-retryable error or exhausted retries
|
|
1248
1424
|
raise HttpClientException(
|
|
1249
|
-
f"Failed to create blank PDF session: {str(e)}",
|
|
1425
|
+
f"Failed to create blank PDF session: {str(e)}",
|
|
1426
|
+
response=None,
|
|
1427
|
+
cause=e,
|
|
1250
1428
|
) from None
|
|
1251
1429
|
|
|
1252
1430
|
# Should not reach here, but handle just in case
|
|
@@ -1289,7 +1467,11 @@ class PDFDancer:
|
|
|
1289
1467
|
request_body = json.dumps(data)
|
|
1290
1468
|
request_size = len(request_body.encode("utf-8"))
|
|
1291
1469
|
if DEBUG:
|
|
1292
|
-
retry_info =
|
|
1470
|
+
retry_info = (
|
|
1471
|
+
f" (attempt {attempt + 1}/{self._max_retries + 1})"
|
|
1472
|
+
if attempt > 0
|
|
1473
|
+
else ""
|
|
1474
|
+
)
|
|
1293
1475
|
print(
|
|
1294
1476
|
f"{time.time()}|{method} {path}{retry_info} - request size: {request_size} bytes"
|
|
1295
1477
|
)
|
|
@@ -1327,9 +1509,47 @@ class PDFDancer:
|
|
|
1327
1509
|
return response
|
|
1328
1510
|
|
|
1329
1511
|
except httpx.HTTPStatusError as e:
|
|
1330
|
-
#
|
|
1512
|
+
# Handle 429 (rate limit) with retry
|
|
1513
|
+
if e.response.status_code == 429 and attempt < self._max_retries:
|
|
1514
|
+
retry_after = _get_retry_after_delay(e.response)
|
|
1515
|
+
if retry_after is not None:
|
|
1516
|
+
delay = retry_after
|
|
1517
|
+
else:
|
|
1518
|
+
# Use exponential backoff if no Retry-After header
|
|
1519
|
+
delay = self._retry_backoff_factor * (2**attempt)
|
|
1520
|
+
|
|
1521
|
+
# Always log 429 to stderr for visibility
|
|
1522
|
+
print(
|
|
1523
|
+
f"Rate limit (429) on {method} {path} - retrying in {delay}s "
|
|
1524
|
+
f"(attempt {attempt + 1}/{self._max_retries})",
|
|
1525
|
+
file=sys.stderr,
|
|
1526
|
+
)
|
|
1527
|
+
if DEBUG:
|
|
1528
|
+
print(
|
|
1529
|
+
f"{time.time()}|{method} {path} - Rate limit exceeded (429), "
|
|
1530
|
+
f"retrying in {delay}s (attempt {attempt + 1}/{self._max_retries})"
|
|
1531
|
+
)
|
|
1532
|
+
time.sleep(delay)
|
|
1533
|
+
attempt += 1
|
|
1534
|
+
continue
|
|
1535
|
+
|
|
1536
|
+
# Other HTTP status errors are not retried (these are application-level errors)
|
|
1331
1537
|
self._handle_authentication_error(e.response)
|
|
1332
1538
|
error_message = self._extract_error_message(e.response)
|
|
1539
|
+
|
|
1540
|
+
# Raise RateLimitException for 429 after exhausting retries
|
|
1541
|
+
if e.response.status_code == 429:
|
|
1542
|
+
retry_after = _get_retry_after_delay(e.response)
|
|
1543
|
+
print(
|
|
1544
|
+
f"Rate limit (429) on {method} {path} - max retries exhausted",
|
|
1545
|
+
file=sys.stderr,
|
|
1546
|
+
)
|
|
1547
|
+
raise RateLimitException(
|
|
1548
|
+
f"Rate limit exceeded: {error_message}",
|
|
1549
|
+
retry_after=retry_after,
|
|
1550
|
+
response=e.response,
|
|
1551
|
+
) from None
|
|
1552
|
+
|
|
1333
1553
|
raise HttpClientException(
|
|
1334
1554
|
f"API request failed: {error_message}", response=e.response, cause=e
|
|
1335
1555
|
) from None
|
|
@@ -1339,7 +1559,7 @@ class PDFDancer:
|
|
|
1339
1559
|
# Check if this is a retryable error
|
|
1340
1560
|
if _is_retryable_error(e) and attempt < self._max_retries:
|
|
1341
1561
|
# Calculate exponential backoff delay
|
|
1342
|
-
delay = self._retry_backoff_factor * (2
|
|
1562
|
+
delay = self._retry_backoff_factor * (2**attempt)
|
|
1343
1563
|
if DEBUG:
|
|
1344
1564
|
print(
|
|
1345
1565
|
f"{time.time()}|{method} {path} - Retryable error: {str(e)}, "
|
{pdfdancer_client_python-0.2.22.dist-info → pdfdancer_client_python-0.2.24.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdfdancer-client-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.24
|
|
4
4
|
Summary: Python client for PDFDancer API
|
|
5
5
|
Author-email: "The Famous Cat Ltd." <hi@thefamouscat.com>
|
|
6
6
|
License:
|
|
@@ -207,7 +207,9 @@ License:
|
|
|
207
207
|
limitations under the License.
|
|
208
208
|
|
|
209
209
|
Project-URL: Homepage, https://www.pdfdancer.com/
|
|
210
|
-
Project-URL:
|
|
210
|
+
Project-URL: Documentation, https://www.pdfdancer.com/
|
|
211
|
+
Project-URL: Source, https://github.com/MenschMachine/pdfdancer-client-python
|
|
212
|
+
Project-URL: Issues, https://github.com/MenschMachine/pdfdancer-client-python/issues
|
|
211
213
|
Classifier: Development Status :: 4 - Beta
|
|
212
214
|
Classifier: Intended Audience :: Developers
|
|
213
215
|
Classifier: License :: OSI Approved :: Apache Software License
|
{pdfdancer_client_python-0.2.22.dist-info → pdfdancer_client_python-0.2.24.dist-info}/RECORD
RENAMED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
pdfdancer/__init__.py,sha256=
|
|
2
|
-
pdfdancer/exceptions.py,sha256=
|
|
1
|
+
pdfdancer/__init__.py,sha256=8E5dsMRws_zZZpM0i8k3zc34wHauCFZOuVa5BkumuBE,2357
|
|
2
|
+
pdfdancer/exceptions.py,sha256=mJacmUJTPGUsB8Bo_FgfeXYkvZkH5OPJCVBfilBVmQo,2058
|
|
3
3
|
pdfdancer/fingerprint.py,sha256=eL3PHPgv-knMya7s95RXg3qzzpkAA1aevxqb6tuOb34,3061
|
|
4
4
|
pdfdancer/image_builder.py,sha256=MdSvZYU7-tq5HcuIpj2cfsd1iJKL9nryp87pCGlMnpM,1888
|
|
5
5
|
pdfdancer/models.py,sha256=feEZc7kr5aN2QgIHpayCWksPLhxJVnukkFfe_Ri1E_w,49003
|
|
6
6
|
pdfdancer/page_builder.py,sha256=ecEK0lXk-7CoWEDOftZ-GwgRfNu5h7AD_J__LNkxJwI,3092
|
|
7
7
|
pdfdancer/paragraph_builder.py,sha256=yXdn2hoxpJYcUVAmSEOgoq-ApGIe9k9GWkokIIQWIJA,20489
|
|
8
8
|
pdfdancer/path_builder.py,sha256=2w0LPJo1u8bSjGAbYevTtOF4VD8M9B4SLe_wSLIYJx8,23917
|
|
9
|
-
pdfdancer/pdfdancer_v1.py,sha256=
|
|
9
|
+
pdfdancer/pdfdancer_v1.py,sha256=pjNwBHF_tPCXv7fnjxcm-6m5g_u91773rtvz9j_W9Yg,118088
|
|
10
10
|
pdfdancer/types.py,sha256=9F5LqgNVz48s0Q6lGgOcIfIbvE3vkPI_Vr4fOQqFk2k,13225
|
|
11
|
-
pdfdancer_client_python-0.2.
|
|
12
|
-
pdfdancer_client_python-0.2.
|
|
13
|
-
pdfdancer_client_python-0.2.
|
|
14
|
-
pdfdancer_client_python-0.2.
|
|
15
|
-
pdfdancer_client_python-0.2.
|
|
16
|
-
pdfdancer_client_python-0.2.
|
|
11
|
+
pdfdancer_client_python-0.2.24.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
12
|
+
pdfdancer_client_python-0.2.24.dist-info/licenses/NOTICE,sha256=xaC4l-IChAmtViNDie8ZWzUk0O6XRMyzOl0zLmVZ2HE,232
|
|
13
|
+
pdfdancer_client_python-0.2.24.dist-info/METADATA,sha256=_uluVSNqy7WP2TZQhTj97nYRtDcOmxElytYGocW71wQ,24804
|
|
14
|
+
pdfdancer_client_python-0.2.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
pdfdancer_client_python-0.2.24.dist-info/top_level.txt,sha256=ICwSVRpcCKrdBF9QlaX9Y0e_N3Nk1p7QVxadGOnbxeY,10
|
|
16
|
+
pdfdancer_client_python-0.2.24.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pdfdancer_client_python-0.2.22.dist-info → pdfdancer_client_python-0.2.24.dist-info}/top_level.txt
RENAMED
|
File without changes
|