pdt-cli 0.1.4__tar.gz → 0.1.7__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.4 → pdt_cli-0.1.7}/.gitignore +1 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/PKG-INFO +90 -26
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/README.md +89 -25
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/ci/tests/test_score_task.py +2 -9
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/pyproject.toml +2 -2
- pdt_cli-0.1.7/src/pdt/__init__.py +9 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/cli.py +246 -58
- pdt_cli-0.1.7/src/pdt/completion.py +255 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/config.py +253 -19
- pdt_cli-0.1.7/src/pdt/console.py +334 -0
- pdt_cli-0.1.7/src/pdt/deploy.py +357 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/deploy_aws.py +85 -41
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/deploy_aws_batch.py +253 -106
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/deploy_azure.py +166 -96
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/deploy_azure_container_apps.py +379 -156
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/deploy_common.py +161 -39
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/deploy_google_cloud.py +245 -124
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/deploy_windows.py +95 -37
- pdt_cli-0.1.7/src/pdt/docker_setup.py +598 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/download.py +15 -10
- pdt_cli-0.1.7/src/pdt/duckdb_wasm.py +100 -0
- pdt_cli-0.1.7/src/pdt/examples/azure-license-waste/LicenseWasteAudit.ps1 +233 -0
- pdt_cli-0.1.7/src/pdt/examples/azure-license-waste/config.yml +21 -0
- pdt_cli-0.1.7/src/pdt/examples/azure-license-waste/env.template +14 -0
- pdt_cli-0.1.7/src/pdt/examples/azure-license-waste/license-prices.csv +33 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/examples/hello-world/run.py +5 -5
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/examples/impossible-travel-report/run.py +4 -4
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/examples/monday-orphaned-account-report/run.py +4 -4
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/gcloud_sdk.py +4 -4
- pdt_cli-0.1.7/src/pdt/gui/__init__.py +12 -0
- pdt_cli-0.1.7/src/pdt/gui/apps.py +6 -0
- pdt_cli-0.1.7/src/pdt/gui/health.py +580 -0
- pdt_cli-0.1.7/src/pdt/gui/migrations/0001_initial.py +81 -0
- pdt_cli-0.1.7/src/pdt/gui/migrations/0002_timing_and_artifacts_by_time.py +33 -0
- pdt_cli-0.1.7/src/pdt/gui/migrations/0003_app_history_synced_at.py +18 -0
- pdt_cli-0.1.7/src/pdt/gui/migrations/0004_reread_time_matched_files.py +22 -0
- pdt_cli-0.1.7/src/pdt/gui/models.py +90 -0
- pdt_cli-0.1.7/src/pdt/gui/pdt_cmd.py +55 -0
- pdt_cli-0.1.7/src/pdt/gui/settings.py +63 -0
- pdt_cli-0.1.7/src/pdt/gui/static/arrow.mjs +4 -0
- pdt_cli-0.1.7/src/pdt/gui/static/pdt.css +175 -0
- pdt_cli-0.1.7/src/pdt/gui/static/workbench.js +284 -0
- pdt_cli-0.1.7/src/pdt/gui/sync.py +230 -0
- pdt_cli-0.1.7/src/pdt/gui/templates/gui/_actions.html +24 -0
- pdt_cli-0.1.7/src/pdt/gui/templates/gui/_health.html +319 -0
- pdt_cli-0.1.7/src/pdt/gui/templates/gui/_health_grid.html +57 -0
- pdt_cli-0.1.7/src/pdt/gui/templates/gui/app.html +50 -0
- pdt_cli-0.1.7/src/pdt/gui/templates/gui/apps.html +39 -0
- pdt_cli-0.1.7/src/pdt/gui/templates/gui/base.html +40 -0
- pdt_cli-0.1.7/src/pdt/gui/templates/gui/explore.html +57 -0
- pdt_cli-0.1.7/src/pdt/gui/templates/gui/run.html +78 -0
- pdt_cli-0.1.7/src/pdt/gui/templates/gui/stats.html +79 -0
- pdt_cli-0.1.7/src/pdt/gui/timing.py +46 -0
- pdt_cli-0.1.7/src/pdt/gui/urls.py +17 -0
- pdt_cli-0.1.7/src/pdt/gui/views.py +293 -0
- pdt_cli-0.1.7/src/pdt/gui/worker.py +117 -0
- pdt_cli-0.1.7/src/pdt/gui_cli.py +162 -0
- pdt_cli-0.1.7/src/pdt/gui_server.py +68 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/powershell.py +196 -21
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/pwsh.py +2 -2
- pdt_cli-0.1.7/src/pdt/python_env.py +98 -0
- pdt_cli-0.1.7/src/pdt/regions.py +412 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/run_powershell.py +5 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/run_windows_task.py +11 -5
- pdt_cli-0.1.7/src/pdt/runs_cli.py +657 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/scaffold.py +51 -40
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/scan_powershell.ps1 +11 -3
- pdt_cli-0.1.7/src/pdt/storage_cli.py +133 -0
- pdt_cli-0.1.7/src/pdt/utils/__init__.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/utils/email_auth.py +7 -6
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/utils/send_email_graph.py +4 -4
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/utils/storage.py +202 -21
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/conftest.py +15 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_cli.py +12 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_cost_estimate.py +1 -1
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_deployer_policy.py +19 -2
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_destroy.py +21 -16
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_inventory.py +9 -0
- pdt_cli-0.1.7/tests/test_aws_pause.py +118 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_runs.py +13 -1
- pdt_cli-0.1.7/tests/test_aws_security_group.py +149 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_shared_deletes.py +67 -4
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_build.py +3 -18
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_environment.py +24 -17
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_job_diagnostics.py +2 -1
- pdt_cli-0.1.7/tests/test_azure_pause.py +63 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_retries.py +49 -12
- pdt_cli-0.1.7/tests/test_azure_runs.py +352 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_shared_locks.py +10 -8
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_storage.py +2 -1
- pdt_cli-0.1.7/tests/test_build_ssh.py +82 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_cli.py +281 -11
- pdt_cli-0.1.7/tests/test_command_mentions.py +165 -0
- pdt_cli-0.1.7/tests/test_completion.py +466 -0
- pdt_cli-0.1.7/tests/test_console.py +74 -0
- pdt_cli-0.1.7/tests/test_console_values.py +127 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_cost_estimate.py +1 -1
- pdt_cli-0.1.7/tests/test_currency.py +211 -0
- pdt_cli-0.1.7/tests/test_deploy_first_run.py +217 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_deploy_state.py +15 -1
- pdt_cli-0.1.7/tests/test_discovery_lines.py +220 -0
- pdt_cli-0.1.7/tests/test_docker_preflight.py +921 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_dockerfile.py +9 -4
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_download.py +2 -1
- pdt_cli-0.1.7/tests/test_duckdb_wasm.py +58 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_env_sources.py +38 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_gcloud_sdk.py +2 -1
- pdt_cli-0.1.7/tests/test_google_cloud_build.py +56 -0
- pdt_cli-0.1.7/tests/test_google_cloud_pause.py +64 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_google_cloud_runs.py +41 -2
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_google_cloud_storage.py +2 -1
- pdt_cli-0.1.7/tests/test_gui.py +525 -0
- pdt_cli-0.1.7/tests/test_gui_cli.py +104 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_init_questions.py +2 -2
- pdt_cli-0.1.7/tests/test_install_notice.py +35 -0
- pdt_cli-0.1.7/tests/test_log_delay.py +38 -0
- pdt_cli-0.1.7/tests/test_next_steps.py +122 -0
- pdt_cli-0.1.7/tests/test_pause_key.py +61 -0
- pdt_cli-0.1.7/tests/test_powershell_env.py +152 -0
- pdt_cli-0.1.7/tests/test_powershell_local_modules.py +156 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_powershell_rules.py +92 -12
- pdt_cli-0.1.7/tests/test_python_env.py +158 -0
- pdt_cli-0.1.7/tests/test_regions.py +175 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_run_log_links.py +3 -3
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_run_powershell.py +17 -1
- pdt_cli-0.1.7/tests/test_runs_cli.py +750 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_scaffold.py +18 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_schedule.py +24 -1
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_set_app_enabled.py +46 -1
- pdt_cli-0.1.7/tests/test_sign_in_status.py +25 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_starter_app.py +2 -2
- pdt_cli-0.1.7/tests/test_storage.py +348 -0
- pdt_cli-0.1.7/tests/test_storage_cli.py +98 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_windows_data.py +8 -7
- pdt_cli-0.1.7/tests/test_windows_pause.py +77 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_windows_runner.py +12 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_windows_runs.py +2 -2
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_winget_launcher.py +3 -1
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/verify/README.md +8 -4
- pdt_cli-0.1.4/src/pdt/__init__.py +0 -6
- pdt_cli-0.1.4/src/pdt/completion.py +0 -161
- pdt_cli-0.1.4/src/pdt/console.py +0 -195
- pdt_cli-0.1.4/src/pdt/deploy.py +0 -217
- pdt_cli-0.1.4/src/pdt/runs_cli.py +0 -349
- pdt_cli-0.1.4/src/pdt/storage_cli.py +0 -94
- pdt_cli-0.1.4/tests/test_azure_runs.py +0 -117
- pdt_cli-0.1.4/tests/test_completion.py +0 -219
- pdt_cli-0.1.4/tests/test_console.py +0 -35
- pdt_cli-0.1.4/tests/test_runs_cli.py +0 -387
- pdt_cli-0.1.4/tests/test_storage.py +0 -149
- pdt_cli-0.1.4/tests/test_storage_cli.py +0 -40
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/LICENSE +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/ci/tests/test_claude_task.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/ci/tests/test_close_stale_drafts.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/examples/hello-world/config.yml +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/examples/impossible-travel-report/config.yml +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/examples/impossible-travel-report/env.template +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/examples/monday-orphaned-account-report/config.yml +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/examples/monday-orphaned-account-report/env.template +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/examples/powershell-report/config.yml +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/examples/powershell-report/report.ps1 +0 -0
- {pdt_cli-0.1.4/src/pdt/utils → pdt_cli-0.1.7/src/pdt/gui/migrations}/__init__.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/utils/entra.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/utils/env_secret.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/utils/log.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/utils/send_email.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/src/pdt/utils/web.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_app_name.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_account.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_job_definition.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_names.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_not_found.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_profile.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_aws_storage.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_deleted_vault.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_environment_key.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_group_ownership.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_job_name.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_names.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_side_resources.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_azure_subscription_state.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_build_context.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_ci_azure.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_ci_google_cloud.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_config_merge.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_env_secret.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_fetch_json.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_google_cloud_inventory.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_google_cloud_not_found.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_google_cloud_retries.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_google_cloud_service_account.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_log_colour.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_powershell_app_config.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_private_file.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_project_discovery.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_pwsh.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_save_platform_key.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_secrets.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_strict_validation.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_verify.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_windows_storage.py +0 -0
- {pdt_cli-0.1.4 → pdt_cli-0.1.7}/tests/test_winget_manifests.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pdt-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: Easily deploy your process to AWS, Azure, Google Cloud, or Windows.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -38,17 +38,31 @@ Run scheduled jobs — reports, integrations, automations — and deploy them to
|
|
|
38
38
|
|
|
39
39
|
```
|
|
40
40
|
uv tool install pdt-cli
|
|
41
|
+
uv tool update-shell
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
`uv tool install` puts the `pdt` command in uv's tool folder. `uv tool update-shell` adds that folder to your PATH if it is not there yet. A terminal window that was already open does not see a PATH change, so open a new window before you type `pdt`. To update pdt later, run `uv tool upgrade pdt-cli`.
|
|
44
45
|
|
|
45
|
-
On Windows
|
|
46
|
+
On Windows, paste these lines into PowerShell. They install `uv` and pdt, and they work in the window you type them in:
|
|
47
|
+
|
|
48
|
+
```powershell
|
|
49
|
+
winget install --id astral-sh.uv -e
|
|
50
|
+
$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')
|
|
51
|
+
uv tool install pdt-cli
|
|
52
|
+
uv tool update-shell
|
|
53
|
+
$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')
|
|
54
|
+
pdt --version
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`winget` and `uv tool update-shell` change the PATH for new windows only. Each `$env:Path = ...` line reads the new PATH into the current window. Another PowerShell window that was open before the install does not find `uv` or `pdt` until you paste the same line into it or open a new window.
|
|
58
|
+
|
|
59
|
+
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. Open a new PowerShell window after it finishes. `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.
|
|
46
60
|
|
|
47
61
|
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.
|
|
48
62
|
|
|
49
63
|
## Set up a project
|
|
50
64
|
|
|
51
|
-
A project is a folder holding `pdt.yml`. Each app is a folder inside it that contains a `run.py`. Every command except `init` finds the project by looking in the current folder, then each folder above it.
|
|
65
|
+
A project is a folder holding `pdt.yml`. Each app is a folder inside it that contains a `run.py`, or the `.ps1` scripts of a PowerShell app. Every command except `init` finds the project by looking in the current folder, then each folder above it.
|
|
52
66
|
|
|
53
67
|
```
|
|
54
68
|
pdt init my-jobs
|
|
@@ -58,6 +72,8 @@ pdt init my-jobs
|
|
|
58
72
|
|
|
59
73
|
Region is the only setting it asks for. Your AWS account, Azure subscription, and Google Cloud project all come from your credentials the first time you deploy, and pdt writes the answer into `pdt.yml` so every later deploy checks against it.
|
|
60
74
|
|
|
75
|
+
pdt suggests the region nearest to you from your computer's time zone. If `pdt.yml` has no region when you first deploy, the deploy suggests one the same way, asks you to confirm it or type another, and saves your answer. With `--yes` it takes the suggestion and tells you which region it used.
|
|
76
|
+
|
|
61
77
|
Starting in an empty folder also gives you a working app called `hello-world`. Run it straight away:
|
|
62
78
|
|
|
63
79
|
```
|
|
@@ -99,7 +115,18 @@ my-jobs/
|
|
|
99
115
|
pdt deploy my-report
|
|
100
116
|
```
|
|
101
117
|
|
|
102
|
-
The command prints a resource plan and a monthly cost estimate before it changes anything. Add `--yes` to skip the question.
|
|
118
|
+
A deploy to AWS, Azure, or Google Cloud builds the app's image with Docker on your computer. The deploy starts Docker when it is installed but not running, and installs it after one question when it is missing (see [Choosing where jobs run](#choosing-where-jobs-run)). The command prints a resource plan and a monthly cost estimate before it changes anything. Add `--yes` to skip the question.
|
|
119
|
+
|
|
120
|
+
When the app has no successful run yet (it never ran, or every run failed), the deploy then asks `Start a run now and show its log? [y/N]`. A yes starts one run, as `pdt run my-report --deployed` does, and prints the run's log lines as they arrive, as `pdt logs my-report --follow` does. The deploy then exits with the run's result: 0 when the run succeeded, 1 when it failed. A run can have side effects, for example an email to real people, so the answer is no unless you type `y`. With `--yes`, or with no terminal (a build server), pdt does not ask and starts no run. Add `--run` to start the run and follow its log with no question. `pdt deploy --all` does not offer a run.
|
|
121
|
+
|
|
122
|
+
To start one run of the deployed job now, and then read its log:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
pdt run my-report --deployed
|
|
126
|
+
pdt logs my-report
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
To remove everything the deploy created:
|
|
103
130
|
|
|
104
131
|
```
|
|
105
132
|
pdt destroy my-report
|
|
@@ -118,26 +145,34 @@ pdt login my-report
|
|
|
118
145
|
| `pdt init [DIR]` | create a project here, or in DIR |
|
|
119
146
|
| `pdt examples` | list the example apps bundled with pdt |
|
|
120
147
|
| `pdt new APP --from EXAMPLE` | add an app to the project |
|
|
121
|
-
| `pdt list` | show every app with its schedule and
|
|
122
|
-
| `pdt validate` | check the config files and the required env vars |
|
|
148
|
+
| `pdt list` | show every app with its schedule and platform; `--names` prints only the enabled app names; `--json` prints one object per app with `name`, `schedule`, `platform`, `enabled`, `paused`, and `error` (a config problem, or `null`) |
|
|
149
|
+
| `pdt validate` | check the config files and the required env vars; exits 1 when it finds a problem; `--json` prints `{"ok": ..., "problems": [{"app": ..., "message": ...}]}`, where `app` is `null` for a problem of the project or of more than one app |
|
|
123
150
|
| `pdt run APP` | run an app on this machine |
|
|
124
|
-
| `pdt
|
|
151
|
+
| `pdt run APP --deployed` | start one run of the deployed job now, on its platform |
|
|
152
|
+
| `pdt deploy APP` | deploy an app to its configured platform; when the app has no successful run yet, asks whether to start one and follow its log |
|
|
153
|
+
| `pdt deploy APP --run` | deploy, then start the app's first run and follow its log with no question; starts no run when the app already has a successful run |
|
|
125
154
|
| `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 |
|
|
126
155
|
| `pdt destroy APP` | remove everything deploy created |
|
|
156
|
+
| `pdt pause APP` | stop the deployed schedule from starting runs; writes `pause: true` into the app's `config.yml` |
|
|
157
|
+
| `pdt unpause APP` | let the schedule start runs again; writes `pause: false` |
|
|
127
158
|
| `pdt secrets APP` | show which `.env` values differ from the deployed app |
|
|
128
159
|
| `pdt secrets APP save` | send your `.env` values to the deployed app; the next run uses them |
|
|
129
160
|
| `pdt secrets APP get` | copy the deployed values into a `.env.<provider>` file |
|
|
130
161
|
| `pdt secrets APP set NAME` | put one value, read from stdin, into the deployed app's secrets |
|
|
131
|
-
| `pdt login APP` | sign in again to the app's
|
|
132
|
-
| `pdt storage APP ls
|
|
162
|
+
| `pdt login APP` | sign in again to the app's platform |
|
|
163
|
+
| `pdt storage APP ls\|get\|query\|unlock\|destroy` | look at, fetch, query, unlock, or delete the app's stored files; `ls PATH --recursive` lists every file under a folder, and `ls --json` prints the list as JSON |
|
|
133
164
|
| `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,
|
|
165
|
+
| `pdt logs APP [N]` | read the log of run N as `pdt runs` numbers it; with no N, the newest run, `--failed` picks the newest failed run instead, and `--id ID` picks a run by the id `pdt runs` shows (repeat it to read several runs; their `--json` is then an object keyed by id); `--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, `--full` prints every line, and `--follow` keeps printing new lines, in time order, until the run ends and its log has the run's exit line; on Azure, a running run's lines come from its container's live log, because Azure Log Analytics shows a line a minute or more after the job writes it; a run that is still running, or whose lines have not reached the log store yet, says so |
|
|
135
166
|
| `pdt health [APP] [--all]` | show whether each app's last run succeeded; exits 1 when one failed; inside an app folder it checks only that app, and `--all` checks every app |
|
|
167
|
+
| `pdt gui` | open the project's dashboard in your browser: every app's health, each app's run history, and each run's log and files, with buttons for pause, unpause, and run now; `--background` keeps the server up after the command returns and `pdt gui --stop` ends it; `--port N` and `--no-browser` are there for the rare case |
|
|
168
|
+
| `pdt aws ...` | run the AWS CLI that pdt installs |
|
|
136
169
|
| `pdt az ...` | run the Azure CLI that pdt installs |
|
|
137
170
|
| `pdt gcloud ...` | run the Google Cloud CLI that pdt installs |
|
|
138
171
|
| `pdt completion [SHELL]` | turn on tab completion for a shell |
|
|
139
172
|
|
|
140
|
-
Inside an app folder, or any folder below it, leave `APP` off any command that takes one and pdt uses that app. For example, `pdt logs 3` in the `hello-world` folder reads run 3 of `hello-world`. Anywhere else, leave `APP` off or mistype it and pdt lists the apps in the project so you can pick one.
|
|
173
|
+
Inside an app folder, or any folder below it, leave `APP` off any command that takes one and pdt uses that app. For example, `pdt logs 3` in the `hello-world` folder reads run 3 of `hello-world`. Anywhere else, leave `APP` off or mistype it and pdt lists the apps in the project so you can pick one; a mistyped name also gets the closest app name. A mistyped command or option gets the closest one too, for example `pdt lgos` asks "Did you mean `pdt logs`?".
|
|
174
|
+
|
|
175
|
+
With `--json`, `pdt list`, `pdt validate`, `pdt runs`, `pdt logs`, `pdt health`, and `pdt storage APP ls` print only JSON on stdout, and every other line, such as an error or a sign-in question, on stderr. In a project with no enabled apps, `pdt runs`, `pdt logs`, `pdt health`, and `pdt storage APP ls` print `[]`. A command that has no JSON to print exits with a code other than 0 and prints nothing on stdout.
|
|
141
176
|
|
|
142
177
|
`pdt az` and `pdt gcloud` hand your arguments straight to the cloud tool, and install it first if it is missing. For example, `pdt az account list`.
|
|
143
178
|
|
|
@@ -159,23 +194,31 @@ with store.open("report.csv", "wb") as f:
|
|
|
159
194
|
|
|
160
195
|
`store.fs()` gives you the full [fsspec](https://filesystem-spec.readthedocs.io/) filesystem, rooted at your app's folder. Output from one run goes under `store.run_folder()`, which names a new `runs/<time>-<id>/` folder each run.
|
|
161
196
|
|
|
162
|
-
Some apps need files from the last run: a Meltano bookmark, a database. Keep those in
|
|
197
|
+
Some apps need files from the last run: a Meltano bookmark, a database. Keep those in the `state` folder that `storage.sync()` hands you, and put anything a person may want to look at afterwards in its `output` folder:
|
|
163
198
|
|
|
164
199
|
```python
|
|
165
|
-
from pathlib import Path
|
|
166
200
|
from pdt.utils import storage
|
|
167
201
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
store.push(Path(".pdt-state"), "state/", lease)
|
|
202
|
+
with storage.sync() as run:
|
|
203
|
+
# do the work; keep anything the next run needs inside run.state/
|
|
204
|
+
(run.output / "report.csv").write_text("name,count\n")
|
|
172
205
|
```
|
|
173
206
|
|
|
174
|
-
`
|
|
207
|
+
`sync` copies `state/` down from your app's folder and locks it, so a second copy of your app cannot run at the same time and mix up the files. When the block ends, it uploads `run.output` to this run's `runs/` folder, checks that nobody else changed the state files, saves them, and unlocks. When the block raises, or the run is interrupted, it still uploads `run.output`, unlocks, and leaves `state/` as it was, so the next run starts from the last good state. An app whose state is safe to keep at any point, such as a Meltano bookmark or an OAuth token that rotates on every login, passes `storage.sync(push_state_on_error=True)` so a failed run keeps what it saved. Both local folders live under `.pdt/runs/` in the app folder and stay there for inspection.
|
|
208
|
+
|
|
209
|
+
The same steps by hand are `store.pull("state/", folder)`, which returns a lease, then `store.push(folder, "state/", lease)` on success or `store.abort(lease)` on failure.
|
|
210
|
+
|
|
211
|
+
A run that dies without reaching either call leaves the lock behind. The next run takes it over right away when it can tell the holder is gone: the holder ran on the same computer and its process has ended, or it carries the same `PDT_RUN_ID`. Otherwise it waits 30 minutes, or you release the lock yourself with `pdt storage APP unlock`, which asks first.
|
|
175
212
|
|
|
176
213
|
From your own computer, `pdt storage APP ls`, `get`, and `query` read the files with your own cloud sign-in. `pdt storage APP destroy` is the only command that deletes them, and it asks first. An app that needs none of this sets `storage: false` in its `config.yml`.
|
|
177
214
|
|
|
178
|
-
An app that is not ready sets `enabled: false` in its `config.yml`. `pdt list` still shows it, and every other command acts as if the app is not there. `uv run run.py` in the app folder still runs it.
|
|
215
|
+
An app that is not ready sets `enabled: false` in its `config.yml`. `pdt list` still shows it, and every other command acts as if the app is not there. `uv run run.py` in the app folder still runs it. `enabled: false` does not stop a schedule that is already deployed, so run `pdt pause APP` or `pdt destroy APP` before you set it.
|
|
216
|
+
|
|
217
|
+
## The dashboard
|
|
218
|
+
|
|
219
|
+
`pdt gui` starts a small web server on your own computer and opens it in your browser. The first page is `pdt health` as a table, with a grid of every run by hour, day, week, or month underneath it. Click an app for its run history, and click a run for its log and the files it kept in storage. A CSV file a run kept opens in a SQL workbench in the browser: a table you can sort, filter, and page, and a SQL view where every CSV of the run is a view you can query and join, with the matching `pdt storage APP query` command shown under it. The workbench runs on DuckDB, which the first `pdt gui` downloads once into the pdt data folder (`~/.local/share/pdt`); a Download link next to each file still saves it. Every number on a page came out of a pdt command (`pdt runs`, `pdt logs`, `pdt storage ls`), and every button runs one (`pdt pause`, `pdt unpause`, `pdt run --deployed`); hover a button to see which. The pages keep what they fetched in `.pdt/gui.sqlite3` inside the project and fetch again when it is older than five minutes or when you press Refresh, so a run stays in the history after the cloud has forgotten it. A worker inside the server checks for work every two minutes: it refreshes each app's runs, asks the provider once for every run it still keeps, and then fetches the log and files of each run that lacks them, newest first, until every run has them. A page shows what is there; it only waits when it asks for something the worker has not reached yet. The Stats link shows how long each page and each pdt command took. The server listens on localhost only and has no login. `pdt gui --background` leaves it running after the command returns; `pdt gui --stop` ends it.
|
|
220
|
+
|
|
221
|
+
An app that should stay deployed but not run for a while sets `pause: true`. The schedule stays in place and does not fire until the key goes back to `false`. `pdt pause APP` and `pdt unpause APP` write the key into the app's `config.yml` and apply it to the deployed schedule in the same step; `pdt deploy` also honors it. A paused app still runs when you ask for it with `pdt run APP --deployed`.
|
|
179
222
|
|
|
180
223
|
## Running pdt from CI
|
|
181
224
|
|
|
@@ -184,12 +227,16 @@ Every command reads its values from the environment it runs in. A build server s
|
|
|
184
227
|
- A `.env` file is a convenience for a person working on their own machine. A value that is already in the environment wins over the same name in a `.env` file.
|
|
185
228
|
- `PDT_ENV_JSON` holds every value as one JSON object, for a CI system that keeps one secret instead of many. For example, `PDT_ENV_JSON={"PDT_TOKEN": "abc", "PDT_SMTP_USER": "reports@example.com"}`.
|
|
186
229
|
- pdt creates no `.env` file on a build server. It looks for the `CI` variable that build systems set, and for a missing terminal. Email authorization must therefore be done first: run `pdt run APP` once on a machine with a browser, then copy `PDT_SMTP_OAUTH_CACHE_B64` (or `PDT_GRAPH_MAIL_CACHE_B64` for Microsoft Graph) from your `.env` into the CI variables.
|
|
187
|
-
- `pdt deploy APP` and `pdt destroy APP` ask before they change anything. Add `--yes` so they proceed without asking.
|
|
230
|
+
- `pdt deploy APP` and `pdt destroy APP` ask before they change anything. Add `--yes` so they proceed without asking. On a build server, `pdt deploy APP` starts no run after the deploy; add `--run` to start the app's first run and follow its log.
|
|
188
231
|
|
|
189
232
|
## Choosing where jobs run
|
|
190
233
|
|
|
191
234
|
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.
|
|
192
235
|
|
|
236
|
+
The cost estimate before each deploy shows prices in the currency of your computer's regional setting, or in US dollars when pdt cannot read it. pdt takes the price in that currency from the cloud when the cloud lists one. Otherwise it converts from US dollars at the cloud's own exchange rate, or at the European Central Bank's daily reference rate, and the estimate names the rate it used.
|
|
237
|
+
|
|
238
|
+
A deploy to AWS, Azure, or Google Cloud builds each app's image with Docker on your computer and uploads it to that cloud's image registry. When Docker is installed but not running, the deploy starts it. When Docker is not installed, the deploy shows the steps that install it and asks once. On macOS with Homebrew, it installs Colima through Homebrew and starts it with `brew services start colima`, which also starts it at each login. On macOS without Homebrew, it downloads Colima, Lima, the docker CLI, and docker-buildx to the `docker` folder in the pdt data folder (`$XDG_DATA_HOME/pdt`, by default `~/.local/share/pdt`), checks each file against its pinned sha256, and starts Colima; each later deploy starts Colima when it is stopped. To remove it, run `~/.local/share/pdt/docker/bin/colima delete`, delete that `docker` folder, and delete the link `~/.docker/cli-plugins/docker-buildx`. On Windows x64, it installs Rancher Desktop through winget. On Linux, it downloads Docker's install script at a pinned docker/docker-install commit, checks it against its pinned sha256, runs it with `sudo sh`, and adds you to the docker group. When Docker is installed and only `sudo` reaches it, the deploy shows the two commands that add you to the docker group and asks once. Without winget or sudo, and on Windows ARM64, the deploy names an install page for your system. When Docker runs without the buildx plugin, the deploy links Homebrew's docker-buildx or downloads the pinned one to the same `docker` folder, and links it into `~/.docker/cli-plugins`. On Linux and on Windows, when the image is for another CPU type than your computer's and the emulator is missing, the deploy first adds the QEMU emulator with `docker run --privileged --rm tonistiigi/binfmt:qemu-v10.2.3@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0 --install <type>`. The Windows provider does not use Docker.
|
|
239
|
+
|
|
193
240
|
### Azure
|
|
194
241
|
|
|
195
242
|
```yaml
|
|
@@ -243,11 +290,15 @@ COPY . /workspace
|
|
|
243
290
|
WORKDIR /workspace/my-app
|
|
244
291
|
ENV PDT_PROJECT=/workspace NO_COLOR=1 DBT_USE_COLORS=false
|
|
245
292
|
RUN uv sync --script run.py
|
|
246
|
-
ENTRYPOINT ["
|
|
293
|
+
ENTRYPOINT ["sh", "-c", "uv run --script run.py; code=$?; echo \"pdt: exit $code\"; exit $code"]
|
|
247
294
|
```
|
|
248
295
|
|
|
249
296
|
A `.dockerignore` in the app folder keeps files out of the image. Write its patterns relative to the app folder (`.meltano`, `output`, `*.csv`); pdt moves them to the root of the build context for you. `.env` and the other secret files never reach the context, with or without a `.dockerignore`.
|
|
250
297
|
|
|
298
|
+
The `ENTRYPOINT` ends each run's output with the line `pdt: exit N`, which `pdt runs` reads for the run's exit code. Keep that line when you change the rest of the file.
|
|
299
|
+
|
|
300
|
+
The build forwards your computer's SSH agent when one is running, so a `RUN --mount=type=ssh` step can install a private git dependency.
|
|
301
|
+
|
|
251
302
|
The Windows provider never builds an image, so `pdt validate` reports a Dockerfile in an app that uses it.
|
|
252
303
|
|
|
253
304
|
### Windows Task Scheduler
|
|
@@ -283,6 +334,12 @@ Run `pdt examples` to list them, then `pdt new <name> --from <example>` to copy
|
|
|
283
334
|
|
|
284
335
|
Small app that logs "Hello world." Shows how to write a config.yml and useful as an empty starting project so you can write your own. `pdt init` puts a copy of this in every new empty project.
|
|
285
336
|
|
|
337
|
+
### azure-license-waste
|
|
338
|
+
|
|
339
|
+
A PowerShell app. Finds the Microsoft 365 licenses a tenant pays for that nobody uses: paid licenses on disabled accounts, on accounts that have not signed in for 90 days (or never did), and seats bought but assigned to nobody. Each run keeps a `LicenseWaste-<date>.xlsx` workbook in the app's data store, with a Summary sheet of monthly and annual cost and one sheet per finding.
|
|
340
|
+
|
|
341
|
+
Monthly prices come from the app's `license-prices.csv`; edit it to match what your tenant pays. A license missing from it is listed on an Unpriced Licenses sheet so you can add it. The Graph permissions and env vars it needs are listed in its `config.yml` and `env.template`.
|
|
342
|
+
|
|
286
343
|
### impossible-travel-report
|
|
287
344
|
|
|
288
345
|
Looks at recent Entra ID sign-ins. Sends an email when one person signs in from two far-apart places too quickly: Dallas an hour ago, Paris now.
|
|
@@ -293,6 +350,10 @@ Settings for the lookback window, the minimum distance, and the email addresses
|
|
|
293
350
|
|
|
294
351
|
Reports active Monday users whose email address does not match the `userPrincipalName` of an active Entra ID user.
|
|
295
352
|
|
|
353
|
+
### powershell-report
|
|
354
|
+
|
|
355
|
+
A PowerShell app with no `run.py`. Its script writes a small CSV report with `Export-Csv`, and pdt keeps the file in the app's data store.
|
|
356
|
+
|
|
296
357
|
## Where things live
|
|
297
358
|
|
|
298
359
|
| Item | Where |
|
|
@@ -302,8 +363,11 @@ Reports active Monday users whose email address does not match the `userPrincipa
|
|
|
302
363
|
| the example apps | inside the pdt package, copied out by `pdt new` |
|
|
303
364
|
| your apps, `pdt.yml`, `.env` | your project folder, under version control |
|
|
304
365
|
| the Google Cloud CLI pdt downloads | `~/.local/share/pdt/gcloud`, or `%LOCALAPPDATA%\pdt\gcloud` |
|
|
366
|
+
| the PowerShell 7 pdt downloads | `~/.local/share/pdt/pwsh`, or `%ProgramData%\pdt\pwsh` |
|
|
367
|
+
| the DuckDB files of the `pdt gui` SQL workbench | `~/.local/share/pdt/duckdb-wasm`, or `%LOCALAPPDATA%\pdt\duckdb-wasm` |
|
|
368
|
+
| what `pdt gui` fetched | `.pdt/gui.sqlite3` in your project folder |
|
|
305
369
|
| a Windows scheduled task's run logs and files | `%ProgramData%\pdt\<app>\logs` and `%ProgramData%\pdt\<app>\storage` |
|
|
306
|
-
| cloud sign-in state | `~/.azure
|
|
370
|
+
| cloud sign-in state | `~/.aws`, `~/.azure`, and `~/.config/gcloud`, as usual |
|
|
307
371
|
|
|
308
372
|
Set `PDT_PROJECT` to name the project folder directly, instead of letting pdt search upward. Deployed jobs get it set for them.
|
|
309
373
|
|
|
@@ -314,7 +378,7 @@ An app is a folder with a `run.py` that has a `main()` function. It declares its
|
|
|
314
378
|
```python
|
|
315
379
|
# /// script
|
|
316
380
|
# requires-python = ">=3.12"
|
|
317
|
-
# dependencies = ["pdt-cli[apps]==0.1.
|
|
381
|
+
# dependencies = ["pdt-cli[apps]==0.1.7"]
|
|
318
382
|
# ///
|
|
319
383
|
from pathlib import Path
|
|
320
384
|
|
|
@@ -328,11 +392,11 @@ def main() -> int:
|
|
|
328
392
|
...
|
|
329
393
|
```
|
|
330
394
|
|
|
331
|
-
The pinned version
|
|
395
|
+
The pinned version is the pdt version the app runs with, on your computer and in a deployed job. Upgrading pdt on your computer does not change it. Keep the pin at the newest pdt release, and run `pdt deploy APP` after you raise it, so the deployed job uses it too.
|
|
332
396
|
|
|
333
397
|
### PowerShell apps
|
|
334
398
|
|
|
335
|
-
A folder of `.ps1` files with no `run.py` is a PowerShell app: pdt reads the scripts to find the modules to install, then runs the entry scripts in name order through pwsh. A `run.ps1` file is the only entry script. Otherwise, use `run_scripts` in `config.yml` to choose entries and set their order. Do not use `run.ps1` and `run_scripts` together. By default, pdt stops after the first failed script. Set `continue_on_error: true` to run every entry script. The run still fails with the first script's exit code. To change either step, run `pdt new <app> --from-scripts`. It writes `requirements.psd1`, which lists the modules with pinned versions, and `run.py`, which runs the scripts. Once they exist, pdt installs exactly the modules in `requirements.psd1` (`pdt validate` warns about a module the scripts need that it leaves out), and `pdt run` and deploy run your `run.py`.
|
|
399
|
+
A folder of `.ps1` files with no `run.py` is a PowerShell app: pdt reads the scripts to find the modules to install, then runs the entry scripts in name order through pwsh. Before `pdt run` starts the scripts on your computer, pdt installs each of those modules that is missing or at another version into its own folder (`~/.local/share/pdt/powershell-modules`, or `%LOCALAPPDATA%\pdt\powershell-modules` on Windows), so a local run loads the same versions a deploy installs. Other PowerShell sessions do not see that folder, and deleting it removes the modules. A `run.ps1` file is the only entry script. Otherwise, use `run_scripts` in `config.yml` to choose entries and set their order. Do not use `run.ps1` and `run_scripts` together. By default, pdt stops after the first failed script. Set `continue_on_error: true` to run every entry script. The run still fails with the first script's exit code. To change either step, run `pdt new <app> --from-scripts`. It writes `requirements.psd1`, which lists the modules with pinned versions, and `run.py`, which runs the scripts. Once they exist, pdt installs exactly the modules in `requirements.psd1` (`pdt validate` warns about a module the scripts need that it leaves out), and `pdt run` and deploy run your `run.py`.
|
|
336
400
|
|
|
337
401
|
```
|
|
338
402
|
pdt new my-report --from-scripts
|
|
@@ -6,17 +6,31 @@ Run scheduled jobs — reports, integrations, automations — and deploy them to
|
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
uv tool install pdt-cli
|
|
9
|
+
uv tool update-shell
|
|
9
10
|
```
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
`uv tool install` puts the `pdt` command in uv's tool folder. `uv tool update-shell` adds that folder to your PATH if it is not there yet. A terminal window that was already open does not see a PATH change, so open a new window before you type `pdt`. To update pdt later, run `uv tool upgrade pdt-cli`.
|
|
12
13
|
|
|
13
|
-
On Windows
|
|
14
|
+
On Windows, paste these lines into PowerShell. They install `uv` and pdt, and they work in the window you type them in:
|
|
15
|
+
|
|
16
|
+
```powershell
|
|
17
|
+
winget install --id astral-sh.uv -e
|
|
18
|
+
$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')
|
|
19
|
+
uv tool install pdt-cli
|
|
20
|
+
uv tool update-shell
|
|
21
|
+
$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')
|
|
22
|
+
pdt --version
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`winget` and `uv tool update-shell` change the PATH for new windows only. Each `$env:Path = ...` line reads the new PATH into the current window. Another PowerShell window that was open before the install does not find `uv` or `pdt` until you paste the same line into it or open a new window.
|
|
26
|
+
|
|
27
|
+
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. Open a new PowerShell window after it finishes. `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
28
|
|
|
15
29
|
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
30
|
|
|
17
31
|
## Set up a project
|
|
18
32
|
|
|
19
|
-
A project is a folder holding `pdt.yml`. Each app is a folder inside it that contains a `run.py`. Every command except `init` finds the project by looking in the current folder, then each folder above it.
|
|
33
|
+
A project is a folder holding `pdt.yml`. Each app is a folder inside it that contains a `run.py`, or the `.ps1` scripts of a PowerShell app. Every command except `init` finds the project by looking in the current folder, then each folder above it.
|
|
20
34
|
|
|
21
35
|
```
|
|
22
36
|
pdt init my-jobs
|
|
@@ -26,6 +40,8 @@ pdt init my-jobs
|
|
|
26
40
|
|
|
27
41
|
Region is the only setting it asks for. Your AWS account, Azure subscription, and Google Cloud project all come from your credentials the first time you deploy, and pdt writes the answer into `pdt.yml` so every later deploy checks against it.
|
|
28
42
|
|
|
43
|
+
pdt suggests the region nearest to you from your computer's time zone. If `pdt.yml` has no region when you first deploy, the deploy suggests one the same way, asks you to confirm it or type another, and saves your answer. With `--yes` it takes the suggestion and tells you which region it used.
|
|
44
|
+
|
|
29
45
|
Starting in an empty folder also gives you a working app called `hello-world`. Run it straight away:
|
|
30
46
|
|
|
31
47
|
```
|
|
@@ -67,7 +83,18 @@ my-jobs/
|
|
|
67
83
|
pdt deploy my-report
|
|
68
84
|
```
|
|
69
85
|
|
|
70
|
-
The command prints a resource plan and a monthly cost estimate before it changes anything. Add `--yes` to skip the question.
|
|
86
|
+
A deploy to AWS, Azure, or Google Cloud builds the app's image with Docker on your computer. The deploy starts Docker when it is installed but not running, and installs it after one question when it is missing (see [Choosing where jobs run](#choosing-where-jobs-run)). The command prints a resource plan and a monthly cost estimate before it changes anything. Add `--yes` to skip the question.
|
|
87
|
+
|
|
88
|
+
When the app has no successful run yet (it never ran, or every run failed), the deploy then asks `Start a run now and show its log? [y/N]`. A yes starts one run, as `pdt run my-report --deployed` does, and prints the run's log lines as they arrive, as `pdt logs my-report --follow` does. The deploy then exits with the run's result: 0 when the run succeeded, 1 when it failed. A run can have side effects, for example an email to real people, so the answer is no unless you type `y`. With `--yes`, or with no terminal (a build server), pdt does not ask and starts no run. Add `--run` to start the run and follow its log with no question. `pdt deploy --all` does not offer a run.
|
|
89
|
+
|
|
90
|
+
To start one run of the deployed job now, and then read its log:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
pdt run my-report --deployed
|
|
94
|
+
pdt logs my-report
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
To remove everything the deploy created:
|
|
71
98
|
|
|
72
99
|
```
|
|
73
100
|
pdt destroy my-report
|
|
@@ -86,26 +113,34 @@ pdt login my-report
|
|
|
86
113
|
| `pdt init [DIR]` | create a project here, or in DIR |
|
|
87
114
|
| `pdt examples` | list the example apps bundled with pdt |
|
|
88
115
|
| `pdt new APP --from EXAMPLE` | add an app to the project |
|
|
89
|
-
| `pdt list` | show every app with its schedule and
|
|
90
|
-
| `pdt validate` | check the config files and the required env vars |
|
|
116
|
+
| `pdt list` | show every app with its schedule and platform; `--names` prints only the enabled app names; `--json` prints one object per app with `name`, `schedule`, `platform`, `enabled`, `paused`, and `error` (a config problem, or `null`) |
|
|
117
|
+
| `pdt validate` | check the config files and the required env vars; exits 1 when it finds a problem; `--json` prints `{"ok": ..., "problems": [{"app": ..., "message": ...}]}`, where `app` is `null` for a problem of the project or of more than one app |
|
|
91
118
|
| `pdt run APP` | run an app on this machine |
|
|
92
|
-
| `pdt
|
|
119
|
+
| `pdt run APP --deployed` | start one run of the deployed job now, on its platform |
|
|
120
|
+
| `pdt deploy APP` | deploy an app to its configured platform; when the app has no successful run yet, asks whether to start one and follow its log |
|
|
121
|
+
| `pdt deploy APP --run` | deploy, then start the app's first run and follow its log with no question; starts no run when the app already has a successful run |
|
|
93
122
|
| `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 |
|
|
94
123
|
| `pdt destroy APP` | remove everything deploy created |
|
|
124
|
+
| `pdt pause APP` | stop the deployed schedule from starting runs; writes `pause: true` into the app's `config.yml` |
|
|
125
|
+
| `pdt unpause APP` | let the schedule start runs again; writes `pause: false` |
|
|
95
126
|
| `pdt secrets APP` | show which `.env` values differ from the deployed app |
|
|
96
127
|
| `pdt secrets APP save` | send your `.env` values to the deployed app; the next run uses them |
|
|
97
128
|
| `pdt secrets APP get` | copy the deployed values into a `.env.<provider>` file |
|
|
98
129
|
| `pdt secrets APP set NAME` | put one value, read from stdin, into the deployed app's secrets |
|
|
99
|
-
| `pdt login APP` | sign in again to the app's
|
|
100
|
-
| `pdt storage APP ls
|
|
130
|
+
| `pdt login APP` | sign in again to the app's platform |
|
|
131
|
+
| `pdt storage APP ls\|get\|query\|unlock\|destroy` | look at, fetch, query, unlock, or delete the app's stored files; `ls PATH --recursive` lists every file under a folder, and `ls --json` prints the list as JSON |
|
|
101
132
|
| `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,
|
|
133
|
+
| `pdt logs APP [N]` | read the log of run N as `pdt runs` numbers it; with no N, the newest run, `--failed` picks the newest failed run instead, and `--id ID` picks a run by the id `pdt runs` shows (repeat it to read several runs; their `--json` is then an object keyed by id); `--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, `--full` prints every line, and `--follow` keeps printing new lines, in time order, until the run ends and its log has the run's exit line; on Azure, a running run's lines come from its container's live log, because Azure Log Analytics shows a line a minute or more after the job writes it; a run that is still running, or whose lines have not reached the log store yet, says so |
|
|
103
134
|
| `pdt health [APP] [--all]` | show whether each app's last run succeeded; exits 1 when one failed; inside an app folder it checks only that app, and `--all` checks every app |
|
|
135
|
+
| `pdt gui` | open the project's dashboard in your browser: every app's health, each app's run history, and each run's log and files, with buttons for pause, unpause, and run now; `--background` keeps the server up after the command returns and `pdt gui --stop` ends it; `--port N` and `--no-browser` are there for the rare case |
|
|
136
|
+
| `pdt aws ...` | run the AWS CLI that pdt installs |
|
|
104
137
|
| `pdt az ...` | run the Azure CLI that pdt installs |
|
|
105
138
|
| `pdt gcloud ...` | run the Google Cloud CLI that pdt installs |
|
|
106
139
|
| `pdt completion [SHELL]` | turn on tab completion for a shell |
|
|
107
140
|
|
|
108
|
-
Inside an app folder, or any folder below it, leave `APP` off any command that takes one and pdt uses that app. For example, `pdt logs 3` in the `hello-world` folder reads run 3 of `hello-world`. Anywhere else, leave `APP` off or mistype it and pdt lists the apps in the project so you can pick one.
|
|
141
|
+
Inside an app folder, or any folder below it, leave `APP` off any command that takes one and pdt uses that app. For example, `pdt logs 3` in the `hello-world` folder reads run 3 of `hello-world`. Anywhere else, leave `APP` off or mistype it and pdt lists the apps in the project so you can pick one; a mistyped name also gets the closest app name. A mistyped command or option gets the closest one too, for example `pdt lgos` asks "Did you mean `pdt logs`?".
|
|
142
|
+
|
|
143
|
+
With `--json`, `pdt list`, `pdt validate`, `pdt runs`, `pdt logs`, `pdt health`, and `pdt storage APP ls` print only JSON on stdout, and every other line, such as an error or a sign-in question, on stderr. In a project with no enabled apps, `pdt runs`, `pdt logs`, `pdt health`, and `pdt storage APP ls` print `[]`. A command that has no JSON to print exits with a code other than 0 and prints nothing on stdout.
|
|
109
144
|
|
|
110
145
|
`pdt az` and `pdt gcloud` hand your arguments straight to the cloud tool, and install it first if it is missing. For example, `pdt az account list`.
|
|
111
146
|
|
|
@@ -127,23 +162,31 @@ with store.open("report.csv", "wb") as f:
|
|
|
127
162
|
|
|
128
163
|
`store.fs()` gives you the full [fsspec](https://filesystem-spec.readthedocs.io/) filesystem, rooted at your app's folder. Output from one run goes under `store.run_folder()`, which names a new `runs/<time>-<id>/` folder each run.
|
|
129
164
|
|
|
130
|
-
Some apps need files from the last run: a Meltano bookmark, a database. Keep those in
|
|
165
|
+
Some apps need files from the last run: a Meltano bookmark, a database. Keep those in the `state` folder that `storage.sync()` hands you, and put anything a person may want to look at afterwards in its `output` folder:
|
|
131
166
|
|
|
132
167
|
```python
|
|
133
|
-
from pathlib import Path
|
|
134
168
|
from pdt.utils import storage
|
|
135
169
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
store.push(Path(".pdt-state"), "state/", lease)
|
|
170
|
+
with storage.sync() as run:
|
|
171
|
+
# do the work; keep anything the next run needs inside run.state/
|
|
172
|
+
(run.output / "report.csv").write_text("name,count\n")
|
|
140
173
|
```
|
|
141
174
|
|
|
142
|
-
`
|
|
175
|
+
`sync` copies `state/` down from your app's folder and locks it, so a second copy of your app cannot run at the same time and mix up the files. When the block ends, it uploads `run.output` to this run's `runs/` folder, checks that nobody else changed the state files, saves them, and unlocks. When the block raises, or the run is interrupted, it still uploads `run.output`, unlocks, and leaves `state/` as it was, so the next run starts from the last good state. An app whose state is safe to keep at any point, such as a Meltano bookmark or an OAuth token that rotates on every login, passes `storage.sync(push_state_on_error=True)` so a failed run keeps what it saved. Both local folders live under `.pdt/runs/` in the app folder and stay there for inspection.
|
|
176
|
+
|
|
177
|
+
The same steps by hand are `store.pull("state/", folder)`, which returns a lease, then `store.push(folder, "state/", lease)` on success or `store.abort(lease)` on failure.
|
|
178
|
+
|
|
179
|
+
A run that dies without reaching either call leaves the lock behind. The next run takes it over right away when it can tell the holder is gone: the holder ran on the same computer and its process has ended, or it carries the same `PDT_RUN_ID`. Otherwise it waits 30 minutes, or you release the lock yourself with `pdt storage APP unlock`, which asks first.
|
|
143
180
|
|
|
144
181
|
From your own computer, `pdt storage APP ls`, `get`, and `query` read the files with your own cloud sign-in. `pdt storage APP destroy` is the only command that deletes them, and it asks first. An app that needs none of this sets `storage: false` in its `config.yml`.
|
|
145
182
|
|
|
146
|
-
An app that is not ready sets `enabled: false` in its `config.yml`. `pdt list` still shows it, and every other command acts as if the app is not there. `uv run run.py` in the app folder still runs it.
|
|
183
|
+
An app that is not ready sets `enabled: false` in its `config.yml`. `pdt list` still shows it, and every other command acts as if the app is not there. `uv run run.py` in the app folder still runs it. `enabled: false` does not stop a schedule that is already deployed, so run `pdt pause APP` or `pdt destroy APP` before you set it.
|
|
184
|
+
|
|
185
|
+
## The dashboard
|
|
186
|
+
|
|
187
|
+
`pdt gui` starts a small web server on your own computer and opens it in your browser. The first page is `pdt health` as a table, with a grid of every run by hour, day, week, or month underneath it. Click an app for its run history, and click a run for its log and the files it kept in storage. A CSV file a run kept opens in a SQL workbench in the browser: a table you can sort, filter, and page, and a SQL view where every CSV of the run is a view you can query and join, with the matching `pdt storage APP query` command shown under it. The workbench runs on DuckDB, which the first `pdt gui` downloads once into the pdt data folder (`~/.local/share/pdt`); a Download link next to each file still saves it. Every number on a page came out of a pdt command (`pdt runs`, `pdt logs`, `pdt storage ls`), and every button runs one (`pdt pause`, `pdt unpause`, `pdt run --deployed`); hover a button to see which. The pages keep what they fetched in `.pdt/gui.sqlite3` inside the project and fetch again when it is older than five minutes or when you press Refresh, so a run stays in the history after the cloud has forgotten it. A worker inside the server checks for work every two minutes: it refreshes each app's runs, asks the provider once for every run it still keeps, and then fetches the log and files of each run that lacks them, newest first, until every run has them. A page shows what is there; it only waits when it asks for something the worker has not reached yet. The Stats link shows how long each page and each pdt command took. The server listens on localhost only and has no login. `pdt gui --background` leaves it running after the command returns; `pdt gui --stop` ends it.
|
|
188
|
+
|
|
189
|
+
An app that should stay deployed but not run for a while sets `pause: true`. The schedule stays in place and does not fire until the key goes back to `false`. `pdt pause APP` and `pdt unpause APP` write the key into the app's `config.yml` and apply it to the deployed schedule in the same step; `pdt deploy` also honors it. A paused app still runs when you ask for it with `pdt run APP --deployed`.
|
|
147
190
|
|
|
148
191
|
## Running pdt from CI
|
|
149
192
|
|
|
@@ -152,12 +195,16 @@ Every command reads its values from the environment it runs in. A build server s
|
|
|
152
195
|
- A `.env` file is a convenience for a person working on their own machine. A value that is already in the environment wins over the same name in a `.env` file.
|
|
153
196
|
- `PDT_ENV_JSON` holds every value as one JSON object, for a CI system that keeps one secret instead of many. For example, `PDT_ENV_JSON={"PDT_TOKEN": "abc", "PDT_SMTP_USER": "reports@example.com"}`.
|
|
154
197
|
- pdt creates no `.env` file on a build server. It looks for the `CI` variable that build systems set, and for a missing terminal. Email authorization must therefore be done first: run `pdt run APP` once on a machine with a browser, then copy `PDT_SMTP_OAUTH_CACHE_B64` (or `PDT_GRAPH_MAIL_CACHE_B64` for Microsoft Graph) from your `.env` into the CI variables.
|
|
155
|
-
- `pdt deploy APP` and `pdt destroy APP` ask before they change anything. Add `--yes` so they proceed without asking.
|
|
198
|
+
- `pdt deploy APP` and `pdt destroy APP` ask before they change anything. Add `--yes` so they proceed without asking. On a build server, `pdt deploy APP` starts no run after the deploy; add `--run` to start the app's first run and follow its log.
|
|
156
199
|
|
|
157
200
|
## Choosing where jobs run
|
|
158
201
|
|
|
159
202
|
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.
|
|
160
203
|
|
|
204
|
+
The cost estimate before each deploy shows prices in the currency of your computer's regional setting, or in US dollars when pdt cannot read it. pdt takes the price in that currency from the cloud when the cloud lists one. Otherwise it converts from US dollars at the cloud's own exchange rate, or at the European Central Bank's daily reference rate, and the estimate names the rate it used.
|
|
205
|
+
|
|
206
|
+
A deploy to AWS, Azure, or Google Cloud builds each app's image with Docker on your computer and uploads it to that cloud's image registry. When Docker is installed but not running, the deploy starts it. When Docker is not installed, the deploy shows the steps that install it and asks once. On macOS with Homebrew, it installs Colima through Homebrew and starts it with `brew services start colima`, which also starts it at each login. On macOS without Homebrew, it downloads Colima, Lima, the docker CLI, and docker-buildx to the `docker` folder in the pdt data folder (`$XDG_DATA_HOME/pdt`, by default `~/.local/share/pdt`), checks each file against its pinned sha256, and starts Colima; each later deploy starts Colima when it is stopped. To remove it, run `~/.local/share/pdt/docker/bin/colima delete`, delete that `docker` folder, and delete the link `~/.docker/cli-plugins/docker-buildx`. On Windows x64, it installs Rancher Desktop through winget. On Linux, it downloads Docker's install script at a pinned docker/docker-install commit, checks it against its pinned sha256, runs it with `sudo sh`, and adds you to the docker group. When Docker is installed and only `sudo` reaches it, the deploy shows the two commands that add you to the docker group and asks once. Without winget or sudo, and on Windows ARM64, the deploy names an install page for your system. When Docker runs without the buildx plugin, the deploy links Homebrew's docker-buildx or downloads the pinned one to the same `docker` folder, and links it into `~/.docker/cli-plugins`. On Linux and on Windows, when the image is for another CPU type than your computer's and the emulator is missing, the deploy first adds the QEMU emulator with `docker run --privileged --rm tonistiigi/binfmt:qemu-v10.2.3@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0 --install <type>`. The Windows provider does not use Docker.
|
|
207
|
+
|
|
161
208
|
### Azure
|
|
162
209
|
|
|
163
210
|
```yaml
|
|
@@ -211,11 +258,15 @@ COPY . /workspace
|
|
|
211
258
|
WORKDIR /workspace/my-app
|
|
212
259
|
ENV PDT_PROJECT=/workspace NO_COLOR=1 DBT_USE_COLORS=false
|
|
213
260
|
RUN uv sync --script run.py
|
|
214
|
-
ENTRYPOINT ["
|
|
261
|
+
ENTRYPOINT ["sh", "-c", "uv run --script run.py; code=$?; echo \"pdt: exit $code\"; exit $code"]
|
|
215
262
|
```
|
|
216
263
|
|
|
217
264
|
A `.dockerignore` in the app folder keeps files out of the image. Write its patterns relative to the app folder (`.meltano`, `output`, `*.csv`); pdt moves them to the root of the build context for you. `.env` and the other secret files never reach the context, with or without a `.dockerignore`.
|
|
218
265
|
|
|
266
|
+
The `ENTRYPOINT` ends each run's output with the line `pdt: exit N`, which `pdt runs` reads for the run's exit code. Keep that line when you change the rest of the file.
|
|
267
|
+
|
|
268
|
+
The build forwards your computer's SSH agent when one is running, so a `RUN --mount=type=ssh` step can install a private git dependency.
|
|
269
|
+
|
|
219
270
|
The Windows provider never builds an image, so `pdt validate` reports a Dockerfile in an app that uses it.
|
|
220
271
|
|
|
221
272
|
### Windows Task Scheduler
|
|
@@ -251,6 +302,12 @@ Run `pdt examples` to list them, then `pdt new <name> --from <example>` to copy
|
|
|
251
302
|
|
|
252
303
|
Small app that logs "Hello world." Shows how to write a config.yml and useful as an empty starting project so you can write your own. `pdt init` puts a copy of this in every new empty project.
|
|
253
304
|
|
|
305
|
+
### azure-license-waste
|
|
306
|
+
|
|
307
|
+
A PowerShell app. Finds the Microsoft 365 licenses a tenant pays for that nobody uses: paid licenses on disabled accounts, on accounts that have not signed in for 90 days (or never did), and seats bought but assigned to nobody. Each run keeps a `LicenseWaste-<date>.xlsx` workbook in the app's data store, with a Summary sheet of monthly and annual cost and one sheet per finding.
|
|
308
|
+
|
|
309
|
+
Monthly prices come from the app's `license-prices.csv`; edit it to match what your tenant pays. A license missing from it is listed on an Unpriced Licenses sheet so you can add it. The Graph permissions and env vars it needs are listed in its `config.yml` and `env.template`.
|
|
310
|
+
|
|
254
311
|
### impossible-travel-report
|
|
255
312
|
|
|
256
313
|
Looks at recent Entra ID sign-ins. Sends an email when one person signs in from two far-apart places too quickly: Dallas an hour ago, Paris now.
|
|
@@ -261,6 +318,10 @@ Settings for the lookback window, the minimum distance, and the email addresses
|
|
|
261
318
|
|
|
262
319
|
Reports active Monday users whose email address does not match the `userPrincipalName` of an active Entra ID user.
|
|
263
320
|
|
|
321
|
+
### powershell-report
|
|
322
|
+
|
|
323
|
+
A PowerShell app with no `run.py`. Its script writes a small CSV report with `Export-Csv`, and pdt keeps the file in the app's data store.
|
|
324
|
+
|
|
264
325
|
## Where things live
|
|
265
326
|
|
|
266
327
|
| Item | Where |
|
|
@@ -270,8 +331,11 @@ Reports active Monday users whose email address does not match the `userPrincipa
|
|
|
270
331
|
| the example apps | inside the pdt package, copied out by `pdt new` |
|
|
271
332
|
| your apps, `pdt.yml`, `.env` | your project folder, under version control |
|
|
272
333
|
| the Google Cloud CLI pdt downloads | `~/.local/share/pdt/gcloud`, or `%LOCALAPPDATA%\pdt\gcloud` |
|
|
334
|
+
| the PowerShell 7 pdt downloads | `~/.local/share/pdt/pwsh`, or `%ProgramData%\pdt\pwsh` |
|
|
335
|
+
| the DuckDB files of the `pdt gui` SQL workbench | `~/.local/share/pdt/duckdb-wasm`, or `%LOCALAPPDATA%\pdt\duckdb-wasm` |
|
|
336
|
+
| what `pdt gui` fetched | `.pdt/gui.sqlite3` in your project folder |
|
|
273
337
|
| a Windows scheduled task's run logs and files | `%ProgramData%\pdt\<app>\logs` and `%ProgramData%\pdt\<app>\storage` |
|
|
274
|
-
| cloud sign-in state | `~/.azure
|
|
338
|
+
| cloud sign-in state | `~/.aws`, `~/.azure`, and `~/.config/gcloud`, as usual |
|
|
275
339
|
|
|
276
340
|
Set `PDT_PROJECT` to name the project folder directly, instead of letting pdt search upward. Deployed jobs get it set for them.
|
|
277
341
|
|
|
@@ -282,7 +346,7 @@ An app is a folder with a `run.py` that has a `main()` function. It declares its
|
|
|
282
346
|
```python
|
|
283
347
|
# /// script
|
|
284
348
|
# requires-python = ">=3.12"
|
|
285
|
-
# dependencies = ["pdt-cli[apps]==0.1.
|
|
349
|
+
# dependencies = ["pdt-cli[apps]==0.1.7"]
|
|
286
350
|
# ///
|
|
287
351
|
from pathlib import Path
|
|
288
352
|
|
|
@@ -296,11 +360,11 @@ def main() -> int:
|
|
|
296
360
|
...
|
|
297
361
|
```
|
|
298
362
|
|
|
299
|
-
The pinned version
|
|
363
|
+
The pinned version is the pdt version the app runs with, on your computer and in a deployed job. Upgrading pdt on your computer does not change it. Keep the pin at the newest pdt release, and run `pdt deploy APP` after you raise it, so the deployed job uses it too.
|
|
300
364
|
|
|
301
365
|
### PowerShell apps
|
|
302
366
|
|
|
303
|
-
A folder of `.ps1` files with no `run.py` is a PowerShell app: pdt reads the scripts to find the modules to install, then runs the entry scripts in name order through pwsh. A `run.ps1` file is the only entry script. Otherwise, use `run_scripts` in `config.yml` to choose entries and set their order. Do not use `run.ps1` and `run_scripts` together. By default, pdt stops after the first failed script. Set `continue_on_error: true` to run every entry script. The run still fails with the first script's exit code. To change either step, run `pdt new <app> --from-scripts`. It writes `requirements.psd1`, which lists the modules with pinned versions, and `run.py`, which runs the scripts. Once they exist, pdt installs exactly the modules in `requirements.psd1` (`pdt validate` warns about a module the scripts need that it leaves out), and `pdt run` and deploy run your `run.py`.
|
|
367
|
+
A folder of `.ps1` files with no `run.py` is a PowerShell app: pdt reads the scripts to find the modules to install, then runs the entry scripts in name order through pwsh. Before `pdt run` starts the scripts on your computer, pdt installs each of those modules that is missing or at another version into its own folder (`~/.local/share/pdt/powershell-modules`, or `%LOCALAPPDATA%\pdt\powershell-modules` on Windows), so a local run loads the same versions a deploy installs. Other PowerShell sessions do not see that folder, and deleting it removes the modules. A `run.ps1` file is the only entry script. Otherwise, use `run_scripts` in `config.yml` to choose entries and set their order. Do not use `run.ps1` and `run_scripts` together. By default, pdt stops after the first failed script. Set `continue_on_error: true` to run every entry script. The run still fails with the first script's exit code. To change either step, run `pdt new <app> --from-scripts`. It writes `requirements.psd1`, which lists the modules with pinned versions, and `run.py`, which runs the scripts. Once they exist, pdt installs exactly the modules in `requirements.psd1` (`pdt validate` warns about a module the scripts need that it leaves out), and `pdt run` and deploy run your `run.py`.
|
|
304
368
|
|
|
305
369
|
```
|
|
306
370
|
pdt new my-report --from-scripts
|
|
@@ -119,7 +119,7 @@ def comment(marker: str, login: str = "pdt-bot") -> dict:
|
|
|
119
119
|
|
|
120
120
|
def test_path_floors_follow_the_table():
|
|
121
121
|
arch = ["src/pdt/utils/log.py", "src/pdt/deploy.py", "src/pdt/deploy_common.py",
|
|
122
|
-
"src/pdt/config.py", "src/pdt/cli.py", "src/pdt/__init__.py",
|
|
122
|
+
"src/pdt/config.py", "src/pdt/cli.py", "src/pdt/__init__.py",
|
|
123
123
|
".github/workflows/ci.yml", "ci/claude_task.py", "AGENTS.md", "CLAUDE.md",
|
|
124
124
|
"pdt", "pdt.bat"]
|
|
125
125
|
for path in arch:
|
|
@@ -172,7 +172,7 @@ def test_a_few_tested_lines_in_a_covered_file_floor_at_simple():
|
|
|
172
172
|
assert select.rule_floor(pr(), [five, test])[0] == "architectural"
|
|
173
173
|
gated = change("src/pdt/cli.py", extra="+token = 1\n")
|
|
174
174
|
assert select.rule_floor(pr(), [gated, test])[0] == "architectural"
|
|
175
|
-
for path in ("src/pdt/utils/log.py", ".
|
|
175
|
+
for path in ("src/pdt/utils/log.py", ".github/workflows/ci.yml", "AGENTS.md", "pdt"):
|
|
176
176
|
assert select.rule_floor(pr(), [change(path), test])[0] == "architectural", path
|
|
177
177
|
|
|
178
178
|
|
|
@@ -670,10 +670,3 @@ def test_score_prs_workflow_runs_on_the_three_pr_events_a_schedule_and_by_hand()
|
|
|
670
670
|
assert score["run"].rstrip().endswith("|| [ $? -eq 2 ]")
|
|
671
671
|
upload = job["steps"][-1]
|
|
672
672
|
assert upload["if"] == "always()" and upload["with"]["retention-days"] == 30
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
def test_gitlab_ci_runs_no_claude_task():
|
|
676
|
-
ci = yaml.safe_load((REPO / ".gitlab-ci.yml").read_text())
|
|
677
|
-
for name in (".claude-task", "rebase-mrs", "score-mrs", "score-mrs-skip",
|
|
678
|
-
"close_stale_drafts"):
|
|
679
|
-
assert name not in ci, name
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pdt-cli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.7"
|
|
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"
|
|
@@ -48,7 +48,7 @@ packages = ["src/pdt"]
|
|
|
48
48
|
include = ["src/pdt", "README.md", "tests"]
|
|
49
49
|
|
|
50
50
|
[dependency-groups]
|
|
51
|
-
dev = ["pytest", "ruff", "fsspec", "boto3"]
|
|
51
|
+
dev = ["pytest", "ruff", "fsspec", "boto3", "django>=5.2", "croniter", "tzlocal"]
|
|
52
52
|
|
|
53
53
|
[tool.pytest.ini_options]
|
|
54
54
|
testpaths = ["tests", "ci/tests"]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
__version__ = version("pdt-cli")
|
|
6
|
+
except PackageNotFoundError:
|
|
7
|
+
# A provider script run from a clone imports pdt from src/, which holds no
|
|
8
|
+
# package metadata; deploy.provider_env passes the parent's version down.
|
|
9
|
+
__version__ = os.environ.get("PDT_CLI_VERSION", "0.0.0.dev0")
|