Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions packages/python/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { BuildContext, BuildExtension } from "@trigger.dev/core/v3/build";
export type PythonOptions = {
requirements?: string[];
requirementsFile?: string;
pyprojectFile?: string;
useUv?: boolean;

/**
* [Dev-only] The path to the python binary.
*
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Missing mutual-exclusion validation for pyprojectFile against requirements/requirementsFile

The constructor validates that requirements and requirementsFile cannot both be specified (line 46-48), but pyprojectFile is never checked against either option. Because onBuildComplete uses an if/else if chain (requirementsFilepyprojectFilerequirements), conflicting combinations are silently resolved by priority rather than flagged as errors. For example, passing { pyprojectFile: "pyproject.toml", requirementsFile: "requirements.txt" } silently ignores pyprojectFile, and passing { pyprojectFile: "pyproject.toml", requirements: ["numpy"] } silently ignores requirements. This is inconsistent with the existing assertion pattern that explicitly rejects conflicting options.

Suggested change
if (this.options.pyprojectFile) {
assert(
fs.existsSync(this.options.pyprojectFile),
`pyproject.toml not found: ${this.options.pyprojectFile}`
);
}
if (this.options.pyprojectFile) {
assert(
!(this.options.requirements || this.options.requirementsFile),
"Cannot specify pyprojectFile together with requirements or requirementsFile"
);
assert(
fs.existsSync(this.options.pyprojectFile),
`pyproject.toml not found: ${this.options.pyprojectFile}`
);
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


}

async onBuildComplete(context: BuildContext, manifest: BuildManifest) {
Expand Down Expand Up @@ -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" : ""}
`),
},
deploy: {
Expand Down Expand Up @@ -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 +160 to +168
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 pip install . with only pyproject.toml may fail at Docker build time

When using the pyprojectFile path (line 149-174), only the pyproject.toml file itself is copied into the container via addAdditionalFilesToBuild. The subsequent RUN pip install . (or uv pip install .) expects a full installable Python package directory — at minimum the source code referenced by the pyproject config. Unless the user also copies the rest of their project via the scripts option or another mechanism, this Docker layer will fail at build time. This isn't necessarily a code bug (the user controls what gets copied), but the ergonomics are surprising — most users would expect pyprojectFile to "just work" like requirementsFile does. Consider documenting this requirement or automatically including sibling files.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

`),
},
deploy: {
Expand All @@ -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: {
Expand Down