Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/bar-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ jobs:
- name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v2
with:
fail_ci_if_error: true
fail_ci_if_error: false
10 changes: 5 additions & 5 deletions api/resources/api_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,22 @@ def send_email_notification():
for line in f:
recipient = line
port = 465
key = os.environ.get('EMAIL_PASS_KEY')
key = os.environ.get("EMAIL_PASS_KEY")
cipher_suite = Fernet(key)
with open(os.environ.get('EMAIL_PASS_FILE'), "rb") as f:
with open(os.environ.get("EMAIL_PASS_FILE"), "rb") as f:
for line in f:
encrypted_key = line
uncipher_text = cipher_suite.decrypt(encrypted_key)
password = bytes(uncipher_text).decode("utf-8")
context = create_default_context()
smtp_server = 'smtp.gmail.com'
sender_email = 'bar.summarization@gmail.com'
smtp_server = "smtp.gmail.com"
sender_email = "bar.summarization@gmail.com"
subject = "[Bio-Analytic Resource] New API key request"
text = """\
There is a new API key request.
You can approve or reject it at http://bar.utoronto.ca/~bpereira/webservices/bar-request-manager/build/index.html
"""
m_text = MIMEText(text, _subtype='plain', _charset='UTF-8')
m_text = MIMEText(text, _subtype="plain", _charset="UTF-8")
msg = MIMEMultipart()
msg["From"] = sender_email
msg["To"] = recipient
Expand Down
110 changes: 73 additions & 37 deletions api/resources/efp_image.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import base64
import re
import requests
import random
import redis.connection
from flask_restx import Namespace, Resource
from markupsafe import escape
from flask import send_from_directory
Expand All @@ -14,7 +17,9 @@
class eFPImageList(Resource):
def get(self):
"""This end point returns the list of species available"""
species = ["efp_arabidopsis"] # This are the only species available so far
# This are the only species available so far
# If this is updated, update get request and test as well
species = ["efp_arabidopsis"]
return BARUtils.success_exit(species)


Expand Down Expand Up @@ -46,7 +51,7 @@ def get(self, efp="", view="", mode="", gene_1="", gene_2=""):
"""This end point returns eFP images."""
# list of allowed eFPs
# See endpoint able
efp_list = ["efp_arabidopsis"]
species = ["efp_arabidopsis"]

# Escape input data
efp = escape(efp)
Expand All @@ -56,7 +61,7 @@ def get(self, efp="", view="", mode="", gene_1="", gene_2=""):
gene_2 = escape(gene_2)

# Validate values
if efp not in efp_list:
if efp not in species:
return BARUtils.error_exit("Invalid eFP."), 400

# Validate view
Expand All @@ -76,41 +81,72 @@ def get(self, efp="", view="", mode="", gene_1="", gene_2=""):
return BARUtils.error_exit("Gene 2 is invalid."), 400

# Check if request is cached
try:
r = BARUtils.connect_redis()
key = "BAR_API_efp_image_" + "_".join([efp, view, mode, gene_1, gene_2])
efp_image_base64 = r.get(key)
except redis.connection.ConnectionError:
# Failed redis connection
r = None
key = None
efp_image_base64 = None

if efp_image_base64 is None:
# Request is not cached
# Run eFP. Note, this is currently running from home directory!
efp_url = (
"https://bar.utoronto.ca/~asher/python3/"
+ efp
+ "/cgi-bin/efpWeb.cgi?dataSource="
+ view
+ "&mode="
+ mode
+ "&primaryGene="
+ gene_1
+ "&secondaryGene="
+ gene_2
+ "&grey_low=None&grey_stddev=None"
)
efp_html = requests.get(efp_url)

# Now search for something like <img src=\"../output/efp-2nBNhe.png\"
# This is the eFP output image
match = re.search(r'"\.\./output/(efp-.{1,10}\.png)', efp_html.text)

# File is not found
if match is None:
return (
BARUtils.error_exit(
"Failed to retrieve image. Data for the given gene may not exist."
),
500,
)

# Save this path for later use
path = match[1]
efp_file_link = (
"https://bar.utoronto.ca/~asher/python3/" + efp + "/output/" + path
)

# Download and serve that image
img_data = requests.get(efp_file_link).content
with open("output/" + path, "wb") as file:
file.write(img_data)

# Cache the request if redis is alive
if r:
efp_image_base64 = base64.b64encode(img_data)
r.set(key, efp_image_base64)
r.close()

# If request is not cached, run the search
# Run eFP. Note, this is currently running from home directory
efp_url = (
"https://bar.utoronto.ca/~asher/python3/"
+ efp
+ "/cgi-bin/efpWeb.cgi?dataSource="
+ view
+ "&mode="
+ mode
+ "&primaryGene="
+ gene_1
+ "&secondaryGene="
+ gene_2
+ "&grey_low=None&grey_stddev=None"
)
efp_html = requests.get(efp_url)

# Now search for something like <img src=\"../output/efp-2nBNhe.png\"
# This is the eFP output image
match = re.search(r'"\.\./output/(efp-.{1,10}\.png)', efp_html.text)

# File is not found
if match is None:
return BARUtils.error_exit("Failed to retrieve image. Data for the given gene may not exist."), 500

efp_file_link = (
"https://bar.utoronto.ca/~asher/python3/" + efp + "/output/" + match[1]
)

# Download and serve that image
img_data = requests.get(efp_file_link).content
with open("output/" + match[1], "wb") as file:
file.write(img_data)
else:
# Request is cached
img_data = base64.b64decode(efp_image_base64)
path = key + str(random.randrange(0, 1000000)) + ".png"
with open("output/" + path, "wb") as file:
file.write(img_data)
r.close()

return send_from_directory(
directory="../output/", path=match[1], mimetype="image/png"
directory="../output/", path=path, mimetype="image/png"
)
15 changes: 14 additions & 1 deletion api/utils/bar_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import re
import redis
import os


class BARUtils:
Expand Down Expand Up @@ -95,7 +97,18 @@ def is_efp_view_name(efp_view):
:param efp_view: string view name
:return: True if valid
"""
if efp_view and re.search(r"[a-z1-9_]{1,20}", efp_view, re.I):
if efp_view and re.search(r"^[a-z1-9_]{1,20}$", efp_view, re.I):
return True
else:
return False

@staticmethod
def connect_redis():
"""This function connects to redis
:returns: redis connection
"""
r = redis.Redis(
host="localhost", port=6379, password=os.environ.get("BAR_REDIS_PASSWORD")
)

return r
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Babel==2.9.1
beautifulsoup4==4.10.0
certifi==2021.10.8
charset-normalizer==2.0.9
docutils==0.17.1
docutils==0.18.1
furo==2021.11.23
idna==3.3
imagesize==1.3.0
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ regex==2021.11.10
requests==2.26.0
scour==0.38.2
six==1.16.0
SQLAlchemy==1.4.27
SQLAlchemy==1.4.28
toml==0.10.2
typed-ast==1.5.1
typing_extensions==4.0.1
Expand Down
78 changes: 78 additions & 0 deletions tests/resources/test_efp_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,81 @@ def test_get_efp_image_list(self):
response = self.app_client.get("/efp_image/")
expected = {"wasSuccessful": True, "data": ["efp_arabidopsis"]}
self.assertEqual(response.json, expected)

def test_get_efp_image(self):
"""This function test eFP image endpoint get request
:return:
"""
# Test absolute modes in the beginning
# A very basic test for Arabidopsis requests
# https://bar.utoronto.ca/api/efp_image/efp_arabidopsis/Developmental_Map/Absolute/At1g01010
response = self.app_client.get(
"/efp_image/efp_arabidopsis/Developmental_Map/Absolute/At1g01010"
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content_type, "image/png")

# Now rerun for Cached requests. A image should be return from the cache
response = self.app_client.get(
"/efp_image/efp_arabidopsis/Developmental_Map/Absolute/At1g01010"
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content_type, "image/png")

# Test for invalid species:
response = self.app_client.get(
"/efp_image/abc/Developmental_Map/Absolute/At1g01010"
)
expected = {"wasSuccessful": False, "error": "Invalid eFP."}
self.assertEqual(response.json, expected)

# Test for eFP view name
response = self.app_client.get(
"/efp_image/efp_arabidopsis/ab.!c/Absolute/At1g01010"
)
expected = {"wasSuccessful": False, "error": "Invalid eFP View name."}
self.assertEqual(response.json, expected)

# Test for eFP mode
response = self.app_client.get("/efp_image/efp_arabidopsis/Root/abc/At1g01010")
expected = {"wasSuccessful": False, "error": "Invalid eFP mode."}
self.assertEqual(response.json, expected)

# Test for gene 1 using Arabidopsis
response = self.app_client.get(
"/efp_image/efp_arabidopsis/Root/Absolute/At1g0101X"
)
expected = {"wasSuccessful": False, "error": "Gene 1 is invalid."}
self.assertEqual(response.json, expected)

response = self.app_client.get(
"/efp_image/efp_arabidopsis/Developmental_Map/Absolute/At1g01011"
)
expected = {
"wasSuccessful": False,
"error": "Failed to retrieve image. Data for the given gene may not exist.",
}
self.assertEqual(response.json, expected)

# Test for gene 2 using Arabidopsis
response = self.app_client.get(
"/efp_image/efp_arabidopsis/Root/Compare/At1g01010/Abc"
)
expected = {"wasSuccessful": False, "error": "Gene 2 is invalid."}
self.assertEqual(response.json, expected)

# Test compare modes in the end
# A very basic test for Arabidopsis requests
# https://bar.utoronto.ca/api/efp_image/efp_arabidopsis/Developmental_Map/Compare/At1g01010/At1g01030
response = self.app_client.get(
"/efp_image/efp_arabidopsis/Developmental_Map/Compare/At1g01010/At1g01030"
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content_type, "image/png")

# Rerun for cached request. An image should be returned
response = self.app_client.get(
"/efp_image/efp_arabidopsis/Developmental_Map/Compare/At1g01010/At1g01030"
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content_type, "image/png")
5 changes: 5 additions & 0 deletions tests/utils/test_bar_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ def test_is_arabidopsis_gene_valid(self):
result = BARUtils.is_arabidopsis_gene_valid("At1g01010.11")
self.assertFalse(result)

def test_is_tomato_gene_valid(self):
# For some reason, coverage is saying that we need this test
result = BARUtils.is_tomato_gene_valid("Solyc04g014530")
self.assertTrue(result)

def test_is_integer(self):
# Valid result
result = BARUtils.is_integer("5")
Expand Down