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.
@@ -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,3 @@
1
+ from .client import Client, GigaiError, __version__
2
+
3
+ __all__ = ["Client", "GigaiError", "__version__"]
@@ -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,9 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ gigai_tools/__init__.py
5
+ gigai_tools/client.py
6
+ gigai_tools.egg-info/PKG-INFO
7
+ gigai_tools.egg-info/SOURCES.txt
8
+ gigai_tools.egg-info/dependency_links.txt
9
+ gigai_tools.egg-info/top_level.txt
@@ -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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+