pdfgate 0.0.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.
- pdfgate/__about__.py +6 -0
- pdfgate/__init__.py +81 -0
- pdfgate/config.py +18 -0
- pdfgate/dict_keys_converter.py +54 -0
- pdfgate/errors.py +9 -0
- pdfgate/http_client.py +76 -0
- pdfgate/params.py +308 -0
- pdfgate/pdfgate.py +370 -0
- pdfgate/request_builder.py +252 -0
- pdfgate/response_builder.py +23 -0
- pdfgate/responses.py +37 -0
- pdfgate/url_builder.py +81 -0
- pdfgate-0.0.2.dist-info/METADATA +264 -0
- pdfgate-0.0.2.dist-info/RECORD +16 -0
- pdfgate-0.0.2.dist-info/WHEEL +4 -0
- pdfgate-0.0.2.dist-info/licenses/LICENSE +21 -0
pdfgate/pdfgate.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
from typing import Any, Union, cast
|
|
2
|
+
|
|
3
|
+
from pdfgate.http_client import PDFGateHTTPClientAsync, PDFGateHTTPClientSync
|
|
4
|
+
from pdfgate.request_builder import RequestBuilder, get_domain_from_api_key
|
|
5
|
+
from pdfgate.response_builder import ResponseBuilder
|
|
6
|
+
|
|
7
|
+
from .errors import ParamsValidationError
|
|
8
|
+
from .params import (
|
|
9
|
+
CompressPDFParams,
|
|
10
|
+
ExtractPDFFormDataParams,
|
|
11
|
+
FlattenPDFParams,
|
|
12
|
+
GeneratePDFParams,
|
|
13
|
+
GetDocumentParams,
|
|
14
|
+
GetFileParams,
|
|
15
|
+
ProtectPDFParams,
|
|
16
|
+
WatermarkPDFParams,
|
|
17
|
+
)
|
|
18
|
+
from .responses import PDFGateDocument
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PDFGate:
|
|
22
|
+
"""Client for the PDFGate API.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
api_key (str):
|
|
26
|
+
API key used for authentication.
|
|
27
|
+
domain (str):
|
|
28
|
+
Base API domain derived from the API key.
|
|
29
|
+
request_builder (RequestBuilder):
|
|
30
|
+
Builds HTTP requests for the API from parameter objects.
|
|
31
|
+
sync_client (PDFGateHTTPClientSync):
|
|
32
|
+
Synchronous HTTP client that handles HTTP errors.
|
|
33
|
+
async_client (PDFGateHTTPClientAsync):
|
|
34
|
+
Asynchronous HTTP client that handles HTTP errors.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, api_key: str):
|
|
38
|
+
"""Initialize a PDFGate client.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
api_key: The API key to use. Must start with 'live_' or 'test_'.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
PDFGateError: If the API key is invalid.
|
|
45
|
+
"""
|
|
46
|
+
self.domain = get_domain_from_api_key(api_key)
|
|
47
|
+
self.api_key = api_key
|
|
48
|
+
self.request_builder = RequestBuilder(api_key=api_key)
|
|
49
|
+
self.sync_client = PDFGateHTTPClientSync(api_key=api_key)
|
|
50
|
+
self.async_client = PDFGateHTTPClientAsync(api_key=api_key)
|
|
51
|
+
|
|
52
|
+
def get_document(self, params: GetDocumentParams) -> PDFGateDocument:
|
|
53
|
+
"""Retrieve a stored document’s metadata (and optionally a fresh pre-signed download URL).
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
A `PDFGateDocument` parsed from the JSON response.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
PDFGateError: If the HTTP request fails (including non-2xx responses) or if
|
|
60
|
+
there is a network/connection error. The error includes status code
|
|
61
|
+
and message when available.
|
|
62
|
+
"""
|
|
63
|
+
request = self.request_builder.build_get_document(params)
|
|
64
|
+
response = self.sync_client.try_make_request(request)
|
|
65
|
+
result = cast(
|
|
66
|
+
PDFGateDocument, ResponseBuilder.build_response(response, json=True)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
async def get_document_async(self, params: GetDocumentParams) -> PDFGateDocument:
|
|
72
|
+
"""Retrieve a stored document’s metadata (and optionally a fresh pre-signed download URL).
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
A `PDFGateDocument` parsed from the JSON response.
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
PDFGateError: If the HTTP request fails (including non-2xx responses) or if
|
|
79
|
+
there is a network/connection error. The error includes status code
|
|
80
|
+
and message when available.
|
|
81
|
+
"""
|
|
82
|
+
request = self.request_builder.build_get_document(params)
|
|
83
|
+
response = await self.async_client.try_make_request_async(request)
|
|
84
|
+
result = cast(
|
|
85
|
+
PDFGateDocument, ResponseBuilder.build_response(response, json=True)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
def get_file(self, params: GetFileParams) -> bytes:
|
|
91
|
+
"""Download a raw PDF file by its document ID.
|
|
92
|
+
|
|
93
|
+
Important: Accessing stored generated files requires enabling
|
|
94
|
+
“Save files” in the PDFGate Dashboard settings (disabled by default).
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The raw bytes of the file.
|
|
98
|
+
"""
|
|
99
|
+
request = self.request_builder.build_get_file(params.document_id)
|
|
100
|
+
response = self.sync_client.try_make_request(request=request)
|
|
101
|
+
|
|
102
|
+
return response.content
|
|
103
|
+
|
|
104
|
+
async def get_file_async(self, params: GetFileParams) -> bytes:
|
|
105
|
+
"""Download a raw PDF file by its document ID.
|
|
106
|
+
|
|
107
|
+
Important: Accessing stored generated files requires enabling
|
|
108
|
+
“Save files” in the PDFGate Dashboard settings (disabled by default).
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
The raw bytes of the file.
|
|
112
|
+
"""
|
|
113
|
+
request = self.request_builder.build_get_file(params.document_id)
|
|
114
|
+
response = await self.async_client.try_make_request_async(request=request)
|
|
115
|
+
|
|
116
|
+
return response.content
|
|
117
|
+
|
|
118
|
+
def generate_pdf(self, params: GeneratePDFParams) -> Union[bytes, PDFGateDocument]:
|
|
119
|
+
"""Generate a PDF from a URL or raw HTML.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Either the raw PDF bytes or a `PDFGateDocument` parsed from the JSON response.
|
|
123
|
+
"""
|
|
124
|
+
if not params.html and not params.url:
|
|
125
|
+
raise ParamsValidationError(
|
|
126
|
+
"Either the 'html' or 'url' parameters must be provided to generate a PDF."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
request = self.request_builder.build_generate_pdf(params)
|
|
130
|
+
response = self.sync_client.try_make_request(request)
|
|
131
|
+
result = ResponseBuilder.build_response(response, json=params.json_response)
|
|
132
|
+
|
|
133
|
+
return result
|
|
134
|
+
|
|
135
|
+
async def generate_pdf_async(
|
|
136
|
+
self, params: GeneratePDFParams
|
|
137
|
+
) -> Union[bytes, PDFGateDocument]:
|
|
138
|
+
"""Generate a PDF from a URL or raw HTML.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Either the raw PDF bytes or a `PDFGateDocument` parsed from the JSON response.
|
|
142
|
+
"""
|
|
143
|
+
if not params.html and not params.url:
|
|
144
|
+
raise ParamsValidationError(
|
|
145
|
+
"Either the 'html' or 'url' parameters must be provided to generate a PDF."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
request = self.request_builder.build_generate_pdf(params)
|
|
149
|
+
response = await self.async_client.try_make_request_async(request)
|
|
150
|
+
result = ResponseBuilder.build_response(response, json=params.json_response)
|
|
151
|
+
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
def flatten_pdf(self, params: FlattenPDFParams) -> Union[bytes, PDFGateDocument]:
|
|
155
|
+
"""Flatten an interactive PDF into a static, non-editable PDF.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
params: Either a `FlattenPDFByDocumentIdParams` or
|
|
159
|
+
`FlattenPDFBinaryParams` instance depending on whether the file
|
|
160
|
+
is provided as a document ID or raw binary data.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Depending on the `json_response` flag in `params`, this method either
|
|
164
|
+
returns the raw PDF bytes or a `PDFGateDocument` instance.
|
|
165
|
+
"""
|
|
166
|
+
request = self.request_builder.build_flatten_pdf(params)
|
|
167
|
+
response = self.sync_client.try_make_request(request)
|
|
168
|
+
result = ResponseBuilder.build_response(response, json=params.json_response)
|
|
169
|
+
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
async def flatten_pdf_async(
|
|
173
|
+
self, params: FlattenPDFParams
|
|
174
|
+
) -> Union[bytes, PDFGateDocument]:
|
|
175
|
+
"""Flatten an interactive PDF into a static, non-editable PDF.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
params: Either a `FlattenPDFByDocumentIdParams` or
|
|
179
|
+
`FlattenPDFBinaryParams` instance depending on whether the file
|
|
180
|
+
is provided as a document ID or raw binary data.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Depending on the `json_response` flag in `params`, this method either
|
|
184
|
+
returns the raw PDF bytes or a `PDFGateDocument` instance.
|
|
185
|
+
"""
|
|
186
|
+
request = self.request_builder.build_flatten_pdf(params)
|
|
187
|
+
response = await self.async_client.try_make_request_async(request)
|
|
188
|
+
result = ResponseBuilder.build_response(response, json=params.json_response)
|
|
189
|
+
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
def extract_pdf_form_data(self, params: ExtractPDFFormDataParams) -> Any:
|
|
193
|
+
"""Extract form field data from a fillable PDF and return it as JSON.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
params: Either a `ExtractPDFFormDataByDocumentIdParams` or
|
|
197
|
+
`ExtractPDFFormDataBinaryParams` instance depending on whether the file
|
|
198
|
+
is provided as a document ID or raw binary data.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
JSON object mapping form field names to their values.
|
|
202
|
+
"""
|
|
203
|
+
request = self.request_builder.build_extract_pdf_form_data(params)
|
|
204
|
+
response = self.sync_client.try_make_request(request)
|
|
205
|
+
result = ResponseBuilder.build_response(response, json=True)
|
|
206
|
+
|
|
207
|
+
return result
|
|
208
|
+
|
|
209
|
+
async def extract_pdf_form_data_async(
|
|
210
|
+
self, params: ExtractPDFFormDataParams
|
|
211
|
+
) -> Any:
|
|
212
|
+
"""Extract form field data from a fillable PDF and return it as JSON.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
params: Either a `ExtractPDFFormDataByDocumentIdParams` or
|
|
216
|
+
`ExtractPDFFormDataBinaryParams` instance depending on whether the file
|
|
217
|
+
is provided as a document ID or raw binary data.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
JSON object mapping form field names to their values.
|
|
221
|
+
"""
|
|
222
|
+
request = self.request_builder.build_extract_pdf_form_data(params)
|
|
223
|
+
response = await self.async_client.try_make_request_async(request)
|
|
224
|
+
result = ResponseBuilder.build_response(response, json=True)
|
|
225
|
+
|
|
226
|
+
return result
|
|
227
|
+
|
|
228
|
+
def protect_pdf(self, params: ProtectPDFParams) -> Union[bytes, PDFGateDocument]:
|
|
229
|
+
"""Protect a PDF using encryption + optional permission restrictions.
|
|
230
|
+
|
|
231
|
+
Security options highlights:
|
|
232
|
+
- `algorithm`: `"AES256"` (default) or `"AES128"`.
|
|
233
|
+
- `user_password`: password required to open the PDF (optional).
|
|
234
|
+
- `owner_password`: full control password; required in some cases (e.g., AES256 with `user_password`).
|
|
235
|
+
- Restrictions: `disable_print`, `disable_copy`, `disable_editing`.
|
|
236
|
+
- `encrypt_metadata`: whether PDF metadata is encrypted (default `False`).
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
params: Either a `ProtectPDFByDocumentIdParams` or
|
|
240
|
+
`ProtectPDFBinaryParams` instance depending on whether the file
|
|
241
|
+
is provided as a document ID or raw binary data.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Depending on the `json_response` flag in `params`, this method either
|
|
245
|
+
returns the raw PDF bytes or a `PDFGateDocument` instance.
|
|
246
|
+
"""
|
|
247
|
+
request = self.request_builder.build_protect_pdf(params)
|
|
248
|
+
response = self.sync_client.try_make_request(request)
|
|
249
|
+
result = ResponseBuilder.build_response(response, json=params.json_response)
|
|
250
|
+
|
|
251
|
+
return result
|
|
252
|
+
|
|
253
|
+
async def protect_pdf_async(
|
|
254
|
+
self, params: ProtectPDFParams
|
|
255
|
+
) -> Union[bytes, PDFGateDocument]:
|
|
256
|
+
"""Protect a PDF using encryption + optional permission restrictions.
|
|
257
|
+
|
|
258
|
+
Security options highlights:
|
|
259
|
+
- `algorithm`: `"AES256"` (default) or `"AES128"`.
|
|
260
|
+
- `user_password`: password required to open the PDF (optional).
|
|
261
|
+
- `owner_password`: full control password; required in some cases (e.g., AES256 with `user_password`).
|
|
262
|
+
- Restrictions: `disable_print`, `disable_copy`, `disable_editing`.
|
|
263
|
+
- `encrypt_metadata`: whether PDF metadata is encrypted (default `False`).
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
params: Either a `ProtectPDFByDocumentIdParams` or
|
|
267
|
+
`ProtectPDFBinaryParams` instance depending on whether the file
|
|
268
|
+
is provided as a document ID or raw binary data.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Depending on the `json_response` flag in `params`, this method either
|
|
272
|
+
returns the raw PDF bytes or a `PDFGateDocument` instance.
|
|
273
|
+
"""
|
|
274
|
+
request = self.request_builder.build_protect_pdf(params)
|
|
275
|
+
response = await self.async_client.try_make_request_async(request)
|
|
276
|
+
result = ResponseBuilder.build_response(response, json=params.json_response)
|
|
277
|
+
|
|
278
|
+
return result
|
|
279
|
+
|
|
280
|
+
def compress_pdf(self, params: CompressPDFParams) -> Union[bytes, PDFGateDocument]:
|
|
281
|
+
"""Compress a PDF document to reduce its size without changing its visual content.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
params: Either a `CompressPDFByDocumentIdParams` or
|
|
285
|
+
`CompressPDFBinaryParams` instance depending on whether the file
|
|
286
|
+
is provided as a document ID or raw binary data.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Depending on the `json_response` flag in `params`, this method either
|
|
290
|
+
returns the raw PDF bytes or a `PDFGateDocument` instance.
|
|
291
|
+
"""
|
|
292
|
+
request = self.request_builder.build_compress_pdf(params)
|
|
293
|
+
response = self.sync_client.try_make_request(request)
|
|
294
|
+
result = ResponseBuilder.build_response(response, json=params.json_response)
|
|
295
|
+
|
|
296
|
+
return result
|
|
297
|
+
|
|
298
|
+
async def compress_pdf_async(
|
|
299
|
+
self, params: CompressPDFParams
|
|
300
|
+
) -> Union[bytes, PDFGateDocument]:
|
|
301
|
+
"""Compress a PDF document to reduce its size without changing its visual content.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
params: Either a `CompressPDFByDocumentIdParams` or
|
|
305
|
+
`CompressPDFBinaryParams` instance depending on whether the file
|
|
306
|
+
is provided as a document ID or raw binary data.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Depending on the `json_response` flag in `params`, this method either
|
|
310
|
+
returns the raw PDF bytes or a `PDFGateDocument` instance.
|
|
311
|
+
"""
|
|
312
|
+
request = self.request_builder.build_compress_pdf(params)
|
|
313
|
+
response = await self.async_client.try_make_request_async(request)
|
|
314
|
+
result = ResponseBuilder.build_response(response, json=params.json_response)
|
|
315
|
+
|
|
316
|
+
return result
|
|
317
|
+
|
|
318
|
+
def watermark_pdf(self, params: WatermarkPDFParams) -> Any:
|
|
319
|
+
"""Apply a text or image watermark to a PDF.
|
|
320
|
+
|
|
321
|
+
Watermark configuration highlights:
|
|
322
|
+
- `type` is required: `"text"` or `"image"`.
|
|
323
|
+
- For text watermarks: `text` required when `type="text"`.
|
|
324
|
+
- For image watermarks: upload `watermark` image file (`.png`, `.jpg`, `.jpeg`).
|
|
325
|
+
- Optional: `font` (standard PDF fonts), `fontFile`
|
|
326
|
+
(`.ttf`/`.otf` overrides `font`), `fontSize`, `fontColor`,
|
|
327
|
+
`opacity` (0..1), `xPosition`, `yPosition`, `imageWidth`, `imageHeight`,
|
|
328
|
+
`rotate` (0..360).
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
params: Either a `WatermarkPDFByDocumentIdParams` or
|
|
332
|
+
`WatermarkPDFByFileParams` instance depending on whether the file
|
|
333
|
+
is provided as a document ID or raw binary data.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Depending on the `json_response` flag in `params`, this method either
|
|
337
|
+
returns the raw PDF bytes or a `PDFGateDocument` instance.
|
|
338
|
+
"""
|
|
339
|
+
request = self.request_builder.build_watermark_pdf(params)
|
|
340
|
+
response = self.sync_client.try_make_request(request)
|
|
341
|
+
result = ResponseBuilder.build_response(response, json=params.json_response)
|
|
342
|
+
|
|
343
|
+
return result
|
|
344
|
+
|
|
345
|
+
async def watermark_pdf_async(self, params: WatermarkPDFParams) -> Any:
|
|
346
|
+
"""Apply a text or image watermark to a PDF.
|
|
347
|
+
|
|
348
|
+
Watermark configuration highlights:
|
|
349
|
+
- `type` is required: `"text"` or `"image"`.
|
|
350
|
+
- For text watermarks: `text` required when `type="text"`.
|
|
351
|
+
- For image watermarks: upload `watermark` image file (`.png`, `.jpg`, `.jpeg`).
|
|
352
|
+
- Optional: `font` (standard PDF fonts), `fontFile`
|
|
353
|
+
(`.ttf`/`.otf` overrides `font`), `fontSize`, `fontColor`,
|
|
354
|
+
`opacity` (0..1), `xPosition`, `yPosition`, `imageWidth`, `imageHeight`,
|
|
355
|
+
`rotate` (0..360).
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
params: Either a `WatermarkPDFByDocumentIdParams` or
|
|
359
|
+
`WatermarkPDFByFileParams` instance depending on whether the file
|
|
360
|
+
is provided as a document ID or raw binary data.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
Depending on the `json_response` flag in `params`, this method either
|
|
364
|
+
returns the raw PDF bytes or a `PDFGateDocument` instance.
|
|
365
|
+
"""
|
|
366
|
+
request = self.request_builder.build_watermark_pdf(params)
|
|
367
|
+
response = await self.async_client.try_make_request_async(request)
|
|
368
|
+
result = ResponseBuilder.build_response(response, json=params.json_response)
|
|
369
|
+
|
|
370
|
+
return result
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""Request construction utilities for PDFGate API calls."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
from enum import Enum
|
|
6
|
+
import mimetypes
|
|
7
|
+
from typing import Any
|
|
8
|
+
import httpx
|
|
9
|
+
from pdfgate.config import Config
|
|
10
|
+
from pdfgate.dict_keys_converter import snake_to_camel
|
|
11
|
+
from pdfgate.errors import PDFGateError
|
|
12
|
+
from pdfgate.params import (
|
|
13
|
+
CompressPDFByDocumentIdParams,
|
|
14
|
+
CompressPDFParams,
|
|
15
|
+
ExtractPDFFormDataByDocumentIdParams,
|
|
16
|
+
ExtractPDFFormDataParams,
|
|
17
|
+
FileParam,
|
|
18
|
+
FlattenPDFByFileParams,
|
|
19
|
+
FlattenPDFParams,
|
|
20
|
+
GeneratePDFParams,
|
|
21
|
+
GetDocumentParams,
|
|
22
|
+
PDFGateParams,
|
|
23
|
+
ProtectPDFByDocumentIdParams,
|
|
24
|
+
ProtectPDFParams,
|
|
25
|
+
WatermarkPDFByDocumentIdParams,
|
|
26
|
+
WatermarkPDFParams,
|
|
27
|
+
WatermarkType,
|
|
28
|
+
)
|
|
29
|
+
from pdfgate.url_builder import URLBuilder
|
|
30
|
+
|
|
31
|
+
FormDataFileParam = tuple[str, bytes, str]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_domain_from_api_key(api_key: str) -> str:
|
|
35
|
+
"""Return the API domain corresponding to an API key.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
api_key:
|
|
39
|
+
API key string that should start with 'live_' or 'test_'.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
The domain (URL) for the API (production or sandbox).
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
PDFGateError: If the API key format is invalid.
|
|
46
|
+
"""
|
|
47
|
+
if api_key.startswith("live_"):
|
|
48
|
+
return Config.PRODUCTION_API_DOMAIN
|
|
49
|
+
elif api_key.startswith("test_"):
|
|
50
|
+
return Config.SANDBOX_API_DOMAIN
|
|
51
|
+
else:
|
|
52
|
+
raise PDFGateError(
|
|
53
|
+
"Invalid API key format. Expected to start with 'live_' or 'test_'."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def pdfgate_params_to_params_dict(instance: PDFGateParams) -> dict[str, Any]:
|
|
58
|
+
"""Convert a params dataclass into a JSON-ready dict."""
|
|
59
|
+
params_dict = asdict(instance)
|
|
60
|
+
params_without_nulls: dict[str, Any] = {}
|
|
61
|
+
for k, v in params_dict.items():
|
|
62
|
+
if v is not None:
|
|
63
|
+
params_without_nulls[snake_to_camel(k)] = (
|
|
64
|
+
v.value if isinstance(v, Enum) else v
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return params_without_nulls
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class PDFGateRequest:
|
|
72
|
+
"""Container for an HTTP request and its timeout."""
|
|
73
|
+
|
|
74
|
+
request: httpx.Request
|
|
75
|
+
timeout: int = int(
|
|
76
|
+
timedelta(seconds=Config.DEFAULT_TIMEOUT_SECONDS).total_seconds()
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class RequestBuilder:
|
|
81
|
+
"""Build HTTP requests for PDFGate endpoints."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, api_key: str):
|
|
84
|
+
domain = get_domain_from_api_key(api_key)
|
|
85
|
+
self.url_builder = URLBuilder(domain)
|
|
86
|
+
self.api_key = api_key
|
|
87
|
+
|
|
88
|
+
def get_headers(self) -> dict[str, str]:
|
|
89
|
+
"""Return the authorization header for API calls."""
|
|
90
|
+
return {"Authorization": f"Bearer {self.api_key}"}
|
|
91
|
+
|
|
92
|
+
def _get_request(self, url: str, params: dict[str, Any] = {}) -> httpx.Request:
|
|
93
|
+
"""Create a GET request with standard headers."""
|
|
94
|
+
return httpx.Request("GET", url=url, headers=self.get_headers(), params=params)
|
|
95
|
+
|
|
96
|
+
def _json_post_request(self, url: str, json: dict[str, Any]) -> httpx.Request:
|
|
97
|
+
"""Create a JSON POST request with standard headers."""
|
|
98
|
+
return httpx.Request("POST", url=url, headers=self.get_headers(), json=json)
|
|
99
|
+
|
|
100
|
+
def _multipart_post_request(
|
|
101
|
+
self, url: str, data: dict[str, Any] = {}, files: dict[str, Any] = {}
|
|
102
|
+
) -> httpx.Request:
|
|
103
|
+
"""Create a multipart POST request with standard headers."""
|
|
104
|
+
return httpx.Request(
|
|
105
|
+
"POST", url=url, headers=self.get_headers(), data=data, files=files
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _build_file_param(
|
|
109
|
+
self, file_param: FileParam, key: str
|
|
110
|
+
) -> dict[str, FormDataFileParam]:
|
|
111
|
+
"""Build a file parameter for multipart requests."""
|
|
112
|
+
if file_param.type is None:
|
|
113
|
+
mime_type, _encoding = mimetypes.guess_type(file_param.name)
|
|
114
|
+
mime_type = mime_type or "application/octet-stream"
|
|
115
|
+
else:
|
|
116
|
+
mime_type = file_param.type or "application/octet-stream"
|
|
117
|
+
|
|
118
|
+
return {key: (file_param.name, file_param.data, mime_type)}
|
|
119
|
+
|
|
120
|
+
def build_get_file(self, document_id: str) -> PDFGateRequest:
|
|
121
|
+
"""Build a request to download a document's file content."""
|
|
122
|
+
url = self.url_builder.get_file_url(document_id)
|
|
123
|
+
request = self._get_request(url)
|
|
124
|
+
|
|
125
|
+
return PDFGateRequest(request=request)
|
|
126
|
+
|
|
127
|
+
def build_get_document(self, params: GetDocumentParams) -> PDFGateRequest:
|
|
128
|
+
"""Build a request to fetch a document's metadata."""
|
|
129
|
+
params_dict: dict[str, int] = {}
|
|
130
|
+
if params.pre_signed_url_expires_in is not None:
|
|
131
|
+
params_dict["preSignedUrlExpiresIn"] = params.pre_signed_url_expires_in
|
|
132
|
+
|
|
133
|
+
url = self.url_builder.get_document_url(params.document_id)
|
|
134
|
+
request = self._get_request(url, params_dict)
|
|
135
|
+
|
|
136
|
+
return PDFGateRequest(request=request)
|
|
137
|
+
|
|
138
|
+
def build_generate_pdf(self, params: GeneratePDFParams) -> PDFGateRequest:
|
|
139
|
+
"""Build a request to generate a PDF from HTML or URL."""
|
|
140
|
+
url = self.url_builder.generate_pdf_url()
|
|
141
|
+
params_without_nulls = pdfgate_params_to_params_dict(params)
|
|
142
|
+
request = self._json_post_request(url, json=params_without_nulls)
|
|
143
|
+
timeout = int(
|
|
144
|
+
timedelta(minutes=Config.GENERATE_PDF_TIMEOUT_MINUTES).total_seconds()
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return PDFGateRequest(request=request, timeout=timeout)
|
|
148
|
+
|
|
149
|
+
def build_flatten_pdf(self, params: FlattenPDFParams) -> PDFGateRequest:
|
|
150
|
+
"""Build a request to flatten a PDF from file or document ID."""
|
|
151
|
+
url = self.url_builder.flatten_pdf_url()
|
|
152
|
+
params_without_nulls = pdfgate_params_to_params_dict(params)
|
|
153
|
+
if isinstance(params, FlattenPDFByFileParams) and params.file is not None:
|
|
154
|
+
files = self._build_file_param(params_without_nulls.pop("file"), "file")
|
|
155
|
+
else:
|
|
156
|
+
files = {}
|
|
157
|
+
|
|
158
|
+
request = self._multipart_post_request(
|
|
159
|
+
url, data=params_without_nulls, files=files
|
|
160
|
+
)
|
|
161
|
+
timeout = int(
|
|
162
|
+
timedelta(minutes=Config.FLATTEN_PDF_TIMEOUT_MINUTES).total_seconds()
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return PDFGateRequest(request=request, timeout=timeout)
|
|
166
|
+
|
|
167
|
+
def build_extract_pdf_form_data(
|
|
168
|
+
self, params: ExtractPDFFormDataParams
|
|
169
|
+
) -> PDFGateRequest:
|
|
170
|
+
"""Build a request to extract PDF form data."""
|
|
171
|
+
url = self.url_builder.extract_pdf_form_data_url()
|
|
172
|
+
if isinstance(params, ExtractPDFFormDataByDocumentIdParams):
|
|
173
|
+
params_dict = {"documentId": params.document_id}
|
|
174
|
+
request = self._multipart_post_request(url, data=params_dict)
|
|
175
|
+
else:
|
|
176
|
+
files = self._build_file_param(params.file, "file")
|
|
177
|
+
request = self._multipart_post_request(url, files=files)
|
|
178
|
+
|
|
179
|
+
return PDFGateRequest(request=request)
|
|
180
|
+
|
|
181
|
+
def build_protect_pdf(self, params: ProtectPDFParams) -> PDFGateRequest:
|
|
182
|
+
"""Build a request to encrypt a PDF from file or document ID."""
|
|
183
|
+
url = self.url_builder.protect_pdf_url()
|
|
184
|
+
params_without_nulls = pdfgate_params_to_params_dict(params)
|
|
185
|
+
if isinstance(params, ProtectPDFByDocumentIdParams):
|
|
186
|
+
request = self._multipart_post_request(url=url, data=params_without_nulls)
|
|
187
|
+
else:
|
|
188
|
+
file_param = {"file": params_without_nulls.pop("file", None)}
|
|
189
|
+
request = self._multipart_post_request(
|
|
190
|
+
url=url,
|
|
191
|
+
data=params_without_nulls,
|
|
192
|
+
files=file_param,
|
|
193
|
+
)
|
|
194
|
+
timeout = int(
|
|
195
|
+
timedelta(minutes=Config.PROTECT_PDF_TIMEOUT_MINUTES).total_seconds()
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return PDFGateRequest(request=request, timeout=timeout)
|
|
199
|
+
|
|
200
|
+
def build_compress_pdf(self, params: CompressPDFParams) -> PDFGateRequest:
|
|
201
|
+
"""Build a request to compress a PDF from file or document ID."""
|
|
202
|
+
url = self.url_builder.compress_pdf_url()
|
|
203
|
+
params_without_nulls = pdfgate_params_to_params_dict(params)
|
|
204
|
+
if isinstance(params, CompressPDFByDocumentIdParams):
|
|
205
|
+
request = self._multipart_post_request(url=url, data=params_without_nulls)
|
|
206
|
+
else:
|
|
207
|
+
file_param = {"file": params_without_nulls.pop("file", None)}
|
|
208
|
+
request = self._multipart_post_request(
|
|
209
|
+
url=url,
|
|
210
|
+
data=params_without_nulls,
|
|
211
|
+
files=file_param,
|
|
212
|
+
)
|
|
213
|
+
timeout = int(
|
|
214
|
+
timedelta(minutes=Config.COMPRESS_PDF_TIMEOUT_MINUTES).total_seconds()
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return PDFGateRequest(request=request, timeout=timeout)
|
|
218
|
+
|
|
219
|
+
def build_watermark_pdf(self, params: WatermarkPDFParams) -> PDFGateRequest:
|
|
220
|
+
"""Build a request to watermark a PDF from file or document ID."""
|
|
221
|
+
url = self.url_builder.watermark_pdf_url()
|
|
222
|
+
params_dict = pdfgate_params_to_params_dict(params)
|
|
223
|
+
if isinstance(params, WatermarkPDFByDocumentIdParams):
|
|
224
|
+
if params.type == WatermarkType.TEXT:
|
|
225
|
+
request = self._multipart_post_request(url=url, data=params_dict)
|
|
226
|
+
else:
|
|
227
|
+
watermark_file_param = self._build_file_param(
|
|
228
|
+
params_dict.pop("watermark"), "watermark"
|
|
229
|
+
)
|
|
230
|
+
request = self._multipart_post_request(
|
|
231
|
+
url=url,
|
|
232
|
+
data=params_dict,
|
|
233
|
+
files=watermark_file_param,
|
|
234
|
+
)
|
|
235
|
+
else:
|
|
236
|
+
if params.type == WatermarkType.IMAGE:
|
|
237
|
+
watermark_file_param = self._build_file_param(
|
|
238
|
+
params_dict.pop("watermark"), "watermark"
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
watermark_file_param = {}
|
|
242
|
+
file_param = self._build_file_param(params_dict.pop("file"), "file")
|
|
243
|
+
request = self._multipart_post_request(
|
|
244
|
+
url=url,
|
|
245
|
+
data=params_dict,
|
|
246
|
+
files={**file_param, **watermark_file_param},
|
|
247
|
+
)
|
|
248
|
+
timeout = int(
|
|
249
|
+
timedelta(minutes=Config.WATERMARK_PDF_TIMEOUT_MINUTES).total_seconds()
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return PDFGateRequest(request=request, timeout=timeout)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Helpers for converting HTTP responses into SDK return types."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Union, cast
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from pdfgate.dict_keys_converter import convert_camel_keys_to_snake
|
|
8
|
+
from pdfgate.responses import PDFGateDocument
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ResponseBuilder:
|
|
12
|
+
"""Build SDK responses from HTTP responses."""
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def build_response(
|
|
16
|
+
response: httpx.Response, json: Optional[bool] = False
|
|
17
|
+
) -> Union[bytes, PDFGateDocument]:
|
|
18
|
+
"""Convert an HTTP response into raw bytes or a document object."""
|
|
19
|
+
if json:
|
|
20
|
+
json_response = response.json()
|
|
21
|
+
return cast(PDFGateDocument, convert_camel_keys_to_snake(json_response))
|
|
22
|
+
|
|
23
|
+
return response.content
|
pdfgate/responses.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Response models for PDFGate API results."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any, Optional, TypedDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DocumentStatus(Enum):
|
|
9
|
+
COMPLETED = "completed"
|
|
10
|
+
PROCESSING = "processing"
|
|
11
|
+
EXPIRED = "expired"
|
|
12
|
+
FAILED = "failed"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DocumentType(Enum):
|
|
16
|
+
"""Document types reported by the PDFGate API."""
|
|
17
|
+
|
|
18
|
+
FROM_HTML = "from_html"
|
|
19
|
+
FLATTENED = "flattened"
|
|
20
|
+
WATERMARKED = "watermarked"
|
|
21
|
+
ENCRYPTED = "encrypted"
|
|
22
|
+
COMPRESSED = "compressed"
|
|
23
|
+
SIGNED = "signed"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PDFGateDocument(TypedDict, total=False):
|
|
27
|
+
"""Typed dictionary representing a PDFGate document response."""
|
|
28
|
+
|
|
29
|
+
id: str
|
|
30
|
+
status: DocumentStatus
|
|
31
|
+
created_at: datetime
|
|
32
|
+
expires_at: datetime
|
|
33
|
+
type: Optional[DocumentType]
|
|
34
|
+
file_url: Optional[str]
|
|
35
|
+
size: Optional[int]
|
|
36
|
+
metadata: Optional[dict[str, Any]]
|
|
37
|
+
derived_from: Optional[str]
|