flyewin 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.
- flyewin/__init__.py +5 -0
- flyewin/_client.py +490 -0
- flyewin/py.typed +0 -0
- flyewin-0.1.0.dist-info/METADATA +104 -0
- flyewin-0.1.0.dist-info/RECORD +7 -0
- flyewin-0.1.0.dist-info/WHEEL +5 -0
- flyewin-0.1.0.dist-info/top_level.txt +1 -0
flyewin/__init__.py
ADDED
flyewin/_client.py
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
"""Official client for the flye.win APIs.
|
|
2
|
+
|
|
3
|
+
Generated from the API catalog by cmd/sdkgen. Do not edit by hand.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import mimetypes
|
|
10
|
+
import os
|
|
11
|
+
import random
|
|
12
|
+
import socket
|
|
13
|
+
import time
|
|
14
|
+
import urllib.error
|
|
15
|
+
import urllib.parse
|
|
16
|
+
import urllib.request
|
|
17
|
+
import uuid
|
|
18
|
+
from typing import IO, Any, Dict, List, Mapping, Optional, Tuple, Union
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
21
|
+
DEFAULT_BASE_URL = "https://flye.win" + "/v1"
|
|
22
|
+
|
|
23
|
+
#: A file to upload: raw bytes, an open binary file, a path, or (filename, bytes[, content_type]).
|
|
24
|
+
FileInput = Union[bytes, bytearray, IO[bytes], "os.PathLike[str]", str, Tuple[str, bytes], Tuple[str, bytes, str]]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FlyeError(Exception):
|
|
28
|
+
"""Raised for every failed call. ``code`` is stable: "rate_limited", "invalid_api_key"..."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
message: str,
|
|
33
|
+
*,
|
|
34
|
+
status: Optional[int] = None,
|
|
35
|
+
code: str = "error",
|
|
36
|
+
body: Any = None,
|
|
37
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
super().__init__(message)
|
|
40
|
+
self.message = message
|
|
41
|
+
self.status = status
|
|
42
|
+
self.code = code
|
|
43
|
+
self.body = body
|
|
44
|
+
self.headers = dict(headers or {})
|
|
45
|
+
|
|
46
|
+
def __repr__(self) -> str:
|
|
47
|
+
return f"FlyeError(status={self.status!r}, code={self.code!r}, message={self.message!r})"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Response:
|
|
51
|
+
"""Status and headers of the last response (X-RateLimit-*, X-Quota-*, X-Flye-Mode)."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, status: int, headers: Mapping[str, str]) -> None:
|
|
54
|
+
self.status = status
|
|
55
|
+
self.headers = {k.lower(): v for k, v in headers.items()}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _text(value: Any) -> str:
|
|
59
|
+
if isinstance(value, bool):
|
|
60
|
+
return "true" if value else "false"
|
|
61
|
+
return str(value)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _file(value: Any, field: str) -> Tuple[str, bytes, str]:
|
|
65
|
+
if isinstance(value, tuple):
|
|
66
|
+
name, data = value[0], value[1]
|
|
67
|
+
ctype = value[2] if len(value) > 2 else None
|
|
68
|
+
elif isinstance(value, (bytes, bytearray)):
|
|
69
|
+
name, data, ctype = field, bytes(value), None
|
|
70
|
+
elif hasattr(value, "read"):
|
|
71
|
+
data = value.read()
|
|
72
|
+
name, ctype = os.path.basename(getattr(value, "name", field) or field), None
|
|
73
|
+
elif isinstance(value, (str, os.PathLike)):
|
|
74
|
+
path = os.fspath(value)
|
|
75
|
+
with open(path, "rb") as fh:
|
|
76
|
+
data = fh.read()
|
|
77
|
+
name, ctype = os.path.basename(path), None
|
|
78
|
+
else:
|
|
79
|
+
raise FlyeError("File parameters take bytes, an open binary file, a path or (filename, bytes).", code="invalid_file")
|
|
80
|
+
if isinstance(data, str):
|
|
81
|
+
data = data.encode()
|
|
82
|
+
ctype = ctype or mimetypes.guess_type(name)[0] or "application/octet-stream"
|
|
83
|
+
return name, bytes(data), ctype
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _multipart(fields: List[Tuple[str, str]], files: List[Tuple[str, Tuple[str, bytes, str]]]) -> Tuple[bytes, str]:
|
|
87
|
+
boundary = "flyewin-" + uuid.uuid4().hex
|
|
88
|
+
out = bytearray()
|
|
89
|
+
for name, value in fields:
|
|
90
|
+
out += f'--{boundary}\r\nContent-Disposition: form-data; name="{name}"\r\n\r\n'.encode()
|
|
91
|
+
out += value.encode() + b"\r\n"
|
|
92
|
+
for name, (filename, data, ctype) in files:
|
|
93
|
+
safe = filename.replace('"', "")
|
|
94
|
+
out += f'--{boundary}\r\nContent-Disposition: form-data; name="{name}"; filename="{safe}"\r\n'.encode()
|
|
95
|
+
out += f"Content-Type: {ctype}\r\n\r\n".encode()
|
|
96
|
+
out += data + b"\r\n"
|
|
97
|
+
out += f"--{boundary}--\r\n".encode()
|
|
98
|
+
return bytes(out), f"multipart/form-data; boundary={boundary}"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _decode(raw: bytes, ctype: str) -> Any:
|
|
102
|
+
if not raw:
|
|
103
|
+
return None
|
|
104
|
+
if "json" in ctype:
|
|
105
|
+
try:
|
|
106
|
+
return json.loads(raw.decode("utf-8"))
|
|
107
|
+
except ValueError:
|
|
108
|
+
return raw.decode("utf-8", "replace")
|
|
109
|
+
if ctype.startswith("text/"):
|
|
110
|
+
return raw.decode("utf-8", "replace")
|
|
111
|
+
return raw
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _backoff(attempt: int) -> float:
|
|
115
|
+
return min(8.0, 0.5 * 2**attempt) * (0.75 + random.random() * 0.5)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Flye:
|
|
119
|
+
"""Client for the flye.win APIs.
|
|
120
|
+
|
|
121
|
+
>>> from flyewin import Flye
|
|
122
|
+
>>> flye = Flye() # reads FLYE_API_KEY
|
|
123
|
+
>>> flye.utils.uuid(count=2)
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
api_key: Optional[str] = None,
|
|
129
|
+
*,
|
|
130
|
+
base_url: Optional[str] = None,
|
|
131
|
+
timeout: float = 30.0,
|
|
132
|
+
max_retries: int = 2,
|
|
133
|
+
) -> None:
|
|
134
|
+
self.api_key = api_key or os.environ.get("FLYE_API_KEY") or ""
|
|
135
|
+
if not self.api_key:
|
|
136
|
+
raise FlyeError("Missing API key: pass Flye(api_key) or set FLYE_API_KEY.", code="missing_api_key")
|
|
137
|
+
self.base_url = (base_url or os.environ.get("FLYE_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
138
|
+
self.timeout = timeout
|
|
139
|
+
self.max_retries = max_retries
|
|
140
|
+
#: Status and headers of the last response.
|
|
141
|
+
self.last_response: Optional[Response] = None
|
|
142
|
+
#: Stickers — Search stickers and animated GIFs, with ready-to-use links.
|
|
143
|
+
self.stickers = _Stickers(self)
|
|
144
|
+
#: CDN — Image hosting with public links, organized in projects and folders.
|
|
145
|
+
self.cdn = _Cdn(self)
|
|
146
|
+
#: Utils — UUIDs, hashes, base64 and IP inspection in one call.
|
|
147
|
+
self.utils = _Utils(self)
|
|
148
|
+
|
|
149
|
+
def request(
|
|
150
|
+
self,
|
|
151
|
+
method: str,
|
|
152
|
+
path: str,
|
|
153
|
+
params: List[Tuple[str, str, Any]],
|
|
154
|
+
extra: Optional[Dict[str, Any]] = None,
|
|
155
|
+
*,
|
|
156
|
+
default: str = "query",
|
|
157
|
+
timeout: Optional[float] = None,
|
|
158
|
+
) -> Any:
|
|
159
|
+
"""Low-level call used by every method. ``params`` is a list of (name, location, value)."""
|
|
160
|
+
query: List[Tuple[str, str]] = []
|
|
161
|
+
headers = {
|
|
162
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
163
|
+
"Accept": "application/json",
|
|
164
|
+
"User-Agent": f"flyewin-python/{__version__}",
|
|
165
|
+
}
|
|
166
|
+
body_json: Dict[str, Any] = {}
|
|
167
|
+
fields: List[Tuple[str, str]] = []
|
|
168
|
+
files: List[Tuple[str, Tuple[str, bytes, str]]] = []
|
|
169
|
+
items = list(params) + [(k, default, v) for k, v in (extra or {}).items()]
|
|
170
|
+
for name, where, value in items:
|
|
171
|
+
if value is None:
|
|
172
|
+
continue
|
|
173
|
+
if where == "path":
|
|
174
|
+
path = path.replace("{" + name + "}", urllib.parse.quote(_text(value), safe=""))
|
|
175
|
+
elif where == "query":
|
|
176
|
+
for item in value if isinstance(value, (list, tuple)) else [value]:
|
|
177
|
+
query.append((name, _text(item)))
|
|
178
|
+
elif where == "header":
|
|
179
|
+
headers[name] = _text(value)
|
|
180
|
+
elif where == "file":
|
|
181
|
+
files.append((name, _file(value, name)))
|
|
182
|
+
elif where == "form":
|
|
183
|
+
if isinstance(value, (bytes, bytearray, tuple)) or hasattr(value, "read"):
|
|
184
|
+
files.append((name, _file(value, name)))
|
|
185
|
+
else:
|
|
186
|
+
fields.append((name, _text(value)))
|
|
187
|
+
else:
|
|
188
|
+
body_json[name] = value
|
|
189
|
+
if "{" in path:
|
|
190
|
+
raise FlyeError(f"Missing path parameter in {method} {path}.", code="missing_param")
|
|
191
|
+
|
|
192
|
+
data: Optional[bytes] = None
|
|
193
|
+
if files or fields:
|
|
194
|
+
data, headers["Content-Type"] = _multipart(fields, files)
|
|
195
|
+
elif body_json or default == "body":
|
|
196
|
+
data = json.dumps(body_json).encode("utf-8")
|
|
197
|
+
headers["Content-Type"] = "application/json"
|
|
198
|
+
url = self.base_url + path + ("?" + urllib.parse.urlencode(query) if query else "")
|
|
199
|
+
idempotent = method == "GET"
|
|
200
|
+
|
|
201
|
+
attempt = 0
|
|
202
|
+
while True:
|
|
203
|
+
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
204
|
+
try:
|
|
205
|
+
with urllib.request.urlopen(req, timeout=timeout or self.timeout) as resp:
|
|
206
|
+
self.last_response = Response(resp.status, resp.headers)
|
|
207
|
+
return _decode(resp.read(), resp.headers.get("Content-Type", ""))
|
|
208
|
+
except urllib.error.HTTPError as err:
|
|
209
|
+
raw = err.read()
|
|
210
|
+
self.last_response = Response(err.code, err.headers)
|
|
211
|
+
payload = _decode(raw, err.headers.get("Content-Type", ""))
|
|
212
|
+
retry_after = 0.0
|
|
213
|
+
try:
|
|
214
|
+
retry_after = float(err.headers.get("Retry-After") or 0)
|
|
215
|
+
except ValueError:
|
|
216
|
+
pass
|
|
217
|
+
# 429 with a short Retry-After (per-minute limit) and 5xx on reads are worth another try.
|
|
218
|
+
can_retry = (err.code == 429 and 0 < retry_after <= 60) or (idempotent and err.code >= 500)
|
|
219
|
+
if can_retry and attempt < self.max_retries:
|
|
220
|
+
time.sleep(retry_after if err.code == 429 else _backoff(attempt))
|
|
221
|
+
attempt += 1
|
|
222
|
+
continue
|
|
223
|
+
info = payload.get("error") if isinstance(payload, dict) else None
|
|
224
|
+
info = info if isinstance(info, dict) else {}
|
|
225
|
+
raise FlyeError(
|
|
226
|
+
info.get("message") or f"HTTP {err.code}",
|
|
227
|
+
status=err.code,
|
|
228
|
+
code=info.get("code") or "http_error",
|
|
229
|
+
body=payload,
|
|
230
|
+
headers=err.headers,
|
|
231
|
+
) from None
|
|
232
|
+
except (urllib.error.URLError, socket.timeout, TimeoutError, ConnectionError) as err:
|
|
233
|
+
if idempotent and attempt < self.max_retries:
|
|
234
|
+
time.sleep(_backoff(attempt))
|
|
235
|
+
attempt += 1
|
|
236
|
+
continue
|
|
237
|
+
reason = getattr(err, "reason", err)
|
|
238
|
+
timed_out = isinstance(reason, (socket.timeout, TimeoutError))
|
|
239
|
+
raise FlyeError(
|
|
240
|
+
"Request timed out." if timed_out else f"Network error: {reason}",
|
|
241
|
+
code="timeout" if timed_out else "network_error",
|
|
242
|
+
) from err
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class _Stickers:
|
|
246
|
+
"""Stickers — Search stickers and animated GIFs, with ready-to-use links.
|
|
247
|
+
|
|
248
|
+
Docs: https://flye.win/apis/stickers
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
def __init__(self, client: Flye) -> None:
|
|
252
|
+
self._client = client
|
|
253
|
+
|
|
254
|
+
def search(self, *, q: Optional[str] = None, end_user_id: Optional[str] = None, max_wait_ms: Optional[int] = None, limit: Optional[int] = None, type: Optional[str] = None, page: Optional[int] = None, rotate: Optional[bool] = None, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
255
|
+
"""Search stickers
|
|
256
|
+
|
|
257
|
+
Returns the most relevant pack for the term, with a link for each sticker. With rotate=true, each call returns the next pack, so bots don't repeat the same result. A term never searched before may come right away with partial=true, with what we already have, while the full search finishes.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
q: Search term, 2 to 80 characters. Without it, the response has 30 featured stickers, drawn on every call, with trending=true. Example: gato
|
|
261
|
+
end_user_id: Scale plan: identifies the user on your platform, so the limit applies per user and not to the account. Use a stable id, preferably a hash.
|
|
262
|
+
max_wait_ms: Longest wait for a term never searched before, 0 to 15000 ms. Past that, the response returns right away with pending=true and the search continues: repeat the call shortly. Without it, waits for the result. Example: 300
|
|
263
|
+
limit: Stickers per response, 1 to 30. Defaults to 30. Example: 10
|
|
264
|
+
type: all, animated or static. Defaults to all.
|
|
265
|
+
page: Pack position in the ranking, starting at 1. Defaults to 1.
|
|
266
|
+
rotate: true returns the next pack on each call from this account. Ignores page.
|
|
267
|
+
|
|
268
|
+
Docs: https://flye.win/apis/stickers
|
|
269
|
+
"""
|
|
270
|
+
return self._client.request(
|
|
271
|
+
"GET", "/stickers/search", [("q", "query", q), ("X-End-User-Id", "header", end_user_id), ("max_wait_ms", "query", max_wait_ms), ("limit", "query", limit), ("type", "query", type), ("page", "query", page), ("rotate", "query", rotate)], extra, default="query", timeout=timeout
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def list_packs(self, *, q: str, end_user_id: Optional[str] = None, max_wait_ms: Optional[int] = None, limit: Optional[int] = None, page: Optional[int] = None, type: Optional[str] = None, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
275
|
+
"""List packs
|
|
276
|
+
|
|
277
|
+
Lists the packs found for the term, with a cover sticker, so you can build a picker.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
q: Search term, 2 to 80 characters. Example: gato
|
|
281
|
+
end_user_id: Scale plan: identifies the user on your platform, so the limit applies per user and not to the account. Use a stable id, preferably a hash.
|
|
282
|
+
max_wait_ms: Longest wait for a term never searched before, 0 to 15000 ms. Past that, the response returns right away with pending=true and the search continues: repeat the call shortly. Without it, waits for the result. Example: 300
|
|
283
|
+
limit: Packs per page, 1 to 20. Defaults to 10. Example: 5
|
|
284
|
+
page: Page, starting at 1.
|
|
285
|
+
type: all, animated or static. Defaults to all.
|
|
286
|
+
|
|
287
|
+
Docs: https://flye.win/apis/stickers
|
|
288
|
+
"""
|
|
289
|
+
return self._client.request(
|
|
290
|
+
"GET", "/stickers/packs", [("q", "query", q), ("X-End-User-Id", "header", end_user_id), ("max_wait_ms", "query", max_wait_ms), ("limit", "query", limit), ("page", "query", page), ("type", "query", type)], extra, default="query", timeout=timeout
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def get_pack(self, id: str, *, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
294
|
+
"""Stickers in a pack
|
|
295
|
+
|
|
296
|
+
Returns every sticker in a pack by its id.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
id: Pack id, from /search or /packs.
|
|
300
|
+
|
|
301
|
+
Docs: https://flye.win/apis/stickers
|
|
302
|
+
"""
|
|
303
|
+
return self._client.request(
|
|
304
|
+
"GET", "/stickers/packs/{id}", [("id", "path", id)], extra, default="query", timeout=timeout
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
class _Cdn:
|
|
308
|
+
"""CDN — Image hosting with public links, organized in projects and folders.
|
|
309
|
+
|
|
310
|
+
Docs: https://flye.win/apis/cdn
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
def __init__(self, client: Flye) -> None:
|
|
314
|
+
self._client = client
|
|
315
|
+
|
|
316
|
+
def upload(self, *, image: FileInput, project: str, folder: Optional[str] = None, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
317
|
+
"""Upload an image
|
|
318
|
+
|
|
319
|
+
Uploads one image as multipart/form-data. The project is created the first time you use it. Accepts jpg, jpeg, png, gif, webp, svg, up to your plan's per-file limit. The type is checked from the file's content, and SVG with scripts or external links is rejected.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
image: The file. Also accepted under the name file.
|
|
323
|
+
project: Project name: letters, numbers, - and _, up to 48 characters. Uppercase becomes lowercase.
|
|
324
|
+
folder: Folder inside the project, optional, with the same rules as the name.
|
|
325
|
+
|
|
326
|
+
Docs: https://flye.win/apis/cdn
|
|
327
|
+
"""
|
|
328
|
+
return self._client.request(
|
|
329
|
+
"POST", "/cdn/upload", [("image", "file", image), ("project", "form", project), ("folder", "form", folder)], extra, default="form", timeout=timeout
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def list_projects(self, *, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
333
|
+
"""List projects
|
|
334
|
+
|
|
335
|
+
Lists your account's projects, with the image count and storage used by each.
|
|
336
|
+
|
|
337
|
+
Docs: https://flye.win/apis/cdn
|
|
338
|
+
"""
|
|
339
|
+
return self._client.request(
|
|
340
|
+
"GET", "/cdn/projects", [], extra, default="query", timeout=timeout
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
def create_project(self, *, name: str, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
344
|
+
"""Create a project
|
|
345
|
+
|
|
346
|
+
Creates an empty project. Optional: uploading already creates the project when it doesn't exist.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
name: Project name.
|
|
350
|
+
|
|
351
|
+
Docs: https://flye.win/apis/cdn
|
|
352
|
+
"""
|
|
353
|
+
return self._client.request(
|
|
354
|
+
"POST", "/cdn/projects", [("name", "body", name)], extra, default="body", timeout=timeout
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
def get_project(self, project: str, *, folder: Optional[str] = None, page: Optional[int] = None, per_page: Optional[int] = None, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
358
|
+
"""List images
|
|
359
|
+
|
|
360
|
+
Lists the project's images, newest first, and the folders it has.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
project: Project name.
|
|
364
|
+
folder: Only this folder's images. Without it, every image in the project. Use / for only those outside folders.
|
|
365
|
+
page: Page, starting at 1.
|
|
366
|
+
per_page: Items per page, 1 to 100. Defaults to 20.
|
|
367
|
+
|
|
368
|
+
Docs: https://flye.win/apis/cdn
|
|
369
|
+
"""
|
|
370
|
+
return self._client.request(
|
|
371
|
+
"GET", "/cdn/projects/{project}", [("project", "path", project), ("folder", "query", folder), ("page", "query", page), ("per_page", "query", per_page)], extra, default="query", timeout=timeout
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
def get_image(self, id: str, *, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
375
|
+
"""Image details
|
|
376
|
+
|
|
377
|
+
Returns one of your images by the id returned on upload.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
id: Image id.
|
|
381
|
+
|
|
382
|
+
Docs: https://flye.win/apis/cdn
|
|
383
|
+
"""
|
|
384
|
+
return self._client.request(
|
|
385
|
+
"GET", "/cdn/images/{id}", [("id", "path", id)], extra, default="query", timeout=timeout
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
def delete_image(self, id: str, *, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
389
|
+
"""Delete an image
|
|
390
|
+
|
|
391
|
+
Deletes the image and frees the space right away. The link stops working within moments.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
id: Image id.
|
|
395
|
+
|
|
396
|
+
Docs: https://flye.win/apis/cdn
|
|
397
|
+
"""
|
|
398
|
+
return self._client.request(
|
|
399
|
+
"DELETE", "/cdn/images/{id}", [("id", "path", id)], extra, default="query", timeout=timeout
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
def delete_project(self, project: str, *, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
403
|
+
"""Delete a project
|
|
404
|
+
|
|
405
|
+
Deletes the project with all its images and frees the space right away. This can't be undone.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
project: Project name.
|
|
409
|
+
|
|
410
|
+
Docs: https://flye.win/apis/cdn
|
|
411
|
+
"""
|
|
412
|
+
return self._client.request(
|
|
413
|
+
"DELETE", "/cdn/projects/{project}", [("project", "path", project)], extra, default="query", timeout=timeout
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
def get_usage(self, *, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
417
|
+
"""Storage and limits
|
|
418
|
+
|
|
419
|
+
Shows the storage used, your plan's limits and the largest file accepted. A null limit means unlimited.
|
|
420
|
+
|
|
421
|
+
Docs: https://flye.win/apis/cdn
|
|
422
|
+
"""
|
|
423
|
+
return self._client.request(
|
|
424
|
+
"GET", "/cdn/usage", [], extra, default="query", timeout=timeout
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
class _Utils:
|
|
428
|
+
"""Utils — UUIDs, hashes, base64 and IP inspection in one call.
|
|
429
|
+
|
|
430
|
+
Docs: https://flye.win/apis/utils
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
def __init__(self, client: Flye) -> None:
|
|
434
|
+
self._client = client
|
|
435
|
+
|
|
436
|
+
def uuid(self, *, count: Optional[int] = None, version: Optional[int] = None, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
437
|
+
"""Generate UUIDs
|
|
438
|
+
|
|
439
|
+
Generates 1 to 100 UUIDs. Version 7 sorts by time, which suits primary keys.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
count: How many, from 1 to 100. Defaults to 1. Example: 2
|
|
443
|
+
version: 4 (random) or 7 (time-ordered). Defaults to 4. Example: 7
|
|
444
|
+
|
|
445
|
+
Docs: https://flye.win/apis/utils
|
|
446
|
+
"""
|
|
447
|
+
return self._client.request(
|
|
448
|
+
"GET", "/utils/uuid", [("count", "query", count), ("version", "query", version)], extra, default="query", timeout=timeout
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
def hash(self, *, text: str, algo: Optional[str] = None, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
452
|
+
"""Compute a hash
|
|
453
|
+
|
|
454
|
+
Hashes the given text and returns it as hex.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
text: Input text (up to 64 KB). Example: flye
|
|
458
|
+
algo: md5, sha1, sha256 or sha512. Defaults to sha256. Example: sha256
|
|
459
|
+
|
|
460
|
+
Docs: https://flye.win/apis/utils
|
|
461
|
+
"""
|
|
462
|
+
return self._client.request(
|
|
463
|
+
"GET", "/utils/hash", [("text", "query", text), ("algo", "query", algo)], extra, default="query", timeout=timeout
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
def base64(self, *, text: str, mode: Optional[str] = None, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
467
|
+
"""Encode / decode base64
|
|
468
|
+
|
|
469
|
+
Converts text to and from standard base64 (RFC 4648).
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
text: Content to convert.
|
|
473
|
+
mode: encode or decode. Defaults to encode.
|
|
474
|
+
|
|
475
|
+
Docs: https://flye.win/apis/utils
|
|
476
|
+
"""
|
|
477
|
+
return self._client.request(
|
|
478
|
+
"POST", "/utils/base64", [("text", "body", text), ("mode", "body", mode)], extra, default="body", timeout=timeout
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
def ip(self, *, timeout: Optional[float] = None, **extra: Any) -> Any:
|
|
482
|
+
"""Source IP
|
|
483
|
+
|
|
484
|
+
Returns the IP and user agent your request reached the platform with.
|
|
485
|
+
|
|
486
|
+
Docs: https://flye.win/apis/utils
|
|
487
|
+
"""
|
|
488
|
+
return self._client.request(
|
|
489
|
+
"GET", "/utils/ip", [], extra, default="query", timeout=timeout
|
|
490
|
+
)
|
flyewin/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: flyewin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official client for the flye.win APIs. No dependencies.
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://flye.win
|
|
7
|
+
Project-URL: Documentation, https://flye.win/docs
|
|
8
|
+
Keywords: flye,flye.win,api,sdk
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Typing :: Typed
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# flyewin
|
|
17
|
+
|
|
18
|
+
Official Python client for the [flye.win](https://flye.win) APIs. No dependencies, Python 3.8+.
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
pip install flyewin
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from flyewin import Flye
|
|
26
|
+
|
|
27
|
+
flye = Flye() # reads FLYE_API_KEY, or Flye("flye_sk_...")
|
|
28
|
+
|
|
29
|
+
print(flye.utils.uuid(count=2))
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Test keys
|
|
33
|
+
|
|
34
|
+
Keys that start with `flye_test_` answer with the documented examples, need no plan and
|
|
35
|
+
use no quota (30 requests/minute, 1,000/day). Use them while building and in CI, then
|
|
36
|
+
switch to a `flye_sk_` key. Create both at https://flye.win/dashboard/keys.
|
|
37
|
+
|
|
38
|
+
## Errors
|
|
39
|
+
|
|
40
|
+
Every failure raises `FlyeError` with a stable `code`:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from flyewin import Flye, FlyeError
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
flye.utils.hash(text="hi")
|
|
47
|
+
except FlyeError as err:
|
|
48
|
+
if err.code == "rate_limited":
|
|
49
|
+
... # err.status == 429
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Network errors and 5xx on GET requests are retried twice with backoff, and so is a `429`
|
|
53
|
+
with a short `Retry-After`. Change it with `Flye(max_retries=0)`.
|
|
54
|
+
|
|
55
|
+
Rate limit and quota headers of the last call are in `flye.last_response.headers`
|
|
56
|
+
(`x-ratelimit-remaining`, `x-quota-remaining`).
|
|
57
|
+
|
|
58
|
+
## Options
|
|
59
|
+
|
|
60
|
+
`Flye(api_key=None, *, base_url="https://flye.win/v1", timeout=30.0, max_retries=2)`
|
|
61
|
+
|
|
62
|
+
Every method also takes `timeout=` for that call.
|
|
63
|
+
|
|
64
|
+
## Methods
|
|
65
|
+
|
|
66
|
+
### Stickers — `flye.stickers`
|
|
67
|
+
|
|
68
|
+
Search stickers and animated GIFs, with ready-to-use links. Docs: https://flye.win/apis/stickers
|
|
69
|
+
|
|
70
|
+
| Method | HTTP | |
|
|
71
|
+
| ------ | ---- | - |
|
|
72
|
+
| `stickers.search()` | `GET /stickers/search` | Search stickers |
|
|
73
|
+
| `stickers.list_packs(q="gato")` | `GET /stickers/packs` | List packs |
|
|
74
|
+
| `stickers.get_pack("...")` | `GET /stickers/packs/{id}` | Stickers in a pack |
|
|
75
|
+
|
|
76
|
+
### CDN — `flye.cdn`
|
|
77
|
+
|
|
78
|
+
Image hosting with public links, organized in projects and folders. Docs: https://flye.win/apis/cdn
|
|
79
|
+
|
|
80
|
+
| Method | HTTP | |
|
|
81
|
+
| ------ | ---- | - |
|
|
82
|
+
| `cdn.upload(image=open("photo.jpg", "rb"), project="...")` | `POST /cdn/upload` | Upload an image |
|
|
83
|
+
| `cdn.list_projects()` | `GET /cdn/projects` | List projects |
|
|
84
|
+
| `cdn.create_project(name="...")` | `POST /cdn/projects` | Create a project |
|
|
85
|
+
| `cdn.get_project("...")` | `GET /cdn/projects/{project}` | List images |
|
|
86
|
+
| `cdn.get_image("...")` | `GET /cdn/images/{id}` | Image details |
|
|
87
|
+
| `cdn.delete_image("...")` | `DELETE /cdn/images/{id}` | Delete an image |
|
|
88
|
+
| `cdn.delete_project("...")` | `DELETE /cdn/projects/{project}` | Delete a project |
|
|
89
|
+
| `cdn.get_usage()` | `GET /cdn/usage` | Storage and limits |
|
|
90
|
+
|
|
91
|
+
### Utils — `flye.utils`
|
|
92
|
+
|
|
93
|
+
UUIDs, hashes, base64 and IP inspection in one call. Docs: https://flye.win/apis/utils
|
|
94
|
+
|
|
95
|
+
| Method | HTTP | |
|
|
96
|
+
| ------ | ---- | - |
|
|
97
|
+
| `utils.uuid()` | `GET /utils/uuid` | Generate UUIDs |
|
|
98
|
+
| `utils.hash(text="flye")` | `GET /utils/hash` | Compute a hash |
|
|
99
|
+
| `utils.base64(text="...")` | `POST /utils/base64` | Encode / decode base64 |
|
|
100
|
+
| `utils.ip()` | `GET /utils/ip` | Source IP |
|
|
101
|
+
|
|
102
|
+
Uploads take bytes, an open binary file, a path, or `(filename, bytes)`.
|
|
103
|
+
|
|
104
|
+
Full reference and the OpenAPI spec: https://flye.win/docs · https://flye.win/openapi.json
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
flyewin/__init__.py,sha256=wUPO4ZBfJOa8-iBVx3Wt8_X8z42JvO3tVFZzb577o64,255
|
|
2
|
+
flyewin/_client.py,sha256=6fqPd8X8bYi7VJHUjJAuIziKtCBu3b77C2QAjst9ZWc,20685
|
|
3
|
+
flyewin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
flyewin-0.1.0.dist-info/METADATA,sha256=B4ESIpIXwust9LzAUm4MZ1cz_PEw7jVbxfiusbE3ndM,3577
|
|
5
|
+
flyewin-0.1.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
6
|
+
flyewin-0.1.0.dist-info/top_level.txt,sha256=mrJO_MYp7HsNnZu_aECHZdGTUi2XDCBkCI7yXr3O3rc,8
|
|
7
|
+
flyewin-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flyewin
|