snapapi-python 1.3.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.
snapapi.py ADDED
@@ -0,0 +1,269 @@
1
+ """
2
+ SnapAPI Python SDK — capture any website as an image.
3
+
4
+ Usage:
5
+ from snapapi import SnapAPI
6
+
7
+ snap = SnapAPI("snap_your_key_here")
8
+ image = snap.screenshot("example.com")
9
+ """
10
+
11
+ import requests
12
+
13
+
14
+ class SnapAPIError(Exception):
15
+ """Raised when the SnapAPI returns a non-2xx response."""
16
+
17
+ def __init__(self, status, message, body=None):
18
+ self.status = status
19
+ self.message = message
20
+ self.body = body
21
+ super().__init__(f"SnapAPI Error {status}: {message}")
22
+
23
+
24
+ class SnapAPI:
25
+ """Client for the SnapAPI screenshot service."""
26
+
27
+ def __init__(self, api_key, base_url="https://snapapi.tech"):
28
+ self.api_key = api_key
29
+ self.base_url = base_url.rstrip("/")
30
+ self._session = requests.Session()
31
+ self._session.headers.update({"X-Api-Key": self.api_key})
32
+
33
+ def _request(self, method, path, params=None, json=None):
34
+ """Send a request and return the response, raising on errors."""
35
+ url = f"{self.base_url}{path}"
36
+ resp = self._session.request(method, url, params=params, json=json)
37
+
38
+ if not resp.ok:
39
+ try:
40
+ body = resp.json()
41
+ message = body.get("error", body.get("message", resp.reason))
42
+ except ValueError:
43
+ body = None
44
+ message = resp.reason or resp.text
45
+ raise SnapAPIError(resp.status_code, message, body)
46
+
47
+ return resp
48
+
49
+ @staticmethod
50
+ def _bool_param(value):
51
+ """Convert a Python bool to a query-string friendly string."""
52
+ return "true" if value else "false"
53
+
54
+ def screenshot(self, url, width=1280, height=800, format="png", quality=80,
55
+ full_page=False, delay=0, dark_mode=False, block_ads=False,
56
+ cache=True, selector=None, meta=False, device=None):
57
+ """Capture a screenshot of the given URL.
58
+
59
+ Returns bytes (raw image data) by default.
60
+ When meta=True, returns a dict with page metadata instead.
61
+ """
62
+ params = {
63
+ "url": url,
64
+ "width": width,
65
+ "height": height,
66
+ "format": format,
67
+ "quality": quality,
68
+ "full_page": self._bool_param(full_page),
69
+ "delay": delay,
70
+ "dark_mode": self._bool_param(dark_mode),
71
+ "block_ads": self._bool_param(block_ads),
72
+ "cache": self._bool_param(cache),
73
+ "meta": self._bool_param(meta),
74
+ }
75
+
76
+ if selector is not None:
77
+ params["selector"] = selector
78
+ if device is not None:
79
+ params["device"] = device
80
+
81
+ resp = self._request("GET", "/v1/screenshot", params=params)
82
+
83
+ content_type = resp.headers.get("Content-Type", "")
84
+ if "application/json" in content_type:
85
+ return resp.json()
86
+
87
+ return resp.content
88
+
89
+ def metadata(self, url):
90
+ """Extract metadata from a URL without taking a screenshot.
91
+
92
+ Returns title, description, OG tags, favicon, headings, links, and more.
93
+ Available on all plans.
94
+
95
+ Args:
96
+ url: The webpage URL to extract metadata from.
97
+
98
+ Returns:
99
+ dict with metadata fields.
100
+ """
101
+ resp = self._request("GET", "/v1/metadata", params={"url": url})
102
+ return resp.json()
103
+
104
+ def render(self, html, width=1200, height=630, format="png", quality=90):
105
+ """Render raw HTML to an image (PNG, JPEG, or WebP).
106
+
107
+ Perfect for OG cards, email previews, and dashboard exports.
108
+ Requires Starter tier or above.
109
+
110
+ Args:
111
+ html: Full HTML string to render (max 2MB).
112
+ width: Viewport width in pixels (default 1200).
113
+ height: Viewport height in pixels (default 630).
114
+ format: Output format — 'png', 'jpeg', or 'webp' (default 'png').
115
+ quality: Image quality 1-100 for jpeg/webp (default 90).
116
+
117
+ Returns:
118
+ bytes (raw image data).
119
+ """
120
+ resp = self._request("POST", "/v1/render", json={
121
+ "html": html,
122
+ "width": width,
123
+ "height": height,
124
+ "format": format,
125
+ "quality": quality,
126
+ })
127
+ return resp.content
128
+
129
+ def usage(self):
130
+ """Return current usage statistics (tier, usage count, limits)."""
131
+ resp = self._request("GET", "/v1/usage")
132
+ return resp.json()
133
+
134
+ def account(self):
135
+ """Return full account information."""
136
+ resp = self._request("GET", "/v1/account")
137
+ return resp.json()
138
+
139
+ def update_settings(self, name=None, allowed_domains=None):
140
+ """Update account settings.
141
+
142
+ Args:
143
+ name: Display name to set.
144
+ allowed_domains: List of allowed domains for the API key.
145
+
146
+ Returns:
147
+ dict with updated settings.
148
+ """
149
+ payload = {}
150
+ if name is not None:
151
+ payload["name"] = name
152
+ if allowed_domains is not None:
153
+ payload["allowed_domains"] = allowed_domains
154
+
155
+ resp = self._request("POST", "/v1/account/settings", json=payload)
156
+ return resp.json()
157
+
158
+ # ─── Monitors (Scheduled Screenshots) ───
159
+
160
+ def create_monitor(self, url, interval_minutes, name=None, params=None, webhook_url=None):
161
+ """Create a new scheduled monitor.
162
+
163
+ Args:
164
+ url: URL to monitor.
165
+ interval_minutes: Capture interval in minutes.
166
+ name: Optional display name.
167
+ params: Optional screenshot options dict (width, height, format, etc.).
168
+ webhook_url: Optional webhook URL for notifications.
169
+
170
+ Returns:
171
+ dict with monitor details.
172
+ """
173
+ payload = {"url": url, "interval_minutes": interval_minutes}
174
+ if name is not None:
175
+ payload["name"] = name
176
+ if params is not None:
177
+ payload["params"] = params
178
+ if webhook_url is not None:
179
+ payload["webhook_url"] = webhook_url
180
+
181
+ resp = self._request("POST", "/v1/monitors", json=payload)
182
+ return resp.json()
183
+
184
+ def list_monitors(self):
185
+ """List all monitors for this API key.
186
+
187
+ Returns:
188
+ list of monitor dicts.
189
+ """
190
+ resp = self._request("GET", "/v1/monitors")
191
+ return resp.json()
192
+
193
+ def get_monitor(self, monitor_id):
194
+ """Get a single monitor by ID.
195
+
196
+ Returns:
197
+ dict with monitor details.
198
+ """
199
+ resp = self._request("GET", f"/v1/monitors/{monitor_id}")
200
+ return resp.json()
201
+
202
+ def update_monitor(self, monitor_id, **kwargs):
203
+ """Update a monitor.
204
+
205
+ Keyword Args:
206
+ name: New display name.
207
+ interval_minutes: New interval.
208
+ status: 'active' or 'paused'.
209
+ params: New screenshot options dict.
210
+ webhook_url: New webhook URL.
211
+
212
+ Returns:
213
+ dict with updated monitor.
214
+ """
215
+ resp = self._request("PATCH", f"/v1/monitors/{monitor_id}", json=kwargs)
216
+ return resp.json()
217
+
218
+ def delete_monitor(self, monitor_id):
219
+ """Delete a monitor and all its snapshots."""
220
+ self._request("DELETE", f"/v1/monitors/{monitor_id}")
221
+
222
+ def list_snapshots(self, monitor_id, limit=50, offset=0):
223
+ """List snapshots for a monitor.
224
+
225
+ Args:
226
+ monitor_id: Monitor ID.
227
+ limit: Max results (default 50).
228
+ offset: Pagination offset.
229
+
230
+ Returns:
231
+ dict with snapshots list, total, limit, offset.
232
+ """
233
+ resp = self._request("GET", f"/v1/monitors/{monitor_id}/snapshots",
234
+ params={"limit": limit, "offset": offset})
235
+ return resp.json()
236
+
237
+ def get_snapshot(self, monitor_id, snapshot_id):
238
+ """Download a specific snapshot image.
239
+
240
+ Returns:
241
+ bytes (raw image data).
242
+ """
243
+ resp = self._request("GET", f"/v1/monitors/{monitor_id}/snapshots/{snapshot_id}")
244
+ return resp.content
245
+
246
+ @staticmethod
247
+ def signup(email, base_url="https://snapapi.tech"):
248
+ """Create a new SnapAPI account. No API key required.
249
+
250
+ Args:
251
+ email: Email address for the new account.
252
+ base_url: API base URL.
253
+
254
+ Returns:
255
+ dict with key, tier, and limit.
256
+ """
257
+ url = f"{base_url.rstrip('/')}/v1/signup"
258
+ resp = requests.post(url, json={"email": email})
259
+
260
+ if not resp.ok:
261
+ try:
262
+ body = resp.json()
263
+ message = body.get("error", body.get("message", resp.reason))
264
+ except ValueError:
265
+ body = None
266
+ message = resp.reason or resp.text
267
+ raise SnapAPIError(resp.status_code, message, body)
268
+
269
+ return resp.json()
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: snapapi-python
3
+ Version: 1.3.0
4
+ Summary: Official Python SDK for SnapAPI — screenshot, metadata extraction, and HTML rendering
5
+ License: MIT
6
+ Keywords: screenshot,api,webpage-capture,snapapi
7
+ Requires-Python: >=3.7
8
+ Requires-Dist: requests>=2.20.0
9
+ Dynamic: keywords
10
+ Dynamic: license
11
+ Dynamic: requires-dist
12
+ Dynamic: requires-python
13
+ Dynamic: summary
@@ -0,0 +1,5 @@
1
+ snapapi.py,sha256=sNkVGGihSbQln8n3kYfeShworXs32B5vf8p5i9Eq0Ug,8726
2
+ snapapi_python-1.3.0.dist-info/METADATA,sha256=PY-9BZ9ybpFJcjDx31o8w5_IWALRCafbKg-6RrmmY3w,385
3
+ snapapi_python-1.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
4
+ snapapi_python-1.3.0.dist-info/top_level.txt,sha256=ixqJtWjxOFIoE_Mirko8xyRTH-3pKC_AYNRLLUuESoA,8
5
+ snapapi_python-1.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ snapapi