machyon 1.0.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.
- machyon-1.0.0/.gitignore +15 -0
- machyon-1.0.0/CHANGELOG.md +35 -0
- machyon-1.0.0/CITATION.cff +17 -0
- machyon-1.0.0/LICENSE +21 -0
- machyon-1.0.0/PKG-INFO +305 -0
- machyon-1.0.0/README.md +254 -0
- machyon-1.0.0/pyproject.toml +80 -0
- machyon-1.0.0/src/machyon/__init__.py +35 -0
- machyon-1.0.0/src/machyon/__main__.py +5 -0
- machyon-1.0.0/src/machyon/cli.py +182 -0
- machyon-1.0.0/src/machyon/compat.py +186 -0
- machyon-1.0.0/src/machyon/config.py +161 -0
- machyon-1.0.0/src/machyon/diagnostics.py +125 -0
- machyon-1.0.0/src/machyon/disk.py +114 -0
- machyon-1.0.0/src/machyon/downloader.py +315 -0
- machyon-1.0.0/src/machyon/engine.py +258 -0
- machyon-1.0.0/src/machyon/errors.py +11 -0
- machyon-1.0.0/src/machyon/fetcher.py +238 -0
- machyon-1.0.0/src/machyon/jobs.py +49 -0
- machyon-1.0.0/src/machyon/ledger.py +201 -0
- machyon-1.0.0/src/machyon/limiter.py +171 -0
- machyon-1.0.0/src/machyon/metrics.py +160 -0
- machyon-1.0.0/src/machyon/naming.py +178 -0
- machyon-1.0.0/src/machyon/planner.py +167 -0
- machyon-1.0.0/src/machyon/py.typed +0 -0
- machyon-1.0.0/src/machyon/session.py +102 -0
- machyon-1.0.0/src/machyon/sources.py +163 -0
- machyon-1.0.0/src/machyon/utils.py +187 -0
- machyon-1.0.0/tests/conftest.py +79 -0
- machyon-1.0.0/tests/fakecdn.py +311 -0
- machyon-1.0.0/tests/test_api.py +153 -0
- machyon-1.0.0/tests/test_compat.py +441 -0
- machyon-1.0.0/tests/test_docs.py +245 -0
- machyon-1.0.0/tests/test_public_api.py +47 -0
- machyon-1.0.0/tests/test_regressions.py +168 -0
- machyon-1.0.0/tests/test_tls.py +80 -0
- machyon-1.0.0/tests/test_units.py +125 -0
machyon-1.0.0/.gitignore
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0 (2026-11-09): first release
|
|
4
|
+
|
|
5
|
+
Machyon is a new bulk image downloader, built from scratch around one promise: **fast, and never loses a file**.
|
|
6
|
+
|
|
7
|
+
**Speed**
|
|
8
|
+
- A streaming worker pool with no batch barriers, so one slow image never holds up the rest.
|
|
9
|
+
- Adaptive concurrency: it grows while throughput improves, steps back when more connections stop helping, backs off on HTTP 429, and remembers the best setting per host.
|
|
10
|
+
- Tail-latency hedging: stragglers at the end of a batch race a duplicate request.
|
|
11
|
+
- uvloop/winloop through `machyon.run()` and the `[fast]` extra.
|
|
12
|
+
|
|
13
|
+
**Never lose a file**
|
|
14
|
+
- Every row ends written, skipped because it's already complete, or recorded with a reason and the real error in `failed_downloads.jsonl`. This holds even on Ctrl+C or a full disk.
|
|
15
|
+
- Atomic writes, so a file that exists is a complete file. Resuming means running the same command again.
|
|
16
|
+
- Large files resume mid-file with HTTP Range requests.
|
|
17
|
+
- Bodies are checked by their magic bytes: HTML error pages are rejected, real images served as octet-stream are accepted.
|
|
18
|
+
- Duplicate URLs are fetched once and written to every row's file. Conflicting filenames are reported, never overwritten.
|
|
19
|
+
|
|
20
|
+
**Works everywhere**
|
|
21
|
+
- HTTPS is verified with the operating system's trust store (`truststore`) and falls back to `certifi`, so sites that open in your browser work. Choose with `--tls-trust`; `--insecure` is a last resort.
|
|
22
|
+
- Windows-safe filenames, and retries when antivirus or the indexer briefly locks a file.
|
|
23
|
+
- Python 3.11 to 3.15 on Linux, macOS and Windows.
|
|
24
|
+
|
|
25
|
+
**Easy to use**
|
|
26
|
+
- `machyon.download(source, folder)`, `await machyon.adownload(...)`, and the `Downloader` class.
|
|
27
|
+
- Any input: CSV, TSV, TXT, JSON, JSONL, Parquet, pandas or Polars DataFrames, iterables and `(url, name)` pairs, streamed in shards so memory stays flat.
|
|
28
|
+
- Filename templates: `{index} {hash} {stem} {ext} {host} {any column}`. `{ext}` takes the real format when the URL has none.
|
|
29
|
+
- CLI: `machyon download | retry | names | doctor`. `doctor` explains why a URL fails (DNS, proxy, TLS, HTTP).
|
|
30
|
+
- Typed `Report` and `DownloadConfig`, plus a class-based API (`ImageDownloader`, `ImageRenamer`).
|
|
31
|
+
- A modular, language-neutral architecture (`docs/ARCHITECTURE.md`), ready for the JavaScript port.
|
|
32
|
+
|
|
33
|
+
**Documentation**
|
|
34
|
+
- A full documentation site at https://divyanshusingh96.github.io/machyon/: guides, the zero-lost contract with its boundaries, benchmarks with their method, and an interactive simulator of the adaptive limiter.
|
|
35
|
+
- The API, configuration, CLI and failure-reason references are generated from the code, and every example on the site runs as a test.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use Machyon in research, please cite it as below."
|
|
3
|
+
title: "Machyon: fast, self-tuning, loss-free bulk image downloading"
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: Singh
|
|
7
|
+
given-names: Divyanshu
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
date-released: 2026-11-09
|
|
10
|
+
license: MIT
|
|
11
|
+
repository-code: "https://github.com/DivyanshuSingh96/machyon"
|
|
12
|
+
url: "https://divyanshusingh96.github.io/machyon/"
|
|
13
|
+
keywords:
|
|
14
|
+
- image downloader
|
|
15
|
+
- datasets
|
|
16
|
+
- machine learning
|
|
17
|
+
- asyncio
|
machyon-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Divyanshu Singh
|
|
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.
|
machyon-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: machyon
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Download any number of images, fast, without losing a single file.
|
|
5
|
+
Project-URL: Homepage, https://github.com/DivyanshuSingh96/machyon
|
|
6
|
+
Project-URL: Documentation, https://divyanshusingh96.github.io/machyon/
|
|
7
|
+
Project-URL: Repository, https://github.com/DivyanshuSingh96/machyon
|
|
8
|
+
Project-URL: Issues, https://github.com/DivyanshuSingh96/machyon/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/DivyanshuSingh96/machyon/blob/main/CHANGELOG.md
|
|
10
|
+
Author: Divyanshu Singh
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: aiohttp,asyncio,bulk download,computer vision,crawler,data collection,dataset,image downloader,machine learning,web scraping
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Framework :: AsyncIO
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Intended Audience :: Science/Research
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
26
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
27
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
28
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
29
|
+
Classifier: Typing :: Typed
|
|
30
|
+
Requires-Python: >=3.11
|
|
31
|
+
Requires-Dist: aiohttp>=3.10
|
|
32
|
+
Requires-Dist: certifi>=2024.2.2
|
|
33
|
+
Requires-Dist: tqdm>=4.66
|
|
34
|
+
Requires-Dist: truststore>=0.10
|
|
35
|
+
Provides-Extra: all
|
|
36
|
+
Requires-Dist: brotli>=1.1; extra == 'all'
|
|
37
|
+
Requires-Dist: pyarrow>=15; extra == 'all'
|
|
38
|
+
Requires-Dist: uvloop>=0.21; (sys_platform != 'win32' and platform_python_implementation == 'CPython') and extra == 'all'
|
|
39
|
+
Requires-Dist: winloop>=0.1; (sys_platform == 'win32') and extra == 'all'
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Requires-Dist: pandas>=2; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
43
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
44
|
+
Provides-Extra: fast
|
|
45
|
+
Requires-Dist: brotli>=1.1; extra == 'fast'
|
|
46
|
+
Requires-Dist: uvloop>=0.21; (sys_platform != 'win32' and platform_python_implementation == 'CPython') and extra == 'fast'
|
|
47
|
+
Requires-Dist: winloop>=0.1; (sys_platform == 'win32') and extra == 'fast'
|
|
48
|
+
Provides-Extra: parquet
|
|
49
|
+
Requires-Dist: pyarrow>=15; extra == 'parquet'
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
|
|
52
|
+
<p align="center">
|
|
53
|
+
<a href="https://divyanshusingh96.github.io/machyon/"><img alt="Machyon. Every image. Mach speed. Zero lost. Fast async image downloads for Python." src="https://raw.githubusercontent.com/DivyanshuSingh96/machyon/main/docs/assets/brand/machyon-readme-banner@2x.png" width="100%"></a>
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<p align="center">
|
|
57
|
+
<a href="https://pypi.org/project/machyon/"><img alt="PyPI" src="https://img.shields.io/pypi/v/machyon?color=2A3142"></a>
|
|
58
|
+
<a href="https://pypi.org/project/machyon/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/machyon?color=2A3142"></a>
|
|
59
|
+
<a href="https://github.com/DivyanshuSingh96/machyon/actions"><img alt="CI" src="https://github.com/DivyanshuSingh96/machyon/actions/workflows/ci.yml/badge.svg"></a>
|
|
60
|
+
<a href="https://github.com/DivyanshuSingh96/machyon/blob/main/LICENSE"><img alt="MIT" src="https://img.shields.io/badge/license-MIT-2A3142"></a>
|
|
61
|
+
<a href="https://divyanshusingh96.github.io/machyon/"><img alt="Docs" src="https://img.shields.io/badge/docs-machyon-2A3142"></a>
|
|
62
|
+
</p>
|
|
63
|
+
|
|
64
|
+
<p align="center">
|
|
65
|
+
Machyon <em>(MAK-ee-on: <b>Mach</b> + tach<b>yon</b>)</em> is a new kind of image downloader.<br>
|
|
66
|
+
It pulls any number of images from any server, tunes its own speed while it runs,<br>
|
|
67
|
+
and <b>accounts for every file</b>: each one is downloaded, already on disk, or recorded with the reason it couldn't be.
|
|
68
|
+
</p>
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install machyon
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import machyon
|
|
76
|
+
|
|
77
|
+
report = machyon.download("urls.csv", "images/")
|
|
78
|
+
print(report)
|
|
79
|
+
# 4980 downloaded, 0 already on disk, 20 failed in 12.3s (404.9 img/s, 8.10 MB/s) | failures: {'http_404': 14, 'not_image': 6}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
That's it. No thread counts to tune, no retry loops to write, no half-written files to clean up.
|
|
83
|
+
|
|
84
|
+
**[Read the documentation →](https://divyanshusingh96.github.io/machyon/)**
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## The story
|
|
89
|
+
|
|
90
|
+
I was in college when my team entered the **Amazon ML Challenge**. The dataset was a CSV of product images: **263,859 rows**. Before we could train anything, we had to download them, and that's where the challenge really started.
|
|
91
|
+
|
|
92
|
+
A simple loop would have taken most of a day. The parallel scripts we tried went faster, but they died halfway, retried nothing, and quietly saved HTML error pages as `.jpg` files. Worst of all, when we finally had "everything", files were missing and nothing told us which ones. When time is the scarcest resource you have, losing it to *downloading* feels terrible.
|
|
93
|
+
|
|
94
|
+
So I started building the downloader I wished we'd had. A year of rewrites later, it's Machyon: fast enough that the network is the limit, and careful enough that "done" means **done**.
|
|
95
|
+
|
|
96
|
+
If you've ever stared at a progress bar while a deadline got closer, this is for you.
|
|
97
|
+
|
|
98
|
+
**In the Amazon ML Challenge right now?** There's a [one-cell recipe](https://divyanshusingh96.github.io/machyon/getting-started/amazon-ml-challenge/) that saves files under the same names as the starter kit's `download_images()`, so the rest of your notebook keeps working.
|
|
99
|
+
|
|
100
|
+
## The name, and Opa
|
|
101
|
+
|
|
102
|
+
**Mach** is the speed of sound. A **tachyon** is a hypothetical particle that can never travel slower than light.
|
|
103
|
+
**Machyon** is both: built to break the waiting barrier, and never fall back below it.
|
|
104
|
+
|
|
105
|
+
The mascot is **Opa**, an *Opabinia*: a real animal that swam in Cambrian seas more than 500 million years ago, with
|
|
106
|
+
five eyes and one long arm ending in a claw. Five eyes to watch every row, one reach to grab exactly what you asked for.
|
|
107
|
+
[Meet Opa](https://divyanshusingh96.github.io/machyon/project/meet-opa/), and watch the 25-second film about where it came from.
|
|
108
|
+
|
|
109
|
+
## The problem everyone hits
|
|
110
|
+
|
|
111
|
+
Downloading a lot of images *looks* trivial, until you do it for real:
|
|
112
|
+
|
|
113
|
+
| What happens | What it costs you |
|
|
114
|
+
|---|---|
|
|
115
|
+
| A sequential loop | Hours instead of minutes |
|
|
116
|
+
| Threads or async with a hard-coded number | Too low wastes your bandwidth; too high gets you **rate-limited (HTTP 429)** and everything fails |
|
|
117
|
+
| One slow server response | The whole batch waits for the slowest image |
|
|
118
|
+
| No retries, or naive retries | Transient errors become permanent holes in your dataset |
|
|
119
|
+
| `status == 200` is treated as success | **HTML "not found" pages saved as `image.jpg`**, discovered weeks later during training |
|
|
120
|
+
| The script crashes or you hit Ctrl+C | Half-written files that look complete, and no idea where to resume |
|
|
121
|
+
| The same URL appears on several rows | Silently skipped or downloaded twice |
|
|
122
|
+
|
|
123
|
+
## What Machyon does differently
|
|
124
|
+
|
|
125
|
+
**🚀 Self-tuning speed.** Concurrency adapts live. It ramps up while throughput improves, backs off the moment a server says "slow down", and remembers the best setting per host for next time.
|
|
126
|
+
|
|
127
|
+
**🪃 No straggler tax.** No batches and no barriers: a continuous worker pool. Near the end, any image far slower than its peers gets a second, racing request (*hedging*). The first good answer wins.
|
|
128
|
+
|
|
129
|
+
**🛡️ Zero lost files.** Every row ends up written, skipped because it's already complete, or recorded with a reason in `failed_downloads.jsonl`. Files appear atomically, so a file that exists is a complete file, through Ctrl+C, crashes and full disks. [The exact contract, and where it stops](https://divyanshusingh96.github.io/machyon/reliability/zero-lost/).
|
|
130
|
+
|
|
131
|
+
**🔍 Real images only.** Bodies are checked by their magic bytes. HTML error pages are rejected, while real images served with odd content types (`application/octet-stream`) are accepted.
|
|
132
|
+
|
|
133
|
+
**🔁 Resume is just "run it again".** Finished files are skipped in a second or two, even for hundreds of thousands of rows (100,000 in 0.5 s). Large files resume mid-file with HTTP Range requests.
|
|
134
|
+
|
|
135
|
+
**🤝 Polite by design.** It honours `Retry-After`, a 429 pauses only the host that sent it, retries use jittered backoff, and duplicate URLs are fetched once.
|
|
136
|
+
|
|
137
|
+
**🧩 A library first.** Use it from sync or async code, with any input (CSV, TSV, TXT, JSON, JSONL, Parquet, DataFrames, iterables), filename templates and typed reports. Small footprint: `aiohttp`, `tqdm`, and `truststore` + `certifi` for browser-grade HTTPS verification.
|
|
138
|
+
|
|
139
|
+
## Benchmarks
|
|
140
|
+
|
|
141
|
+
<picture>
|
|
142
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/DivyanshuSingh96/machyon/main/assets/benchmark-dark.svg">
|
|
143
|
+
<img alt="Images per second: Machyon vs img2dataset across five network scenarios" src="https://raw.githubusercontent.com/DivyanshuSingh96/machyon/main/assets/benchmark-light.svg">
|
|
144
|
+
</picture>
|
|
145
|
+
|
|
146
|
+
| Scenario (images) | Machyon 1.0 | img2dataset 1.47, defaults | img2dataset, best tuning |
|
|
147
|
+
|---|---|---|---|
|
|
148
|
+
| Clean network, 80 ms RTT (5,000) | **820 img/s**, 0 lost | 538 img/s, 0 lost | 565 (`retries=3`) |
|
|
149
|
+
| Real-world faults (5,000) | **519 img/s**, 0 lost, 0 bogus | 257 img/s, **97 lost, 10 bogus** | 268, 0 lost but **10 bogus** (`retries=3`) |
|
|
150
|
+
| Rate-limited CDN, max 48 in flight (3,000) | **343 img/s**, 0 lost | 0.6 img/s, **2,997 lost** | 222 (hand-tuned to 24 in flight) |
|
|
151
|
+
| Slow link, 2.3 MB/s (2,000) | **114 img/s** | 95 img/s | 96 |
|
|
152
|
+
| High latency, 350 ms (3,000) | 219 img/s | 214 img/s | 224 (`retries=3`) |
|
|
153
|
+
|
|
154
|
+
*Lost* means a row that could have succeeded but has no file. *Bogus* means a file that isn't the image: an HTML error page saved as `.jpg`.
|
|
155
|
+
|
|
156
|
+
**How this was measured.**
|
|
157
|
+
- Both tools downloaded from the same local fault-injecting CDN ([`benchmarks/fakecdn.py`](https://github.com/DivyanshuSingh96/machyon/blob/main/benchmarks/fakecdn.py)). The "real-world faults" scenario injects slow tails, 503s, dropped connections, stalls, 404s, HTML soft-404s and odd content types. Every saved file was compared byte-for-byte with the original.
|
|
158
|
+
- img2dataset was given its fastest raw-download settings: no resizing, no re-encoding, no EXIF or hash work, 2 processes.
|
|
159
|
+
- Times are wall-clock, including process start-up, on a 2-vCPU machine shared with the CDN.
|
|
160
|
+
- **Your numbers will differ.** On a fast connection Machyon is usually limited by the network, not by itself. Reproduce with [`benchmarks/harness.py`](https://github.com/DivyanshuSingh96/machyon/blob/main/benchmarks/harness.py).
|
|
161
|
+
|
|
162
|
+
**Where img2dataset is the better choice:** it's a superb tool built for a different job. It does resizing, WebDataset/Parquet shards, captions, and distributed runs on Spark or Ray for hundreds of millions of images on big clusters. Machyon is the embeddable, self-tuning, never-lose-a-file downloader for everything up to that scale, on the machine you already have.
|
|
163
|
+
|
|
164
|
+
## Quickstart
|
|
165
|
+
|
|
166
|
+
### Command line
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
machyon urls.csv -o images/ # URL column detected automatically
|
|
170
|
+
machyon train.csv -o images/ --url-col image_link --names "{index}_{entity_name}{ext}"
|
|
171
|
+
machyon retry -o images/ # retry what failed, under the same names
|
|
172
|
+
machyon names urls.csv --names "{host}/{hash}{ext}" # preview filenames without downloading
|
|
173
|
+
machyon doctor "https://site.com/img.jpg" # why does a URL fail? DNS, proxy, TLS, HTTP
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Python (sync)
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
import machyon
|
|
180
|
+
|
|
181
|
+
report = machyon.download(
|
|
182
|
+
"train.csv", "images/train",
|
|
183
|
+
url_col="image_link",
|
|
184
|
+
names="{index}_{entity_name}{ext}", # any column + {index} {hash} {stem} {ext} {host}
|
|
185
|
+
)
|
|
186
|
+
print(report.downloaded, report.failed, report.rate)
|
|
187
|
+
print(report.failure_reasons) # {"http_404": 12, "not_image": 3}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Python (async, inside your app)
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
async with machyon.Downloader("images/", headers={"Authorization": "Bearer …"}) as dl:
|
|
194
|
+
report = await dl.download(urls, names="{host}/{hash}{ext}")
|
|
195
|
+
await dl.retry_failed()
|
|
196
|
+
print(dl.failures) # [{"url": ..., "name": ..., "reason": ...}]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
In Jupyter, FastAPI or any running event loop, use `await machyon.adownload(...)`.
|
|
200
|
+
|
|
201
|
+
## Inputs and filenames
|
|
202
|
+
|
|
203
|
+
| Input | Example |
|
|
204
|
+
|---|---|
|
|
205
|
+
| CSV / TSV / TXT / JSON / JSONL / Parquet | `machyon.download("urls.parquet", "out")` |
|
|
206
|
+
| a list or generator of URLs (streamed, memory stays flat) | `machyon.download(url_list, "out")` |
|
|
207
|
+
| `(url, filename)` pairs | `machyon.download([(u, "a/1.jpg"), ...], "out")` |
|
|
208
|
+
| URLs plus a list of names | `machyon.download(urls, "out", names_list)` |
|
|
209
|
+
| a pandas or Polars DataFrame | `machyon.download(df, "out", names="{label}/{index}{ext}")` |
|
|
210
|
+
|
|
211
|
+
Name templates use `{index}` (the row number), `{hash}` (stable and unique per URL; this is the default), `{stem}` and `{ext}` (from the URL), `{host}`, and any column of your table. When the URL has no image extension, `{ext}` becomes the **real** format detected from the bytes (`.jpg`, `.png`, `.webp`, …). A `/` in a template creates subfolders.
|
|
212
|
+
|
|
213
|
+
## Configuration
|
|
214
|
+
|
|
215
|
+
The defaults are tuned so you rarely need any of this. Every option works as a keyword (`machyon.download(..., max_retries=10)`) or through `DownloadConfig`:
|
|
216
|
+
|
|
217
|
+
| Option | Default | What it does |
|
|
218
|
+
|---|---|---|
|
|
219
|
+
| `concurrency` / `max_concurrency` | 32 / 256 | Starting point and ceiling for in-flight requests (adapts between them) |
|
|
220
|
+
| `adaptive` | `True` | Live concurrency tuning |
|
|
221
|
+
| `hedge` | `True` | Race a duplicate request against stragglers at the end of a batch |
|
|
222
|
+
| `max_retries` / `backoff` | 5 / 0.5 s | Retries after the first attempt; jittered exponential backoff |
|
|
223
|
+
| `timeout` / `max_request_time` | 15 s / 120 s | Connect and read-stall timeout; hard cap per attempt (extended for big files) |
|
|
224
|
+
| `headers`, `referer`, `user_agent` | | Auth tokens, cookies, identity |
|
|
225
|
+
| `validate` | `True` | Reject non-image bodies |
|
|
226
|
+
| `verify_existing` | `False` | Check that files already on disk really are images before skipping them |
|
|
227
|
+
| `fsync` | `False` | Flush to physical disk before each rename (power-loss safe, slower) |
|
|
228
|
+
| `metrics_dir` / `log_dir` | `None` | Append run summaries to JSON / write a rotating log |
|
|
229
|
+
|
|
230
|
+
When Python itself is the bottleneck (many small images on a fast link), install `pip install "machyon[fast]"` for uvloop (Linux/macOS) or winloop (Windows). `machyon.download()` and the CLI use it automatically; in your own code, call `machyon.run(main())` in place of `asyncio.run`.
|
|
231
|
+
|
|
232
|
+
Every option, with its CLI flag: [configuration reference](https://divyanshusingh96.github.io/machyon/reference/configuration/).
|
|
233
|
+
|
|
234
|
+
## How it works
|
|
235
|
+
|
|
236
|
+
```mermaid
|
|
237
|
+
flowchart LR
|
|
238
|
+
A[Any source<br/>CSV · Parquet · list · DataFrame] --> P[Planner<br/>dedupe · skip finished · safe names]
|
|
239
|
+
P --> E[Engine<br/>worker pool · retries · hedging]
|
|
240
|
+
L[AdaptiveLimiter<br/>live concurrency] <--> E
|
|
241
|
+
S[NetworkSessionManager<br/>pooled HTTP · proxies] <--> E
|
|
242
|
+
E --> F[Fetcher<br/>validate · stream · resume]
|
|
243
|
+
F --> D[DiskWorkerPool<br/>atomic writes]
|
|
244
|
+
E --> R[FailureLedger<br/>failed_downloads.jsonl]
|
|
245
|
+
E --> M[MetricsEngine<br/>Report]
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The full design (algorithms, guarantees and the outcome table) is in [docs/ARCHITECTURE.md](https://github.com/DivyanshuSingh96/machyon/blob/main/docs/ARCHITECTURE.md). It's written language-neutral, because Machyon is coming to JavaScript next.
|
|
249
|
+
|
|
250
|
+
## Class-based API
|
|
251
|
+
|
|
252
|
+
Prefer explicit lists and step-by-step control? `ImageDownloader` and `ImageRenamer` give you exactly that, on the same engine:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
from machyon import ImageDownloader, ImageRenamer
|
|
256
|
+
|
|
257
|
+
links, names = ImageRenamer(entity_col="entity_name").hashed_names("train.csv") # stable names
|
|
258
|
+
async with ImageDownloader(save_dir="images", max_retries=10) as d:
|
|
259
|
+
await d.metrics_json(links, names) # download + append a run summary to metrics/
|
|
260
|
+
await d.retry_failed_downloads()
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Troubleshooting
|
|
264
|
+
|
|
265
|
+
Every failure is recorded in `failed_downloads.jsonl` with its reason **and the real error**, and the CLI explains each kind once ([every reason, explained](https://divyanshusingh96.github.io/machyon/reference/failure-reasons/)). For any URL that fails, ask the doctor:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
machyon doctor "https://example.com/some/image.jpg"
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
It checks DNS, your proxy, each TLS trust source and the HTTP response, then tells you what to change: `--referer` for hotlink-protected sites, `--tls-trust certifi` if your OS store is missing a root, or, if something on your machine is re-signing HTTPS (antivirus "HTTPS scanning", a corporate proxy), how to deal with that.
|
|
272
|
+
|
|
273
|
+
## Responsible use
|
|
274
|
+
|
|
275
|
+
Machyon is fast, so please be a good citizen. Download only what you have the right to use, respect each site's terms and `robots.txt`, and identify yourself with a `user_agent` when you're collecting data at scale. Machyon helps: it honours `Retry-After`, pauses a host that answers 429 and lowers its concurrency automatically. [Responsible use](https://divyanshusingh96.github.io/machyon/project/responsible-use/)
|
|
276
|
+
|
|
277
|
+
## Roadmap
|
|
278
|
+
|
|
279
|
+
- [x] Python library: adaptive concurrency, hedging, atomic writes, resume
|
|
280
|
+
- [ ] **JavaScript / TypeScript** (`npm i machyon`), on the same architecture and the same fault-injection suite
|
|
281
|
+
- [ ] Optional post-processing hooks (resize, convert, dedupe by perceptual hash)
|
|
282
|
+
- [ ] WebDataset / Parquet shard output
|
|
283
|
+
- [ ] Source plugins (sitemaps, cloud buckets)
|
|
284
|
+
|
|
285
|
+
## FAQ
|
|
286
|
+
|
|
287
|
+
**Is it really faster than my 20-line asyncio script?** On a clean network with the right concurrency, both will hit your bandwidth. Machyon's advantage shows up on real networks: it finds the right concurrency itself, doesn't stall on stragglers, survives rate limits, and doesn't lose files.
|
|
288
|
+
|
|
289
|
+
**How big can the input be?** Rows are read and planned in shards of 200,000. Machyon keeps about 70 bytes per row to catch filename conflicts across the whole run, which is roughly 0.7 GB per 10 million rows. Very large single folders are slow on every filesystem, so use a template like `{hash:.2}/{hash}{ext}` to spread files across subfolders.
|
|
290
|
+
|
|
291
|
+
**Does it work on Windows?** Yes. Filenames are made Windows-safe, and writes retry when antivirus or the search indexer briefly locks a file.
|
|
292
|
+
|
|
293
|
+
**Can it download from pages instead of URL lists?** Not yet; v1 takes URL lists. Page and sitemap sources are on the roadmap.
|
|
294
|
+
|
|
295
|
+
## Contributing
|
|
296
|
+
|
|
297
|
+
Bug reports, ideas and pull requests are very welcome; see [CONTRIBUTING.md](https://github.com/DivyanshuSingh96/machyon/blob/main/CONTRIBUTING.md). Running `pytest` covers 90+ tests in about two and a half minutes: fault injection against a fake CDN, plus every example in the documentation.
|
|
298
|
+
|
|
299
|
+
## Citing
|
|
300
|
+
|
|
301
|
+
If Machyon helps your research, please cite it ([CITATION.cff](https://github.com/DivyanshuSingh96/machyon/blob/main/CITATION.cff)).
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
[MIT](https://github.com/DivyanshuSingh96/machyon/blob/main/LICENSE) © 2026 Divyanshu Singh
|