Skip to content

Commit e50dae5

Browse files
authored
Lockfile linting and dependency updates (#2434)
## Changes This primary purpose of this PR is: - Fixing some changes to `uv.lock` that were inadvertently introduced in #2151. - Updating our CI to detect issues in PRs relating to the lock-files. The intention within this repo is: - The lock-files should only update under 2 circumstances: - A refresh-only update of the lock-files. - A dependency in `pyproject.toml` has been changed. - Outside of this, `uv.lock` and `.build-constraints.txt` should not be changing. - When they change, it should be via `make lock-dependencies` which ensures `uv.lock` refers to PyPI properly. (Manual review is unlikely to spot the error if a dev proxy is in there, so this PR automates the check.) Further to these linting-releated changes: - Running `make lock-dependencies` now locks to the latest versions of dependencies instead of keeping the existing versions if they're still within their allowed version range. - The use of `databricks-bb-analyzer==0.3.1` is blocked: the analyzer binary in this release doesn't work properly. (Issue #2373.) Finally, to verify the linting this PR updates some dependencies: - Versions are locked against the highest version. - Outstanding dependabot updates are included. ### Relevant implementation details The linting script has been written so that it can be run locally by developers, it doesn't require the GHA environment to be in place. ### Linked Issues Resolves: #2373 Subsumes: - #2409 - #2410 - #2411 - #2412 - #2413 - #2415 ### Tests - manually tested - new CI check
1 parent a8ff8a2 commit e50dae5

6 files changed

Lines changed: 582 additions & 337 deletions

File tree

.build-constraints.txt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
hatchling==1.29.0 \
22
--hash=sha256:50af9343281f34785fab12da82e445ed987a6efb34fd8c2fc0f6e6630dbcc1b0 \
33
--hash=sha256:793c31816d952cee405b83488ce001c719f325d9cda69f1fc4cd750527640ea6
4-
packaging==26.0 \
5-
--hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \
6-
--hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
4+
packaging==26.2 \
5+
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \
6+
--hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661
77
# via hatchling
8-
pathspec==1.0.4 \
9-
--hash=sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645 \
10-
--hash=sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723
8+
pathspec==1.1.1 \
9+
--hash=sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a \
10+
--hash=sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189
1111
# via hatchling
1212
pluggy==1.6.0 \
1313
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
@@ -62,7 +62,7 @@ tomli==2.4.1 ; python_full_version < '3.11' \
6262
--hash=sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9 \
6363
--hash=sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049
6464
# via hatchling
65-
trove-classifiers==2026.1.14.14 \
66-
--hash=sha256:00492545a1402b09d4858605ba190ea33243d361e2b01c9c296ce06b5c3325f3 \
67-
--hash=sha256:1f9553927f18d0513d8e5ff80ab8980b8202ce37ecae0e3274ed2ef11880e74d
65+
trove-classifiers==2026.4.28.13 \
66+
--hash=sha256:8f4b1eb4e16296b57d612965444f87a83861cc989a0451ac97fe4265ddef03b8 \
67+
--hash=sha256:c85bb8a53c3de7330d1699b844ed9fb809a602a09ac15dc79ad6d1a509be0676
6868
# via hatchling

.github/scripts/lint-uv

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/bin/sh
2+
#
3+
# Check the uv settings of the project, to ensure dependencies are being updated properly.
4+
#
5+
# The intent here is that:
6+
#
7+
# - Lock-files should only be updated via the `make lock-dependencies` target.
8+
# - We only re-lock when:
9+
# - Updating the project dependencies in some way.
10+
# - Refreshing the locked versions, where that is the sole purpose of the PR.
11+
# - Developer environment information should not be present in the lock-file; it should be usable by the public.
12+
#
13+
# The checks that we perform to enforce this are:
14+
# - The only registry we should see in uv.lock is "https://pypi.org/simple/".
15+
# - The uv.lock file must be consistent with pyproject.toml.
16+
# - If changes to uv.lock are present, there must be no other files changed in the PR _or_ pyproject.toml must also
17+
# be updated.
18+
# - The same applies to .build-constraints.txt.
19+
#
20+
21+
set -eu
22+
23+
PROGNAME="${0##*/}"
24+
die() {
25+
_exitcode="$1"
26+
shift
27+
printf "%s: %s\n" "${PROGNAME}" "$*" 1>&2
28+
29+
exit "${_exitcode}"
30+
}
31+
32+
# Locate the root of the project.
33+
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
34+
35+
# Set up a scratch directory for temporary files during the check.
36+
SCRATCH="$(mktemp -d -t "${PROGNAME}.XXX")"
37+
trap 'rm -fr "${SCRATCH}"' EXIT
38+
39+
# This script handles the locked/frozen flags for uv itself.
40+
unset UV_FROZEN UV_LOCKED
41+
42+
tomlq() {
43+
# Assumes typical project setup: tomlq is present (and locked).
44+
uv run --frozen --quiet --group yq tomlq "$@"
45+
}
46+
47+
#
48+
# Check 1: Verify that uv.lock refers only to PyPI as a registry.
49+
#
50+
check_lock_registry() {
51+
# Collect all registry URLs present in uv.lock, except for the only one we allow.
52+
tomlq --raw-output \
53+
'[.package[]?.source.registry // empty] - ["https://pypi.org/simple/"] | unique | .[]' \
54+
"${PROJECT_ROOT}/uv.lock" > "${SCRATCH}/foreign-registries.txt"
55+
if [ -s "${SCRATCH}/foreign-registries.txt" ]
56+
then
57+
printf "%s: Detected non-PyPI registries:\n" "${PROGNAME}"
58+
sed 's/^/ /' "${SCRATCH}/foreign-registries.txt"
59+
die 1 "uv.lock may only contain PyPI registries."
60+
fi
61+
}
62+
check_lock_registry
63+
64+
#
65+
# Check 2: Verify that uv.lock is consistent with pyproject.toml.
66+
#
67+
68+
detect_index_url() {
69+
# Annoyingly, there is no way to query from uv the index URL it's going to use; the only approach is to use a fake
70+
# project and ask to resolve, then dig the index URL out of the results.
71+
mkdir -p "${SCRATCH}/uvtest"
72+
cat > "${SCRATCH}/uvtest/pyproject.toml" << 'EOF'
73+
[project]
74+
name = "uvtest"
75+
version = "0"
76+
requires-python = ">=3.10"
77+
dependencies = ["packaging"]
78+
EOF
79+
uv lock --quiet --project "${SCRATCH}/uvtest"
80+
tomlq --raw-output 'first(.package[].source.registry // empty)' "${SCRATCH}/uvtest/uv.lock"
81+
}
82+
83+
check_lock_consistency() {
84+
# We can't check uv.lock directly, because we can't reach the registry recorded in there. Instead we make a shadow
85+
# version of this project and check that.
86+
index_url="$(detect_index_url)"
87+
[ -n "${index_url}" ] || die 2 "Could not detect the URL to use for PyPI."
88+
mkdir -p "${SCRATCH}/shadow-project"
89+
cp "${PROJECT_ROOT}/pyproject.toml" "${SCRATCH}/shadow-project/"
90+
# shellcheck disable=SC2016 # $index_url is a jq variable, bound via --arg
91+
tomlq --toml-output --arg index_url "${index_url}" \
92+
'(.package[]?.source | select(.registry? == "https://pypi.org/simple/")).registry = $index_url' \
93+
"${PROJECT_ROOT}/uv.lock" > "${SCRATCH}/shadow-project/uv.lock"
94+
uv lock --project "${SCRATCH}/shadow-project" --check ||
95+
die 2 "Inconsistency detected between pyproject.toml and uv.lock."
96+
}
97+
check_lock_consistency
98+
99+
#
100+
# Check 3: Detect if uv.lock or .build-constraints.txt have been updated with pyproject.toml or on their own.
101+
#
102+
103+
base_rev() {
104+
# A few situations to deal with, in order of priority:
105+
# - In CI, we set BASE_SHA for pull requests and the merge queue.
106+
# - In CI, we also have the push to main where there's no base: in this case we can use GITHUB_REF and diff will
107+
# end up a no-op (because there are no differences).
108+
# - For local dev, fall-back to `main` which by convention is always available.
109+
_base_rev="${BASE_SHA:-${GITHUB_REF:-main}}"
110+
git rev-parse --verify --quiet "${_base_rev}" > /dev/null ||
111+
die 3 "Base revision of repository is not available: ${_base_rev}"
112+
printf "%s" "${_base_rev}"
113+
}
114+
merge_base() {
115+
# Figure out the merge-base between this branch and verify it's available. (CI checkout might be shallow.)
116+
_tip="$1"
117+
_base_rev=$(base_rev)
118+
if ! git merge-base "${_base_rev}" "${_tip}" 2> /dev/null
119+
then
120+
die 4 "Repository is incomplete, merge base between ${_base_rev} and ${_tip} is not available."
121+
fi
122+
}
123+
124+
check_lockfile_updates() {
125+
_merge_base=$(merge_base HEAD)
126+
git diff "${_merge_base}..HEAD" --name-only > "${SCRATCH}/changed-files.txt"
127+
128+
if grep -qxF -e 'uv.lock' -e '.build-constraints.txt' "${SCRATCH}/changed-files.txt"
129+
then
130+
# Lock-files changed: either pyproject.toml has to be changed, or nothing else is allowed.
131+
if grep -vxF -e 'uv.lock' -e '.build-constraints.txt' "${SCRATCH}/changed-files.txt" > "${SCRATCH}/other-files.txt" && ! grep -qxF 'pyproject.toml' "${SCRATCH}/other-files.txt"
132+
then
133+
printf "%s: Detected changes to uv.lock and/or .build-constraints.txt mixed with changes to:\n" "${PROGNAME}"
134+
sed -e 's/^/ /' "${SCRATCH}/other-files.txt"
135+
die 5 "Lock-files must be updated either in conjunction with dependencies in pyproject.toml or on their own."
136+
fi
137+
fi
138+
}
139+
check_lockfile_updates

.github/workflows/push.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,49 @@ jobs:
7878
# Exit with status code 1 if there are differences (i.e. unformatted files)
7979
git diff --exit-code
8080
81+
lint-uv:
82+
runs-on:
83+
group: databrickslabs-protected-runner-group
84+
labels: linux-ubuntu-latest
85+
permissions:
86+
# JFrog OIDC authentication.
87+
id-token: write
88+
steps:
89+
- name: Checkout
90+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
91+
92+
- name: Deepening checkout to find merge-base
93+
if: github.event.pull_request || github.event.merge_group
94+
env:
95+
BASE_SHA: "${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }}"
96+
run: |
97+
_depth=32
98+
_max=512
99+
until git merge-base "${BASE_SHA}" HEAD > /dev/null 2>&1
100+
do
101+
if [[ "${_max}" -lt "${_depth}" ]]
102+
then
103+
# Give up deepening, just fetch the rest of the repo.
104+
git fetch --unshallow origin "${BASE_SHA}" "${GITHUB_REF}"
105+
break
106+
fi
107+
git fetch --deepen "${_depth}" origin "${BASE_SHA}" "${GITHUB_REF}"
108+
_depth=$((_depth * 2))
109+
done
110+
printf '%s=%s\n' 'BASE_SHA' "${BASE_SHA}" >> "${GITHUB_ENV}"
111+
112+
- name: Setup uv
113+
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
114+
with:
115+
version: "0.11.2"
116+
checksum: "7ac2ca0449c8d68dae9b99e635cd3bc9b22a4cb1de64b7c43716398447d42981"
117+
118+
- name: Setup for JFrog
119+
uses: ./.github/actions/jfrog-auth
120+
121+
- name: Linting project lock-files
122+
run: ./.github/scripts/lint-uv
123+
81124
python-no-pylint-disable:
82125
runs-on:
83126
group: databrickslabs-protected-runner-group

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ build:
5555

5656
lock-dependencies: UV_FROZEN := 0
5757
lock-dependencies:
58-
uv lock
58+
uv lock --upgrade
5959
$(UV_RUN) --group yq tomlq -r '.["build-system"].requires[]' pyproject.toml | \
60-
uv pip compile --generate-hashes --universal --no-header --quiet - > build-constraints-new.txt
60+
uv pip compile --upgrade --generate-hashes --universal --no-header --quiet - > build-constraints-new.txt
6161
mv build-constraints-new.txt .build-constraints.txt
6262
@perl -pi -e 's|registry = "https://[^"]*"|registry = "https://pypi.org/simple/"|g' uv.lock
6363
@printf 'Stripped registry references from uv.lock.\n'

pyproject.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ classifiers = [
3131
dependencies = [
3232
"databricks-sdk~=0.85.0",
3333
"standard-distutils~=3.11.9; python_version>='3.11'",
34-
"databricks-bb-analyzer~=0.3.0",
34+
"databricks-bb-analyzer~=0.3.0,!=0.3.1", # 0.3.1 is a broken release.
3535
"sqlglot==28.5.0",
3636
"databricks-labs-blueprint[yaml]>=0.12.0,<0.13.0",
3737
"databricks-labs-lsql==0.16.0",
@@ -84,12 +84,12 @@ lint = [
8484
"pylint~=3.2.2",
8585
"pylint-pytest==2.0.0a0",
8686
"black~=25.9.0",
87-
"ruff~=0.13.2",
88-
"mypy~=1.18.2",
87+
"ruff~=0.15.12",
88+
"mypy~=1.20.2",
8989
"databricks-labs-pylint~=0.5.0",
9090
"types-requests>=2.28.1,<3",
9191
"types-pyYAML~=6.0.12",
92-
"types-pytz~=2025.2",
92+
"types-pytz~=2026.1",
9393
"pandas-stubs~=2.3.0.250703",
9494
# Azure SDK dependencies for linting resources/assessments folder
9595
"azure-identity~=1.19.0",
@@ -109,8 +109,8 @@ test = [
109109
{ include-group = "pytest" },
110110
"databricks-connect==15.1",
111111
"databricks-labs-pytester>=0.3.0,<0.8.0",
112-
"cattrs>=25.2.0,<26.0.0",
113-
"faker~=40.12.0",
112+
"cattrs~=26.1",
113+
"faker~=40.15.0",
114114
]
115115
sqlglot = [
116116
"sqlglot~=28.0",

0 commit comments

Comments
 (0)