snapapi-python 1.3.0__tar.gz

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.
@@ -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,137 @@
1
+ # SnapAPI Python SDK
2
+
3
+ Official Python SDK for [SnapAPI](https://snapapi.tech) — capture any website as an image.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install snapapi-python
9
+ ```
10
+
11
+ Or install from source:
12
+
13
+ ```bash
14
+ pip install .
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```python
20
+ from snapapi import SnapAPI
21
+
22
+ snap = SnapAPI("snap_your_key_here")
23
+
24
+ # Basic screenshot — returns PNG bytes
25
+ image = snap.screenshot("example.com")
26
+ with open("screenshot.png", "wb") as f:
27
+ f.write(image)
28
+ ```
29
+
30
+ ## Options
31
+
32
+ ```python
33
+ # WebP format with dark mode and full-page capture
34
+ webp = snap.screenshot("example.com", format="webp", dark_mode=True, full_page=True)
35
+
36
+ # Mobile screenshot (iPhone 14)
37
+ mobile = snap.screenshot("example.com", device="iphone14")
38
+
39
+ # Custom viewport
40
+ image = snap.screenshot("example.com", width=1920, height=1080)
41
+
42
+ # Capture a specific element
43
+ header = snap.screenshot("example.com", selector="header")
44
+
45
+ # Add a delay before capture (milliseconds)
46
+ image = snap.screenshot("example.com", delay=2000)
47
+ ```
48
+
49
+ ## Metadata Mode
50
+
51
+ ```python
52
+ meta = snap.screenshot("example.com", meta=True)
53
+ print(meta["title"], meta["description"])
54
+ print(meta["headings"]) # [{"level": 1, "text": "..."}, ...]
55
+ print(meta["links"]) # [{"text": "...", "href": "..."}, ...]
56
+ print(meta["text"]) # Extracted visible text (first 2000 chars)
57
+ ```
58
+
59
+ ## Account & Usage
60
+
61
+ ```python
62
+ # Check usage
63
+ usage = snap.usage()
64
+ print(f"Used {usage['usage']} of {usage['limit']}")
65
+
66
+ # Account info
67
+ account = snap.account()
68
+
69
+ # Update settings
70
+ snap.update_settings(name="My App", allowed_domains=["example.com"])
71
+ ```
72
+
73
+ ## Scheduled Monitors
74
+
75
+ ```python
76
+ # Create a monitor — captures every 15 minutes
77
+ monitor = snap.create_monitor(
78
+ url="https://example.com",
79
+ interval_minutes=15,
80
+ name="Homepage",
81
+ params={"format": "webp", "full_page": True},
82
+ webhook_url="https://your-app.com/webhook",
83
+ )
84
+
85
+ # List all monitors
86
+ monitors = snap.list_monitors()
87
+
88
+ # Pause / resume
89
+ snap.update_monitor(monitor["id"], status="paused")
90
+ snap.update_monitor(monitor["id"], status="active")
91
+
92
+ # Browse snapshots
93
+ result = snap.list_snapshots(monitor["id"], limit=10)
94
+ for s in result["snapshots"]:
95
+ print(s["taken_at"], s["file_size"])
96
+
97
+ # Download a snapshot
98
+ image = snap.get_snapshot(monitor["id"], result["snapshots"][0]["id"])
99
+ with open("snapshot.webp", "wb") as f:
100
+ f.write(image)
101
+
102
+ # Delete a monitor (cascades to snapshots)
103
+ snap.delete_monitor(monitor["id"])
104
+ ```
105
+
106
+ > Monitors require **Starter** tier or above. See [pricing](https://snapapi.tech/#pricing) for limits.
107
+
108
+ ## Sign Up
109
+
110
+ ```python
111
+ from snapapi import SnapAPI
112
+
113
+ result = SnapAPI.signup("you@example.com")
114
+ print(f"Your API key: {result['key']}")
115
+ ```
116
+
117
+ ## Error Handling
118
+
119
+ ```python
120
+ from snapapi import SnapAPI, SnapAPIError
121
+
122
+ snap = SnapAPI("snap_your_key_here")
123
+
124
+ try:
125
+ image = snap.screenshot("example.com")
126
+ except SnapAPIError as e:
127
+ print(f"Status: {e.status}, Message: {e.message}")
128
+ ```
129
+
130
+ ## Requirements
131
+
132
+ - Python 3.7+
133
+ - `requests` >= 2.20.0
134
+
135
+ ## License
136
+
137
+ MIT
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,12 @@
1
+ from setuptools import setup
2
+
3
+ setup(
4
+ name="snapapi-python",
5
+ version="1.3.0",
6
+ description="Official Python SDK for SnapAPI — screenshot, metadata extraction, and HTML rendering",
7
+ py_modules=["snapapi"],
8
+ python_requires=">=3.7",
9
+ install_requires=["requests>=2.20.0"],
10
+ license="MIT",
11
+ keywords=["screenshot", "api", "webpage-capture", "snapapi"],
12
+ )
@@ -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,8 @@
1
+ README.md
2
+ setup.py
3
+ snapapi.py
4
+ snapapi_python.egg-info/PKG-INFO
5
+ snapapi_python.egg-info/SOURCES.txt
6
+ snapapi_python.egg-info/dependency_links.txt
7
+ snapapi_python.egg-info/requires.txt
8
+ snapapi_python.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ requests>=2.20.0