diff --git a/bel/api/endpoints/belspec.py b/bel/api/endpoints/belspec.py index c4aa7bc..419802a 100644 --- a/bel/api/endpoints/belspec.py +++ b/bel/api/endpoints/belspec.py @@ -109,9 +109,9 @@ def get_belhelp(version: str = "latest"): @router.post("/belspec/help") def post_belhelp(belhelp: dict): - """Create or Update BEL Help""" + """Create or Update BEL Help """ - bel.belspec.crud.update_belhelp(belhelp) + return bel.belspec.crud.update_belhelp(belhelp) @router.delete("/belspec/help/{version}") @@ -121,7 +121,7 @@ def delete_belhelp(version: str): if version == "latest": version = bel.belspec.crud.get_latest_version() - bel.belspec.crud.delete_belhelp(version) + return bel.belspec.crud.delete_belhelp(version) @router.get("/belspec/versions", response_model=BelSpecVersions) @@ -149,4 +149,4 @@ def delete_belspec(version: str): if version == "latest": version = bel.belspec.crud.get_latest_version() - bel.belspec.crud.delete_belspec(version) + return bel.belspec.crud.delete_belspec(version) diff --git a/bel/belspec/crud.py b/bel/belspec/crud.py index 9ce15ab..ea54cec 100644 --- a/bel/belspec/crud.py +++ b/bel/belspec/crud.py @@ -266,10 +266,12 @@ def update_belhelp(belhelp: dict): version = belhelp["version"] - doc = {"_key": f"belspec_{version}", "doc_type": "belhelp", "belhelp": belhelp} + doc = {"_key": f"belhelp_{version}", "doc_type": "belhelp", "belhelp": belhelp} bel_config_coll.insert(doc, overwrite=True) + return {"msg": "Loaded belspec help"} + def delete_belhelp(version: str): """Delete BEL specification help""" diff --git a/bel/belspec/enhance.py b/bel/belspec/enhance.py index ab0cfe4..b5f01ad 100644 --- a/bel/belspec/enhance.py +++ b/bel/belspec/enhance.py @@ -38,6 +38,14 @@ def add_function_signature_help(specification: dict) -> dict: Simplify the function signatures for presentation to BEL Editor users """ for f in specification["functions"]["signatures"]: + + # Copy primary_function into function signature + if specification["functions"]["signatures"][f]["func_type"] == "Modifier": + specification["functions"]["signatures"][f]["primary_function"] = specification[ + "functions" + ]["info"][f]["primary_function"] + + # Enhance signature for function for argset_idx, argset in enumerate( specification["functions"]["signatures"][f]["signatures"] ): diff --git a/bel/lang/ast.py b/bel/lang/ast.py index 6caff73..58b07e4 100644 --- a/bel/lang/ast.py +++ b/bel/lang/ast.py @@ -36,6 +36,18 @@ from bel.schemas.constants import strarg_validation_lists +def compare_fn_args(args1, args2, ignore_locations: bool = False) -> bool: + """If args set1 is the same as arg set2 - returns True + + This is used to see if two functions have the same set of arguments + """ + + args1 = ", ".join([arg.to_string(ignore_location=True) for arg in args1]) + args2 = ", ".join([arg.to_string(ignore_location=True) for arg in args2]) + + return args1 == args2 + + ######################### # Unknown string # ######################### @@ -58,7 +70,7 @@ def __str__(self): __repr__ = __str__ - def to_string(self, fmt: str = "medium") -> str: + def to_string(self, fmt: str = "medium", ignore_location: bool = False) -> str: return str(self) @@ -79,7 +91,7 @@ def __init__(self, name, version: str = "latest", span: Span = None): self.type = "Relation" - def to_string(self, fmt: str = "medium"): + def to_string(self, fmt: str = "medium", ignore_location: bool = False): if fmt == "short": return self.name_short else: @@ -227,6 +239,17 @@ def orthologizable(self, species_key: Key) -> Optional[bool]: return true_response + def optimize(self): + """Optimize Assertion + + Currently this only optimizes reactions if they match the following pattern + """ + + if self.name == "reaction": + self = optimize_rxn(self) + + return self + def get_species_keys(self, species_keys: List[str] = None): """Collect species associated with NSArgs @@ -274,7 +297,7 @@ def validate(self, errors: List[ValidationError] = None): try: errors.extend(validate_function(self)) except Exception as e: - logger.error(f"Could not validate function {self.to_string()} -- error: {str(e)}") + logger.exception(f"Could not validate function {self.to_string()} -- error: {str(e)}") errors.append( ValidationError( type="Assertion", @@ -291,13 +314,7 @@ def validate(self, errors: List[ValidationError] = None): return errors - def to_string( - self, - fmt: str = "medium", - canonicalize: bool = False, - decanonicalize: bool = False, - orthologize: str = None, - ) -> str: + def to_string(self, fmt: str = "medium", ignore_location: bool = False) -> str: """Convert AST object to string Args: @@ -310,7 +327,12 @@ def to_string( str: string version of BEL AST """ - arg_string = ", ".join([a.to_string(fmt=fmt) for a in self.args]) + if ignore_location and self.name == "location": + return "" + + arg_string = ", ".join( + [a.to_string(fmt=fmt, ignore_location=ignore_location) for a in self.args] + ) if fmt in ["short", "medium"]: function_name = self.name_short @@ -480,7 +502,7 @@ def update(self, entity: BelEntity): self.entity = entity self.span = None - def to_string(self, fmt: str = "medium") -> str: + def to_string(self, fmt: str = "medium", ignore_location: bool = False) -> str: return str(self.entity) @@ -508,7 +530,7 @@ def update(self, value: str): self.value = value self.span = None - def to_string(self, fmt: str = "medium") -> str: + def to_string(self, fmt: str = "medium", ignore_location: bool = False) -> str: """Convert AST object to string Args: @@ -737,8 +759,14 @@ def parse(self): else: logger.error(f"Unknown span type {span}") + return self.args_to_components() + + def args_to_components(self): + """Convert AST args to subject, relation, object components""" + # Subject only assertion - if len(self.args) == 1 and self.args[0].type == "Function": + # if len(self.args) == 1 and self.args[0].type == "Function": + if len(self.args) == 1: self.subject = self.args[0] # Normal SRO BEL assertion elif ( @@ -914,6 +942,23 @@ def validate(self): if arg and arg.type in ["Function"]: self.errors.extend(arg.validate(errors=[])) + def optimize(self): + """Optimize Assertion + + Currently this only optimizes reactions if they match the following pattern + reactants(A, B) -> products(complex(A, B)) SHOULD BE complex(A, B) + """ + + if hasattr(self, "args"): + for idx, arg in enumerate(self.args): + if arg and arg.type == "Function": + tmp = arg.optimize() + self.args[idx] = tmp + + self.args_to_components() + + return self + def subcomponents(self, subcomponents=None): """Generate subcomponents of the BEL subject or object @@ -939,7 +984,7 @@ def subcomponents(self, subcomponents=None): return subcomponents - def to_string(self, fmt: str = "medium") -> str: + def to_string(self, fmt: str = "medium", ignore_location: bool = False) -> str: """Convert AST object to string Args: @@ -947,7 +992,7 @@ def to_string(self, fmt: str = "medium") -> str: short = short function and short relation format medium = short function and long relation format long = long function and long relation format - canonicalize + ignore_location: don't add location to output string Returns: str: string version of BEL AST @@ -958,19 +1003,19 @@ def to_string(self, fmt: str = "medium") -> str: if self.subject and self.relation and self.object: if isinstance(self.object, BELAst): return "{} {} ({})".format( - self.subject.to_string(fmt=fmt), + self.subject.to_string(fmt=fmt, ignore_location=ignore_location), self.relation.to_string(fmt=fmt), - self.object.to_string(fmt=fmt), + self.object.to_string(fmt=fmt, ignore_location=ignore_location), ) else: return "{} {} {}".format( - self.subject.to_string(fmt=fmt), + self.subject.to_string(fmt=fmt, ignore_location=ignore_location), self.relation.to_string(fmt=fmt), - self.object.to_string(fmt=fmt), + self.object.to_string(fmt=fmt, ignore_location=ignore_location), ) elif self.subject: - return "{}".format(self.subject.to_string(fmt=fmt)) + return "{}".format(self.subject.to_string(fmt=fmt, ignore_location=ignore_location)) else: return "" @@ -1405,6 +1450,13 @@ def validate_function(fn: Function, errors: List[ValidationError] = None) -> Lis ) ) + # Check for bad reactions + # 1. reactants = products -> error + # 2. reactants = products(complex(reactants)) = warning to replace with just the complex + + if fn.name == "reaction": + errors.extend(validate_rxn_semantics(fn)) + # Modifier function with wrong parent function if ( fn.function_signature["func_type"] == "Modifier" @@ -1424,6 +1476,72 @@ def validate_function(fn: Function, errors: List[ValidationError] = None) -> Lis return errors +def validate_rxn_semantics(rxn: Function) -> List[ValidationError]: + """Validate Reactions + + Check for bad reactions + 1. reactants = products -> error + 2. reactants = products(complex(reactants)) = warning to replace with just the complex + + """ + + errors = [] + + reactants = rxn.args[0] + products = rxn.args[1] + + if reactants.name != "reactants" or products.name != "products": + return errors + + # ERROR reactants(A, B) -> products(complex(A, B)) SHOULD BE complex(A, B) + if products.args[0].name == "complexAbundance" and compare_fn_args( + reactants.args, products.args[0].args + ): + errors.append( + ValidationError( + type="Assertion", + severity="Error", + msg=f"Reaction should be replaced with just the product complex: {products.args[0].to_string()}", + visual_pairs=[(rxn.span.start, rxn.span.end)], + index=rxn.span.start, + ) + ) + + # ERROR reactants(A, B) SHOULD NOT EQUAL products(A, B) + elif compare_fn_args(reactants.args, products.args): + errors.append( + ValidationError( + type="Assertion", + severity="Error", + msg=f"Reaction should not have equivalent reactants and products", + visual_pairs=[(rxn.span.start, rxn.span.end)], + index=rxn.span.start, + ) + ) + + return errors + + +def optimize_rxn(rxn: Function) -> Function: + """Transform reaction into more optimal BEL""" + + parent = rxn.parent + reactants = rxn.args[0] + products = rxn.args[1] + if reactants.name != "reactants" or products.name != "products": + return rxn + + # Convert reactants(A, B) -> products(complex(A, B)) SHOULD BE complex(A, B) + + if products.args[0].name == "complexAbundance" and compare_fn_args( + reactants.args, products.args[0].args, ignore_locations=True + ): + rxn = products.args[0] + rxn.parent = parent + + return rxn + + def sort_function_args(fn: Function): """Add sort tuple values to function arguments for canonicalization and sort function arguments""" diff --git a/bel/schemas/belspec.py b/bel/schemas/belspec.py index cd23c38..024bc2c 100644 --- a/bel/schemas/belspec.py +++ b/bel/schemas/belspec.py @@ -8,8 +8,8 @@ # BEL Specification Schema ########################################################### class FunctionTypes(str, enum.Enum): - primary = "primary" - modifier = "modifier" + Primary = "Primary" + Modifier = "Modifier" class ArgumentTypes(str, enum.Enum): diff --git a/tests/lang/test_ast_canonicalization.py b/tests/lang/test_ast_canonicalization.py new file mode 100644 index 0000000..05c874e --- /dev/null +++ b/tests/lang/test_ast_canonicalization.py @@ -0,0 +1,116 @@ +# Standard Library +import json +import pprint + +# Third Party +import pytest + +# Local +import bel.lang.ast +from bel.schemas.bel import AssertionStr, ValidationError + +# cSpell:disable + + +@pytest.mark.parametrize( + "test_input,expected", + [ + ("p(HGNC:AKT1)", "p(EG:207)"), + ("complex(p(HGNC:IL12B), p(HGNC:IL12A))", "complex(p(EG:3592), p(EG:3593))"), + ( + 'complex(loc(GO:"extracellular space"), p(HGNC:IL12A), p(EG:207), p(HGNC:IL12B))', + "complex(p(EG:207), p(EG:3592), p(EG:3593), loc(GO:0005615))", + ), + ( + 'complex(p(HGNC:MTOR), a(CHEBI:"phosphatidic acid"), a(CHEBI:sirolimus))', + "complex(a(CHEBI:16337), a(CHEBI:9168), p(EG:2475))", + ), + ( + 'rxn(reactants(a(CHEBI:hypoxanthine), a(CHEBI:water), a(CHEBI:dioxygen)), products(a(CHEBI:xanthine), a(CHEBI:"hydrogen peroxide"))', + "rxn(reactants(a(CHEBI:15377), a(CHEBI:15379), a(CHEBI:17368)), products(a(CHEBI:15318), a(CHEBI:16240)))", + ), + ( + "p(HGNC:MAPK1, pmod(Ph, Thr, 185), pmod(Ph, Tyr, 187), pmod(Ph))", + "p(EG:5594, pmod(Ph), pmod(Ph, Thr, 185), pmod(Ph, Tyr, 187))", + ), + ( + "p(HGNC:KRAS, pmod(Palm, Cys), pmod(Ph, Tyr, 32))", + "p(EG:3845, pmod(Palm, Cys), pmod(Ph, Tyr, 32))", + ), + ( + "p(HGNC:TP53, var(p.His168Arg), var(p.Arg249Ser))", + "p(EG:7157, var(p.Arg249Ser), var(p.His168Arg))", + ), + ( + "p(HGNC:NFE2L2, pmod(Ac, Lys, 596), pmod(Ac, Lys, 599), loc(GO:nucleus))", + "p(EG:4780, loc(GO:0005634), pmod(Ac, Lys, 596), pmod(Ac, Lys, 599))", + ), + ( + "path(DO:0080600!COVID-19)", + "path(DO:0080600)", + ), + ], +) +def test_ast_canonicalization(test_input, expected): + """Test AST canonicalization and sorting function arguments + + See issue: https://github.com/belbio/bel/issues/13 + """ + + assertion = AssertionStr(entire=test_input) + + ast = bel.lang.ast.BELAst(assertion=assertion) + + ast.canonicalize() + + print("Canonicalized", ast.to_string()) + + assert ast.to_string() == expected + + +def test_ast_canonicalization_2(): + """Test AST canonicalization and sorting function arguments + + See issue: https://github.com/belbio/bel/issues/13 + """ + + test_input = "path(DO:0080600!COVID-19)" + expected = "path(DO:0080600)" + + assertion = AssertionStr(entire=test_input) + + ast = bel.lang.ast.BELAst(assertion=assertion) + + ast.canonicalize() + + print("Canonicalized", ast.to_string()) + + assert ast.to_string() == expected + + +def test_ast_subcomponents_simple(): + + test_input = "path(DO:0080600!COVID-19)" + assertion = AssertionStr(entire=test_input) + + ast = bel.lang.ast.BELAst(assertion=assertion) + + subcomponents = ast.subcomponents() + + print("Subcomponents", subcomponents) + + assert subcomponents == ["path(DO:0080600!COVID-19)", "DO:0080600!COVID-19", "DO:COVID-19"] + + +def test_ast_nsarg(): + + test_input = "HGNC:AKT1" + + assertion = AssertionStr(entire=test_input) + + ast = bel.lang.ast.BELAst(assertion=assertion) + + ast.canonicalize() + print("Canonicalized", ast.to_string()) + + assert ast.to_string() == "EG:207" diff --git a/tests/lang/test_ast_optimization.py b/tests/lang/test_ast_optimization.py new file mode 100644 index 0000000..77ad66a --- /dev/null +++ b/tests/lang/test_ast_optimization.py @@ -0,0 +1,49 @@ +# Standard Library +import json +import pprint + +# Third Party +import pytest + +# Local +import bel.lang.ast +from bel.lang.ast import Function +from bel.schemas.bel import AssertionStr, ValidationError + +# cSpell:disable + + +def test_optimize_rxn_1(): + """Optimize rxn 1 + + rxn(A, B) -> rxn(complex(A, B)) ==> complex(A, B) + """ + + assertion = AssertionStr( + subject="rxn(reactants(p(HGNC:AKT1), p(HGNC:AKT2)), products(complex(p(HGNC:AKT1), p(HGNC:AKT2)))" + ) + + ast = bel.lang.ast.BELAst(assertion=assertion) + + ast.optimize() + + print("RXN", ast.to_string()) + + assert ast.to_string() == "complex(p(HGNC:391!AKT1), p(HGNC:392!AKT2))" + + +def test_optimize_rxn_2(): + assertion = AssertionStr( + subject="rxn(reactants(g(ensembl:ENSG00000157557!ETS2), p(SP:P50548!ERF, loc(GO:0005654!nucleoplasm))), products(complex(g(ensembl:ENSG00000157557!ETS2), p(SP:P50548!ERF), loc(GO:0005654!nucleoplasm))))" + ) + + ast = bel.lang.ast.BELAst(assertion=assertion) + + ast.optimize() + + print("RXN", ast.to_string()) + + assert ( + ast.to_string() + == "complex(g(ensembl:ENSG00000157557!ETS2), p(SP:P50548!ERF), loc(GO:0005654!nucleoplasm))" + ) diff --git a/tests/lang/test_ast_parsing.py b/tests/lang/test_ast_parsing.py new file mode 100644 index 0000000..73454fa --- /dev/null +++ b/tests/lang/test_ast_parsing.py @@ -0,0 +1,157 @@ +# Standard Library +import json +import pprint + +# Third Party +import pytest + +# Local +import bel.lang.ast +from bel.schemas.bel import AssertionStr, ValidationError + +# cSpell:disable + +# TODO test reading in a string from a file doesn't remove the escape backslash + + +@pytest.mark.skip(msg="Cannot handle escaped quote") +def test_ast_parse_escaped_quote(): + + # Bad quote + # assertion = AssertionStr(entire='complex(SCOMP:"Test named\" complex", p(HGNC:"207"!"AKT1 Test), p(HGNC:207!"Test"), loc(nucleus)) increases p(HGNC:EGF) increases p(hgnc : "here I am" ! X)') + + assertion = AssertionStr( + entire='complex(SCOMP:"Test named" complex", p(HGNC:"207"!"AKT1 Test"), p(HGNC:207!"Test"), loc(nucleus)) increases p(HGNC:EGF) increases p(hgnc : "here I am" ! X)' + ) + + ast = bel.lang.ast.BELAst(assertion=assertion) + + ast.print_tree() + + print("\n") + + for arg in ast.args: + print("AST arg: ", arg) + + assert "complexAbundance" == ast.args[0].name + + assert "increases" == ast.args[1].name + + assert "proteinAbundance(HGNC:EGF)" == str(ast.args[2]) + + +def test_ast_orthologization(): + """Test AST orthologization""" + + assertion = AssertionStr(entire="p(HGNC:AKT1)") + + ast = bel.lang.ast.BELAst(assertion=assertion) + + if ast.orthologizable("TAX:10090"): + ast.orthologize("TAX:10090") + + print("Orthologized to mouse", ast.to_string()) + assert ast.to_string() == "p(EG:11651!Akt1)" + + ast.decanonicalize() + + print("Orthologized and decanonicalized to mouse", ast.to_string()) + + assert ast.to_string() == "p(MGI:87986!Akt1)" + + else: + assert False, "Not orthologizable" + + +def test_ast_nested_orthologization(): + + assertion = AssertionStr(entire="p(HGNC:AKT1) increases (p(HGNC:AKT1) increases p(HGNC:EGF))") + ast = bel.lang.ast.BELAst(assertion=assertion) + + orthologizable = ast.orthologizable("TAX:10090") + print("Orthologizable", orthologizable) + + ast.orthologize("TAX:10090").decanonicalize() + + expected = "p(MGI:87986!Akt1) increases (p(MGI:87986!Akt1) increases p(MGI:95290!Egf))" + + result = ast.to_string() + print("Result", result) + + assert result == expected + + +def test_ast_orthologizable(): + """Test AST orthologization""" + + # No rat ortholog for human EG:9 + assertion = AssertionStr(entire="p(HGNC:AKT1) increases p(EG:9)") + + ast = bel.lang.ast.BELAst(assertion=assertion) + + result = ast.orthologizable("TAX:10116") # orthologizable to Rat + + assert result == False + + result = ast.orthologizable("TAX:10090") # orthologizable to mouse + + assert result == True + + +def test_ast_parse_fus(): + + assertion = AssertionStr(entire="act(p(fus(HGNC:EWSR1, start, HGNC:FLI1, end)), ma(tscript))") + + ast = bel.lang.ast.BELAst(assertion=assertion) + + print("To String", ast.to_string()) + + assert ( + ast.to_string() == "act(p(fus(HGNC:3508!EWSR1, start, HGNC:3749!FLI1, end)), ma(tscript))" + ) + + +def test_get_species(): + """Collect all NSArg species for Assertion""" + + assertion = AssertionStr(entire="p(HGNC:391!AKT1) increases p(MGI:87986!Akt1)") + + ast = bel.lang.ast.BELAst(assertion=assertion) + + species = ast.get_species_keys() + + print("Species", species) + + assert species == ["TAX:9606", "TAX:10090"] + + +@pytest.mark.skip("Figure out a better way to handle checks") +def test_get_orthologs(): + """Get all orthologs for any NSArgs in Assertion""" + + assertion = AssertionStr(entire="p(MGI:Akt2) increases p(HGNC:391!AKT1)") + + ast = bel.lang.ast.BELAst(assertion=assertion) + + orthologs = ast.get_orthologs() + + print("Orthologs") + for ortholog in orthologs: + print(ortholog) + + expected = [ + { + "TAX:10090": {"canonical": "EG:11652", "decanonical": "MGI:104874"}, + "TAX:9606": {"canonical": "EG:208", "decanonical": "HGNC:392"}, + "TAX:10116": {"canonical": "EG:25233", "decanonical": "RGD:2082"}, + }, + { + "TAX:9606": {"canonical": "EG:207", "decanonical": "HGNC:391"}, + "TAX:10116": {"canonical": "EG:24185", "decanonical": "RGD:2081"}, + "TAX:10090": {"canonical": "EG:11651", "decanonical": "MGI:87986"}, + }, + ] + + # orthologs - compares the string result of the NSVal object for the orthologs + + assert orthologs == expected diff --git a/tests/lang/test_ast.py b/tests/lang/test_ast_validation.py similarity index 89% rename from tests/lang/test_ast.py rename to tests/lang/test_ast_validation.py index e15c533..5cbfc18 100644 --- a/tests/lang/test_ast.py +++ b/tests/lang/test_ast_validation.py @@ -406,7 +406,7 @@ def test_validate_nested(): def test_validate_rxn1(): - """Validate rxn()""" + """Validate path()""" assertion = AssertionStr( subject="rxn(reactants(complex(reactome:R-HSA-1112584.1, p(SP:O14543, loc(GO:0005829)))), products(complex(reactome:R-HSA-1112584.1, p(SP:O14543), loc(GO:0005829))))" @@ -426,7 +426,7 @@ def test_validate_rxn1(): def test_validate_rxn2(): - """Validate rxn()""" + """Validate path()""" assertion = AssertionStr( subject='rxn(reactants(a(CHEBI:"guanidinoacetic acid"), a(CHEBI:"(S)-S-adenosyl-L-methionine")), products(a(CHEMBL:s-adenosylhomocysteine), a(CHEBI:creatine)))' @@ -444,7 +444,7 @@ def test_validate_rxn2(): def test_validate_rxn3(): - """Validate rxn()""" + """Validate path()""" assertion = AssertionStr( entire='act(p(HGNC:GPT2), ma(cat)) directlyIncreases rxn(reactants(a(CHEBI:alanine), a(SCHEM:"alpha-Ketoglutaric acid")), products(a(SCHEM:"Propanoic acid, 2-oxo-, ion(1-)"), a(CHEBI:"L-glutamic acid")))' @@ -462,7 +462,7 @@ def test_validate_rxn3(): def test_validate_rxn4(): - """Validate simple g() in rxn()""" + """Validate path()""" assertion = AssertionStr(subject="rxn(reactants(g(HGNC:AKT1)), products(g(HGNC:AKT2)))") @@ -477,24 +477,6 @@ def test_validate_rxn4(): assert ast.errors == [] -def test_validate_rxn5(): - """Validate realistic g() in rxn()""" - - assertion = AssertionStr( - subject='rxn(reactants(a(CHEBI:36144!"ferriheme b", loc(GO:0005654!nucleoplasm)), g(ensembl:ENSG00000133794!ARNTL), p(SP:O15379!HDAC3, loc(GO:0005654!nucleoplasm)), p(SP:O75376!NCOR1, loc(GO:0005654!nucleoplasm)), p(SP:P20393!NR1D1, loc(GO:0005654!nucleoplasm))), products(complex(a(CHEBI:36144!"ferriheme b"), g(ensembl:ENSG00000133794!ARNTL), p(SP:O15379!HDAC3), p(SP:O75376!NCOR1), p(SP:P20393!NR1D1))))' - ) - - ast = bel.lang.ast.BELAst(assertion=assertion) - - ast.validate() - - print("Errors") - for error in ast.errors: - print("Error", error.json()) - - assert ast.errors == [] - - def test_validate_complex_nsarg(): assertion = AssertionStr( @@ -671,28 +653,3 @@ def test_validate_rxn_semantics(): print("Validation Errors", ast.errors) assert ast.errors[0].msg == "Reaction should not have equivalent reactants and products" - - -def test_validate_escaped_quotes(): - """Test adding backslashes and make the parser robust to them - - If someone copied an assertion with quotes in it like frag("217_374") below - those will be - escaped in the JSON strings. The nanopub curation form will forward the \ to the backend - which will not parse the Assertion correctly and generate an exception error. - - TODO - need to figure out how to handle this correctly - either silently remove them or flag them - as errors. - """ - assertion = AssertionStr( - subject='act(complex(p(SP:Q14790!CASP8, frag("217_374")), p(SP:Q14790!CASP8, frag("385_479"))))', - relation="increases", - object='p(SP:P55957!BID, frag("62_195"), loc(GO:0005829!cytosol))', - ) - - ast = bel.lang.ast.BELAst(assertion=assertion) - - ast.validate() - - print("Validation Errors", ast.errors) - - assert False diff --git a/tests/lang/test_belobj.py b/tests/lang/test_belobj.py index c602a35..251add7 100644 --- a/tests/lang/test_belobj.py +++ b/tests/lang/test_belobj.py @@ -8,7 +8,7 @@ bo = bel.lang.belobj.BEL() -@pytest.mark.parametrize("assertion, expected", [("p(HGNC:PBX2)", "p(EG:5089!PBX2)")]) +@pytest.mark.parametrize("assertion, expected", [("p(HGNC:PBX2)", "p(EG:5089)")]) def test_canonicalization(assertion, expected): """Test canonicalization"""