Skip to content

Instantly share code, notes, and snippets.

@exzhawk
Last active October 8, 2024 05:52
Show Gist options
  • Save exzhawk/33e5dcfc8859e3b6ff4e5269b1ba0ba4 to your computer and use it in GitHub Desktop.
Save exzhawk/33e5dcfc8859e3b6ff4e5269b1ba0ba4 to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
import os
from html.parser import HTMLParser
import dash
import pandas as pd
import plotly.express as px
import requests
from dash import html, dcc, dash_table, Input, Output
def patch_file(file_path: str, content: bytes, extra: dict = None) -> bytes:
if file_path == 'index.html':
index_html_content = content.decode('utf8')
extra_jsons = f'''
var patched_jsons_content={{
{','.join(["'/" + k + "':" + v.decode("utf8") + "" for k, v in extra.items()])}
}};
'''
patched_content = index_html_content.replace(
'<footer>',
f'''
<footer>
<script>
''' + extra_jsons + '''
const origFetch = window.fetch;
window.fetch = function () {
const e = arguments[0]
if (patched_jsons_content.hasOwnProperty(e)) {
return Promise.resolve({
json: () => Promise.resolve(patched_jsons_content[e]),
headers: new Headers({'content-type': 'application/json'}),
status: 200,
});
} else {
return origFetch.apply(this, arguments)
}
}
</script>
'''
).replace(
'href="/',
'href="'
).replace(
'src="/',
'src="'
)
return patched_content.encode('utf8')
else:
return content
def write_file(file_path: str, content: bytes, target_dir='target', ):
target_file_path = os.path.join(target_dir, file_path.lstrip('/').split('?')[0])
target_leaf_dir = os.path.dirname(target_file_path)
os.makedirs(target_leaf_dir, exist_ok=True)
with open(target_file_path, 'wb') as f:
f.write(content)
pass
class ExternalResourceParser(HTMLParser):
def __init__(self):
super().__init__()
self.resources = []
def handle_starttag(self, tag, attrs):
if tag == 'link':
for k, v in attrs:
if k == 'href':
self.resources.append(v)
if tag == 'script':
for k, v in attrs:
if k == 'src':
self.resources.append(v)
def make_static(base_url, target_dir='target'):
index_html_bytes = requests.get(base_url).content
json_paths = ['_dash-layout', '_dash-dependencies', ]
extra_json = {}
for json_path in json_paths:
json_content = requests.get(base_url + json_path).content
extra_json[json_path] = json_content
patched_bytes = patch_file('index.html', index_html_bytes, extra=extra_json)
write_file('index.html', patched_bytes, target_dir)
parser = ExternalResourceParser()
parser.feed(patched_bytes.decode('utf8'))
extra_js = [
'_dash-component-suites/dash/dcc/async-graph.js',
'_dash-component-suites/dash/dcc/async-plotlyjs.js',
'_dash-component-suites/dash/dash_table/async-table.js',
'_dash-component-suites/dash/dash_table/async-highlight.js'
]
for resource_url in parser.resources + extra_js:
resource_url_full = base_url + resource_url
print(f'get {resource_url_full}')
resource_bytes = requests.get(resource_url_full).content
patched_bytes = patch_file(resource_url, resource_bytes)
write_file(resource_url, patched_bytes, target_dir)
def main():
port = 9050
app = dash.Dash(__name__)
df = pd.DataFrame({
"Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
"Amount": [4, 1, 2, 2, 4, 5],
"City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]
})
fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group")
app.layout = html.Div(children=[
html.Button('save static', id='save', n_clicks=0),
html.Span('', id='saved'),
html.H1(children='Hello Dash'),
html.Div(children='''
Dash: A web application framework for your data.
'''),
dcc.Graph(
id='example-graph',
figure=fig
),
dash_table.DataTable(
id='table',
columns=[{"name": i, "id": i} for i in df.columns],
data=df.to_dict('records'),
)
])
@app.callback(
Output('saved', 'children'),
Input('save', 'n_clicks'),
)
def save_result(n_clicks):
if n_clicks == 0:
return 'not saved'
else:
make_static(f'http://127.0.0.1:{port}/')
return 'saved'
app.run_server(debug=False, port=port)
if __name__ == '__main__':
main()
@RajonDawn
Copy link

Hi, exzhawk

I have run your code in vscode, and click the "save" button in the server, then create a 'target' folder normally. But there is no figure graph in the 'index.html', only the table and other divs in the page.

There is the console log in html page. and an error in vscode terminal. Could you help me with this issue? Thanks.
code
index
server

@lesart
Copy link

lesart commented Mar 26, 2024

Hi. Very nice snippet!!!
For those encountering the issue mentioned by @RajonDawn, just change these:
extra_js = [
"_dash-component-suites/dash/dcc/async-graph.js",
# "_dash-component-suites/dash/dcc/async-plotlyjs.js", # TODO: remove
"_dash-component-suites/dash/dash_table/async-table.js",
"_dash-component-suites/dash/dash_table/async-highlight.js",
"_dash-component-suites/plotly/package_data/plotly.min.js", # TODO: add
]

@YifanYangEbidanko
Copy link

Just want to say a big thank you for creating this snippet 3 years ago, really nice snippet, saved me from tons of time and frustration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment