Skip to content

Commit 3fbee7a

Browse files
mscolnickakshayka
andauthored
Starlette migration (marimo-team#606)
* chore: decouple sessions from tornado * docs * fix * Use fast-api * asset tests * fix uvicorn * fix * fix asset again * add ws endpoint * more setup improvements * fix tests * fixes * fix virutal file * add more tests for assets and files * more tests * dependencies * py 3.9 compatibility * py 3.10 compat * py 3.9 compat * conditional config based on pydantic version * maybe a better shutdown process * fixes * shutdown fixes * reconnect fixes * handle missing app config * Fix DeleteRequest * Fix some unit tests * comments * fastapi lower bound * Aka/starlette (marimo-team#624) * start starlette migration * tests passing, merge routes, dataclases and camelcase --------- Co-authored-by: Akshay Agrawal <[email protected]> * mpl improvements (marimo-team#625) * starlette lower bound * Fix lint, typechecks, some import regressions * nit index.html * fix envinfo * add ws test * add ws test * fix checkalive * 2 more ws test * Restore signals in kernel process to fix process teardown * typecheck * Get unit tests passing again * starlette exclude 0.36.0 * Remove dependence on tornado for logging * better mpl * improve asset mount * Starlette migration - refactor sessions (marimo-team#636) * delete old code, move server2 * remove tornado logging leftovers * test interrupt * more endpoint tests * fixes to sessions.py * join threads in test * Restore signal handlers in mpl.interactive * Use select event loop on windows * Fix interrupt handler * fixes, lint * Get server tests passing on Windows * lint * add httpx to dev deps * wait 20ms for open_url_in_browser * fix cancellation logic for listener * add authentication for edit mode, pycheck * lint/typecheck * refactor tests * dep versions * python 3.8 compatibility * fix on_stop reader removal logic * fix test.yaml * fix test.yml, playwright * refactor waitForDownload * skip export as png, not working in ci * windows typecheck * fix typig log formatter * fix * fix dev mode * edit pyproject.toml * add back server token middleware --------- Co-authored-by: Akshay Agrawal <[email protected]>
1 parent 37c0143 commit 3fbee7a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+3527
-2032
lines changed

.github/workflows/test.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ jobs:
9292
if: ${{ matrix.python-version == '3.9' || matrix.python-version == '3.10' }}
9393
run: |
9494
mypy --config-file pyproject.toml marimo/
95+
# This step is needed since some of our tests rely on the index.html file
96+
- name: Create assets directory, copy over index.html
97+
run: |
98+
mkdir -p marimo/_static/assets
99+
cp frontend/index.html marimo/_static/index.html
100+
cp frontend/public/favicon.ico marimo/_static/favicon.ico
95101
- name: Test
96102
run: |
97103
pytest -v tests/ -k "not test_cli" --cov=marimo --cov-branch

frontend/e2e-tests/helper.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,15 @@ export async function exportAsHTMLAndTakeScreenshot(page: Page) {
9595
await page.waitForLoadState("networkidle");
9696

9797
// Start waiting for download before clicking.
98-
const downloadPromise = page.waitForEvent("download");
99-
await page.getByTestId("notebook-menu-dropdown").click();
100-
await page.getByText("Export as HTML").click();
101-
const download = await downloadPromise;
98+
const [download] = await Promise.all([
99+
page.waitForEvent("download"),
100+
page
101+
.getByTestId("notebook-menu-dropdown")
102+
.click()
103+
.then(() => {
104+
return page.getByText("Export as HTML").click();
105+
}),
106+
]);
102107

103108
// Wait for the download process to complete and save the downloaded file somewhere.
104109
const path = `e2e-tests/exports/${download.suggestedFilename()}`;
@@ -126,11 +131,15 @@ export async function exportAsPNG(page: Page) {
126131
// Wait for networkidle so that the notebook is fully loaded
127132
await page.waitForLoadState("networkidle");
128133

129-
// Start waiting for download before clicking.
130-
const downloadPromise = page.waitForEvent("download");
131-
await page.getByTestId("notebook-menu-dropdown").click();
132-
await page.getByText("Export as PNG").click();
133-
const download = await downloadPromise;
134+
const [download] = await Promise.all([
135+
page.waitForEvent("download"),
136+
page
137+
.getByTestId("notebook-menu-dropdown")
138+
.click()
139+
.then(() => {
140+
return page.getByText("Export as PNG").click();
141+
}),
142+
]);
134143

135144
// Wait for the download process to complete and save the downloaded file somewhere.
136145
const path = `e2e-tests/screenshots/${download.suggestedFilename()}`;

frontend/e2e-tests/kitchen-sink.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ import {
77
takeScreenshot,
88
} from "./helper";
99

10+
const appUrl = getAppUrl("kitchen_sink.py//edit");
11+
1012
test("can screenshot and export as html", async ({ page }) => {
11-
const appUrl = getAppUrl("kitchen_sink.py//edit");
1213
await page.goto(appUrl);
1314

1415
await takeScreenshot(page, __filename);
1516
await exportAsHTMLAndTakeScreenshot(page);
17+
});
18+
19+
test.skip("can screenshot and export as png", async ({ page }) => {
20+
await page.goto(appUrl);
21+
1622
await exportAsPNG(page);
1723
});

frontend/e2e-tests/output.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ test("it can clear and append output", async ({ page }) => {
1616

1717
// Test the end state of the output
1818
await expect(page.getByText("Appended!")).toBeVisible();
19-
await expect(page.getByText("Loading 0/5")).toBeVisible();
20-
await expect(page.getByText("Loading 4/5")).toBeVisible();
19+
await expect(page.getByText("Loading 0/5").first()).toBeVisible();
20+
await expect(page.getByText("Loading 4/5").first()).toBeVisible();
2121

2222
// Test that Cleared does not exist
2323
await expect(page.getByText("Cleared!")).not.toBeVisible();

frontend/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636
}
3737
</script>
3838
<marimo-filename hidden>{{ filename }}</marimo-filename>
39-
<marimo-mode data-mode={{ mode }} hidden></marimo-mode>
40-
<marimo-version data-version={{ version }} hidden></marimo-version>
39+
<marimo-mode data-mode='{{ mode }}' hidden></marimo-mode>
40+
<marimo-version data-version='{{ version }}' hidden></marimo-version>
4141
<marimo-user-config data-config='{{ user_config }}' hidden></marimo-user-config>
4242
<marimo-app-config data-config='{{ app_config }}' hidden></marimo-app-config>
4343
<marimo-server-token data-token='{{ server_token }}' hidden></marimo-server-token>

frontend/src/components/editor/Controls.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ export const Controls = ({
9393
<ShutdownButton
9494
onShutdown={() => {
9595
onShutdown();
96-
window.close();
96+
// Let the shutdown process start before closing the window.
97+
setTimeout(() => {
98+
window.close();
99+
}, 200);
97100
}}
98101
/>
99102
</div>

frontend/src/core/network/requests.ts

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ function createNetworkRequests(): EditRequests & RunRequests {
3636
}
3737

3838
return API.post<SetComponentValuesRequest>(
39-
"/kernel/set_ui_element_value/",
39+
"/kernel/set_ui_element_value",
4040
{
4141
objectIds: objectIds,
4242
values: values,
4343
}
4444
);
4545
},
4646
sendRename: (filename: string | null) => {
47-
return API.post<RenameRequest>("/kernel/rename/", {
47+
return API.post<RenameRequest>("/kernel/rename", {
4848
filename: filename,
4949
});
5050
},
@@ -59,25 +59,25 @@ function createNetworkRequests(): EditRequests & RunRequests {
5959
"cell codes and configs must be the same length"
6060
);
6161

62-
return API.post<SaveKernelRequest>("/kernel/save/", request);
62+
return API.post<SaveKernelRequest>("/kernel/save", request);
6363
},
6464
sendFormat: (request: FormatRequest) => {
6565
return API.post<FormatRequest, FormatResponse>(
66-
"/kernel/format/",
66+
"/kernel/format",
6767
request
6868
).then((res) => res.codes);
6969
},
7070
sendInterrupt: () => {
71-
return API.post("/kernel/interrupt/", {});
71+
return API.post("/kernel/interrupt", {});
7272
},
7373
sendShutdown: () => {
74-
return API.post("/kernel/shutdown/", {});
74+
return API.post("/kernel/shutdown", {});
7575
},
7676
sendRun: (cellIds: CellId[], codes: string[]) => {
7777
// Validate same length
7878
invariant(cellIds.length === codes.length, "must be the same length");
7979

80-
return API.post<RunRequest>("/kernel/run/", {
80+
return API.post<RunRequest>("/kernel/run", {
8181
cellIds: cellIds,
8282
codes: codes,
8383
});
@@ -89,53 +89,50 @@ function createNetworkRequests(): EditRequests & RunRequests {
8989
"must be the same length"
9090
);
9191

92-
return API.post<InstantiateRequest>("/kernel/instantiate/", request);
92+
return API.post<InstantiateRequest>("/kernel/instantiate", request);
9393
},
9494
sendDeleteCell: (cellId) => {
95-
return API.post<DeleteRequest>("/kernel/delete/", {
95+
return API.post<DeleteRequest>("/kernel/delete", {
9696
cellId: cellId,
9797
});
9898
},
9999
sendDirectoryAutocompleteRequest: (prefix) => {
100100
return API.post<
101101
SendDirectoryAutocompleteRequest,
102102
SendDirectoryAutocompleteResponse
103-
>("/kernel/directory_autocomplete/", {
103+
>("/kernel/directory_autocomplete", {
104104
prefix: prefix,
105105
});
106106
},
107107
sendCodeCompletionRequest: (request) => {
108108
return API.post<CodeCompletionRequest>(
109-
"/kernel/code_autocomplete/",
109+
"/kernel/code_autocomplete",
110110
request
111111
);
112112
},
113113
saveUserConfig: (request) => {
114114
return API.post<SaveUserConfigRequest>(
115-
"/kernel/save_user_config/",
115+
"/kernel/save_user_config",
116116
request
117117
);
118118
},
119119
saveAppConfig: (request) => {
120-
return API.post<SaveAppConfigRequest>(
121-
"/kernel/save_app_config/",
122-
request
123-
);
120+
return API.post<SaveAppConfigRequest>("/kernel/save_app_config", request);
124121
},
125122
saveCellConfig: (request) => {
126123
return API.post<SaveCellConfigRequest>(
127-
"/kernel/set_cell_config/",
124+
"/kernel/set_cell_config",
128125
request
129126
);
130127
},
131128
sendFunctionRequest: (request) => {
132-
return API.post<SendFunctionRequest>("/kernel/function_call/", request);
129+
return API.post<SendFunctionRequest>("/kernel/function_call", request);
133130
},
134131
sendStdin: (request) => {
135-
return API.post<SendStdin>("/kernel/stdin/", request);
132+
return API.post<SendStdin>("/kernel/stdin", request);
136133
},
137134
readCode: () => {
138-
return API.post<{}, { contents: string }>("/kernel/read_code/", {});
135+
return API.post<{}, { contents: string }>("/kernel/read_code", {});
139136
},
140137
};
141138
}

frontend/src/core/websocket/useMarimoWebSocket.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,5 +270,5 @@ export function useMarimoWebSocket(opts: {
270270
function createWsUrl(sessionId: string): string {
271271
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
272272

273-
return `${protocol}://${window.location.host}/iosocket?session_id=${sessionId}`;
273+
return `${protocol}://${window.location.host}/ws?session_id=${sessionId}`;
274274
}

frontend/src/mocks/socket.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Logger } from "../utils/Logger";
77
export function createMockServer() {
88
const fakeURL = `ws://${
99
window.location.host
10-
}/iosocket?kernel_id=${getKernelId()}&uuid=${UUID}`;
10+
}/ws?kernel_id=${getKernelId()}&uuid=${UUID}`;
1111
const mockServer = new Server(fakeURL);
1212

1313
mockServer.on("connection", (socket) => {

frontend/vite.config.mts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import tsconfigPaths from "vite-tsconfig-paths";
55
import { JSDOM } from "jsdom";
66

77
const SERVER_PORT = process.env.SERVER_PORT || 2718;
8+
const HOST = process.env.HOST || "127.0.0.1";
9+
const TARGET = `http://${HOST}:${SERVER_PORT}`;
810
const isDev = process.env.NODE_ENV === "development";
911
const isStorybook = process.env.npm_lifecycle_script?.includes("storybook");
1012

@@ -18,9 +20,8 @@ const htmlDevPlugin = (): Plugin => {
1820
}
1921

2022
// fetch html from server
21-
const serverHtml = await fetch(`http://localhost:${SERVER_PORT}/`).then(
22-
(res) => res.text()
23-
);
23+
const serverHtmlResponse = await fetch(TARGET);
24+
const serverHtml = await serverHtmlResponse.text();
2425

2526
const serverDoc = new JSDOM(serverHtml).window.document;
2627
const devDoc = new JSDOM(html).window.document;
@@ -66,19 +67,19 @@ export default defineConfig({
6667
port: 3000,
6768
proxy: {
6869
"/api": {
69-
target: `http://localhost:${SERVER_PORT}`,
70+
target: TARGET,
7071
changeOrigin: true,
7172
},
7273
"/@file": {
73-
target: `http://localhost:${SERVER_PORT}`,
74+
target: TARGET,
7475
changeOrigin: true,
7576
},
76-
"/iosocket": {
77-
target: `ws://localhost:${SERVER_PORT}`,
77+
"/ws": {
78+
target: `ws://${HOST}:${SERVER_PORT}`,
7879
ws: true,
7980
changeOrigin: true,
8081
headers: {
81-
origin: `http://localhost:${SERVER_PORT}`,
82+
origin: TARGET,
8283
},
8384
},
8485
},

0 commit comments

Comments
 (0)