Skip to content

Commit e2c316d

Browse files
freddyaboultoncontrastive-vaegradio-pr-bot
authored
Fix Hot Reload of Custom CSS (#12549)
* changes * changes * changes * add changeset * changes * Fix reload mode css * revert * revert * revert * revert * revert * add changeset * fix code * hot reload --------- Co-authored-by: Abubakar Abid <[email protected]> Co-authored-by: gradio-pr-bot <[email protected]>
1 parent cf27938 commit e2c316d

File tree

4 files changed

+131
-59
lines changed

4 files changed

+131
-59
lines changed

.changeset/eight-paths-own.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"gradio": patch
3+
---
4+
5+
fix:Fix Hot Reload of Custom CSS

gradio/blocks.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2558,10 +2558,6 @@ def reverse(text):
25582558
"""
25592559
from gradio.routes import App
25602560

2561-
if self._is_running_in_reload_thread:
2562-
# We have already launched the demo
2563-
return None, None, None # type: ignore
2564-
25652561
theme = theme if theme is not None else self._deprecated_theme
25662562
css = css if css is not None else self._deprecated_css
25672563
css_paths = css_paths if css_paths is not None else self._deprecated_css_paths
@@ -2579,6 +2575,10 @@ def reverse(text):
25792575
self.head_paths = head_paths
25802576
self._set_html_css_theme_variables()
25812577

2578+
if self._is_running_in_reload_thread:
2579+
# We have already launched the demo
2580+
return None, None, None # type: ignore
2581+
25822582
if not self.exited:
25832583
self.__exit__()
25842584

gradio/utils.py

Lines changed: 22 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
import ast
65
import asyncio
76
import copy
87
import functools
@@ -156,6 +155,17 @@ class BaseReloader(ABC):
156155
def running_app(self) -> App:
157156
pass
158157

158+
def get_attribute(self, attr: str, demo) -> Any:
159+
if (
160+
hasattr(demo, f"_deprecated_{attr}")
161+
and getattr(demo, f"_deprecated_{attr}") is not None
162+
):
163+
return getattr(demo, f"_deprecated_{attr}")
164+
elif hasattr(demo, attr) and getattr(demo, attr) is not None:
165+
return getattr(demo, attr)
166+
else:
167+
return getattr(self.running_app.blocks, attr)
168+
159169
def swap_blocks(self, demo: Blocks):
160170
assert self.running_app.blocks # noqa: S101
161171
# Copy over the blocks to get new components and events but
@@ -166,11 +176,13 @@ def swap_blocks(self, demo: Blocks):
166176
demo.is_running = True
167177
demo.allowed_paths = self.running_app.blocks.allowed_paths
168178
demo.blocked_paths = self.running_app.blocks.blocked_paths
169-
demo.theme = self.running_app.blocks.theme
170-
demo.head_paths = self.running_app.blocks.head_paths
171-
demo.css = self.running_app.blocks.css
172-
demo.head = self.running_app.blocks.head
173-
demo.css_paths = self.running_app.blocks.css_paths
179+
180+
demo.theme = self.get_attribute("theme", demo)
181+
demo.css = self.get_attribute("css", demo)
182+
demo.css_paths = self.get_attribute("css_paths", demo)
183+
demo.js = self.get_attribute("js", demo)
184+
demo.head = self.get_attribute("head", demo)
185+
demo.head_paths = self.get_attribute("head_paths", demo)
174186
demo._set_html_css_theme_variables()
175187
self.running_app.state_holder.set_blocks(demo)
176188
for session in self.running_app.state_holder.session_data.values():
@@ -295,51 +307,6 @@ def swap_blocks(self, demo: Blocks):
295307
self.alert_change("reload")
296308

297309

298-
def _remove_if_name_main_codeblock(file_path: str, encoding: str = "utf-8"):
299-
"""Parse the file, remove the gr.no_reload code blocks, and write the file back to disk.
300-
301-
Parameters:
302-
file_path (str): The path to the file to remove the no_reload code blocks from.
303-
"""
304-
305-
with open(file_path, encoding=encoding) as file:
306-
code = file.read()
307-
308-
tree = ast.parse(code)
309-
310-
def _is_if_name_main(expr: ast.AST) -> bool:
311-
"""Find the if __name__ == '__main__': block."""
312-
return (
313-
isinstance(expr, ast.If)
314-
and isinstance(expr.test, ast.Compare)
315-
and isinstance(expr.test.left, ast.Name)
316-
and expr.test.left.id == "__name__"
317-
and len(expr.test.ops) == 1
318-
and isinstance(expr.test.ops[0], ast.Eq)
319-
and isinstance(expr.test.comparators[0], ast.Constant)
320-
and expr.test.comparators[0].value == "__main__"
321-
)
322-
323-
# Find the positions of the code blocks to load
324-
for node in ast.walk(tree):
325-
if _is_if_name_main(node):
326-
assert isinstance(node, ast.If) # noqa: S101
327-
node.body = [ast.Pass(lineno=node.lineno, col_offset=node.col_offset)]
328-
329-
# convert tree to string
330-
code_removed = compile(tree, filename=file_path, mode="exec")
331-
return code_removed
332-
333-
334-
def _find_module(source_file: Path) -> ModuleType | None:
335-
for s, v in sys.modules.items():
336-
if s not in {"__main__", "__mp_main__"} and getattr(v, "__file__", None) == str(
337-
source_file
338-
):
339-
return v
340-
return None
341-
342-
343310
def watchfn_spaces(reloader: SpacesReloader):
344311
try:
345312
spaces_version = importlib.metadata.version("spaces")
@@ -423,8 +390,8 @@ def is_in_watch_dirs_and_not_sitepackages(file_path):
423390
# Need to import the module in this thread so that the
424391
# module is available in the namespace of this thread
425392
module = reloader.watch_module
426-
no_reload_source_code = _remove_if_name_main_codeblock(
427-
str(reloader.demo_file), encoding=reloader.encoding
393+
no_reload_source_code = Path(str(reloader.demo_file)).read_text(
394+
encoding=reloader.encoding
428395
)
429396
# Reset the context to id 0 so that the loaded module is the same as the original
430397
# See https://github.com/gradio-app/gradio/issues/10253
@@ -457,8 +424,8 @@ def is_in_watch_dirs_and_not_sitepackages(file_path):
457424

458425
NO_RELOAD.set(False)
459426
# Remove the gr.no_reload code blocks and exec in the new module's dict
460-
no_reload_source_code = _remove_if_name_main_codeblock(
461-
str(reloader.demo_file), encoding=reloader.encoding
427+
no_reload_source_code = Path(str(reloader.demo_file)).read_text(
428+
encoding=reloader.encoding
462429
)
463430
exec(no_reload_source_code, module.__dict__)
464431

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { test, expect } from "@playwright/test";
2+
import { spawnSync } from "node:child_process";
3+
import { launch_app_background, kill_process } from "./utils";
4+
import { join } from "path";
5+
6+
let _process;
7+
8+
test.beforeAll(() => {
9+
const demo = `
10+
import gradio as gr
11+
12+
custom_css= """
13+
h1 { color: #6D94C5; text-align: center; }
14+
.gradio-container { max-width: 900px !important; }
15+
"""
16+
17+
# gr.HTML style works for hot reload
18+
with gr.Blocks(css=custom_css,
19+
theme=gr.themes.Soft()) as demo:
20+
gr.HTML("<h1> AI Travel Agent</h1>")
21+
gr.Chatbot(height=400)
22+
gr.Textbox(placeholder="Type here...")
23+
24+
if __name__ == "__main__":
25+
demo.launch()
26+
`;
27+
// write contents of demo to a local 'run.py' file
28+
spawnSync(`echo '${demo}' > ${join(process.cwd(), "run.py")}`, {
29+
shell: true,
30+
stdio: "pipe",
31+
env: {
32+
...process.env,
33+
PYTHONUNBUFFERED: "true"
34+
}
35+
});
36+
});
37+
38+
test.afterAll(() => {
39+
if (_process) kill_process(_process);
40+
spawnSync(`rm ${join(process.cwd(), "run.py")}`, {
41+
shell: true,
42+
stdio: "pipe",
43+
env: {
44+
...process.env,
45+
PYTHONUNBUFFERED: "true"
46+
}
47+
});
48+
});
49+
50+
test("gradio dev mode correctly reloads custom css", async ({ page }) => {
51+
test.setTimeout(20 * 1000);
52+
53+
try {
54+
const { _process: server_process, port: port } =
55+
await launch_app_background(
56+
`gradio ${join(process.cwd(), "run.py")}`,
57+
process.cwd()
58+
);
59+
60+
_process = server_process;
61+
console.log("Connected to port", port);
62+
63+
await page.goto(`http://localhost:${port}`);
64+
await page.waitForTimeout(2_000);
65+
66+
await expect(page.locator("h1")).toHaveCSS("color", "rgb(109, 148, 197)");
67+
68+
const demo1 = `
69+
import gradio as gr
70+
71+
custom_css= """
72+
h1 { color: red; text-align: center; }
73+
.gradio-container { max-width: 900px !important; }
74+
"""
75+
76+
# gr.HTML style works for hot reload
77+
with gr.Blocks(css=custom_css,
78+
theme=gr.themes.Soft()) as demo:
79+
gr.HTML("<h1> AI Travel Agent</h1>")
80+
gr.Chatbot(height=400)
81+
gr.Textbox(placeholder="Type here...")
82+
83+
if __name__ == "__main__":
84+
demo.launch()
85+
`;
86+
// write contents of demo to a local 'run.py' file
87+
spawnSync(`echo '${demo1}' > ${join(process.cwd(), "run.py")}`, {
88+
shell: true,
89+
stdio: "pipe",
90+
env: {
91+
...process.env,
92+
PYTHONUNBUFFERED: "true"
93+
}
94+
});
95+
96+
await expect(page.locator("h1")).toHaveCSS("color", "rgb(255, 0, 0)");
97+
} finally {
98+
if (_process) kill_process(_process);
99+
}
100+
});

0 commit comments

Comments
 (0)