diff --git a/bb4g/__init__.py b/bb4g/__init__.py index 510bca89..4c93cd8f 100644 --- a/bb4g/__init__.py +++ b/bb4g/__init__.py @@ -89,14 +89,13 @@ class BB4G(Classifier): } 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) logger.info(f"Starting BB4G: {res.status_code}") if res.status_code == 200: started = True elif res.status_code in [500, 502]: - retries += 1 import time time.sleep(5) else: @@ -171,7 +170,7 @@ class BB4G(Classifier): } retries = 0 - while retries < 5: + while retries < 100: resp = requests.post(f"{self.url}/compute", headers=header, data=json.dumps(data), proxies=s.PROXIES or None) @@ -179,7 +178,7 @@ class BB4G(Classifier): retries += 1 logger.info(f"BB4G predict hit a 418, retrying in 60 seconds") import time - time.sleep(60) + time.sleep(3) continue resp.raise_for_status() diff --git a/epdb/exceptions.py b/epdb/exceptions.py new file mode 100644 index 00000000..d9334662 --- /dev/null +++ b/epdb/exceptions.py @@ -0,0 +1,2 @@ +class InvalidSMILESException(Exception): + pass \ No newline at end of file diff --git a/epdb/models.py b/epdb/models.py index 305be1df..81bc456e 100644 --- a/epdb/models.py +++ b/epdb/models.py @@ -31,6 +31,7 @@ from sklearn.model_selection import ShuffleSplit from bridge.contracts import Property from bridge.dto import RunResult, PropertyPrediction +from epdb.exceptions import InvalidSMILESException from utilities.chem import FormatConverter, IndigoUtils, PredictionResult, ProductSet from utilities.ml import ( ApplicabilityDomainPCA, @@ -785,7 +786,7 @@ class Compound( "CompoundStructure", verbose_name="Default Structure", related_name="compound_default_structure", - on_delete=models.CASCADE, + on_delete=models.SET_NULL, null=True, ) @@ -862,13 +863,17 @@ class Compound( package: "Package", smiles: str, name: str = None, description: str = None, *args, **kwargs ) -> "Compound": if smiles is None or smiles.strip() == "": - raise ValueError("SMILES is required") + raise InvalidSMILESException("SMILES is required") smiles = smiles.strip() parsed = FormatConverter.from_smiles(smiles) 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) @@ -880,7 +885,12 @@ class Compound( # Check if we find a direct match for a given SMILES 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) @@ -890,16 +900,17 @@ class Compound( # Check if we can find the standardized one if qs.exists(): # 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 c = Compound() 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 == "": name = f"Compound {Compound.objects.filter(package=package).count() + 1}" @@ -1152,16 +1163,25 @@ class CompoundStructure( def create( 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(): - 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: raise ValueError("Unpersisted Compound! Persist compound first!") cs = CompoundStructure() - # Clean for potential XSS + if name is not None: - cs.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() + cs.name = name if description is not None: 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): 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) if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "": @@ -1394,14 +1417,17 @@ class SimpleAmbitRule(SimpleRule): if query.exists(): if query.count() > 1: 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.package = package - if name is not None: - name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() - if name is None or name == "": name = f"Rule {Rule.objects.filter(package=package).count() + 1}" @@ -1642,8 +1668,13 @@ class Reaction( educts: Union[List[str], List[CompoundStructure]] = None, products: Union[List[str], List[CompoundStructure]] = 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 = [] _products = [] @@ -1698,14 +1729,19 @@ class Reaction( logger.error( 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.package = package - # Clean for potential XSS - if name is not None and name.strip() != "": - r.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() + if r is not None: + r.name = name if description is not None and description.strip() != "": r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() diff --git a/epdb/views.py b/epdb/views.py index 90245991..67e6f3e4 100644 --- a/epdb/views.py +++ b/epdb/views.py @@ -19,6 +19,7 @@ from sentry_sdk import capture_exception from utilities.chem import FormatConverter, IndigoUtils from utilities.decorators import package_permission_required +from .exceptions import InvalidSMILESException from .logic import ( EPDBURLParser, @@ -2364,7 +2365,10 @@ def package_pathway_nodes(request, package_uuid, pathway_uuid): node_description = request.POST.get("node-description") 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)