...
Some checks failed
CI / test (pull_request) Failing after 17s
API CI / api-tests (pull_request) Failing after 31s

This commit is contained in:
Tim Lorsbach
2026-06-23 21:20:02 +02:00
parent 687a90af0e
commit 0842f67560
4 changed files with 68 additions and 27 deletions

View File

@ -89,14 +89,13 @@ class BB4G(Classifier):
} }
started = False started = False
retries = 0
while not started and retries < 5: while not started:
res = requests.post(f"{self.url}/start", headers=header, data={}, proxies=s.PROXIES or None) res = requests.post(f"{self.url}/start", headers=header, data={}, proxies=s.PROXIES or None)
logger.info(f"Starting BB4G: {res.status_code}") logger.info(f"Starting BB4G: {res.status_code}")
if res.status_code == 200: if res.status_code == 200:
started = True started = True
elif res.status_code in [500, 502]: elif res.status_code in [500, 502]:
retries += 1
import time import time
time.sleep(5) time.sleep(5)
else: else:
@ -171,7 +170,7 @@ class BB4G(Classifier):
} }
retries = 0 retries = 0
while retries < 5: while retries < 100:
resp = requests.post(f"{self.url}/compute", headers=header, data=json.dumps(data), resp = requests.post(f"{self.url}/compute", headers=header, data=json.dumps(data),
proxies=s.PROXIES or None) proxies=s.PROXIES or None)
@ -179,7 +178,7 @@ class BB4G(Classifier):
retries += 1 retries += 1
logger.info(f"BB4G predict hit a 418, retrying in 60 seconds") logger.info(f"BB4G predict hit a 418, retrying in 60 seconds")
import time import time
time.sleep(60) time.sleep(3)
continue continue
resp.raise_for_status() resp.raise_for_status()

2
epdb/exceptions.py Normal file
View File

@ -0,0 +1,2 @@
class InvalidSMILESException(Exception):
pass

View File

@ -31,6 +31,7 @@ from sklearn.model_selection import ShuffleSplit
from bridge.contracts import Property from bridge.contracts import Property
from bridge.dto import RunResult, PropertyPrediction from bridge.dto import RunResult, PropertyPrediction
from epdb.exceptions import InvalidSMILESException
from utilities.chem import FormatConverter, IndigoUtils, PredictionResult, ProductSet from utilities.chem import FormatConverter, IndigoUtils, PredictionResult, ProductSet
from utilities.ml import ( from utilities.ml import (
ApplicabilityDomainPCA, ApplicabilityDomainPCA,
@ -785,7 +786,7 @@ class Compound(
"CompoundStructure", "CompoundStructure",
verbose_name="Default Structure", verbose_name="Default Structure",
related_name="compound_default_structure", related_name="compound_default_structure",
on_delete=models.CASCADE, on_delete=models.SET_NULL,
null=True, null=True,
) )
@ -862,13 +863,17 @@ class Compound(
package: "Package", smiles: str, name: str = None, description: str = None, *args, **kwargs package: "Package", smiles: str, name: str = None, description: str = None, *args, **kwargs
) -> "Compound": ) -> "Compound":
if smiles is None or smiles.strip() == "": if smiles is None or smiles.strip() == "":
raise ValueError("SMILES is required") raise InvalidSMILESException("SMILES is required")
smiles = smiles.strip() smiles = smiles.strip()
parsed = FormatConverter.from_smiles(smiles) parsed = FormatConverter.from_smiles(smiles)
if parsed is None: if parsed is None:
raise ValueError("Given SMILES is invalid") raise InvalidSMILESException("Given SMILES is invalid")
if name is not None:
# Clean for potential XSS
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
standardized_smiles = FormatConverter.standardize(smiles, remove_stereo=True) standardized_smiles = FormatConverter.standardize(smiles, remove_stereo=True)
@ -880,7 +885,12 @@ class Compound(
# Check if we find a direct match for a given SMILES # Check if we find a direct match for a given SMILES
if qs.exists(): if qs.exists():
return qs.first().compound found_compound = qs.first().compound
if name:
found_compound.add_alias(name)
return found_compound
qs = CompoundStructure.objects.filter(smiles=standardized_smiles, compound__package=package) qs = CompoundStructure.objects.filter(smiles=standardized_smiles, compound__package=package)
@ -890,16 +900,17 @@ class Compound(
# Check if we can find the standardized one # Check if we can find the standardized one
if qs.exists(): if qs.exists():
# TODO should we add a structure? # TODO should we add a structure?
return qs.first().compound found_compound = qs.first().compound
if name:
found_compound.add_alias(name)
return found_compound
# Generate Compound # Generate Compound
c = Compound() c = Compound()
c.package = package c.package = package
if name is not None:
# Clean for potential XSS
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
if name is None or name == "": if name is None or name == "":
name = f"Compound {Compound.objects.filter(package=package).count() + 1}" name = f"Compound {Compound.objects.filter(package=package).count() + 1}"
@ -1152,16 +1163,25 @@ class CompoundStructure(
def create( def create(
compound: Compound, smiles: str, name: str = None, description: str = None, *args, **kwargs compound: Compound, smiles: str, name: str = None, description: str = None, *args, **kwargs
): ):
# Clean for potential XSS
if name is not None:
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
if CompoundStructure.objects.filter(compound=compound, smiles=smiles).exists(): if CompoundStructure.objects.filter(compound=compound, smiles=smiles).exists():
return CompoundStructure.objects.get(compound=compound, smiles=smiles) found_cs = CompoundStructure.objects.get(compound=compound, smiles=smiles)
if name:
found_cs.add_alias(name)
return found_cs
if compound.pk is None: if compound.pk is None:
raise ValueError("Unpersisted Compound! Persist compound first!") raise ValueError("Unpersisted Compound! Persist compound first!")
cs = CompoundStructure() cs = CompoundStructure()
# Clean for potential XSS
if name is not None: if name is not None:
cs.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() cs.name = name
if description is not None: if description is not None:
cs.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() cs.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
@ -1383,6 +1403,9 @@ class SimpleAmbitRule(SimpleRule):
if not FormatConverter.is_valid_smirks(smirks): if not FormatConverter.is_valid_smirks(smirks):
raise ValueError(f'SMIRKS "{smirks}" is invalid!') raise ValueError(f'SMIRKS "{smirks}" is invalid!')
if name is not None:
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
query = SimpleAmbitRule.objects.filter(package=package, smirks=smirks) query = SimpleAmbitRule.objects.filter(package=package, smirks=smirks)
if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "": if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "":
@ -1394,14 +1417,17 @@ class SimpleAmbitRule(SimpleRule):
if query.exists(): if query.exists():
if query.count() > 1: if query.count() > 1:
logger.error(f"More than one rule matched this one! {query}") logger.error(f"More than one rule matched this one! {query}")
return query.first()
found_rule = query.first()
if name:
found_rule.add_alias(name)
return found_rule
r = SimpleAmbitRule() r = SimpleAmbitRule()
r.package = package r.package = package
if name is not None:
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
if name is None or name == "": if name is None or name == "":
name = f"Rule {Rule.objects.filter(package=package).count() + 1}" name = f"Rule {Rule.objects.filter(package=package).count() + 1}"
@ -1642,8 +1668,13 @@ class Reaction(
educts: Union[List[str], List[CompoundStructure]] = None, educts: Union[List[str], List[CompoundStructure]] = None,
products: Union[List[str], List[CompoundStructure]] = None, products: Union[List[str], List[CompoundStructure]] = None,
rules: Union[Rule | List[Rule]] = None, rules: Union[Rule | List[Rule]] = None,
multi_step: bool = True, multi_step: bool = False,
): ):
# Clean for potential XSS
if name is not None and name.strip() != "":
name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
_educts = [] _educts = []
_products = [] _products = []
@ -1698,14 +1729,19 @@ class Reaction(
logger.error( logger.error(
f"Found more than one reaction for given input! {existing_reaction_qs}" f"Found more than one reaction for given input! {existing_reaction_qs}"
) )
return existing_reaction_qs.first()
found_reaction = existing_reaction_qs.first()
if name:
found_reaction.add_alias(name)
return found_reaction
r = Reaction() r = Reaction()
r.package = package r.package = package
# Clean for potential XSS if r is not None:
if name is not None and name.strip() != "": r.name = name
r.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
if description is not None and description.strip() != "": if description is not None and description.strip() != "":
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()

View File

@ -19,6 +19,7 @@ from sentry_sdk import capture_exception
from utilities.chem import FormatConverter, IndigoUtils from utilities.chem import FormatConverter, IndigoUtils
from utilities.decorators import package_permission_required from utilities.decorators import package_permission_required
from .exceptions import InvalidSMILESException
from .logic import ( from .logic import (
EPDBURLParser, EPDBURLParser,
@ -2364,7 +2365,10 @@ def package_pathway_nodes(request, package_uuid, pathway_uuid):
node_description = request.POST.get("node-description") node_description = request.POST.get("node-description")
node_smiles = request.POST.get("node-smiles").strip() node_smiles = request.POST.get("node-smiles").strip()
current_pathway.add_node(node_smiles, name=node_name, description=node_description) try:
current_pathway.add_node(node_smiles, name=node_name, description=node_description)
except InvalidSMILESException as e:
return error(request, "Node creation failed!", f"Given SMILES ({node_smiles}) is invalid")
return redirect(current_pathway.url) return redirect(current_pathway.url)