dr-environment 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 (76) hide show
  1. dr_environment-0.1.0/.cursor/BUGBOT.md +67 -0
  2. dr_environment-0.1.0/.cursor/rules/test-convention.mdc +11 -0
  3. dr_environment-0.1.0/.github/CODEOWNERS +1 -0
  4. dr_environment-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +6 -0
  5. dr_environment-0.1.0/.github/dependabot.yml +33 -0
  6. dr_environment-0.1.0/.github/pip-audit-ignore.txt +21 -0
  7. dr_environment-0.1.0/.github/workflows/changelog-check.yml +22 -0
  8. dr_environment-0.1.0/.github/workflows/ci.yml +183 -0
  9. dr_environment-0.1.0/.github/workflows/release.yml +108 -0
  10. dr_environment-0.1.0/.github/workflows/secret-scan.yml +22 -0
  11. dr_environment-0.1.0/.github/workflows/version-check.yml +72 -0
  12. dr_environment-0.1.0/.github/workflows/workflow-pr-automation.yaml +66 -0
  13. dr_environment-0.1.0/.gitignore +217 -0
  14. dr_environment-0.1.0/.licenserc.yaml +66 -0
  15. dr_environment-0.1.0/.pre-commit-config.yaml +50 -0
  16. dr_environment-0.1.0/AGENTS.md +255 -0
  17. dr_environment-0.1.0/AUTHORS +2 -0
  18. dr_environment-0.1.0/CHANGELOG.md +36 -0
  19. dr_environment-0.1.0/CODE_OF_CONDUCT.md +76 -0
  20. dr_environment-0.1.0/CONTRIBUTING.md +44 -0
  21. dr_environment-0.1.0/LICENSE +201 -0
  22. dr_environment-0.1.0/NOTICE +43 -0
  23. dr_environment-0.1.0/PKG-INFO +216 -0
  24. dr_environment-0.1.0/PLUGIN_TESTING.md +89 -0
  25. dr_environment-0.1.0/README.md +182 -0
  26. dr_environment-0.1.0/SECURITY.md +25 -0
  27. dr_environment-0.1.0/docs/exec-env.png +0 -0
  28. dr_environment-0.1.0/docs/img/datarobot_logo.avif +0 -0
  29. dr_environment-0.1.0/pyproject.toml +98 -0
  30. dr_environment-0.1.0/src/dr_environment/__init__.py +14 -0
  31. dr_environment-0.1.0/src/dr_environment/cli.py +91 -0
  32. dr_environment-0.1.0/src/dr_environment/recipe/__init__.py +19 -0
  33. dr_environment-0.1.0/src/dr_environment/recipe/build.py +99 -0
  34. dr_environment-0.1.0/src/dr_environment/recipe/cache/__init__.py +15 -0
  35. dr_environment-0.1.0/src/dr_environment/recipe/cache/stages.py +151 -0
  36. dr_environment-0.1.0/src/dr_environment/recipe/discover.py +88 -0
  37. dr_environment-0.1.0/src/dr_environment/recipe/hooks.py +57 -0
  38. dr_environment-0.1.0/src/dr_environment/recipe/layout.py +109 -0
  39. dr_environment-0.1.0/src/dr_environment/recipe/manifests.py +60 -0
  40. dr_environment-0.1.0/src/dr_environment/recipe/models.py +62 -0
  41. dr_environment-0.1.0/src/dr_environment/recipe/render.py +139 -0
  42. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/00-base.fragment.j2 +96 -0
  43. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/01-user.fragment.j2 +39 -0
  44. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/02-versions.fragment.j2 +141 -0
  45. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/03-build-deps.fragment.j2 +44 -0
  46. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/04-kernel.fragment.j2 +73 -0
  47. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/99-offline.fragment.j2 +98 -0
  48. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/build-deps/build-requirements.txt +11 -0
  49. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/agent/agent.py +67 -0
  50. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/agent/cgroup_watchers.py +181 -0
  51. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/common-user-limits.sh +38 -0
  52. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/extensions/README.md +2 -0
  53. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/extensions/dataframe_formatter.py +344 -0
  54. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/ipython_config.py +17 -0
  55. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/jupyter_kernel_gateway_config.py +33 -0
  56. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/kernel.json +15 -0
  57. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/notebooks-path.sh +17 -0
  58. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/requirements.txt +8 -0
  59. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/setup-caches.sh +49 -0
  60. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/setup-prompt.sh +20 -0
  61. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/setup-ssh.sh +42 -0
  62. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/setup-venv.sh +97 -0
  63. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/sshd_config +130 -0
  64. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/start_server_codespaces.sh +58 -0
  65. dr_environment-0.1.0/src/dr_environment/recipe/templates/docker_context/kernel/start_server_custom_model.sh +99 -0
  66. dr_environment-0.1.0/src/dr_environment/recipe/validate.py +158 -0
  67. dr_environment-0.1.0/src/dr_environment/recipe/versions.py +82 -0
  68. dr_environment-0.1.0/tests/__init__.py +15 -0
  69. dr_environment-0.1.0/tests/recipe/__init__.py +14 -0
  70. dr_environment-0.1.0/tests/recipe/test_discover.py +67 -0
  71. dr_environment-0.1.0/tests/recipe/test_layout.py +166 -0
  72. dr_environment-0.1.0/tests/recipe/test_render.py +270 -0
  73. dr_environment-0.1.0/tests/recipe/test_stages.py +81 -0
  74. dr_environment-0.1.0/tests/recipe/test_validate.py +49 -0
  75. dr_environment-0.1.0/tests/test_cli.py +42 -0
  76. dr_environment-0.1.0/uv.lock +400 -0
@@ -0,0 +1,67 @@
1
+ # BugBot Review Instructions
2
+
3
+ You are a code review bot. When reviewing pull requests in this repository, use the guideline below to identify issues and leave comments.
4
+
5
+ For each violation you detect, leave a comment with a clear title and a message referencing the relevant section of the guideline.
6
+
7
+ Full guideline source: https://datarobot.atlassian.net/wiki/spaces/BUZOK/pages/7305920528/REVIEW+BEFORE+COMMIT+Working+with+agentic+starter+application+and+its+components
8
+
9
+ ---
10
+
11
+ # General guidelines
12
+
13
+ The sections below are the parts of the shared "review before commit" guideline that apply here. The
14
+ sign-off matrix and the template rules in the source cover the agentic starter application and its
15
+ components, not this plugin.
16
+
17
+ ### B1. Assume public
18
+
19
+ This repository is public. Therefore:
20
+
21
+ - Do not include internal code, proprietary logic, or private repository references.
22
+ - Avoid leaking internal architecture, infrastructure details, or security mechanisms.
23
+ - Keep comments, README and any other communication language civil, polite, and free of internal references
24
+
25
+ ### B2. Update CHANGELOG
26
+
27
+ Use past tense
28
+
29
+ When CHANGELOG.md is present, and your change looks like it should be communicated with users, please add it.
30
+
31
+ Do not add every change: Added .woff2 and .woff and .js files to fastapi_server/static/.gitignore is not important to our users.
32
+
33
+ When bumping child components, list all the changes:
34
+
35
+ ```
36
+ - Updated agent component from 11.6.3 to 11.6.10:
37
+ - Migrated to a new interface
38
+ - Refactored agent infra concurrency configuration
39
+ - Fixed header forwarding in LangGraph
40
+ - Added debugpy for debugging in IDE
41
+ ```
42
+
43
+ ---
44
+
45
+ # Repo-specific: dr-environment
46
+
47
+ The sections above come from the shared "review before commit" guideline. The following applies
48
+ **only to this repository** (`datarobot-oss/dr-environment`).
49
+
50
+ ## Offline and air-gapped builds
51
+
52
+ This plugin renders the execution environment that air-gapped installs build from, so a build step
53
+ that reaches the network at deploy time is a defect rather than a slow path. When reviewing, flag:
54
+
55
+ - A new fetch (`curl`, `pip install`, `uv sync`, `apk add`) in a Dockerfile fragment with no baked
56
+ cache, wheelhouse entry, or find-links source behind it.
57
+ - A fetch inside a command substitution. `$(curl ...)` discards curl's exit status, so a failed
58
+ download still produces a green build and an image missing the tool.
59
+ - Changes to `UV_OFFLINE`, `UV_FROZEN`, `UV_FIND_LINKS`, `UV_PYTHON_PREFERENCE`,
60
+ `NPM_CONFIG_OFFLINE`, or `GOPROXY` with no note on what they break offline.
61
+
62
+ ## Pins that ship to customers
63
+
64
+ `versions.yaml`, `kernel/requirements.txt`, and `build-deps/build-requirements.txt` are baked into
65
+ every generated image, so a bump in any of them reaches customer environments and deserves the same
66
+ scrutiny as a runtime dependency. A `versions.yaml` `minimum-version` is interpolated unquoted into
67
+ Dockerfile `ARG` and `RUN` lines, so it must stay a version literal.
@@ -0,0 +1,11 @@
1
+ ---
2
+ globs: tests/*
3
+ alwaysApply: false
4
+ ---
5
+ Follow GIVEN–WHEN–THEN style in tests:
6
+
7
+ - GIVEN: preconditions/state
8
+ - WHEN: the behavior under test
9
+ - THEN: expected outcomes/assertions
10
+
11
+ For background, see Martin Fowler’s article on GivenWhenThen: https://martinfowler.com/bliki/GivenWhenThen.html
@@ -0,0 +1 @@
1
+ * @datarobot-oss/buzok @datarobot-oss/mcp
@@ -0,0 +1,6 @@
1
+ # This repository is public. Do not put here any private DataRobot or customer's data: code, datasets, model artifacts, .etc.
2
+
3
+ ## Summary
4
+
5
+
6
+ ## Rationale
@@ -0,0 +1,33 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "uv"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ open-pull-requests-limit: 5
8
+ labels:
9
+ - "dependencies"
10
+
11
+ - package-ecosystem: "github-actions"
12
+ directory: "/"
13
+ schedule:
14
+ interval: "weekly"
15
+ open-pull-requests-limit: 5
16
+ labels:
17
+ - "dependencies"
18
+ groups:
19
+ actions:
20
+ patterns:
21
+ - "*"
22
+
23
+ # The two pinned manifests baked into every generated image. Dependabot's uv ecosystem
24
+ # does not read bare requirements.txt, so these need their own pip entries.
25
+ - package-ecosystem: "pip"
26
+ directories:
27
+ - "/src/dr_environment/recipe/templates/docker_context/kernel"
28
+ - "/src/dr_environment/recipe/templates/docker_context/build-deps"
29
+ schedule:
30
+ interval: "weekly"
31
+ open-pull-requests-limit: 5
32
+ labels:
33
+ - "dependencies"
@@ -0,0 +1,21 @@
1
+ # Advisory IDs the `audit` CI job skips, one per line. Blank lines and `#` comments ignored.
2
+ #
3
+ # The job fails on any known vulnerability in this project's closure or in either image
4
+ # manifest's resolved closure. Most of those packages are transitive and not pinned here, so an
5
+ # advisory published upstream can turn every open PR red before a fix is available. Listing an ID
6
+ # here unblocks PRs while the bump is worked out; it is not a way to close a finding.
7
+ #
8
+ # Each entry needs the ID, why it is being skipped, and what removes it. Review on every bump.
9
+ #
10
+ # Format:
11
+ # GHSA-xxxx-xxxx-xxxx # <package>: <why skipped> - remove when <condition>
12
+ #
13
+ # Example: pip-audit prints the ID and whether a fix exists:
14
+ #
15
+ # Name Version ID Fix Versions
16
+ # -------- -------- -------------------- ------------
17
+ # urllib3 2.2.1 GHSA-34jh-p97f-mpxf
18
+ #
19
+ # No fix version, and urllib3 is transitive via datarobot, so there is nothing here to bump. Add:
20
+ #
21
+ # GHSA-34jh-p97f-mpxf # urllib3 (transitive via datarobot): no fixed release yet - remove when datarobot pins a fixed urllib3
@@ -0,0 +1,22 @@
1
+ name: Changelog
2
+
3
+ # Fails a pull request that did not update CHANGELOG.md, bots included: a dependency
4
+ # bump can change what ships. Waivable with the `skip-changelog` label.
5
+
6
+ # `labeled` and `unlabeled` are listed because naming any type replaces the defaults.
7
+ on:
8
+ pull_request:
9
+ types: [opened, synchronize, reopened, labeled, unlabeled]
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ changelog:
16
+ uses: datarobot-oss/github-actions/.github/workflows/check-changelog.yaml@0.0.24
17
+ permissions:
18
+ contents: read
19
+ with:
20
+ changelog_path: CHANGELOG.md
21
+ skip_label: skip-changelog
22
+ skip_bot_authors: false
@@ -0,0 +1,183 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [ main ]
7
+
8
+ concurrency:
9
+ group: ${{ github.workflow }}-${{ github.ref }}
10
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ lint:
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ contents: read
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
23
+
24
+ - name: Set up Python
25
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
26
+ with:
27
+ python-version: '3.12'
28
+
29
+ - name: Install uv
30
+ uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
31
+
32
+ - name: Cache uv download cache
33
+ uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
34
+ with:
35
+ path: ~/.cache/uv
36
+ key: uv-${{ runner.os }}-3.12-${{ hashFiles('uv.lock') }}
37
+ restore-keys: |
38
+ uv-${{ runner.os }}-3.12-
39
+
40
+ - name: Check uv.lock is current
41
+ run: uv lock --check
42
+
43
+ - name: Install dependencies
44
+ run: uv sync --extra dev
45
+
46
+ - name: Lint (ruff format)
47
+ run: uv run ruff format --check src tests
48
+
49
+ - name: Lint (ruff check)
50
+ run: uv run ruff check src tests
51
+
52
+ - name: Type check (mypy)
53
+ run: uv run mypy src
54
+
55
+ test:
56
+ runs-on: ubuntu-latest
57
+ strategy:
58
+ fail-fast: false
59
+ matrix:
60
+ python-version: [ '3.11', '3.12', '3.13' ]
61
+ permissions:
62
+ contents: read
63
+ steps:
64
+ - name: Checkout
65
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
66
+
67
+ - name: Set up Python
68
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
69
+ with:
70
+ python-version: ${{ matrix.python-version }}
71
+
72
+ - name: Install uv
73
+ uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
74
+
75
+ - name: Cache uv download cache
76
+ uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
77
+ with:
78
+ path: ~/.cache/uv
79
+ key: uv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}
80
+ restore-keys: |
81
+ uv-${{ runner.os }}-${{ matrix.python-version }}-
82
+
83
+ - name: Install dependencies
84
+ run: uv sync --extra dev
85
+
86
+ - name: Run tests
87
+ run: uv run pytest -q --junitxml=test_results/test-results.xml
88
+
89
+ - name: Upload test results
90
+ if: always()
91
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
92
+ with:
93
+ name: junit-py${{ matrix.python-version }}
94
+ path: test_results/test-results.xml
95
+ if-no-files-found: ignore
96
+
97
+ build:
98
+ runs-on: ubuntu-latest
99
+ permissions:
100
+ contents: read
101
+ steps:
102
+ - name: Checkout
103
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
104
+
105
+ - name: Set up Python
106
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
107
+ with:
108
+ python-version: '3.12'
109
+
110
+ - name: Install uv
111
+ uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
112
+
113
+ - name: Install dependencies
114
+ run: uv sync --extra dev
115
+
116
+ - name: Build package
117
+ run: uv build
118
+
119
+ - name: Validate package metadata
120
+ run: uvx twine check dist/*
121
+
122
+ - name: Upload dist
123
+ if: always()
124
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
125
+ with:
126
+ name: dist
127
+ path: dist
128
+ if-no-files-found: ignore
129
+
130
+ licenses:
131
+ runs-on: ubuntu-latest
132
+ permissions:
133
+ contents: read
134
+ steps:
135
+ - name: Checkout
136
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
137
+
138
+ - name: Check copyright headers
139
+ run: |
140
+ docker run --rm -v "$(pwd)":/github/workspace \
141
+ ghcr.io/apache/skywalking-eyes/license-eye:785bb7f3810572d6912666b4f64bad28e4360799 \
142
+ -v info -c .licenserc.yaml header check
143
+
144
+ audit:
145
+ runs-on: ubuntu-latest
146
+ permissions:
147
+ contents: read
148
+ steps:
149
+ - name: Checkout
150
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
151
+
152
+ - name: Install uv
153
+ uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
154
+
155
+ # Covers this project's own dependencies and the two pinned manifests that are baked
156
+ # into every generated image, which Dependabot's uv ecosystem cannot read.
157
+ - name: Audit dependencies for known CVEs
158
+ run: |
159
+ # Advisories listed in .github/pip-audit-ignore.txt are skipped, so a CVE with no fix
160
+ # available yet does not block every open PR. That file carries the rationale per entry.
161
+ IDS=()
162
+ while read -r id _; do
163
+ [ -n "$id" ] && IDS+=("$id")
164
+ done < <(sed 's/#.*//' .github/pip-audit-ignore.txt | grep -vE '^[[:space:]]*$')
165
+ IGNORE=()
166
+ for id in "${IDS[@]}"; do IGNORE+=(--ignore-vuln "$id"); done
167
+ echo "Skipping ${#IDS[@]} advisory ID(s) from .github/pip-audit-ignore.txt"
168
+ # uv.lock already resolves this project's full closure, so export and audit directly.
169
+ uv export --frozen --no-hashes --no-dev --no-emit-project -o "${RUNNER_TEMP}/project.txt"
170
+ echo "::group::pip-audit project"
171
+ uvx pip-audit==2.9.0 --disable-pip --no-deps "${IGNORE[@]}" -r "${RUNNER_TEMP}/project.txt"
172
+ echo "::endgroup::"
173
+ # The image manifests list direct pins only, but the build installs their transitive
174
+ # closures. Resolve each against the image's Python before auditing, or CVEs in
175
+ # transitive packages ship without failing CI.
176
+ for f in src/dr_environment/recipe/templates/docker_context/kernel/requirements.txt \
177
+ src/dr_environment/recipe/templates/docker_context/build-deps/build-requirements.txt; do
178
+ resolved="${RUNNER_TEMP}/$(basename "$f").resolved.txt"
179
+ uv pip compile --python-version 3.11 --no-header --no-annotate -o "$resolved" "$f"
180
+ echo "::group::pip-audit $f"
181
+ uvx pip-audit==2.9.0 --disable-pip --no-deps "${IGNORE[@]}" -r "$resolved"
182
+ echo "::endgroup::"
183
+ done
@@ -0,0 +1,108 @@
1
+ name: Release
2
+
3
+ # Tags the version `pyproject.toml` declares and publishes that build to PyPI.
4
+ # Recover a failed publish with "Re-run failed jobs", never "Re-run all jobs": the tag
5
+ # already exists by then, so the publish is skipped rather than retried.
6
+ on:
7
+ push:
8
+ branches: [ main ]
9
+
10
+ # `cancel-in-progress: false` so a second merge waits, then finds this run's tag. Only one
11
+ # run queues, so a third merge inside one release evicts the second; re-run it on that sha.
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: false
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ # Ahead of the tag: tagging first would strand the version, since an existing tag
21
+ # releases nothing.
22
+ build:
23
+ runs-on: ubuntu-latest
24
+ permissions:
25
+ contents: read
26
+ outputs:
27
+ version: ${{ steps.version.outputs.version }}
28
+ steps:
29
+ - name: Checkout
30
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
31
+
32
+ - name: Set up Python
33
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
34
+ with:
35
+ python-version: '3.12'
36
+
37
+ - name: Read version from pyproject.toml
38
+ id: version
39
+ uses: datarobot-oss/github-actions/actions/read-pyproject-version@0.0.24
40
+
41
+ - name: Install uv
42
+ uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
43
+
44
+ - name: Install dependencies
45
+ run: uv sync --extra dev
46
+
47
+ # Gates the publish, not the merge: `ci.yml` covers this commit on its own trigger.
48
+ # Tests only; lint and audit stay there. A release can be yanked, never replaced.
49
+ - name: Run tests
50
+ run: uv run pytest
51
+
52
+ - name: Build package
53
+ run: uv build
54
+
55
+ - name: Validate package metadata
56
+ run: uvx twine check dist/*
57
+
58
+ - name: Upload dist
59
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
60
+ with:
61
+ name: dist
62
+ path: dist
63
+
64
+ release:
65
+ needs: build
66
+ uses: datarobot-oss/github-actions/.github/workflows/create-release-on-merge.yaml@0.0.24
67
+ with:
68
+ version: ${{ needs.build.outputs.version }}
69
+ permissions:
70
+ contents: write
71
+ pull-requests: read
72
+
73
+ # A merge without a bump cuts no tag, so there is nothing to publish.
74
+ publish:
75
+ needs: [ build, release ]
76
+ if: needs.release.outputs.released == 'true'
77
+ runs-on: ubuntu-latest
78
+ # Trusted publishing: PyPI checks this environment claim, so no token is stored.
79
+ environment: pypi
80
+ permissions:
81
+ contents: read
82
+ id-token: write
83
+ steps:
84
+ # The release workflow passes no `target_commitish`, so the tag lands on main's
85
+ # tip, which a merge landing mid-run has already moved. Checked before the upload:
86
+ # a stale tag is deletable, a wheel on PyPI is yank-only.
87
+ - name: Verify the tag is the commit that was built
88
+ env:
89
+ GH_TOKEN: ${{ github.token }}
90
+ TAG: ${{ needs.build.outputs.version }}
91
+ run: |
92
+ TAG_SHA="$(gh api "repos/$GITHUB_REPOSITORY/commits/$TAG" --jq .sha)"
93
+ if [ "$TAG_SHA" != "$GITHUB_SHA" ]; then
94
+ echo "::error::Tag $TAG is $TAG_SHA, not the built $GITHUB_SHA."
95
+ exit 1
96
+ fi
97
+
98
+ - name: Download dist
99
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
100
+ with:
101
+ name: dist
102
+ path: dist
103
+
104
+ - name: Install uv
105
+ uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
106
+
107
+ - name: Publish to PyPI
108
+ run: uv publish --trusted-publishing always
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: secret-scan
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ permissions:
8
+ contents: read
9
+ jobs:
10
+ gitleaks:
11
+ name: "gitleaks"
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
15
+ with:
16
+ fetch-depth: 0
17
+
18
+ - name: gitleaks
19
+ run: |
20
+ docker run --rm -v "$(pwd)":/repo -w /repo \
21
+ ghcr.io/gitleaks/gitleaks:v8.29.1 \
22
+ detect --source=/repo --redact --verbose --no-banner
@@ -0,0 +1,72 @@
1
+ name: Version
2
+
3
+ # A merge releases the version `pyproject.toml` declares, so a pull request that changes
4
+ # what ships has to bump it. Waivable with the `skip-version-bump` label.
5
+
6
+ # `labeled` and `unlabeled` are listed because naming any type replaces the defaults.
7
+ on:
8
+ pull_request:
9
+ types: [opened, synchronize, reopened, labeled, unlabeled]
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ version:
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ contents: read
19
+ steps:
20
+ - name: Checkout
21
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
22
+ with:
23
+ # Full history, for the base branch's pyproject.toml.
24
+ fetch-depth: 0
25
+
26
+ - name: Require a version bump when what ships changes
27
+ env:
28
+ BASE: origin/${{ github.base_ref }}
29
+ SKIP: ${{ contains(github.event.pull_request.labels.*.name, 'skip-version-bump') }}
30
+ run: |
31
+ set -euo pipefail
32
+
33
+ # In the step, not a job-level `if`: branch protection counts a skipped job as
34
+ # passing, so the waiver has to run and report rather than skip.
35
+ if [ "$SKIP" = 'true' ]; then
36
+ echo "Waived by skip-version-bump."
37
+ exit 0
38
+ fi
39
+
40
+ # Paths that reach a consumer; `pyproject.toml` because its metadata ships in METADATA.
41
+ CHANGED="$(git -c core.quotePath=false diff --name-only --no-renames "${BASE}...HEAD")"
42
+ if ! grep -qE '^(src/|pyproject\.toml$|README\.md$|LICENSE$|NOTICE$|AUTHORS$)' <<<"$CHANGED"; then
43
+ echo "Nothing that ships changed."
44
+ exit 0
45
+ fi
46
+
47
+ # Parsed, not grepped: `version` appears in other tables and spacing around `=` varies.
48
+ read_version() {
49
+ git show "$1:pyproject.toml" \
50
+ | python3 -c 'import sys, tomllib; print(tomllib.load(sys.stdin.buffer)["project"]["version"])'
51
+ }
52
+ BASE_VERSION="$(read_version "$BASE")"
53
+ HEAD_VERSION="$(read_version HEAD)"
54
+
55
+ if [ "$BASE_VERSION" = "$HEAD_VERSION" ]; then
56
+ echo "::error::pyproject.toml still declares $BASE_VERSION. Bump it, or apply skip-version-bump."
57
+ exit 1
58
+ fi
59
+
60
+ # Checked here, not at release time: a bad version would otherwise redden main.
61
+ if ! printf '%s' "$HEAD_VERSION" | grep -qE '^(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*)){2}$'; then
62
+ echo "::error::'$HEAD_VERSION' is not a plain X.Y.Z; the build backend strips leading zeros."
63
+ exit 1
64
+ fi
65
+
66
+ LATEST="$(printf '%s\n%s\n' "$BASE_VERSION" "$HEAD_VERSION" | sort -V | tail -1)"
67
+ if [ "$LATEST" != "$HEAD_VERSION" ]; then
68
+ echo "::error::pyproject.toml goes backwards, from $BASE_VERSION to $HEAD_VERSION."
69
+ exit 1
70
+ fi
71
+
72
+ echo "Version bumped from $BASE_VERSION to $HEAD_VERSION."
@@ -0,0 +1,66 @@
1
+ name: PR Automation
2
+
3
+ # Reviewer-facing helpers: labels approved pull requests `00 - Reviewed`, comments a
4
+ # Jira link, and posts to Slack. Both Slack jobs need `SLACK_WEBHOOK_URL`.
5
+
6
+ on:
7
+ pull_request_review:
8
+ types: [submitted]
9
+ pull_request:
10
+ types: [opened, edited, labeled]
11
+ schedule:
12
+ - cron: '0 * * * *'
13
+ workflow_dispatch:
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ # On an approving review: label the PR "00 - Reviewed".
20
+ mark-reviewed:
21
+ if: github.event_name == 'pull_request_review' && github.event.review.state == 'approved'
22
+ uses: datarobot-oss/github-actions/.github/workflows/mark-pr-reviewed.yaml@0.0.24
23
+ with:
24
+ pr_number: ${{ github.event.pull_request.number }}
25
+ permissions:
26
+ pull-requests: write
27
+
28
+ # On the `00 - Ready for Review` label: announce the pull request in Slack.
29
+ notify-ready-for-review:
30
+ if: github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == '00 - Ready for Review'
31
+ uses: datarobot-oss/github-actions/.github/workflows/mark-pr-to-review.yaml@0.0.24
32
+ with:
33
+ pr_number: ${{ github.event.pull_request.number }}
34
+ pr_url: ${{ github.event.pull_request.html_url }}
35
+ pr_title: ${{ github.event.pull_request.title }}
36
+ pr_author: ${{ github.event.pull_request.user.login }}
37
+ pr_additions: ${{ github.event.pull_request.additions }}
38
+ pr_deletions: ${{ github.event.pull_request.deletions }}
39
+ repo_name: ${{ github.repository }}
40
+ secrets:
41
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
42
+ permissions:
43
+ pull-requests: write
44
+
45
+ # On PR open/edit: comment a link to any Jira ticket in the title or branch.
46
+ add-jira-link:
47
+ if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'edited')
48
+ uses: datarobot-oss/github-actions/.github/workflows/add-jira-link.yaml@0.0.24
49
+ with:
50
+ jira_base_url: 'https://datarobot.atlassian.net/browse'
51
+ pr_title: ${{ github.event.pull_request.title }}
52
+ pr_number: ${{ github.event.pull_request.number }}
53
+ pr_branch: ${{ github.head_ref }}
54
+ permissions:
55
+ pull-requests: write
56
+
57
+ # On schedule or dispatch: post a digest of what is still awaiting review.
58
+ notify-slack-digest:
59
+ if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
60
+ uses: datarobot-oss/github-actions/.github/workflows/notify-slack.yaml@0.0.24
61
+ secrets:
62
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
63
+ permissions:
64
+ contents: read
65
+ pull-requests: write
66
+ statuses: read