From f5efa72835250ba649e7ca642f95c4fa8a2a53f1 Mon Sep 17 00:00:00 2001 From: mirthyy Date: Tue, 13 Apr 2021 14:47:33 -0700 Subject: [PATCH] Comments now follow standard Python protocol. Removed 2nd 'for k in wordcount' loop.Changed and removed some comments if unclear or not needed. --- textsearch.py | 102 ++++++++++++++------------- uploader.py | 192 +++++++++++++++++++++++++++----------------------- 2 files changed, 156 insertions(+), 138 deletions(-) diff --git a/textsearch.py b/textsearch.py index 9b7e30d..1de061c 100644 --- a/textsearch.py +++ b/textsearch.py @@ -12,7 +12,7 @@ from bokeh.resources import CDN from bokeh.embed import components from bokeh.plotting import figure -import geopandas as gpd +import geopandas as gpd from bokeh.models import GeoJSONDataSource import json from bokeh.io import show, curdoc @@ -32,15 +32,13 @@ import re import geojson -app = Flask(__name__) #create flask object -app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 #avoid storing cache -bootstrap = Bootstrap(app) #create bootstrap object - - -@app.route('/') #declare flask page url -def my_form(): #function for main index - return render_template('index.html') #return index page +app = Flask(__name__) # create flask object +app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 # avoid storing cache +bootstrap = Bootstrap(app) # create bootstrap object +@app.route('/') # declare flask page url +def my_form(): + return render_template('index.html') # display index page def getResults(wordinput): """This function is used to take word input in the searchbox, query elasticsearch, @@ -78,15 +76,14 @@ def getResults(wordinput): results.append(new_result) - - return results class Result: """This results class stores the data of a single search 'hit'. """ - def __init__(self, state, filename, is_city, place_name, plan_date, filetype, query, county='na', population=0, city_type='na', score=0): + def __init__(self, state, filename, is_city, place_name, plan_date, filetype, + query, county='na', population=0, city_type='na', score=0): # place properties self.state = state self.filename = filename @@ -94,7 +91,7 @@ def __init__(self, state, filename, is_city, place_name, plan_date, filetype, q self.place_name = place_name self.plan_date = plan_date self.filetype = filetype - #search things + # search things self.score = score # additional properties @@ -104,8 +101,9 @@ def __init__(self, state, filename, is_city, place_name, plan_date, filetype, q self.pdf_filename = self.filename.split('.')[0] + '.pdf' parsed_query = self.parse_query(query) - # this self.year is the html that will be displayed around the year - # it will link to a function that will highlight the word occuraces in the file + + # allows user to click on year on webpage's result table; + # will link to 'highlight_pdf' function self.year = ' '+self.plan_date+"" def parse_query(self, query): @@ -118,7 +116,6 @@ def parse_query(self, query): Returns: [type]: a parsed query that can be used in html """ - phrases_in_quotes = re.findall(r'\"(.+?)\"',query) non_quotes = re.sub(r'"',"", re.sub(r'\"(.+?)\"', '', query)) all_words = re.findall('[A-z]+', non_quotes) @@ -128,7 +125,6 @@ def parse_query(self, query): @property def cityName(self): """This is a property tag that is useful for parts of legacy code - Returns: str: place name """ @@ -150,7 +146,6 @@ def change_json_colors(json_dict, results, blank_city_outline='#dedede', blank_county_outline='#b3b3b3', match_city_fill_color="#d47500", match_city_outline='#dedede', match_county_fill_color="#00a4a6", match_county_outline='#b3b3b3'): - result_names = [] result_dict = {} for result in results: @@ -170,10 +165,11 @@ def change_json_colors(json_dict, results, feature['properties']['color'] = match_county_fill_color feature['properties']['line_color'] = match_county_outline - else: # no match + else: # no match feature['properties']['color'] = blank_city_color feature['properties']['line_color'] = blank_city_outline - # else: # a county not yet implimtented + # ****** NOT YET IMPLEMENTED ****** + # else: # a county # feature['properties']['color'] = blank_county_color # feature['properties']['line_color'] = blank_county_outline @@ -185,15 +181,16 @@ def change_json_colors(json_dict, results, with open(os.path.join(geojson_path, 'pop_map.geojson'), 'r') as f: pop_map = json.load(f) -@app.route('/', methods=['POST']) #connect search form to html page -def index_search_box(): #function for accepting search input - """The code for the search box functionality +@app.route('/', methods=['POST']) # connect search form to html page +def index_search_box(): + """This function gets input from webpage, calls getResults and displays + a map and table for the results of the search. Returns: str : html webpage """ - wordinput=" " #initialize string input for search - wordinput=request.form['u'] #set name for search form + wordinput=" " # initialize string input for search + wordinput=request.form['u'] # get input from request form on webpage results = getResults(wordinput) matched_city_names = [] matched_county_names = [] @@ -222,6 +219,7 @@ def index_search_box(): if len(results) < 1: return render_template('noresult.html') + # *************** BEGIN MAP CREATION *************** change_json_colors(my_map, results) change_json_colors(pop_map, results) geosource = GeoJSONDataSource(geojson = json.dumps(my_map)) @@ -247,8 +245,9 @@ def index_search_box(): p.grid.grid_line_color = None p.hover.point_policy = "follow_mouse" p.patches('xs','ys', source = geosource, fill_color='color', line_color='line_color') - + # *************** END MAP CREATION *************** + # *************** BEGIN TABLE CREATION *************** cityData = dict( names=[res.cityName for res in cityResults], years=[res.year for res in cityResults], @@ -271,7 +270,6 @@ def index_search_box(): uniqueCities = len(set(cityData["names"])) uniqueCounties = len(set(countyData["names"])) - citySource = ColumnDataSource(cityData) columns = [ @@ -296,6 +294,7 @@ def index_search_box(): cityTab = Panel(title="Cities", child=city_table) countyTab = Panel(title="Counties", child=county_table) tabs = Tabs(tabs=[cityTab, countyTab]) + # *************** END TABLE CREATION *************** numCities = 482 numCounties = 58 @@ -309,17 +308,21 @@ def index_search_box(): mapTabs = Tabs(tabs=[outlineMap, popMap]) l = layout(column([row([mapTabs, resultsDiv]), tabs])) + # lScript contains data for plot, lDiv is target to show data on webpage lScript,lDiv = components(l) + + # js_files and css_files gives URLs for any files needed by lScript and lDiv cdn_js = CDN.js_files cdn_css = CDN.css_files - return render_template('results.html',lScript=lScript,lDiv=lDiv) #render results page with map and table object as arguments - + # display results page with map and table objects + return render_template('results.html',lScript=lScript,lDiv=lDiv) -@app.route('/outp//') #route for page containing highlighted pdf +@app.route('/outp//') # route for page containing highlighted pdf def highlight_pdf(city, words): - """Function responsible for highlighting pdf words + """This function opens a pdf of a city's general plan and highlights + all instances of the search input. Args: city (str): the name of the city @@ -328,35 +331,38 @@ def highlight_pdf(city, words): Returns: str: webpages """ - complete_name = os.path.join("static/data/places", city) #path for city pdf file - doc = fitz.open(complete_name) #create open pdf file object - page_count= len(doc) #find no. of pages in pdf + complete_name = os.path.join("static/data/places", city) + doc = fitz.open(complete_name) + page_count= len(doc) # find no. of pages in pdf if "," in words: - list_split=words.split(",") #split wordlist by commas + list_split=words.split(",") else: - list_split=[words] #if no commas means single word + list_split=[words] # if no commas in wordlist, single word + wordcount=len(list_split) - text_instances = [" "] * wordcount #occurences of a phrase in a page + text_instances = [" "] * wordcount # occurences of any phrase in a page for i in range(page_count): for k in range(wordcount): - text_instances[k] = doc[i].searchFor(list_split[k],hit_max = 100) #search for the phrase in the page(maximum 100 occurences) - for k in range(wordcount): + text_instances[k] = doc[i].searchFor(list_split[k],hit_max = 100) # look for search phrase in page (max. 100 occurences) for inst in text_instances[k]: - highlight = doc[i].addHighlightAnnot(inst) #highlight all occurences of phrase - highlighted_complete_name = os.path.join("static/data/pdfoutput","output.pdf") #path for highlighted pdf - doc.save(highlighted_complete_name) #save highlighted pdf + highlight = doc[i].addHighlightAnnot(inst) # highlight all occurences of phrase + + highlighted_complete_name = os.path.join("static/data/pdfoutput","output.pdf") + doc.save(highlighted_complete_name) doc.close() - fht= 'window.location.href = "/static/data/pdfoutput/output.pdf";' #send highlighted pdf link - - fht = Markup(fht) #make the link safe for sending to html + + # set link for highlighted pdf and make safe to send to html + fht= 'window.location.href = "/static/data/pdfoutput/output.pdf";' + fht = Markup(fht) - return render_template('download.html',fht=fht) #render pdf file with the higlighted pdflink as argument + # render highlighted pdf file (? need to see what happens after this function) + return render_template('download.html',fht=fht) -if __name__ == "__main__": #run app on local host at port 5000 in debug mode +if __name__ == "__main__": # from werkzeug.contrib.profiler import ProfilerMiddleware # app.config['PROFILE'] = True # app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30]) - app.run(host="0.0.0.0", port=5000, debug=True) + app.run(host="0.0.0.0", port=5002, debug=True) diff --git a/uploader.py b/uploader.py index 2685207..1980599 100644 --- a/uploader.py +++ b/uploader.py @@ -19,57 +19,62 @@ import es -app = Flask(__name__) #create flask object -mail= Mail(app) #create mail object -app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 #to avoid storing cache -app.config["GEOIPIFY_API_KEY"] = "at_8RFyAJbk6JHFY9eJCUEuHOFEPMEjG" #ip address finder API key -simple_geoip = SimpleGeoIP(app) #ip address finder object +app = Flask(__name__) # create flask object +mail= Mail(app) # create mail object +app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 # to avoid storing cache +app.config["GEOIPIFY_API_KEY"] = "at_8RFyAJbk6JHFY9eJCUEuHOFEPMEjG" # ip address finder API key +simple_geoip = SimpleGeoIP(app) # ip address finder object -gauth = GoogleAuth() #initiate google drive authentication -gauth.LoadCredentialsFile("mycreds.txt") #load api credential details -drive = GoogleDrive(gauth) #create drive object +gauth = GoogleAuth() # initiate google drive authentication +gauth.LoadCredentialsFile("mycreds.txt") # load api credential details +drive = GoogleDrive(gauth) # create drive object fileg=open("drivep.txt","r") gpp=fileg.readline() fileg.close() -app.config['MAIL_SERVER']='smtp.gmail.com' #use gmail server -app.config['MAIL_PORT'] = 465 #set mail port -app.config['MAIL_USERNAME'] = 'generalplanserver@gmail.com' #set sender email id -app.config['MAIL_PASSWORD'] = gpp #set sender password +app.config['MAIL_SERVER']='smtp.gmail.com' # use gmail server +app.config['MAIL_PORT'] = 465 +app.config['MAIL_USERNAME'] = 'generalplanserver@gmail.com' +app.config['MAIL_PASSWORD'] = gpp # set sender password app.config['MAIL_USE_TLS'] = False app.config['MAIL_USE_SSL'] = True -mail = Mail(app) #build object again +mail = Mail(app) # build object again -blockip = { #dictionary with list of ips to block +blockip = { # dictionary with list of ips to block "": 0, } @app.route('/') -def home(): #function for log in screen +def home(): + """This function renders and controls login screen - del_list="" #list of files in delete section + Returns: + str: webpages + """ + + del_list="" # list of files in delete section for filename in sorted(os.listdir("static/data/places")): if filename.endswith(".txt"): filename=filename.replace(".txt","") - del_list += '' #add each file name in selection list - del_list=Markup(del_list) #mark the html script as safe object - if not session.get('logged_in'): #if not logged in return login page + del_list += '' # add each file name in selection list + del_list=Markup(del_list) # mark the html script as safe object + if not session.get('logged_in'): # if not logged in, return login page return render_template('login.html') else: session['logged_in'] = False - return render_template('upload_index.html',del_list=del_list) #if logged in update file list + return render_template('upload_index.html',del_list=del_list) # if logged in, update file list @app.route('/', methods=['POST']) -def do_admin_login(): #function to collect username password +def do_admin_login(): # function to collect username & password global blockip - if str(request.remote_addr)+"t" in blockip: #check if the ip has exceeded the 10 try mark + if str(request.remote_addr)+"t" in blockip: # check if the ip has exceeded the 10 try mark if (datetime.now()-blockip[str(request.remote_addr)+"t"]).total_seconds() <1800: cal=(1800-(datetime.now()-blockip[str(request.remote_addr)+"t"]).total_seconds())/60 - flash("Try again after "+str(int(cal))+" minutes") + flash("Try again after "+str(int(cal))+" minutes") else: flash("Try again ") del blockip[str(request.remote_addr)+"t"] @@ -78,8 +83,8 @@ def do_admin_login(): filep=open("passw",'r') hashed=filep.read().encode('utf-8') filep.close() - pwd=request.form['password'].encode('utf-8') #store password and encode to UTF-8 - if bcrypt.checkpw(pwd, hashed) and request.form['username'] == 'admin': #check username and password + pwd=request.form['password'].encode('utf-8') # store password and encode to UTF-8 + if bcrypt.checkpw(pwd, hashed) and request.form['username'] == 'admin': # check username and password if str(request.remote_addr) in blockip: del blockip[str(request.remote_addr)] session['logged_in'] = True @@ -87,8 +92,9 @@ def do_admin_login(): if str(request.remote_addr) in blockip: blockip[str(request.remote_addr)]+=1 - if blockip[str(request.remote_addr)]>10: #check if ip address has exceeded 10 incorrect attempts - msg = Message('Excessive log in attempts', sender = 'generalplanserver@gmail.com', recipients = ['ckbrinkley@ucdavis.edu']) #send email for download notification + if blockip[str(request.remote_addr)]>10: # check if ip address has exceeded 10 incorrect attempts + # send email for download notification + msg = Message('Excessive log in attempts', sender = 'generalplanserver@gmail.com', recipients = ['ckbrinkley@ucdavis.edu']) geoip_data = simple_geoip.get_geoip_data() ip=str(geoip_data['ip']) co=str(geoip_data['location']['country']) @@ -96,13 +102,13 @@ def do_admin_login(): brow=request.user_agent.platform+" "+request.user_agent.browser msg.body = "Dear Admin,\n\nThere have been excessive log in attempts on Uploader site. The details of the client are as follows:\n\nIP:"+ip+"\nCountry:"+co+"\nRegion:"+rg+"\nBrowser:"+brow+"\n\nGeneral Plan Server." mail.send(msg) - - flash('Excessive incorrect attempts, Try again after 30 minutes') + + flash('Excessive incorrect attempts, Try again after 30 minutes') del blockip[str(request.remote_addr)] blockip[str(request.remote_addr)+"t"]=datetime.now() else: - flash('Incorrect Username/Password, please try again') #if ID doesnt match username password + flash('Incorrect Username/Password, please try again') # if ID doesnt match username & password else: blockip[str(request.remote_addr)]=0 flash('Incorrect Username/Password, please try again') @@ -111,34 +117,34 @@ def do_admin_login(): @app.route('/delpg') -def delete_page_update(): #to update page list +def delete_page_update(): # to update page list session['logged_in'] = True return redirect(url_for('home')) @app.route('/delete', methods = ['POST']) -def delete_file(): #function to delete file from list - del_req=request.form['deleter'] #access delete button argument +def delete_file(): # function to delete file from list + del_req=request.form['deleter'] # access delete button argument del_req=del_req+".pdf" rempdf = os.path.join("static/data/places", del_req) rempdftemp=rempdf.replace("places","temp") remtxt= rempdf.replace(".pdf",".txt") try: - os.remove(remtxt) #remove text file followed by pdf + os.remove(remtxt) # remove text file followed by pdf except: print("not found") try: - os.remove(rempdf) #remove text file followed by pdf + os.remove(rempdf) # remove text file followed by pdf except: print("not found") try: - os.remove(rempdftemp) #remove text file followed by pdf + os.remove(rempdftemp) # remove text file followed by pdf except: print("not found") - drive = GoogleDrive(gauth) #rebuild the drive object - top_list = drive.ListFile({'q': "'root' in parents and trashed=false"}).GetList() #generate lsit of files in drive + drive = GoogleDrive(gauth) # rebuild the drive object + top_list = drive.ListFile({'q': "'root' in parents and trashed=false"}).GetList() # generate list of files in drive for fileu in top_list: - if fileu['originalFilename'] == del_req: #delete file matching the delete request from drive + if fileu['originalFilename'] == del_req: # delete file matching the delete request from drive fileu.Delete() @@ -147,119 +153,125 @@ def delete_file(): -@app.route('/upload', methods = ['GET', 'POST']) #route to upload form in upload_index html in for getting files and posting to the server -def upload_file1(): #function to upload file +@app.route('/upload', methods = ['GET', 'POST']) # route to upload form in upload_index html in for getting files and posting to the server +def upload_file1(): # function to upload file completeName="" - if request.method == 'POST': #when upload button is clicked + if request.method == 'POST': # when upload button is clicked - files = request.files.getlist("file") #get list of files uploaded in form - for file in files: #open place pdf file + files = request.files.getlist("file") # get list of files uploaded in form + for file in files: # open place pdf file print(file.filename) location_name="" if request.form['type'] == "City": location_name=request.form['City'] else: location_name=request.form['county'] - file.filename=request.form['state']+"_"+request.form['type']+"_"+location_name+"_"+request.form['year']+".pdf" #generate filename with select form data - print(file.filename) - msg = Message('General Plan file upload', sender = 'generalplanserver@gmail.com', recipients = ['ckbrinkley@ucdavis.edu']) #send email for download notification + + # generate filename with select form data + file.filename=request.form['state']+"_"+request.form['type']+"_"+location_name+"_"+request.form['year']+".pdf" + print(file.filename) + + # send email for download notification + msg = Message('General Plan file upload', sender = 'generalplanserver@gmail.com', recipients = ['ckbrinkley@ucdavis.edu']) msg.body = "Dear Admin,\n\nA file named "+file.filename+" has been uploaded to the server by: "+request.form['email']+" .\n\nGeneral Plan Server" - mail.send(msg) #send mail for file upload to server + mail.send(msg) # send mail for file upload to server completeName = os.path.join("static/data/places",file.filename) print(completeName) - tempname=os.path.join("static/data/temp",secure_filename(file.filename)) #temporary copy file in case compression is not possible - file.save(completeName) #save file to server - arg1= '-sOutputFile='+ tempname #path for output file after compression to reduce pdf size - p = subprocess.Popen(['/usr/bin/gs', + # temporary copy file in case compression is not possible + tempname=os.path.join("static/data/temp",secure_filename(file.filename)) + file.save(completeName) # save file to server + arg1= '-sOutputFile='+ tempname # path for output file after compression to reduce pdf size + p = subprocess.Popen(['/usr/bin/gs', '-sDEVICE=pdfwrite','-dCompatibilityLevel=1.4', '-dPDFSETTINGS=/screen','-dNOPAUSE', '-dBATCH', '-dQUIET', - str(arg1),completeName ], stdout=subprocess.PIPE) #function to compress pdf + str(arg1),completeName ], stdout=subprocess.PIPE) # function to compress pdf try: - out, err = p.communicate(timeout=1800) #try compression for 1800s max + out, err = p.communicate(timeout=1800) # try compression for 1800 secs max except subprocess.TimeoutExpired: - p.kill() #kill the process since a timeout was triggered - out, error = p.communicate() #capture both standard output and standard error + p.kill() # kill the process since a timeout was triggered + out, error = p.communicate() # capture both standard output and standard error else: pass try: fh= open(tempname, "rb") - check=PyPDF2.PdfFileReader(fh) #check if pdf is valid file + check=PyPDF2.PdfFileReader(fh) # check if pdf is valid file fh.close() except: print("invalid PDF file") fh.close() - os.remove(tempname) #remove temp file if compressed pdf is corrupt and causes exception + os.remove(tempname) # remove temp file if compressed pdf is corrupt and causes exception else: pass os.remove(completeName) - shutil.move(tempname,"static/data/places") #move compressed tempfile to places directory is compressed file is valid + shutil.move(tempname,"static/data/places") # move compressed tempfile to places directory is compressed file is valid fname =completeName fnamecpy=fname - doc = fitz.open(fname) #open pdf file object - length=len(doc) #find no. of pages - imornot=0 #flag variable to check if pdf contains scanned data or text + doc = fitz.open(fname) # open pdf file object + length=len(doc) # find no. of pages + imornot=0 # flag variable to check if pdf contains scanned data or text for page in doc: - if not page.getText(): #check check if pdf contains scanned data or text + if not page.getText(): # check if pdf contains scanned data or text imornot=imornot+1 - if imornot > int(length/2): #if more than half pages of pdf are scanned convert to text pdf through OCR + if imornot > int(length/2): # if more than half pages of pdf are scanned convert to text pdf through OCR fname=fname.replace('.pdf', '') text_file_name = fname + ".txt" - textfile = open(text_file_name, "a") #create text file with place name + textfile = open(text_file_name, "a") # create text file with place name for page in doc: - pix = page.getPixmap(alpha = False) #generate image file from page + pix = page.getPixmap(alpha = False) # generate image file from page pixn=os.path.join("static/data/places","page-%i.png" % page.number) - pix.writePNG(pixn) #save page image as png + pix.writePNG(pixn) # save page image as png pdfn=os.path.join("static/data/places","page-"+str(page.number)+".pdf") with open(pdfn, 'w+b') as f: - text = pytesseract.image_to_string(pixn) #obtain text from image - textfile.write(text) #write text from the image to text file - pdf = pytesseract.image_to_pdf_or_hocr(pixn, extension='pdf') #convert image to pdf - f.write(pdf) #create pdf for the page - os.remove(pixn) #remove the image after pdf creation + text = pytesseract.image_to_string(pixn) # obtain text from image + textfile.write(text) # write text from the image to text file + pdf = pytesseract.image_to_pdf_or_hocr(pixn, extension='pdf') # convert image to pdf + f.write(pdf) # create pdf for the page + os.remove(pixn) # remove the image after pdf creation f.close() textfile.close() - mergedObject = PdfFileMerger() #create file merger object + mergedObject = PdfFileMerger() # create file merger object - for fileNumber in range(page.number+1): #merge all page pdfs into a single pdf for the particlular place + # merge all page pdfs into a single pdf for the particular place + for fileNumber in range(page.number+1): pdfn=os.path.join("static/data/places",("page-"+str(fileNumber)+".pdf")) - mergedObject.append(PdfFileReader(pdfn, 'rb')) #append page to place pdf - os.remove(pdfn) #remove appended page pdf from server + mergedObject.append(PdfFileReader(pdfn, 'rb')) # append page to place pdf + os.remove(pdfn) # remove appended page pdf from server - mergedObject.write(fnamecpy) #save the complete place pdf to single file in server + mergedObject.write(fnamecpy) # save the complete place pdf to single file in server mergedObject.close() - else: #if the pdf contains less than hald scanned pages + else: # if the pdf contains less than half scanned pages imornot=0 fname=fname.replace('.pdf', '') - textfile = open(fname + ".txt", "wb") #create text document to store text data + textfile = open(fname + ".txt", "wb") # create text document to store text data for page in doc: - text = page.getText().encode("utf8") #get text from pdf page - textfile.write(text) #write text to text file for the place + text = page.getText().encode("utf8") # get text from pdf page + textfile.write(text) # write text to text file for the place textfile.write(bytes((12,))) textfile.close() doc.close() - drive = GoogleDrive(gauth) #rebuild drive object - file1=drive.CreateFile({'title':file.filename}) #name the drive file - file1.SetContentFile(completeName) #obtain contents of the pdf - file1.Upload() #upload the file to drive + drive = GoogleDrive(gauth) # rebuild drive object + file1=drive.CreateFile({'title':file.filename}) # name the drive file + file1.SetContentFile(completeName) # obtain contents of the pdf + file1.Upload() # upload the file to drive up="Files Uploaded Successfully!" - - es.add_to_index(text_file_name) - return render_template('upload_confirm.html',up=up) #render upload confirmation message page + es.add_to_index(text_file_name) + + return render_template('upload_confirm.html',up=up) # render upload confirmation message page -if __name__ == "__main__": #run app on local host at port 5001 in debug mode - app.secret_key = os.urandom(12) #random key for log in authentication +if __name__ == "__main__": # run app on local host at port 5001 in debug mode + app.secret_key = os.urandom(12) # random key for log in authentication app.run(host="0.0.0.0", port=5001, debug=True)