From a212172b415375307c7d2942d2a784b79517277c Mon Sep 17 00:00:00 2001 From: Gyeongjae Choi Date: Thu, 5 Feb 2026 18:12:44 +0900 Subject: [PATCH 1/7] Always use forward slashes for python workers path handling --- packages/wrangler/src/__tests__/deploy.test.ts | 9 ++++----- .../src/deployment-bundle/find-additional-modules.ts | 9 ++++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index 03900743ea..41362c4771 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -13951,9 +13951,8 @@ 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(); @@ -13961,8 +13960,8 @@ export default{ 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", ], }); diff --git a/packages/wrangler/src/deployment-bundle/find-additional-modules.ts b/packages/wrangler/src/deployment-bundle/find-additional-modules.ts index a050bee7f0..5d3df2b36e 100644 --- a/packages/wrangler/src/deployment-bundle/find-additional-modules.ts +++ b/packages/wrangler/src/deployment-bundle/find-additional-modules.ts @@ -56,12 +56,12 @@ function removePythonVendorModules( if (!isPythonEntrypoint) { return modules; } - return modules.filter((m) => !m.name.startsWith("python_modules" + path.sep)); + return modules.filter((m) => !m.name.startsWith("python_modules/")); } function getPythonVendorModulesSize(modules: CfModule[]): number { const vendorModules = modules.filter((m) => - m.name.startsWith("python_modules" + path.sep) + m.name.startsWith("python_modules/") ); return vendorModules.reduce((total, m) => total + m.content.length, 0); } @@ -187,7 +187,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, From ca2a35661bd11b202f15a7052050f3bd6b4d7fb3 Mon Sep 17 00:00:00 2001 From: Gyeongjae Choi Date: Thu, 5 Feb 2026 18:19:56 +0900 Subject: [PATCH 2/7] Add changeset --- .changeset/young-vans-reply.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/young-vans-reply.md diff --git a/.changeset/young-vans-reply.md b/.changeset/young-vans-reply.md new file mode 100644 index 0000000000..0d4605c034 --- /dev/null +++ b/.changeset/young-vans-reply.md @@ -0,0 +1,5 @@ +--- +"wrangler": major +--- + +Fixed incorrect python workers deployment in Windows environment From 2207e8ca0589bffd43b3b385abeacc5c9121212a Mon Sep 17 00:00:00 2001 From: Gyeongjae Choi Date: Thu, 5 Feb 2026 18:20:58 +0900 Subject: [PATCH 3/7] patch release --- .changeset/young-vans-reply.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/young-vans-reply.md b/.changeset/young-vans-reply.md index 0d4605c034..ac3dd20d52 100644 --- a/.changeset/young-vans-reply.md +++ b/.changeset/young-vans-reply.md @@ -1,5 +1,5 @@ --- -"wrangler": major +"wrangler": patch --- Fixed incorrect python workers deployment in Windows environment From 9be0af07d5b8021ae22c367e4c902f61d747ee3d Mon Sep 17 00:00:00 2001 From: Gyeongjae Choi Date: Thu, 5 Feb 2026 18:29:36 +0900 Subject: [PATCH 4/7] Update changeset to be more descriptive --- .changeset/young-vans-reply.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.changeset/young-vans-reply.md b/.changeset/young-vans-reply.md index ac3dd20d52..11f67514bc 100644 --- a/.changeset/young-vans-reply.md +++ b/.changeset/young-vans-reply.md @@ -2,4 +2,6 @@ "wrangler": patch --- -Fixed incorrect python workers deployment in Windows environment +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. From 4d73e59b7fc229be5e73aa2e01604343a10ad531 Mon Sep 17 00:00:00 2001 From: Gyeongjae Choi Date: Thu, 5 Feb 2026 19:40:26 +0900 Subject: [PATCH 5/7] Add more unittest --- .../__tests__/find-additional-modules.test.ts | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/packages/wrangler/src/__tests__/find-additional-modules.test.ts b/packages/wrangler/src/__tests__/find-additional-modules.test.ts index 6b882e6cb9..8c92b9b72e 100644 --- a/packages/wrangler/src/__tests__/find-additional-modules.test.ts +++ b/packages/wrangler/src/__tests__/find-additional-modules.test.ts @@ -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"); + }); +}); From c214999c28a0b9f9a8f0d375ddb4efc3325bf293 Mon Sep 17 00:00:00 2001 From: Gyeongjae Choi Date: Thu, 5 Feb 2026 19:40:33 +0900 Subject: [PATCH 6/7] Add comment about forward slashes --- .../wrangler/src/deployment-bundle/find-additional-modules.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/wrangler/src/deployment-bundle/find-additional-modules.ts b/packages/wrangler/src/deployment-bundle/find-additional-modules.ts index 5d3df2b36e..471d2adcef 100644 --- a/packages/wrangler/src/deployment-bundle/find-additional-modules.ts +++ b/packages/wrangler/src/deployment-bundle/find-additional-modules.ts @@ -56,11 +56,15 @@ function removePythonVendorModules( if (!isPythonEntrypoint) { return modules; } + // separator should be forward slash, as we always use forward slash for module names + // see `getFiles()` for more details return modules.filter((m) => !m.name.startsWith("python_modules/")); } function getPythonVendorModulesSize(modules: CfModule[]): number { const vendorModules = modules.filter((m) => + // separator should be forward slash, as we always use forward slash for module names + // see `getFiles()` for more details m.name.startsWith("python_modules/") ); return vendorModules.reduce((total, m) => total + m.content.length, 0); From 2afbbfe79d0f894640f1d3f3cfb3341a5af130a9 Mon Sep 17 00:00:00 2001 From: Gyeongjae Choi Date: Thu, 5 Feb 2026 19:42:25 +0900 Subject: [PATCH 7/7] Extract to a common function --- .../find-additional-modules.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/wrangler/src/deployment-bundle/find-additional-modules.ts b/packages/wrangler/src/deployment-bundle/find-additional-modules.ts index 471d2adcef..c58b2f89ed 100644 --- a/packages/wrangler/src/deployment-bundle/find-additional-modules.ts +++ b/packages/wrangler/src/deployment-bundle/find-additional-modules.ts @@ -49,6 +49,16 @@ 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[] @@ -56,17 +66,11 @@ function removePythonVendorModules( if (!isPythonEntrypoint) { return modules; } - // separator should be forward slash, as we always use forward slash for module names - // see `getFiles()` for more details - return modules.filter((m) => !m.name.startsWith("python_modules/")); + return modules.filter((m) => !isPythonVendorModule(m.name)); } function getPythonVendorModulesSize(modules: CfModule[]): number { - const vendorModules = modules.filter((m) => - // separator should be forward slash, as we always use forward slash for module names - // see `getFiles()` for more details - m.name.startsWith("python_modules/") - ); + const vendorModules = modules.filter((m) => isPythonVendorModule(m.name)); return vendorModules.reduce((total, m) => total + m.content.length, 0); }