Skip to content

Commit a96cd7f

Browse files
authored
Add in-memory archiving option for production (#72)
* Add in-memory archiving for production * Apply suggestions from review
1 parent 4a23de8 commit a96cd7f

File tree

2 files changed

+75
-16
lines changed

2 files changed

+75
-16
lines changed

app/codegen.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
"""Code Generator base module.
22
"""
3+
import base64
4+
import io
35
import shutil
6+
import tarfile
7+
import zipfile
48
from pathlib import Path
59

610
from jinja2 import Environment, FileSystemLoader
@@ -46,7 +50,7 @@ def make_and_write(self, template_name: str, dest_path: Path):
4650
for fname, code in self.rendered_code.items():
4751
(dest_path / template_name / fname).write_text(code)
4852

49-
def make_archive(self, template_name, archive_format, dest_path):
53+
def write_archive(self, template_name, archive_format, dest_path):
5054
"""Creates dist dir with generated code, then makes the archive."""
5155
self.make_and_write(template_name, dest_path)
5256
archive_fname = shutil.make_archive(
@@ -57,3 +61,26 @@ def make_archive(self, template_name, archive_format, dest_path):
5761
)
5862
archive_fname = shutil.move(archive_fname, dest_path / archive_fname.split("/")[-1])
5963
return archive_fname
64+
65+
def writes_archive(self, template_name, archive_format):
66+
"""Writes archive as Base64 string."""
67+
arch_buffer = io.BytesIO()
68+
69+
if archive_format == "zip":
70+
with zipfile.ZipFile(arch_buffer, "w", zipfile.ZIP_DEFLATED) as arch:
71+
for fname, code in self.rendered_code.items():
72+
arch.writestr(f"{template_name}/{fname}", code)
73+
74+
elif archive_format == "tar.gz":
75+
with tarfile.open(fileobj=arch_buffer, mode="w:gz") as arch:
76+
for fname, code in self.rendered_code.items():
77+
tarinfo = tarfile.TarInfo(name=f"{template_name}/{fname}")
78+
code_fileobj = io.BytesIO(code.encode())
79+
tarinfo.size = len(code_fileobj.getvalue())
80+
arch.addfile(tarinfo, code_fileobj)
81+
else:
82+
raise ValueError(f"Wrong archive format '{archive_format}', use one of available formats: zip, tar.gz")
83+
84+
arch_str = base64.b64encode(arch_buffer.getvalue()).decode()
85+
86+
return arch_str

app/streamlit_app.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import shutil
23
import tempfile
34
from pathlib import Path
@@ -7,7 +8,7 @@
78
from utils import import_from_file
89

910
__version__ = "0.1.0"
10-
11+
DEV_MODE = os.getenv("DEV_MODE", 0) == 1
1112

1213
FOLDER_TO_TEMPLATE_NAME = {
1314
"Image Classification": "image_classification",
@@ -127,7 +128,7 @@ def add_content(self):
127128
if len(code): # don't show files which don't have content in them
128129
self.render_code(fname, code)
129130

130-
def add_download(self):
131+
def download_panel_dev(self):
131132
st.markdown("")
132133
col1, col2 = st.beta_columns(2)
133134
with col1:
@@ -136,27 +137,58 @@ def add_download(self):
136137
# https://github.com/streamlit/streamlit/issues/400
137138
# https://github.com/streamlit/streamlit/issues/400#issuecomment-648580840
138139
if st.button("Generate an archive"):
139-
with tempfile.TemporaryDirectory(prefix="", dir=self.codegen.dist_dir) as tmp_dir:
140-
tmp_dir = Path(tmp_dir)
140+
tmp_dir = tempfile.TemporaryDirectory(prefix="", dir=self.codegen.dist_dir)
141+
tmp_dir = Path(tmp_dir.name)
142+
143+
archive_fname = self.codegen.write_archive(self.template_name, archive_format, tmp_dir)
144+
# this is where streamlit serves static files
145+
# ~/site-packages/streamlit/static/static/
146+
dist_path = Path(st.__path__[0]) / "static/static" / tmp_dir
147+
148+
if not dist_path.is_dir():
149+
dist_path.mkdir(parents=True, exist_ok=True)
150+
151+
shutil.copy(archive_fname, dist_path)
152+
st.success(f"Download link : [{archive_fname}](./static/{archive_fname})")
153+
154+
with col2:
155+
self.render_directory(Path(tmp_dir, self.template_name))
156+
157+
def download_panel(self):
158+
st.markdown("")
159+
archive_format = None
160+
mimetype = ""
161+
162+
_, zip_col, tar_col, _ = st.beta_columns(4)
163+
if zip_col.button("📦 Download zip"):
164+
archive_format = "zip"
165+
mimetype = "application/zip"
166+
167+
if tar_col.button("📦 Download tar"):
168+
archive_format = "tar.gz"
169+
mimetype = "application/x-tar"
141170

142-
archive_fname = self.codegen.make_archive(self.template_name, archive_format, tmp_dir)
143-
# this is where streamlit serves static files
144-
# ~/site-packages/streamlit/static/static/
145-
dist_path = Path(st.__path__[0]) / "static/static" / tmp_dir
171+
if archive_format is not None:
172+
archive = self.codegen.writes_archive(self.template_name, archive_format)
146173

147-
if not dist_path.is_dir():
148-
dist_path.mkdir(parents=True, exist_ok=True)
174+
download_link = (
175+
f'Download link: <a href="data:{mimetype};base64,{archive}" '
176+
f'download="{self.template_name}.{archive_format}">'
177+
f"{self.template_name}.{archive_format}</a>"
178+
)
149179

150-
shutil.copy(archive_fname, dist_path)
151-
st.success(f"Download link : [{archive_fname}](./static/{archive_fname})")
180+
st.markdown(download_link, unsafe_allow_html=True)
152181

153-
with col2:
154-
self.render_directory(Path(tmp_dir, self.template_name))
182+
def add_download_panel(self):
183+
if DEV_MODE:
184+
self.download_panel_dev()
185+
else:
186+
self.download_panel()
155187

156188
def run(self):
157189
self.add_sidebar()
158190
self.add_content()
159-
self.add_download()
191+
self.add_download_panel()
160192
st.info(TIP)
161193

162194

0 commit comments

Comments
 (0)