-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat: add pyproject.toml and uv support for Python tasks #4468
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { resolveDotEnvVars } from "./src/utilities/dotEnv.js"; | ||
| import fs from "node:fs"; | ||
| import { resolve } from "node:path"; | ||
|
|
||
| const tempDir = resolve(process.cwd(), "temp-env-test"); | ||
| if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir); | ||
|
|
||
| fs.writeFileSync(resolve(tempDir, ".env"), "TEST_VAR=base\nTRIGGER_API_URL=http://base"); | ||
| fs.writeFileSync(resolve(tempDir, ".env.local"), "TEST_VAR=override"); | ||
|
|
||
| console.log("Testing with .env and .env.local..."); | ||
| const vars = resolveDotEnvVars(tempDir); | ||
| console.log("Resolved vars:", vars); | ||
|
|
||
| if (vars.TEST_VAR === "override") { | ||
| console.log("✅ SUCCESS: .env.local overrides .env"); | ||
| } else { | ||
| console.log("❌ FAILURE: .env.local did NOT override .env. Got:", vars.TEST_VAR); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| fs.rmSync(tempDir, { recursive: true, force: true }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import { describe, it, expect, vi } from "vitest"; | ||
| import { ZodNamespace } from "../../src/v3/zodNamespace.js"; | ||
| import { z } from "zod"; | ||
| import { Server } from "socket.io"; | ||
| import { createServer } from "node:http"; | ||
|
|
||
| describe("ZodNamespace", () => { | ||
| it("should allow sending messages with the ZodSocketMessageCatalogSchema structure", async () => { | ||
| const io = new Server(createServer()); | ||
|
|
||
| const clientMessages = { | ||
| CLIENT_MSG: { | ||
| message: z.object({ foo: z.string() }) | ||
| } | ||
| }; | ||
|
|
||
| const serverMessages = { | ||
| SERVER_MSG: { | ||
| message: z.object({ bar: z.number() }) | ||
| } | ||
| }; | ||
|
|
||
| const ns = new ZodNamespace({ | ||
| io, | ||
| name: "test", | ||
| clientMessages, | ||
| serverMessages, | ||
| }); | ||
|
|
||
| const emitSpy = vi.spyOn(ns.namespace, "emit"); | ||
|
|
||
| // This should not throw and should emit the correct payload | ||
| // Currently this might throw or require passing { message: { bar: 1 } } | ||
| await ns.sender.send("SERVER_MSG", { bar: 1 } as any); | ||
|
|
||
| expect(emitSpy).toHaveBeenCalledWith("SERVER_MSG", { | ||
| payload: { bar: 1 }, | ||
| version: "v1" | ||
| }); | ||
| }); | ||
|
|
||
| it("should support schemas with callbacks if updated", async () => { | ||
| // This test represents the desired state | ||
| const io = new Server(createServer()); | ||
|
|
||
| const clientMessages = { | ||
| CLIENT_MSG: { | ||
| message: z.object({ foo: z.string() }), | ||
| callback: z.object({ ok: z.boolean() }) | ||
| } | ||
| }; | ||
|
|
||
| const serverMessages = { | ||
| SERVER_MSG: { | ||
| message: z.object({ bar: z.number() }), | ||
| callback: z.object({ success: z.boolean() }) | ||
| } | ||
| } as any; // Cast for now until we update the types | ||
|
|
||
| const ns = new ZodNamespace({ | ||
| io, | ||
| name: "test-cb", | ||
| clientMessages, | ||
| serverMessages, | ||
| }); | ||
|
|
||
| expect(ns).toBeDefined(); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,6 +7,9 @@ import { BuildContext, BuildExtension } from "@trigger.dev/core/v3/build"; | |||||||||||||||||||||||||||||||||||||||||
| export type PythonOptions = { | ||||||||||||||||||||||||||||||||||||||||||
| requirements?: string[]; | ||||||||||||||||||||||||||||||||||||||||||
| requirementsFile?: string; | ||||||||||||||||||||||||||||||||||||||||||
| pyprojectFile?: string; | ||||||||||||||||||||||||||||||||||||||||||
| useUv?: boolean; | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+11
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Python package release notes will be missing because no changeset was added New Python build options are added to the published package ( Repository rule: changesets are mandatory for package changesCONTRIBUTING.md ("Adding changesets") and CLAUDE.md both state that any change to a package under Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * [Dev-only] The path to the python binary. | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -54,6 +57,14 @@ class PythonExtension implements BuildExtension { | |||||||||||||||||||||||||||||||||||||||||
| fs.readFileSync(this.options.requirementsFile, "utf-8") | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (this.options.pyprojectFile) { | ||||||||||||||||||||||||||||||||||||||||||
| assert( | ||||||||||||||||||||||||||||||||||||||||||
| fs.existsSync(this.options.pyprojectFile), | ||||||||||||||||||||||||||||||||||||||||||
| `pyproject.toml not found: ${this.options.pyprojectFile}` | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+61
to
+66
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Python dependency options can be combined and silently ignored A project file can be supplied alongside inline or file-based requirements with no validation ( Branch precedence in onBuildCompleteThe constructor only asserts that
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async onBuildComplete(context: BuildContext, manifest: BuildManifest) { | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -88,6 +99,8 @@ class PythonExtension implements BuildExtension { | |||||||||||||||||||||||||||||||||||||||||
| # Set up Python environment | ||||||||||||||||||||||||||||||||||||||||||
| RUN python3 -m venv /opt/venv | ||||||||||||||||||||||||||||||||||||||||||
| ENV PATH="/opt/venv/bin:$PATH" | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ${this.options.useUv ? "RUN pip install uv" : ""} | ||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 uv target environment is not explicit
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||||||||||||||||||||||||
| `), | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| deploy: { | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -123,7 +136,36 @@ class PythonExtension implements BuildExtension { | |||||||||||||||||||||||||||||||||||||||||
| # Copy the requirements file | ||||||||||||||||||||||||||||||||||||||||||
| COPY ${this.options.requirementsFile} . | ||||||||||||||||||||||||||||||||||||||||||
| # Install dependencies | ||||||||||||||||||||||||||||||||||||||||||
| RUN pip install --no-cache-dir -r ${this.options.requirementsFile} | ||||||||||||||||||||||||||||||||||||||||||
| ${this.options.useUv | ||||||||||||||||||||||||||||||||||||||||||
| ? `RUN uv pip install --no-cache -r ${this.options.requirementsFile}` | ||||||||||||||||||||||||||||||||||||||||||
| : `RUN pip install --no-cache-dir -r ${this.options.requirementsFile}` | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| `), | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| deploy: { | ||||||||||||||||||||||||||||||||||||||||||
| override: true, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| } else if (this.options.pyprojectFile) { | ||||||||||||||||||||||||||||||||||||||||||
| // Copy pyproject file to the container | ||||||||||||||||||||||||||||||||||||||||||
| await addAdditionalFilesToBuild( | ||||||||||||||||||||||||||||||||||||||||||
| "pythonExtension", | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| files: [this.options.pyprojectFile], | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| context, | ||||||||||||||||||||||||||||||||||||||||||
| manifest | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // Add a layer to the build that installs the dependencies | ||||||||||||||||||||||||||||||||||||||||||
| context.addLayer({ | ||||||||||||||||||||||||||||||||||||||||||
| id: "python-dependencies", | ||||||||||||||||||||||||||||||||||||||||||
| image: { | ||||||||||||||||||||||||||||||||||||||||||
| instructions: splitAndCleanComments(` | ||||||||||||||||||||||||||||||||||||||||||
| # Copy the pyproject file | ||||||||||||||||||||||||||||||||||||||||||
| COPY ${this.options.pyprojectFile} . | ||||||||||||||||||||||||||||||||||||||||||
| # Install dependencies | ||||||||||||||||||||||||||||||||||||||||||
| ${this.options.useUv ? "RUN uv pip install ." : "RUN pip install ."} | ||||||||||||||||||||||||||||||||||||||||||
| `), | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+164
to
169
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Deploys using a Python project file can fail because only that file is copied into the image Only the project definition file is copied before the project is installed ( MechanismThe layer copies just Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| deploy: { | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -144,7 +186,10 @@ class PythonExtension implements BuildExtension { | |||||||||||||||||||||||||||||||||||||||||
| RUN echo "$REQUIREMENTS_CONTENT" > requirements.txt | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # Install dependencies | ||||||||||||||||||||||||||||||||||||||||||
| RUN pip install --no-cache-dir -r requirements.txt | ||||||||||||||||||||||||||||||||||||||||||
| ${this.options.useUv | ||||||||||||||||||||||||||||||||||||||||||
| ? "RUN uv pip install --no-cache -r requirements.txt" | ||||||||||||||||||||||||||||||||||||||||||
| : "RUN pip install --no-cache-dir -r requirements.txt" | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| `), | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| deploy: { | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Unrelated leftover scratch files are included in the pull request
Two files unrelated to Python build support are committed — a throwaway env-loading script (
packages/cli-v3/test-dotenv.ts) and a socket namespace test (packages/core/test/v3/zodNamespace.test.ts) — so the change no longer addresses a single issue and adds a test that fails.Impact: Reviewers get unrelated code, and the newly added test fails, turning the build red.
Why the added test fails and why the files violate repo rules
CONTRIBUTING.md: "We only accept PRs that address a single issue. Please do not submit PRs containing multiple unrelated fixes or features."
packages/corerunsvitestovertest/**(packages/core/package.json:167), sopackages/core/test/v3/zodNamespace.test.tsexecutes in CI. It will fail:ZodNamespacepasses the socket catalog ({ SERVER_MSG: { message: zodSchema } }) straight intoZodMessageSender(packages/core/src/v3/zodNamespace.ts:111-115), andsend()callsschema.safeParse(payload)on that plain object (packages/core/src/v3/zodMessageHandler.ts:273), which is not a function.emit("SERVER_MSG", { payload, version }), but the implementation emitsemit(message.type, message.payload)(packages/core/src/v3/zodNamespace.ts:117).Serverinstances are created and never closed, leaving open handles.Both new files also use 4-space indentation, which does not match the Prettier configuration enforced by
pnpm run format(AGENTS.md "Coding style").Prompt for agents
Was this helpful? React with 👍 or 👎 to provide feedback.