rendex 0.1.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.
- rendex/__init__.py +29 -0
- rendex/client.py +187 -0
- rendex/errors.py +47 -0
- rendex/py.typed +0 -0
- rendex/types.py +102 -0
- rendex-0.1.0.dist-info/METADATA +171 -0
- rendex-0.1.0.dist-info/RECORD +9 -0
- rendex-0.1.0.dist-info/WHEEL +4 -0
- rendex-0.1.0.dist-info/licenses/LICENSE +21 -0
rendex/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Rendex — Official Python SDK for the Rendex screenshot API."""
|
|
2
|
+
|
|
3
|
+
from .client import Rendex
|
|
4
|
+
from .errors import RendexApiError, RendexError, RendexNetworkError
|
|
5
|
+
from .types import (
|
|
6
|
+
ScreenshotData,
|
|
7
|
+
ScreenshotJsonResponse,
|
|
8
|
+
ScreenshotMetadata,
|
|
9
|
+
ScreenshotOptions,
|
|
10
|
+
ScreenshotResult,
|
|
11
|
+
ResponseMeta,
|
|
12
|
+
UsageInfo,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__version__ = "0.1.0"
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Rendex",
|
|
19
|
+
"RendexError",
|
|
20
|
+
"RendexApiError",
|
|
21
|
+
"RendexNetworkError",
|
|
22
|
+
"ScreenshotOptions",
|
|
23
|
+
"ScreenshotResult",
|
|
24
|
+
"ScreenshotMetadata",
|
|
25
|
+
"ScreenshotJsonResponse",
|
|
26
|
+
"ScreenshotData",
|
|
27
|
+
"ResponseMeta",
|
|
28
|
+
"UsageInfo",
|
|
29
|
+
]
|
rendex/client.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Rendex SDK client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.parse import urlencode
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from .errors import RendexApiError, RendexError, RendexNetworkError
|
|
11
|
+
from .types import (
|
|
12
|
+
ScreenshotJsonResponse,
|
|
13
|
+
ScreenshotMetadata,
|
|
14
|
+
ScreenshotResult,
|
|
15
|
+
_build_body,
|
|
16
|
+
_to_camel,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
DEFAULT_BASE_URL = "https://api.rendex.dev"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Rendex:
|
|
23
|
+
"""Official Python client for the Rendex screenshot API.
|
|
24
|
+
|
|
25
|
+
Usage::
|
|
26
|
+
|
|
27
|
+
from rendex import Rendex
|
|
28
|
+
|
|
29
|
+
rendex = Rendex("your-api-key")
|
|
30
|
+
result = rendex.screenshot("https://example.com", full_page=True)
|
|
31
|
+
Path("screenshot.png").write_bytes(result.image)
|
|
32
|
+
|
|
33
|
+
Or as a context manager::
|
|
34
|
+
|
|
35
|
+
with Rendex("your-api-key") as rendex:
|
|
36
|
+
result = rendex.screenshot("https://example.com")
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, api_key: str, *, base_url: str = DEFAULT_BASE_URL) -> None:
|
|
40
|
+
if not api_key:
|
|
41
|
+
raise RendexError("API key is required. Get one at https://rendex.dev")
|
|
42
|
+
self._api_key = api_key
|
|
43
|
+
self._base_url = base_url.rstrip("/")
|
|
44
|
+
self._client = httpx.Client(
|
|
45
|
+
base_url=self._base_url,
|
|
46
|
+
headers={
|
|
47
|
+
"Authorization": f"Bearer {api_key}",
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
},
|
|
50
|
+
timeout=90.0,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def screenshot(self, url: str, **options: Any) -> ScreenshotResult:
|
|
54
|
+
"""Capture a screenshot and return the binary image with metadata.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
url: The webpage URL to capture.
|
|
58
|
+
**options: Screenshot options (snake_case). See ScreenshotOptions.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
ScreenshotResult with ``image`` (bytes) and ``metadata``.
|
|
62
|
+
"""
|
|
63
|
+
body = _build_body(url, options)
|
|
64
|
+
response = self._request("/v1/screenshot", body)
|
|
65
|
+
|
|
66
|
+
metadata = self._parse_metadata_headers(response.headers, len(response.content))
|
|
67
|
+
|
|
68
|
+
return ScreenshotResult(image=response.content, metadata=metadata)
|
|
69
|
+
|
|
70
|
+
def screenshot_json(self, url: str, **options: Any) -> ScreenshotJsonResponse:
|
|
71
|
+
"""Capture a screenshot and return JSON with a base64-encoded image.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
url: The webpage URL to capture.
|
|
75
|
+
**options: Screenshot options (snake_case). See ScreenshotOptions.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
ScreenshotJsonResponse with ``data`` and ``meta``.
|
|
79
|
+
"""
|
|
80
|
+
body = _build_body(url, options)
|
|
81
|
+
response = self._request("/v1/screenshot/json", body)
|
|
82
|
+
data = response.json()
|
|
83
|
+
|
|
84
|
+
if not data.get("success"):
|
|
85
|
+
error = data.get("error", {})
|
|
86
|
+
meta = data.get("meta", {})
|
|
87
|
+
raise RendexApiError(
|
|
88
|
+
message=error.get("message", "Unknown error"),
|
|
89
|
+
status_code=response.status_code,
|
|
90
|
+
error_code=error.get("code", "UNKNOWN"),
|
|
91
|
+
request_id=meta.get("requestId"),
|
|
92
|
+
details=error.get("details"),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return data # type: ignore[return-value]
|
|
96
|
+
|
|
97
|
+
def screenshot_url(self, url: str, **options: Any) -> str:
|
|
98
|
+
"""Generate a signed GET URL for embedding (no network call).
|
|
99
|
+
|
|
100
|
+
Useful for ``<img>`` tags, OpenGraph images, or anywhere you need a URL.
|
|
101
|
+
|
|
102
|
+
**Note**: The API key is included in the URL. Use server-side only.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
url: The webpage URL to capture.
|
|
106
|
+
**options: Screenshot options (snake_case).
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
A complete URL string to the screenshot endpoint.
|
|
110
|
+
"""
|
|
111
|
+
params: list[tuple[str, str]] = [("key", self._api_key), ("url", url)]
|
|
112
|
+
|
|
113
|
+
for key, value in options.items():
|
|
114
|
+
if value is None:
|
|
115
|
+
continue
|
|
116
|
+
camel_key = _to_camel(key)
|
|
117
|
+
if isinstance(value, list):
|
|
118
|
+
for item in value:
|
|
119
|
+
params.append((camel_key, str(item)))
|
|
120
|
+
elif isinstance(value, bool):
|
|
121
|
+
params.append((camel_key, str(value).lower()))
|
|
122
|
+
else:
|
|
123
|
+
params.append((camel_key, str(value)))
|
|
124
|
+
|
|
125
|
+
return f"{self._base_url}/v1/screenshot?{urlencode(params)}"
|
|
126
|
+
|
|
127
|
+
def close(self) -> None:
|
|
128
|
+
"""Close the underlying HTTP client."""
|
|
129
|
+
self._client.close()
|
|
130
|
+
|
|
131
|
+
def __enter__(self) -> Rendex:
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
def __exit__(self, *args: Any) -> None:
|
|
135
|
+
self.close()
|
|
136
|
+
|
|
137
|
+
# ─── Private ─────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
def _request(self, path: str, body: dict[str, Any]) -> httpx.Response:
|
|
140
|
+
try:
|
|
141
|
+
response = self._client.post(path, json=body)
|
|
142
|
+
except httpx.HTTPError as exc:
|
|
143
|
+
raise RendexNetworkError(
|
|
144
|
+
f"Network request to {self._base_url}{path} failed: {exc}",
|
|
145
|
+
cause=exc,
|
|
146
|
+
) from exc
|
|
147
|
+
|
|
148
|
+
if response.status_code >= 400:
|
|
149
|
+
self._handle_error_response(response)
|
|
150
|
+
|
|
151
|
+
return response
|
|
152
|
+
|
|
153
|
+
def _handle_error_response(self, response: httpx.Response) -> None:
|
|
154
|
+
try:
|
|
155
|
+
data = response.json()
|
|
156
|
+
except Exception:
|
|
157
|
+
raise RendexApiError(
|
|
158
|
+
message=f"HTTP {response.status_code}",
|
|
159
|
+
status_code=response.status_code,
|
|
160
|
+
error_code="UNKNOWN",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
error = data.get("error", {})
|
|
164
|
+
meta = data.get("meta", {})
|
|
165
|
+
|
|
166
|
+
raise RendexApiError(
|
|
167
|
+
message=error.get("message", f"HTTP {response.status_code}"),
|
|
168
|
+
status_code=response.status_code,
|
|
169
|
+
error_code=error.get("code", "UNKNOWN"),
|
|
170
|
+
request_id=meta.get("requestId"),
|
|
171
|
+
details=error.get("details"),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def _parse_metadata_headers(headers: httpx.Headers, fallback_size: int) -> ScreenshotMetadata:
|
|
176
|
+
return ScreenshotMetadata(
|
|
177
|
+
url=headers.get("x-screenshot-url", ""),
|
|
178
|
+
width=int(headers.get("x-screenshot-width", "0")),
|
|
179
|
+
height=int(headers.get("x-screenshot-height", "0")),
|
|
180
|
+
format=headers.get("x-rendex-format", "png"),
|
|
181
|
+
bytes_size=int(headers.get("x-screenshot-size", str(fallback_size))),
|
|
182
|
+
captured_at=headers.get("x-screenshot-captured-at", ""),
|
|
183
|
+
quality=headers.get("x-rendex-quality", "full"),
|
|
184
|
+
wait_strategy=headers.get("x-rendex-wait-strategy", "unknown"),
|
|
185
|
+
load_time_ms=int(headers.get("x-rendex-load-time-ms", "0")),
|
|
186
|
+
truncated=headers.get("x-rendex-truncated") == "true",
|
|
187
|
+
)
|
rendex/errors.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Rendex SDK error classes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RendexError(Exception):
|
|
9
|
+
"""Base error for all Rendex SDK errors."""
|
|
10
|
+
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RendexApiError(RendexError):
|
|
15
|
+
"""Error returned by the Rendex API (non-2xx response with a parsed body)."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
message: str,
|
|
20
|
+
status_code: int,
|
|
21
|
+
error_code: str,
|
|
22
|
+
request_id: Optional[str] = None,
|
|
23
|
+
details: Any = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
self.status_code = status_code
|
|
27
|
+
"""HTTP status code (e.g. 400, 401, 429, 500)."""
|
|
28
|
+
self.error_code = error_code
|
|
29
|
+
"""API error code (e.g. "RATE_LIMITED", "VALIDATION_ERROR")."""
|
|
30
|
+
self.request_id = request_id
|
|
31
|
+
"""Unique request ID for debugging with Rendex support."""
|
|
32
|
+
self.details = details
|
|
33
|
+
"""Additional error details (e.g. validation field errors)."""
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
parts = [f"[{self.error_code}] {super().__str__()}"]
|
|
37
|
+
if self.request_id:
|
|
38
|
+
parts.append(f"(request: {self.request_id})")
|
|
39
|
+
return " ".join(parts)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RendexNetworkError(RendexError):
|
|
43
|
+
"""Network-level error (DNS failure, timeout, connection refused, etc.)."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, message: str, cause: Optional[Exception] = None) -> None:
|
|
46
|
+
super().__init__(message)
|
|
47
|
+
self.__cause__ = cause
|
rendex/py.typed
ADDED
|
File without changes
|
rendex/types.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Rendex SDK type definitions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, List, Literal, Optional, TypedDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ScreenshotOptions(TypedDict, total=False):
|
|
10
|
+
"""Options for capturing a screenshot. Only ``url`` is required."""
|
|
11
|
+
|
|
12
|
+
url: str # Required — set via positional arg in client methods
|
|
13
|
+
format: Literal["png", "jpeg", "webp"]
|
|
14
|
+
width: int
|
|
15
|
+
height: int
|
|
16
|
+
full_page: bool
|
|
17
|
+
quality: int
|
|
18
|
+
delay: int
|
|
19
|
+
dark_mode: bool
|
|
20
|
+
device_scale_factor: float
|
|
21
|
+
block_ads: bool
|
|
22
|
+
block_resource_types: List[Literal["font", "image", "media", "stylesheet", "other"]]
|
|
23
|
+
timeout: int
|
|
24
|
+
wait_until: Literal["load", "domcontentloaded", "networkidle0", "networkidle2"]
|
|
25
|
+
wait_for_selector: str
|
|
26
|
+
best_attempt: bool
|
|
27
|
+
selector: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class UsageInfo(TypedDict):
|
|
31
|
+
credits: int
|
|
32
|
+
remaining: int
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ResponseMeta(TypedDict, total=False):
|
|
36
|
+
requestId: str
|
|
37
|
+
timestamp: str
|
|
38
|
+
usage: UsageInfo
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ScreenshotData(TypedDict, total=False):
|
|
42
|
+
image: str # base64
|
|
43
|
+
contentType: str
|
|
44
|
+
url: str
|
|
45
|
+
width: int
|
|
46
|
+
height: int
|
|
47
|
+
format: str
|
|
48
|
+
bytesSize: int
|
|
49
|
+
capturedAt: str
|
|
50
|
+
quality: Literal["full", "degraded", "best_attempt"]
|
|
51
|
+
waitStrategy: str
|
|
52
|
+
loadTimeMs: int
|
|
53
|
+
truncated: bool
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ScreenshotJsonResponse(TypedDict):
|
|
57
|
+
success: bool
|
|
58
|
+
data: ScreenshotData
|
|
59
|
+
meta: ResponseMeta
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class ScreenshotMetadata:
|
|
64
|
+
"""Metadata from response headers for binary screenshots."""
|
|
65
|
+
|
|
66
|
+
url: str
|
|
67
|
+
width: int
|
|
68
|
+
height: int
|
|
69
|
+
format: str
|
|
70
|
+
bytes_size: int
|
|
71
|
+
captured_at: str
|
|
72
|
+
quality: str # "full", "degraded", or "best_attempt"
|
|
73
|
+
wait_strategy: str
|
|
74
|
+
load_time_ms: int
|
|
75
|
+
truncated: bool
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True)
|
|
79
|
+
class ScreenshotResult:
|
|
80
|
+
"""Result from ``Rendex.screenshot()`` — binary image with metadata."""
|
|
81
|
+
|
|
82
|
+
image: bytes
|
|
83
|
+
"""Raw image bytes. Write to file, upload to S3, etc."""
|
|
84
|
+
|
|
85
|
+
metadata: ScreenshotMetadata
|
|
86
|
+
"""Capture metadata extracted from response headers."""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# camelCase conversion for API requests
|
|
90
|
+
def _to_camel(key: str) -> str:
|
|
91
|
+
"""Convert snake_case to camelCase."""
|
|
92
|
+
parts = key.split("_")
|
|
93
|
+
return parts[0] + "".join(p.capitalize() for p in parts[1:])
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _build_body(url: str, options: Any) -> dict[str, Any]:
|
|
97
|
+
"""Build the JSON request body from url + snake_case kwargs."""
|
|
98
|
+
body: dict[str, Any] = {"url": url}
|
|
99
|
+
for key, value in options.items():
|
|
100
|
+
if value is not None:
|
|
101
|
+
body[_to_camel(key)] = value
|
|
102
|
+
return body
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rendex
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Rendex screenshot API — capture any webpage as an image
|
|
5
|
+
Project-URL: Homepage, https://rendex.dev
|
|
6
|
+
Project-URL: Documentation, https://rendex.dev/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/copperline-labs/copperline-labs
|
|
8
|
+
Project-URL: Issues, https://github.com/copperline-labs/copperline-labs/issues
|
|
9
|
+
Author-email: Copperline Labs LLC <support@rendex.dev>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ai-agent,rendex,screenshot,screenshot-api,sdk,webpage-capture
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# rendex
|
|
28
|
+
|
|
29
|
+
Official Python SDK for the [Rendex](https://rendex.dev) screenshot API. Capture any webpage as a high-quality image with a single function call.
|
|
30
|
+
|
|
31
|
+
- Full type hints (PEP 561 compatible)
|
|
32
|
+
- Single dependency (`httpx`)
|
|
33
|
+
- Sync API with context manager support
|
|
34
|
+
- Typed error handling with API error codes
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install rendex
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
from rendex import Rendex
|
|
47
|
+
|
|
48
|
+
rendex = Rendex("your-api-key")
|
|
49
|
+
|
|
50
|
+
# Capture a screenshot (returns binary image)
|
|
51
|
+
result = rendex.screenshot("https://example.com", format="png", full_page=True)
|
|
52
|
+
Path("screenshot.png").write_bytes(result.image)
|
|
53
|
+
|
|
54
|
+
print(f"{result.metadata.bytes_size} bytes, loaded in {result.metadata.load_time_ms}ms")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API Reference
|
|
58
|
+
|
|
59
|
+
### `Rendex(api_key, *, base_url="https://api.rendex.dev")`
|
|
60
|
+
|
|
61
|
+
Create a new Rendex client.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
rendex = Rendex("your-api-key")
|
|
65
|
+
|
|
66
|
+
# Or with context manager for connection reuse
|
|
67
|
+
with Rendex("your-api-key") as rendex:
|
|
68
|
+
result = rendex.screenshot("https://example.com")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `rendex.screenshot(url, **options)`
|
|
72
|
+
|
|
73
|
+
Capture a screenshot and return the binary image with metadata.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
result = rendex.screenshot(
|
|
77
|
+
"https://example.com",
|
|
78
|
+
format="webp",
|
|
79
|
+
width=1920,
|
|
80
|
+
height=1080,
|
|
81
|
+
dark_mode=True,
|
|
82
|
+
)
|
|
83
|
+
Path("screenshot.webp").write_bytes(result.image)
|
|
84
|
+
print(result.metadata.load_time_ms) # 350
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Returns** `ScreenshotResult`:
|
|
88
|
+
- `image` — `bytes` of the captured image
|
|
89
|
+
- `metadata` — `ScreenshotMetadata` with url, dimensions, format, bytes_size, load_time_ms, quality, etc.
|
|
90
|
+
|
|
91
|
+
### `rendex.screenshot_json(url, **options)`
|
|
92
|
+
|
|
93
|
+
Capture a screenshot and return JSON with a base64-encoded image.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
result = rendex.screenshot_json("https://example.com")
|
|
97
|
+
print(result["data"]["bytesSize"]) # 45823
|
|
98
|
+
print(result["meta"]["usage"]["remaining"]) # 499
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Returns** `ScreenshotJsonResponse` dict with `data` (image + metadata) and `meta` (request ID, usage).
|
|
102
|
+
|
|
103
|
+
### `rendex.screenshot_url(url, **options)`
|
|
104
|
+
|
|
105
|
+
Generate a GET URL for embedding. No network call — pure URL builder.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
url = rendex.screenshot_url("https://example.com", format="png", width=1200)
|
|
109
|
+
# Use in <img> tags, OpenGraph, etc.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
> **Note**: The API key is included in the URL. Use server-side only.
|
|
113
|
+
|
|
114
|
+
### Screenshot Options
|
|
115
|
+
|
|
116
|
+
All options are keyword arguments in snake_case. Only `url` (positional) is required:
|
|
117
|
+
|
|
118
|
+
| Option | Type | Default | Description |
|
|
119
|
+
|--------|------|---------|-------------|
|
|
120
|
+
| `format` | `str` | `"png"` | `"png"`, `"jpeg"`, or `"webp"` |
|
|
121
|
+
| `width` | `int` | `1280` | Viewport width (320–3840) |
|
|
122
|
+
| `height` | `int` | `800` | Viewport height (240–2160) |
|
|
123
|
+
| `full_page` | `bool` | `False` | Capture the full scrollable page |
|
|
124
|
+
| `quality` | `int` | — | JPEG/WebP quality (1–100) |
|
|
125
|
+
| `delay` | `int` | `0` | Delay before capture in ms (0–10000) |
|
|
126
|
+
| `dark_mode` | `bool` | `False` | Emulate dark mode |
|
|
127
|
+
| `device_scale_factor` | `float` | `1` | Device pixel ratio (1–3) for Retina |
|
|
128
|
+
| `block_ads` | `bool` | `True` | Block ads and trackers |
|
|
129
|
+
| `block_resource_types` | `list` | — | Block: `"font"`, `"image"`, `"media"`, `"stylesheet"`, `"other"` |
|
|
130
|
+
| `timeout` | `int` | `30` | Page load timeout in seconds (5–60) |
|
|
131
|
+
| `wait_until` | `str` | `"networkidle2"` | `"load"`, `"domcontentloaded"`, `"networkidle0"`, `"networkidle2"` |
|
|
132
|
+
| `wait_for_selector` | `str` | — | CSS selector to wait for |
|
|
133
|
+
| `best_attempt` | `bool` | `True` | Return best-effort screenshot on timeout |
|
|
134
|
+
| `selector` | `str` | — | Capture a specific element by CSS selector |
|
|
135
|
+
|
|
136
|
+
## Error Handling
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from rendex import Rendex, RendexApiError, RendexNetworkError
|
|
140
|
+
|
|
141
|
+
rendex = Rendex("your-api-key")
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
rendex.screenshot("https://example.com")
|
|
145
|
+
except RendexApiError as e:
|
|
146
|
+
# API returned an error
|
|
147
|
+
print(e.error_code) # "RATE_LIMITED", "VALIDATION_ERROR", etc.
|
|
148
|
+
print(e.status_code) # 429, 400, etc.
|
|
149
|
+
print(e.request_id) # For debugging with Rendex support
|
|
150
|
+
print(e.details) # Validation details (if any)
|
|
151
|
+
except RendexNetworkError as e:
|
|
152
|
+
# Network failure (DNS, timeout, connection refused)
|
|
153
|
+
print(f"Network error: {e}")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Error Codes
|
|
157
|
+
|
|
158
|
+
| Code | HTTP Status | Description |
|
|
159
|
+
|------|-------------|-------------|
|
|
160
|
+
| `VALIDATION_ERROR` | 400 | Invalid request parameters |
|
|
161
|
+
| `INVALID_URL` | 400 | URL failed SSRF validation |
|
|
162
|
+
| `TIMEOUT` | 408 | Page took too long to load |
|
|
163
|
+
| `CAPTURE_FAILED` | 500 | Browser rendering error |
|
|
164
|
+
| `RATE_LIMITED` | 429 | Rate limit exceeded |
|
|
165
|
+
| `USAGE_EXCEEDED` | 429 | Monthly credit limit reached |
|
|
166
|
+
| `MISSING_API_KEY` | 401 | No API key provided |
|
|
167
|
+
| `INVALID_API_KEY` | 401 | API key verification failed |
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT - [Copperline Labs LLC](https://copperlinelabs.com)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
rendex/__init__.py,sha256=NN-y-lNUDI3zrcvviX8OTkVlE_nwkBwXHa0ZbG1YOzQ,620
|
|
2
|
+
rendex/client.py,sha256=EwEO5zHny2knZrhv_NUAHBbeKsH7yphUb3RUKaOrqEs,6533
|
|
3
|
+
rendex/errors.py,sha256=jQPHPmu9nerZZLS8sf0SwW2FCv_1LMYg9TjMrOOBtFQ,1426
|
|
4
|
+
rendex/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
rendex/types.py,sha256=wHhkU-7cUYnZK6IXptztEc3RKJOPJ4bTutVq8TyFBx4,2553
|
|
6
|
+
rendex-0.1.0.dist-info/METADATA,sha256=5DOYPfCek-xEiIN9XIKIMp19rymMk82M7DCcmDBzJxo,5844
|
|
7
|
+
rendex-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
8
|
+
rendex-0.1.0.dist-info/licenses/LICENSE,sha256=dxFNlrSgJ6--KVwaItmS-RsYzgkAbOUasB8Xj8j_iLQ,1076
|
|
9
|
+
rendex-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Copperline Labs LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|