diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6e3fff8a9..2f42b4baf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -73,6 +73,11 @@ updates: ignore: - dependency-name: node + - package-ecosystem: docker + directory: /apps/spacy-server + schedule: + interval: weekly + - package-ecosystem: uv directory: /apps/spacy-server schedule: diff --git a/apps/spacy-server/pyproject.toml b/apps/spacy-server/pyproject.toml index 00e103096..ba9aba1fe 100644 --- a/apps/spacy-server/pyproject.toml +++ b/apps/spacy-server/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "cat-spacy-server" version = "0.1.0" -requires-python = "==3.12.11" +requires-python = ">=3.12,<3.13" dependencies = [ "fastapi==0.116.1", "langcodes==3.5.0", diff --git a/apps/spacy-server/uv.lock b/apps/spacy-server/uv.lock index 00a86ddc0..bcc69a460 100644 --- a/apps/spacy-server/uv.lock +++ b/apps/spacy-server/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 2 -requires-python = "==3.12.11" +requires-python = "==3.12.*" [[package]] name = "annotated-doc" diff --git a/scripts/ci-config-contract.spec.ts b/scripts/ci-config-contract.spec.ts index 4c32b7279..72e6526fc 100644 --- a/scripts/ci-config-contract.spec.ts +++ b/scripts/ci-config-contract.spec.ts @@ -571,7 +571,7 @@ describe("CI configuration contract", () => { expect(dependabot.version).toBe(2); expect( dependabot.updates?.map((update) => update["package-ecosystem"]), - ).toEqual(["npm", "docker", "uv", "github-actions"]); + ).toEqual(["npm", "docker", "docker", "uv", "github-actions"]); const npm = dependabot.updates?.find( (update) => update["package-ecosystem"] === "npm", @@ -587,13 +587,24 @@ describe("CI configuration contract", () => { vueuse: compatibilityCohort(["@vueuse/*"]), }); - const devcontainerImages = dependabot.updates?.find( + const containerImages = dependabot.updates?.filter( (update) => update["package-ecosystem"] === "docker", ); - expect(devcontainerImages?.directory).toBe("/.devcontainer"); + expect(containerImages?.map((update) => update.directory)).toEqual([ + "/.devcontainer", + "/apps/spacy-server", + ]); + const devcontainerImages = containerImages?.find( + (update) => update.directory === "/.devcontainer", + ); expect(devcontainerImages?.ignore).toContainEqual({ "dependency-name": "node", }); + expect( + containerImages?.find( + (update) => update.directory === "/apps/spacy-server", + )?.ignore, + ).toBeUndefined(); const uv = dependabot.updates?.find( (update) => update["package-ecosystem"] === "uv", @@ -612,6 +623,35 @@ describe("CI configuration contract", () => { ).toBeUndefined(); }); + it("separates Python compatibility from the exact deployed patch for native uv updates", () => { + const pythonVersion = String( + workflow.jobs?.quality?.steps?.find((step) => + step.uses?.startsWith("astral-sh/setup-uv@"), + )?.with?.["python-version"], + ); + expect(pythonVersion).toMatch(/^3\.12\.\d+$/); + const [major, minor] = pythonVersion.split(".").map(Number); + const supportedRange = `>=${major}.${minor},<${major}.${minor! + 1}`; + expect( + readFileSync(resolve(root, "apps/spacy-server/pyproject.toml"), "utf8"), + ).toContain(`requires-python = "${supportedRange}"`); + expect( + readFileSync(resolve(root, "apps/spacy-server/uv.lock"), "utf8"), + ).toContain(`requires-python = "==${major}.${minor}.*"`); + + const spacyDockerfile = readFileSync( + resolve(root, "apps/spacy-server/Dockerfile"), + "utf8", + ); + expect(spacyDockerfile.match(/^FROM python:/gm)).toHaveLength(2); + expect(spacyDockerfile).not.toMatch( + new RegExp(`^FROM python:(?!${pythonVersion}-)`, "m"), + ); + expect( + readFileSync(resolve(root, ".devcontainer/Dockerfile"), "utf8"), + ).toContain(`UV_PYTHON=${pythonVersion}`); + }); + it("provides Docker CLI, buildx, and Compose through the Dockerfile-first devcontainer", () => { const config = JSON.parse( readFileSync(resolve(root, ".devcontainer/devcontainer.json"), "utf8"),