pdt-cli 0.1.0__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/.gitignore +1 -0
  2. pdt_cli-0.1.0/README.md → pdt_cli-0.1.2/PKG-INFO +128 -7
  3. pdt_cli-0.1.0/PKG-INFO → pdt_cli-0.1.2/README.md +97 -25
  4. pdt_cli-0.1.2/ci/tests/test_claude_task.py +296 -0
  5. pdt_cli-0.1.2/ci/tests/test_close_stale_drafts.py +79 -0
  6. pdt_cli-0.1.2/ci/tests/test_rebase_task.py +333 -0
  7. pdt_cli-0.1.2/ci/tests/test_score_task.py +640 -0
  8. pdt_cli-0.1.2/pyproject.toml +65 -0
  9. pdt_cli-0.1.2/src/pdt/cli.py +299 -0
  10. pdt_cli-0.1.2/src/pdt/completion.py +159 -0
  11. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/config.py +233 -46
  12. pdt_cli-0.1.2/src/pdt/console.py +178 -0
  13. pdt_cli-0.1.2/src/pdt/deploy.py +152 -0
  14. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/deploy_aws.py +177 -84
  15. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/deploy_aws_fargate.py +103 -44
  16. pdt_cli-0.1.2/src/pdt/deploy_azure.py +919 -0
  17. pdt_cli-0.1.2/src/pdt/deploy_azure_container_apps.py +676 -0
  18. pdt_cli-0.1.2/src/pdt/deploy_common.py +403 -0
  19. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/deploy_google_cloud.py +293 -121
  20. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/deploy_windows.py +61 -19
  21. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/hello-world/config.yml +3 -0
  22. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/impossible-travel-report/config.yml +2 -2
  23. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/impossible-travel-report/run.py +4 -4
  24. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/gcloud_sdk.py +18 -27
  25. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/scaffold.py +80 -48
  26. pdt_cli-0.1.2/src/pdt/storage_cli.py +93 -0
  27. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/email_auth.py +32 -38
  28. pdt_cli-0.1.2/src/pdt/utils/env_secret.py +192 -0
  29. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/log.py +15 -1
  30. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/send_email.py +16 -3
  31. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/send_email_graph.py +20 -19
  32. pdt_cli-0.1.2/src/pdt/utils/storage.py +345 -0
  33. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/conftest.py +7 -0
  34. pdt_cli-0.1.2/tests/test_aws_inventory.py +38 -0
  35. pdt_cli-0.1.2/tests/test_aws_names.py +9 -0
  36. pdt_cli-0.1.2/tests/test_aws_not_found.py +19 -0
  37. pdt_cli-0.1.2/tests/test_aws_profile.py +16 -0
  38. pdt_cli-0.1.2/tests/test_aws_storage.py +42 -0
  39. pdt_cli-0.1.2/tests/test_azure_build.py +62 -0
  40. pdt_cli-0.1.2/tests/test_azure_deleted_vault.py +54 -0
  41. pdt_cli-0.1.2/tests/test_azure_environment.py +210 -0
  42. pdt_cli-0.1.2/tests/test_azure_environment_key.py +23 -0
  43. pdt_cli-0.1.2/tests/test_azure_group_ownership.py +21 -0
  44. pdt_cli-0.1.2/tests/test_azure_job_diagnostics.py +132 -0
  45. pdt_cli-0.1.2/tests/test_azure_names.py +24 -0
  46. pdt_cli-0.1.2/tests/test_azure_retries.py +113 -0
  47. pdt_cli-0.1.2/tests/test_azure_side_resources.py +39 -0
  48. pdt_cli-0.1.2/tests/test_azure_storage.py +80 -0
  49. pdt_cli-0.1.2/tests/test_azure_subscription_state.py +29 -0
  50. pdt_cli-0.1.2/tests/test_ci_azure.py +235 -0
  51. pdt_cli-0.1.2/tests/test_ci_google_cloud.py +93 -0
  52. pdt_cli-0.1.2/tests/test_cli.py +104 -0
  53. pdt_cli-0.1.2/tests/test_completion.py +185 -0
  54. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_config_merge.py +16 -1
  55. pdt_cli-0.1.2/tests/test_console.py +35 -0
  56. pdt_cli-0.1.2/tests/test_cost_estimate.py +11 -0
  57. pdt_cli-0.1.2/tests/test_deploy_state.py +52 -0
  58. pdt_cli-0.1.2/tests/test_dockerfile.py +79 -0
  59. pdt_cli-0.1.2/tests/test_env_secret.py +142 -0
  60. pdt_cli-0.1.2/tests/test_env_sources.py +227 -0
  61. pdt_cli-0.1.2/tests/test_fetch_json.py +111 -0
  62. pdt_cli-0.1.2/tests/test_gcloud_sdk.py +24 -0
  63. pdt_cli-0.1.2/tests/test_google_cloud_inventory.py +23 -0
  64. pdt_cli-0.1.2/tests/test_google_cloud_not_found.py +13 -0
  65. pdt_cli-0.1.2/tests/test_google_cloud_retries.py +41 -0
  66. pdt_cli-0.1.2/tests/test_google_cloud_service_account.py +24 -0
  67. pdt_cli-0.1.2/tests/test_google_cloud_storage.py +25 -0
  68. pdt_cli-0.1.2/tests/test_log_colour.py +30 -0
  69. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_project_discovery.py +7 -0
  70. pdt_cli-0.1.2/tests/test_run_log_links.py +51 -0
  71. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_scaffold.py +43 -3
  72. pdt_cli-0.1.2/tests/test_secrets.py +110 -0
  73. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_starter_app.py +44 -1
  74. pdt_cli-0.1.2/tests/test_storage.py +150 -0
  75. pdt_cli-0.1.2/tests/test_storage_cli.py +40 -0
  76. pdt_cli-0.1.2/tests/test_strict_validation.py +65 -0
  77. pdt_cli-0.1.2/tests/test_verify.py +277 -0
  78. pdt_cli-0.1.2/tests/test_windows_storage.py +50 -0
  79. pdt_cli-0.1.2/verify/README.md +92 -0
  80. pdt_cli-0.1.0/pyproject.toml +0 -41
  81. pdt_cli-0.1.0/src/pdt/cli.py +0 -177
  82. pdt_cli-0.1.0/src/pdt/deploy.py +0 -106
  83. pdt_cli-0.1.0/src/pdt/deploy_aws_lambda.py +0 -359
  84. pdt_cli-0.1.0/src/pdt/deploy_azure.py +0 -531
  85. pdt_cli-0.1.0/src/pdt/deploy_azure_container_apps.py +0 -328
  86. pdt_cli-0.1.0/src/pdt/deploy_azure_functions.py +0 -307
  87. pdt_cli-0.1.0/src/pdt/deploy_common.py +0 -94
  88. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/__init__.py +0 -0
  89. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/hello-world/run.py +0 -0
  90. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/impossible-travel-report/env.template +0 -0
  91. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/monday-orphaned-account-report/config.yml +0 -0
  92. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/monday-orphaned-account-report/env.template +0 -0
  93. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/monday-orphaned-account-report/run.py +0 -0
  94. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/__init__.py +0 -0
  95. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/entra.py +0 -0
  96. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/web.py +0 -0
  97. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_aws_account.py +0 -0
  98. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_init_questions.py +0 -0
  99. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_save_platform_key.py +0 -0
  100. {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_schedule.py +0 -0
@@ -15,6 +15,7 @@ dist/
15
15
  *.egg-info/
16
16
  .pytest_cache/
17
17
  .uv-cache/
18
+ claude-task-report/
18
19
 
19
20
  # OS / editor noise
20
21
  .DS_Store
@@ -1,3 +1,34 @@
1
+ Metadata-Version: 2.5
2
+ Name: pdt-cli
3
+ Version: 0.1.2
4
+ Summary: Easily deploy your process to AWS, Azure, Google Cloud, or Windows.
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: argcomplete
8
+ Requires-Dist: backoff
9
+ Requires-Dist: google-auth
10
+ Requires-Dist: google-auth-oauthlib
11
+ Requires-Dist: msal
12
+ Requires-Dist: python-dotenv
13
+ Requires-Dist: pyyaml
14
+ Requires-Dist: rich
15
+ Requires-Dist: rich-argparse>=1.8.0
16
+ Requires-Dist: shellingham
17
+ Provides-Extra: apps
18
+ Requires-Dist: adlfs; extra == 'apps'
19
+ Requires-Dist: azure-identity; extra == 'apps'
20
+ Requires-Dist: azure-keyvault-secrets; extra == 'apps'
21
+ Requires-Dist: azure-storage-blob; extra == 'apps'
22
+ Requires-Dist: boto3; extra == 'apps'
23
+ Requires-Dist: cryptography; extra == 'apps'
24
+ Requires-Dist: fsspec; extra == 'apps'
25
+ Requires-Dist: gcsfs; extra == 'apps'
26
+ Requires-Dist: google-cloud-secret-manager; extra == 'apps'
27
+ Requires-Dist: google-cloud-storage; extra == 'apps'
28
+ Requires-Dist: pyjwt; extra == 'apps'
29
+ Requires-Dist: s3fs; extra == 'apps'
30
+ Description-Content-Type: text/markdown
31
+
1
32
  # pdt
2
33
 
3
34
  Run scheduled jobs — reports, integrations, automations — and deploy them to AWS, Azure, Google Cloud, or Windows Task Scheduler with one command.
@@ -10,6 +41,8 @@ uv tool install pdt-cli
10
41
 
11
42
  That puts a `pdt` command on your PATH. To update it later, run `uv tool upgrade pdt-cli`.
12
43
 
44
+ The first interactive `pdt` command turns on tab completion without a prompt. It works in every new terminal: Tab finishes pdt commands, their options, your app names, and example names for `new --from`. It supports bash, zsh, fish, and PowerShell. `pdt completion [SHELL]` sets up a second shell, and `pdt completion --script bash` prints the script instead of writing it, if you keep your own dotfiles.
45
+
13
46
  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.
14
47
 
15
48
  ## Set up a project
@@ -20,7 +53,7 @@ A project is a folder holding `pdt.yml`. Each app is a folder inside it that con
20
53
  pdt init my-jobs
21
54
  ```
22
55
 
23
- `pdt init` asks where the project should live, which cloud you want, and which region. It warns you if you are about to create a project somewhere unwise, such as your home folder. Run `pdt init` with no folder name to use the current folder, or add `--yes` to take the defaults and answer nothing.
56
+ `pdt init` asks where the project should live, which cloud you want, and which region. It warns you if you are about to create a project somewhere unwise, such as your home folder. The folder name is optional: `pdt init` with no name uses the current folder, and `pdt init DIR` uses `DIR`, creating it if it is not there yet. Add `--yes` to take the defaults and answer nothing.
24
57
 
25
58
  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.
26
59
 
@@ -84,17 +117,70 @@ pdt login my-report
84
117
  | `pdt init [DIR]` | create a project here, or in DIR |
85
118
  | `pdt examples` | list the example apps bundled with pdt |
86
119
  | `pdt new APP --from EXAMPLE` | add an app to the project |
87
- | `pdt list` | show every app with its schedule and provider |
120
+ | `pdt list` | show every app with its schedule and provider; `--names` prints only the enabled app names |
88
121
  | `pdt validate` | check the config files and the required env vars |
89
122
  | `pdt run APP` | run an app on this machine |
90
123
  | `pdt deploy APP` | deploy an app to its configured platform |
91
124
  | `pdt destroy APP` | remove everything deploy created |
125
+ | `pdt secrets APP` | show which `.env` values differ from the deployed app |
126
+ | `pdt secrets APP save` | send your `.env` values to the deployed app; the next run uses them |
127
+ | `pdt secrets APP get` | copy the deployed values into a `.env.<provider>` file |
128
+ | `pdt secrets APP set NAME` | put one value, read from stdin, into the deployed app's secrets |
92
129
  | `pdt login APP` | sign in again to the app's cloud provider |
130
+ | `pdt storage APP ls|get|query|destroy` | look at, fetch, query, or delete the app's stored files |
93
131
  | `pdt az ...` | run the Azure CLI that pdt installs |
94
132
  | `pdt gcloud ...` | run the Google Cloud CLI that pdt installs |
133
+ | `pdt completion [SHELL]` | turn on tab completion for a shell |
134
+
135
+ Leave `APP` off `run`, `deploy`, `destroy`, `secrets`, or `login`, or mistype it, and pdt lists the apps in the project so you can pick one.
95
136
 
96
137
  `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`.
97
138
 
139
+ ## Keeping files between runs
140
+
141
+ 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
+
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 both places.
144
+
145
+ Write and read files with `pdt.utils.storage`:
146
+
147
+ ```python
148
+ from pdt.utils import storage
149
+
150
+ store = storage.store()
151
+ with store.open("report.csv", "wb") as f:
152
+ f.write(b"name,count\n")
153
+ ```
154
+
155
+ `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.
156
+
157
+ Some apps need files from the last run: a Meltano bookmark, a database. Keep those in one local folder, and copy it down before the work and up after:
158
+
159
+ ```python
160
+ from pathlib import Path
161
+ from pdt.utils import storage
162
+
163
+ store = storage.store()
164
+ lease = store.pull("state/", Path(".pdt-state"))
165
+ # do the work; keep anything the next run needs inside .pdt-state/
166
+ store.push(Path(".pdt-state"), "state/", lease)
167
+ ```
168
+
169
+ `pull` locks the folder, so a second copy of your app cannot run at the same time and mix up the files. `push` checks that nobody else changed them, saves them, and unlocks. If a run crashes, the next one takes over the lock after 30 minutes.
170
+
171
+ 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`.
172
+
173
+ 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.
174
+
175
+ ## Running pdt from CI
176
+
177
+ Every command reads its values from the environment it runs in. A build server sets them as CI variables, and no `.env` file is needed.
178
+
179
+ - 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.
180
+ - `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"}`.
181
+ - 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.
182
+ - `pdt deploy APP` and `pdt destroy APP` ask before they change anything. Add `--yes` so they proceed without asking.
183
+
98
184
  ## Choosing where jobs run
99
185
 
100
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.
@@ -107,12 +193,14 @@ platform:
107
193
  subscription: 00000000-0000-0000-0000-000000000000
108
194
  region: eastus
109
195
  resource_group: pdt
110
- # Options: functions (default), container_apps
111
- runtime: functions
196
+ # Optional. An environment you already own, as <resource-group>/<name>.
197
+ # environment: my-group/my-environment
112
198
  ```
113
199
 
114
200
  `subscription` is optional. When it is missing or wrong, the deploy asks you to choose one.
115
201
 
202
+ Every pdt project in a subscription runs its jobs in one shared Container Apps environment per region, `pdt-<region>` in the resource group `pdt-shared`, because a subscription allows only a few environments. Destroying the last app that uses the environment removes it, and removes `pdt-shared` once it holds no environment. Set `environment` to use an environment you already have; pdt then never creates, changes, or deletes it.
203
+
116
204
  ### AWS
117
205
 
118
206
  ```yaml
@@ -121,10 +209,12 @@ platform:
121
209
  region: us-east-1
122
210
  # Written for you on the first deploy, from your credentials.
123
211
  account: "123456789012"
124
- # lambda (default) or fargate
125
- runtime: lambda
212
+ # Optional. Which profile in ~/.aws to use. Written for you if pdt has to ask.
213
+ profile: work
126
214
  ```
127
215
 
216
+ `profile` is optional. When it is missing, pdt uses `AWS_PROFILE`, or the only profile on your computer. When there are several and none is chosen, or the one in the file is not on this computer, the deploy lists them and asks you to choose, then writes your answer here.
217
+
128
218
  ### Google Cloud
129
219
 
130
220
  ```yaml
@@ -136,6 +226,25 @@ platform:
136
226
 
137
227
  `project` is optional. When it is missing or wrong, the deploy lists your projects and asks you to choose one, then writes your answer here.
138
228
 
229
+ ### Bringing your own Dockerfile
230
+
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.
232
+
233
+ 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
+
235
+ ```dockerfile
236
+ FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
237
+ COPY . /workspace
238
+ WORKDIR /workspace/my-app
239
+ ENV PDT_PROJECT=/workspace
240
+ RUN uv sync --script run.py
241
+ ENTRYPOINT ["uv", "run", "--script", "run.py"]
242
+ ```
243
+
244
+ 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`.
245
+
246
+ The Windows provider never builds an image, so `pdt validate` reports a Dockerfile in an app that uses it.
247
+
139
248
  ### Windows Task Scheduler
140
249
 
141
250
  ```yaml
@@ -195,7 +304,7 @@ An app is a folder with a `run.py` that has a `main()` function. It declares its
195
304
  ```python
196
305
  # /// script
197
306
  # requires-python = ">=3.12"
198
- # dependencies = ["pdt-cli[apps]==0.1.0"]
307
+ # dependencies = ["pdt-cli[apps]==0.1.2"]
199
308
  # ///
200
309
  from pathlib import Path
201
310
 
@@ -305,3 +414,15 @@ Leave the two SES key variables empty to use the standard AWS credential chain (
305
414
  Required environment variables:
306
415
 
307
416
  - PDT_RESEND_API_KEY
417
+
418
+ ## Logging
419
+
420
+ `pdt.utils.log` writes one log line per event:
421
+
422
+ ```python
423
+ from pdt.utils.log import die, log
424
+
425
+ log("info", "processed", rows=42)
426
+ ```
427
+
428
+ Levels are `debug`, `info`, `warning`, and `error`. Locally a line is human-readable text on stdout; with `LOG_FORMAT=json` it is one JSON object per line, which is also the default on Cloud Run.
@@ -1,21 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: pdt-cli
3
- Version: 0.1.0
4
- Summary: Validate, run, and deploy small scheduled jobs to AWS, Azure, Google Cloud, or Windows.
5
- License-Expression: MIT
6
- Requires-Python: >=3.12
7
- Requires-Dist: google-auth
8
- Requires-Dist: google-auth-oauthlib
9
- Requires-Dist: msal
10
- Requires-Dist: python-dotenv
11
- Requires-Dist: pyyaml
12
- Provides-Extra: apps
13
- Requires-Dist: boto3; extra == 'apps'
14
- Requires-Dist: cryptography; extra == 'apps'
15
- Requires-Dist: google-cloud-secret-manager; extra == 'apps'
16
- Requires-Dist: pyjwt; extra == 'apps'
17
- Description-Content-Type: text/markdown
18
-
19
1
  # pdt
20
2
 
21
3
  Run scheduled jobs — reports, integrations, automations — and deploy them to AWS, Azure, Google Cloud, or Windows Task Scheduler with one command.
@@ -28,6 +10,8 @@ uv tool install pdt-cli
28
10
 
29
11
  That puts a `pdt` command on your PATH. To update it later, run `uv tool upgrade pdt-cli`.
30
12
 
13
+ The first interactive `pdt` command turns on tab completion without a prompt. It works in every new terminal: Tab finishes pdt commands, their options, your app names, and example names for `new --from`. It supports bash, zsh, fish, and PowerShell. `pdt completion [SHELL]` sets up a second shell, and `pdt completion --script bash` prints the script instead of writing it, if you keep your own dotfiles.
14
+
31
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.
32
16
 
33
17
  ## Set up a project
@@ -38,7 +22,7 @@ A project is a folder holding `pdt.yml`. Each app is a folder inside it that con
38
22
  pdt init my-jobs
39
23
  ```
40
24
 
41
- `pdt init` asks where the project should live, which cloud you want, and which region. It warns you if you are about to create a project somewhere unwise, such as your home folder. Run `pdt init` with no folder name to use the current folder, or add `--yes` to take the defaults and answer nothing.
25
+ `pdt init` asks where the project should live, which cloud you want, and which region. It warns you if you are about to create a project somewhere unwise, such as your home folder. The folder name is optional: `pdt init` with no name uses the current folder, and `pdt init DIR` uses `DIR`, creating it if it is not there yet. Add `--yes` to take the defaults and answer nothing.
42
26
 
43
27
  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.
44
28
 
@@ -102,17 +86,70 @@ pdt login my-report
102
86
  | `pdt init [DIR]` | create a project here, or in DIR |
103
87
  | `pdt examples` | list the example apps bundled with pdt |
104
88
  | `pdt new APP --from EXAMPLE` | add an app to the project |
105
- | `pdt list` | show every app with its schedule and provider |
89
+ | `pdt list` | show every app with its schedule and provider; `--names` prints only the enabled app names |
106
90
  | `pdt validate` | check the config files and the required env vars |
107
91
  | `pdt run APP` | run an app on this machine |
108
92
  | `pdt deploy APP` | deploy an app to its configured platform |
109
93
  | `pdt destroy APP` | remove everything deploy created |
94
+ | `pdt secrets APP` | show which `.env` values differ from the deployed app |
95
+ | `pdt secrets APP save` | send your `.env` values to the deployed app; the next run uses them |
96
+ | `pdt secrets APP get` | copy the deployed values into a `.env.<provider>` file |
97
+ | `pdt secrets APP set NAME` | put one value, read from stdin, into the deployed app's secrets |
110
98
  | `pdt login APP` | sign in again to the app's cloud provider |
99
+ | `pdt storage APP ls|get|query|destroy` | look at, fetch, query, or delete the app's stored files |
111
100
  | `pdt az ...` | run the Azure CLI that pdt installs |
112
101
  | `pdt gcloud ...` | run the Google Cloud CLI that pdt installs |
102
+ | `pdt completion [SHELL]` | turn on tab completion for a shell |
103
+
104
+ Leave `APP` off `run`, `deploy`, `destroy`, `secrets`, or `login`, or mistype it, and pdt lists the apps in the project so you can pick one.
113
105
 
114
106
  `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`.
115
107
 
108
+ ## Keeping files between runs
109
+
110
+ 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
+
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 both places.
113
+
114
+ Write and read files with `pdt.utils.storage`:
115
+
116
+ ```python
117
+ from pdt.utils import storage
118
+
119
+ store = storage.store()
120
+ with store.open("report.csv", "wb") as f:
121
+ f.write(b"name,count\n")
122
+ ```
123
+
124
+ `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.
125
+
126
+ Some apps need files from the last run: a Meltano bookmark, a database. Keep those in one local folder, and copy it down before the work and up after:
127
+
128
+ ```python
129
+ from pathlib import Path
130
+ from pdt.utils import storage
131
+
132
+ store = storage.store()
133
+ lease = store.pull("state/", Path(".pdt-state"))
134
+ # do the work; keep anything the next run needs inside .pdt-state/
135
+ store.push(Path(".pdt-state"), "state/", lease)
136
+ ```
137
+
138
+ `pull` locks the folder, so a second copy of your app cannot run at the same time and mix up the files. `push` checks that nobody else changed them, saves them, and unlocks. If a run crashes, the next one takes over the lock after 30 minutes.
139
+
140
+ 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`.
141
+
142
+ 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.
143
+
144
+ ## Running pdt from CI
145
+
146
+ Every command reads its values from the environment it runs in. A build server sets them as CI variables, and no `.env` file is needed.
147
+
148
+ - 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.
149
+ - `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"}`.
150
+ - 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.
151
+ - `pdt deploy APP` and `pdt destroy APP` ask before they change anything. Add `--yes` so they proceed without asking.
152
+
116
153
  ## Choosing where jobs run
117
154
 
118
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.
@@ -125,12 +162,14 @@ platform:
125
162
  subscription: 00000000-0000-0000-0000-000000000000
126
163
  region: eastus
127
164
  resource_group: pdt
128
- # Options: functions (default), container_apps
129
- runtime: functions
165
+ # Optional. An environment you already own, as <resource-group>/<name>.
166
+ # environment: my-group/my-environment
130
167
  ```
131
168
 
132
169
  `subscription` is optional. When it is missing or wrong, the deploy asks you to choose one.
133
170
 
171
+ Every pdt project in a subscription runs its jobs in one shared Container Apps environment per region, `pdt-<region>` in the resource group `pdt-shared`, because a subscription allows only a few environments. Destroying the last app that uses the environment removes it, and removes `pdt-shared` once it holds no environment. Set `environment` to use an environment you already have; pdt then never creates, changes, or deletes it.
172
+
134
173
  ### AWS
135
174
 
136
175
  ```yaml
@@ -139,10 +178,12 @@ platform:
139
178
  region: us-east-1
140
179
  # Written for you on the first deploy, from your credentials.
141
180
  account: "123456789012"
142
- # lambda (default) or fargate
143
- runtime: lambda
181
+ # Optional. Which profile in ~/.aws to use. Written for you if pdt has to ask.
182
+ profile: work
144
183
  ```
145
184
 
185
+ `profile` is optional. When it is missing, pdt uses `AWS_PROFILE`, or the only profile on your computer. When there are several and none is chosen, or the one in the file is not on this computer, the deploy lists them and asks you to choose, then writes your answer here.
186
+
146
187
  ### Google Cloud
147
188
 
148
189
  ```yaml
@@ -154,6 +195,25 @@ platform:
154
195
 
155
196
  `project` is optional. When it is missing or wrong, the deploy lists your projects and asks you to choose one, then writes your answer here.
156
197
 
198
+ ### Bringing your own Dockerfile
199
+
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.
201
+
202
+ 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
+
204
+ ```dockerfile
205
+ FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
206
+ COPY . /workspace
207
+ WORKDIR /workspace/my-app
208
+ ENV PDT_PROJECT=/workspace
209
+ RUN uv sync --script run.py
210
+ ENTRYPOINT ["uv", "run", "--script", "run.py"]
211
+ ```
212
+
213
+ 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`.
214
+
215
+ The Windows provider never builds an image, so `pdt validate` reports a Dockerfile in an app that uses it.
216
+
157
217
  ### Windows Task Scheduler
158
218
 
159
219
  ```yaml
@@ -213,7 +273,7 @@ An app is a folder with a `run.py` that has a `main()` function. It declares its
213
273
  ```python
214
274
  # /// script
215
275
  # requires-python = ">=3.12"
216
- # dependencies = ["pdt-cli[apps]==0.1.0"]
276
+ # dependencies = ["pdt-cli[apps]==0.1.2"]
217
277
  # ///
218
278
  from pathlib import Path
219
279
 
@@ -323,3 +383,15 @@ Leave the two SES key variables empty to use the standard AWS credential chain (
323
383
  Required environment variables:
324
384
 
325
385
  - PDT_RESEND_API_KEY
386
+
387
+ ## Logging
388
+
389
+ `pdt.utils.log` writes one log line per event:
390
+
391
+ ```python
392
+ from pdt.utils.log import die, log
393
+
394
+ log("info", "processed", rows=42)
395
+ ```
396
+
397
+ Levels are `debug`, `info`, `warning`, and `error`. Locally a line is human-readable text on stdout; with `LOG_FORMAT=json` it is one JSON object per line, which is also the default on Cloud Run.