pdfbridge-python 1.0.0__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.
pdfbridge/__init__.py ADDED
@@ -0,0 +1,20 @@
1
+ from .client import PDFBridge, PDFBridgeError
2
+ from .models import (
3
+ ConvertRequest,
4
+ BulkConvertRequest,
5
+ ConvertResponse,
6
+ BulkConvertResponse,
7
+ JobStatusResponse,
8
+ PdfOptions,
9
+ )
10
+
11
+ __all__ = [
12
+ "PDFBridge",
13
+ "PDFBridgeError",
14
+ "ConvertRequest",
15
+ "BulkConvertRequest",
16
+ "ConvertResponse",
17
+ "BulkConvertResponse",
18
+ "JobStatusResponse",
19
+ "PdfOptions",
20
+ ]
pdfbridge/client.py ADDED
@@ -0,0 +1,125 @@
1
+ import os
2
+ import time
3
+ import requests
4
+ from typing import Optional, Union, Dict, Any
5
+
6
+ from .models import (
7
+ ConvertRequest,
8
+ BulkConvertRequest,
9
+ ConvertResponse,
10
+ BulkConvertResponse,
11
+ JobStatusResponse,
12
+ )
13
+
14
+
15
+ class PDFBridgeError(Exception):
16
+ def __init__(self, message: str, status_code: Optional[int] = None, metadata: Optional[Dict[str, Any]] = None):
17
+ super().__init__(message)
18
+ self.status_code = status_code
19
+ self.metadata = metadata
20
+
21
+
22
+ class PDFBridge:
23
+ def __init__(
24
+ self,
25
+ api_key: Optional[str] = None,
26
+ base_url: str = "https://api.pdfbridge.xyz/api/v1",
27
+ max_retries: int = 2,
28
+ ):
29
+ self.api_key = api_key or os.environ.get("PDFBRIDGE_API_KEY")
30
+ if not self.api_key:
31
+ raise PDFBridgeError(
32
+ "API Key is required to initialize the PDFBridge Client. Pass it explicitly or set PDFBRIDGE_API_KEY in your environment."
33
+ )
34
+
35
+ self.base_url = base_url.rstrip("/")
36
+ self.max_retries = max_retries
37
+ self.session = requests.Session()
38
+ self.session.headers.update({
39
+ "Content-Type": "application/json",
40
+ "x-api-key": self.api_key,
41
+ "User-Agent": "pdfbridge-python/1.0.0",
42
+ })
43
+
44
+ def _request(self, method: str, path: str, **kwargs) -> Any:
45
+ url = f"{self.base_url}{path if path.startswith('/') else '/' + path}"
46
+ attempt = 0
47
+
48
+ while attempt <= self.max_retries:
49
+ try:
50
+ response = self.session.request(method, url, **kwargs)
51
+
52
+ if response.status_code == 204:
53
+ return None
54
+
55
+ # Handle binary ghost mode response
56
+ content_type = response.headers.get("content-type", "")
57
+ if "application/json" not in content_type and response.ok:
58
+ return response.content
59
+
60
+ try:
61
+ data = response.json()
62
+ except ValueError:
63
+ data = {}
64
+
65
+ if not response.ok:
66
+ raise PDFBridgeError(
67
+ data.get("message") or data.get("error") or f"Request failed with status {response.status_code}",
68
+ status_code=response.status_code,
69
+ metadata=data,
70
+ )
71
+
72
+ return data
73
+
74
+ except requests.exceptions.RequestException as e:
75
+ if attempt == self.max_retries:
76
+ raise PDFBridgeError(f"Network error: {str(e)}")
77
+ attempt += 1
78
+ time.sleep((2 ** attempt) * 0.5)
79
+
80
+ raise PDFBridgeError("Unknown network error occurred.")
81
+
82
+ def generate(self, **kwargs) -> Union[ConvertResponse, bytes]:
83
+ """
84
+ Start a new URL or HTML to PDF conversion job.
85
+ If ghostMode=True is passed, this immediately returns raw PDF bytes.
86
+ Otherwise, returns a ConvertResponse map containing the jobId.
87
+ """
88
+ payload = ConvertRequest.model_validate(kwargs)
89
+ res = self._request("POST", "/convert", json=payload.model_dump(exclude_none=True))
90
+
91
+ if payload.ghostMode:
92
+ return res # returns bytes directly
93
+ return ConvertResponse.model_validate(res)
94
+
95
+ def generate_and_wait(self, poll_interval_ms: int = 2000, **kwargs) -> JobStatusResponse:
96
+ """
97
+ Start a new job and automatically poll the status endpoint until COMPLETED or FAILED.
98
+ Returns the final JobStatusResponse containing the pdfUrl.
99
+ """
100
+ payload = ConvertRequest.model_validate(kwargs)
101
+ if payload.ghostMode:
102
+ raise PDFBridgeError("Cannot use generate_and_wait with ghostMode=True. Using ghostMode natively returns the bytes immediately on the standard .generate() method.")
103
+
104
+ init_response = self.generate(**kwargs)
105
+ job_id = init_response.jobId
106
+
107
+ while True:
108
+ time.sleep(poll_interval_ms / 1000.0)
109
+ status = self.get_job(job_id)
110
+
111
+ if status.status == "COMPLETED":
112
+ return status
113
+ if status.status == "FAILED":
114
+ raise PDFBridgeError(f"Job failed: {status.error}", metadata=status.model_dump())
115
+
116
+ def generate_bulk(self, **kwargs) -> BulkConvertResponse:
117
+ """Start a bulk conversion job for up to 1,000 documents at once."""
118
+ payload = BulkConvertRequest.model_validate(kwargs)
119
+ res = self._request("POST", "/convert/bulk", json=payload.model_dump(exclude_none=True))
120
+ return BulkConvertResponse.model_validate(res)
121
+
122
+ def get_job(self, job_id: str) -> JobStatusResponse:
123
+ """Retrieve the status of an existing conversion job."""
124
+ res = self._request("GET", f"/jobs/{job_id}")
125
+ return JobStatusResponse.model_validate(res)
pdfbridge/models.py ADDED
@@ -0,0 +1,76 @@
1
+ from typing import Optional, List, Dict, Any, Union
2
+ from pydantic import BaseModel, Field, HttpUrl, model_validator
3
+
4
+
5
+ class PdfOptions(BaseModel):
6
+ format: Optional[str] = Field(None, description="A4, Letter, Legal, Tabloid, Ledger, A3")
7
+ landscape: Optional[bool] = None
8
+ printBackground: Optional[bool] = None
9
+ scale: Optional[float] = Field(None, ge=0.1, le=2.0)
10
+ margin: Optional[str] = None
11
+ marginTop: Optional[str] = None
12
+ marginBottom: Optional[str] = None
13
+ marginLeft: Optional[str] = None
14
+ marginRight: Optional[str] = None
15
+ displayHeaderFooter: Optional[bool] = None
16
+ headerTemplate: Optional[str] = None
17
+ footerTemplate: Optional[str] = None
18
+ preferCSSPageSize: Optional[bool] = None
19
+ width: Optional[str] = None
20
+ height: Optional[str] = None
21
+ waitDelay: Optional[str] = None
22
+ userAgent: Optional[str] = None
23
+ waitForSelector: Optional[str] = None
24
+ metadata: Optional[Dict[str, str]] = None
25
+
26
+
27
+ class ConvertRequest(BaseModel):
28
+ url: Optional[str] = None
29
+ html: Optional[str] = None
30
+ filename: Optional[str] = None
31
+ webhookUrl: Optional[str] = None
32
+ ghostMode: Optional[bool] = None
33
+ tailwind: Optional[bool] = None
34
+ extractMetadata: Optional[bool] = None
35
+ templateId: Optional[str] = None
36
+ variables: Optional[Dict[str, Any]] = None
37
+ options: Optional[PdfOptions] = None
38
+
39
+ @model_validator(mode='after')
40
+ def check_source_provided(self) -> 'ConvertRequest':
41
+ if not self.url and not self.html and not self.templateId:
42
+ raise ValueError("You must provide either 'url', 'html', or 'templateId'")
43
+ return self
44
+
45
+
46
+ class BulkConvertRequest(BaseModel):
47
+ jobs: List[ConvertRequest] = Field(..., min_length=1, max_length=1000)
48
+ ghostMode: Optional[bool] = None
49
+ webhookUrl: Optional[str] = None
50
+ extractMetadata: Optional[bool] = None
51
+
52
+
53
+ class ConvertResponse(BaseModel):
54
+ message: str
55
+ jobId: str
56
+ statusUrl: str
57
+
58
+
59
+ class JobItem(BaseModel):
60
+ jobId: str
61
+ statusUrl: str
62
+
63
+
64
+ class BulkConvertResponse(BaseModel):
65
+ message: str
66
+ jobs: List[JobItem]
67
+
68
+
69
+ class JobStatusResponse(BaseModel):
70
+ id: str
71
+ status: str # 'PENDING', 'PROCESSING', 'COMPLETED', 'FAILED'
72
+ pdfUrl: Optional[str] = None
73
+ error: Optional[str] = None
74
+ createdAt: str
75
+ updatedAt: str
76
+ metadata: Optional[Any] = None
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: pdfbridge-python
3
+ Version: 1.0.0
4
+ Summary: The official Python SDK for the PDFBridge API. Generate pixel-perfect PDFs from HTML/URLs.
5
+ Project-URL: Homepage, https://pdfbridge.xyz
6
+ Project-URL: Bug Tracker, https://github.com/techhspyder/pdfbridge-python/issues
7
+ Author-email: TechhSpyder <hello@techhspyder.com>
8
+ License: MIT
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
13
+ Requires-Python: >=3.8
14
+ Requires-Dist: pydantic>=2.0.0
15
+ Requires-Dist: requests>=2.25.1
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
18
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
19
+ Requires-Dist: responses>=0.23.0; extra == 'dev'
20
+ Description-Content-Type: text/markdown
21
+
22
+ <div align="center">
23
+ <img src="https://assets.pdfbridge.xyz/logo.svg" alt="PDFBridge Logo" width="200"/>
24
+ <h1>pdfbridge-python</h1>
25
+ <p>The official Python SDK for the <a href="https://pdfbridge.xyz">PDFBridge API</a>. Generate pixel-perfect PDFs from HTML or URLs.</p>
26
+ </div>
27
+
28
+ [![PyPI version](https://badge.fury.io/py/pdfbridge-python.svg)](https://badge.fury.io/py/pdfbridge-python)
29
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
30
+ [![Python Support](https://img.shields.io/pypi/pyversions/pdfbridge-python.svg)](https://pypi.org/project/pdfbridge-python/)
31
+
32
+ ## Features
33
+
34
+ - **Typed Inputs & Outputs**: Fully powered by Pydantic V2 for strict runtime validation and autocomplete.
35
+ - **Convenient Sync Methods**: Out-of-the-box `generate_and_wait` blocking wrapper so you don't have to write your own pollers.
36
+ - **Ghost Mode Native**: Fetch raw PDF bytes directly into memory. No intermediate storage or URLs.
37
+ - **Bulk Conversions**: Convert up to 1,000 documents simultaneously.
38
+
39
+ ---
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install pdfbridge-python
45
+ ```
46
+
47
+ ## Quick Start
48
+
49
+ ```python
50
+ import os
51
+ from pdfbridge import PDFBridge
52
+
53
+ # Initialize with your secret API key
54
+ # Can also be picked up automatically via the PDFBRIDGE_API_KEY environment variable.
55
+ client = PDFBridge(api_key="pk_live_your_key_here")
56
+
57
+ # Generate and wait for completion (blocks execution until PDF is ready)
58
+ status = client.generate_and_wait(
59
+ url="https://github.com",
60
+ filename="github_page.pdf",
61
+ options={
62
+ "format": "A4",
63
+ "printBackground": True
64
+ }
65
+ )
66
+
67
+ print(f"Success! Download PDF from: {status.pdfUrl}")
68
+ ```
69
+
70
+ ## Advanced Usage
71
+
72
+ ### 👻 Ghost Mode (Memory-Native)
73
+
74
+ Ghost Mode returns the direct `bytes` of the generated PDF without saving it to Cloud Storage. This is perfect for securely piping documents to your own S3 bucket or streaming directly to users.
75
+
76
+ ```python
77
+ # Returns raw bytes natively
78
+ pdf_bytes = client.generate(
79
+ html="<h1>Highly Confidential Financial Data</h1>",
80
+ ghostMode=True,
81
+ options={
82
+ "format": "Letter",
83
+ "margin": "1in"
84
+ }
85
+ )
86
+
87
+ with open("secure_report.pdf", "wb") as f:
88
+ f.write(pdf_bytes)
89
+ ```
90
+
91
+ ### 🚀 Bulk Generation
92
+
93
+ Queue up to 1,000 PDF generation jobs natively in a single API call.
94
+
95
+ ```python
96
+ bulk_job = client.generate_bulk(
97
+ webhookUrl="https://api.yourdomain.com/webhooks/pdfbridge",
98
+ jobs=[
99
+ {"url": "https://example.com/invoice/123", "filename": "INV-123.pdf"},
100
+ {"url": "https://example.com/invoice/124", "filename": "INV-124.pdf"},
101
+ {"url": "https://example.com/invoice/125", "filename": "INV-125.pdf"}
102
+ ]
103
+ )
104
+
105
+ print(f"Queued {len(bulk_job.jobs)} items.")
106
+ ```
107
+
108
+ ### 🧠 Templates & Variables
109
+
110
+ Pass dynamic data into your saved HTML templates.
111
+
112
+ ```python
113
+ client.generate_and_wait(
114
+ templateId="tmpl_987654321",
115
+ variables={
116
+ "customer_name": "Jane Doe",
117
+ "total_amount": "$45.00"
118
+ }
119
+ )
120
+ ```
121
+
122
+ ## Error Handling
123
+
124
+ The SDK raises `PDFBridgeError` on any HTTP failures, authentication issues, or data malformations.
125
+
126
+ ```python
127
+ from pdfbridge import PDFBridgeError
128
+
129
+ try:
130
+ client.generate(url="invalid-url")
131
+ except PDFBridgeError as e:
132
+ print(f"API Failed with status {e.status_code}: {str(e)}")
133
+ ```
@@ -0,0 +1,6 @@
1
+ pdfbridge/__init__.py,sha256=aV3cJpMXhk_RXu3EAvOJ_IMWoUybpC7W9lXmq2Ymuvs,414
2
+ pdfbridge/client.py,sha256=oOY1XacKb-SOM_t-xtbz_x-OUwNZwvD8IM3OwYuESRA,4938
3
+ pdfbridge/models.py,sha256=LRVjzMNohHyDHsvsh0H2cJrTSdKhZE5LTxll8EgTXoE,2427
4
+ pdfbridge_python-1.0.0.dist-info/METADATA,sha256=Hu29oeHmh1IFq8hRLQQE1COWogh7MORG6_cZbi1OL6I,4126
5
+ pdfbridge_python-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
6
+ pdfbridge_python-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any