Skip to content
Merged
Show file tree
Hide file tree
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
7 changes: 7 additions & 0 deletions .changeset/young-vans-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": patch
---

Fix Python Workers deployment failing on Windows due to path separator handling

Previously, deploying Python Workers on Windows would fail because the backslash path separator (`\`) was not properly handled, causing the entire full path to be treated as a single filename. The deployment process now correctly normalizes paths to use forward slashes on all platforms.
9 changes: 4 additions & 5 deletions packages/wrangler/src/__tests__/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13951,18 +13951,17 @@ export default{
const expectedModules = {
"index.py": mainPython,
"helper.py": "# Helper module\ndef helper(): pass",
[`python_modules${path.sep}module1.so`]: "binary content for module 1",
[`python_modules${path.sep}module2.py`]:
"# Python vendor module 2\nprint('hello')",
"python_modules/module1.so": "binary content for module 1",
"python_modules/module2.py": "# Python vendor module 2\nprint('hello')",
};

mockSubDomainRequest();
mockUploadWorkerRequest({
expectedMainModule: "index.py",
expectedModules,
excludedModules: [
`python_modules${path.sep}test.pyc`,
`python_modules${path.sep}other${path.sep}test.pyc`,
"python_modules/test.pyc",
"python_modules/other/test.pyc",
],
});

Expand Down
65 changes: 65 additions & 0 deletions packages/wrangler/src/__tests__/find-additional-modules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,3 +322,68 @@ describe("traverse module graph", () => {
);
});
});

describe("Python modules", () => {
runInTempDir();
mockConsoleMethods();

it("should find python_modules with forward slashes (for cross-platform deploy)", async () => {
await mkdir("./python_modules/pkg/subpkg", { recursive: true });
await writeFile("./index.py", "def fetch(request): pass");
await writeFile("./python_modules/pkg/__init__.py", "");
await writeFile("./python_modules/pkg/subpkg/mod.py", "x = 1");

const modules = await findAdditionalModules(
{
file: path.join(process.cwd(), "./index.py"),
projectRoot: process.cwd(),
configPath: undefined,
format: "modules",
moduleRoot: process.cwd(),
exports: [],
},
[]
);

const pythonModules = modules.filter((m) =>
m.name.startsWith("python_modules/")
);
expect(pythonModules.map((m) => m.name)).toEqual(
expect.arrayContaining([
"python_modules/pkg/__init__.py",
"python_modules/pkg/subpkg/mod.py",
])
);
// This assertion catches Windows path.join() regression
pythonModules.forEach((m) => {
expect(m.name).not.toContain("\\");
});
});

it("should exclude files matching pythonModulesExcludes patterns", async () => {
await mkdir("./python_modules", { recursive: true });
await writeFile("./index.py", "def fetch(request): pass");
await writeFile("./python_modules/module.py", "x = 1");
await writeFile("./python_modules/module.pyc", "compiled");
await writeFile("./python_modules/test_module.py", "def test(): pass");

const modules = await findAdditionalModules(
{
file: path.join(process.cwd(), "./index.py"),
projectRoot: process.cwd(),
configPath: undefined,
format: "modules",
moduleRoot: process.cwd(),
exports: [],
},
[],
false,
["**/*.pyc", "**/test_*.py"]
);

const moduleNames = modules.map((m) => m.name);
expect(moduleNames).toContain("python_modules/module.py");
expect(moduleNames).not.toContain("python_modules/module.pyc");
expect(moduleNames).not.toContain("python_modules/test_module.py");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,28 @@ function isValidPythonPackageName(name: string): boolean {
return regex.test(name);
}

/**
* Checks if a given module name is a Python vendor module.
* @param moduleName The module name to check
*/
function isPythonVendorModule(moduleName: string): boolean {
// separator should be forward slash, as we always use forward slash for module names
// see `getFiles()` for more details
return moduleName.startsWith("python_modules/");
}

function removePythonVendorModules(
isPythonEntrypoint: boolean,
modules: CfModule[]
): CfModule[] {
if (!isPythonEntrypoint) {
return modules;
}
return modules.filter((m) => !m.name.startsWith("python_modules" + path.sep));
Comment thread
ryanking13 marked this conversation as resolved.
return modules.filter((m) => !isPythonVendorModule(m.name));
}

function getPythonVendorModulesSize(modules: CfModule[]): number {
const vendorModules = modules.filter((m) =>
m.name.startsWith("python_modules" + path.sep)
);
const vendorModules = modules.filter((m) => isPythonVendorModule(m.name));
return vendorModules.reduce((total, m) => total + m.content.length, 0);
}

Expand Down Expand Up @@ -187,7 +195,10 @@ export async function findAdditionalModules(
return true; // Include this file
})
.map((m) => {
const prefixedPath = path.join("python_modules", m.name);
// Always use forward slashes for module names, regardless of platform.
// path.join() uses backslashes on Windows, but module names must use
// forward slashes for proper directory structure when deployed.
const prefixedPath = `python_modules/${m.name}`;
return {
...m,
name: prefixedPath,
Expand Down
Loading