gdown2 0.2.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 (60) hide show
  1. gdown2-0.2.0/LICENSE +22 -0
  2. gdown2-0.2.0/PKG-INFO +456 -0
  3. gdown2-0.2.0/README.md +414 -0
  4. gdown2-0.2.0/gdown2/__init__.py +27 -0
  5. gdown2-0.2.0/gdown2/__main__.py +9 -0
  6. gdown2-0.2.0/gdown2/application/__init__.py +31 -0
  7. gdown2-0.2.0/gdown2/application/crawler.py +128 -0
  8. gdown2-0.2.0/gdown2/application/exporter.py +40 -0
  9. gdown2-0.2.0/gdown2/application/planner.py +193 -0
  10. gdown2-0.2.0/gdown2/application/ports.py +85 -0
  11. gdown2-0.2.0/gdown2/application/reporter.py +285 -0
  12. gdown2-0.2.0/gdown2/application/resolver.py +50 -0
  13. gdown2-0.2.0/gdown2/application/scheduler.py +150 -0
  14. gdown2-0.2.0/gdown2/application/verifier.py +81 -0
  15. gdown2-0.2.0/gdown2/application/worker.py +307 -0
  16. gdown2-0.2.0/gdown2/domain/__init__.py +89 -0
  17. gdown2-0.2.0/gdown2/domain/errors.py +151 -0
  18. gdown2-0.2.0/gdown2/domain/models.py +254 -0
  19. gdown2-0.2.0/gdown2/domain/path_policy.py +138 -0
  20. gdown2-0.2.0/gdown2/domain/policies.py +117 -0
  21. gdown2-0.2.0/gdown2/domain/tree.py +113 -0
  22. gdown2-0.2.0/gdown2/domain/url_parser.py +148 -0
  23. gdown2-0.2.0/gdown2/infrastructure/__init__.py +12 -0
  24. gdown2-0.2.0/gdown2/infrastructure/auth/__init__.py +16 -0
  25. gdown2-0.2.0/gdown2/infrastructure/auth/anonymous.py +21 -0
  26. gdown2-0.2.0/gdown2/infrastructure/auth/api_key.py +25 -0
  27. gdown2-0.2.0/gdown2/infrastructure/auth/base.py +21 -0
  28. gdown2-0.2.0/gdown2/infrastructure/auth/oauth.py +128 -0
  29. gdown2-0.2.0/gdown2/infrastructure/auth/service_account.py +92 -0
  30. gdown2-0.2.0/gdown2/infrastructure/cache.py +131 -0
  31. gdown2-0.2.0/gdown2/infrastructure/config.py +182 -0
  32. gdown2-0.2.0/gdown2/infrastructure/drive/__init__.py +20 -0
  33. gdown2-0.2.0/gdown2/infrastructure/drive/metadata.py +105 -0
  34. gdown2-0.2.0/gdown2/infrastructure/drive/source_api.py +204 -0
  35. gdown2-0.2.0/gdown2/infrastructure/drive/source_composite.py +70 -0
  36. gdown2-0.2.0/gdown2/infrastructure/drive/source_web.py +666 -0
  37. gdown2-0.2.0/gdown2/infrastructure/http/__init__.py +14 -0
  38. gdown2-0.2.0/gdown2/infrastructure/http/client.py +209 -0
  39. gdown2-0.2.0/gdown2/infrastructure/http/ratelimit.py +46 -0
  40. gdown2-0.2.0/gdown2/infrastructure/http/retry.py +114 -0
  41. gdown2-0.2.0/gdown2/infrastructure/storage/__init__.py +7 -0
  42. gdown2-0.2.0/gdown2/infrastructure/storage/fs_sink.py +87 -0
  43. gdown2-0.2.0/gdown2/infrastructure/storage/resume_state.py +94 -0
  44. gdown2-0.2.0/gdown2/interfaces/__init__.py +6 -0
  45. gdown2-0.2.0/gdown2/interfaces/cli/__init__.py +6 -0
  46. gdown2-0.2.0/gdown2/interfaces/cli/app.py +405 -0
  47. gdown2-0.2.0/gdown2/interfaces/cli/render.py +247 -0
  48. gdown2-0.2.0/gdown2/interfaces/json_events.py +27 -0
  49. gdown2-0.2.0/gdown2/interfaces/tui/__init__.py +6 -0
  50. gdown2-0.2.0/gdown2/interfaces/tui/app.py +205 -0
  51. gdown2-0.2.0/gdown2/py.typed +0 -0
  52. gdown2-0.2.0/gdown2/sdk.py +278 -0
  53. gdown2-0.2.0/gdown2.egg-info/PKG-INFO +456 -0
  54. gdown2-0.2.0/gdown2.egg-info/SOURCES.txt +58 -0
  55. gdown2-0.2.0/gdown2.egg-info/dependency_links.txt +1 -0
  56. gdown2-0.2.0/gdown2.egg-info/entry_points.txt +2 -0
  57. gdown2-0.2.0/gdown2.egg-info/requires.txt +17 -0
  58. gdown2-0.2.0/gdown2.egg-info/top_level.txt +1 -0
  59. gdown2-0.2.0/pyproject.toml +85 -0
  60. gdown2-0.2.0/setup.cfg +4 -0
gdown2-0.2.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gdown2 contributors
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.
22
+
gdown2-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,456 @@
1
+ Metadata-Version: 2.4
2
+ Name: gdown2
3
+ Version: 0.2.0
4
+ Summary: Fast, resumable Google Drive file and folder downloader with async I/O, Rich CLI, and Textual TUI.
5
+ Author: gdown2 contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/moustaph97/gdown2
8
+ Project-URL: Repository, https://github.com/moustaph97/gdown2
9
+ Project-URL: Issues, https://github.com/moustaph97/gdown2/issues
10
+ Keywords: google-drive,downloader,cli,tui,async,gdown
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Internet :: WWW/HTTP
21
+ Classifier: Topic :: Utilities
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: httpx>=0.27.0
27
+ Requires-Dist: typer>=0.12.0
28
+ Requires-Dist: rich>=13.7.0
29
+ Requires-Dist: textual>=0.70.0
30
+ Requires-Dist: beautifulsoup4>=4.12.0
31
+ Requires-Dist: platformdirs>=4.0.0
32
+ Provides-Extra: service-account
33
+ Requires-Dist: google-auth>=2.0.0; extra == "service-account"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
36
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
37
+ Requires-Dist: respx>=0.21.0; extra == "dev"
38
+ Requires-Dist: ruff>=0.5.0; extra == "dev"
39
+ Requires-Dist: mypy>=1.10.0; extra == "dev"
40
+ Requires-Dist: google-auth>=2.0.0; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ # gdown2
44
+
45
+ **Fast, crash-safe Google Drive downloads for files, folders, and Google Workspace documents.**
46
+
47
+ `gdown2` is a ground-up rewrite of [`gdown`](https://github.com/wkentaro/gdown) built on
48
+ `asyncio` and `httpx`. It keeps the zero-config convenience of public Drive links while
49
+ adding a resumable transfer engine, adaptive rate limiting, recursive folder crawling,
50
+ checksum verification, three user interfaces, and a clean Python API.
51
+
52
+ [![CI](https://github.com/moustaph97/gdown2/actions/workflows/ci.yml/badge.svg)](https://github.com/moustaph97/gdown2/actions/workflows/ci.yml)
53
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
54
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
55
+ [![Code style: Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
56
+
57
+ ---
58
+
59
+ ## Why gdown2?
60
+
61
+ The original `gdown` is excellent for quick scripts. It becomes fragile when you pull
62
+ multi-gigabyte checkpoints, deeply nested folders with hundreds of files, or unstable
63
+ connections that drop mid-transfer.
64
+
65
+ `gdown2` was designed for those cases:
66
+
67
+ | Capability | What it means in practice |
68
+ |---|---|
69
+ | **Crash-safe resume** | Data is streamed to `<filename>.part`; completed files are promoted with an atomic `os.replace`. Interrupted transfers resume from the last byte. |
70
+ | **Google Drive API v3 + public web** | Works out of the box on public links, then transparently switches to the official API when given an API key, OAuth token, or service account. |
71
+ | **Recursive folder crawling** | Full BFS traversal with pagination, shortcut-cycle protection, filename sanitization, and collision handling. |
72
+ | **Two-level concurrency** | Parallel file downloads (`--workers`) plus optional multi-Range chunking for large files (`--chunks`). |
73
+ | **Adaptive throttling** | Per-host token bucket (`--rate`) and AIMD backoff when Google returns `429` or transient `403` responses. |
74
+ | **Integrity verification** | Size and MD5 checks after every transfer, with automatic retry and clean restart on corruption. |
75
+ | **Workspace exports** | Google Docs, Sheets, Slides, and Drawings are exported on the fly to Office, PDF, CSV, and other formats. |
76
+ | **Three interfaces** | Rich CLI, interactive Textual TUI, and newline-delimited JSON for automation. |
77
+
78
+ ---
79
+
80
+ ## Table of contents
81
+
82
+ - [Installation](#installation)
83
+ - [Quick start](#quick-start)
84
+ - [CLI overview](#cli-overview)
85
+ - [Python API](#python-api)
86
+ - [Authentication](#authentication)
87
+ - [Configuration](#configuration)
88
+ - [Reliability model](#reliability-model)
89
+ - [Exit codes](#exit-codes)
90
+ - [Documentation](#documentation)
91
+ - [Development](#development)
92
+ - [Project layout](#project-layout)
93
+ - [Contributing](#contributing)
94
+ - [License](#license)
95
+
96
+ ---
97
+
98
+ ## Installation
99
+
100
+ Requires **Python 3.11 or newer**.
101
+
102
+ ### From source
103
+
104
+ ```bash
105
+ git clone https://github.com/moustaph97/gdown2.git
106
+ cd gdown2
107
+ pip install .
108
+ ```
109
+
110
+ ### Editable development install
111
+
112
+ ```bash
113
+ git clone https://github.com/moustaph97/gdown2.git
114
+ cd gdown2
115
+ pip install -e ".[dev]"
116
+ ```
117
+
118
+ ### Optional service-account support
119
+
120
+ Service-account authentication requires `google-auth`:
121
+
122
+ ```bash
123
+ pip install "gdown2[service-account]"
124
+ ```
125
+
126
+ Verify the installation:
127
+
128
+ ```bash
129
+ gdown2 --version
130
+ gdown2 --help
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Quick start
136
+
137
+ ### 1. Inspect metadata
138
+
139
+ ```bash
140
+ gdown2 info "https://drive.google.com/file/d/1A2B3C4D5E6F7G8H9I0J/view?usp=sharing"
141
+ ```
142
+
143
+ ### 2. Preview a remote folder without downloading
144
+
145
+ ```bash
146
+ gdown2 tree "https://drive.google.com/drive/folders/1FolderId_9876543210"
147
+ ```
148
+
149
+ ```text
150
+ Dataset/
151
+ ├── train/
152
+ │ ├── part-000.parquet [412.5 MB]
153
+ │ └── part-001.parquet [410.1 MB]
154
+ ├── Notes.docx [export]
155
+ └── README.md [4.2 KB]
156
+
157
+ 1 folders, 4 files (1 docs), total known size: 822.6 MB
158
+ ```
159
+
160
+ ### 3. Download a file or an entire folder
161
+
162
+ ```bash
163
+ # Single file or recursive folder download.
164
+ gdown2 get "https://drive.google.com/drive/folders/1FolderId_9876543210" -o ./downloads
165
+
166
+ # Six parallel files, four Range chunks per large file, five requests per second.
167
+ gdown2 get <url-or-id> -o ./downloads --workers 6 --chunks 4 --rate 5.0
168
+
169
+ # Export Google Docs/Sheets/Slides as PDF instead of their Office default.
170
+ gdown2 get "https://docs.google.com/document/d/1DocId_1234567890abc/edit" --export-format pdf
171
+
172
+ # Flatten the remote hierarchy into one directory.
173
+ gdown2 get <folder-url> -o ./downloads --flat
174
+
175
+ # Launch the interactive three-pane TUI.
176
+ gdown2 get <folder-url> -o ./downloads --tui
177
+
178
+ # Stream machine-readable NDJSON events for CI and automation.
179
+ gdown2 get <url-or-id> -o ./downloads --json
180
+ ```
181
+
182
+ ### 4. Resume an interrupted session
183
+
184
+ If a transfer is interrupted by `Ctrl+C`, a network drop, or a Drive quota limit:
185
+
186
+ ```bash
187
+ gdown2 resume ./downloads
188
+ ```
189
+
190
+ Resume state is stored in `<output>/.gdown2.state.json`; partial data lives in
191
+ `<filename>.part` files. Run the command from the same output directory to continue
192
+ exactly where the previous session stopped.
193
+
194
+ ---
195
+
196
+ ## CLI overview
197
+
198
+ | Command | Purpose |
199
+ |---|---|
200
+ | `gdown2 info <url-or-id>` | Resolve and display metadata for a file, folder, or document. |
201
+ | `gdown2 tree <folder-url-or-id>` | Crawl a remote folder and print its hierarchy without downloading. |
202
+ | `gdown2 get <url-or-id> [-o DIR]` | Download a file, folder, or Google Workspace document. |
203
+ | `gdown2 resume [DIR]` | Resume incomplete transfers recorded in a directory. |
204
+ | `gdown2 cache-clear` | Delete the local metadata cache. |
205
+
206
+ Common `get` options:
207
+
208
+ | Option | Description |
209
+ |---|---|
210
+ | `-o, --output PATH` | Destination directory or file path. Defaults to the current directory. |
211
+ | `-w, --workers N` | Maximum concurrent file transfers. |
212
+ | `-c, --chunks N` | Parallel HTTP Range streams per large file. |
213
+ | `-r, --rate RPS` | Per-host request rate limit in requests per second. |
214
+ | `--flat` | Flatten the remote tree into the output directory. |
215
+ | `--overwrite` | Overwrite existing local files. |
216
+ | `--skip` | Skip files that already exist locally. |
217
+ | `--export-format FORMAT` | Export format for Google Workspace documents (`docx`, `xlsx`, `pptx`, `pdf`, `csv`, `txt`, ...). |
218
+ | `--verify / --no-verify` | Enable or disable MD5 verification. |
219
+ | `--api-key KEY` | Use Google Drive API v3 with an API key. |
220
+ | `--service-account FILE` | Use a GCP service-account JSON key. |
221
+ | `--tui` | Launch the interactive terminal UI. |
222
+ | `--json` | Emit newline-delimited JSON events. |
223
+
224
+ See the full [CLI reference](docs/cli.md) for every command and option.
225
+
226
+ ---
227
+
228
+ ## Python API
229
+
230
+ `gdown2` exposes both `async` functions and a synchronous convenience wrapper.
231
+
232
+ ```python
233
+ import asyncio
234
+ import gdown2
235
+
236
+
237
+ def main() -> None:
238
+ # Synchronous helper for scripts and notebooks.
239
+ tasks = gdown2.download_sync(
240
+ "https://drive.google.com/file/d/1A2B3C4D5E6F7G8H9I0J/view",
241
+ output="./data",
242
+ )
243
+ print(f"Downloaded {len(tasks)} task(s)")
244
+
245
+
246
+ async def async_example() -> None:
247
+ info = await gdown2.get_info("1A2B3C4D5E6F7G8H9I0J")
248
+ print(f"Resolved: {info.name} ({info.size} bytes)")
249
+
250
+ tree = await gdown2.get_tree(
251
+ "https://drive.google.com/drive/folders/1FolderId_9876543210"
252
+ )
253
+ tasks = await gdown2.download(tree.item.id, output="./data")
254
+ print(f"Completed {sum(t.state == 'done' for t in tasks)} file(s)")
255
+
256
+
257
+ main()
258
+ asyncio.run(async_example())
259
+ ```
260
+
261
+ For custom observers, runtime configuration, and cancellation patterns, see
262
+ [Python API](docs/python-api.md).
263
+
264
+ ---
265
+
266
+ ## Authentication
267
+
268
+ Public links work with no configuration. Providing credentials switches `gdown2` from
269
+ web scraping to the official Drive API v3, which is recommended for large folders,
270
+ private files, and higher rate limits.
271
+
272
+ Credential sources can be combined with CLI flags, environment variables, and
273
+ `config.toml`. When more than one credential is available, the selected provider order
274
+ is:
275
+
276
+ 1. Service account (`--service-account` / `GDOWN2_SERVICE_ACCOUNT_FILE`)
277
+ 2. OAuth user file (`GDOWN2_OAUTH_FILE`)
278
+ 3. OAuth bearer token (`GDOWN2_ACCESS_TOKEN`)
279
+ 4. API key (`--api-key` / `GDOWN2_API_KEY` / `GOOGLE_API_KEY`)
280
+ 5. Anonymous public access
281
+
282
+ Environment examples:
283
+
284
+ ```bash
285
+ # API key
286
+ export GDOWN2_API_KEY="AIza..."
287
+
288
+ # OAuth bearer token
289
+ export GDOWN2_ACCESS_TOKEN="ya29..."
290
+
291
+ # OAuth authorized_user JSON file
292
+ export GDOWN2_OAUTH_FILE="$HOME/.config/gdown2/authorized_user.json"
293
+
294
+ # GCP service account
295
+ export GDOWN2_SERVICE_ACCOUNT_FILE="$HOME/.config/gdown2/service_account.json"
296
+ ```
297
+
298
+ See [Configuration](docs/configuration.md) for the complete authentication and settings
299
+ reference.
300
+
301
+ ---
302
+
303
+ ## Configuration
304
+
305
+ Configuration is merged with the following precedence:
306
+
307
+ **CLI flags > environment variables > `config.toml` > built-in defaults**
308
+
309
+ Default config location: `~/.config/gdown2/config.toml` (or your OS user config
310
+ directory when that path does not exist).
311
+
312
+ ```toml
313
+ [download]
314
+ workers = 6
315
+ chunks_per_file = 4
316
+ rate_limit_rps = 5.0
317
+ max_retries = 5
318
+ timeout_seconds = 30.0
319
+ verify_checksums = true
320
+ existing_policy = "resume" # "resume" | "skip" | "overwrite"
321
+ export_format = "pdf"
322
+
323
+ [auth]
324
+ # Supplying credentials switches gdown2 to Drive API v3.
325
+ api_key = "AIza..."
326
+ # oauth_token_file = "~/.config/gdown2/authorized_user.json"
327
+ # service_account_file = "~/.config/gdown2/service_account.json"
328
+ ```
329
+
330
+ ---
331
+
332
+ ## Reliability model
333
+
334
+ `gdown2` treats downloads as recoverable transactions rather than one-shot streams.
335
+
336
+ - **Never writes directly to the final filename.** Incoming bytes go to
337
+ `<filename>.part`. The final file appears only after verification succeeds.
338
+ - **Handles `206`, `200`, and `416` correctly.** If the server honours `Range`, the
339
+ transfer resumes. If it ignores `Range` and returns `200`, the partial file is reset
340
+ instead of being corrupted. If it returns `416`, the existing part is verified and
341
+ promoted when complete.
342
+ - **Protects against stale partial data.** When the remote ETag changes or overwrite mode
343
+ is requested, stale `.part` files are discarded before a new transfer starts.
344
+ - **Verifies before promoting.** Size and MD5 checks run before the atomic rename.
345
+ Checksum failures delete the corrupted part and retry from byte zero.
346
+ - **Survives cancellation.** `Ctrl+C` flushes resume state and leaves partial files
347
+ ready for `gdown2 resume`.
348
+ - **Rejects untrusted redirects.** Only HTTPS Google domains are followed during
349
+ redirects and initial download URLs are validated before use.
350
+
351
+ The complete design and edge-case walkthrough is in [Architecture](docs/architecture.md).
352
+
353
+ ---
354
+
355
+ ## Exit codes
356
+
357
+ | Code | Name | Meaning |
358
+ |---|---|---|
359
+ | `0` | `OK` | All requested files downloaded (or skipped) and verified. |
360
+ | `1` | `GENERAL_ERROR` | One or more transfers failed. |
361
+ | `2` | `INVALID_ARGUMENT` | Malformed URL or unrecognized Google Drive ID. |
362
+ | `3` | `AUTH_REQUIRED` | HTTP 401 or expired/missing credentials. |
363
+ | `4` | `PERMISSION_DENIED` | HTTP 403 permission error (private file or folder). |
364
+ | `5` | `NOT_FOUND` | HTTP 404 / 410 resource does not exist. |
365
+ | `6` | `QUOTA_EXCEEDED` | Per-file download quota or rate limit exhausted. |
366
+ | `7` | `NETWORK_ERROR` | Unrecoverable transport error or untrusted redirect. |
367
+ | `8` | `INTEGRITY_ERROR` | Size or MD5 checksum mismatch after retries. |
368
+ | `9` | `STORAGE_ERROR` | Insufficient disk space, permission error, or path traversal. |
369
+ | `130` | `CANCELLED` | Interrupted by the user (`Ctrl+C`). |
370
+
371
+ This allows shell scripts and CI jobs to distinguish transient network problems from
372
+ permission or quota failures.
373
+
374
+ ---
375
+
376
+ ## Documentation
377
+
378
+ - [CLI reference](docs/cli.md)
379
+ - [Python API](docs/python-api.md)
380
+ - [Configuration](docs/configuration.md)
381
+ - [Architecture](docs/architecture.md)
382
+ - [Troubleshooting](docs/troubleshooting.md)
383
+ - [Development guide](docs/development.md)
384
+ - [Releasing and PyPI publishing](docs/releasing.md)
385
+
386
+ ---
387
+
388
+ ## Development
389
+
390
+ ```bash
391
+ # Create an isolated environment.
392
+ python -m venv .venv
393
+ .venv\Scripts\activate # Windows
394
+ # source .venv/bin/activate # macOS / Linux
395
+
396
+ # Install with development dependencies.
397
+ pip install -e ".[dev]"
398
+
399
+ # Run the test suite.
400
+ pytest
401
+
402
+ # Lint and type-check.
403
+ ruff check .
404
+ mypy gdown2
405
+ ```
406
+
407
+ The test suite uses `respx` to mock Google Drive endpoints. It covers URL parsing,
408
+ cross-platform path sanitization, tree operations, retry/AIMD policies, virus-scan
409
+ interstitials, `206`/`200`/`416` resume behaviour, rate-limit backoff, checksum
410
+ recovery, folder pagination, and shortcut cycles.
411
+
412
+ Live end-to-end tests are opt-in:
413
+
414
+ ```bash
415
+ GDOWN2_E2E_URL="https://drive.google.com/file/d/<id>/view" pytest tests/e2e
416
+ ```
417
+
418
+ See [Development guide](docs/development.md) for the full workflow.
419
+
420
+ ---
421
+
422
+ ## Project layout
423
+
424
+ ```text
425
+ gdown2/
426
+ ├── domain/ # Pure models, URL parser, tree operations, path policy, errors
427
+ ├── application/ # Resolver, crawler, planner, scheduler, worker, verifier, reporter
428
+ ├── infrastructure/ # HTTP, Drive API/web adapters, auth, cache, config, storage
429
+ ├── interfaces/ # Typer/Rich CLI, Textual TUI, NDJSON event writer
430
+ ├── sdk.py # Public async and synchronous Python API
431
+ └── py.typed # PEP 561 typing marker
432
+ ```
433
+
434
+ The `domain` layer has no network or filesystem dependencies. Application services
435
+ coordinate work through protocol interfaces, and infrastructure adapters can be replaced
436
+ without touching the engine. See [Architecture](docs/architecture.md) for details.
437
+
438
+ ---
439
+
440
+ ## Contributing
441
+
442
+ Contributions are welcome.
443
+
444
+ 1. Fork the repository and create a feature branch.
445
+ 2. Install the development dependencies with `pip install -e ".[dev]"`.
446
+ 3. Add or update tests for your change.
447
+ 4. Run `pytest`, `ruff check .`, and `mypy gdown2`.
448
+ 5. Open a pull request with a clear description of the problem and solution.
449
+
450
+ Please keep user-facing strings, comments, and documentation in English.
451
+
452
+ ---
453
+
454
+ ## License
455
+
456
+ MIT. See [LICENSE](LICENSE).