Skip to content

Commit 3ff9592

Browse files
V3 backport: Support wrangler version upload for Python Workers (#9713)
* Support version upload for Python Workers * Create chilly-dancers-clap.md * fix format * fix rebase * fix snapshots * address comments (from v4 PR) --------- Co-authored-by: Dario Piotrowicz <[email protected]>
1 parent e101451 commit 3ff9592

File tree

8 files changed

+151
-66
lines changed

8 files changed

+151
-66
lines changed

.changeset/chilly-dancers-clap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Support `wrangler version upload` for Python Workers
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { mockConsoleMethods } from "./mock-console";
2+
3+
/**
4+
* Assert that Wrangler has made a request matching a certain pattern. Unlike MSW (which mocks the return value of the request),
5+
* this helper asserts that a request has been made. It should be used in combination with MSW—MSW providing the mock API, and this helper being used
6+
* to make sure the right data is sent to the mock API.
7+
*
8+
* This works by matching against the contents of Wrangler's debug logging,
9+
* which includes Cloudflare API requests that are made
10+
*/
11+
export function makeApiRequestAsserter(
12+
console: ReturnType<typeof mockConsoleMethods>
13+
) {
14+
beforeEach(() => {
15+
vi.stubEnv("WRANGLER_LOG", "debug");
16+
vi.stubEnv("WRANGLER_LOG_SANITIZE", "false");
17+
});
18+
return function assertApiRequest(
19+
url: RegExp,
20+
{ method, body }: { method?: string; body?: RegExp }
21+
) {
22+
const startLine = console.debug.match(
23+
new RegExp(`-- START CF API REQUEST: ${method} ` + url.source)
24+
);
25+
assert(startLine !== null, "Request not made by Wrangler");
26+
const requestDetails = console.debug
27+
.slice(startLine.index)
28+
.match(
29+
/HEADERS: (?<headers>(.|\n)*?)\nINIT: (?<init>(.|\n)*?)\n(BODY: (?<bodyMatch>(.|\n)*?)\n)?-- END CF API REQUEST/
30+
);
31+
const {
32+
headers: _headers,
33+
init: _init,
34+
bodyMatch,
35+
} = requestDetails?.groups ?? {};
36+
37+
if (body) {
38+
expect(bodyMatch).toMatch(body);
39+
}
40+
};
41+
}
Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from "node:fs";
2+
import dedent from "ts-dedent";
23

34
/** Write a mock Worker script to disk. */
45
export function writeWorkerSource({
@@ -7,25 +8,42 @@ export function writeWorkerSource({
78
type = "esm",
89
}: {
910
basePath?: string;
10-
format?: "js" | "ts" | "jsx" | "tsx" | "mjs";
11-
type?: "esm" | "sw";
11+
format?: "js" | "ts" | "jsx" | "tsx" | "mjs" | "py";
12+
type?: "esm" | "sw" | "python";
1213
} = {}) {
1314
if (basePath !== ".") {
1415
fs.mkdirSync(basePath, { recursive: true });
1516
}
16-
fs.writeFileSync(
17-
`${basePath}/index.${format}`,
18-
type === "esm"
19-
? `import { foo } from "./another";
20-
export default {
21-
async fetch(request) {
22-
return new Response('Hello' + foo);
23-
},
24-
};`
25-
: `import { foo } from "./another";
26-
addEventListener('fetch', event => {
27-
event.respondWith(new Response('Hello' + foo));
28-
})`
29-
);
17+
18+
let workerContent;
19+
20+
switch (type) {
21+
case "esm":
22+
workerContent = /* javascript */ dedent`
23+
import { foo } from "./another";
24+
export default {
25+
async fetch(request) {
26+
return new Response('Hello' + foo);
27+
},
28+
};`;
29+
break;
30+
case "sw":
31+
workerContent = /* javascript */ dedent`
32+
import { foo } from "./another";
33+
addEventListener('fetch', event => {
34+
event.respondWith(new Response('Hello' + foo));
35+
})`;
36+
break;
37+
case "python":
38+
workerContent = /* python */ dedent`
39+
from js import Response
40+
def on_fetch(request):
41+
return Response.new("Hello World")
42+
`;
43+
break;
44+
}
45+
46+
fs.writeFileSync(`${basePath}/index.${format}`, workerContent);
47+
3048
fs.writeFileSync(`${basePath}/another.${format}`, `export const foo = 100;`);
3149
}

packages/wrangler/src/__tests__/versions/versions.upload.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { http, HttpResponse } from "msw";
2+
import { makeApiRequestAsserter } from "../helpers/assert-request";
23
import { mockAccountId, mockApiToken } from "../helpers/mock-account-id";
34
import { mockConsoleMethods } from "../helpers/mock-console";
45
import { useMockIsTTY } from "../helpers/mock-istty";
@@ -18,6 +19,7 @@ describe("versions upload", () => {
1819
mockApiToken();
1920
const { setIsTTY } = useMockIsTTY();
2021
const std = mockConsoleMethods();
22+
const assertApiRequest = makeApiRequestAsserter(std);
2123

2224
function mockGetScript() {
2325
msw.use(
@@ -172,4 +174,44 @@ describe("versions upload", () => {
172174

173175
expect(std.info).toContain("Retrying API call after error...");
174176
});
177+
178+
test("correctly detects python workers", async () => {
179+
mockGetScript();
180+
mockUploadVersion(true);
181+
mockGetWorkerSubdomain({ enabled: true, previews_enabled: true });
182+
mockSubDomainRequest();
183+
184+
// Setup
185+
writeWranglerConfig({
186+
name: "test-name",
187+
main: "./index.py",
188+
compatibility_flags: ["python_workers"],
189+
});
190+
writeWorkerSource({ type: "python", format: "py" });
191+
setIsTTY(false);
192+
193+
await runWrangler("versions upload");
194+
195+
assertApiRequest(/.*?workers\/scripts\/test-name\/versions/, {
196+
method: "POST",
197+
// Make sure the main module (index.py) has a text/x-python content type
198+
body: /Content-Disposition: form-data; name="index.py"; filename="index.py"\nContent-Type: text\/x-python/,
199+
});
200+
201+
expect(std.out).toMatchInlineSnapshot(`
202+
"┌──────────────────┬────────┬──────────┐
203+
│ Name │ Type │ Size │
204+
├──────────────────┼────────┼──────────┤
205+
│ another.py │ python │ xx KiB │
206+
├──────────────────┼────────┼──────────┤
207+
│ Total (1 module) │ │ xx KiB │
208+
└──────────────────┴────────┴──────────┘
209+
Total Upload: xx KiB / gzip: xx KiB
210+
Worker Startup Time: 500 ms
211+
No bindings found.
212+
Uploaded test-name (TIMINGS)
213+
Worker Version ID: 51e4886e-2db7-4900-8d38-fbfecfeab993
214+
Version Preview URL: https://51e4886e-test-name.test-sub-domain.workers.dev"
215+
`);
216+
});
175217
});

packages/wrangler/src/api/startDevWorker/BundlerController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import assert from "assert";
22
import { readFileSync, realpathSync, writeFileSync } from "fs";
33
import path from "path";
44
import { watch } from "chokidar";
5-
import { noBundleWorker } from "../../deploy/deploy";
65
import { bundleWorker, shouldCheckFetch } from "../../deployment-bundle/bundle";
76
import { getBundleType } from "../../deployment-bundle/bundle-type";
87
import {
98
createModuleCollector,
109
getWrangler1xLegacyModuleReferences,
1110
} from "../../deployment-bundle/module-collection";
11+
import { noBundleWorker } from "../../deployment-bundle/no-bundle-worker";
1212
import { runCustomBuild } from "../../deployment-bundle/run-custom-build";
1313
import { getAssetChangeMessage } from "../../dev";
1414
import { runBuild } from "../../dev/use-esbuild";

packages/wrangler/src/deploy/deploy.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,13 @@ import { configFileName, formatConfigSnippet } from "../config";
1111
import { getBindings, provisionBindings } from "../deployment-bundle/bindings";
1212
import { bundleWorker } from "../deployment-bundle/bundle";
1313
import { printBundleSize } from "../deployment-bundle/bundle-reporter";
14-
import { getBundleType } from "../deployment-bundle/bundle-type";
1514
import { createWorkerUploadForm } from "../deployment-bundle/create-worker-upload-form";
1615
import { logBuildOutput } from "../deployment-bundle/esbuild-plugins/log-build-output";
17-
import {
18-
findAdditionalModules,
19-
writeAdditionalModules,
20-
} from "../deployment-bundle/find-additional-modules";
2116
import {
2217
createModuleCollector,
2318
getWrangler1xLegacyModuleReferences,
2419
} from "../deployment-bundle/module-collection";
20+
import { noBundleWorker } from "../deployment-bundle/no-bundle-worker";
2521
import { validateNodeCompatMode } from "../deployment-bundle/node-compat";
2622
import { loadSourceMaps } from "../deployment-bundle/source-maps";
2723
import { confirm } from "../dialogs";
@@ -61,7 +57,6 @@ import type { Config } from "../config";
6157
import type {
6258
CustomDomainRoute,
6359
Route,
64-
Rule,
6560
ZoneIdRoute,
6661
ZoneNameRoute,
6762
} from "../config/environment";
@@ -1283,22 +1278,3 @@ export async function updateQueueConsumers(
12831278

12841279
return updateConsumers;
12851280
}
1286-
1287-
export async function noBundleWorker(
1288-
entry: Entry,
1289-
rules: Rule[],
1290-
outDir: string | undefined
1291-
) {
1292-
const modules = await findAdditionalModules(entry, rules);
1293-
if (outDir) {
1294-
await writeAdditionalModules(modules, outDir);
1295-
}
1296-
1297-
const bundleType = getBundleType(entry.format, entry.file);
1298-
return {
1299-
modules,
1300-
dependencies: {} as { [path: string]: { bytesInOutput: number } },
1301-
resolvedEntryPointPath: entry.file,
1302-
bundleType,
1303-
};
1304-
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { getBundleType } from "./bundle-type";
2+
import {
3+
findAdditionalModules,
4+
writeAdditionalModules,
5+
} from "./find-additional-modules";
6+
import type { Rule } from "../config/environment";
7+
import type { Entry } from "./entry";
8+
9+
export async function noBundleWorker(
10+
entry: Entry,
11+
rules: Rule[],
12+
outDir: string | undefined
13+
) {
14+
const modules = await findAdditionalModules(entry, rules);
15+
if (outDir) {
16+
await writeAdditionalModules(modules, outDir);
17+
}
18+
19+
const bundleType = getBundleType(entry.format, entry.file);
20+
return {
21+
modules,
22+
dependencies: {} as { [path: string]: { bytesInOutput: number } },
23+
resolvedEntryPointPath: entry.file,
24+
bundleType,
25+
};
26+
}

packages/wrangler/src/versions/upload.ts

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,14 @@ import { createCommand } from "../core/create-command";
1313
import { getBindings, provisionBindings } from "../deployment-bundle/bindings";
1414
import { bundleWorker } from "../deployment-bundle/bundle";
1515
import { printBundleSize } from "../deployment-bundle/bundle-reporter";
16-
import { getBundleType } from "../deployment-bundle/bundle-type";
1716
import { createWorkerUploadForm } from "../deployment-bundle/create-worker-upload-form";
1817
import { getEntry } from "../deployment-bundle/entry";
1918
import { logBuildOutput } from "../deployment-bundle/esbuild-plugins/log-build-output";
20-
import {
21-
findAdditionalModules,
22-
writeAdditionalModules,
23-
} from "../deployment-bundle/find-additional-modules";
2419
import {
2520
createModuleCollector,
2621
getWrangler1xLegacyModuleReferences,
2722
} from "../deployment-bundle/module-collection";
23+
import { noBundleWorker } from "../deployment-bundle/no-bundle-worker";
2824
import { validateNodeCompatMode } from "../deployment-bundle/node-compat";
2925
import { loadSourceMaps } from "../deployment-bundle/source-maps";
3026
import { confirm } from "../dialogs";
@@ -56,7 +52,6 @@ import { printBindings } from "../utils/print-bindings";
5652
import { retryOnAPIFailure } from "../utils/retry";
5753
import type { AssetsOptions } from "../assets";
5854
import type { Config } from "../config";
59-
import type { Rule } from "../config/environment";
6055
import type { Entry } from "../deployment-bundle/entry";
6156
import type { CfPlacement, CfWorkerInit } from "../deployment-bundle/worker";
6257
import type { RetrieveSourceMapFunction } from "../sourcemap";
@@ -891,21 +886,3 @@ Changes to triggers (routes, custom domains, cron schedules, etc) must be applie
891886
function formatTime(duration: number) {
892887
return `(${(duration / 1000).toFixed(2)} sec)`;
893888
}
894-
895-
async function noBundleWorker(
896-
entry: Entry,
897-
rules: Rule[],
898-
outDir: string | undefined
899-
) {
900-
const modules = await findAdditionalModules(entry, rules);
901-
if (outDir) {
902-
await writeAdditionalModules(modules, outDir);
903-
}
904-
905-
return {
906-
modules,
907-
dependencies: {} as { [path: string]: { bytesInOutput: number } },
908-
resolvedEntryPointPath: entry.file,
909-
bundleType: getBundleType(entry.format),
910-
};
911-
}

0 commit comments

Comments
 (0)