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 ADDED
@@ -0,0 +1,6 @@
1
+ """Package metadata for pdfgate."""
2
+
3
+ # SPDX-FileCopyrightText: 2026-present Fernando Gasperi <fgasperijabalera@gmail.com>
4
+ #
5
+ # SPDX-License-Identifier: MIT
6
+ __version__ = "0.0.2"
pdfgate/__init__.py ADDED
@@ -0,0 +1,81 @@
1
+ """Public package initialization for pdfgate."""
2
+
3
+ # SPDX-FileCopyrightText: 2026-present Fernando Gasperi <fgasperijabalera@gmail.com>
4
+ #
5
+ # SPDX-License-Identifier: MIT
6
+
7
+ from .pdfgate import PDFGate
8
+ from .params import (
9
+ PageSizeType,
10
+ FileOrientation,
11
+ EmulateMediaType,
12
+ PdfStandardFont,
13
+ PdfPageMargin,
14
+ ClickSelectorChain,
15
+ ClickSelectorChainSetup,
16
+ PDFGateParams,
17
+ GetDocumentParams,
18
+ GetFileParams,
19
+ GeneratePDFAuthentication,
20
+ Viewport,
21
+ GeneratePDFParams,
22
+ FlattenPDFBaseParams,
23
+ FileParam,
24
+ FlattenPDFByFileParams,
25
+ FlattenPDFByDocumentIdParams,
26
+ ExtractPDFFormDataByDocumentIdParams,
27
+ ExtractPDFFormDataByFileParams,
28
+ EncryptionAlgorithm,
29
+ ProtectPDFBaseParams,
30
+ ProtectPDFByDocumentIdParams,
31
+ ProtectPDFByFileParams,
32
+ CompressPDFBaseParams,
33
+ CompressPDFByDocumentIdParams,
34
+ CompressPDFByFileParams,
35
+ WatermarkType,
36
+ WatermarkPDFBaseParams,
37
+ WatermarkPDFByDocumentIdParams,
38
+ WatermarkPDFByFileParams,
39
+ )
40
+ from .responses import (
41
+ DocumentStatus,
42
+ DocumentType,
43
+ PDFGateDocument,
44
+ )
45
+
46
+ __all__ = [
47
+ "PDFGate",
48
+ "PageSizeType",
49
+ "FileOrientation",
50
+ "EmulateMediaType",
51
+ "PdfStandardFont",
52
+ "PdfPageMargin",
53
+ "ClickSelectorChain",
54
+ "ClickSelectorChainSetup",
55
+ "PDFGateParams",
56
+ "GetDocumentParams",
57
+ "GetFileParams",
58
+ "GeneratePDFAuthentication",
59
+ "Viewport",
60
+ "GeneratePDFParams",
61
+ "FlattenPDFBaseParams",
62
+ "FileParam",
63
+ "FlattenPDFByFileParams",
64
+ "FlattenPDFByDocumentIdParams",
65
+ "ExtractPDFFormDataByDocumentIdParams",
66
+ "ExtractPDFFormDataByFileParams",
67
+ "EncryptionAlgorithm",
68
+ "ProtectPDFBaseParams",
69
+ "ProtectPDFByDocumentIdParams",
70
+ "ProtectPDFByFileParams",
71
+ "CompressPDFBaseParams",
72
+ "CompressPDFByDocumentIdParams",
73
+ "CompressPDFByFileParams",
74
+ "WatermarkType",
75
+ "WatermarkPDFBaseParams",
76
+ "WatermarkPDFByDocumentIdParams",
77
+ "WatermarkPDFByFileParams",
78
+ "DocumentStatus",
79
+ "DocumentType",
80
+ "PDFGateDocument",
81
+ ]
pdfgate/config.py ADDED
@@ -0,0 +1,18 @@
1
+ """Configuration values for API domains and default timeouts."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class Config:
8
+ """Holds constant configuration values for the SDK."""
9
+
10
+ PRODUCTION_API_DOMAIN: str = "https://api.pdfgate.com"
11
+ SANDBOX_API_DOMAIN: str = "https://api-sandbox.pdfgate.com"
12
+
13
+ DEFAULT_TIMEOUT_SECONDS: int = 60
14
+ GENERATE_PDF_TIMEOUT_MINUTES: int = 15
15
+ FLATTEN_PDF_TIMEOUT_MINUTES: int = 3
16
+ COMPRESS_PDF_TIMEOUT_MINUTES: int = 3
17
+ PROTECT_PDF_TIMEOUT_MINUTES: int = 3
18
+ WATERMARK_PDF_TIMEOUT_MINUTES: int = 3
@@ -0,0 +1,54 @@
1
+ """Key conversion helpers for camelCase and snake_case dictionaries."""
2
+
3
+ import re
4
+ from typing import Any, Union
5
+
6
+
7
+ def camel_to_snake(name: str) -> str:
8
+ """Convert a string from camelCase to snake_case."""
9
+ # Inserts an underscore before any uppercase letter that is followed by a
10
+ # lowercase letter or digit.
11
+ # This is needed for cases like: TestHTTPResponse -> TestHTTP_Response
12
+ s1 = re.sub("(.)([A-Z][a-z0-9]+)", r"\1_\2", name)
13
+ # Inserts an underscore before any uppercase letter that is preceded by a lowercase letter or digit
14
+ s2 = re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1)
15
+ return s2.lower()
16
+
17
+
18
+ def convert_camel_keys_to_snake(
19
+ data: Union[dict[Any, Any], list[Any], Any],
20
+ ) -> Union[dict[Any, Any], list[Any], Any]:
21
+ """Recursively converts all keys in a dictionary or list to snake_case."""
22
+ if isinstance(data, dict):
23
+ new_dict = {}
24
+ for key, value in data.items(): # type: ignore
25
+ new_key = camel_to_snake(key) # type: ignore
26
+ new_dict[new_key] = convert_camel_keys_to_snake(value) # type: ignore
27
+ return new_dict # type: ignore
28
+ elif isinstance(data, list):
29
+ return [convert_camel_keys_to_snake(item) for item in data] # type: ignore
30
+ else:
31
+ return data
32
+
33
+
34
+ def snake_to_camel(key: str) -> str:
35
+ """Convert a snake_case string to camelCase."""
36
+ parts = key.split("_")
37
+
38
+ return parts[0] + "".join(word.capitalize() for word in parts[1:])
39
+
40
+
41
+ def convert_snake_keys_to_camel(
42
+ data: Union[dict[Any, Any], list[Any], Any],
43
+ ) -> Union[dict[Any, Any], list[Any], Any]:
44
+ """Recursively converts all keys in a dictionary or list to camelCase."""
45
+ if isinstance(data, dict):
46
+ new_dict = {}
47
+ for key, value in data.items(): # type: ignore
48
+ new_key = snake_to_camel(key) # type: ignore
49
+ new_dict[new_key] = convert_snake_keys_to_camel(value) # type: ignore
50
+ return new_dict # type: ignore
51
+ elif isinstance(data, list):
52
+ return [convert_snake_keys_to_camel(item) for item in data] # type: ignore
53
+ else:
54
+ return data
pdfgate/errors.py ADDED
@@ -0,0 +1,9 @@
1
+ """Custom exception types for the PDFGate SDK."""
2
+
3
+
4
+ class PDFGateError(Exception):
5
+ """Base error for SDK failures."""
6
+
7
+
8
+ class ParamsValidationError(PDFGateError):
9
+ """Raised when required parameters are missing or invalid."""
pdfgate/http_client.py ADDED
@@ -0,0 +1,76 @@
1
+ """HTTP client implementations for synchronous and asynchronous requests."""
2
+
3
+ from typing import NoReturn
4
+ import httpx
5
+
6
+ from pdfgate.errors import PDFGateError
7
+ from pdfgate.request_builder import PDFGateRequest
8
+
9
+
10
+ class PDFGateHTTPClientBase:
11
+ def __init__(self, api_key: str):
12
+ self.api_key = api_key
13
+
14
+ def get_headers(self) -> dict[str, str]:
15
+ """Return the authorization HTTP headers for API requests.
16
+
17
+ Returns:
18
+ A dict of headers including the Authorization bearer token.
19
+ """
20
+ return {"Authorization": f"Bearer {self.api_key}"}
21
+
22
+ def raise_error_from_http_status_error(self, e: httpx.HTTPStatusError) -> NoReturn:
23
+ """Raise a PDFGateError with details from an HTTP status error."""
24
+ status_code = e.response.status_code
25
+ content_type = e.response.headers.get("Content-Type", "")
26
+ message = e.response.text
27
+ if "application/json" in content_type:
28
+ try:
29
+ error_info = e.response.json()
30
+ message = error_info.get("message", e.response.text)
31
+ except ValueError:
32
+ message = e.response.text
33
+
34
+ raise PDFGateError(
35
+ f"HTTP Error: status {status_code} - message: {message}"
36
+ ) from e
37
+
38
+
39
+ class PDFGateHTTPClientSync(PDFGateHTTPClientBase):
40
+ """Synchronous HTTP client for PDFGate API requests."""
41
+
42
+ def __init__(self, api_key: str):
43
+ super().__init__(api_key=api_key)
44
+
45
+ def try_make_request(self, request: PDFGateRequest) -> httpx.Response:
46
+ """Send a request synchronously raising HTTP errors."""
47
+ try:
48
+ with httpx.Client(timeout=request.timeout) as client:
49
+ response = client.send(request.request)
50
+ response.raise_for_status()
51
+ except httpx.HTTPStatusError as e:
52
+ self.raise_error_from_http_status_error(e)
53
+ except httpx.RequestError as e:
54
+ raise PDFGateError(f"Request failed: {e}") from e
55
+
56
+ return response
57
+
58
+
59
+ class PDFGateHTTPClientAsync(PDFGateHTTPClientBase):
60
+ """Asynchronous HTTP client for PDFGate API requests."""
61
+
62
+ def __init__(self, api_key: str):
63
+ super().__init__(api_key=api_key)
64
+
65
+ async def try_make_request_async(self, request: PDFGateRequest) -> httpx.Response:
66
+ """Send a request asynchronously raising HTTP errors."""
67
+ try:
68
+ async with httpx.AsyncClient(timeout=request.timeout) as client:
69
+ response = await client.send(request.request)
70
+ response.raise_for_status()
71
+ except httpx.HTTPStatusError as e:
72
+ self.raise_error_from_http_status_error(e)
73
+ except httpx.RequestError as e:
74
+ raise PDFGateError(f"Request failed: {e}") from e
75
+
76
+ return response
pdfgate/params.py ADDED
@@ -0,0 +1,308 @@
1
+ """Parameter types and enums used by PDFGate API calls."""
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import Any, NamedTuple, Optional, Union
6
+
7
+
8
+ class PageSizeType(Enum):
9
+ """Supported page sizes for generated PDFs."""
10
+
11
+ A0 = "a0"
12
+ A1 = "a1"
13
+ A2 = "a2"
14
+ A3 = "a3"
15
+ A4 = "a4"
16
+ A5 = "a5"
17
+ A6 = "a6"
18
+ LEDGER = "ledger"
19
+ TABLOID = "tabloid"
20
+ LEGAL = "legal"
21
+ LETTER = "letter"
22
+
23
+
24
+ class FileOrientation(Enum):
25
+ """Orientation options for generated PDFs."""
26
+
27
+ PORTRAIT = "portrait"
28
+ LANDSCAPE = "landscape"
29
+
30
+
31
+ class EmulateMediaType(Enum):
32
+ """Media types for CSS emulation during rendering."""
33
+
34
+ SCREEN = "screen"
35
+ PRINT = "print"
36
+
37
+
38
+ class PdfStandardFont(Enum):
39
+ """Standard built-in fonts supported by the renderer."""
40
+
41
+ TIMES_ROMAN = "times-roman"
42
+ TIMES_BOLD = "times-bold"
43
+ TIMES_ITALIC = "times-italic"
44
+ TIMES_BOLD_ITALIC = "times-bolditalic"
45
+ HELVETICA = "helvetica"
46
+ HELVETICA_BOLD = "helvetica-bold"
47
+ HELVETICA_OBLIQUE = "helvetica-oblique"
48
+ HELVETICA_BOLD_OBLIQUE = "helvetica-boldoblique"
49
+ COURIER = "courier"
50
+ COURIER_BOLD = "courier-bold"
51
+ COURIER_OBLIQUE = "courier-oblique"
52
+ COURIER_BOLD_OBLIQUE = "courier-boldoblique"
53
+
54
+
55
+ @dataclass
56
+ class PdfPageMargin:
57
+ """Margins to apply to a PDF page."""
58
+
59
+ top: Optional[str] = None
60
+ bottom: Optional[str] = None
61
+ left: Optional[str] = None
62
+ right: Optional[str] = None
63
+
64
+
65
+ @dataclass
66
+ class ClickSelectorChain:
67
+ """Sequence of selectors to click in order."""
68
+
69
+ selectors: list[str]
70
+
71
+
72
+ @dataclass
73
+ class ClickSelectorChainSetup:
74
+ """Configuration for click selector chains."""
75
+
76
+ ignore_failing_chains: Optional[bool] = None
77
+ chains: Optional[list["ClickSelectorChain"]] = None
78
+
79
+
80
+ @dataclass
81
+ class PDFGateParams:
82
+ """Marker base class for all parameter dataclasses."""
83
+
84
+ pass
85
+
86
+
87
+ @dataclass
88
+ class GetDocumentParams(PDFGateParams):
89
+ """Parameters for fetching a document's metadata."""
90
+
91
+ document_id: str
92
+ pre_signed_url_expires_in: Optional[int] = None
93
+
94
+
95
+ @dataclass
96
+ class GetFileParams(PDFGateParams):
97
+ """Parameters for downloading a document's file content."""
98
+
99
+ document_id: str
100
+
101
+
102
+ @dataclass
103
+ class GeneratePDFAuthentication:
104
+ """ "Authentication credentials for accessing protected web content."""
105
+
106
+ username: str
107
+ password: str
108
+
109
+
110
+ @dataclass
111
+ class Viewport:
112
+ """Viewport dimensions for rendering."""
113
+
114
+ width: int
115
+ height: int
116
+
117
+
118
+ @dataclass
119
+ class GeneratePDFParams(PDFGateParams):
120
+ """Parameters for generating a PDF from HTML or a URL."""
121
+
122
+ html: Optional[str] = None
123
+ url: Optional[str] = None
124
+ json_response: Optional[bool] = False
125
+ pre_signed_url_expires_in: Optional[int] = None
126
+ page_size_type: Optional[PageSizeType] = None
127
+ width: Optional[int] = None
128
+ height: Optional[int] = None
129
+ orientation: Optional[FileOrientation] = None
130
+ header: Optional[str] = None
131
+ footer: Optional[str] = None
132
+ margin: Optional[PdfPageMargin] = None
133
+ timeout: Optional[int] = None
134
+ javascript: Optional[str] = None
135
+ css: Optional[str] = None
136
+ emulate_media_type: Optional[EmulateMediaType] = None
137
+ http_headers: Optional[dict[str, str]] = None
138
+ metadata: Optional[Any] = None
139
+ wait_for_selector: Optional[str] = None
140
+ click_selector: Optional[str] = None
141
+ click_selector_chain_setup: Optional[ClickSelectorChainSetup] = None
142
+ wait_for_network_idle: Optional[bool] = None
143
+ enable_form_fields: Optional[bool] = None
144
+ delay: Optional[int] = None
145
+ load_images: Optional[bool] = None
146
+ scale: Optional[float] = None
147
+ page_ranges: Optional[str] = None
148
+ print_background: Optional[bool] = None
149
+ user_agent: Optional[str] = None
150
+ authentication: Optional[GeneratePDFAuthentication] = None
151
+ viewport: Optional[Viewport] = None
152
+
153
+
154
+ @dataclass
155
+ class FlattenPDFBaseParams(PDFGateParams):
156
+ """Common parameters for flattening PDFs."""
157
+
158
+ json_response: Optional[bool] = False
159
+ pre_signed_url_expires_in: Optional[int] = None
160
+ metadata: Optional[Any] = None
161
+
162
+
163
+ class FileParam(NamedTuple):
164
+ """Binary file payload for multipart PDF uploads."""
165
+
166
+ name: str
167
+ data: bytes
168
+ type: Optional[str] = None
169
+
170
+
171
+ @dataclass
172
+ class FlattenPDFByFileParams(FlattenPDFBaseParams):
173
+ """Parameters for flattening a PDF provided as a file."""
174
+
175
+ file: Optional[FileParam] = None
176
+
177
+
178
+ @dataclass
179
+ class FlattenPDFByDocumentIdParams(FlattenPDFBaseParams):
180
+ """Parameters for flattening a PDF by document ID."""
181
+
182
+ document_id: Optional[str] = None
183
+
184
+
185
+ FlattenPDFParams = Union[FlattenPDFByFileParams, FlattenPDFByDocumentIdParams]
186
+
187
+
188
+ @dataclass
189
+ class ExtractPDFFormDataByDocumentIdParams(PDFGateParams):
190
+ """Parameters for extracting form data by document ID."""
191
+
192
+ document_id: str
193
+
194
+
195
+ @dataclass
196
+ class ExtractPDFFormDataByFileParams(PDFGateParams):
197
+ """Parameters for extracting form data from a file."""
198
+
199
+ file: FileParam
200
+
201
+
202
+ ExtractPDFFormDataParams = Union[
203
+ ExtractPDFFormDataByDocumentIdParams, ExtractPDFFormDataByFileParams
204
+ ]
205
+
206
+
207
+ class EncryptionAlgorithm(Enum):
208
+ """Supported encryption algorithms."""
209
+
210
+ AES_256 = "AES256"
211
+ AES_128 = "AES128"
212
+
213
+
214
+ @dataclass
215
+ class ProtectPDFBaseParams(PDFGateParams):
216
+ """Common parameters for protecting PDFs."""
217
+
218
+ algorithm: Optional[EncryptionAlgorithm] = None
219
+ user_password: Optional[str] = None
220
+ owner_password: Optional[str] = None
221
+ disable_print: Optional[bool] = None
222
+ disable_copy: Optional[bool] = None
223
+ disable_editing: Optional[bool] = None
224
+ encrypt_metadata: Optional[bool] = None
225
+ json_response: Optional[bool] = False
226
+ pre_signed_url_expires_in: Optional[int] = None
227
+ metadata: Optional[Any] = None
228
+
229
+
230
+ @dataclass
231
+ class ProtectPDFByDocumentIdParams(ProtectPDFBaseParams):
232
+ """Parameters for protecting a PDF by document ID."""
233
+
234
+ document_id: Optional[str] = None
235
+
236
+
237
+ @dataclass
238
+ class ProtectPDFByFileParams(ProtectPDFBaseParams):
239
+ """Parameters for protecting a PDF provided as a file."""
240
+
241
+ file: Optional[FileParam] = None
242
+
243
+
244
+ ProtectPDFParams = Union[ProtectPDFByDocumentIdParams, ProtectPDFByFileParams]
245
+
246
+
247
+ @dataclass
248
+ class CompressPDFBaseParams(PDFGateParams):
249
+ """Common parameters for compressing PDFs."""
250
+
251
+ linearize: Optional[bool] = None
252
+ json_response: Optional[bool] = False
253
+ pre_signed_url_expires_in: Optional[int] = None
254
+ metadata: Optional[Any] = None
255
+
256
+
257
+ @dataclass
258
+ class CompressPDFByDocumentIdParams(CompressPDFBaseParams):
259
+ """Parameters for compressing a PDF by document ID."""
260
+
261
+ document_id: Optional[str] = None
262
+
263
+
264
+ @dataclass
265
+ class CompressPDFByFileParams(CompressPDFBaseParams):
266
+ """Parameters for compressing a PDF provided as a file."""
267
+
268
+ file: Optional[FileParam] = None
269
+
270
+
271
+ CompressPDFParams = Union[CompressPDFByDocumentIdParams, CompressPDFByFileParams]
272
+
273
+
274
+ class WatermarkType(Enum):
275
+ TEXT = "text"
276
+ IMAGE = "image"
277
+
278
+
279
+ @dataclass
280
+ class WatermarkPDFBaseParams(PDFGateParams):
281
+ type: WatermarkType
282
+ text: Optional[str] = None
283
+ watermark: Optional[FileParam] = None
284
+ font: Optional[PdfStandardFont] = None
285
+ font_size: Optional[int] = None
286
+ font_color: Optional[str] = None
287
+ opacity: Optional[float] = None
288
+ x_position: Optional[int] = None
289
+ y_position: Optional[int] = None
290
+ image_width: Optional[int] = None
291
+ image_height: Optional[int] = None
292
+ rotate: Optional[float] = None
293
+ json_response: Optional[bool] = False
294
+ pre_signed_url_expires_in: Optional[int] = None
295
+ metadata: Optional[Any] = None
296
+
297
+
298
+ @dataclass
299
+ class WatermarkPDFByDocumentIdParams(WatermarkPDFBaseParams):
300
+ document_id: Optional[str] = None
301
+
302
+
303
+ @dataclass
304
+ class WatermarkPDFByFileParams(WatermarkPDFBaseParams):
305
+ file: Optional[FileParam] = None
306
+
307
+
308
+ WatermarkPDFParams = Union[WatermarkPDFByDocumentIdParams, WatermarkPDFByFileParams]