Loading src/doc2tosca.py +20 −19 Original line number Original line Diff line number Diff line Loading @@ -6,6 +6,7 @@ Generate tosca definitions from Docx specfication import sys import sys import os import os import re import re import logging from io import StringIO from io import StringIO import docx import docx Loading Loading @@ -141,9 +142,9 @@ def get_content(doc): ret.append(Table(element, body)) ret.append(Table(element, body)) table_count = table_count + 1 table_count = table_count + 1 # else: # else: # print("Non paragraph or table " + str(type(element))) # logging.info("Non paragraph or table " + str(type(element))) print("Paragraphs: " + str(parag_count)) logging.info("Paragraphs: " + str(parag_count)) print("Tables: " + str(table_count)) logging.info("Tables: " + str(table_count)) return ret return ret Loading @@ -160,7 +161,7 @@ def find_sect(sect_to_find, start_idx, doc_content): break break start_idx = start_idx + 1 start_idx = start_idx + 1 print("FOUND " + sect_to_find + " at " + str(start_idx)) logging.info("FOUND " + sect_to_find + " at " + str(start_idx)) return start_idx return start_idx Loading Loading @@ -249,7 +250,7 @@ def gen_tables_btwn(a_id, b_id, content, buf): for idx in range(a_id, b_id): for idx in range(a_id, b_id): if idx >= len(content): if idx >= len(content): print( logging.info( range_err_mess.format(a_id, b_id, idx, len(content))) range_err_mess.format(a_id, b_id, idx, len(content))) return definitions_count return definitions_count Loading @@ -257,7 +258,7 @@ def gen_tables_btwn(a_id, b_id, content, buf): if isinstance(tmp_elem, Paragraph) and \ if isinstance(tmp_elem, Paragraph) and \ is_lvl2_section_hdn(tmp_elem.text): is_lvl2_section_hdn(tmp_elem.text): print(tmp_elem.text) logging.info(tmp_elem.text) write_subsection_to_file(tmp_elem.text.split("\t")[1], buf) write_subsection_to_file(tmp_elem.text.split("\t")[1], buf) definitions_count = definitions_count + 1 definitions_count = definitions_count + 1 Loading Loading @@ -326,14 +327,14 @@ def generate_templates( the MODELS dictionary the MODELS dictionary ''' ''' if isinstance(filename, str): if isinstance(filename, str): print("Opening " + filename) logging.info("Opening " + filename) init_models(yaml_root, spec_ver, tosc_ver) init_models(yaml_root, spec_ver, tosc_ver) try: try: sol_001 = docx.Document(filename) sol_001 = docx.Document(filename) except: except: print("Error opening the submitted Docx file") logging.info("Error opening the submitted Docx file") raise ValueError("Cannot open the submitted Docx file") raise ValueError("Cannot open the submitted Docx file") content = get_content(sol_001) content = get_content(sol_001) Loading @@ -347,13 +348,13 @@ def generate_templates( count = gen_tables_btwn( count = gen_tables_btwn( sect.from_id, sect.to_id, content, MODELS[model]['buf'] sect.from_id, sect.to_id, content, MODELS[model]['buf'] ) ) print("Printed " + str(count) + " types to " + model) logging.info("Printed " + str(count) + " types to " + model) else: else: if sect.letter == "A" or sect.letter == "E": if sect.letter == "A" or sect.letter == "E": count = generate_examples_between( count = generate_examples_between( sect.from_id, sect.to_id, content, EXAMPLES sect.from_id, sect.to_id, content, EXAMPLES ) ) print("Printed {} types to Annex {}".format( logging.info("Printed {} types to Annex {}".format( str(count), sect.letter) str(count), sect.letter) ) ) Loading @@ -367,7 +368,7 @@ def print_to_files(prefix=None): if prefix is not None: if prefix is not None: mod['fn'] = os.path.join(prefix, mod['fn']) mod['fn'] = os.path.join(prefix, mod['fn']) print("Writing to " + mod['fn']) logging.info("Writing to " + mod['fn']) mod['fd'] = open(mod['fn'], 'w') mod['fd'] = open(mod['fn'], 'w') mod['buf'].seek(0) mod['buf'].seek(0) mod['fd'].write(mod['buf'].read()) mod['fd'].write(mod['buf'].read()) Loading @@ -379,7 +380,7 @@ def print_to_files(prefix=None): fnm = os.path.join(prefix, "example_"+EXAMPLES[k].filename) fnm = os.path.join(prefix, "example_"+EXAMPLES[k].filename) else: else: fnm = EXAMPLES[k].filename fnm = EXAMPLES[k].filename print("Writing example file: " + fnm) logging.info("Writing example file: " + fnm) with open(fnm, 'w') as newf: with open(fnm, 'w') as newf: newf.write(EXAMPLES[k].text) newf.write(EXAMPLES[k].text) newf.write("\n") newf.write("\n") Loading Loading @@ -410,8 +411,8 @@ if __name__ == "__main__": try: try: SOL001_FN = sys.argv[1] SOL001_FN = sys.argv[1] except: except: print('Error: Filename missing or filename not a docx document') logging.info('Error: Filename missing or filename not a docx document') print('Usage: doc2tosca <docx-with-tosca-definitions>') logging.info('Usage: doc2tosca <docx-with-tosca-definitions>') sys.exit(1) sys.exit(1) ver = parse_version_from_filename(SOL001_FN) ver = parse_version_from_filename(SOL001_FN) Loading src/web_app.py +51 −27 Original line number Original line Diff line number Diff line Loading @@ -7,6 +7,8 @@ Web app to offer a frontend to the doc2tosca and tosca2doc tools import os import os import shutil import shutil import tempfile import tempfile import logging import traceback from zipfile import ZipFile from zipfile import ZipFile from flask import Flask, flash, request, redirect, send_file, render_template, g from flask import Flask, flash, request, redirect, send_file, render_template, g Loading @@ -17,6 +19,7 @@ import config import tosca2doc import tosca2doc import doc2tosca import doc2tosca class ReverseProxied(object): class ReverseProxied(object): def __init__(self, the_app, script_name=None, scheme=None, server=None): def __init__(self, the_app, script_name=None, scheme=None, server=None): Loading @@ -40,6 +43,7 @@ class ReverseProxied(object): environ['HTTP_HOST'] = server environ['HTTP_HOST'] = server return self.app(environ, start_response) return self.app(environ, start_response) app = Flask(__name__) app = Flask(__name__) app.wsgi_app = ReverseProxied(app.wsgi_app, script_name='/tosca-ie') app.wsgi_app = ReverseProxied(app.wsgi_app, script_name='/tosca-ie') Loading @@ -57,6 +61,7 @@ MISSING_MODULE_ERR_MSG = \ 'Error: some TOSCA module missing. Make sure to \ 'Error: some TOSCA module missing. Make sure to \ upload definitions for NSD, VNFD, PNFD.' upload definitions for NSD, VNFD, PNFD.' def allowed_file(filename): def allowed_file(filename): ''' ''' Check filename is in the list of allowed extensions Check filename is in the list of allowed extensions Loading @@ -64,6 +69,7 @@ def allowed_file(filename): return '.' in filename and \ return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route("/") @app.route("/") def hello(): def hello(): ''' ''' Loading @@ -83,6 +89,7 @@ def hello(): rev_link=link rev_link=link ) ) @app.route("/doc2tosca-info") @app.route("/doc2tosca-info") def doc2tosca_info(): def doc2tosca_info(): ''' ''' Loading @@ -90,6 +97,7 @@ def doc2tosca_info(): ''' ''' return render_template("./doc2tosca-details.html", VERSION=config.VERSION) return render_template("./doc2tosca-details.html", VERSION=config.VERSION) @app.route("/tosca2doc-info") @app.route("/tosca2doc-info") def tosca2doc_info(): def tosca2doc_info(): ''' ''' Loading @@ -97,6 +105,7 @@ def tosca2doc_info(): ''' ''' return render_template("./tosca2doc-details.html", VERSION=config.VERSION) return render_template("./tosca2doc-details.html", VERSION=config.VERSION) @app.route("/tosca2doc", methods=['POST']) @app.route("/tosca2doc", methods=['POST']) def mk_tosca2doc(): def mk_tosca2doc(): ''' ''' Loading Loading @@ -149,7 +158,8 @@ def mk_tosca2doc(): #return ("No content found") #return ("No content found") def get_all_yaml_file_paths(directory): def get_all_yaml_file_paths_and_logs(directory): ''' ''' Finds yaml files within a directory and sub directories and Finds yaml files within a directory and sub directories and returns the list of paths returns the list of paths Loading @@ -159,12 +169,13 @@ def get_all_yaml_file_paths(directory): for root, _, files in os.walk(directory): for root, _, files in os.walk(directory): for filename in files: for filename in files: if ".yaml" in filename: if ".yaml" in filename or ".log" in filename: filepath = os.path.join(root, filename) filepath = os.path.join(root, filename) file_paths.append(filepath) file_paths.append(filepath) return file_paths return file_paths @app.after_request @app.after_request def after_request(response): def after_request(response): ''' ''' Loading @@ -175,16 +186,26 @@ def after_request(response): if g.get('tmp_dir') is None: if g.get('tmp_dir') is None: return response return response shutil.rmtree(g.tmp_dir, ignore_errors=True) shutil.rmtree(g.tmp_dir, ignore_errors=True) print("Deleted {}\n\n".format(g.tmp_dir)) logging.debug("Deleted {}\n\n".format(g.tmp_dir)) return response return response @app.route("/doc2tosca", methods=['POST']) @app.route("/doc2tosca", methods=['POST']) def mk_doc2tosca(): def mk_doc2tosca(): ''' ''' Executes doc2tosca on the uploaded file Executes doc2tosca on the uploaded file ''' ''' tmp_dir = tempfile.mkdtemp() logfilename = os.path.join(tmp_dir, 'toscatools.log') logging.basicConfig(filename=logfilename, level=logging.INFO) logging.info("Starting") logging.debug("TMP DIR: " + tmp_dir) zip_fn = "tosca_defs.zip" zip_fn = "tosca_defs.zip" zip_path = os.path.join(tmp_dir, zip_fn) if 'file' not in request.files: if 'file' not in request.files: flash('No file uploaded') flash('No file uploaded') Loading @@ -193,7 +214,7 @@ def mk_doc2tosca(): ufiles = request.files.getlist("file") ufiles = request.files.getlist("file") try: try: print(request.form) logging.debug(request.form) doc_version = request.form.get('doc-version') doc_version = request.form.get('doc-version') custom_doc_version = request.form.get('custom-doc-version') custom_doc_version = request.form.get('custom-doc-version') custom_tosca_version = request.form.get('custom-tosca-version') custom_tosca_version = request.form.get('custom-tosca-version') Loading @@ -217,29 +238,32 @@ def mk_doc2tosca(): tosca_version = custom_tosca_version tosca_version = custom_tosca_version try: try: doc2tosca.generate_templates(sol001_file, doc_version, yaml_root_path, tosca_version) doc2tosca.generate_templates( sol001_file, doc_version, yaml_root_path, tosca_version ) doc2tosca.print_to_files(tmp_dir) except ValueError as e: except ValueError as e: flash(str(e)) flash(str(e)) return redirect("/tosca-ie") return redirect("/tosca-ie") except BaseException as e: except BaseException as e: print(e) # logging.error(traceback.print_exc()) flash(str(e)) flash(str(e)) flash("Unknown error in the generation of the files. Please contact the support.") flash("Unknown error in the generation of the files. \ return redirect("/tosca-ie") Please contact the support.") finally: tmp_dir = tempfile.mkdtemp() file_paths = get_all_yaml_file_paths_and_logs(tmp_dir) print("TMP DIR: " + tmp_dir) doc2tosca.print_to_files(tmp_dir) file_paths = get_all_yaml_file_paths(tmp_dir) zip_path = os.path.join(tmp_dir, zip_fn) with ZipFile(zip_path, 'w') as archive: with ZipFile(zip_path, 'w') as archive: for myfile in file_paths: for myfile in file_paths: print(myfile) logging.info(myfile) archive.write(myfile, arcname=os.path.basename(myfile)) archive.write(myfile, arcname=os.path.basename(myfile)) g.tmp_dir = tmp_dir g.tmp_dir = tmp_dir return send_file(zip_path, as_attachment=True) return send_file(zip_path, as_attachment=True) if __name__ == '__main__': if __name__ == '__main__': app.run(debug=True, host='0.0.0.0') app.run(debug=True, host='0.0.0.0') Loading
src/doc2tosca.py +20 −19 Original line number Original line Diff line number Diff line Loading @@ -6,6 +6,7 @@ Generate tosca definitions from Docx specfication import sys import sys import os import os import re import re import logging from io import StringIO from io import StringIO import docx import docx Loading Loading @@ -141,9 +142,9 @@ def get_content(doc): ret.append(Table(element, body)) ret.append(Table(element, body)) table_count = table_count + 1 table_count = table_count + 1 # else: # else: # print("Non paragraph or table " + str(type(element))) # logging.info("Non paragraph or table " + str(type(element))) print("Paragraphs: " + str(parag_count)) logging.info("Paragraphs: " + str(parag_count)) print("Tables: " + str(table_count)) logging.info("Tables: " + str(table_count)) return ret return ret Loading @@ -160,7 +161,7 @@ def find_sect(sect_to_find, start_idx, doc_content): break break start_idx = start_idx + 1 start_idx = start_idx + 1 print("FOUND " + sect_to_find + " at " + str(start_idx)) logging.info("FOUND " + sect_to_find + " at " + str(start_idx)) return start_idx return start_idx Loading Loading @@ -249,7 +250,7 @@ def gen_tables_btwn(a_id, b_id, content, buf): for idx in range(a_id, b_id): for idx in range(a_id, b_id): if idx >= len(content): if idx >= len(content): print( logging.info( range_err_mess.format(a_id, b_id, idx, len(content))) range_err_mess.format(a_id, b_id, idx, len(content))) return definitions_count return definitions_count Loading @@ -257,7 +258,7 @@ def gen_tables_btwn(a_id, b_id, content, buf): if isinstance(tmp_elem, Paragraph) and \ if isinstance(tmp_elem, Paragraph) and \ is_lvl2_section_hdn(tmp_elem.text): is_lvl2_section_hdn(tmp_elem.text): print(tmp_elem.text) logging.info(tmp_elem.text) write_subsection_to_file(tmp_elem.text.split("\t")[1], buf) write_subsection_to_file(tmp_elem.text.split("\t")[1], buf) definitions_count = definitions_count + 1 definitions_count = definitions_count + 1 Loading Loading @@ -326,14 +327,14 @@ def generate_templates( the MODELS dictionary the MODELS dictionary ''' ''' if isinstance(filename, str): if isinstance(filename, str): print("Opening " + filename) logging.info("Opening " + filename) init_models(yaml_root, spec_ver, tosc_ver) init_models(yaml_root, spec_ver, tosc_ver) try: try: sol_001 = docx.Document(filename) sol_001 = docx.Document(filename) except: except: print("Error opening the submitted Docx file") logging.info("Error opening the submitted Docx file") raise ValueError("Cannot open the submitted Docx file") raise ValueError("Cannot open the submitted Docx file") content = get_content(sol_001) content = get_content(sol_001) Loading @@ -347,13 +348,13 @@ def generate_templates( count = gen_tables_btwn( count = gen_tables_btwn( sect.from_id, sect.to_id, content, MODELS[model]['buf'] sect.from_id, sect.to_id, content, MODELS[model]['buf'] ) ) print("Printed " + str(count) + " types to " + model) logging.info("Printed " + str(count) + " types to " + model) else: else: if sect.letter == "A" or sect.letter == "E": if sect.letter == "A" or sect.letter == "E": count = generate_examples_between( count = generate_examples_between( sect.from_id, sect.to_id, content, EXAMPLES sect.from_id, sect.to_id, content, EXAMPLES ) ) print("Printed {} types to Annex {}".format( logging.info("Printed {} types to Annex {}".format( str(count), sect.letter) str(count), sect.letter) ) ) Loading @@ -367,7 +368,7 @@ def print_to_files(prefix=None): if prefix is not None: if prefix is not None: mod['fn'] = os.path.join(prefix, mod['fn']) mod['fn'] = os.path.join(prefix, mod['fn']) print("Writing to " + mod['fn']) logging.info("Writing to " + mod['fn']) mod['fd'] = open(mod['fn'], 'w') mod['fd'] = open(mod['fn'], 'w') mod['buf'].seek(0) mod['buf'].seek(0) mod['fd'].write(mod['buf'].read()) mod['fd'].write(mod['buf'].read()) Loading @@ -379,7 +380,7 @@ def print_to_files(prefix=None): fnm = os.path.join(prefix, "example_"+EXAMPLES[k].filename) fnm = os.path.join(prefix, "example_"+EXAMPLES[k].filename) else: else: fnm = EXAMPLES[k].filename fnm = EXAMPLES[k].filename print("Writing example file: " + fnm) logging.info("Writing example file: " + fnm) with open(fnm, 'w') as newf: with open(fnm, 'w') as newf: newf.write(EXAMPLES[k].text) newf.write(EXAMPLES[k].text) newf.write("\n") newf.write("\n") Loading Loading @@ -410,8 +411,8 @@ if __name__ == "__main__": try: try: SOL001_FN = sys.argv[1] SOL001_FN = sys.argv[1] except: except: print('Error: Filename missing or filename not a docx document') logging.info('Error: Filename missing or filename not a docx document') print('Usage: doc2tosca <docx-with-tosca-definitions>') logging.info('Usage: doc2tosca <docx-with-tosca-definitions>') sys.exit(1) sys.exit(1) ver = parse_version_from_filename(SOL001_FN) ver = parse_version_from_filename(SOL001_FN) Loading
src/web_app.py +51 −27 Original line number Original line Diff line number Diff line Loading @@ -7,6 +7,8 @@ Web app to offer a frontend to the doc2tosca and tosca2doc tools import os import os import shutil import shutil import tempfile import tempfile import logging import traceback from zipfile import ZipFile from zipfile import ZipFile from flask import Flask, flash, request, redirect, send_file, render_template, g from flask import Flask, flash, request, redirect, send_file, render_template, g Loading @@ -17,6 +19,7 @@ import config import tosca2doc import tosca2doc import doc2tosca import doc2tosca class ReverseProxied(object): class ReverseProxied(object): def __init__(self, the_app, script_name=None, scheme=None, server=None): def __init__(self, the_app, script_name=None, scheme=None, server=None): Loading @@ -40,6 +43,7 @@ class ReverseProxied(object): environ['HTTP_HOST'] = server environ['HTTP_HOST'] = server return self.app(environ, start_response) return self.app(environ, start_response) app = Flask(__name__) app = Flask(__name__) app.wsgi_app = ReverseProxied(app.wsgi_app, script_name='/tosca-ie') app.wsgi_app = ReverseProxied(app.wsgi_app, script_name='/tosca-ie') Loading @@ -57,6 +61,7 @@ MISSING_MODULE_ERR_MSG = \ 'Error: some TOSCA module missing. Make sure to \ 'Error: some TOSCA module missing. Make sure to \ upload definitions for NSD, VNFD, PNFD.' upload definitions for NSD, VNFD, PNFD.' def allowed_file(filename): def allowed_file(filename): ''' ''' Check filename is in the list of allowed extensions Check filename is in the list of allowed extensions Loading @@ -64,6 +69,7 @@ def allowed_file(filename): return '.' in filename and \ return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route("/") @app.route("/") def hello(): def hello(): ''' ''' Loading @@ -83,6 +89,7 @@ def hello(): rev_link=link rev_link=link ) ) @app.route("/doc2tosca-info") @app.route("/doc2tosca-info") def doc2tosca_info(): def doc2tosca_info(): ''' ''' Loading @@ -90,6 +97,7 @@ def doc2tosca_info(): ''' ''' return render_template("./doc2tosca-details.html", VERSION=config.VERSION) return render_template("./doc2tosca-details.html", VERSION=config.VERSION) @app.route("/tosca2doc-info") @app.route("/tosca2doc-info") def tosca2doc_info(): def tosca2doc_info(): ''' ''' Loading @@ -97,6 +105,7 @@ def tosca2doc_info(): ''' ''' return render_template("./tosca2doc-details.html", VERSION=config.VERSION) return render_template("./tosca2doc-details.html", VERSION=config.VERSION) @app.route("/tosca2doc", methods=['POST']) @app.route("/tosca2doc", methods=['POST']) def mk_tosca2doc(): def mk_tosca2doc(): ''' ''' Loading Loading @@ -149,7 +158,8 @@ def mk_tosca2doc(): #return ("No content found") #return ("No content found") def get_all_yaml_file_paths(directory): def get_all_yaml_file_paths_and_logs(directory): ''' ''' Finds yaml files within a directory and sub directories and Finds yaml files within a directory and sub directories and returns the list of paths returns the list of paths Loading @@ -159,12 +169,13 @@ def get_all_yaml_file_paths(directory): for root, _, files in os.walk(directory): for root, _, files in os.walk(directory): for filename in files: for filename in files: if ".yaml" in filename: if ".yaml" in filename or ".log" in filename: filepath = os.path.join(root, filename) filepath = os.path.join(root, filename) file_paths.append(filepath) file_paths.append(filepath) return file_paths return file_paths @app.after_request @app.after_request def after_request(response): def after_request(response): ''' ''' Loading @@ -175,16 +186,26 @@ def after_request(response): if g.get('tmp_dir') is None: if g.get('tmp_dir') is None: return response return response shutil.rmtree(g.tmp_dir, ignore_errors=True) shutil.rmtree(g.tmp_dir, ignore_errors=True) print("Deleted {}\n\n".format(g.tmp_dir)) logging.debug("Deleted {}\n\n".format(g.tmp_dir)) return response return response @app.route("/doc2tosca", methods=['POST']) @app.route("/doc2tosca", methods=['POST']) def mk_doc2tosca(): def mk_doc2tosca(): ''' ''' Executes doc2tosca on the uploaded file Executes doc2tosca on the uploaded file ''' ''' tmp_dir = tempfile.mkdtemp() logfilename = os.path.join(tmp_dir, 'toscatools.log') logging.basicConfig(filename=logfilename, level=logging.INFO) logging.info("Starting") logging.debug("TMP DIR: " + tmp_dir) zip_fn = "tosca_defs.zip" zip_fn = "tosca_defs.zip" zip_path = os.path.join(tmp_dir, zip_fn) if 'file' not in request.files: if 'file' not in request.files: flash('No file uploaded') flash('No file uploaded') Loading @@ -193,7 +214,7 @@ def mk_doc2tosca(): ufiles = request.files.getlist("file") ufiles = request.files.getlist("file") try: try: print(request.form) logging.debug(request.form) doc_version = request.form.get('doc-version') doc_version = request.form.get('doc-version') custom_doc_version = request.form.get('custom-doc-version') custom_doc_version = request.form.get('custom-doc-version') custom_tosca_version = request.form.get('custom-tosca-version') custom_tosca_version = request.form.get('custom-tosca-version') Loading @@ -217,29 +238,32 @@ def mk_doc2tosca(): tosca_version = custom_tosca_version tosca_version = custom_tosca_version try: try: doc2tosca.generate_templates(sol001_file, doc_version, yaml_root_path, tosca_version) doc2tosca.generate_templates( sol001_file, doc_version, yaml_root_path, tosca_version ) doc2tosca.print_to_files(tmp_dir) except ValueError as e: except ValueError as e: flash(str(e)) flash(str(e)) return redirect("/tosca-ie") return redirect("/tosca-ie") except BaseException as e: except BaseException as e: print(e) # logging.error(traceback.print_exc()) flash(str(e)) flash(str(e)) flash("Unknown error in the generation of the files. Please contact the support.") flash("Unknown error in the generation of the files. \ return redirect("/tosca-ie") Please contact the support.") finally: tmp_dir = tempfile.mkdtemp() file_paths = get_all_yaml_file_paths_and_logs(tmp_dir) print("TMP DIR: " + tmp_dir) doc2tosca.print_to_files(tmp_dir) file_paths = get_all_yaml_file_paths(tmp_dir) zip_path = os.path.join(tmp_dir, zip_fn) with ZipFile(zip_path, 'w') as archive: with ZipFile(zip_path, 'w') as archive: for myfile in file_paths: for myfile in file_paths: print(myfile) logging.info(myfile) archive.write(myfile, arcname=os.path.basename(myfile)) archive.write(myfile, arcname=os.path.basename(myfile)) g.tmp_dir = tmp_dir g.tmp_dir = tmp_dir return send_file(zip_path, as_attachment=True) return send_file(zip_path, as_attachment=True) if __name__ == '__main__': if __name__ == '__main__': app.run(debug=True, host='0.0.0.0') app.run(debug=True, host='0.0.0.0')