prefector 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. prefector-0.1.0/.github/workflows/lint.yml +37 -0
  2. prefector-0.1.0/.github/workflows/pypi-publish.yml +51 -0
  3. prefector-0.1.0/.github/workflows/test.yml +30 -0
  4. prefector-0.1.0/.gitignore +12 -0
  5. prefector-0.1.0/.pre-commit-config.yaml +32 -0
  6. prefector-0.1.0/.talismanrc +5 -0
  7. prefector-0.1.0/PKG-INFO +285 -0
  8. prefector-0.1.0/README.md +266 -0
  9. prefector-0.1.0/poetry.lock +4494 -0
  10. prefector-0.1.0/pyproject.toml +55 -0
  11. prefector-0.1.0/src/prefector/__init__.py +5 -0
  12. prefector-0.1.0/src/prefector/blocks/__init__.py +0 -0
  13. prefector-0.1.0/src/prefector/blocks/base.py +137 -0
  14. prefector-0.1.0/src/prefector/blocks/cli.py +190 -0
  15. prefector-0.1.0/src/prefector/blocks/options.py +39 -0
  16. prefector-0.1.0/src/prefector/blocks/sources.py +237 -0
  17. prefector-0.1.0/src/prefector/cli.py +20 -0
  18. prefector-0.1.0/src/prefector/deployments/__init__.py +0 -0
  19. prefector-0.1.0/src/prefector/deployments/base.py +160 -0
  20. prefector-0.1.0/src/prefector/deployments/cli.py +177 -0
  21. prefector-0.1.0/src/prefector/deployments/options.py +65 -0
  22. prefector-0.1.0/src/prefector/errors.py +21 -0
  23. prefector-0.1.0/src/prefector/prefect_connection/__init__.py +0 -0
  24. prefector-0.1.0/src/prefector/prefect_connection/connection.py +107 -0
  25. prefector-0.1.0/src/prefector/prefect_connection/options.py +63 -0
  26. prefector-0.1.0/tests/blocks/__init__.py +0 -0
  27. prefector-0.1.0/tests/blocks/test_block_sources.py +358 -0
  28. prefector-0.1.0/tests/blocks/test_blocks_base.py +89 -0
  29. prefector-0.1.0/tests/blocks/test_blocks_cli.py +67 -0
  30. prefector-0.1.0/tests/cli/__init__.py +0 -0
  31. prefector-0.1.0/tests/cli/test_blocks_cli.py +23 -0
  32. prefector-0.1.0/tests/cli/test_cli.py +78 -0
  33. prefector-0.1.0/tests/cli/test_prefect_options.py +138 -0
  34. prefector-0.1.0/tests/conftest.py +88 -0
  35. prefector-0.1.0/tests/deployments/__init__.py +0 -0
  36. prefector-0.1.0/tests/deployments/test_deployments_base.py +242 -0
  37. prefector-0.1.0/tests/deployments/test_deployments_cli.py +113 -0
  38. prefector-0.1.0/tests/prefect_connection/__init__.py +0 -0
  39. prefector-0.1.0/tests/prefect_connection/test_prefect_connection.py +239 -0
@@ -0,0 +1,37 @@
1
+ name: Lint
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ jobs:
10
+ ruff:
11
+ name: Ruff
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Check out repository
16
+ uses: actions/checkout@v6
17
+
18
+ - name: Run ruff check
19
+ uses: astral-sh/ruff-action@v3
20
+
21
+ - name: Run ruff format
22
+ uses: astral-sh/ruff-action@v3
23
+ with:
24
+ args: "format --check"
25
+
26
+ shellcheck:
27
+ name: Shellcheck
28
+ runs-on: ubuntu-latest
29
+
30
+ steps:
31
+ - name: Check out repository
32
+ uses: actions/checkout@v6
33
+
34
+ - name: Run Shellcheck
35
+ uses: ludeeus/action-shellcheck@2.0.0
36
+ env:
37
+ SHELLCHECK_OPTS: --severity=warning --external-sources
@@ -0,0 +1,51 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ release-build:
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.x"
20
+
21
+ - name: Build release distributions
22
+ run: |
23
+ python -m pip install build
24
+ python -m build
25
+
26
+ - name: Upload distributions
27
+ uses: actions/upload-artifact@v4
28
+ with:
29
+ name: release-dists
30
+ path: dist/
31
+
32
+ pypi-publish:
33
+ runs-on: ubuntu-latest
34
+ needs:
35
+ - release-build
36
+ permissions:
37
+ id-token: write
38
+ environment:
39
+ name: pypi
40
+ # Add pyPI URL when known
41
+ steps:
42
+ - name: Retrieve release distributions
43
+ uses: actions/download-artifact@v4
44
+ with:
45
+ name: release-dists
46
+ path: dist/
47
+
48
+ - name: Publish release distributions to PyPI
49
+ uses: pypa/gh-action-pypi-publish@release/v1
50
+ with:
51
+ packages-dir: dist/
@@ -0,0 +1,30 @@
1
+ name: Test
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ jobs:
10
+ pytest:
11
+ name: Pytest
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Check out repository
16
+ uses: actions/checkout@v6
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.13"
22
+
23
+ - name: Install Poetry
24
+ uses: snok/install-poetry@v1
25
+
26
+ - name: Install dependencies
27
+ run: poetry install
28
+
29
+ - name: Run tests
30
+ run: poetry run pytest
@@ -0,0 +1,12 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+
5
+ # IDEs
6
+ .idea
7
+
8
+ # envs
9
+ venv/
10
+ .venv/
11
+
12
+ .coverage
@@ -0,0 +1,32 @@
1
+ default_language_version:
2
+ python: python3.13
3
+ repos:
4
+ - repo: local
5
+ hooks:
6
+ - id: poetry-lock
7
+ name: "Poetry lock"
8
+ entry: bash -c 'poetry lock'
9
+ language: system
10
+ always_run: true
11
+ - repo: https://github.com/thoughtworks/talisman
12
+ rev: "v1.32.0"
13
+ hooks:
14
+ - id: talisman-commit
15
+ name: "Talisman (secrets detection)"
16
+ entry: cmd --githook pre-commit
17
+ - repo: https://github.com/jumanjihouse/pre-commit-hooks
18
+ rev: 3.0.0
19
+ hooks:
20
+ - id: shellcheck
21
+ name: "Shellcheck (shell script checker)"
22
+ args:
23
+ - --severity=warning
24
+ - --external-sources
25
+ - repo: https://github.com/astral-sh/ruff-pre-commit
26
+ rev: v0.15.8
27
+ hooks:
28
+ - id: ruff
29
+ name: "Ruff (Python linting and import sorting)"
30
+ args: ["--fix"]
31
+ - id: ruff-format
32
+ name: "Ruff (Python formatter)"
@@ -0,0 +1,5 @@
1
+ threshold: medium
2
+ fileignoreconfig:
3
+ - filename: poetry.lock
4
+ allowed_patterns:
5
+ - ".*"
@@ -0,0 +1,285 @@
1
+ Metadata-Version: 2.4
2
+ Name: prefector
3
+ Version: 0.1.0
4
+ Summary: Reusable Prefect block and deployment CLI.
5
+ Requires-Python: <3.14,>=3.11
6
+ Requires-Dist: click-option-group<0.6.0,>=0.5.9
7
+ Requires-Dist: prefect-aws<0.8.0,>=0.6.9
8
+ Requires-Dist: prefect<4,>=3
9
+ Requires-Dist: pydantic-settings
10
+ Requires-Dist: pydantic>=2
11
+ Requires-Dist: pyyaml~=6.0.3
12
+ Requires-Dist: requests~=2.33.1
13
+ Requires-Dist: rich
14
+ Provides-Extra: all
15
+ Requires-Dist: keeper-secrets-manager-core; extra == 'all'
16
+ Provides-Extra: keeper
17
+ Requires-Dist: keeper-secrets-manager-core; extra == 'keeper'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Prefector
21
+
22
+ [![Tests](https://github.com/sanger-pathogens/prefector/actions/workflows/test.yml/badge.svg)](https://github.com/sanger-pathogens/prefector/actions/workflows/test.yml)
23
+
24
+ Reusable CLI helpers for deploying Prefect blocks and deployments from downstream
25
+ project specs.
26
+
27
+ ## Install
28
+
29
+ Install `prefector` into the same Python environment as the block specs, flow
30
+ modules, and Prefect collection packages it needs to import.
31
+
32
+ ```bash
33
+ pip install prefector
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ```bash
39
+ prefector blocks list --blocks-dir path/to/block/specs
40
+ prefector blocks deploy --blocks-dir path/to/block/specs --api-url "$PREFECT_API_URL"
41
+
42
+ prefector deployments list --deployments-dir path/to/deployment/specs
43
+
44
+ prefector deployments deploy \
45
+ --deployments-dir path/to/deployment/specs \
46
+ --images-manifest path/to/images.yaml \
47
+ --api-url "$PREFECT_API_URL" \
48
+ --work-pool default \
49
+ --image-prefix ghcr.io/example
50
+ ```
51
+
52
+ Block spec modules must expose `BLOCKS: list[prefector.BlockSpec]`.
53
+ Deployment specs are YAML files loaded as `prefector.DeploymentSpec`.
54
+
55
+ ## Block specs
56
+
57
+ Each block spec is a Python module in the `--blocks-dir` directory. A module
58
+ must expose a `BLOCKS` list of `BlockSpec` objects, each pairing a
59
+ `pydantic_settings.BaseSettings` subclass (which reads field values from the
60
+ environment) with a Prefect `Block` subclass.
61
+
62
+ ```python
63
+ # blocks/trino.py
64
+ from pydantic_settings import BaseSettings, SettingsConfigDict
65
+ from prefect_sqlalchemy import DatabaseCredentials, SyncDriver
66
+ from prefector.blocks.base import BlockSpec
67
+
68
+ class TrinoSettings(BaseSettings):
69
+ model_config = SettingsConfigDict(env_prefix="TRINO_")
70
+ user: str
71
+ password: str
72
+ host: str
73
+ port: int = 8080
74
+
75
+ class TrinoBlock(DatabaseCredentials):
76
+ ...
77
+
78
+ BLOCKS = [BlockSpec(name="trino-credentials", settings_cls=TrinoSettings, block_cls=TrinoBlock)]
79
+ ```
80
+
81
+ When `prefector blocks deploy` runs, it instantiates `TrinoSettings()` — which
82
+ reads `TRINO_USER`, `TRINO_PASSWORD`, etc. from the environment — and passes the
83
+ values to the block.
84
+
85
+ ## Block sources
86
+
87
+ A `block-sources.yaml` file lets different teams use the same block spec modules
88
+ while sourcing secret values from different backends (environment variables or
89
+ Keeper Secrets Manager) and with different field naming conventions.
90
+
91
+ **It is not required.** Blocks that already define their own `settings_cls` with
92
+ the right env prefix will continue to work exactly as before. Only add a
93
+ block-sources entry when you need to override where values come from.
94
+
95
+ ### Loading
96
+
97
+ Provide the file explicitly:
98
+
99
+ ```bash
100
+ prefector blocks deploy --blocks-dir path/to/specs --sources path/to/block-sources.yaml
101
+ ```
102
+
103
+ Or place it at `block-sources.yaml` inside `--blocks-dir` and it will be picked
104
+ up automatically with no extra flags needed.
105
+
106
+ ### File format
107
+
108
+ Three equivalent YAML shapes are accepted:
109
+
110
+ **Flat mapping** (simplest):
111
+ ```yaml
112
+ trino-credentials:
113
+ source: env
114
+ env_var_prefix: TRINO_
115
+ ```
116
+
117
+ **List** (useful when ordering matters or you prefer the list style):
118
+ ```yaml
119
+ - trino-credentials:
120
+ source: env
121
+ env_var_prefix: TRINO_
122
+ - other-block:
123
+ source: keeper
124
+ record_title: trino-credentials
125
+ ```
126
+
127
+ **`blocks:` wrapper** (same list, under an explicit key):
128
+ ```yaml
129
+ blocks:
130
+ - trino-credentials:
131
+ source: env
132
+ env_var_prefix: TRINO_
133
+ ```
134
+
135
+ ### Environment variable source
136
+
137
+ Reads block field values from environment variables.
138
+
139
+ ```yaml
140
+ trino-credentials:
141
+ source: env
142
+ env_var_prefix: TRINO_ # env vars are read as <prefix><field>
143
+ fields: # optional: override individual field names
144
+ user: USERNAME # reads TRINO_USERNAME into field `user`
145
+ password: PASSWORD # reads TRINO_PASSWORD into field `password`
146
+ # unlisted fields use the field name as-is: `host` -> TRINO_host
147
+ ```
148
+
149
+ The `fields` mapping is optional. Without it, each block field is read from
150
+ `<env_var_prefix><field_name>` (case-insensitive). Only add `fields` entries
151
+ when the env var suffix differs from the field name.
152
+
153
+ If a required env var is missing, the command exits with a clear error naming
154
+ the variable that needs to be set.
155
+
156
+ ### Keeper Secrets Manager source
157
+
158
+ Reads block field values from a record in Keeper Secrets Manager.
159
+
160
+ ```yaml
161
+ trino-credentials:
162
+ source: keeper
163
+ record_title: trino-credentials # required: base record title
164
+ record_prefix: dlh # optional: prepended before title
165
+ record_suffix: ${ENVIRONMENT} # optional: appended after title
166
+ separator: ":" # optional: joins the parts (default: ":"); must be quoted in YAML
167
+ ksm_token: ${KSM_TOKEN} # optional: one-time token; falls back to KSM_CONFIG env var
168
+ fields: # optional: map block field -> KSM field title
169
+ user: login # reads KSM field "login" into block field `user`
170
+ # unlisted fields use the field name as-is
171
+ ```
172
+
173
+ The full record title is assembled as
174
+ `<record_prefix><separator><record_title><separator><record_suffix>`, with any
175
+ absent components skipped cleanly (no leading or trailing separator):
176
+
177
+ The Keeper SDK (`keeper-secrets-manager-core`) must be installed to read values from Keeper. The
178
+ extra `prefector[keeper]` provides this.
179
+
180
+ ### Environment variable substitution
181
+
182
+ Any string value in `block-sources.yaml` may use `${VAR_NAME}` syntax.
183
+ Substitution happens at build time (when `prefector blocks deploy` runs), so
184
+ you can parameterise record names, prefixes, or tokens from CI environment
185
+ variables:
186
+
187
+ ```yaml
188
+ trino-credentials:
189
+ source: keeper
190
+ record_title: trino-credentials
191
+ record_suffix: ${ENVIRONMENT} # e.g. resolves to "prod" or "staging"
192
+ ksm_token: ${KSM_TOKEN}
193
+ ```
194
+
195
+ All referenced variables must be set at deploy time, or the command will exit
196
+ with an error naming the missing variable.
197
+
198
+ ## Deployment spec
199
+
200
+ Each deployment is a YAML file. All fields except `name`, `flow`, and `image_key` are optional.
201
+
202
+ ```yaml
203
+ name: my_deployment
204
+ flow: flows.my_module:my_flow # <module>:<function> format
205
+ image_key: flow_runtime # key from images manifest
206
+
207
+ cron: "0 6 * * *" # standard cron expression
208
+ tags:
209
+ - project_name
210
+ - bronze
211
+ parameters:
212
+ retries: 3
213
+ bucket:
214
+ block: my-s3-bucket # load a Prefect block by name at run time
215
+ env:
216
+ ENVIRONMENT: ${ENVIRONMENT} # resolved from the environment at deploy time
217
+ LOG_LEVEL: INFO
218
+ ```
219
+
220
+ ### Environment variable substitution
221
+
222
+ Values in the form `${VAR_NAME}` are replaced with the corresponding environment
223
+ variable when the spec is loaded. This happens at deploy time (e.g. in CI), not
224
+ at flow run time.
225
+
226
+ ```yaml
227
+ env:
228
+ COMMIT_SHA: ${CI_COMMIT_SHORT_SHA}
229
+ PROJECT: ${PROJECT_NAME}
230
+ ```
231
+
232
+ All referenced variables must be set when `prefector deployments deploy` runs, or
233
+ the command will exit with an error naming the missing variable.
234
+
235
+ **Using environment variables in the deployment spec:**
236
+
237
+ - Only `${VAR}` brace syntax is supported. A bare `$VAR` is left as-is.
238
+ - Substitution happens on the raw text before YAML parsing. If a variable value
239
+ contains YAML special characters (`:`, `{`, `}`, `#`), it can produce invalid
240
+ YAML. Quote the value to be safe:
241
+ ```yaml
242
+ env:
243
+ LABEL: "${MY_LABEL}"
244
+ ```
245
+ - Resolved values are stored in Prefect as `job_variables` and are visible in the
246
+ Prefect UI. Avoid substituting secrets this way; use Prefect blocks instead.
247
+ - Environment variables are resolved only for deployments that are actually being
248
+ deployed. Untargeted deployments (filtered by `--target`) and the `list`
249
+ command do not require any variables to be set.
250
+
251
+ ## Development
252
+
253
+ Setup local environment
254
+
255
+ Install project dependencies:
256
+
257
+ ```bash
258
+ poetry env use 3.12
259
+ source .venv/bin/activate
260
+ poetry install --with dev
261
+ ```
262
+
263
+ Set up pre-commit hooks and linting:
264
+
265
+ ```bash
266
+ pre-commit install
267
+ ```
268
+
269
+ This will run pre-commit hooks on every commit. To run pre-commit manually, use
270
+
271
+ ```bash
272
+ pre commit run -a
273
+ ```
274
+
275
+ Run tests with:
276
+
277
+ ```bash
278
+ pytest
279
+ ```
280
+
281
+ With coverage:
282
+
283
+ ```bash
284
+ pytest --cov=src/prefector
285
+ ```