rungpu 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 (36) hide show
  1. rungpu-0.1.0/PKG-INFO +200 -0
  2. rungpu-0.1.0/README.md +178 -0
  3. rungpu-0.1.0/pyproject.toml +85 -0
  4. rungpu-0.1.0/pyproject.toml.orig +61 -0
  5. rungpu-0.1.0/src/gpu/__init__.py +0 -0
  6. rungpu-0.1.0/src/gpu/cli/__init__.py +0 -0
  7. rungpu-0.1.0/src/gpu/cli/app.py +285 -0
  8. rungpu-0.1.0/src/gpu/cli/render.py +209 -0
  9. rungpu-0.1.0/src/gpu/config.py +31 -0
  10. rungpu-0.1.0/src/gpu/domain/__init__.py +0 -0
  11. rungpu-0.1.0/src/gpu/domain/enums.py +31 -0
  12. rungpu-0.1.0/src/gpu/domain/exceptions.py +50 -0
  13. rungpu-0.1.0/src/gpu/domain/models.py +151 -0
  14. rungpu-0.1.0/src/gpu/orchestration/__init__.py +0 -0
  15. rungpu-0.1.0/src/gpu/orchestration/job_log.py +44 -0
  16. rungpu-0.1.0/src/gpu/orchestration/job_runner.py +487 -0
  17. rungpu-0.1.0/src/gpu/orchestration/job_store.py +44 -0
  18. rungpu-0.1.0/src/gpu/orchestration/lifecycle.py +162 -0
  19. rungpu-0.1.0/src/gpu/orchestration/reporter.py +51 -0
  20. rungpu-0.1.0/src/gpu/providers/__init__.py +0 -0
  21. rungpu-0.1.0/src/gpu/providers/base.py +36 -0
  22. rungpu-0.1.0/src/gpu/providers/factory.py +8 -0
  23. rungpu-0.1.0/src/gpu/providers/vast/__init__.py +3 -0
  24. rungpu-0.1.0/src/gpu/providers/vast/mapper.py +77 -0
  25. rungpu-0.1.0/src/gpu/providers/vast/provider.py +222 -0
  26. rungpu-0.1.0/src/gpu/providers/vast/query.py +34 -0
  27. rungpu-0.1.0/src/gpu/remote/__init__.py +0 -0
  28. rungpu-0.1.0/src/gpu/remote/commands.py +74 -0
  29. rungpu-0.1.0/src/gpu/remote/redaction.py +57 -0
  30. rungpu-0.1.0/src/gpu/remote/session.py +45 -0
  31. rungpu-0.1.0/src/gpu/remote/ssh.py +220 -0
  32. rungpu-0.1.0/src/gpu/remote/transfer.py +110 -0
  33. rungpu-0.1.0/src/gpu/remote/watchdog.py +57 -0
  34. rungpu-0.1.0/src/gpu/selection/__init__.py +0 -0
  35. rungpu-0.1.0/src/gpu/selection/scoring.py +45 -0
  36. rungpu-0.1.0/src/gpu/selection/selector.py +89 -0
rungpu-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,200 @@
1
+ Metadata-Version: 2.3
2
+ Name: rungpu
3
+ Version: 0.1.0
4
+ Summary: Run a Python/ML job on the cheapest suitable GPU without thinking about cloud infrastructure.
5
+ Keywords: gpu,machine-learning,vast-ai,cloud,cli
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Environment :: Console
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
11
+ Requires-Dist: asyncssh>=2.24.0
12
+ Requires-Dist: pathspec>=0.12.1
13
+ Requires-Dist: pydantic>=2.13.5
14
+ Requires-Dist: pydantic-settings>=2.6.0
15
+ Requires-Dist: requests>=2.33.0
16
+ Requires-Dist: rich>=15.0.0
17
+ Requires-Dist: tenacity>=9.1.4
18
+ Requires-Dist: typer>=0.27.2
19
+ Requires-Dist: vastai>=1.7.0
20
+ Requires-Python: >=3.13
21
+ Description-Content-Type: text/markdown
22
+
23
+ # rungpu
24
+
25
+ Run a Python/ML job on the cheapest suitable GPU without thinking about cloud infrastructure.
26
+
27
+ ```bash
28
+ gpu run "python train.py" --vram 24 --max-price 0.50
29
+ ```
30
+
31
+ That one command finds a GPU, rents it, uploads your project, installs dependencies, streams the
32
+ output, brings your results back and destroys the instance. You never open a cloud console or
33
+ configure SSH.
34
+
35
+ ![A `gpu run` session from search to destroying the instance](docs/demo.gif)
36
+
37
+ <sub>A replay of a real run, sped up, with two lines of noise removed.</sub>
38
+
39
+ > **Status: early MVP.** Vast.ai is the only provider. The full flow works on real rented GPUs,
40
+ > but only on demo projects so far. See [Limitations](#limitations).
41
+
42
+ ## Quick start
43
+
44
+ You need a [Vast.ai](https://vast.ai) account with credit and an
45
+ [API key](https://console.vast.ai/manage-keys/). You pay Vast.ai directly.
46
+
47
+ rungpu needs Python 3.13. [uv](https://docs.astral.sh/uv/) installs it for you:
48
+
49
+ ```bash
50
+ uv tool install rungpu
51
+ ```
52
+
53
+ With Python 3.13 already installed, `pip install rungpu` works too. The command is `gpu`:
54
+
55
+ ```bash
56
+ export VAST_API_KEY=... # or put it in a .env file
57
+ gpu search --vram 24 --max-price 0.50
58
+ ```
59
+
60
+ Then, from your project directory:
61
+
62
+ ```bash
63
+ gpu run "python train.py" --vram 24 --max-price 0.50
64
+ ```
65
+
66
+ ## Commands
67
+
68
+ ### `gpu search`
69
+
70
+ Shows matching offers, best value first.
71
+
72
+ ```bash
73
+ gpu search --vram 24 --max-price 0.50 --gpus 1 --limit 10
74
+ ```
75
+
76
+ ### `gpu run`
77
+
78
+ | Option | Meaning | Default |
79
+ |---|---|---|
80
+ | `--vram` | Minimum VRAM per GPU, in GB | 24 |
81
+ | `--max-price` | Maximum $/hour. Without it, you are asked to confirm | none |
82
+ | `--gpus` | Number of GPUs | 1 |
83
+ | `--mode` | What matters most: `cheap`, `balanced` or `fast` | `cheap` |
84
+ | `--disk` | Disk size, in GB | 30 |
85
+ | `--min-reliability` | From 0 to 1 | 0.99 |
86
+ | `--artifacts`, `-a` | Path to bring back. Repeatable | `runs` |
87
+ | `--env`, `-e` | `KEY=VALUE`, or `KEY` to copy it from your shell. Repeatable | none |
88
+ | `--image` | Docker image | `vastai/pytorch` |
89
+ | `--setup` | Replaces the automatic dependency install | detected |
90
+ | `--no-setup` | Skip dependency install | off |
91
+ | `--max-hours` | Stop the command after this long | none |
92
+ | `--yes`, `-y` | Don't ask to confirm the rental | off |
93
+
94
+ `gpu run` exits with your command's exit code, so it works in scripts. Exit code `3` means an
95
+ instance could not be confirmed destroyed. `130` means it was interrupted.
96
+
97
+ ### Choosing what matters
98
+
99
+ `--mode` works with `gpu search` and `gpu run`.
100
+
101
+ | Mode | Picks | Good for |
102
+ |---|---|---|
103
+ | `cheap` | The lowest expected cost of the job, not the lowest hourly price | Long jobs on a budget |
104
+ | `balanced` | A big speed gain for a small extra cost | Most jobs |
105
+ | `fast` | The fastest GPU within `--max-price` | When time matters more than money |
106
+
107
+ ```bash
108
+ gpu run "python train.py" --vram 24 --max-price 1.00 --mode fast
109
+ ```
110
+
111
+ When the choice isn't the best value offer, `gpu run` shows the trade-off:
112
+
113
+ ```text
114
+ vs best value (RTX PRO 4000): about 1.9x faster, about 4% more per job (DLPerf estimate)
115
+ ```
116
+
117
+ This is a rough estimate. Modes compare GPUs using DLPerf, a general benchmark that knows nothing
118
+ about your model, so it can't predict how long your job will take. If your job is limited by its
119
+ disk or CPU, a faster GPU may just sit idle. When one offer is both the fastest and the best
120
+ value, all three modes pick it. Vast's search doesn't return every offer on every call, so the
121
+ choice is the best one seen, not a guaranteed market best.
122
+
123
+ ### `gpu jobs`
124
+
125
+ Lists recent jobs and warns about instances that are still running.
126
+
127
+ ### `gpu destroy <instance-id>`
128
+
129
+ For emergencies. `gpu run` destroys instances by itself.
130
+
131
+ ## How your project is handled
132
+
133
+ **What gets uploaded.** The current directory, except for `.git`, virtual environments, caches,
134
+ everything in `.gitignore`, and secrets: `.env`, `*.pem`, `*.key` and SSH keys.
135
+
136
+ **Your dataset may be gitignored.** If so, it won't be uploaded and your job won't find it. Add a
137
+ `.gpuignore` file, which uses the `.gitignore` syntax and takes priority:
138
+
139
+ ```gitignore
140
+ !data/
141
+ *.ckpt
142
+ ```
143
+
144
+ **Dependencies** come from `requirements.txt`, or otherwise from `[project].dependencies` in
145
+ `pyproject.toml`.
146
+
147
+ **Results** are copied into the same relative path locally, overwriting files with the same name.
148
+ They are also downloaded when your command fails, because logs and checkpoints matter most then.
149
+
150
+ ## Safety
151
+
152
+ The instance is destroyed after an exception, a training crash, an SSH failure or Ctrl+C, including
153
+ an impatient second Ctrl+C. After destroying it, the tool reads it back to confirm. It then looks
154
+ for any other instance carrying the job's label, which catches an instance whose id was lost
155
+ because of an interruption mid-request.
156
+
157
+ If destruction can't be confirmed, you get a red panel with the exact command to run, and exit code
158
+ 3.
159
+
160
+ If your computer dies, `gpu` can't clean up. So each instance watches for a heartbeat from your
161
+ computer, and destroys itself after 10 minutes of silence, using a key that only works for that
162
+ instance. Your account key never leaves your computer. To change the timeout, set
163
+ `GPU_WATCHDOG_TIMEOUT_SECONDS`, where `0` turns this off.
164
+
165
+ **Marketplace hosts are third parties.** Whoever owns the machine can technically see your code,
166
+ your data and any variables you pass with `--env`. Don't run anything there that you couldn't show
167
+ them. Prefer `--env` over uploading `.env`, and scoped tokens over personal ones. Output is masked
168
+ for `--env` values and common token formats, but this is best effort.
169
+
170
+ ## Limitations
171
+
172
+ - **The dead-man switch has a limit.** After `kill -9`, a power cut or a lost network, the instance
173
+ destroys itself after 10 minutes without a heartbeat. If the instance never finishes starting,
174
+ the switch can't run, so check `gpu jobs` after a crash. If it can't arm itself, you get a warning.
175
+ - **Don't let your laptop sleep during a job.** A sleeping computer sends no heartbeats, so the
176
+ instance is destroyed.
177
+ - **A network outage of more than about 3 minutes ends the job,** because the command runs inside
178
+ the SSH session. Reconnection is planned.
179
+ - **The cost shown excludes traffic and storage during startup.** Traffic prices vary by a factor
180
+ of about 15 between hosts. On a first real job, the final charge was about 2.5 times the estimate.
181
+ - DLPerf is a proxy. It doesn't know your workload, so `--mode` ranks GPUs but can't predict
182
+ your job's runtime or cost. A lone offer for its GPU model can't be checked for an inflated
183
+ benchmark, which matters most with `--mode fast`.
184
+ - On-demand instances on a single node only.
185
+
186
+ ## Development
187
+
188
+ ```bash
189
+ git clone https://github.com/MikhailSeliakov/rungpu.git
190
+ uv sync
191
+ uv run pytest # Unit tests. Never touches the network.
192
+ uv run ruff check .
193
+ uv run pytest -m integration # Real Vast API. Free and read-only.
194
+ ```
195
+
196
+ The end-to-end test **rents a real GPU** and needs a second, explicit switch:
197
+
198
+ ```bash
199
+ GPU_E2E_ALLOW_SPEND=1 uv run pytest -m e2e
200
+ ```
rungpu-0.1.0/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # rungpu
2
+
3
+ Run a Python/ML job on the cheapest suitable GPU without thinking about cloud infrastructure.
4
+
5
+ ```bash
6
+ gpu run "python train.py" --vram 24 --max-price 0.50
7
+ ```
8
+
9
+ That one command finds a GPU, rents it, uploads your project, installs dependencies, streams the
10
+ output, brings your results back and destroys the instance. You never open a cloud console or
11
+ configure SSH.
12
+
13
+ ![A `gpu run` session from search to destroying the instance](docs/demo.gif)
14
+
15
+ <sub>A replay of a real run, sped up, with two lines of noise removed.</sub>
16
+
17
+ > **Status: early MVP.** Vast.ai is the only provider. The full flow works on real rented GPUs,
18
+ > but only on demo projects so far. See [Limitations](#limitations).
19
+
20
+ ## Quick start
21
+
22
+ You need a [Vast.ai](https://vast.ai) account with credit and an
23
+ [API key](https://console.vast.ai/manage-keys/). You pay Vast.ai directly.
24
+
25
+ rungpu needs Python 3.13. [uv](https://docs.astral.sh/uv/) installs it for you:
26
+
27
+ ```bash
28
+ uv tool install rungpu
29
+ ```
30
+
31
+ With Python 3.13 already installed, `pip install rungpu` works too. The command is `gpu`:
32
+
33
+ ```bash
34
+ export VAST_API_KEY=... # or put it in a .env file
35
+ gpu search --vram 24 --max-price 0.50
36
+ ```
37
+
38
+ Then, from your project directory:
39
+
40
+ ```bash
41
+ gpu run "python train.py" --vram 24 --max-price 0.50
42
+ ```
43
+
44
+ ## Commands
45
+
46
+ ### `gpu search`
47
+
48
+ Shows matching offers, best value first.
49
+
50
+ ```bash
51
+ gpu search --vram 24 --max-price 0.50 --gpus 1 --limit 10
52
+ ```
53
+
54
+ ### `gpu run`
55
+
56
+ | Option | Meaning | Default |
57
+ |---|---|---|
58
+ | `--vram` | Minimum VRAM per GPU, in GB | 24 |
59
+ | `--max-price` | Maximum $/hour. Without it, you are asked to confirm | none |
60
+ | `--gpus` | Number of GPUs | 1 |
61
+ | `--mode` | What matters most: `cheap`, `balanced` or `fast` | `cheap` |
62
+ | `--disk` | Disk size, in GB | 30 |
63
+ | `--min-reliability` | From 0 to 1 | 0.99 |
64
+ | `--artifacts`, `-a` | Path to bring back. Repeatable | `runs` |
65
+ | `--env`, `-e` | `KEY=VALUE`, or `KEY` to copy it from your shell. Repeatable | none |
66
+ | `--image` | Docker image | `vastai/pytorch` |
67
+ | `--setup` | Replaces the automatic dependency install | detected |
68
+ | `--no-setup` | Skip dependency install | off |
69
+ | `--max-hours` | Stop the command after this long | none |
70
+ | `--yes`, `-y` | Don't ask to confirm the rental | off |
71
+
72
+ `gpu run` exits with your command's exit code, so it works in scripts. Exit code `3` means an
73
+ instance could not be confirmed destroyed. `130` means it was interrupted.
74
+
75
+ ### Choosing what matters
76
+
77
+ `--mode` works with `gpu search` and `gpu run`.
78
+
79
+ | Mode | Picks | Good for |
80
+ |---|---|---|
81
+ | `cheap` | The lowest expected cost of the job, not the lowest hourly price | Long jobs on a budget |
82
+ | `balanced` | A big speed gain for a small extra cost | Most jobs |
83
+ | `fast` | The fastest GPU within `--max-price` | When time matters more than money |
84
+
85
+ ```bash
86
+ gpu run "python train.py" --vram 24 --max-price 1.00 --mode fast
87
+ ```
88
+
89
+ When the choice isn't the best value offer, `gpu run` shows the trade-off:
90
+
91
+ ```text
92
+ vs best value (RTX PRO 4000): about 1.9x faster, about 4% more per job (DLPerf estimate)
93
+ ```
94
+
95
+ This is a rough estimate. Modes compare GPUs using DLPerf, a general benchmark that knows nothing
96
+ about your model, so it can't predict how long your job will take. If your job is limited by its
97
+ disk or CPU, a faster GPU may just sit idle. When one offer is both the fastest and the best
98
+ value, all three modes pick it. Vast's search doesn't return every offer on every call, so the
99
+ choice is the best one seen, not a guaranteed market best.
100
+
101
+ ### `gpu jobs`
102
+
103
+ Lists recent jobs and warns about instances that are still running.
104
+
105
+ ### `gpu destroy <instance-id>`
106
+
107
+ For emergencies. `gpu run` destroys instances by itself.
108
+
109
+ ## How your project is handled
110
+
111
+ **What gets uploaded.** The current directory, except for `.git`, virtual environments, caches,
112
+ everything in `.gitignore`, and secrets: `.env`, `*.pem`, `*.key` and SSH keys.
113
+
114
+ **Your dataset may be gitignored.** If so, it won't be uploaded and your job won't find it. Add a
115
+ `.gpuignore` file, which uses the `.gitignore` syntax and takes priority:
116
+
117
+ ```gitignore
118
+ !data/
119
+ *.ckpt
120
+ ```
121
+
122
+ **Dependencies** come from `requirements.txt`, or otherwise from `[project].dependencies` in
123
+ `pyproject.toml`.
124
+
125
+ **Results** are copied into the same relative path locally, overwriting files with the same name.
126
+ They are also downloaded when your command fails, because logs and checkpoints matter most then.
127
+
128
+ ## Safety
129
+
130
+ The instance is destroyed after an exception, a training crash, an SSH failure or Ctrl+C, including
131
+ an impatient second Ctrl+C. After destroying it, the tool reads it back to confirm. It then looks
132
+ for any other instance carrying the job's label, which catches an instance whose id was lost
133
+ because of an interruption mid-request.
134
+
135
+ If destruction can't be confirmed, you get a red panel with the exact command to run, and exit code
136
+ 3.
137
+
138
+ If your computer dies, `gpu` can't clean up. So each instance watches for a heartbeat from your
139
+ computer, and destroys itself after 10 minutes of silence, using a key that only works for that
140
+ instance. Your account key never leaves your computer. To change the timeout, set
141
+ `GPU_WATCHDOG_TIMEOUT_SECONDS`, where `0` turns this off.
142
+
143
+ **Marketplace hosts are third parties.** Whoever owns the machine can technically see your code,
144
+ your data and any variables you pass with `--env`. Don't run anything there that you couldn't show
145
+ them. Prefer `--env` over uploading `.env`, and scoped tokens over personal ones. Output is masked
146
+ for `--env` values and common token formats, but this is best effort.
147
+
148
+ ## Limitations
149
+
150
+ - **The dead-man switch has a limit.** After `kill -9`, a power cut or a lost network, the instance
151
+ destroys itself after 10 minutes without a heartbeat. If the instance never finishes starting,
152
+ the switch can't run, so check `gpu jobs` after a crash. If it can't arm itself, you get a warning.
153
+ - **Don't let your laptop sleep during a job.** A sleeping computer sends no heartbeats, so the
154
+ instance is destroyed.
155
+ - **A network outage of more than about 3 minutes ends the job,** because the command runs inside
156
+ the SSH session. Reconnection is planned.
157
+ - **The cost shown excludes traffic and storage during startup.** Traffic prices vary by a factor
158
+ of about 15 between hosts. On a first real job, the final charge was about 2.5 times the estimate.
159
+ - DLPerf is a proxy. It doesn't know your workload, so `--mode` ranks GPUs but can't predict
160
+ your job's runtime or cost. A lone offer for its GPU model can't be checked for an inflated
161
+ benchmark, which matters most with `--mode fast`.
162
+ - On-demand instances on a single node only.
163
+
164
+ ## Development
165
+
166
+ ```bash
167
+ git clone https://github.com/MikhailSeliakov/rungpu.git
168
+ uv sync
169
+ uv run pytest # Unit tests. Never touches the network.
170
+ uv run ruff check .
171
+ uv run pytest -m integration # Real Vast API. Free and read-only.
172
+ ```
173
+
174
+ The end-to-end test **rents a real GPU** and needs a second, explicit switch:
175
+
176
+ ```bash
177
+ GPU_E2E_ALLOW_SPEND=1 uv run pytest -m e2e
178
+ ```
@@ -0,0 +1,85 @@
1
+ [project]
2
+ name = "rungpu"
3
+ version = "0.1.0"
4
+ description = "Run a Python/ML job on the cheapest suitable GPU without thinking about cloud infrastructure."
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ keywords = [
8
+ "gpu",
9
+ "machine-learning",
10
+ "vast-ai",
11
+ "cloud",
12
+ "cli",
13
+ ]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
20
+ ]
21
+ dependencies = [
22
+ "asyncssh>=2.24.0",
23
+ "pathspec>=0.12.1",
24
+ "pydantic>=2.13.5",
25
+ "pydantic-settings>=2.6.0",
26
+ "requests>=2.33.0",
27
+ "rich>=15.0.0",
28
+ "tenacity>=9.1.4",
29
+ "typer>=0.27.2",
30
+ "vastai>=1.7.0",
31
+ ]
32
+
33
+ [project.scripts]
34
+ gpu = "gpu.cli.app:main"
35
+
36
+ [dependency-groups]
37
+ dev = [
38
+ "black>=26.5.1",
39
+ "pytest>=8.3.0",
40
+ "ruff>=0.16.7",
41
+ ]
42
+
43
+ [build-system]
44
+ requires = ["uv_build>=0.12.13,<0.13"]
45
+ build-backend = "uv_build"
46
+
47
+ [tool.uv.build-backend]
48
+ module-name = "gpu"
49
+
50
+ [tool.pytest.ini_options]
51
+ testpaths = ["tests"]
52
+ addopts = "-m 'not integration and not e2e' --strict-markers"
53
+ markers = [
54
+ "integration: free read-only calls to the real provider API (pytest -m integration)",
55
+ "e2e: rents a real, paid instance; also requires GPU_E2E_ALLOW_SPEND=1 (pytest -m e2e)",
56
+ ]
57
+
58
+ [tool.ruff]
59
+ line-length = 100
60
+ target-version = "py313"
61
+ src = [
62
+ "src",
63
+ "tests",
64
+ ]
65
+
66
+ [tool.ruff.lint]
67
+ select = [
68
+ "E",
69
+ "F",
70
+ "I",
71
+ "UP",
72
+ "B",
73
+ "S",
74
+ "SIM",
75
+ "RUF",
76
+ ]
77
+
78
+ [tool.ruff.lint.per-file-ignores]
79
+ "tests/**" = [
80
+ "S101",
81
+ "S104",
82
+ "S105",
83
+ "S106",
84
+ "S108",
85
+ ]
@@ -0,0 +1,61 @@
1
+ [project]
2
+ name = "rungpu"
3
+ version = "0.1.0"
4
+ description = "Run a Python/ML job on the cheapest suitable GPU without thinking about cloud infrastructure."
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ keywords = ["gpu", "machine-learning", "vast-ai", "cloud", "cli"]
8
+ classifiers = [
9
+ "Development Status :: 3 - Alpha",
10
+ "Environment :: Console",
11
+ "Intended Audience :: Developers",
12
+ "Programming Language :: Python :: 3.13",
13
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
14
+ ]
15
+ dependencies = [
16
+ "asyncssh>=2.24.0",
17
+ "pathspec>=0.12.1",
18
+ "pydantic>=2.13.5",
19
+ "pydantic-settings>=2.6.0",
20
+ "requests>=2.33.0",
21
+ "rich>=15.0.0",
22
+ "tenacity>=9.1.4",
23
+ "typer>=0.27.2",
24
+ "vastai>=1.7.0",
25
+ ]
26
+
27
+ [project.scripts]
28
+ gpu = "gpu.cli.app:main"
29
+
30
+ [dependency-groups]
31
+ dev = [
32
+ "black>=26.5.1",
33
+ "pytest>=8.3.0",
34
+ "ruff>=0.16.7",
35
+ ]
36
+
37
+ [build-system]
38
+ requires = ["uv_build>=0.12.13,<0.13"]
39
+ build-backend = "uv_build"
40
+
41
+ [tool.uv.build-backend]
42
+ module-name = "gpu"
43
+
44
+ [tool.pytest.ini_options]
45
+ testpaths = ["tests"]
46
+ addopts = "-m 'not integration and not e2e' --strict-markers"
47
+ markers = [
48
+ "integration: free read-only calls to the real provider API (pytest -m integration)",
49
+ "e2e: rents a real, paid instance; also requires GPU_E2E_ALLOW_SPEND=1 (pytest -m e2e)",
50
+ ]
51
+
52
+ [tool.ruff]
53
+ line-length = 100
54
+ target-version = "py313"
55
+ src = ["src", "tests"]
56
+
57
+ [tool.ruff.lint]
58
+ select = ["E", "F", "I", "UP", "B", "S", "SIM", "RUF"]
59
+
60
+ [tool.ruff.lint.per-file-ignores]
61
+ "tests/**" = ["S101", "S104", "S105", "S106", "S108"]
File without changes
File without changes