pdt-cli 0.1.2__tar.gz → 0.1.3__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.
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/.gitignore +1 -0
- pdt_cli-0.1.3/LICENSE +9 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/PKG-INFO +19 -9
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/README.md +16 -7
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/pyproject.toml +3 -3
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/cli.py +133 -4
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/completion.py +1 -1
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/config.py +111 -24
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/console.py +23 -6
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/deploy.py +66 -6
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/deploy_aws.py +200 -53
- pdt_cli-0.1.3/src/pdt/deploy_aws_batch.py +796 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/deploy_azure.py +57 -8
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/deploy_azure_container_apps.py +199 -28
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/deploy_common.py +27 -8
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/deploy_google_cloud.py +204 -36
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/deploy_windows.py +205 -41
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/gcloud_sdk.py +7 -0
- pdt_cli-0.1.3/src/pdt/run_windows_task.py +59 -0
- pdt_cli-0.1.3/src/pdt/runs_cli.py +349 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/scaffold.py +8 -1
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/utils/email_auth.py +3 -1
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/utils/env_secret.py +9 -1
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/utils/storage.py +4 -3
- pdt_cli-0.1.3/tests/test_app_name.py +41 -0
- pdt_cli-0.1.3/tests/test_aws_cost_estimate.py +20 -0
- pdt_cli-0.1.3/tests/test_aws_deployer_policy.py +148 -0
- pdt_cli-0.1.3/tests/test_aws_destroy.py +251 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_aws_inventory.py +25 -0
- pdt_cli-0.1.3/tests/test_aws_job_definition.py +127 -0
- pdt_cli-0.1.3/tests/test_aws_names.py +20 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_aws_not_found.py +4 -0
- pdt_cli-0.1.3/tests/test_aws_runs.py +121 -0
- pdt_cli-0.1.3/tests/test_aws_shared_deletes.py +143 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_azure_deleted_vault.py +5 -1
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_azure_environment.py +3 -3
- pdt_cli-0.1.3/tests/test_azure_runs.py +117 -0
- pdt_cli-0.1.3/tests/test_azure_shared_locks.py +187 -0
- pdt_cli-0.1.3/tests/test_build_context.py +62 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_ci_azure.py +33 -0
- pdt_cli-0.1.3/tests/test_ci_google_cloud.py +200 -0
- pdt_cli-0.1.3/tests/test_cli.py +325 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_completion.py +35 -1
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_config_merge.py +61 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_deploy_state.py +51 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_dockerfile.py +18 -0
- pdt_cli-0.1.3/tests/test_gcloud_sdk.py +52 -0
- pdt_cli-0.1.3/tests/test_google_cloud_retries.py +124 -0
- pdt_cli-0.1.3/tests/test_google_cloud_runs.py +154 -0
- pdt_cli-0.1.3/tests/test_google_cloud_storage.py +124 -0
- pdt_cli-0.1.3/tests/test_private_file.py +73 -0
- pdt_cli-0.1.3/tests/test_runs_cli.py +387 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_save_platform_key.py +60 -1
- pdt_cli-0.1.3/tests/test_set_app_enabled.py +49 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_storage.py +1 -2
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_verify.py +20 -3
- pdt_cli-0.1.3/tests/test_windows_data.py +101 -0
- pdt_cli-0.1.3/tests/test_windows_runner.py +96 -0
- pdt_cli-0.1.3/tests/test_windows_runs.py +159 -0
- pdt_cli-0.1.3/tests/test_windows_storage.py +144 -0
- pdt_cli-0.1.3/tests/test_winget_launcher.py +66 -0
- pdt_cli-0.1.3/tests/test_winget_manifests.py +48 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/verify/README.md +1 -1
- pdt_cli-0.1.2/src/pdt/deploy_aws_fargate.py +0 -508
- pdt_cli-0.1.2/tests/test_aws_names.py +0 -9
- pdt_cli-0.1.2/tests/test_ci_google_cloud.py +0 -93
- pdt_cli-0.1.2/tests/test_cli.py +0 -104
- pdt_cli-0.1.2/tests/test_gcloud_sdk.py +0 -24
- pdt_cli-0.1.2/tests/test_google_cloud_retries.py +0 -41
- pdt_cli-0.1.2/tests/test_google_cloud_storage.py +0 -25
- pdt_cli-0.1.2/tests/test_windows_storage.py +0 -50
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/ci/tests/test_claude_task.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/ci/tests/test_close_stale_drafts.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/ci/tests/test_rebase_task.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/ci/tests/test_score_task.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/__init__.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/examples/hello-world/config.yml +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/examples/hello-world/run.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/examples/impossible-travel-report/config.yml +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/examples/impossible-travel-report/env.template +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/examples/impossible-travel-report/run.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/examples/monday-orphaned-account-report/config.yml +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/examples/monday-orphaned-account-report/env.template +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/examples/monday-orphaned-account-report/run.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/storage_cli.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/utils/__init__.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/utils/entra.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/utils/log.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/utils/send_email.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/utils/send_email_graph.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/src/pdt/utils/web.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/conftest.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_aws_account.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_aws_profile.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_aws_storage.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_azure_build.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_azure_environment_key.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_azure_group_ownership.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_azure_job_diagnostics.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_azure_names.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_azure_retries.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_azure_side_resources.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_azure_storage.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_azure_subscription_state.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_console.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_cost_estimate.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_env_secret.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_env_sources.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_fetch_json.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_google_cloud_inventory.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_google_cloud_not_found.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_google_cloud_service_account.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_init_questions.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_log_colour.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_project_discovery.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_run_log_links.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_scaffold.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_schedule.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_secrets.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_starter_app.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_storage_cli.py +0 -0
- {pdt_cli-0.1.2 → pdt_cli-0.1.3}/tests/test_strict_validation.py +0 -0
pdt_cli-0.1.3/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AutoIDM
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pdt-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Easily deploy your process to AWS, Azure, Google Cloud, or Windows.
|
|
5
5
|
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
6
7
|
Requires-Python: >=3.12
|
|
7
8
|
Requires-Dist: argcomplete
|
|
8
9
|
Requires-Dist: backoff
|
|
@@ -26,7 +27,7 @@ Requires-Dist: gcsfs; extra == 'apps'
|
|
|
26
27
|
Requires-Dist: google-cloud-secret-manager; extra == 'apps'
|
|
27
28
|
Requires-Dist: google-cloud-storage; extra == 'apps'
|
|
28
29
|
Requires-Dist: pyjwt; extra == 'apps'
|
|
29
|
-
Requires-Dist: s3fs; extra == 'apps'
|
|
30
|
+
Requires-Dist: s3fs>=2024; extra == 'apps'
|
|
30
31
|
Description-Content-Type: text/markdown
|
|
31
32
|
|
|
32
33
|
# pdt
|
|
@@ -41,7 +42,7 @@ uv tool install pdt-cli
|
|
|
41
42
|
|
|
42
43
|
That puts a `pdt` command on your PATH. To update it later, run `uv tool upgrade pdt-cli`.
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
On Windows you can run `winget install AutoIDM.pdt` instead, which also installs `uv` if you do not have it and puts the same `pdt` command on your PATH. `winget upgrade AutoIDM.pdt` moves pdt to the latest release, and the first `pdt` command after an install or upgrade finishes setting up that version.
|
|
45
46
|
|
|
46
47
|
You can also clone this repository and run `./pdt` (or `.\pdt.bat` on Windows) instead. It installs `uv` for you if you do not have it. Both ways give you the same commands.
|
|
47
48
|
|
|
@@ -121,6 +122,7 @@ pdt login my-report
|
|
|
121
122
|
| `pdt validate` | check the config files and the required env vars |
|
|
122
123
|
| `pdt run APP` | run an app on this machine |
|
|
123
124
|
| `pdt deploy APP` | deploy an app to its configured platform |
|
|
125
|
+
| `pdt deploy --all` | deploy every enabled app, in order; asks whether to skip and disable an app that fails; add `--yes --skip-failures` to run unattended |
|
|
124
126
|
| `pdt destroy APP` | remove everything deploy created |
|
|
125
127
|
| `pdt secrets APP` | show which `.env` values differ from the deployed app |
|
|
126
128
|
| `pdt secrets APP save` | send your `.env` values to the deployed app; the next run uses them |
|
|
@@ -128,6 +130,9 @@ pdt login my-report
|
|
|
128
130
|
| `pdt secrets APP set NAME` | put one value, read from stdin, into the deployed app's secrets |
|
|
129
131
|
| `pdt login APP` | sign in again to the app's cloud provider |
|
|
130
132
|
| `pdt storage APP ls|get|query|destroy` | look at, fetch, query, or delete the app's stored files |
|
|
133
|
+
| `pdt runs APP` | list the deployed app's runs with each run's exit code: the 10 newest, or with `--since 3d` (or `12h`, `2w`, `2026-09-20`, `2026-09-20T14:00`) every run since then, `--span 1d` keeping only the runs within that long after `--since` and `--count N` keeping only the N newest; a run's number is its place among every run pdt can still find, so it stays the same whichever runs print |
|
|
134
|
+
| `pdt logs APP [N]` | read the log of run N as `pdt runs` numbers it; with no N, the newest run, and `--failed` picks the newest failed run instead; `--since`, `--span`, and `--count` limit which runs those two choose from, `--errors` leaves out DEBUG and INFO lines; the last 20 lines print, `--lines N` prints N instead, `--head` prints the first lines instead of the last, and `--full` prints every line |
|
|
135
|
+
| `pdt health [APP]` | show whether each app's last run succeeded; exits 1 when one failed |
|
|
131
136
|
| `pdt az ...` | run the Azure CLI that pdt installs |
|
|
132
137
|
| `pdt gcloud ...` | run the Google Cloud CLI that pdt installs |
|
|
133
138
|
| `pdt completion [SHELL]` | turn on tab completion for a shell |
|
|
@@ -140,7 +145,7 @@ Leave `APP` off `run`, `deploy`, `destroy`, `secrets`, or `login`, or mistype it
|
|
|
140
145
|
|
|
141
146
|
Your app runs, writes some files, and stops. Then the computer it ran on is thrown away, and the files go with it. So pdt gives every app a folder in your cloud account that stays. Deploy the app, destroy it, deploy it again: the folder and everything in it is still there.
|
|
142
147
|
|
|
143
|
-
pdt creates one bucket per cloud account, named `pdt-data-` plus a short code, and gives each app its own folder inside it. When your app runs in the cloud, `PDT_STORAGE_URL` points at that folder. When you run `pdt run APP` on your own computer, it points at `.pdt/storage/APP/` inside your project instead. The app code is the same in
|
|
148
|
+
pdt creates one bucket per cloud account, named `pdt-data-` plus a short code, and gives each app its own folder inside it. When your app runs in the cloud, `PDT_STORAGE_URL` points at that folder. A Windows scheduled task points it at `%ProgramData%\pdt\APP\storage\`. When you run `pdt run APP` on your own computer, it points at `.pdt/storage/APP/` inside your project instead. The app code is the same in every place.
|
|
144
149
|
|
|
145
150
|
Write and read files with `pdt.utils.storage`:
|
|
146
151
|
|
|
@@ -183,7 +188,7 @@ Every command reads its values from the environment it runs in. A build server s
|
|
|
183
188
|
|
|
184
189
|
## Choosing where jobs run
|
|
185
190
|
|
|
186
|
-
Set `platform:` in `pdt.yml` for every app, or in an app's own `config.yml` for one app. An app's own file wins.
|
|
191
|
+
Set `platform:` in `pdt.yml` for every app, or in an app's own `config.yml` for one app. An app's own file wins. `timezone` may live under `platform:` as the default for every app, and an app's own `timezone` overrides it.
|
|
187
192
|
|
|
188
193
|
### Azure
|
|
189
194
|
|
|
@@ -228,7 +233,7 @@ platform:
|
|
|
228
233
|
|
|
229
234
|
### Bringing your own Dockerfile
|
|
230
235
|
|
|
231
|
-
Every cloud provider runs a job as a container: AWS on Fargate, Azure on Container Apps Jobs, Google Cloud on Cloud Run Jobs. The deploy builds an image for each app from a generated Dockerfile. It copies the app folder and `pdt.yml` into `/workspace`, installs the script-header dependencies of `run.py` with `uv sync --script`, and runs `run.py` as the entrypoint.
|
|
236
|
+
Every cloud provider runs a job as a container: AWS on Batch (Fargate), Azure on Container Apps Jobs, Google Cloud on Cloud Run Jobs. The deploy builds an image for each app from a generated Dockerfile. It copies the app folder and `pdt.yml` into `/workspace`, installs the script-header dependencies of `run.py` with `uv sync --script`, and runs `run.py` as the entrypoint.
|
|
232
237
|
|
|
233
238
|
Put a `Dockerfile` in the app folder to build the image your own way, for example to add system packages or to install a heavy tool at build time instead of on every run. The build context is the same as the generated one: the app folder under its own name, next to `pdt.yml`. Start from the generated file:
|
|
234
239
|
|
|
@@ -236,7 +241,7 @@ Put a `Dockerfile` in the app folder to build the image your own way, for exampl
|
|
|
236
241
|
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
|
|
237
242
|
COPY . /workspace
|
|
238
243
|
WORKDIR /workspace/my-app
|
|
239
|
-
ENV PDT_PROJECT=/workspace
|
|
244
|
+
ENV PDT_PROJECT=/workspace NO_COLOR=1 DBT_USE_COLORS=false
|
|
240
245
|
RUN uv sync --script run.py
|
|
241
246
|
ENTRYPOINT ["uv", "run", "--script", "run.py"]
|
|
242
247
|
```
|
|
@@ -253,10 +258,11 @@ platform:
|
|
|
253
258
|
|
|
254
259
|
apps:
|
|
255
260
|
- name: my-report
|
|
256
|
-
timezone: local
|
|
257
261
|
schedule: daily
|
|
258
262
|
```
|
|
259
263
|
|
|
264
|
+
The Windows provider runs each job on the PC's local time, so `timezone` defaults to `local` there, the only value it accepts.
|
|
265
|
+
|
|
260
266
|
The job runs as the SYSTEM account. Windows accepts these schedules:
|
|
261
267
|
|
|
262
268
|
- `hourly`, `daily`, `weekly`, `monthly`, and `yearly`
|
|
@@ -267,6 +273,8 @@ The job runs as the SYSTEM account. Windows accepts these schedules:
|
|
|
267
273
|
|
|
268
274
|
Other cron forms are rejected, because they do not translate to Windows Task Scheduler.
|
|
269
275
|
|
|
276
|
+
Each app keeps its run data in `%ProgramData%\pdt\<app>\`. The task writes one log file per run to the `logs\` folder in it, named by the run's UTC start time, and `pdt runs <app>` and `pdt logs <app>` read those files. The app's own files (see [Keeping files between runs](#keeping-files-between-runs)) live in the `storage\` folder next to it. Deploy creates the folder and gives the SYSTEM account full control and you modify rights, so `pdt storage <app> destroy` works without an administrator. `pdt destroy <app>` removes the task and the `logs\` folder and keeps `storage\`. A run is stopped after 30 minutes, the same limit as the cloud providers, and a run that is still going when the next one is due is left alone; the new start is skipped.
|
|
277
|
+
|
|
270
278
|
## The bundled examples
|
|
271
279
|
|
|
272
280
|
Run `pdt examples` to list them, then `pdt new <name> --from <example>` to copy one.
|
|
@@ -290,9 +298,11 @@ Reports active Monday users whose email address does not match the `userPrincipa
|
|
|
290
298
|
| Item | Where |
|
|
291
299
|
| --- | --- |
|
|
292
300
|
| the `pdt` command and its code | the tool's own environment, from `uv tool install` |
|
|
301
|
+
| the `pdt` launcher from winget | `%LOCALAPPDATA%\Microsoft\WinGet\Packages`, linked from `%LOCALAPPDATA%\Microsoft\WinGet\Links` |
|
|
293
302
|
| the example apps | inside the pdt package, copied out by `pdt new` |
|
|
294
303
|
| your apps, `pdt.yml`, `.env` | your project folder, under version control |
|
|
295
304
|
| the Google Cloud CLI pdt downloads | `~/.local/share/pdt/gcloud`, or `%LOCALAPPDATA%\pdt\gcloud` |
|
|
305
|
+
| a Windows scheduled task's run logs and files | `%ProgramData%\pdt\<app>\logs` and `%ProgramData%\pdt\<app>\storage` |
|
|
296
306
|
| cloud sign-in state | `~/.azure` and `~/.config/gcloud`, as usual |
|
|
297
307
|
|
|
298
308
|
Set `PDT_PROJECT` to name the project folder directly, instead of letting pdt search upward. Deployed jobs get it set for them.
|
|
@@ -304,7 +314,7 @@ An app is a folder with a `run.py` that has a `main()` function. It declares its
|
|
|
304
314
|
```python
|
|
305
315
|
# /// script
|
|
306
316
|
# requires-python = ">=3.12"
|
|
307
|
-
# dependencies = ["pdt-cli[apps]==0.1.
|
|
317
|
+
# dependencies = ["pdt-cli[apps]==0.1.3"]
|
|
308
318
|
# ///
|
|
309
319
|
from pathlib import Path
|
|
310
320
|
|
|
@@ -10,7 +10,7 @@ uv tool install pdt-cli
|
|
|
10
10
|
|
|
11
11
|
That puts a `pdt` command on your PATH. To update it later, run `uv tool upgrade pdt-cli`.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
On Windows you can run `winget install AutoIDM.pdt` instead, which also installs `uv` if you do not have it and puts the same `pdt` command on your PATH. `winget upgrade AutoIDM.pdt` moves pdt to the latest release, and the first `pdt` command after an install or upgrade finishes setting up that version.
|
|
14
14
|
|
|
15
15
|
You can also clone this repository and run `./pdt` (or `.\pdt.bat` on Windows) instead. It installs `uv` for you if you do not have it. Both ways give you the same commands.
|
|
16
16
|
|
|
@@ -90,6 +90,7 @@ pdt login my-report
|
|
|
90
90
|
| `pdt validate` | check the config files and the required env vars |
|
|
91
91
|
| `pdt run APP` | run an app on this machine |
|
|
92
92
|
| `pdt deploy APP` | deploy an app to its configured platform |
|
|
93
|
+
| `pdt deploy --all` | deploy every enabled app, in order; asks whether to skip and disable an app that fails; add `--yes --skip-failures` to run unattended |
|
|
93
94
|
| `pdt destroy APP` | remove everything deploy created |
|
|
94
95
|
| `pdt secrets APP` | show which `.env` values differ from the deployed app |
|
|
95
96
|
| `pdt secrets APP save` | send your `.env` values to the deployed app; the next run uses them |
|
|
@@ -97,6 +98,9 @@ pdt login my-report
|
|
|
97
98
|
| `pdt secrets APP set NAME` | put one value, read from stdin, into the deployed app's secrets |
|
|
98
99
|
| `pdt login APP` | sign in again to the app's cloud provider |
|
|
99
100
|
| `pdt storage APP ls|get|query|destroy` | look at, fetch, query, or delete the app's stored files |
|
|
101
|
+
| `pdt runs APP` | list the deployed app's runs with each run's exit code: the 10 newest, or with `--since 3d` (or `12h`, `2w`, `2026-09-20`, `2026-09-20T14:00`) every run since then, `--span 1d` keeping only the runs within that long after `--since` and `--count N` keeping only the N newest; a run's number is its place among every run pdt can still find, so it stays the same whichever runs print |
|
|
102
|
+
| `pdt logs APP [N]` | read the log of run N as `pdt runs` numbers it; with no N, the newest run, and `--failed` picks the newest failed run instead; `--since`, `--span`, and `--count` limit which runs those two choose from, `--errors` leaves out DEBUG and INFO lines; the last 20 lines print, `--lines N` prints N instead, `--head` prints the first lines instead of the last, and `--full` prints every line |
|
|
103
|
+
| `pdt health [APP]` | show whether each app's last run succeeded; exits 1 when one failed |
|
|
100
104
|
| `pdt az ...` | run the Azure CLI that pdt installs |
|
|
101
105
|
| `pdt gcloud ...` | run the Google Cloud CLI that pdt installs |
|
|
102
106
|
| `pdt completion [SHELL]` | turn on tab completion for a shell |
|
|
@@ -109,7 +113,7 @@ Leave `APP` off `run`, `deploy`, `destroy`, `secrets`, or `login`, or mistype it
|
|
|
109
113
|
|
|
110
114
|
Your app runs, writes some files, and stops. Then the computer it ran on is thrown away, and the files go with it. So pdt gives every app a folder in your cloud account that stays. Deploy the app, destroy it, deploy it again: the folder and everything in it is still there.
|
|
111
115
|
|
|
112
|
-
pdt creates one bucket per cloud account, named `pdt-data-` plus a short code, and gives each app its own folder inside it. When your app runs in the cloud, `PDT_STORAGE_URL` points at that folder. When you run `pdt run APP` on your own computer, it points at `.pdt/storage/APP/` inside your project instead. The app code is the same in
|
|
116
|
+
pdt creates one bucket per cloud account, named `pdt-data-` plus a short code, and gives each app its own folder inside it. When your app runs in the cloud, `PDT_STORAGE_URL` points at that folder. A Windows scheduled task points it at `%ProgramData%\pdt\APP\storage\`. When you run `pdt run APP` on your own computer, it points at `.pdt/storage/APP/` inside your project instead. The app code is the same in every place.
|
|
113
117
|
|
|
114
118
|
Write and read files with `pdt.utils.storage`:
|
|
115
119
|
|
|
@@ -152,7 +156,7 @@ Every command reads its values from the environment it runs in. A build server s
|
|
|
152
156
|
|
|
153
157
|
## Choosing where jobs run
|
|
154
158
|
|
|
155
|
-
Set `platform:` in `pdt.yml` for every app, or in an app's own `config.yml` for one app. An app's own file wins.
|
|
159
|
+
Set `platform:` in `pdt.yml` for every app, or in an app's own `config.yml` for one app. An app's own file wins. `timezone` may live under `platform:` as the default for every app, and an app's own `timezone` overrides it.
|
|
156
160
|
|
|
157
161
|
### Azure
|
|
158
162
|
|
|
@@ -197,7 +201,7 @@ platform:
|
|
|
197
201
|
|
|
198
202
|
### Bringing your own Dockerfile
|
|
199
203
|
|
|
200
|
-
Every cloud provider runs a job as a container: AWS on Fargate, Azure on Container Apps Jobs, Google Cloud on Cloud Run Jobs. The deploy builds an image for each app from a generated Dockerfile. It copies the app folder and `pdt.yml` into `/workspace`, installs the script-header dependencies of `run.py` with `uv sync --script`, and runs `run.py` as the entrypoint.
|
|
204
|
+
Every cloud provider runs a job as a container: AWS on Batch (Fargate), Azure on Container Apps Jobs, Google Cloud on Cloud Run Jobs. The deploy builds an image for each app from a generated Dockerfile. It copies the app folder and `pdt.yml` into `/workspace`, installs the script-header dependencies of `run.py` with `uv sync --script`, and runs `run.py` as the entrypoint.
|
|
201
205
|
|
|
202
206
|
Put a `Dockerfile` in the app folder to build the image your own way, for example to add system packages or to install a heavy tool at build time instead of on every run. The build context is the same as the generated one: the app folder under its own name, next to `pdt.yml`. Start from the generated file:
|
|
203
207
|
|
|
@@ -205,7 +209,7 @@ Put a `Dockerfile` in the app folder to build the image your own way, for exampl
|
|
|
205
209
|
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
|
|
206
210
|
COPY . /workspace
|
|
207
211
|
WORKDIR /workspace/my-app
|
|
208
|
-
ENV PDT_PROJECT=/workspace
|
|
212
|
+
ENV PDT_PROJECT=/workspace NO_COLOR=1 DBT_USE_COLORS=false
|
|
209
213
|
RUN uv sync --script run.py
|
|
210
214
|
ENTRYPOINT ["uv", "run", "--script", "run.py"]
|
|
211
215
|
```
|
|
@@ -222,10 +226,11 @@ platform:
|
|
|
222
226
|
|
|
223
227
|
apps:
|
|
224
228
|
- name: my-report
|
|
225
|
-
timezone: local
|
|
226
229
|
schedule: daily
|
|
227
230
|
```
|
|
228
231
|
|
|
232
|
+
The Windows provider runs each job on the PC's local time, so `timezone` defaults to `local` there, the only value it accepts.
|
|
233
|
+
|
|
229
234
|
The job runs as the SYSTEM account. Windows accepts these schedules:
|
|
230
235
|
|
|
231
236
|
- `hourly`, `daily`, `weekly`, `monthly`, and `yearly`
|
|
@@ -236,6 +241,8 @@ The job runs as the SYSTEM account. Windows accepts these schedules:
|
|
|
236
241
|
|
|
237
242
|
Other cron forms are rejected, because they do not translate to Windows Task Scheduler.
|
|
238
243
|
|
|
244
|
+
Each app keeps its run data in `%ProgramData%\pdt\<app>\`. The task writes one log file per run to the `logs\` folder in it, named by the run's UTC start time, and `pdt runs <app>` and `pdt logs <app>` read those files. The app's own files (see [Keeping files between runs](#keeping-files-between-runs)) live in the `storage\` folder next to it. Deploy creates the folder and gives the SYSTEM account full control and you modify rights, so `pdt storage <app> destroy` works without an administrator. `pdt destroy <app>` removes the task and the `logs\` folder and keeps `storage\`. A run is stopped after 30 minutes, the same limit as the cloud providers, and a run that is still going when the next one is due is left alone; the new start is skipped.
|
|
245
|
+
|
|
239
246
|
## The bundled examples
|
|
240
247
|
|
|
241
248
|
Run `pdt examples` to list them, then `pdt new <name> --from <example>` to copy one.
|
|
@@ -259,9 +266,11 @@ Reports active Monday users whose email address does not match the `userPrincipa
|
|
|
259
266
|
| Item | Where |
|
|
260
267
|
| --- | --- |
|
|
261
268
|
| the `pdt` command and its code | the tool's own environment, from `uv tool install` |
|
|
269
|
+
| the `pdt` launcher from winget | `%LOCALAPPDATA%\Microsoft\WinGet\Packages`, linked from `%LOCALAPPDATA%\Microsoft\WinGet\Links` |
|
|
262
270
|
| the example apps | inside the pdt package, copied out by `pdt new` |
|
|
263
271
|
| your apps, `pdt.yml`, `.env` | your project folder, under version control |
|
|
264
272
|
| the Google Cloud CLI pdt downloads | `~/.local/share/pdt/gcloud`, or `%LOCALAPPDATA%\pdt\gcloud` |
|
|
273
|
+
| a Windows scheduled task's run logs and files | `%ProgramData%\pdt\<app>\logs` and `%ProgramData%\pdt\<app>\storage` |
|
|
265
274
|
| cloud sign-in state | `~/.azure` and `~/.config/gcloud`, as usual |
|
|
266
275
|
|
|
267
276
|
Set `PDT_PROJECT` to name the project folder directly, instead of letting pdt search upward. Deployed jobs get it set for them.
|
|
@@ -273,7 +282,7 @@ An app is a folder with a `run.py` that has a `main()` function. It declares its
|
|
|
273
282
|
```python
|
|
274
283
|
# /// script
|
|
275
284
|
# requires-python = ">=3.12"
|
|
276
|
-
# dependencies = ["pdt-cli[apps]==0.1.
|
|
285
|
+
# dependencies = ["pdt-cli[apps]==0.1.3"]
|
|
277
286
|
# ///
|
|
278
287
|
from pathlib import Path
|
|
279
288
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pdt-cli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3"
|
|
4
4
|
description = "Easily deploy your process to AWS, Azure, Google Cloud, or Windows."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -25,7 +25,7 @@ apps = [
|
|
|
25
25
|
"boto3",
|
|
26
26
|
"google-cloud-secret-manager",
|
|
27
27
|
"fsspec",
|
|
28
|
-
"s3fs",
|
|
28
|
+
"s3fs>=2024",
|
|
29
29
|
"gcsfs",
|
|
30
30
|
"adlfs",
|
|
31
31
|
"google-cloud-storage",
|
|
@@ -58,7 +58,7 @@ include = ["src/**/*.py"]
|
|
|
58
58
|
extend-exclude = ["src/pdt/examples"]
|
|
59
59
|
|
|
60
60
|
[tool.ruff.lint]
|
|
61
|
-
select = ["T20"]
|
|
61
|
+
select = ["T20", "F821"]
|
|
62
62
|
|
|
63
63
|
[tool.ruff.lint.per-file-ignores]
|
|
64
64
|
"src/pdt/utils/log.py" = ["T20"]
|
|
@@ -18,6 +18,7 @@ import rich_argparse
|
|
|
18
18
|
|
|
19
19
|
from pdt import __version__, completion, config, console, deploy, deploy_common, scaffold
|
|
20
20
|
from pdt.config import ConfigError
|
|
21
|
+
from pdt.utils.email_auth import can_prompt
|
|
21
22
|
from pdt.utils.send_email import auth_env_file, email_problems, prepare_email_auth
|
|
22
23
|
|
|
23
24
|
CLOUD_CLIS = {
|
|
@@ -52,8 +53,17 @@ APP_QUESTIONS = {
|
|
|
52
53
|
"login": "Which app's cloud provider do you want to sign in to?",
|
|
53
54
|
"destroy": "Which app do you want to destroy?",
|
|
54
55
|
"secrets": "Which app's secrets?",
|
|
56
|
+
"storage": "Which app's files do you want to manage?",
|
|
57
|
+
"runs": "Which app's runs do you want to see?",
|
|
58
|
+
"logs": "Which app's log do you want to read?",
|
|
59
|
+
"health": "Which app do you want to check?",
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
SINCE_HELP = ("show every run that started at or after this: 12h, 3d, 2w, 2026-09-20, "
|
|
63
|
+
"or 2026-09-20T14:00 (default: the 10 newest runs)")
|
|
64
|
+
SPAN_HELP = "with --since, show only the runs that started within this long after it: 12h, 3d, 2w"
|
|
65
|
+
COUNT_HELP = "show at most this many runs (default: 10 without --since, else every run)"
|
|
66
|
+
|
|
57
67
|
|
|
58
68
|
def choose_app(name: str | None, command: str) -> str | None:
|
|
59
69
|
"""Return the app `pdt <command>` should act on, or None after guiding the user.
|
|
@@ -156,12 +166,51 @@ def cmd_run(args) -> int:
|
|
|
156
166
|
|
|
157
167
|
|
|
158
168
|
def cmd_deploy(args) -> int:
|
|
169
|
+
if args.skip_failures and not args.all:
|
|
170
|
+
console.error("--skip-failures only works with --all")
|
|
171
|
+
return 1
|
|
172
|
+
if args.all:
|
|
173
|
+
if args.app is not None:
|
|
174
|
+
console.error("pick an app or --all, not both")
|
|
175
|
+
return 1
|
|
176
|
+
return deploy_all(args.yes, args.skip_failures)
|
|
159
177
|
name = choose_app(args.app, "deploy")
|
|
160
178
|
if name is None:
|
|
161
179
|
return 1
|
|
162
180
|
return deploy.deploy(name, assume_yes=args.yes)
|
|
163
181
|
|
|
164
182
|
|
|
183
|
+
def deploy_all(assume_yes: bool, skip_failures: bool) -> int:
|
|
184
|
+
names = config.find_apps()
|
|
185
|
+
if not names:
|
|
186
|
+
say_no_apps()
|
|
187
|
+
return 1
|
|
188
|
+
ask = not skip_failures and can_prompt(None)
|
|
189
|
+
failed: list[str] = []
|
|
190
|
+
for index, name in enumerate(names, 1):
|
|
191
|
+
console.heading(f"Deploying {name} ({index} of {len(names)})")
|
|
192
|
+
code = deploy.deploy(name, assume_yes=assume_yes)
|
|
193
|
+
if code == 0:
|
|
194
|
+
continue
|
|
195
|
+
failed.append(name)
|
|
196
|
+
console.error(f"{name} did not deploy.")
|
|
197
|
+
if index < len(names) and not skip_failures and not (
|
|
198
|
+
ask and console.confirm(
|
|
199
|
+
f"Skip the failing app {name} and deploy the rest?")):
|
|
200
|
+
console.say("Fix the problem above and run pdt deploy --all again, "
|
|
201
|
+
"or add --skip-failures to go on past it.")
|
|
202
|
+
return code
|
|
203
|
+
if ask and not assume_yes and console.confirm(
|
|
204
|
+
f"Disable the failing app {name}?"):
|
|
205
|
+
path = config.set_app_enabled(name, False)
|
|
206
|
+
console.done(f"Disabled {name} in {path.relative_to(config.find_project())}. "
|
|
207
|
+
"Set enabled: true there to bring it back.")
|
|
208
|
+
if failed:
|
|
209
|
+
console.warn(f"Not deployed: {', '.join(failed)}")
|
|
210
|
+
return 1
|
|
211
|
+
return 0
|
|
212
|
+
|
|
213
|
+
|
|
165
214
|
def cmd_login(args) -> int:
|
|
166
215
|
name = choose_app(args.app, "login")
|
|
167
216
|
if name is None:
|
|
@@ -184,7 +233,48 @@ def cmd_secrets(args) -> int:
|
|
|
184
233
|
|
|
185
234
|
|
|
186
235
|
def cmd_storage(args) -> int:
|
|
187
|
-
|
|
236
|
+
name = choose_app(args.app, "storage")
|
|
237
|
+
if name is None:
|
|
238
|
+
return 1
|
|
239
|
+
return deploy.storage(name, args.rest)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def window_options(args) -> list[str]:
|
|
243
|
+
return [part for option, value in (("--since", args.since), ("--span", args.span),
|
|
244
|
+
("--count", args.count)) if value is not None
|
|
245
|
+
for part in (option, str(value))]
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def cmd_runs(args) -> int:
|
|
249
|
+
name = choose_app(args.app, "runs")
|
|
250
|
+
if name is None:
|
|
251
|
+
return 1
|
|
252
|
+
return deploy.runs(name, [*window_options(args), *(["--json"] if args.json else [])])
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def cmd_logs(args) -> int:
|
|
256
|
+
name = choose_app(args.app, "logs")
|
|
257
|
+
if name is None:
|
|
258
|
+
return 1
|
|
259
|
+
flags = [flag for flag, on in (("--failed", args.failed), ("--errors", args.errors),
|
|
260
|
+
("--head", args.head), ("--full", args.full),
|
|
261
|
+
("--json", args.json)) if on]
|
|
262
|
+
lines = [] if args.lines is None else ["--lines", str(args.lines)]
|
|
263
|
+
return deploy.logs(name, [str(args.number), *window_options(args), *lines, *flags])
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def cmd_health(args) -> int:
|
|
267
|
+
if args.app is None:
|
|
268
|
+
names = config.find_apps()
|
|
269
|
+
if not names:
|
|
270
|
+
say_no_apps()
|
|
271
|
+
return 0
|
|
272
|
+
else:
|
|
273
|
+
name = choose_app(args.app, "health")
|
|
274
|
+
if name is None:
|
|
275
|
+
return 1
|
|
276
|
+
names = [name]
|
|
277
|
+
return deploy.health(names, args.json)
|
|
188
278
|
|
|
189
279
|
|
|
190
280
|
def cmd_completion(args) -> int:
|
|
@@ -232,6 +322,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
232
322
|
help="the app's folder name; omit to see the choices")
|
|
233
323
|
app.completer = completion.apps
|
|
234
324
|
p.add_argument("--yes", action="store_true", help="skip the confirmation prompt")
|
|
325
|
+
p.add_argument("--all", action="store_true", help="deploy every enabled app, in order")
|
|
326
|
+
p.add_argument("--skip-failures", action="store_true",
|
|
327
|
+
help="with --all, go on past an app that fails to deploy instead of asking")
|
|
235
328
|
p.set_defaults(func=cmd_deploy)
|
|
236
329
|
p = add_parser("login", help="sign in again to an app's cloud provider")
|
|
237
330
|
app = p.add_argument("app", nargs="?",
|
|
@@ -257,10 +350,44 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
257
350
|
p.add_argument("--yes", action="store_true", help="skip the confirmation prompt")
|
|
258
351
|
p.set_defaults(func=cmd_secrets)
|
|
259
352
|
p = add_parser("storage", help="read or manage an app's data store")
|
|
260
|
-
app = p.add_argument("app",
|
|
353
|
+
app = p.add_argument("app", nargs="?",
|
|
354
|
+
help="the app's folder name; omit to see the choices")
|
|
261
355
|
app.completer = completion.apps
|
|
262
356
|
p.add_argument("rest", nargs=argparse.REMAINDER, help="ls|get|query|destroy [args...]")
|
|
263
357
|
p.set_defaults(func=cmd_storage)
|
|
358
|
+
p = add_parser("runs", help="list a deployed app's recent runs")
|
|
359
|
+
app = p.add_argument("app", nargs="?",
|
|
360
|
+
help="the app's folder name; omit to see the choices")
|
|
361
|
+
app.completer = completion.apps
|
|
362
|
+
p.add_argument("--since", help=SINCE_HELP)
|
|
363
|
+
p.add_argument("--span", help=SPAN_HELP)
|
|
364
|
+
p.add_argument("--count", type=int, help=COUNT_HELP)
|
|
365
|
+
p.add_argument("--json", action="store_true", help="print JSON for a script or an agent")
|
|
366
|
+
p.set_defaults(func=cmd_runs)
|
|
367
|
+
p = add_parser("logs", help="read the log of one of a deployed app's runs")
|
|
368
|
+
app = p.add_argument("app", nargs="?",
|
|
369
|
+
help="the app's folder name; omit to see the choices")
|
|
370
|
+
app.completer = completion.apps
|
|
371
|
+
p.add_argument("number", nargs="?", type=int, default=1,
|
|
372
|
+
help="which run, as `pdt runs` numbers them (default: 1, the newest)")
|
|
373
|
+
p.add_argument("--since", help=SINCE_HELP)
|
|
374
|
+
p.add_argument("--span", help=SPAN_HELP)
|
|
375
|
+
p.add_argument("--count", type=int, help=COUNT_HELP)
|
|
376
|
+
p.add_argument("--failed", action="store_true", help="read the newest failed run")
|
|
377
|
+
p.add_argument("--errors", action="store_true",
|
|
378
|
+
help="leave out DEBUG and INFO lines")
|
|
379
|
+
p.add_argument("--lines", type=int, help="print this many lines (default: 20)")
|
|
380
|
+
p.add_argument("--head", action="store_true",
|
|
381
|
+
help="print the first lines, not the last")
|
|
382
|
+
p.add_argument("--full", action="store_true",
|
|
383
|
+
help="print every line, not only the last 20")
|
|
384
|
+
p.add_argument("--json", action="store_true", help="print JSON for a script or an agent")
|
|
385
|
+
p.set_defaults(func=cmd_logs)
|
|
386
|
+
p = add_parser("health", help="show whether each deployed app's last run succeeded")
|
|
387
|
+
app = p.add_argument("app", nargs="?", help="one app's folder name; omit to check every app")
|
|
388
|
+
app.completer = completion.apps
|
|
389
|
+
p.add_argument("--json", action="store_true", help="print JSON for a script or an agent")
|
|
390
|
+
p.set_defaults(func=cmd_health)
|
|
264
391
|
p = add_parser("completion", help="turn on tab completion in your shell")
|
|
265
392
|
p.add_argument("shell", nargs="?", choices=completion.SHELLS,
|
|
266
393
|
help="which shell; pdt works it out when left off")
|
|
@@ -277,10 +404,12 @@ def main() -> int:
|
|
|
277
404
|
parser = build_parser()
|
|
278
405
|
completion.configure(parser)
|
|
279
406
|
if len(sys.argv) > 1 and sys.argv[1] in CLOUD_CLIS:
|
|
280
|
-
# Before argparse, so the cloud CLI parses its own flags.
|
|
407
|
+
# Before argparse, so the cloud CLI parses its own flags. --project keeps
|
|
408
|
+
# `uvx pdt aws` working; deploy.provider_command explains why.
|
|
281
409
|
script = Path(__file__).with_name(CLOUD_CLIS[sys.argv[1]])
|
|
282
410
|
return subprocess.run(
|
|
283
|
-
["uv", "run", "--script", str(script),
|
|
411
|
+
["uv", "run", "--project", str(Path.cwd()), "--script", str(script),
|
|
412
|
+
*sys.argv[1:]]).returncode
|
|
284
413
|
args = parser.parse_args()
|
|
285
414
|
if args.command is None:
|
|
286
415
|
parser.print_help()
|
|
@@ -132,7 +132,7 @@ def setup(shell: str | None = None) -> None:
|
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
def configure(parser) -> None:
|
|
135
|
-
argcomplete.autocomplete(parser)
|
|
135
|
+
argcomplete.autocomplete(parser, always_complete_options=False)
|
|
136
136
|
if not (sys.stdin.isatty() and sys.stderr.isatty()):
|
|
137
137
|
return
|
|
138
138
|
try:
|