gigai-tools 0.1.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.
- gigai_tools-0.1.0/LICENSE +21 -0
- gigai_tools-0.1.0/PKG-INFO +89 -0
- gigai_tools-0.1.0/README.md +70 -0
- gigai_tools-0.1.0/gigai_tools/__init__.py +3 -0
- gigai_tools-0.1.0/gigai_tools/client.py +219 -0
- gigai_tools-0.1.0/gigai_tools.egg-info/PKG-INFO +89 -0
- gigai_tools-0.1.0/gigai_tools.egg-info/SOURCES.txt +9 -0
- gigai_tools-0.1.0/gigai_tools.egg-info/dependency_links.txt +1 -0
- gigai_tools-0.1.0/gigai_tools.egg-info/top_level.txt +1 -0
- gigai_tools-0.1.0/pyproject.toml +27 -0
- gigai_tools-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gigai Kripa Services
|
|
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.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gigai-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Client for the GigAI Tools API - run PDF, image, SVG and QR tools from Python. Free plan, no dependencies.
|
|
5
|
+
Author-email: Gigai Kripa Services <hello@gigaikripaservices.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://gigai.tools/developers
|
|
8
|
+
Project-URL: Documentation, https://gigai.tools/developers
|
|
9
|
+
Keywords: pdf,merge,split,image,convert,resize,compress,qr,svg,api
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# gigai-tools
|
|
21
|
+
|
|
22
|
+
Python client for the [GigAI Tools API](https://gigai.tools/developers). Run PDF, image, SVG and QR tools from code. The free plan gives 1,000 tasks a month with no card.
|
|
23
|
+
|
|
24
|
+
No dependencies. Python 3.9+.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
pip install gigai-tools
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Get a key
|
|
33
|
+
|
|
34
|
+
Create an account at [gigai.tools/developers](https://gigai.tools/developers) and make an API key in the dashboard. Set it as `GIGAI_API_KEY` or pass `api_key=`.
|
|
35
|
+
|
|
36
|
+
## One call
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from gigai_tools import Client
|
|
40
|
+
|
|
41
|
+
client = Client()
|
|
42
|
+
|
|
43
|
+
# Merge two PDFs
|
|
44
|
+
client.run("merge-pdf", files=["a.pdf", "b.pdf"], output="merged.pdf")
|
|
45
|
+
|
|
46
|
+
# Compress an image to fit under 200 KB
|
|
47
|
+
client.run("compress-image", files=["photo.jpg"], options={"target_kb": 200}, output="small.jpg")
|
|
48
|
+
|
|
49
|
+
# Generate a QR code (no input file - the text is the payload)
|
|
50
|
+
client.run("generate-qr", options={"text": "https://example.com"}, output="qr.png")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`run()` imports your files, runs the tool, exports the result and downloads it. Files can also be `https://` URLs and the server fetches them.
|
|
54
|
+
|
|
55
|
+
## Tools
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
for tool in client.tools():
|
|
59
|
+
print(tool["slug"], "-", tool["description"])
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Live engines: merge-pdf, split-pdf, rotate-pdf, pdf-to-text, convert-image, resize-image, compress-image, optimize-svg, markdown-to-html, generate-qr. The catalog only lists tools the API can actually run.
|
|
63
|
+
|
|
64
|
+
## Job graphs
|
|
65
|
+
|
|
66
|
+
Chain tools in one job. Each task names its inputs and the server runs them in dependency order:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
job = client.create_job({
|
|
70
|
+
"in": {"operation": "import/url", "url": "https://example.com/report.pdf"},
|
|
71
|
+
"rot": {"operation": "rotate-pdf", "input": ["in"], "angle": 90},
|
|
72
|
+
"txt": {"operation": "pdf-to-text", "input": ["rot"]},
|
|
73
|
+
"out": {"operation": "export/url", "input": ["txt"]},
|
|
74
|
+
}, retention_s=600)
|
|
75
|
+
|
|
76
|
+
job = client.wait(job["id"])
|
|
77
|
+
export = next(t for t in job["tasks"] if t["name"] == "out")
|
|
78
|
+
client.download(export["result"]["files"][0]["url"], "report.txt")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`Idempotency-Key` makes retries safe: pass `idempotency_key=` and the same key returns the same job instead of running the work twice.
|
|
82
|
+
|
|
83
|
+
## Errors
|
|
84
|
+
|
|
85
|
+
Every failure raises `GigaiError` with a machine-readable `code` (`invalid_option`, `file_too_large`, `quota_exceeded`, `target_unreachable`, ...), the human message, and the offending parameter when there is one.
|
|
86
|
+
|
|
87
|
+
## Privacy
|
|
88
|
+
|
|
89
|
+
File contents are never logged. Results are deleted after the retention you choose (default 1 hour, `retention_s=0` means one download or 60 seconds) and deletion is verified. The web tools at [gigai.tools](https://gigai.tools) never need an account - accounts exist only for this API.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# gigai-tools
|
|
2
|
+
|
|
3
|
+
Python client for the [GigAI Tools API](https://gigai.tools/developers). Run PDF, image, SVG and QR tools from code. The free plan gives 1,000 tasks a month with no card.
|
|
4
|
+
|
|
5
|
+
No dependencies. Python 3.9+.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
pip install gigai-tools
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Get a key
|
|
14
|
+
|
|
15
|
+
Create an account at [gigai.tools/developers](https://gigai.tools/developers) and make an API key in the dashboard. Set it as `GIGAI_API_KEY` or pass `api_key=`.
|
|
16
|
+
|
|
17
|
+
## One call
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from gigai_tools import Client
|
|
21
|
+
|
|
22
|
+
client = Client()
|
|
23
|
+
|
|
24
|
+
# Merge two PDFs
|
|
25
|
+
client.run("merge-pdf", files=["a.pdf", "b.pdf"], output="merged.pdf")
|
|
26
|
+
|
|
27
|
+
# Compress an image to fit under 200 KB
|
|
28
|
+
client.run("compress-image", files=["photo.jpg"], options={"target_kb": 200}, output="small.jpg")
|
|
29
|
+
|
|
30
|
+
# Generate a QR code (no input file - the text is the payload)
|
|
31
|
+
client.run("generate-qr", options={"text": "https://example.com"}, output="qr.png")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`run()` imports your files, runs the tool, exports the result and downloads it. Files can also be `https://` URLs and the server fetches them.
|
|
35
|
+
|
|
36
|
+
## Tools
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
for tool in client.tools():
|
|
40
|
+
print(tool["slug"], "-", tool["description"])
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Live engines: merge-pdf, split-pdf, rotate-pdf, pdf-to-text, convert-image, resize-image, compress-image, optimize-svg, markdown-to-html, generate-qr. The catalog only lists tools the API can actually run.
|
|
44
|
+
|
|
45
|
+
## Job graphs
|
|
46
|
+
|
|
47
|
+
Chain tools in one job. Each task names its inputs and the server runs them in dependency order:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
job = client.create_job({
|
|
51
|
+
"in": {"operation": "import/url", "url": "https://example.com/report.pdf"},
|
|
52
|
+
"rot": {"operation": "rotate-pdf", "input": ["in"], "angle": 90},
|
|
53
|
+
"txt": {"operation": "pdf-to-text", "input": ["rot"]},
|
|
54
|
+
"out": {"operation": "export/url", "input": ["txt"]},
|
|
55
|
+
}, retention_s=600)
|
|
56
|
+
|
|
57
|
+
job = client.wait(job["id"])
|
|
58
|
+
export = next(t for t in job["tasks"] if t["name"] == "out")
|
|
59
|
+
client.download(export["result"]["files"][0]["url"], "report.txt")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`Idempotency-Key` makes retries safe: pass `idempotency_key=` and the same key returns the same job instead of running the work twice.
|
|
63
|
+
|
|
64
|
+
## Errors
|
|
65
|
+
|
|
66
|
+
Every failure raises `GigaiError` with a machine-readable `code` (`invalid_option`, `file_too_large`, `quota_exceeded`, `target_unreachable`, ...), the human message, and the offending parameter when there is one.
|
|
67
|
+
|
|
68
|
+
## Privacy
|
|
69
|
+
|
|
70
|
+
File contents are never logged. Results are deleted after the retention you choose (default 1 hour, `retention_s=0` means one download or 60 seconds) and deletion is verified. The web tools at [gigai.tools](https://gigai.tools) never need an account - accounts exist only for this API.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""GigAI Tools API client. Stdlib only - no dependencies to install or audit.
|
|
2
|
+
|
|
3
|
+
from gigai_tools import Client
|
|
4
|
+
|
|
5
|
+
client = Client() # reads GIGAI_API_KEY
|
|
6
|
+
paths = client.run("merge-pdf", files=["a.pdf", "b.pdf"], output="merged.pdf")
|
|
7
|
+
|
|
8
|
+
The low-level surface (create_job, wait, download) mirrors the HTTP API
|
|
9
|
+
one to one. run() is the convenience wrapper: import -> tool -> export ->
|
|
10
|
+
download in a single call.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import base64
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import time
|
|
19
|
+
import urllib.error
|
|
20
|
+
import urllib.request
|
|
21
|
+
from typing import Any, Optional, Union
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
|
|
25
|
+
DEFAULT_BASE = "https://api.gigai.tools/v1"
|
|
26
|
+
FINAL_STATES = ("finished", "error", "cancelled")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class GigaiError(Exception):
|
|
30
|
+
"""An error answer from the API, with its machine-readable code."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, status: int, code: str, message: str, param: Optional[str] = None):
|
|
33
|
+
super().__init__(f"{code}: {message}")
|
|
34
|
+
self.status = status
|
|
35
|
+
self.code = code
|
|
36
|
+
self.message = message
|
|
37
|
+
self.param = param
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Client:
|
|
41
|
+
def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None,
|
|
42
|
+
timeout: float = 120.0):
|
|
43
|
+
self.api_key = api_key or os.environ.get("GIGAI_API_KEY", "")
|
|
44
|
+
self.base_url = (base_url or os.environ.get("GIGAI_API_BASE", DEFAULT_BASE)).rstrip("/")
|
|
45
|
+
self.timeout = timeout
|
|
46
|
+
if not self.api_key:
|
|
47
|
+
raise GigaiError(0, "no_api_key",
|
|
48
|
+
"No API key. Pass api_key= or set GIGAI_API_KEY. "
|
|
49
|
+
"Keys come from https://gigai.tools/developers/dashboard")
|
|
50
|
+
|
|
51
|
+
# ------------------------------------------------------------------ http
|
|
52
|
+
|
|
53
|
+
def _request(self, method: str, path: str, body: Any = None,
|
|
54
|
+
headers: Optional[dict] = None) -> Any:
|
|
55
|
+
req = urllib.request.Request(
|
|
56
|
+
self.base_url + path,
|
|
57
|
+
data=json.dumps(body).encode() if body is not None else None,
|
|
58
|
+
method=method,
|
|
59
|
+
headers={
|
|
60
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
"User-Agent": f"gigai-tools-python/{__version__}",
|
|
63
|
+
**(headers or {}),
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
try:
|
|
67
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
68
|
+
return json.loads(resp.read() or b"{}")
|
|
69
|
+
except urllib.error.HTTPError as exc:
|
|
70
|
+
raise self._as_error(exc) from None
|
|
71
|
+
except urllib.error.URLError as exc:
|
|
72
|
+
raise GigaiError(0, "network_error", f"Could not reach {self.base_url}: {exc.reason}") from None
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def _as_error(exc: urllib.error.HTTPError) -> GigaiError:
|
|
76
|
+
try:
|
|
77
|
+
payload = json.loads(exc.read())
|
|
78
|
+
err = payload.get("detail", payload).get("error", {})
|
|
79
|
+
return GigaiError(exc.code, err.get("code", "http_error"),
|
|
80
|
+
err.get("message", str(exc)), err.get("param"))
|
|
81
|
+
except Exception:
|
|
82
|
+
return GigaiError(exc.code, "http_error", f"HTTP {exc.code}")
|
|
83
|
+
|
|
84
|
+
# ----------------------------------------------------------------- tools
|
|
85
|
+
|
|
86
|
+
def tools(self) -> list:
|
|
87
|
+
return self._request("GET", "/tools")["tools"]
|
|
88
|
+
|
|
89
|
+
def tool(self, slug: str) -> dict:
|
|
90
|
+
return self._request("GET", f"/tools/{slug}")
|
|
91
|
+
|
|
92
|
+
# ------------------------------------------------------------------ jobs
|
|
93
|
+
|
|
94
|
+
def create_job(self, tasks: dict, tag: Optional[str] = None,
|
|
95
|
+
retention_s: Optional[int] = None,
|
|
96
|
+
idempotency_key: Optional[str] = None) -> dict:
|
|
97
|
+
body: dict[str, Any] = {"tasks": tasks}
|
|
98
|
+
if tag is not None:
|
|
99
|
+
body["tag"] = tag
|
|
100
|
+
if retention_s is not None:
|
|
101
|
+
body["retention_s"] = retention_s
|
|
102
|
+
headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
|
|
103
|
+
return self._request("POST", "/jobs", body, headers)
|
|
104
|
+
|
|
105
|
+
def job(self, job_id: str) -> dict:
|
|
106
|
+
return self._request("GET", f"/jobs/{job_id}")
|
|
107
|
+
|
|
108
|
+
def jobs(self, status: Optional[str] = None, limit: int = 20) -> list:
|
|
109
|
+
qs = f"?limit={limit}" + (f"&status={status}" if status else "")
|
|
110
|
+
return self._request("GET", f"/jobs{qs}")["jobs"]
|
|
111
|
+
|
|
112
|
+
def wait(self, job_id: str, timeout: float = 600.0) -> dict:
|
|
113
|
+
"""Block until the job settles (finished, error or cancelled)."""
|
|
114
|
+
deadline = time.time() + timeout
|
|
115
|
+
while True:
|
|
116
|
+
job = self._request("GET", f"/jobs/{job_id}/wait")
|
|
117
|
+
if job["status"] in FINAL_STATES:
|
|
118
|
+
return job
|
|
119
|
+
if time.time() > deadline:
|
|
120
|
+
raise GigaiError(0, "wait_timeout",
|
|
121
|
+
f"Job {job_id} still {job['status']} after {timeout}s.")
|
|
122
|
+
|
|
123
|
+
def cancel(self, job_id: str) -> dict:
|
|
124
|
+
"""Cancel outstanding work and delete the job's files immediately."""
|
|
125
|
+
return self._request("DELETE", f"/jobs/{job_id}")
|
|
126
|
+
|
|
127
|
+
# ----------------------------------------------------------------- files
|
|
128
|
+
|
|
129
|
+
def file(self, file_id: str) -> dict:
|
|
130
|
+
return self._request("GET", f"/files/{file_id}")
|
|
131
|
+
|
|
132
|
+
def delete_file(self, file_id: str) -> dict:
|
|
133
|
+
return self._request("DELETE", f"/files/{file_id}")
|
|
134
|
+
|
|
135
|
+
def _localize(self, url: str) -> str:
|
|
136
|
+
"""Point a signed URL at this client's base host. The signature covers
|
|
137
|
+
the file id and expiry, not the host, so the base can differ (tunnel,
|
|
138
|
+
staging, or the api. host before the server config catches up)."""
|
|
139
|
+
if url.startswith(self.base_url):
|
|
140
|
+
return url
|
|
141
|
+
idx = url.find("/v1/")
|
|
142
|
+
return self.base_url + url[idx + 3:] if idx >= 0 else url
|
|
143
|
+
|
|
144
|
+
def download(self, url: str, path: str) -> str:
|
|
145
|
+
"""Save a signed result URL to a local file."""
|
|
146
|
+
req = urllib.request.Request(self._localize(url),
|
|
147
|
+
headers={"User-Agent": f"gigai-tools-python/{__version__}"})
|
|
148
|
+
try:
|
|
149
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp, open(path, "wb") as out:
|
|
150
|
+
while chunk := resp.read(1 << 16):
|
|
151
|
+
out.write(chunk)
|
|
152
|
+
except urllib.error.HTTPError as exc:
|
|
153
|
+
raise self._as_error(exc) from None
|
|
154
|
+
return path
|
|
155
|
+
|
|
156
|
+
# -------------------------------------------------------------- webhooks
|
|
157
|
+
|
|
158
|
+
def webhooks(self) -> list:
|
|
159
|
+
return self._request("GET", "/webhooks")["webhooks"]
|
|
160
|
+
|
|
161
|
+
def create_webhook(self, url: str, events: Optional[list] = None) -> dict:
|
|
162
|
+
body: dict[str, Any] = {"url": url}
|
|
163
|
+
if events:
|
|
164
|
+
body["events"] = events
|
|
165
|
+
return self._request("POST", "/webhooks", body)
|
|
166
|
+
|
|
167
|
+
def delete_webhook(self, webhook_id: str) -> dict:
|
|
168
|
+
return self._request("DELETE", f"/webhooks/{webhook_id}")
|
|
169
|
+
|
|
170
|
+
# --------------------------------------------------------------- one-call
|
|
171
|
+
|
|
172
|
+
def run(self, operation: str, files: Union[str, list, None] = None,
|
|
173
|
+
options: Optional[dict] = None, output: Optional[str] = None,
|
|
174
|
+
retention_s: Optional[int] = None, timeout: float = 600.0) -> list:
|
|
175
|
+
"""Run one tool start to finish. Returns the list of saved file paths.
|
|
176
|
+
|
|
177
|
+
files: local paths, or http(s) URLs to import server-side. Omit for
|
|
178
|
+
tools that take no input (generate-qr).
|
|
179
|
+
output: a file path (single result) or a directory. Default: the
|
|
180
|
+
current directory, using the API's result filenames.
|
|
181
|
+
"""
|
|
182
|
+
if isinstance(files, str):
|
|
183
|
+
files = [files]
|
|
184
|
+
tasks: dict[str, Any] = {}
|
|
185
|
+
names = []
|
|
186
|
+
for i, f in enumerate(files or []):
|
|
187
|
+
name = f"in{i}"
|
|
188
|
+
names.append(name)
|
|
189
|
+
if f.startswith(("http://", "https://")):
|
|
190
|
+
tasks[name] = {"operation": "import/url", "url": f}
|
|
191
|
+
else:
|
|
192
|
+
with open(f, "rb") as fh:
|
|
193
|
+
data = fh.read()
|
|
194
|
+
tasks[name] = {"operation": "import/base64",
|
|
195
|
+
"file": base64.b64encode(data).decode(),
|
|
196
|
+
"filename": os.path.basename(f)}
|
|
197
|
+
tasks["work"] = {"operation": operation, **(options or {})}
|
|
198
|
+
if names:
|
|
199
|
+
tasks["work"]["input"] = names
|
|
200
|
+
tasks["out"] = {"operation": "export/url", "input": ["work"]}
|
|
201
|
+
|
|
202
|
+
job = self.create_job(tasks, retention_s=retention_s)
|
|
203
|
+
job = self.wait(job["id"], timeout=timeout)
|
|
204
|
+
if job["status"] != "finished":
|
|
205
|
+
failed = next((t for t in job["tasks"] if t.get("error")), None)
|
|
206
|
+
err = (failed or {}).get("error", {})
|
|
207
|
+
raise GigaiError(0, err.get("code", "job_" + job["status"]),
|
|
208
|
+
err.get("message", f"The job ended {job['status']}."))
|
|
209
|
+
|
|
210
|
+
export = next(t for t in job["tasks"] if t["name"] == "out")
|
|
211
|
+
saved = []
|
|
212
|
+
results = export["result"]["files"]
|
|
213
|
+
for r in results:
|
|
214
|
+
if output and len(results) == 1 and not os.path.isdir(output):
|
|
215
|
+
path = output
|
|
216
|
+
else:
|
|
217
|
+
path = os.path.join(output or ".", r["filename"])
|
|
218
|
+
saved.append(self.download(r["url"], path))
|
|
219
|
+
return saved
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gigai-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Client for the GigAI Tools API - run PDF, image, SVG and QR tools from Python. Free plan, no dependencies.
|
|
5
|
+
Author-email: Gigai Kripa Services <hello@gigaikripaservices.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://gigai.tools/developers
|
|
8
|
+
Project-URL: Documentation, https://gigai.tools/developers
|
|
9
|
+
Keywords: pdf,merge,split,image,convert,resize,compress,qr,svg,api
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# gigai-tools
|
|
21
|
+
|
|
22
|
+
Python client for the [GigAI Tools API](https://gigai.tools/developers). Run PDF, image, SVG and QR tools from code. The free plan gives 1,000 tasks a month with no card.
|
|
23
|
+
|
|
24
|
+
No dependencies. Python 3.9+.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
pip install gigai-tools
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Get a key
|
|
33
|
+
|
|
34
|
+
Create an account at [gigai.tools/developers](https://gigai.tools/developers) and make an API key in the dashboard. Set it as `GIGAI_API_KEY` or pass `api_key=`.
|
|
35
|
+
|
|
36
|
+
## One call
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from gigai_tools import Client
|
|
40
|
+
|
|
41
|
+
client = Client()
|
|
42
|
+
|
|
43
|
+
# Merge two PDFs
|
|
44
|
+
client.run("merge-pdf", files=["a.pdf", "b.pdf"], output="merged.pdf")
|
|
45
|
+
|
|
46
|
+
# Compress an image to fit under 200 KB
|
|
47
|
+
client.run("compress-image", files=["photo.jpg"], options={"target_kb": 200}, output="small.jpg")
|
|
48
|
+
|
|
49
|
+
# Generate a QR code (no input file - the text is the payload)
|
|
50
|
+
client.run("generate-qr", options={"text": "https://example.com"}, output="qr.png")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`run()` imports your files, runs the tool, exports the result and downloads it. Files can also be `https://` URLs and the server fetches them.
|
|
54
|
+
|
|
55
|
+
## Tools
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
for tool in client.tools():
|
|
59
|
+
print(tool["slug"], "-", tool["description"])
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Live engines: merge-pdf, split-pdf, rotate-pdf, pdf-to-text, convert-image, resize-image, compress-image, optimize-svg, markdown-to-html, generate-qr. The catalog only lists tools the API can actually run.
|
|
63
|
+
|
|
64
|
+
## Job graphs
|
|
65
|
+
|
|
66
|
+
Chain tools in one job. Each task names its inputs and the server runs them in dependency order:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
job = client.create_job({
|
|
70
|
+
"in": {"operation": "import/url", "url": "https://example.com/report.pdf"},
|
|
71
|
+
"rot": {"operation": "rotate-pdf", "input": ["in"], "angle": 90},
|
|
72
|
+
"txt": {"operation": "pdf-to-text", "input": ["rot"]},
|
|
73
|
+
"out": {"operation": "export/url", "input": ["txt"]},
|
|
74
|
+
}, retention_s=600)
|
|
75
|
+
|
|
76
|
+
job = client.wait(job["id"])
|
|
77
|
+
export = next(t for t in job["tasks"] if t["name"] == "out")
|
|
78
|
+
client.download(export["result"]["files"][0]["url"], "report.txt")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`Idempotency-Key` makes retries safe: pass `idempotency_key=` and the same key returns the same job instead of running the work twice.
|
|
82
|
+
|
|
83
|
+
## Errors
|
|
84
|
+
|
|
85
|
+
Every failure raises `GigaiError` with a machine-readable `code` (`invalid_option`, `file_too_large`, `quota_exceeded`, `target_unreachable`, ...), the human message, and the offending parameter when there is one.
|
|
86
|
+
|
|
87
|
+
## Privacy
|
|
88
|
+
|
|
89
|
+
File contents are never logged. Results are deleted after the retention you choose (default 1 hour, `retention_s=0` means one download or 60 seconds) and deletion is verified. The web tools at [gigai.tools](https://gigai.tools) never need an account - accounts exist only for this API.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gigai_tools
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gigai-tools"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Client for the GigAI Tools API - run PDF, image, SVG and QR tools from Python. Free plan, no dependencies."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "Gigai Kripa Services", email = "hello@gigaikripaservices.com" }]
|
|
13
|
+
keywords = ["pdf", "merge", "split", "image", "convert", "resize", "compress", "qr", "svg", "api"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Software Development :: Libraries",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://gigai.tools/developers"
|
|
24
|
+
Documentation = "https://gigai.tools/developers"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools]
|
|
27
|
+
packages = ["gigai_tools"]
|