forgefile 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.
Files changed (37) hide show
  1. forgefile-0.1.0/.github/workflows/ci.yml +37 -0
  2. forgefile-0.1.0/.github/workflows/release.yml +51 -0
  3. forgefile-0.1.0/.gitignore +29 -0
  4. forgefile-0.1.0/CHANGELOG.md +22 -0
  5. forgefile-0.1.0/LICENSE +21 -0
  6. forgefile-0.1.0/PKG-INFO +199 -0
  7. forgefile-0.1.0/README.md +170 -0
  8. forgefile-0.1.0/examples/01_public_data.py +24 -0
  9. forgefile-0.1.0/examples/02_translate_document.py +36 -0
  10. forgefile-0.1.0/examples/03_transcribe_with_progress.py +41 -0
  11. forgefile-0.1.0/examples/04_convert_a_folder.py +59 -0
  12. forgefile-0.1.0/examples/05_handling_errors.py +42 -0
  13. forgefile-0.1.0/examples/06_custom_transport.py +58 -0
  14. forgefile-0.1.0/pyproject.toml +72 -0
  15. forgefile-0.1.0/src/forgefile/__init__.py +52 -0
  16. forgefile-0.1.0/src/forgefile/client.py +88 -0
  17. forgefile-0.1.0/src/forgefile/config.py +47 -0
  18. forgefile-0.1.0/src/forgefile/envelope.py +32 -0
  19. forgefile-0.1.0/src/forgefile/errors.py +146 -0
  20. forgefile-0.1.0/src/forgefile/models.py +97 -0
  21. forgefile-0.1.0/src/forgefile/py.typed +0 -0
  22. forgefile-0.1.0/src/forgefile/resources/__init__.py +9 -0
  23. forgefile-0.1.0/src/forgefile/resources/_base.py +52 -0
  24. forgefile-0.1.0/src/forgefile/resources/account.py +28 -0
  25. forgefile-0.1.0/src/forgefile/resources/files.py +96 -0
  26. forgefile-0.1.0/src/forgefile/resources/jobs.py +56 -0
  27. forgefile-0.1.0/src/forgefile/resources/public.py +32 -0
  28. forgefile-0.1.0/src/forgefile/resources/system.py +23 -0
  29. forgefile-0.1.0/src/forgefile/transport.py +133 -0
  30. forgefile-0.1.0/tests/__init__.py +0 -0
  31. forgefile-0.1.0/tests/fakes.py +50 -0
  32. forgefile-0.1.0/tests/test_envelope.py +30 -0
  33. forgefile-0.1.0/tests/test_errors.py +67 -0
  34. forgefile-0.1.0/tests/test_http.py +185 -0
  35. forgefile-0.1.0/tests/test_models.py +44 -0
  36. forgefile-0.1.0/tests/test_resources.py +211 -0
  37. forgefile-0.1.0/uv.lock +798 -0
@@ -0,0 +1,37 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ check:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v5
21
+ with:
22
+ enable-cache: true
23
+
24
+ - name: Install dependencies
25
+ run: uv sync --python ${{ matrix.python-version }}
26
+
27
+ - name: Lint
28
+ run: uv run ruff check .
29
+
30
+ - name: Check formatting
31
+ run: uv run ruff format --check .
32
+
33
+ - name: Type check
34
+ run: uv run mypy
35
+
36
+ - name: Test
37
+ run: uv run pytest
@@ -0,0 +1,51 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+
13
+ - name: Install uv
14
+ uses: astral-sh/setup-uv@v5
15
+ with:
16
+ enable-cache: true
17
+
18
+ - name: Verify the tag matches the packaged version
19
+ run: |
20
+ tag="${GITHUB_REF_NAME#v}"
21
+ version="$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)"
22
+ if [ "$tag" != "$version" ]; then
23
+ echo "Tag $tag does not match pyproject version $version" >&2
24
+ exit 1
25
+ fi
26
+
27
+ - name: Build
28
+ run: uv build
29
+
30
+ - name: Check metadata
31
+ run: uvx twine check dist/*
32
+
33
+ - uses: actions/upload-artifact@v4
34
+ with:
35
+ name: dist
36
+ path: dist/
37
+
38
+ publish:
39
+ needs: build
40
+ runs-on: ubuntu-latest
41
+ environment: pypi
42
+ permissions:
43
+ id-token: write # required for trusted publishing; no token is stored
44
+ steps:
45
+ - uses: actions/download-artifact@v4
46
+ with:
47
+ name: dist
48
+ path: dist/
49
+
50
+ - name: Publish to PyPI
51
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,29 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+
8
+ # Environments
9
+ .venv/
10
+ .env
11
+
12
+ # Tooling caches
13
+ .mypy_cache/
14
+ .pytest_cache/
15
+ .ruff_cache/
16
+
17
+ # Coverage
18
+ .coverage
19
+ .coverage.*
20
+ coverage.xml
21
+ htmlcov/
22
+
23
+ # Interrupted downloads written by the client
24
+ *.part
25
+
26
+ # Editors and OS
27
+ .DS_Store
28
+ .idea/
29
+ .vscode/
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions
4
+ follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ## [Unreleased]
7
+
8
+ ### Added
9
+
10
+ - Initial client for the [ForgeFile](https://forgefile.com) REST API, with the `system`,
11
+ `public`, `account`, `files` and `jobs` resource groups.
12
+ - `HTTPTransport` protocol, so the HTTP layer can be replaced without touching the resources.
13
+ - Typed exception hierarchy carrying the API's `error_code`, plus `retry_after` on
14
+ `RateLimitError`.
15
+ - `files.wait()` to block until a job finishes, and `files.track()` to report progress while
16
+ it runs.
17
+ - Runnable examples under `examples/`.
18
+
19
+ ### Fixed
20
+
21
+ - `subscription_plans()` moved from `public` to `account`: the public route does not
22
+ exist on the API, the authenticated one does.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ForgeFile
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,199 @@
1
+ Metadata-Version: 2.5
2
+ Name: forgefile
3
+ Version: 0.1.0
4
+ Summary: Python client for the ForgeFile REST API — translate, transcribe, convert, OCR, summarize and rewrite files.
5
+ Project-URL: Homepage, https://forgefile.com
6
+ Project-URL: Documentation, https://forgefile.com/docs
7
+ Project-URL: Source, https://github.com/ForgeFile/forgefile-python
8
+ Project-URL: Issues, https://github.com/ForgeFile/forgefile-python/issues
9
+ Project-URL: Changelog, https://github.com/ForgeFile/forgefile-python/blob/main/CHANGELOG.md
10
+ Author-email: ForgeFile <support@forgefile.com>
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: api-client,file-conversion,forgefile,ocr,transcription,translation
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: Implementation :: CPython
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Text Processing :: Linguistic
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: httpx>=0.27
27
+ Requires-Dist: pydantic>=2.7
28
+ Description-Content-Type: text/markdown
29
+
30
+ # forgefile-python
31
+
32
+ Python client for the [ForgeFile](https://forgefile.com) REST API — translate, transcribe,
33
+ convert, OCR, summarize and rewrite files.
34
+
35
+ Requires Python 3.11+.
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install forgefile
41
+ ```
42
+
43
+ ## Quick start
44
+
45
+ ```python
46
+ from forgefile import ForgeFile
47
+
48
+ with ForgeFile("your-token") as api:
49
+ job = api.jobs.translate("contract.pdf", target_language="es")
50
+ api.files.wait(job.uuid)
51
+ api.files.download(job.uuid, "contract.es.pdf")
52
+ ```
53
+
54
+ The token is read from `FORGEFILE_API_KEY` when the first argument is omitted.
55
+ Public endpoints need no token at all:
56
+
57
+ ```python
58
+ with ForgeFile() as api:
59
+ for language in api.public.languages():
60
+ print(language.code, language.name)
61
+ ```
62
+
63
+ ## Processing a file
64
+
65
+ Every job endpoint uploads the file and starts the work in one call, returning a `FileJob`
66
+ whose `uuid` identifies it from then on.
67
+
68
+ ```python
69
+ api.jobs.translate("contract.pdf", target_language="de") # source_language is optional
70
+ api.jobs.transcribe("interview.mp3")
71
+ api.jobs.convert("report.docx", to_format="pdf")
72
+ api.jobs.ocr("receipt.jpg")
73
+ api.jobs.summarize("paper.pdf")
74
+ api.jobs.rewrite("draft.docx")
75
+ api.jobs.compress("scan.pdf")
76
+ ```
77
+
78
+ Uploads are capped at 10 MB by the API.
79
+
80
+ ### Waiting for the result
81
+
82
+ `wait()` blocks until the job reaches a terminal state and raises `JobTimeoutError` if it
83
+ does not. The job keeps running server-side, so calling again resumes waiting.
84
+
85
+ ```python
86
+ finished = api.files.wait(job.uuid, timeout=900, interval=5)
87
+
88
+ if finished.succeeded:
89
+ transcript = api.files.result(job.uuid) # structured output
90
+ api.files.download(job.uuid, "interview.srt")
91
+ ```
92
+
93
+ `track()` yields every observed state instead, for progress reporting:
94
+
95
+ ```python
96
+ for state in api.files.track(job.uuid, interval=5):
97
+ print(state.status)
98
+ ```
99
+
100
+ Branch on `job.is_finished` and `job.succeeded` rather than comparing status strings — a
101
+ status this client does not recognise is never treated as finished, so a wait loop cannot
102
+ end early on a state it has not seen.
103
+
104
+ ## Errors
105
+
106
+ Every failure raises a subclass of `ForgeFileError` carrying the API's stable `error_code`,
107
+ so you can branch on the code rather than on message text.
108
+
109
+ ```python
110
+ from forgefile import RateLimitError, ValidationError
111
+
112
+ try:
113
+ api.jobs.translate("contract.pdf", target_language="es")
114
+ except ValidationError as exc:
115
+ print(exc.context) # field errors
116
+ except RateLimitError as exc:
117
+ print(f"retry in {exc.retry_after}s")
118
+ ```
119
+
120
+ | Exception | Raised when |
121
+ |---|---|
122
+ | `AuthenticationError` | 401 — no token, or rejected |
123
+ | `ForbiddenError` | 403 — token lacks the right |
124
+ | `NotFoundError` | 404 |
125
+ | `ValidationError` | 422 — field errors in `context` |
126
+ | `RateLimitError` | 429 — `retry_after` in seconds |
127
+ | `ServerError` | 5xx — safe to retry with backoff |
128
+ | `TransportError` | no response at all: DNS, TLS, timeout |
129
+ | `JobTimeoutError` | `wait()` gave up; the job still runs |
130
+
131
+ The API allows 60 requests per minute.
132
+
133
+ ## Configuration
134
+
135
+ | Argument | Environment variable | Default |
136
+ |---|---|---|
137
+ | `api_key` | `FORGEFILE_API_KEY` | none — public endpoints only |
138
+ | `base_url` | `FORGEFILE_BASE_URL` | `https://forgefile.com/api/v1` |
139
+ | `timeout` | — | 60 seconds |
140
+
141
+ ## Examples
142
+
143
+ Runnable scripts in [`examples/`](examples):
144
+
145
+ | File | Shows |
146
+ |---|---|
147
+ | `01_public_data.py` | reference data without a token |
148
+ | `02_translate_document.py` | submit, wait, download |
149
+ | `03_transcribe_with_progress.py` | streaming progress with `track()` |
150
+ | `04_convert_a_folder.py` | batching — submit all, then collect |
151
+ | `05_handling_errors.py` | every failure mode and its remedy |
152
+ | `06_custom_transport.py` | replacing the HTTP layer |
153
+
154
+ ## Architecture
155
+
156
+ | Module | Responsibility |
157
+ |---|---|
158
+ | `config` | where to connect and with what headers |
159
+ | `envelope` | the API's `{success, message, data}` wrapper — the only place that knows it |
160
+ | `errors` | turning a failed response into the right exception |
161
+ | `transport` | HTTP, as a `Protocol` plus an httpx implementation |
162
+ | `resources/` | one class per endpoint group: `system`, `public`, `account`, `files`, `jobs` |
163
+
164
+ Resources depend on the `HTTPTransport` protocol, never on httpx, so the HTTP layer can be
165
+ replaced with a recorded fixture, a proxy or a different library — see
166
+ `examples/06_custom_transport.py`.
167
+
168
+ Response models accept unknown fields, which stay reachable through `model_extra`. Only
169
+ fields observed against the live API are typed explicitly; authenticated endpoints could not
170
+ be inspected without a token while this client was written, so their payloads are permissive
171
+ rather than guessed.
172
+
173
+ The package ships a `py.typed` marker, so your type checker sees these signatures.
174
+
175
+ ## Development
176
+
177
+ ```bash
178
+ git clone https://github.com/ForgeFile/forgefile-python
179
+ cd forgefile-python
180
+ uv sync
181
+
182
+ uv run ruff check .
183
+ uv run ruff format --check .
184
+ uv run mypy
185
+ uv run pytest
186
+ ```
187
+
188
+ Tests make no network calls: the resource layer runs against a fake transport, the httpx
189
+ layer against `respx`. CI runs these commands on Python 3.11, 3.12 and 3.13.
190
+
191
+ ## Links
192
+
193
+ - [ForgeFile](https://forgefile.com) — the product
194
+ - [API reference](https://forgefile.com/docs)
195
+ - [Source and issues](https://github.com/ForgeFile/forgefile-python)
196
+ - [ForgeFile on GitHub](https://github.com/ForgeFile)
197
+ - Support — <support@forgefile.com>
198
+
199
+ MIT licensed. See [LICENSE](LICENSE).
@@ -0,0 +1,170 @@
1
+ # forgefile-python
2
+
3
+ Python client for the [ForgeFile](https://forgefile.com) REST API — translate, transcribe,
4
+ convert, OCR, summarize and rewrite files.
5
+
6
+ Requires Python 3.11+.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install forgefile
12
+ ```
13
+
14
+ ## Quick start
15
+
16
+ ```python
17
+ from forgefile import ForgeFile
18
+
19
+ with ForgeFile("your-token") as api:
20
+ job = api.jobs.translate("contract.pdf", target_language="es")
21
+ api.files.wait(job.uuid)
22
+ api.files.download(job.uuid, "contract.es.pdf")
23
+ ```
24
+
25
+ The token is read from `FORGEFILE_API_KEY` when the first argument is omitted.
26
+ Public endpoints need no token at all:
27
+
28
+ ```python
29
+ with ForgeFile() as api:
30
+ for language in api.public.languages():
31
+ print(language.code, language.name)
32
+ ```
33
+
34
+ ## Processing a file
35
+
36
+ Every job endpoint uploads the file and starts the work in one call, returning a `FileJob`
37
+ whose `uuid` identifies it from then on.
38
+
39
+ ```python
40
+ api.jobs.translate("contract.pdf", target_language="de") # source_language is optional
41
+ api.jobs.transcribe("interview.mp3")
42
+ api.jobs.convert("report.docx", to_format="pdf")
43
+ api.jobs.ocr("receipt.jpg")
44
+ api.jobs.summarize("paper.pdf")
45
+ api.jobs.rewrite("draft.docx")
46
+ api.jobs.compress("scan.pdf")
47
+ ```
48
+
49
+ Uploads are capped at 10 MB by the API.
50
+
51
+ ### Waiting for the result
52
+
53
+ `wait()` blocks until the job reaches a terminal state and raises `JobTimeoutError` if it
54
+ does not. The job keeps running server-side, so calling again resumes waiting.
55
+
56
+ ```python
57
+ finished = api.files.wait(job.uuid, timeout=900, interval=5)
58
+
59
+ if finished.succeeded:
60
+ transcript = api.files.result(job.uuid) # structured output
61
+ api.files.download(job.uuid, "interview.srt")
62
+ ```
63
+
64
+ `track()` yields every observed state instead, for progress reporting:
65
+
66
+ ```python
67
+ for state in api.files.track(job.uuid, interval=5):
68
+ print(state.status)
69
+ ```
70
+
71
+ Branch on `job.is_finished` and `job.succeeded` rather than comparing status strings — a
72
+ status this client does not recognise is never treated as finished, so a wait loop cannot
73
+ end early on a state it has not seen.
74
+
75
+ ## Errors
76
+
77
+ Every failure raises a subclass of `ForgeFileError` carrying the API's stable `error_code`,
78
+ so you can branch on the code rather than on message text.
79
+
80
+ ```python
81
+ from forgefile import RateLimitError, ValidationError
82
+
83
+ try:
84
+ api.jobs.translate("contract.pdf", target_language="es")
85
+ except ValidationError as exc:
86
+ print(exc.context) # field errors
87
+ except RateLimitError as exc:
88
+ print(f"retry in {exc.retry_after}s")
89
+ ```
90
+
91
+ | Exception | Raised when |
92
+ |---|---|
93
+ | `AuthenticationError` | 401 — no token, or rejected |
94
+ | `ForbiddenError` | 403 — token lacks the right |
95
+ | `NotFoundError` | 404 |
96
+ | `ValidationError` | 422 — field errors in `context` |
97
+ | `RateLimitError` | 429 — `retry_after` in seconds |
98
+ | `ServerError` | 5xx — safe to retry with backoff |
99
+ | `TransportError` | no response at all: DNS, TLS, timeout |
100
+ | `JobTimeoutError` | `wait()` gave up; the job still runs |
101
+
102
+ The API allows 60 requests per minute.
103
+
104
+ ## Configuration
105
+
106
+ | Argument | Environment variable | Default |
107
+ |---|---|---|
108
+ | `api_key` | `FORGEFILE_API_KEY` | none — public endpoints only |
109
+ | `base_url` | `FORGEFILE_BASE_URL` | `https://forgefile.com/api/v1` |
110
+ | `timeout` | — | 60 seconds |
111
+
112
+ ## Examples
113
+
114
+ Runnable scripts in [`examples/`](examples):
115
+
116
+ | File | Shows |
117
+ |---|---|
118
+ | `01_public_data.py` | reference data without a token |
119
+ | `02_translate_document.py` | submit, wait, download |
120
+ | `03_transcribe_with_progress.py` | streaming progress with `track()` |
121
+ | `04_convert_a_folder.py` | batching — submit all, then collect |
122
+ | `05_handling_errors.py` | every failure mode and its remedy |
123
+ | `06_custom_transport.py` | replacing the HTTP layer |
124
+
125
+ ## Architecture
126
+
127
+ | Module | Responsibility |
128
+ |---|---|
129
+ | `config` | where to connect and with what headers |
130
+ | `envelope` | the API's `{success, message, data}` wrapper — the only place that knows it |
131
+ | `errors` | turning a failed response into the right exception |
132
+ | `transport` | HTTP, as a `Protocol` plus an httpx implementation |
133
+ | `resources/` | one class per endpoint group: `system`, `public`, `account`, `files`, `jobs` |
134
+
135
+ Resources depend on the `HTTPTransport` protocol, never on httpx, so the HTTP layer can be
136
+ replaced with a recorded fixture, a proxy or a different library — see
137
+ `examples/06_custom_transport.py`.
138
+
139
+ Response models accept unknown fields, which stay reachable through `model_extra`. Only
140
+ fields observed against the live API are typed explicitly; authenticated endpoints could not
141
+ be inspected without a token while this client was written, so their payloads are permissive
142
+ rather than guessed.
143
+
144
+ The package ships a `py.typed` marker, so your type checker sees these signatures.
145
+
146
+ ## Development
147
+
148
+ ```bash
149
+ git clone https://github.com/ForgeFile/forgefile-python
150
+ cd forgefile-python
151
+ uv sync
152
+
153
+ uv run ruff check .
154
+ uv run ruff format --check .
155
+ uv run mypy
156
+ uv run pytest
157
+ ```
158
+
159
+ Tests make no network calls: the resource layer runs against a fake transport, the httpx
160
+ layer against `respx`. CI runs these commands on Python 3.11, 3.12 and 3.13.
161
+
162
+ ## Links
163
+
164
+ - [ForgeFile](https://forgefile.com) — the product
165
+ - [API reference](https://forgefile.com/docs)
166
+ - [Source and issues](https://github.com/ForgeFile/forgefile-python)
167
+ - [ForgeFile on GitHub](https://github.com/ForgeFile)
168
+ - Support — <support@forgefile.com>
169
+
170
+ MIT licensed. See [LICENSE](LICENSE).
@@ -0,0 +1,24 @@
1
+ """Reference data — the endpoints that need no token.
2
+
3
+ python examples/01_public_data.py
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from forgefile import ForgeFile
9
+
10
+
11
+ def main() -> None:
12
+ with ForgeFile() as api:
13
+ print("service:", api.system.health())
14
+
15
+ languages = api.public.languages()
16
+ print(f"\n{len(languages)} languages available, first ten:")
17
+ for language in languages[:10]:
18
+ print(f" {language.code:<6} {language.name} ({language.native or '—'})")
19
+
20
+ print("\ncurrencies:", ", ".join(c.code or "?" for c in api.public.currencies()))
21
+
22
+
23
+ if __name__ == "__main__":
24
+ main()
@@ -0,0 +1,36 @@
1
+ """Translate a document and save the result next to the original.
2
+
3
+ export FORGEFILE_API_KEY=...
4
+ python examples/02_translate_document.py contract.pdf es
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from forgefile import ForgeFile
13
+
14
+
15
+ def translate(source: Path, target_language: str) -> Path | None:
16
+ with ForgeFile() as api:
17
+ job = api.jobs.translate(source, target_language=target_language)
18
+ print(f"submitted {job.uuid}")
19
+
20
+ finished = api.files.wait(job.uuid)
21
+ if not finished.succeeded:
22
+ print(f"job ended as {finished.status}", file=sys.stderr)
23
+ return None
24
+
25
+ destination = source.with_suffix(f".{target_language}{source.suffix}")
26
+ return api.files.download(job.uuid, destination)
27
+
28
+
29
+ if __name__ == "__main__":
30
+ if len(sys.argv) != 3:
31
+ print(__doc__, file=sys.stderr)
32
+ raise SystemExit(2)
33
+
34
+ result = translate(Path(sys.argv[1]), sys.argv[2])
35
+ print(f"saved {result}" if result else "translation failed")
36
+ raise SystemExit(0 if result else 1)
@@ -0,0 +1,41 @@
1
+ """Transcribe media and report progress as it happens.
2
+
3
+ ``track()`` yields every observed state instead of blocking silently, so a long
4
+ job can drive a progress line.
5
+
6
+ export FORGEFILE_API_KEY=...
7
+ python examples/03_transcribe_with_progress.py interview.mp3
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ from forgefile import ForgeFile
16
+
17
+
18
+ def main(source: Path) -> int:
19
+ with ForgeFile() as api:
20
+ job = api.jobs.transcribe(source)
21
+
22
+ for state in api.files.track(job.uuid, interval=5):
23
+ print(f"\r{state.status or 'unknown':<16}", end="", flush=True)
24
+ print()
25
+
26
+ final = api.files.get(job.uuid)
27
+ if not final.succeeded:
28
+ print(f"job ended as {final.status}", file=sys.stderr)
29
+ return 1
30
+
31
+ transcript = api.files.result(job.uuid)
32
+ print(transcript)
33
+ api.files.download(job.uuid, source.with_suffix(".srt"))
34
+ return 0
35
+
36
+
37
+ if __name__ == "__main__":
38
+ if len(sys.argv) != 2:
39
+ print(__doc__, file=sys.stderr)
40
+ raise SystemExit(2)
41
+ raise SystemExit(main(Path(sys.argv[1])))
@@ -0,0 +1,59 @@
1
+ """Convert every file in a folder, submitting all jobs before waiting.
2
+
3
+ Jobs run server-side, so start them all first and collect the results after —
4
+ that turns N sequential waits into one.
5
+
6
+ export FORGEFILE_API_KEY=...
7
+ python examples/04_convert_a_folder.py ./invoices pdf
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ from forgefile import FileJob, ForgeFile, ForgeFileError, JobTimeoutError
16
+
17
+
18
+ def main(folder: Path, to_format: str) -> int:
19
+ sources = sorted(p for p in folder.iterdir() if p.is_file())
20
+ if not sources:
21
+ print(f"nothing to convert in {folder}", file=sys.stderr)
22
+ return 1
23
+
24
+ with ForgeFile() as api:
25
+ submitted: dict[str, Path] = {}
26
+ for source in sources:
27
+ try:
28
+ job: FileJob = api.jobs.convert(source, to_format=to_format)
29
+ except ForgeFileError as exc:
30
+ print(f"skipped {source.name}: {exc}", file=sys.stderr)
31
+ continue
32
+ submitted[job.uuid] = source
33
+ print(f"submitted {source.name} -> {job.uuid}")
34
+
35
+ failures = 0
36
+ for uuid, source in submitted.items():
37
+ destination = source.with_suffix(f".{to_format}")
38
+ try:
39
+ finished = api.files.wait(uuid, timeout=900)
40
+ except JobTimeoutError as exc:
41
+ print(f"timeout {source.name}: {exc}", file=sys.stderr)
42
+ failures += 1
43
+ continue
44
+
45
+ if finished.succeeded:
46
+ api.files.download(uuid, destination)
47
+ print(f"saved {destination.name}")
48
+ else:
49
+ print(f"failed {source.name}: {finished.status}", file=sys.stderr)
50
+ failures += 1
51
+
52
+ return 1 if failures else 0
53
+
54
+
55
+ if __name__ == "__main__":
56
+ if len(sys.argv) != 3:
57
+ print(__doc__, file=sys.stderr)
58
+ raise SystemExit(2)
59
+ raise SystemExit(main(Path(sys.argv[1]), sys.argv[2]))