From bbdc2091da8f2ac071bc9387bb0e88d547c35b59 Mon Sep 17 00:00:00 2001 From: Simon d'Oelsnitz Date: Sat, 4 May 2024 13:26:09 -0400 Subject: [PATCH 01/15] added error-handling for all API queries in acc2operon --- streamlit/ligify/predict/accID2operon.py | 32 ++++++++---------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/streamlit/ligify/predict/accID2operon.py b/streamlit/ligify/predict/accID2operon.py index 1f52e5d..a595ba8 100644 --- a/streamlit/ligify/predict/accID2operon.py +++ b/streamlit/ligify/predict/accID2operon.py @@ -1,3 +1,4 @@ +import streamlit as st import requests import re from pprint import pprint @@ -13,7 +14,8 @@ def acc2MetaData(access_id: str): result = requests.get(f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=protein&id={access_id}&rettype=ipg") if result.status_code != 200: - print("non-200 HTTP response. eFetch failed") + st.error("Sorry! NCBI won't send us data due to high traffic. Please try again soon.\ + (metadata eFetch HTTP !== 200)") parsed = xmltodict.parse(result.text) @@ -50,24 +52,6 @@ def acc2MetaData(access_id: str): - - - - # OLD VERSION - -# def NC2genome(genome_id, startPos, stopPos): - -# base_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=nuccore" -# response = requests.get(base_url+"&id="+str(genome_id)+"&seq_start="+str(startPos)+"&seq_stop="+str(stopPos)+"&rettype=fasta_cds_aa") -# # old script that fetches the entire genome -# # response = requests.get('https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=nuccore&id='+str(genome_id)+'&rettype=fasta_cds_aa') - -# if response.ok: -# genome = response.text.split("\n") -# return genome - - - def NC2genome(genome_id, operon): base_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=nuccore" @@ -158,6 +142,10 @@ def NC2genome(genome_id, operon): return out, genome_reassembly_match + else: + st.error("Sorry! NCBI won't send us data due to high traffic. Please try again soon.\ + (genome eFetch HTTP !== 200)") + @@ -182,7 +170,8 @@ def getGenes(genome_id, startPos, stopPos): response = requests.get(base_url+"&id="+str(genome_id)+"&seq_start="+str(startPos-5000)+"&seq_stop="+str(stopPos)+"&rettype=fasta_cds_aa") genome = response.text.split("\n") except: - print("error fetching the genome fragment") + st.error("Sorry! NCBI won't send us data due to high traffic. Please try again soon.\ + (genome-fragment eFetch HTTP !== 200)") re1 = re.compile(str(startPos)) @@ -397,7 +386,8 @@ def predict_promoter(operon, regIndex, genome_id): # print('WARNING: Intergenic region is over 800bp') return None else: - print('FATAL: Bad eFetch request') + st.error("Sorry! NCBI won't send us data due to high traffic. Please try again soon.\ + (promoter eFetch HTTP !== 200)") return None # 800bp cutoff for an inter-operon region. From 15bec8f379483be52f7a6c745ebb579bbf81ed46 Mon Sep 17 00:00:00 2001 From: Simon d'Oelsnitz Date: Sat, 4 May 2024 13:44:54 -0400 Subject: [PATCH 02/15] added config file to work in light mode and remove error traceback code --- streamlit/.streamlit/config.toml | 186 ++++++++++++++++++++++++++++++ streamlit/ligify/streamlit_app.py | 4 +- 2 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 streamlit/.streamlit/config.toml diff --git a/streamlit/.streamlit/config.toml b/streamlit/.streamlit/config.toml new file mode 100644 index 0000000..f829aaf --- /dev/null +++ b/streamlit/.streamlit/config.toml @@ -0,0 +1,186 @@ +# Below are all the sections and options you can have in ~/.streamlit/config.toml. + +[global] + +# By default, Streamlit checks if the Python watchdog module is available and, if not, prints a warning asking for you to install it. The watchdog module is not required, but highly recommended. It improves Streamlit's ability to detect changes to files in your filesystem. +# If you'd like to turn off this warning, set this to True. +# Default: false +disableWatchdogWarning = true + +# If True, will show a warning when you run a Streamlit-enabled script via "python my_script.py". +# Default: true +showWarningOnDirectExecution = false + + +[logger] + +# Level of logging: 'error', 'warning', 'info', or 'debug'. +# Default: 'info' +level = "info" + +# String format for logging messages. If logger.datetimeFormat is set, logger messages will default to `%(asctime)s.%(msecs)03d %(message)s`. See [Python's documentation](https://docs.python.org/2.6/library/logging.html#formatter-objects) for available attributes. +# Default: None +messageFormat = "%(asctime)s %(message)s" + + +[client] + +# Whether to enable st.cache. +# Default: true +caching = true + +# If false, makes your Streamlit script not draw to a Streamlit app. +# Default: true +displayEnabled = true + +# Controls whether uncaught app exceptions are displayed in the browser. By default, this is set to True and Streamlit displays app exceptions and associated tracebacks in the browser. +# If set to False, an exception will result in a generic message being shown in the browser, and exceptions and tracebacks will be printed to the console only. +# Default: true +showErrorDetails = false + + +[runner] + +# Allows you to type a variable or string by itself in a single line of Python code to write it to the app. +# Default: true +magicEnabled = true + +# Install a Python tracer to allow you to stop or pause your script at any point and introspect it. As a side-effect, this slows down your script's execution. +# Default: false +installTracer = false + +# Sets the MPLBACKEND environment variable to Agg inside Streamlit to prevent Python crashing. +# Default: true +fixMatplotlib = true + +[theme] + +base="light" + +# Primary accent for interactive elements +primaryColor = '#5c84fa' + +[server] + +# List of folders that should not be watched for changes. This impacts both "Run on Save" and @st.cache. +# Relative paths will be taken as relative to the current working directory. +# Example: ['/home/user1/env', 'relative/path/to/folder'] +# Default: [] +folderWatchBlacklist = [] + +# Change the type of file watcher used by Streamlit, or turn it off completely. +# Allowed values: * "auto" : Streamlit will attempt to use the watchdog module, and falls back to polling if watchdog is not available. * "watchdog" : Force Streamlit to use the watchdog module. * "poll" : Force Streamlit to always use polling. * "none" : Streamlit will not watch files. +# Default: "auto" +fileWatcherType = "auto" + +# Symmetric key used to produce signed cookies. If deploying on multiple replicas, this should be set to the same value across all replicas to ensure they all share the same secret. +# Default: randomly generated secret key. +cookieSecret = "e95070f37458bc01a700421e511d14f7452df3a1a3f428d434fd127517277629" + +# If false, will attempt to open a browser window on start. +# Default: false unless (1) we are on a Linux box where DISPLAY is unset, or (2) server.liveSave is set. +headless = true + +# Automatically rerun script when the file is modified on disk. +# Default: false +runOnSave = false + +# The address where the server will listen for client and browser connections. Use this if you want to bind the server to a specific address. If set, the server will only be accessible from this address, and not from any aliases (like localhost). +# Default: (unset) +#address = + +# The port where the server will listen for browser connections. +# Default: 8501 +port = 8501 + +# The base path for the URL where Streamlit should be served from. +# Default: "" +baseUrlPath = "" + +# Enables support for Cross-Origin Request Sharing (CORS) protection, for added security. +# Due to conflicts between CORS and XSRF, if `server.enableXsrfProtection` is on and `server.enableCORS` is off at the same time, we will prioritize `server.enableXsrfProtection`. +# Default: true +enableCORS = true + +# Enables support for Cross-Site Request Forgery (XSRF) protection, for added security. +# Due to conflicts between CORS and XSRF, if `server.enableXsrfProtection` is on and `server.enableCORS` is off at the same time, we will prioritize `server.enableXsrfProtection`. +# Default: true +enableXsrfProtection = true + +# Max size, in megabytes, for files uploaded with the file_uploader. +# Default: 200 +maxUploadSize = 1 + +# Enables support for websocket compression. +# Default: true +enableWebsocketCompression = true + + +[browser] + +# Internet address where users should point their browsers in order to connect to the app. Can be IP address or DNS name and path. +# This is used to: - Set the correct URL for CORS and XSRF protection purposes. - Show the URL on the terminal - Open the browser - Tell the browser where to connect to the server when in liveSave mode. +# Default: 'localhost' +serverAddress = "localhost" + +# Whether to send usage statistics to Streamlit. +# Default: true +gatherUsageStats = true + +# Port where users should point their browsers in order to connect to the app. +# This is used to: - Set the correct URL for CORS and XSRF protection purposes. - Show the URL on the terminal - Open the browser - Tell the browser where to connect to the server when in liveSave mode. +# Default: whatever value is set in server.port. +serverPort = 8501 + + +[mapbox] + +# Configure Streamlit to use a custom Mapbox token for elements like st.pydeck_chart and st.map. To get a token for yourself, create an account at https://mapbox.com. It's free (for moderate usage levels)! +# Default: "" +token = "" + + +[deprecation] + +# Set to false to disable the deprecation warning for the file uploader encoding. +# Default: "True" +showfileUploaderEncoding = "True" + +# Set to false to disable the deprecation warning for using the global pyplot instance. +# Default: "True" +showPyplotGlobalUse = "True" + + +[s3] + +# Name of the AWS S3 bucket to save apps. +# Default: (unset) +#bucket = + +# URL root for external view of Streamlit apps. +# Default: (unset) +#url = + +# Access key to write to the S3 bucket. +# Leave unset if you want to use an AWS profile. +# Default: (unset) +#accessKeyId = + +# Secret access key to write to the S3 bucket. +# Leave unset if you want to use an AWS profile. +# Default: (unset) +#secretAccessKey = + +# The "subdirectory" within the S3 bucket where to save apps. +# S3 calls paths "keys" which is why the keyPrefix is like a subdirectory. Use "" to mean the root directory. +# Default: "" +keyPrefix = "" + +# AWS region where the bucket is located, e.g. "us-west-2". +# Default: (unset) +#region = + +# AWS credentials profile to use. +# Leave unset to use your default profile. +# Default: (unset) +#profile = diff --git a/streamlit/ligify/streamlit_app.py b/streamlit/ligify/streamlit_app.py index 439efe3..9b8fbb3 100644 --- a/streamlit/ligify/streamlit_app.py +++ b/streamlit/ligify/streamlit_app.py @@ -13,6 +13,7 @@ def setup_page(): + st.set_page_config(page_title="Ligify", layout='wide', initial_sidebar_state='auto', page_icon="ligify/images/Ligify_Favicon.png") #sys.tracebacklimit = 0 #removes traceback so code is not shown during errors @@ -27,7 +28,8 @@ def setup_page(): st.markdown(f'
Version: {ligify_version}
', unsafe_allow_html=True) - + # removed traceback error messages + st.markdown(""" """, unsafe_allow_html=True) # Removes the full-screen button for various elements From 83dd38a609eb2c0278ce7f121dc285574aa854dc Mon Sep 17 00:00:00 2001 From: Simon d'Oelsnitz Date: Thu, 9 May 2024 08:41:53 -0400 Subject: [PATCH 03/15] added usage note to the input page --- streamlit/ligify/streamlit_app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/streamlit/ligify/streamlit_app.py b/streamlit/ligify/streamlit_app.py index 9b8fbb3..16fc33b 100644 --- a/streamlit/ligify/streamlit_app.py +++ b/streamlit/ligify/streamlit_app.py @@ -102,6 +102,7 @@ def _connect_form_cb(connect_status): input_mode = head2.radio(label="Select an input mode", options=["SMILES", "Name", "Draw"], horizontal=True) + # OPTIONS options = st.container() col1, col2, col3 = options.columns((1,3,1)) @@ -133,7 +134,7 @@ def _connect_form_cb(connect_status): chemical = {"name": chemical_name, "smiles": smiles, "InChiKey": InChiKey} - + head2.write('Note: Chemical identifiers must be specific. For example, use `D-ribofuranose` rather than `ribose`') with st.sidebar: @@ -193,7 +194,7 @@ def _connect_form_cb(connect_status): # SUBMIT BUTTON submit = st.container() - submit_spacer_1, submit_button, submit_spacer_2 = submit.columns([5,1,5]) + submit_spacer_1, submit_button, submit_spacer_2 = submit.columns([5,3,5]) submitted = submit_button.form_submit_button("Submit", use_container_width=True, on_click=_connect_form_cb, args=(True,)) # PROGRESS BAR From e3d51b4cd8a5968d5655e1890ae343d2a62decb9 Mon Sep 17 00:00:00 2001 From: Simon d'Oelsnitz Date: Thu, 9 May 2024 08:56:57 -0400 Subject: [PATCH 04/15] fixed error handling to prevent crashing --- streamlit/ligify/predict/accID2operon.py | 48 +++++++++++++----------- streamlit/ligify/streamlit_app.py | 3 +- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/streamlit/ligify/predict/accID2operon.py b/streamlit/ligify/predict/accID2operon.py index a595ba8..48379e2 100644 --- a/streamlit/ligify/predict/accID2operon.py +++ b/streamlit/ligify/predict/accID2operon.py @@ -14,39 +14,41 @@ def acc2MetaData(access_id: str): result = requests.get(f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=protein&id={access_id}&rettype=ipg") if result.status_code != 200: - st.error("Sorry! NCBI won't send us data due to high traffic. Please try again soon.\ + st.error("Sorry! NCBI won't send us data due to high traffic. Please try again soon.\ (metadata eFetch HTTP !== 200)") + return "EMPTY" - parsed = xmltodict.parse(result.text) + else: + parsed = xmltodict.parse(result.text) - if "IPGReport" in parsed["IPGReportSet"].keys(): - if "ProteinList" in parsed["IPGReportSet"]["IPGReport"]: - protein = parsed["IPGReportSet"]["IPGReport"]["ProteinList"]["Protein"] + if "IPGReport" in parsed["IPGReportSet"].keys(): + if "ProteinList" in parsed["IPGReportSet"]["IPGReport"]: + protein = parsed["IPGReportSet"]["IPGReport"]["ProteinList"]["Protein"] - if isinstance(protein, list): - protein = protein[0] + if isinstance(protein, list): + protein = protein[0] - if "CDSList" not in protein.keys(): - return "EMPTY" + if "CDSList" not in protein.keys(): + return "EMPTY" - CDS = protein["CDSList"]["CDS"] + CDS = protein["CDSList"]["CDS"] - #CDS is a list if there is more than 1 CDS returned, otherwise it's a dictionary - if isinstance(CDS, list): - CDS = CDS[0] + #CDS is a list if there is more than 1 CDS returned, otherwise it's a dictionary + if isinstance(CDS, list): + CDS = CDS[0] - proteinDict = { - "accver":CDS["@accver"], - "start":CDS["@start"], - "stop":CDS["@stop"], - "strand":CDS["@strand"], - } + proteinDict = { + "accver":CDS["@accver"], + "start":CDS["@start"], + "stop":CDS["@stop"], + "strand":CDS["@strand"], + } - return proteinDict + return proteinDict + else: + return "EMPTY" else: return "EMPTY" - else: - return "EMPTY" @@ -145,6 +147,7 @@ def NC2genome(genome_id, operon): else: st.error("Sorry! NCBI won't send us data due to high traffic. Please try again soon.\ (genome eFetch HTTP !== 200)") + return "EMPTY", False @@ -172,6 +175,7 @@ def getGenes(genome_id, startPos, stopPos): except: st.error("Sorry! NCBI won't send us data due to high traffic. Please try again soon.\ (genome-fragment eFetch HTTP !== 200)") + return None re1 = re.compile(str(startPos)) diff --git a/streamlit/ligify/streamlit_app.py b/streamlit/ligify/streamlit_app.py index 16fc33b..9de5bc1 100644 --- a/streamlit/ligify/streamlit_app.py +++ b/streamlit/ligify/streamlit_app.py @@ -134,7 +134,8 @@ def _connect_form_cb(connect_status): chemical = {"name": chemical_name, "smiles": smiles, "InChiKey": InChiKey} - head2.write('Note: Chemical identifiers must be specific. For example, use `D-ribofuranose` rather than `ribose`') + head2.write('Note: Chemical identifiers must be specific. For example, use `D-ribofuranose` rather than `ribose`.\ + Also consider trying an alternative input mode for your chemical.') with st.sidebar: From 4fcdcda6b38a3c7f6cfa35cf55d8975f23c35b6d Mon Sep 17 00:00:00 2001 From: Josh Love Date: Mon, 13 May 2024 08:07:44 -0500 Subject: [PATCH 05/15] Adding https --- docker-compose.yml | 21 ++++++++++----------- nginx/Dockerfile | 10 +++++++++- nginx/http.conf | 12 ++++++++++++ nginx/{default.conf => https.conf} | 21 ++++++++++++++++++--- 4 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 nginx/http.conf rename nginx/{default.conf => https.conf} (59%) diff --git a/docker-compose.yml b/docker-compose.yml index 0562d50..0ea2025 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,12 +6,16 @@ services: build: ./nginx restart: always ports: - - 80:80 - deploy: - resources: - limits: - cpus: '0.2' - memory: '0.1g' + - "80:80" + - "443:443" + + certbot: + image: certbot/certbot + container_name: certbot + volumes: + - ./data/certbot/conf:/etc/letsencrypt + - ./data/certbot/www:/var/www/certbot + entrypoint: ["/bin/sh", "-c", "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;"] ligify: container_name: ligify @@ -23,8 +27,3 @@ services: - 8501:8501 depends_on: - nginx - deploy: - resources: - limits: - cpus: '1.8' - memory: '0.9g' diff --git a/nginx/Dockerfile b/nginx/Dockerfile index bf2f691..4ec9084 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,6 +1,14 @@ FROM nginx:latest +# Remove the default Nginx configuration file RUN rm /etc/nginx/conf.d/default.conf -COPY default.conf /etc/nginx/conf.d +# Copying Nginx configurations +COPY http.conf /etc/nginx/conf.d/http.conf +COPY https.conf /etc/nginx/conf.d/https.conf +# Create a directory for Certbot webroot +RUN mkdir -p /var/www/certbot + +# Expose both the HTTP and HTTPS ports +EXPOSE 80 443 diff --git a/nginx/http.conf b/nginx/http.conf new file mode 100644 index 0000000..5ebeea4 --- /dev/null +++ b/nginx/http.conf @@ -0,0 +1,12 @@ +server { + listen 80; + server_name ligify_server; # Use your actual server name or domain + + location /.well-known/acme-challenge/ { + root /var/www/certbot; # Ensure this directory is accessible by Nginx + } + + location / { + return 301 https://$host$request_uri; + } +} diff --git a/nginx/default.conf b/nginx/https.conf similarity index 59% rename from nginx/default.conf rename to nginx/https.conf index 639f78c..d75b7e9 100644 --- a/nginx/default.conf +++ b/nginx/https.conf @@ -1,20 +1,34 @@ server { + listen 443 ssl; + server_name ligify.groov.bio; # Use your actual server name or domain - listen 80; - server_name ligify_server; + ssl_certificate /etc/letsencrypt/live/ligify_server/fullchain.pem + ssl_certificate_key /etc/letsencrypt/live/ligify_server/privkey.pem + include /etc/letsencrypt/options-ssl-nginx.conf + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem location / { proxy_pass http://ligify:8501/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; } + location ^~ /static { proxy_pass http://ligify:8501/static/; } + location ^~ /healthz { proxy_pass http://ligify:8501/healthz; } + location ^~ /vendor { proxy_pass http://ligify:8501/vendor; } + location /stream { proxy_pass http://ligify:8501/stream; proxy_http_version 1.1; @@ -24,6 +38,7 @@ server { proxy_set_header Connection "upgrade"; proxy_read_timeout 86400; } + location ^~ /_stcore { proxy_pass http://ligify:8501; proxy_set_header Upgrade $http_upgrade; @@ -32,4 +47,4 @@ server { proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } -} \ No newline at end of file +} From 9ca38a1ed13abab9c4b2f4d3ec6ba06fee2c2aa0 Mon Sep 17 00:00:00 2001 From: Josh Love Date: Mon, 13 May 2024 10:27:36 -0500 Subject: [PATCH 06/15] Remove ipv6 listener --- nginx/Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 4ec9084..9b8049b 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,9 +1,12 @@ FROM nginx:latest +# Set environment variable to disable IPv6 listener script +ENV NGINX_DISABLE_IPV6=true + # Remove the default Nginx configuration file RUN rm /etc/nginx/conf.d/default.conf -# Copying Nginx configurations +# Copy custom Nginx configurations COPY http.conf /etc/nginx/conf.d/http.conf COPY https.conf /etc/nginx/conf.d/https.conf @@ -11,4 +14,4 @@ COPY https.conf /etc/nginx/conf.d/https.conf RUN mkdir -p /var/www/certbot # Expose both the HTTP and HTTPS ports -EXPOSE 80 443 +EXPOSE 80 443 \ No newline at end of file From 200ada96f1348abe26bfad5fb8056772784b43fa Mon Sep 17 00:00:00 2001 From: Josh Love Date: Mon, 13 May 2024 10:31:25 -0500 Subject: [PATCH 07/15] Add missing semi-colons --- nginx/https.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nginx/https.conf b/nginx/https.conf index d75b7e9..83c2ede 100644 --- a/nginx/https.conf +++ b/nginx/https.conf @@ -2,10 +2,10 @@ server { listen 443 ssl; server_name ligify.groov.bio; # Use your actual server name or domain - ssl_certificate /etc/letsencrypt/live/ligify_server/fullchain.pem - ssl_certificate_key /etc/letsencrypt/live/ligify_server/privkey.pem - include /etc/letsencrypt/options-ssl-nginx.conf - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem + ssl_certificate /etc/letsencrypt/live/ligify_server/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/ligify_server/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; location / { proxy_pass http://ligify:8501/; From 10ebb46512333c7afca6bd498f8c74b5407097b3 Mon Sep 17 00:00:00 2001 From: Josh Love Date: Mon, 13 May 2024 10:42:39 -0500 Subject: [PATCH 08/15] Adjust http.conf redirect for letsencrypt challenge --- nginx/http.conf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nginx/http.conf b/nginx/http.conf index 5ebeea4..8f98067 100644 --- a/nginx/http.conf +++ b/nginx/http.conf @@ -1,9 +1,11 @@ server { listen 80; - server_name ligify_server; # Use your actual server name or domain + server_name ligify.groov.bio; # Change to your actual domain location /.well-known/acme-challenge/ { - root /var/www/certbot; # Ensure this directory is accessible by Nginx + root /var/www/certbot; + try_files $uri =404; + allow all; } location / { From d6e669f56a0d1fb57a0c361b015a87e7fe25dc3b Mon Sep 17 00:00:00 2001 From: Josh Love Date: Mon, 13 May 2024 13:26:41 -0500 Subject: [PATCH 09/15] Add new comment to docker-compose for initial cert --- docker-compose.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 0ea2025..eca677f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,8 @@ services: ports: - "80:80" - "443:443" + volumes: + - ./data/certbot/www:/var/www/certbot certbot: image: certbot/certbot @@ -15,6 +17,10 @@ services: volumes: - ./data/certbot/conf:/etc/letsencrypt - ./data/certbot/www:/var/www/certbot +# Use the below command to create an initial certificate +# entrypoint: ["/bin/sh", "-c"] +# command: ["certbot certonly --webroot --webroot-path=/var/www/certbot --email ENTER_EMAIL_HERE --agree-tos --no-eff-em +# ail -d ligify.groov.bio --non-interactive"] entrypoint: ["/bin/sh", "-c", "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;"] ligify: From 619d75f52ca0b328418c2bf63cd487550dbd7366 Mon Sep 17 00:00:00 2001 From: Josh Love Date: Mon, 13 May 2024 14:13:54 -0500 Subject: [PATCH 10/15] Add missing volume --- docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index eca677f..6be2900 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,8 @@ services: - "443:443" volumes: - ./data/certbot/www:/var/www/certbot - + - ./data/certbot/conf:/etc/letsencrypt + certbot: image: certbot/certbot container_name: certbot From bd69fdd96222b84df536f42c4ecb42c6918d1f47 Mon Sep 17 00:00:00 2001 From: Josh Love Date: Mon, 13 May 2024 14:25:20 -0500 Subject: [PATCH 11/15] Fix cert location --- nginx/https.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nginx/https.conf b/nginx/https.conf index 83c2ede..05ff16c 100644 --- a/nginx/https.conf +++ b/nginx/https.conf @@ -2,8 +2,8 @@ server { listen 443 ssl; server_name ligify.groov.bio; # Use your actual server name or domain - ssl_certificate /etc/letsencrypt/live/ligify_server/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/ligify_server/privkey.pem; + ssl_certificate /etc/letsencrypt/live/ligify.groov.bio/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/ligify.groov.bio/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; From 5b0af2f03a0efbda90ce43130c8e8d3163a8a616 Mon Sep 17 00:00:00 2001 From: Josh Love Date: Thu, 23 May 2024 18:20:33 -0500 Subject: [PATCH 12/15] Working job queue --- docker-compose.yml | 52 +++-- nginx/Dockerfile | 2 +- streamlit/Dockerfile | 15 +- streamlit/Dockerfile.celery | 16 ++ streamlit/__init__.py | 0 streamlit/celery_worker.py | 22 ++ streamlit/entry.py | 48 +++- streamlit/ligify/format_results.py | 353 +++++++++++++---------------- streamlit/ligify/streamlit_app.py | 107 ++++----- streamlit/requirements.txt | 4 +- 10 files changed, 347 insertions(+), 272 deletions(-) create mode 100644 streamlit/Dockerfile.celery create mode 100644 streamlit/__init__.py create mode 100644 streamlit/celery_worker.py diff --git a/docker-compose.yml b/docker-compose.yml index 6be2900..54f6776 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,25 +12,51 @@ services: - ./data/certbot/www:/var/www/certbot - ./data/certbot/conf:/etc/letsencrypt - certbot: - image: certbot/certbot - container_name: certbot - volumes: - - ./data/certbot/conf:/etc/letsencrypt - - ./data/certbot/www:/var/www/certbot -# Use the below command to create an initial certificate -# entrypoint: ["/bin/sh", "-c"] -# command: ["certbot certonly --webroot --webroot-path=/var/www/certbot --email ENTER_EMAIL_HERE --agree-tos --no-eff-em -# ail -d ligify.groov.bio --non-interactive"] - entrypoint: ["/bin/sh", "-c", "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;"] +# certbot: +# image: certbot/certbot +# container_name: certbot +# volumes: +# - ./data/certbot/conf:/etc/letsencrypt +# - ./data/certbot/www:/var/www/certbot +# # Use the below command to create an initial certificate +# # entrypoint: ["/bin/sh", "-c"] +# # command: ["certbot certonly --webroot --webroot-path=/var/www/certbot --email ENTER_EMAIL_HERE --agree-tos --no-eff-em +# # ail -d ligify.groov.bio --non-interactive"] +# entrypoint: ["/bin/sh", "-c", "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;"] + ligify: container_name: ligify - build: - ./streamlit + build: ./streamlit restart: always entrypoint: ["streamlit", "run", "entry.py"] ports: - 8501:8501 depends_on: - nginx + - redis + - celery_worker + + redis: + image: redis:5.0.4 + container_name: redis + restart: always + ports: + - "6379:6379" + volumes: + - redis-data:/data + + celery_worker: + container_name: celery_worker + build: + context: ./streamlit + dockerfile: Dockerfile.celery + command: celery -A celery_worker worker --loglevel=info --concurrency=1 + depends_on: + - redis + environment: + - CELERY_BROKER_URL=redis://redis:6379/0 + - CELERY_RESULT_BACKEND=redis://redis:6379/0 + +volumes: + redis-data: \ No newline at end of file diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 9b8049b..a8cc8d0 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -8,7 +8,7 @@ RUN rm /etc/nginx/conf.d/default.conf # Copy custom Nginx configurations COPY http.conf /etc/nginx/conf.d/http.conf -COPY https.conf /etc/nginx/conf.d/https.conf +#COPY https.conf /etc/nginx/conf.d/https.conf # Create a directory for Certbot webroot RUN mkdir -p /var/www/certbot diff --git a/streamlit/Dockerfile b/streamlit/Dockerfile index ec26f3e..57a2a2a 100644 --- a/streamlit/Dockerfile +++ b/streamlit/Dockerfile @@ -1,10 +1,19 @@ +# streamlit/Dockerfile FROM python:3.10 +# Create and set the working directory RUN mkdir /app - WORKDIR /app +# Copy requirements and install dependencies COPY requirements.txt . - RUN pip install -r requirements.txt -COPY . . \ No newline at end of file + +# Copy the entire app +COPY . . + +# Set PYTHONPATH to include both /app and /app/ligify +ENV PYTHONPATH=/app:/app/ligify + +# Expose the default Streamlit port +EXPOSE 8501 diff --git a/streamlit/Dockerfile.celery b/streamlit/Dockerfile.celery new file mode 100644 index 0000000..f2acad7 --- /dev/null +++ b/streamlit/Dockerfile.celery @@ -0,0 +1,16 @@ +# streamlit/Dockerfile.celery +FROM python:3.10 + +# Create and set the working directory +RUN mkdir /app +WORKDIR /app + +# Copy requirements and install dependencies +COPY requirements.txt . +RUN pip install -r requirements.txt + +# Copy the entire app +COPY . . + +# Set PYTHONPATH to include both /app and /app/ligify +ENV PYTHONPATH=/app:/app/ligify diff --git a/streamlit/__init__.py b/streamlit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/streamlit/celery_worker.py b/streamlit/celery_worker.py new file mode 100644 index 0000000..a76af42 --- /dev/null +++ b/streamlit/celery_worker.py @@ -0,0 +1,22 @@ +# streamlit/celery_worker.py +from celery import Celery +from ligify.fetch_data import fetch_data + +app = Celery('celery_worker', broker='redis://redis:6379/0', backend='redis://redis:6379/0') + +app.conf.update( + result_backend='redis://redis:6379/0', + task_serializer='json', + accept_content=['json'], + result_serializer='json', + timezone='UTC', + enable_utc=True, + worker_concurrency=1, # Ensure only one task is processed at a time +) + +@app.task +def run_ligify_task(chemical, filters): + # Fetch data + regulators, metrics = fetch_data(chemical["InChiKey"], filters) + + return {"regulators": regulators, "metrics": metrics} diff --git a/streamlit/entry.py b/streamlit/entry.py index 2ceab3b..e919ebc 100644 --- a/streamlit/entry.py +++ b/streamlit/entry.py @@ -1,12 +1,48 @@ +import streamlit as st +from ligify.streamlit_app import run_streamlit, run_ligify +from celery_worker import run_ligify_task, app +import time -import streamlit.web.cli -import sys +def streamlit_run(): + chem, results, prog, chemical, filters = run_streamlit() + + print('STREAMLIT_RUN') -from ligify.streamlit_app import run_streamlit, run_ligify + if st.session_state.SUBMITTED: + # Start Celery task + task = run_ligify_task.delay(chemical, filters) + st.session_state.task_id = task.id + st.session_state.SUBMITTED = False + st.experimental_rerun() # Trigger a rerun to avoid immediate execution + if 'task_id' in st.session_state and st.session_state.task_id is not None: + task_id = st.session_state.task_id + task = app.AsyncResult(task_id) -def streamlit_run(): - chem, results, prog, smiles, filters = run_streamlit() - run_ligify(chem, results, prog, smiles, filters) + if task.state == 'SUCCESS': + st.session_state.result = task.result + st.session_state.task_id = None + print('APP IS SUCCESS') + st.experimental_rerun() # Force rerun to update UI + elif task.state == 'PENDING': + st.write('Task is in the queue, please wait...') + time.sleep(1) # Sleep for a second to avoid too many reruns + st.experimental_rerun() # Check again + elif task.state == 'STARTED': + st.write('Task is currently being processed...') + time.sleep(1) # Sleep for a second to avoid too many reruns + st.experimental_rerun() # Check again + elif task.state == 'FAILURE': + st.write('Task failed, please try again.') + st.session_state.task_id = None + + if 'result' in st.session_state: + regulators = st.session_state.result['regulators'] + metrics = st.session_state.result['metrics'] + + # Update the UI with the results + run_ligify(chem, results, prog, chemical, filters, regulators, metrics) + + print(st.session_state) streamlit_run() \ No newline at end of file diff --git a/streamlit/ligify/format_results.py b/streamlit/ligify/format_results.py index 123f74d..e7fe178 100644 --- a/streamlit/ligify/format_results.py +++ b/streamlit/ligify/format_results.py @@ -62,198 +62,161 @@ def download_df(ligand_name, genbank_con): -def format_results(data_column, ligand_name): - - if st.session_state.data: - - # Header - refseq = st.session_state.data['refseq'] - data_column.write("") - data_column.write("") - data_column.markdown(f'

{refseq}

', unsafe_allow_html=True) - - data_column.divider() - - - # Spacer - data_column.text("") - data_column.text("") - - - # Regulator info - reg_ncbi_anno = st.session_state.data["annotation"] - reg_protein_seq = st.session_state.data["reg_protein_seq"] - reg_uniprot_id = st.session_state.data["uniprot_reg_data"]["id"] - organism = st.session_state.data["protein"]["organism"] - organism_name = str(organism[-2])+", "+str(organism[-1]) - - reg_json = { - "name": "Regulator attribute", - "NCBI annotation": reg_ncbi_anno, - "Uniprot ID": reg_uniprot_id, - "Protein length": len(reg_protein_seq), - "Organism": organism_name, - } - - #Regulator container - regulator_con = data_column.container() - - #GenBank container - genbank_con = data_column.container() - - reg_info, reg_spacer, reg_genbank = regulator_con.columns((8,1,5)) - regulator_df = pd.DataFrame(reg_json, index=["name"]).astype(str) - regulator_df.set_index("name", inplace=True) - regulator_df = regulator_df.T - reg_info.subheader("Regulator information") - reg_info.table(regulator_df) - - reg_genbank.header("") - reg_genbank.header("") - reg_genbank.form_submit_button(label="Show Plasmid", type="primary", on_click=download_df, args=(ligand_name,genbank_con)) - reg_genbank.markdown(f'

This plasmid is designed to induce GFP expression in the presence of the target molecule via '+str(refseq)+', within E. coli', unsafe_allow_html=True) - - - - - # Enzyme info - enz_annotation = st.session_state.data['protein']['enzyme']['description'] - enz_refseq = st.session_state.data['protein']['enzyme']['ncbi_id'] - equation = st.session_state.data['equation'] - rhea_id = st.session_state.data['rhea_id'] - references = st.session_state.data['protein']['enzyme']['dois'] - - enz_json = {"name": "Enzyme attribute", - "Annotation": enz_annotation, - "Reaction": equation, - "RHEA ID": rhea_id, - "RefSeq ID": enz_refseq, - } - - enzyme_and_lig = data_column.container() - enz, alt_lig = enzyme_and_lig.columns([1,1]) - enz_df = pd.DataFrame(enz_json, index=[0]) - enz_df.set_index("name", inplace=True) - enz_df = enz_df.T - enz.subheader("Associated enzyme") - enz.table(enz_df) - - - # Alternative ligands - alt_lig.subheader("Possible alternative ligands") - alt_lig_list = st.session_state.data['alt_ligands'] - lig_html = "

    " - for lig in alt_lig_list: - lig_html += "
  • " + lig + "
  • " - lig_html += "
" - alt_lig.markdown(lig_html, unsafe_allow_html=True) - #alt_lig.markdown("- " + lig + "\n") - #alt_lig.table(st.session_state.data['alt_ligands']) - #TODO: Clean this table up - - - # Enzyme references - ref_and_rank = data_column.container() - reference_col, rank_col = ref_and_rank.columns(2) - reference_col.subheader("Enzyme references") - for i in references: - reference_col.markdown(f'{"https://doi.org/"+i}', unsafe_allow_html=True) - - - - # Rank metrics - rank_col.subheader("Rank description") - rank_metrics = st.session_state.data["rank"]["metrics"] - - rank_df = pd.DataFrame(rank_metrics).T - #orient='index') - # rank_df = pd.DataFrame.from_dict({(i,j): rank_metrics[i][j] - # for i in rank_metrics.keys() - # for j in rank_metrics[i].keys()}, - # orient='index') - rank_col.dataframe(rank_df) - - - - - - # Operon - operon_seq = data_column.container() - operon_seq.subheader("Operon") - operon_data = st.session_state.data['protein']['context'] - operon_json = operon_data['operon'] - - with operon_seq.expander(label="Click here to see the genetic context for "+str(refseq)): - - - # Get the regulator position within the operon - reg_index = 0 - for i in operon_json: - if i["accession"] == refseq: - break - else: - reg_index += 1 - - # Get the enzyme position within the operon - enz_index = st.session_state.data['protein']['context']['enzyme_index'] - - - # Create the operon table - genes = [] - for i in operon_json: - if "description" in i.keys(): - gene = { - "RefSeq ID": i['accession'], - "Description": i['description'], - "Direction": i['direction'], - "Start position": i['start'], - "End position": i['stop'] - } - else: - gene = { - "RefSeq ID": i['accession'], - "Description": " ", - "Direction": i['direction'], - "Start position": i['start'], - "End position": i['stop'] - } - genes.append(gene) - operon_df = pd.DataFrame(genes) - - # Color the operon table - colors = ["#fcb1b1", "#e6cffc", "#9afcac", "#fcc99d", "#a3fff6", "#fdff9c", "#ccd4fc", "#fcbdf6"] - def bg_color_col(col): - color = [colors[i % len(colors)] for i,x in col.items()] - return ['background-color: %s' % color[i] - if col.name=='RefSeq ID' or (col.name=="Description" and i==reg_index) or (col.name=="Description" and i==enz_index) - else '' - for i,x in col.items()] - - operon_df = operon_df.style.apply(bg_color_col) - st.table(operon_df) - - - # Display the predicted promoter - st.markdown("
Predicted promoter
", unsafe_allow_html=True) - st.write(operon_data["promoter"]['regulated_seq']) - - - # Create and display the color-annotated genome fragment - operon_seq = "" - - c = 0 - for seq in operon_data["operon_seq"]: - sequence = operon_data["operon_seq"][seq] - if re.compile(r"spacer").search(seq): - html = ""+str(sequence)+"" - elif re.compile(r"overlap").search(seq): - html = ""+str(sequence)+"" - elif re.compile(r"fwd").search(seq): - html = f""+str(sequence)+"" - c += 1 - else: - html = f""+str(sequence)+"" - c += 1 - operon_seq += html - st.markdown("
Full operon sequence
", unsafe_allow_html=True) - st.markdown(operon_seq, unsafe_allow_html=True) +def format_results(data_column, ligand_name, data): + + print(len(data)) + + print(data) + + refseq = data['refseq'] + data_column.write("") + data_column.write("") + data_column.markdown(f'

{refseq}

', unsafe_allow_html=True) + + data_column.divider() + + # Spacer + data_column.text("") + data_column.text("") + + # Regulator info + reg_ncbi_anno = data["annotation"] + reg_protein_seq = data["reg_protein_seq"] + reg_uniprot_id = data["uniprot_reg_data"]["id"] + organism = data["protein"]["organism"] + organism_name = str(organism[-2]) + ", " + str(organism[-1]) + + reg_json = { + "name": "Regulator attribute", + "NCBI annotation": reg_ncbi_anno, + "Uniprot ID": reg_uniprot_id, + "Protein length": len(reg_protein_seq), + "Organism": organism_name, + } + + # Regulator container + regulator_con = data_column.container() + + # GenBank container + genbank_con = data_column.container() + + reg_info, reg_spacer, reg_genbank = regulator_con.columns((8,1,5)) + regulator_df = pd.DataFrame(reg_json, index=["name"]).astype(str) + regulator_df.set_index("name", inplace=True) + regulator_df = regulator_df.T + reg_info.subheader("Regulator information") + reg_info.table(regulator_df) + + reg_genbank.header("") + reg_genbank.header("") + reg_genbank.form_submit_button(label=f"Show Plasmid for {data['refseq']}", type="primary", on_click=download_df, args=(ligand_name,genbank_con)) + reg_genbank.markdown(f'

This plasmid is designed to induce GFP expression in the presence of the target molecule via {refseq}, within E. coli', unsafe_allow_html=True) + + # Enzyme info + enz_annotation = data['protein']['enzyme']['description'] + enz_refseq = data['protein']['enzyme']['ncbi_id'] + equation = data['equation'] + rhea_id = data['rhea_id'] + references = data['protein']['enzyme']['dois'] + + enz_json = { + "name": "Enzyme attribute", + "Annotation": enz_annotation, + "Reaction": equation, + "RHEA ID": rhea_id, + "RefSeq ID": enz_refseq, + } + + enzyme_and_lig = data_column.container() + enz, alt_lig = enzyme_and_lig.columns([1,1]) + enz_df = pd.DataFrame(enz_json, index=[0]) + enz_df.set_index("name", inplace=True) + enz_df = enz_df.T + enz.subheader("Associated enzyme") + enz.table(enz_df) + + # Alternative ligands + alt_lig.subheader("Possible alternative ligands") + alt_lig_list = data['alt_ligands'] + lig_html = "

    " + for lig in alt_lig_list: + lig_html += "
  • " + lig + "
  • " + lig_html += "
" + alt_lig.markdown(lig_html, unsafe_allow_html=True) + + # Enzyme references + ref_and_rank = data_column.container() + reference_col, rank_col = ref_and_rank.columns(2) + reference_col.subheader("Enzyme references") + for i in references: + reference_col.markdown(f'{"https://doi.org/"+i}', unsafe_allow_html=True) + + # Rank metrics + rank_col.subheader("Rank description") + rank_metrics = data["rank"]["metrics"] + + rank_df = pd.DataFrame(rank_metrics).T + rank_col.dataframe(rank_df) + + # Operon + operon_seq = data_column.container() + operon_seq.subheader("Operon") + operon_data = data['protein']['context'] + operon_json = operon_data['operon'] + + with operon_seq.expander(label=f"Click here to see the genetic context for {refseq}"): + # Get the regulator position within the operon + reg_index = 0 + for i in operon_json: + if i["accession"] == refseq: + break + else: + reg_index += 1 + + # Get the enzyme position within the operon + enz_index = data['protein']['context']['enzyme_index'] + + # Create the operon table + genes = [] + for i in operon_json: + gene = { + "RefSeq ID": i['accession'], + "Description": i.get("description", " "), + "Direction": i['direction'], + "Start position": i['start'], + "End position": i['stop'] + } + genes.append(gene) + operon_df = pd.DataFrame(genes) + + # Color the operon table + colors = ["#fcb1b1", "#e6cffc", "#9afcac", "#fcc99d", "#a3fff6", "#fdff9c", "#ccd4fc", "#fcbdf6"] + def bg_color_col(col): + color = [colors[i % len(colors)] for i, x in col.items()] + return ['background-color: %s' % color[i] if col.name == 'RefSeq ID' or (col.name == "Description" and i == reg_index) or (col.name == "Description" and i == enz_index) else '' for i, x in col.items()] + + operon_df = operon_df.style.apply(bg_color_col) + st.table(operon_df) + + # Display the predicted promoter + st.markdown("
Predicted promoter
", unsafe_allow_html=True) + st.write(operon_data["promoter"]['regulated_seq']) + + # Create and display the color-annotated genome fragment + operon_seq_html = "" + c = 0 + for seq in operon_data["operon_seq"]: + sequence = operon_data["operon_seq"][seq] + if re.compile(r"spacer").search(seq): + html = "" + str(sequence) + "" + elif re.compile(r"overlap").search(seq): + html = "" + str(sequence) + "" + elif re.compile(r"fwd").search(seq): + html = f"" + str(sequence) + "" + c += 1 + else: + html = f"" + str(sequence) + "" + c += 1 + operon_seq_html += html + st.markdown("
Full operon sequence
", unsafe_allow_html=True) + st.markdown(operon_seq_html, unsafe_allow_html=True) diff --git a/streamlit/ligify/streamlit_app.py b/streamlit/ligify/streamlit_app.py index 9de5bc1..3172083 100644 --- a/streamlit/ligify/streamlit_app.py +++ b/streamlit/ligify/streamlit_app.py @@ -221,66 +221,67 @@ def _connect_form_cb(connect_status): # This is essentially the frontend component of the Ligify Web applications. -def run_ligify(chem, results, progress, chemical, filters): +def run_ligify(chem, results, progress, chemical, filters, regulators, metrics): m_spacer1, metrics_col, m_spacer2 = results.container().columns((1,3,1)) regulator_column, data_column = results.columns([1,3]) + + print('inside') - if st.session_state.SUBMITTED: - - if chemical["smiles"] == None: + if chemical["smiles"] == None: data_column.subheader("Chemical input was not recognized. Please try a different input method.") - else: - - # SMILES = str(chemical_smiles) - # chem.image(f'http://hulab.rxnfinder.org/smi2img/{SMILES}/', width=200) + else: + # SMILES = str(chemical_smiles) + # chem.image(f'http://hulab.rxnfinder.org/smi2img/{SMILES}/', width=200) + + if not regulators and not metrics: regulators, metrics = fetch_data(chemical["InChiKey"], filters) - select_spacerL, please_select, select_spacerR = data_column.container().columns([1,2,1]) - - format_results(data_column, chemical["name"]) - - regulator_column.header('') - regulator_column.subheader('Sensor candidates') - regulator_column.divider() - - # If no regulators are returned, suggest alternative queries - if regulators == None: - similar_chemicals = blast_chemical(chemical["smiles"], filters["max_alt_chems"]) - regulator_column.write("No regulators found") - please_select.subheader("No associated reactions :pensive:") - please_select.write("Consider an alternative query") - data_column.dataframe(similar_chemicals) - - # If regulators are returned, format display - else: - - # Metrics data - metrics_col.subheader("Search metrics") - m_rhea, m_genes, m_filtered, m_operons, m_regs = metrics_col.columns(5) - m_rhea.metric("Rhea reactions", metrics["RHEA Reactions"]) - m_genes.metric("Bacterial genes", metrics["Total genes"]) - m_filtered.metric("Filtered genes", metrics["Filtered genes"]) - m_operons.metric("Operons", metrics["Total operons"]) - m_regs.metric("Regulators", metrics["Total regulators"]) - metrics_col.divider() - - if not st.session_state.data: - please_select.subheader("Please select a regulator") - - reg_acc_col, rank_col = regulator_column.columns((2,1)) - reg_acc_col.markdown("
Regulator
", unsafe_allow_html=True) - rank_col.markdown("
Rank
", unsafe_allow_html=True) - - for i in range(0, len(regulators)): - name = "var"+str(i) - rank = regulators[i]["rank"]["rank"] - color = regulators[i]["rank"]["color"] - rank_col.markdown(f"

{rank}

", unsafe_allow_html=True) - name = reg_acc_col.form_submit_button(regulators[i]['refseq']) - if name: - st.session_state.data = regulators[i] - st.experimental_rerun() + select_spacerL, please_select, select_spacerR = data_column.container().columns([1,2,1]) + + for entry in regulators: + format_results(data_column, chemical["name"], entry) + + regulator_column.header('') + regulator_column.subheader('Sensor candidates') + regulator_column.divider() + + # If no regulators are returned, suggest alternative queries + if regulators == None: + similar_chemicals = blast_chemical(chemical["smiles"], filters["max_alt_chems"]) + regulator_column.write("No regulators found") + please_select.subheader("No associated reactions :pensive:") + please_select.write("Consider an alternative query") + data_column.dataframe(similar_chemicals) + + # If regulators are returned, format display + else: + # Metrics data + metrics_col.subheader("Search metrics") + m_rhea, m_genes, m_filtered, m_operons, m_regs = metrics_col.columns(5) + m_rhea.metric("Rhea reactions", metrics["RHEA Reactions"]) + m_genes.metric("Bacterial genes", metrics["Total genes"]) + m_filtered.metric("Filtered genes", metrics["Filtered genes"]) + m_operons.metric("Operons", metrics["Total operons"]) + m_regs.metric("Regulators", metrics["Total regulators"]) + metrics_col.divider() + + if not st.session_state.data: + please_select.subheader("Please select a regulator") + + reg_acc_col, rank_col = regulator_column.columns((2,1)) + reg_acc_col.markdown("
Regulator
", unsafe_allow_html=True) + rank_col.markdown("
Rank
", unsafe_allow_html=True) + + for i in range(0, len(regulators)): + name = "var"+str(i) + rank = regulators[i]["rank"]["rank"] + color = regulators[i]["rank"]["color"] + rank_col.markdown(f"

{rank}

", unsafe_allow_html=True) + name = reg_acc_col.form_submit_button(regulators[i]['refseq']) + if name: + st.session_state.data = regulators[i] + st.experimental_rerun() diff --git a/streamlit/requirements.txt b/streamlit/requirements.txt index 8f0a149..ff242dc 100644 --- a/streamlit/requirements.txt +++ b/streamlit/requirements.txt @@ -10,4 +10,6 @@ streamlit-ketcher==0.0.1 rdkit==2023.3.2 libChEBIpy==1.0.10 biopython==1.81 -dnachisel==3.2.11 \ No newline at end of file +dnachisel==3.2.11 +redis==5.0.4 +celery==5.4.0 \ No newline at end of file From ae5055a3074403a0ea76d7e9c5925ab4066da612 Mon Sep 17 00:00:00 2001 From: Josh Love Date: Thu, 23 May 2024 19:53:51 -0500 Subject: [PATCH 13/15] Finish functionality and return https --- docker-compose.yml | 14 +- nginx/Dockerfile | 2 +- streamlit/entry.py | 5 - streamlit/ligify/format_results.py | 353 ++++++++++++++++------------- streamlit/ligify/streamlit_app.py | 9 +- 5 files changed, 206 insertions(+), 177 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 54f6776..9b429ce 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,17 +12,17 @@ services: - ./data/certbot/www:/var/www/certbot - ./data/certbot/conf:/etc/letsencrypt -# certbot: -# image: certbot/certbot -# container_name: certbot -# volumes: -# - ./data/certbot/conf:/etc/letsencrypt -# - ./data/certbot/www:/var/www/certbot + certbot: + image: certbot/certbot + container_name: certbot + volumes: + - ./data/certbot/conf:/etc/letsencrypt + - ./data/certbot/www:/var/www/certbot # # Use the below command to create an initial certificate # # entrypoint: ["/bin/sh", "-c"] # # command: ["certbot certonly --webroot --webroot-path=/var/www/certbot --email ENTER_EMAIL_HERE --agree-tos --no-eff-em # # ail -d ligify.groov.bio --non-interactive"] -# entrypoint: ["/bin/sh", "-c", "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;"] + entrypoint: ["/bin/sh", "-c", "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;"] ligify: diff --git a/nginx/Dockerfile b/nginx/Dockerfile index a8cc8d0..9b8049b 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -8,7 +8,7 @@ RUN rm /etc/nginx/conf.d/default.conf # Copy custom Nginx configurations COPY http.conf /etc/nginx/conf.d/http.conf -#COPY https.conf /etc/nginx/conf.d/https.conf +COPY https.conf /etc/nginx/conf.d/https.conf # Create a directory for Certbot webroot RUN mkdir -p /var/www/certbot diff --git a/streamlit/entry.py b/streamlit/entry.py index e919ebc..870f372 100644 --- a/streamlit/entry.py +++ b/streamlit/entry.py @@ -5,8 +5,6 @@ def streamlit_run(): chem, results, prog, chemical, filters = run_streamlit() - - print('STREAMLIT_RUN') if st.session_state.SUBMITTED: # Start Celery task @@ -22,7 +20,6 @@ def streamlit_run(): if task.state == 'SUCCESS': st.session_state.result = task.result st.session_state.task_id = None - print('APP IS SUCCESS') st.experimental_rerun() # Force rerun to update UI elif task.state == 'PENDING': st.write('Task is in the queue, please wait...') @@ -42,7 +39,5 @@ def streamlit_run(): # Update the UI with the results run_ligify(chem, results, prog, chemical, filters, regulators, metrics) - - print(st.session_state) streamlit_run() \ No newline at end of file diff --git a/streamlit/ligify/format_results.py b/streamlit/ligify/format_results.py index e7fe178..ced87c8 100644 --- a/streamlit/ligify/format_results.py +++ b/streamlit/ligify/format_results.py @@ -62,161 +62,198 @@ def download_df(ligand_name, genbank_con): -def format_results(data_column, ligand_name, data): - - print(len(data)) - - print(data) - - refseq = data['refseq'] - data_column.write("") - data_column.write("") - data_column.markdown(f'

{refseq}

', unsafe_allow_html=True) - - data_column.divider() - - # Spacer - data_column.text("") - data_column.text("") - - # Regulator info - reg_ncbi_anno = data["annotation"] - reg_protein_seq = data["reg_protein_seq"] - reg_uniprot_id = data["uniprot_reg_data"]["id"] - organism = data["protein"]["organism"] - organism_name = str(organism[-2]) + ", " + str(organism[-1]) - - reg_json = { - "name": "Regulator attribute", - "NCBI annotation": reg_ncbi_anno, - "Uniprot ID": reg_uniprot_id, - "Protein length": len(reg_protein_seq), - "Organism": organism_name, - } - - # Regulator container - regulator_con = data_column.container() - - # GenBank container - genbank_con = data_column.container() - - reg_info, reg_spacer, reg_genbank = regulator_con.columns((8,1,5)) - regulator_df = pd.DataFrame(reg_json, index=["name"]).astype(str) - regulator_df.set_index("name", inplace=True) - regulator_df = regulator_df.T - reg_info.subheader("Regulator information") - reg_info.table(regulator_df) - - reg_genbank.header("") - reg_genbank.header("") - reg_genbank.form_submit_button(label=f"Show Plasmid for {data['refseq']}", type="primary", on_click=download_df, args=(ligand_name,genbank_con)) - reg_genbank.markdown(f'

This plasmid is designed to induce GFP expression in the presence of the target molecule via {refseq}, within E. coli', unsafe_allow_html=True) - - # Enzyme info - enz_annotation = data['protein']['enzyme']['description'] - enz_refseq = data['protein']['enzyme']['ncbi_id'] - equation = data['equation'] - rhea_id = data['rhea_id'] - references = data['protein']['enzyme']['dois'] - - enz_json = { - "name": "Enzyme attribute", - "Annotation": enz_annotation, - "Reaction": equation, - "RHEA ID": rhea_id, - "RefSeq ID": enz_refseq, - } - - enzyme_and_lig = data_column.container() - enz, alt_lig = enzyme_and_lig.columns([1,1]) - enz_df = pd.DataFrame(enz_json, index=[0]) - enz_df.set_index("name", inplace=True) - enz_df = enz_df.T - enz.subheader("Associated enzyme") - enz.table(enz_df) - - # Alternative ligands - alt_lig.subheader("Possible alternative ligands") - alt_lig_list = data['alt_ligands'] - lig_html = "

    " - for lig in alt_lig_list: - lig_html += "
  • " + lig + "
  • " - lig_html += "
" - alt_lig.markdown(lig_html, unsafe_allow_html=True) - - # Enzyme references - ref_and_rank = data_column.container() - reference_col, rank_col = ref_and_rank.columns(2) - reference_col.subheader("Enzyme references") - for i in references: - reference_col.markdown(f'{"https://doi.org/"+i}', unsafe_allow_html=True) - - # Rank metrics - rank_col.subheader("Rank description") - rank_metrics = data["rank"]["metrics"] - - rank_df = pd.DataFrame(rank_metrics).T - rank_col.dataframe(rank_df) - - # Operon - operon_seq = data_column.container() - operon_seq.subheader("Operon") - operon_data = data['protein']['context'] - operon_json = operon_data['operon'] - - with operon_seq.expander(label=f"Click here to see the genetic context for {refseq}"): - # Get the regulator position within the operon - reg_index = 0 - for i in operon_json: - if i["accession"] == refseq: - break - else: - reg_index += 1 - - # Get the enzyme position within the operon - enz_index = data['protein']['context']['enzyme_index'] - - # Create the operon table - genes = [] - for i in operon_json: - gene = { - "RefSeq ID": i['accession'], - "Description": i.get("description", " "), - "Direction": i['direction'], - "Start position": i['start'], - "End position": i['stop'] - } - genes.append(gene) - operon_df = pd.DataFrame(genes) - - # Color the operon table - colors = ["#fcb1b1", "#e6cffc", "#9afcac", "#fcc99d", "#a3fff6", "#fdff9c", "#ccd4fc", "#fcbdf6"] - def bg_color_col(col): - color = [colors[i % len(colors)] for i, x in col.items()] - return ['background-color: %s' % color[i] if col.name == 'RefSeq ID' or (col.name == "Description" and i == reg_index) or (col.name == "Description" and i == enz_index) else '' for i, x in col.items()] - - operon_df = operon_df.style.apply(bg_color_col) - st.table(operon_df) - - # Display the predicted promoter - st.markdown("
Predicted promoter
", unsafe_allow_html=True) - st.write(operon_data["promoter"]['regulated_seq']) - - # Create and display the color-annotated genome fragment - operon_seq_html = "" - c = 0 - for seq in operon_data["operon_seq"]: - sequence = operon_data["operon_seq"][seq] - if re.compile(r"spacer").search(seq): - html = "" + str(sequence) + "" - elif re.compile(r"overlap").search(seq): - html = "" + str(sequence) + "" - elif re.compile(r"fwd").search(seq): - html = f"" + str(sequence) + "" - c += 1 - else: - html = f"" + str(sequence) + "" - c += 1 - operon_seq_html += html - st.markdown("
Full operon sequence
", unsafe_allow_html=True) - st.markdown(operon_seq_html, unsafe_allow_html=True) +def format_results(data_column, ligand_name): + + if st.session_state.data: + + # Header + refseq = st.session_state.data['refseq'] + data_column.write("") + data_column.write("") + data_column.markdown(f'

{refseq}

', unsafe_allow_html=True) + + data_column.divider() + + + # Spacer + data_column.text("") + data_column.text("") + + + # Regulator info + reg_ncbi_anno = st.session_state.data["annotation"] + reg_protein_seq = st.session_state.data["reg_protein_seq"] + reg_uniprot_id = st.session_state.data["uniprot_reg_data"]["id"] + organism = st.session_state.data["protein"]["organism"] + organism_name = str(organism[-2])+", "+str(organism[-1]) + + reg_json = { + "name": "Regulator attribute", + "NCBI annotation": reg_ncbi_anno, + "Uniprot ID": reg_uniprot_id, + "Protein length": len(reg_protein_seq), + "Organism": organism_name, + } + + #Regulator container + regulator_con = data_column.container() + + #GenBank container + genbank_con = data_column.container() + + reg_info, reg_spacer, reg_genbank = regulator_con.columns((8,1,5)) + regulator_df = pd.DataFrame(reg_json, index=["name"]).astype(str) + regulator_df.set_index("name", inplace=True) + regulator_df = regulator_df.T + reg_info.subheader("Regulator information") + reg_info.table(regulator_df) + + reg_genbank.header("") + reg_genbank.header("") + reg_genbank.form_submit_button(label="Show Plasmid", type="primary", on_click=download_df, args=(ligand_name,genbank_con)) + reg_genbank.markdown(f'

This plasmid is designed to induce GFP expression in the presence of the target molecule via '+str(refseq)+', within E. coli', unsafe_allow_html=True) + + + + + # Enzyme info + enz_annotation = st.session_state.data['protein']['enzyme']['description'] + enz_refseq = st.session_state.data['protein']['enzyme']['ncbi_id'] + equation = st.session_state.data['equation'] + rhea_id = st.session_state.data['rhea_id'] + references = st.session_state.data['protein']['enzyme']['dois'] + + enz_json = {"name": "Enzyme attribute", + "Annotation": enz_annotation, + "Reaction": equation, + "RHEA ID": rhea_id, + "RefSeq ID": enz_refseq, + } + + enzyme_and_lig = data_column.container() + enz, alt_lig = enzyme_and_lig.columns([1,1]) + enz_df = pd.DataFrame(enz_json, index=[0]) + enz_df.set_index("name", inplace=True) + enz_df = enz_df.T + enz.subheader("Associated enzyme") + enz.table(enz_df) + + + # Alternative ligands + alt_lig.subheader("Possible alternative ligands") + alt_lig_list = st.session_state.data['alt_ligands'] + lig_html = "

    " + for lig in alt_lig_list: + lig_html += "
  • " + lig + "
  • " + lig_html += "
" + alt_lig.markdown(lig_html, unsafe_allow_html=True) + #alt_lig.markdown("- " + lig + "\n") + #alt_lig.table(st.session_state.data['alt_ligands']) + #TODO: Clean this table up + + + # Enzyme references + ref_and_rank = data_column.container() + reference_col, rank_col = ref_and_rank.columns(2) + reference_col.subheader("Enzyme references") + for i in references: + reference_col.markdown(f'{"https://doi.org/"+i}', unsafe_allow_html=True) + + + + # Rank metrics + rank_col.subheader("Rank description") + rank_metrics = st.session_state.data["rank"]["metrics"] + + rank_df = pd.DataFrame(rank_metrics).T + #orient='index') + # rank_df = pd.DataFrame.from_dict({(i,j): rank_metrics[i][j] + # for i in rank_metrics.keys() + # for j in rank_metrics[i].keys()}, + # orient='index') + rank_col.dataframe(rank_df) + + + + + + # Operon + operon_seq = data_column.container() + operon_seq.subheader("Operon") + operon_data = st.session_state.data['protein']['context'] + operon_json = operon_data['operon'] + + with operon_seq.expander(label="Click here to see the genetic context for "+str(refseq)): + + + # Get the regulator position within the operon + reg_index = 0 + for i in operon_json: + if i["accession"] == refseq: + break + else: + reg_index += 1 + + # Get the enzyme position within the operon + enz_index = st.session_state.data['protein']['context']['enzyme_index'] + + + # Create the operon table + genes = [] + for i in operon_json: + if "description" in i.keys(): + gene = { + "RefSeq ID": i['accession'], + "Description": i['description'], + "Direction": i['direction'], + "Start position": i['start'], + "End position": i['stop'] + } + else: + gene = { + "RefSeq ID": i['accession'], + "Description": " ", + "Direction": i['direction'], + "Start position": i['start'], + "End position": i['stop'] + } + genes.append(gene) + operon_df = pd.DataFrame(genes) + + # Color the operon table + colors = ["#fcb1b1", "#e6cffc", "#9afcac", "#fcc99d", "#a3fff6", "#fdff9c", "#ccd4fc", "#fcbdf6"] + def bg_color_col(col): + color = [colors[i % len(colors)] for i,x in col.items()] + return ['background-color: %s' % color[i] + if col.name=='RefSeq ID' or (col.name=="Description" and i==reg_index) or (col.name=="Description" and i==enz_index) + else '' + for i,x in col.items()] + + operon_df = operon_df.style.apply(bg_color_col) + st.table(operon_df) + + + # Display the predicted promoter + st.markdown("
Predicted promoter
", unsafe_allow_html=True) + st.write(operon_data["promoter"]['regulated_seq']) + + + # Create and display the color-annotated genome fragment + operon_seq = "" + + c = 0 + for seq in operon_data["operon_seq"]: + sequence = operon_data["operon_seq"][seq] + if re.compile(r"spacer").search(seq): + html = ""+str(sequence)+"" + elif re.compile(r"overlap").search(seq): + html = ""+str(sequence)+"" + elif re.compile(r"fwd").search(seq): + html = f""+str(sequence)+"" + c += 1 + else: + html = f""+str(sequence)+"" + c += 1 + operon_seq += html + st.markdown("
Full operon sequence
", unsafe_allow_html=True) + st.markdown(operon_seq, unsafe_allow_html=True) diff --git a/streamlit/ligify/streamlit_app.py b/streamlit/ligify/streamlit_app.py index 3172083..d8c5fd4 100644 --- a/streamlit/ligify/streamlit_app.py +++ b/streamlit/ligify/streamlit_app.py @@ -225,10 +225,8 @@ def run_ligify(chem, results, progress, chemical, filters, regulators, metrics): m_spacer1, metrics_col, m_spacer2 = results.container().columns((1,3,1)) regulator_column, data_column = results.columns([1,3]) - - print('inside') - if chemical["smiles"] == None: + if chemical["smiles"] == None or (regulators is None and metrics is None): data_column.subheader("Chemical input was not recognized. Please try a different input method.") else: @@ -239,10 +237,9 @@ def run_ligify(chem, results, progress, chemical, filters, regulators, metrics): if not regulators and not metrics: regulators, metrics = fetch_data(chemical["InChiKey"], filters) - select_spacerL, please_select, select_spacerR = data_column.container().columns([1,2,1]) + select_spacerL, please_select, select_spacerR = data_column.container().columns([1,2,1]) - for entry in regulators: - format_results(data_column, chemical["name"], entry) + format_results(data_column, chemical["name"]) regulator_column.header('') regulator_column.subheader('Sensor candidates') From a4265fde01f21baf668955a99578d742976bd92d Mon Sep 17 00:00:00 2001 From: Josh Love Date: Fri, 31 May 2024 13:24:07 -0500 Subject: [PATCH 14/15] Return no associated reactions feature --- streamlit/ligify/streamlit_app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/streamlit/ligify/streamlit_app.py b/streamlit/ligify/streamlit_app.py index d8c5fd4..6c2e501 100644 --- a/streamlit/ligify/streamlit_app.py +++ b/streamlit/ligify/streamlit_app.py @@ -226,7 +226,7 @@ def run_ligify(chem, results, progress, chemical, filters, regulators, metrics): m_spacer1, metrics_col, m_spacer2 = results.container().columns((1,3,1)) regulator_column, data_column = results.columns([1,3]) - if chemical["smiles"] == None or (regulators is None and metrics is None): + if chemical["smiles"] == None: data_column.subheader("Chemical input was not recognized. Please try a different input method.") else: @@ -234,8 +234,8 @@ def run_ligify(chem, results, progress, chemical, filters, regulators, metrics): # SMILES = str(chemical_smiles) # chem.image(f'http://hulab.rxnfinder.org/smi2img/{SMILES}/', width=200) - if not regulators and not metrics: - regulators, metrics = fetch_data(chemical["InChiKey"], filters) + # if not regulators and not metrics: + # regulators, metrics = fetch_data(chemical["InChiKey"], filters) select_spacerL, please_select, select_spacerR = data_column.container().columns([1,2,1]) From 5915c0c994693025a71f87f78609797c55e35641 Mon Sep 17 00:00:00 2001 From: Josh Love Date: Thu, 25 Jul 2024 10:21:35 -0500 Subject: [PATCH 15/15] Remove progress bar from code --- streamlit/ligify/fetch_data.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/streamlit/ligify/fetch_data.py b/streamlit/ligify/fetch_data.py index 9c3e21c..d3409e2 100644 --- a/streamlit/ligify/fetch_data.py +++ b/streamlit/ligify/fetch_data.py @@ -18,7 +18,7 @@ def fetch_data(InChiKey, filters): # FETCH REACTIONS - prog_bar = prog.progress(0, text="1. Fetching reaction IDs") + # prog_bar = prog.progress(0, text="1. Fetching reaction IDs") reactions = fetch_reactions(InChiKey = InChiKey, max_reactions = filters["max_reactions"]) total_rxns = len(reactions["rxn_data"]) metrics["RHEA Reactions"] = total_rxns @@ -27,12 +27,12 @@ def fetch_data(InChiKey, filters): # FETCH GENES - prog_bar_increment = 20/int(total_rxns) + # prog_bar_increment = 20/int(total_rxns) counter = 0 for i in reactions["rxn_data"]: - prog_value = int(10+counter*prog_bar_increment) - prog_bar.progress(prog_value, text=f"2. Fetching genes for reaction {str(counter+1)} of {str(total_rxns)} (rhea:{i['rhea_id']})") + # prog_value = int(10+counter*prog_bar_increment) + # prog_bar.progress(prog_value, text=f"2. Fetching genes for reaction {str(counter+1)} of {str(total_rxns)} (rhea:{i['rhea_id']})") associated_proteins = fetch_genes(i["rhea_id"], filters["reviewed"], filters["proteins_per_reaction"]) i["proteins"] = associated_proteins counter += 1 @@ -48,7 +48,7 @@ def fetch_data(InChiKey, filters): if len(reactions["rxn_data"]) == 0: print("No enzymes found") - prog_bar.empty() + # prog_bar.empty() return None, None else: operon_counter = 0 @@ -62,7 +62,7 @@ def fetch_data(InChiKey, filters): if protein["enzyme"]["ncbi_id"] != None: total_genes +=1 - prog_bar_increment = 50/int(total_genes) + # prog_bar_increment = 50/int(total_genes) for rxn in range(0,len(reactions["rxn_data"])): for i in range(0, len(reactions["rxn_data"][rxn]["proteins"])): @@ -72,9 +72,9 @@ def fetch_data(InChiKey, filters): # Limit number of operons evaluated to avoid program taking too long to complete. if operon_counter <= filters["max_operons"]: - prog_value = int(30+operon_counter*prog_bar_increment) + # prog_value = int(30+operon_counter*prog_bar_increment) - prog_bar.progress(prog_value, text=f"3. Fetching operon for gene {str(operon_counter+1)} of {str(total_genes)} ({refseq_id})") + # prog_bar.progress(prog_value, text=f"3. Fetching operon for gene {str(operon_counter+1)} of {str(total_genes)} ({refseq_id})") protein["context"] = acc2operon(refseq_id) operon_counter += 1 @@ -84,7 +84,7 @@ def fetch_data(InChiKey, filters): # FETCH REGULATORS if reactions == None: - prog_bar.empty() + # prog_bar.empty() return None, None else: @@ -92,7 +92,7 @@ def fetch_data(InChiKey, filters): for rxn in reactions["rxn_data"]: for protein in rxn["proteins"]: total_regs += 1 - prog_bar_increment = 20/int(total_regs) + # prog_bar_increment = 20/int(total_regs) # This is where all of the display data is created @@ -102,8 +102,8 @@ def fetch_data(InChiKey, filters): for rxn in reactions["rxn_data"]: for protein in rxn["proteins"]: - prog_value = int(80+counter*prog_bar_increment) - prog_bar.progress(prog_value, text=f"4. Fetching data for regulator {str(counter+1)} of {str(total_regs)} ({protein['organism'][-2]}, {protein['organism'][-1]})") + # prog_value = int(80+counter*prog_bar_increment) + # prog_bar.progress(prog_value, text=f"4. Fetching data for regulator {str(counter+1)} of {str(total_regs)} ({protein['organism'][-2]}, {protein['organism'][-1]})") regs = pull_regulators(protein, rxn) for r in regs: @@ -111,7 +111,7 @@ def fetch_data(InChiKey, filters): counter += 1 metrics["Total regulators"] = len(regulators) - prog_bar.empty() + # prog_bar.empty() @@ -139,6 +139,6 @@ def fetch_data(InChiKey, filters): else: - prog_bar.empty() + # prog_bar.empty() return None, None