From 53cfad27ac6e48085ea9219384680662010127ca Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Mon, 5 Oct 2026 19:08:12 -0400 Subject: [PATCH 1/8] Rewrite the OpenAPI document to describe the Elasticsearch service The document was copied from NodeNorm Redis and never pruned, so it advertised TRAPI 1.5 (info.x-trapi), carried 145 schemas no path referenced, named RENCI's developer as the contact and sent readers to /get_curie_prefixes, which this service has never served. It also failed openapi-spec-validator: the /get_allowed_conflations response had no description, and FilterKgraphTopNParameters.max_edges declared maximum 0 (NCATSTranslator/NodeNormalization#409). Rewritten from scratch against build_handlers(): the six routes this service registers, nothing else. Responses that were `schema: {}` now have schemas (NormalizedNodes, SetIDResponse), the FastAPI 422 responses are replaced by the 400 the Tornado handlers raise, and the examples are taken from the live CI instance. The servers block lists the ci and test deployments at ITRB instead of RENCI. Contact is Will Liu and the team is DOGSURF (biothings/NodeNormalizationAPI#35); infores and termsOfService are unchanged for now. info.version is bumped to 1.1.0 because /status reports it and the response shapes change in the next commit. handlers/curie_prefix.py was an unregistered copy of the semantic-types handler (its class is even named SemanticTypeHandler), so it goes too. Refs biothings/NodeNormalizationAPI#35, #36 Co-Authored-By: Claude Fable 5.1 --- src/nodenorm/handlers/curie_prefix.py | 63 - src/nodenorm/webapp/openapi.json | 3360 +++---------------------- 2 files changed, 385 insertions(+), 3038 deletions(-) delete mode 100644 src/nodenorm/handlers/curie_prefix.py diff --git a/src/nodenorm/handlers/curie_prefix.py b/src/nodenorm/handlers/curie_prefix.py deleted file mode 100644 index e4a4dc6..0000000 --- a/src/nodenorm/handlers/curie_prefix.py +++ /dev/null @@ -1,63 +0,0 @@ -from biothings.web.handlers import BaseHandler -from tornado.web import HTTPError - -from nodenorm.biolink import toolkit - - -class SemanticTypeHandler(BaseHandler): - """ - Mirror implementation to the renci implementation found at - https://nodenormalization-sri.renci.org/docs - - We intend to mirror the /get_curie_prefixes endpoint - """ - - name = "curie_prefixes" - - async def get(self) -> dict: - type_aggregation = {"unique_types": {"terms": {"field": "type", "size": 100}}} - source_fields = ["type"] - try: - search_indices = self.biothings.elasticsearch.indices - type_aggregation_result = await self.biothings.elasticsearch.async_client.search( - aggregations=type_aggregation, index=search_indices, size=0, source_includes=source_fields - ) - except Exception as gen_exc: - network_error = HTTPError( - detail="Unable to access the elasticsearch index for type information", status_code=500 - ) - raise network_error from gen_exc - - semantic_types = set() - for bucket in type_aggregation_result.body["aggregations"]["unique_types"]["buckets"]: - biolink_type = bucket["key"] - semantic_types.add(biolink_type) - for ancestor in toolkit.get_ancestors(biolink_type): - semantic_types.add(toolkit.get_element(ancestor)["class_uri"].lower()) - - semantic_type_response = {"semantic_types": {"types": list(semantic_types)}} - self.finish(semantic_type_response) - - async def post(self) -> dict: - type_aggregation = {"unique_types": {"terms": {"field": "type", "size": 100}}} - source_fields = ["type"] - try: - search_indices = self.biothings.elasticsearch.indices - type_aggregation_result = await self.biothings.elasticsearch.async_client.search( - aggregations=type_aggregation, index=search_indices, size=0, source_includes=source_fields - ) - except Exception as gen_exc: - network_error = HTTPError( - detail="Unable to access the elasticsearch index for type information", status_code=500 - ) - raise network_error from gen_exc - - semantic_types = set() - for bucket in type_aggregation_result.body["aggregations"]["unique_types"]["buckets"]: - biolink_type = bucket["key"] - semantic_types.add(biolink_type) - for ancestor in toolkit.get_ancestors(biolink_type): - semantic_types.add(toolkit.get_element(ancestor)["class_uri"].lower()) - - semantic_type_response = {"semantic_types": {"types": list(semantic_types)}} - self.finish(semantic_type_response) diff --git a/src/nodenorm/webapp/openapi.json b/src/nodenorm/webapp/openapi.json index 72e4d8a..e36e50f 100644 --- a/src/nodenorm/webapp/openapi.json +++ b/src/nodenorm/webapp/openapi.json @@ -2,44 +2,104 @@ "openapi": "3.1.0", "info": { "title": "Node Normalization", - "version": "1.0.0", + "version": "1.1.0", "x-translator": { "component": "Utility", "team": [ - "Standards Reference Implementation Team" + "DOGSURF" ], "infores": "infores:sri-node-normalizer" }, - "x-trapi": { - "version": "1.5.0", - "operations": [ - "annotate_nodes" - ] - }, "contact": { - "email": "bizon@renci.org", - "name": "Chris Bizon", - "x-id": "https://github.com/cbizon", + "email": "shuchenliu2014@gmail.com", + "name": "Will Liu", + "x-id": "https://github.com/shuchenliu", "x-role": "responsible developer" }, "termsOfService": "https://toss.apps.renci.org/tos?service_long=Node+Normalization&provider_long=the+Translator+Consortium", - "description": "Node Normalization takes a CURIE, and returns:
  1. The preferred CURIE for this entity
  2. All other known equivalent identifiers for the entity
  3. Semantic types for the entity as defined by the BioLink Model
The data served by Node Normalization is created by Babel, which attempts to find identifier equivalences, and makes sure that CURIE prefixes are BioLink Model Compliant. To determine whether Node Normalization is likely to be useful, check /get_semantic_types, which lists the BioLink semantic types for which normalization has been attempted, and /get_curie_prefixes, which lists the number of times each prefix is used for a semantic type." + "description": "Node Normalization takes a CURIE, and returns:
  1. The preferred CURIE for this entity
  2. All other known equivalent identifiers for the entity
  3. Semantic types for the entity as defined by the Biolink Model
This is the Elasticsearch-backed implementation of the Node Normalization API, maintained by the BioThings group at Scripps Research (source code). The data it serves is created by Babel, which finds identifier equivalences and makes sure that CURIE prefixes are Biolink Model compliant. To determine whether Node Normalization is likely to be useful, check /get_semantic_types, which lists the Biolink semantic types for which normalization has been attempted. The semantics of each endpoint are described in the Node Normalization API documentation. To learn more about how Babel creates cliques of equivalent identifiers, read its documentation." }, + "servers": [ + { + "description": "Continuous integration", + "url": "https://nodenorm-es.ci.transltr.io/", + "x-maturity": "staging", + "x-location": "ITRB" + }, + { + "description": "Test", + "url": "https://nodenorm-es.test.transltr.io/", + "x-maturity": "testing", + "x-location": "ITRB" + }, + { + "description": "Localhost", + "url": "http://localhost:8000/", + "x-maturity": "development" + } + ], + "tags": [ + { + "name": "translator" + }, + { + "name": "health" + }, + { + "name": "metadata" + } + ], "paths": { "/status": { "get": { "summary": "Status information on this NodeNorm instance", "tags": ["health"], - "description": "Returns information about this NodeNorm instance and the elasticsearch instance backing it.", + "description": "Returns information about this NodeNorm instance, the Babel release it serves and the Elasticsearch cluster backing it. `status` is `error` when the Elasticsearch cluster cannot be reached.", "operationId": "status", "responses": { "200": { - "description": "Successful Response", + "description": "Status of this NodeNorm instance", "content": { "application/json": { "schema": { "type": "object", - "title": "Response Status Get" + "properties": { + "status": {"type": "string", "enum": ["running", "error"]}, + "version": {"type": "string", "description": "The `info.version` of this OpenAPI document"}, + "babel_version": {"type": "string", "description": "The Babel release loaded into the index"}, + "babel_version_url": {"type": "string", "format": "uri", "description": "Release notes for that Babel release"}, + "backend": {"type": "string", "enum": ["elasticsearch"]}, + "biolink_model_toolkit_version": {"type": "string"}, + "elasticsearch": {"type": "object", "description": "Per-node statistics from the cluster's `_cat/nodes`"} + }, + "required": ["status", "version", "babel_version", "babel_version_url", "backend"] + }, + "example": { + "status": "running", + "version": "1.1.0", + "babel_version": "2025sep1", + "babel_version_url": "https://github.com/NCATSTranslator/Babel/blob/main/releases/2025sep1/README.md", + "backend": "elasticsearch", + "biolink_model_toolkit_version": "v4.4.3", + "elasticsearch": { + "nodes": { + "es-0": { + "name": "es-0", + "cpu": "0", + "disk.avail": "334.2gb", + "disk.total": "786.3gb", + "disk.used": "452.1gb", + "disk.used_percent": "57.50", + "heap.current": "5.8gb", + "heap.max": "16gb", + "load_1m": "0.16", + "load_5m": "0.16", + "load_15m": "0.19", + "uptime": "81.2d", + "version": "9.3.1" + } + } + } } } } @@ -47,136 +107,119 @@ } } }, + "/version": { + "get": { + "summary": "The git commit this instance was built from", + "tags": ["health"], + "description": "Returns the git commit hash of the deployed code. For the API version, see `version` in /status.", + "operationId": "version", + "responses": { + "200": { + "description": "The deployed commit", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "version": {"type": "string"} + }, + "required": ["version"] + }, + "example": {"version": "fa7d74dc9537373fb36a7bef0d29cdaf4e83b897"} + } + } + } + } + } + }, "/get_normalized_nodes": { "get": { "summary": "Get the equivalent identifiers and semantic types for the curie(s) entered.", "tags": ["translator"], - "description": "Returns the equivalent identifiers and semantic types for the curie(s)", - "operationId": "get_normalized_node_handler_get_normalized_nodes_get", + "description": "Returns the equivalent identifiers and semantic types for the curie(s). Each input CURIE maps to a normalized node, or to `null` if it is unknown.", + "operationId": "get_normalized_nodes_get", "parameters": [ { + "name": "curie", + "in": "query", "description": "List of curies to normalize", - "required": false, + "required": true, "schema": { - "items": { - "type": "string" - }, "type": "array", - "minItems": 1, - "title": "Curie", - "description": "List of curies to normalize", - "default": [] + "items": {"type": "string"}, + "minItems": 1 }, - "example": [ - "MESH:D014867", - "NCIT:C34373" - ], - "name": "curie", - "in": "query" + "example": ["MESH:D014867", "NCIT:C34373"] }, { + "name": "conflate", + "in": "query", "description": "Whether to apply gene/protein conflation", "required": false, - "schema": { - "type": "boolean", - "title": "Conflate", - "description": "Whether to apply gene/protein conflation", - "default": true - }, - "name": "conflate", - "in": "query" + "schema": {"type": "boolean", "default": true} }, { + "name": "drug_chemical_conflate", + "in": "query", "description": "Whether to apply drug/chemical conflation", "required": false, - "schema": { - "type": "boolean", - "title": "Drug Chemical Conflate", - "description": "Whether to apply drug/chemical conflation", - "default": false - }, - "name": "drug_chemical_conflate", - "in": "query" + "schema": {"type": "boolean", "default": false} }, { + "name": "description", + "in": "query", "description": "Whether to return curie descriptions when possible", "required": false, - "schema": { - "type": "boolean", - "title": "Description", - "description": "Whether to return curie descriptions when possible", - "default": false - }, - "name": "description", - "in": "query" + "schema": {"type": "boolean", "default": false} }, { + "name": "individual_types", + "in": "query", "description": "Whether to return individual types for equivalent identifiers", "required": false, - "schema": { - "type": "boolean", - "title": "Individual Types", - "description": "Whether to return individual types for equivalent identifiers", - "default": false - }, - "name": "individual_types", - "in": "query" + "schema": {"type": "boolean", "default": false} } ], "responses": { "200": { - "description": "Successful Response", + "description": "A normalized node (or `null`) for every input CURIE", "content": { "application/json": { - "schema": {} + "schema": {"$ref": "#/components/schemas/NormalizedNodes"}, + "examples": {"default": {"$ref": "#/components/examples/NormalizedNodes"}} } } }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } + "400": { + "description": "No `curie` parameter was supplied" } } }, "post": { "summary": "Get the equivalent identifiers and semantic types for the curie(s) entered.", "tags": ["translator"], - "description": "Returns the equivalent identifiers and semantic types for the curie(s). Use the `conflate` flag to choose whether to apply conflation.", - "operationId": "get_normalized_node_handler_post_get_normalized_nodes_post", + "description": "Returns the equivalent identifiers and semantic types for the curie(s). Use the `conflate` and `drug_chemical_conflate` flags to choose which conflations to apply.", + "operationId": "get_normalized_nodes_post", "requestBody": { + "required": true, "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/CurieList" - } + "schema": {"$ref": "#/components/schemas/CurieList"} } - }, - "required": true + } }, "responses": { "200": { - "description": "Successful Response", + "description": "A normalized node (or `null`) for every input CURIE", "content": { "application/json": { - "schema": {} + "schema": {"$ref": "#/components/schemas/NormalizedNodes"}, + "examples": {"default": {"$ref": "#/components/examples/NormalizedNodes"}} } } }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } + "400": { + "description": "The body has no `curies`" } } } @@ -185,107 +228,67 @@ "get": { "summary": "Normalize and deduplicate a set of identifiers and return a single hash that represents this set.", "tags": ["translator"], - "operationId": "get_setid_get_setid_get", + "description": "Returns the set ID for a given set of CURIEs. A set ID is an identifier that can be used to identify this set of CURIEs going forward. It is currently impossible to recreate a set of CURIEs from a set ID, but the same set of CURIEs given to the same version of Node Normalization will always return the same set ID.", + "operationId": "get_setid_get", "parameters": [ { + "name": "curie", + "in": "query", "description": "Set of curies to normalize", - "required": false, + "required": true, "schema": { - "items": { - "type": "string" - }, "type": "array", - "minItems": 1, - "title": "Curie", - "description": "Set of curies to normalize", - "default": [] + "items": {"type": "string"}, + "minItems": 1 }, - "example": [ - "MESH:D014867", - "NCIT:C34373", - "UNII:63M8RYN44N", - "RUBBISH:1234" - ], - "name": "curie", - "in": "query" + "example": ["MESH:D014867", "NCIT:C34373", "UNII:63M8RYN44N", "RUBBISH:1234"] }, { - "description": "Set of conflations to apply", + "name": "conflation", + "in": "query", + "description": "Set of conflations to apply; see /get_allowed_conflations", "required": false, "schema": { - "items": { - "type": "string" - }, "type": "array", - "title": "Conflation", - "description": "Set of conflations to apply", + "items": {"type": "string"}, "default": [] }, - "example": [ - "GeneProtein", - "DrugChemical" - ], - "name": "conflation", - "in": "query" + "example": ["GeneProtein", "DrugChemical"] } ], "responses": { "200": { - "description": "Successful Response", + "description": "The set ID for the input CURIEs", "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/SetIDResponse" - } + "schema": {"$ref": "#/components/schemas/SetIDResponse"} } } }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } + "400": { + "description": "No `curie` parameter was supplied" } } }, "post": { - "summary": "Normalize and deduplicate a set of identifiers and return a single hash that represents this set.", + "summary": "Normalize and deduplicate several sets of identifiers and return a hash for each set.", "tags": ["translator"], - "operationId": "get_setid_get_setid_post", + "description": "Returns one set ID per input set, keyed by the set's position in the request as a string (\"0\", \"1\", ...).", + "operationId": "get_setid_post", "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/SetIDQuery" - }, "type": "array", - "title": "Sets", - "description": "Set of identifiers to normalize", - "default": [] + "items": {"$ref": "#/components/schemas/SetIDQuery"}, + "minItems": 1 }, "example": [ + {"curies": ["MESH:D014867", "NCIT:C34373"]}, { - "curies": [ - "MESH:D014867", - "NCIT:C34373" - ] - }, - { - "curies": [ - "NCIT:C34373", - "MESH:D014867", - "UNII:63M8RYN44N", - "RUBBISH:1234" - ], - "conflations": [ - "GeneProtein", - "DrugChemical" - ] + "curies": ["NCIT:C34373", "MESH:D014867", "UNII:63M8RYN44N", "RUBBISH:1234"], + "conflations": ["GeneProtein", "DrugChemical"] } ] } @@ -293,45 +296,45 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "One set ID per input set, keyed by its index", "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/SetIDResponse" - }, - "type": "array", - "title": "Response Get Setid Get Setid Post" + "type": "object", + "additionalProperties": {"$ref": "#/components/schemas/SetIDResponse"} } } } }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } + "400": { + "description": "The body is empty" } } } }, "/get_semantic_types": { "get": { - "summary": "Return a list of BioLink semantic types for which normalization has been attempted.", + "summary": "Return a list of Biolink semantic types for which normalization has been attempted.", "tags": ["metadata"], - "description": "Returns a distinct set of the semantic types discovered in the compendium data.", - "operationId": "get_semantic_types_handler_get_semantic_types_get", + "description": "Returns the distinct Biolink classes found in the index, together with all of their ancestors.", + "operationId": "get_semantic_types", "responses": { "200": { - "description": "Successful Response", + "description": "The semantic types in this instance", "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/SemanticTypes" + "schema": {"$ref": "#/components/schemas/SemanticTypes"}, + "example": { + "semantic_types": { + "types": [ + "biolink:NamedThing", + "biolink:ChemicalEntity", + "biolink:SmallMolecule", + "biolink:Disease", + "biolink:Gene", + "biolink:Protein" + ] + } } } } @@ -341,26 +344,17 @@ }, "/get_allowed_conflations": { "get": { + "summary": "Return a list of named conflations.", "tags": ["metadata"], - "description": "Returns a list of allowed conflation options", + "description": "Returns the conflations this instance can apply. Conflation combines cliques on the fly:
  1. GeneProtein merges protein-coding genes with the proteins they encode. The gene(s) always appear first in the combined clique.
  2. DrugChemical merges chemicals based on their active ingredient. The active ingredient appears before any formulations in the combined clique.
You can read more about conflation in the Babel documentation.", + "operationId": "get_allowed_conflations", "responses": { "200": { + "description": "The conflations this instance supports", "content": { "application/json": { - "schema": { - "example": { - "conflations": [ - "GeneProtein", - "DrugChemical" - ], - "type": "object", - "description": "Results", - "summary": "Return a list of named conflations.", - "tags": [ - "Interfaces" - ] - } - } + "schema": {"$ref": "#/components/schemas/ConflationList"}, + "example": {"conflations": ["GeneProtein", "DrugChemical"]} } } } @@ -370,2839 +364,255 @@ }, "components": { "schemas": { - "AboveOrBelowEnum": { - "type": "string", - "enum": [ - "above", - "below" - ], - "title": "AboveOrBelowEnum", - "description": "'above' or 'below'." - }, - "Analysis": { + "CurieList": { + "title": "CurieList", + "description": "Curie list input model", + "type": "object", + "required": ["curies"], "properties": { - "resource_id": { - "type": "string", - "title": "resource infores" - }, - "edge_bindings": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableMapping_str__HashableSet_EdgeBinding__" - } - ], - "title": "list of edge bindings" + "curies": { + "title": "List of CURIEs to normalize", + "type": "array", + "items": {"type": "string"}, + "minItems": 1 }, - "score": { - "type": "number", - "format": "float", - "title": "Score" + "conflate": { + "title": "Whether to apply gene/protein conflation", + "type": "boolean", + "default": true }, - "support_graphs": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_str_" - } - ], - "title": "Support Graphs", - "nullable": true + "drug_chemical_conflate": { + "title": "Whether to apply drug/chemical conflation", + "type": "boolean", + "default": false }, - "scoring_method": { - "type": "string", - "title": "Scoring Method", - "nullable": true + "description": { + "title": "Whether to return CURIE descriptions when possible", + "type": "boolean", + "default": false }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_Attribute_" - } - ], - "title": "Attributes", - "nullable": true + "individual_types": { + "title": "Whether to return individual types for equivalent identifiers", + "type": "boolean", + "default": false } }, - "type": "object", - "required": [ - "resource_id", - "edge_bindings" - ], - "title": "analysis", - "description": "Analysis." + "example": { + "curies": ["MESH:D014867", "NCIT:C34373"], + "conflate": true, + "drug_chemical_conflate": false, + "description": false, + "individual_types": false + } }, - "AnnotateEdgesParameters": { - "properties": { - "attributes": { - "$ref": "#/components/schemas/HashableSequence_str_" - } - }, + "Identifier": { + "title": "Identifier", + "description": "A CURIE with its label, and optionally its description and Biolink type", "type": "object", - "title": "AnnotateEdgesParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "AnnotateNodesParameters": { + "required": ["identifier"], "properties": { - "attributes": { - "$ref": "#/components/schemas/HashableSequence_str_" - } - }, - "type": "object", - "title": "AnnotateNodesParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "AscOrDescEnum": { - "type": "string", - "enum": [ - "ascending", - "descending" - ], - "title": "AscOrDescEnum", - "description": "'ascending' or 'descending'." + "identifier": {"type": "string"}, + "label": {"type": "string"}, + "description": {"type": "string", "description": "Only when `description` was requested"}, + "type": {"type": "string", "description": "Only when `individual_types` was requested"} + } }, - "AsyncQuery": { - "properties": { - "callback": { - "type": "string", - "pattern": "^https?://", - "format": "uri", - "title": "Callback" - }, - "message": { - "allOf": [ - { - "$ref": "#/components/schemas/Message" - } - ], - "title": "message" - }, - "log_level": { - "allOf": [ - { - "$ref": "#/components/schemas/LogLevel" - } - ], - "title": "log_level", - "nullable": true - }, - "workflow": { - "$ref": "#/components/schemas/Workflow" - } - }, + "NormalizedNode": { + "title": "NormalizedNode", + "description": "The clique an input CURIE belongs to", "type": "object", - "required": [ - "callback", - "message" - ], - "title": "query", - "description": "AsyncQuery.", - "x-body-name": "request_body" - }, - "Attribute": { + "required": ["id", "equivalent_identifiers", "type", "taxa"], "properties": { - "attribute_type_id": { - "type": "string", - "title": "type" - }, - "value": { - "title": "value" - }, - "value_type_id": { - "type": "string", - "title": "value_type_id", - "nullable": true + "id": { + "description": "The preferred identifier for this clique", + "$ref": "#/components/schemas/Identifier" }, - "original_attribute_name": { - "type": "string", - "title": "Original Attribute Name", - "nullable": true + "equivalent_identifiers": { + "type": "array", + "items": {"$ref": "#/components/schemas/Identifier"} }, - "value_url": { - "type": "string", - "title": "Value Url", - "nullable": true + "type": { + "description": "Biolink classes for this clique, most specific first", + "type": "array", + "items": {"type": "string"} }, - "attribute_source": { - "type": "string", - "title": "Attribute Source", - "nullable": true + "information_content": { + "type": "number", + "description": "Omitted when Babel has no information content for this clique" }, - "description": { - "type": "string", - "title": "Description", - "nullable": true + "descriptions": { + "type": "array", + "items": {"type": "string"}, + "description": "Only when `description` was requested" }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_SubAttribute_" - } - ], - "title": "Attributes", - "nullable": true + "taxa": { + "type": "array", + "items": {"type": "string"}, + "description": "NCBITaxon CURIEs this clique is associated with" } - }, - "additionalProperties": false, + } + }, + "NormalizedNodes": { + "title": "NormalizedNodes", + "description": "One normalized node per input CURIE, keyed by the input CURIE; `null` when the CURIE is unknown", "type": "object", - "required": [ - "attribute_type_id", - "value" - ], - "title": "Attribute", - "description": "Node/edge attribute." + "additionalProperties": { + "oneOf": [ + {"$ref": "#/components/schemas/NormalizedNode"}, + {"type": "null"} + ] + } }, - "AttributeConstraint": { + "SetIDQuery": { + "title": "SetIDQuery", + "type": "object", + "required": ["curies"], "properties": { - "name": { - "type": "string", - "title": "name", - "nullable": false - }, - "id": { - "type": "string", - "title": "id", - "nullable": false - }, - "not": { - "type": "boolean", - "title": "not", - "default": false - }, - "operator": { - "allOf": [ - { - "$ref": "#/components/schemas/Operator" - } - ], - "title": "operator" - }, - "value": { - "title": "value" - }, - "unit_id": { - "title": "unit_id" + "curies": { + "title": "Set of CURIEs", + "type": "array", + "items": {"type": "string"}, + "minItems": 1 }, - "unit_name": { - "title": "unit_name" + "conflations": { + "title": "Conflations to apply; see /get_allowed_conflations", + "type": "array", + "items": {"type": "string"}, + "default": [] } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "name", - "id", - "operator", - "value" - ], - "title": "AttributeConstraint", - "description": "QNode or QEdge attribute constraint." + } }, - "AuxiliaryGraph": { + "SetIDResponse": { + "title": "SetIDResponse", + "type": "object", + "required": ["curies", "conflations", "error", "normalized_curies", "normalized_string", "setid"], "properties": { - "edges": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_str_" - } - ], - "title": "edges in auxiliary graph" + "curies": {"type": "array", "items": {"type": "string"}}, + "conflations": {"type": "array", "items": {"type": "string"}}, + "error": {"type": ["string", "null"], "description": "Set when the conflations are not all allowed"}, + "normalized_curies": { + "type": ["array", "null"], + "items": {"type": "string"}, + "description": "The preferred identifiers, deduplicated and sorted; unknown CURIEs pass through unchanged" }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_Attribute_" - } - ], - "title": "Attributes", - "nullable": true - } + "normalized_string": {"type": ["string", "null"], "description": "`normalized_curies` joined with `||`"}, + "setid": {"type": ["string", "null"], "description": "UUID v5 of `normalized_string`, prefixed `uuid:`"} }, - "type": "object", - "required": [ - "edges" - ], - "title": "auxiliary graph", - "description": "Auxiliary Graph" + "example": { + "curies": ["MESH:D014867", "NCIT:C34373", "UNII:63M8RYN44N", "RUBBISH:1234"], + "conflations": ["GeneProtein", "DrugChemical"], + "error": null, + "normalized_curies": ["CHEBI:15377", "MONDO:0004976", "RUBBISH:1234"], + "normalized_string": "CHEBI:15377||MONDO:0004976||RUBBISH:1234", + "setid": "uuid:771d3c09-9a8c-5c46-8b85-97f481a90d40" + } }, - "AuxiliaryGraphs": { - "$ref": "#/components/schemas/HashableMapping_str__AuxiliaryGraph_", - "title": "auxiliary graphs", - "description": "Auxiliary Graphs" + "SemanticTypes": { + "title": "SemanticTypes", + "type": "object", + "required": ["semantic_types"], + "properties": { + "semantic_types": { + "type": "object", + "required": ["types"], + "properties": { + "types": { + "type": "array", + "items": {"type": "string"} + } + } + } + } }, "ConflationList": { + "title": "ConflationList", + "type": "object", + "required": ["conflations"], "properties": { "conflations": { - "items": {}, "type": "array", - "title": "Conflations" + "items": {"type": "string"} } - }, - "type": "object", - "required": [ - "conflations" - ], - "title": "ConflationList" - }, - "CurieList": { - "properties": { - "curies": { - "items": { - "type": "string" - }, - "type": "array", - "minItems": 1, - "title": "List of CURIEs to normalize" - }, - "conflate": { - "type": "boolean", - "title": "Whether to apply gene/protein conflation", - "default": true - }, - "description": { - "type": "boolean", - "title": "Whether to return CURIE descriptions when possible", - "default": false - }, - "drug_chemical_conflate": { - "type": "boolean", - "title": "Whether to apply drug/chemical conflation", - "default": false - }, - "individual_types": { - "type": "boolean", - "title": "Whether to return individual types for equivalent identifiers", - "default": false - } - }, - "type": "object", - "required": [ - "curies" - ], - "title": "CurieList", - "description": "Curie list input model", - "example": { - "curies": [ - "MESH:D014867", - "NCIT:C34373" - ], - "conflate": true, - "description": false, - "drug_chemical_conflate": false } - }, - "CuriePivot": { - "properties": { - "curie_prefix": { - "additionalProperties": { - "type": "string" + } + }, + "examples": { + "NormalizedNodes": { + "summary": "Water, ALS and an unknown CURIE, with descriptions and individual types requested", + "value": { + "MESH:D014867": { + "id": { + "identifier": "CHEBI:15377", + "label": "Water", + "description": "An oxygen hydride consisting of an oxygen atom that is covalently bonded to two hydrogen atoms" }, - "type": "object", - "title": "Curie Prefix" - } - }, - "type": "object", - "required": [ - "curie_prefix" - ], - "title": "CuriePivot" - }, - "Edge": { - "properties": { - "subject": { - "type": "string", - "title": "subject node id" - }, - "object": { - "type": "string", - "title": "object node id" - }, - "predicate": { - "type": "string", - "pattern": "^biolink:[a-z][a-z_]*$", - "title": "edge predicate" - }, - "sources": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_RetrievalSource_" - } - ], - "title": "list of source retrievals" - }, - "qualifiers": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_Qualifier_" - } - ], - "title": "Qualifiers", - "nullable": true - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_Attribute_" - } - ], - "title": "Attributes", - "nullable": true - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "subject", - "object", - "predicate", - "sources" - ], - "title": "knowledge-graph edge", - "description": "Knowledge graph edge." - }, - "EdgeBinding": { - "properties": { - "id": { - "type": "string", - "title": "knowledge graph id" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_Attribute_" - } - ], - "title": "Attributes", - "nullable": true - } - }, - "type": "object", - "required": [ - "id" - ], - "title": "edge binding", - "description": "Edge binding.", - "example": { - "id": "string" - } - }, - "EnrichResultsParameters": { - "properties": { - "pvalue_threshold": { - "type": "number", - "maximum": 1.0, - "minimum": 0.0, - "title": "Pvalue Threshold", - "default": 0.000001 - }, - "qnode_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" - }, - "predicates_to_exclude": { - "$ref": "#/components/schemas/HashableSequence_BiolinkPredicate_" - } - }, - "type": "object", - "title": "EnrichResultsParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "FillAllowParameters": { - "properties": { - "allowlist": { - "$ref": "#/components/schemas/HashableSequence_str_" - }, - "qedge_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" - } - }, - "additionalProperties": false, - "type": "object", - "title": "FillAllowParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "FillDenyParameters": { - "properties": { - "denylist": { - "$ref": "#/components/schemas/HashableSequence_str_" - }, - "qedge_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" - } - }, - "additionalProperties": false, - "type": "object", - "title": "FillDenyParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "FillParameters": { - "anyOf": [ - { - "$ref": "#/components/schemas/FillAllowParameters" - }, - { - "$ref": "#/components/schemas/FillDenyParameters" - } - ], - "title": "FillParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "FilterKgraphContinuousKedgeAttributeParameters": { - "properties": { - "edge_attribute": { - "type": "string", - "title": "Edge Attribute" - }, - "threshold": { - "type": "number", - "title": "Threshold" - }, - "remove_above_or_below": { - "$ref": "#/components/schemas/AboveOrBelowEnum" - }, - "qedge_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" - }, - "qnode_keys": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_str_" - } + "descriptions": [ + "An oxygen hydride consisting of an oxygen atom that is covalently bonded to two hydrogen atoms" ], - "title": "Qnode Keys", - "default": [] - } - }, - "type": "object", - "required": [ - "edge_attribute", - "threshold", - "remove_above_or_below" - ], - "title": "FilterKgraphContinuousKedgeAttributeParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "FilterKgraphDiscreteKedgeAttributeParameters": { - "properties": { - "edge_attribute": { - "type": "string", - "title": "Edge Attribute" - }, - "remove_value": { - "title": "Remove Value" - }, - "qedge_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" - }, - "qnode_keys": { - "allOf": [ + "equivalent_identifiers": [ { - "$ref": "#/components/schemas/HashableSequence_str_" - } - ], - "title": "Qnode Keys", - "default": [] - } - }, - "type": "object", - "required": [ - "edge_attribute" - ], - "title": "FilterKgraphDiscreteKedgeAttributeParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "FilterKgraphDiscreteKnodeAttributeParameters": { - "properties": { - "node_attribute": { - "type": "string", - "title": "Node Attribute" - }, - "remove_value": { - "title": "Remove Value" - }, - "qnode_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" - } - }, - "type": "object", - "required": [ - "node_attribute" - ], - "title": "FilterKgraphDiscreteKnodeAttributeParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "FilterKgraphPercentileParameters": { - "properties": { - "edge_attribute": { - "type": "string", - "title": "Edge Attribute" - }, - "threshold": { - "type": "number", - "maximum": 100.0, - "minimum": 0.0, - "title": "Threshold", - "default": 95 - }, - "remove_above_or_below": { - "allOf": [ + "identifier": "CHEBI:15377", + "label": "water", + "description": "An oxygen hydride consisting of an oxygen atom that is covalently bonded to two hydrogen atoms", + "type": "biolink:SmallMolecule" + }, { - "$ref": "#/components/schemas/AboveOrBelowEnum" + "identifier": "CHEBI:44701", + "label": "", + "type": "biolink:SmallMolecule" } ], - "default": "below" - }, - "qedge_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" - }, - "qnode_keys": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_str_" - } + "type": [ + "biolink:SmallMolecule", + "biolink:MolecularEntity", + "biolink:OntologyClass", + "biolink:ChemicalEntity", + "biolink:PhysicalEssence", + "biolink:ChemicalOrDrugOrTreatment", + "biolink:ChemicalEntityOrGeneOrGeneProduct", + "biolink:ChemicalEntityOrProteinOrPolypeptide", + "biolink:NamedThing", + "biolink:PhysicalEssenceOrOccurrent" ], - "title": "Qnode Keys", - "default": [] - } - }, - "type": "object", - "required": [ - "edge_attribute" - ], - "title": "FilterKgraphPercentileParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "FilterKgraphStdDevParameters": { - "properties": { - "edge_attribute": { - "type": "string", - "title": "Edge Attribute" + "information_content": 47.5, + "taxa": [] }, - "plus_or_minus_std_dev": { - "allOf": [ - { - "$ref": "#/components/schemas/PlusOrMinusEnum" - } + "NCIT:C34373": { + "id": { + "identifier": "MONDO:0004976", + "label": "amyotrophic lateral sclerosis", + "description": "Amyotrophic lateral sclerosis (ALS) is a neurodegenerative disease characterized by progressive muscular paralysis reflecting degeneration of motor neurons in the primary motor cortex, corticospinal tracts, brainstem and spinal cord." + }, + "descriptions": [ + "Amyotrophic lateral sclerosis (ALS) is a neurodegenerative disease characterized by progressive muscular paralysis reflecting degeneration of motor neurons in the primary motor cortex, corticospinal tracts, brainstem and spinal cord." ], - "default": "plus" - }, - "num_sigma": { - "type": "number", - "minimum": 0.0, - "title": "Num Sigma", - "default": 1 - }, - "remove_above_or_below": { - "allOf": [ + "equivalent_identifiers": [ { - "$ref": "#/components/schemas/AboveOrBelowEnum" - } - ], - "default": "below" - }, - "qedge_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" - }, - "qnode_keys": { - "allOf": [ + "identifier": "MONDO:0004976", + "label": "amyotrophic lateral sclerosis", + "description": "Amyotrophic lateral sclerosis (ALS) is a neurodegenerative disease characterized by progressive muscular paralysis reflecting degeneration of motor neurons in the primary motor cortex, corticospinal tracts, brainstem and spinal cord.", + "type": "biolink:Disease" + }, { - "$ref": "#/components/schemas/HashableSequence_str_" + "identifier": "DOID:332", + "label": "amyotrophic lateral sclerosis", + "type": "biolink:Disease" } ], - "title": "Qnode Keys", - "default": [] - } - }, - "type": "object", - "required": [ - "edge_attribute" - ], - "title": "FilterKgraphStdDevParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "FilterKgraphTopNParameters": { - "properties": { - "edge_attribute": { - "type": "string", - "title": "Edge Attribute" - }, - "max_edges": { - "type": "integer", - "maximum": 0.0, - "title": "Max Edges", - "default": 50 - }, - "remove_top_or_bottom": { - "allOf": [ - { - "$ref": "#/components/schemas/TopOrBottomEnum" - } + "type": [ + "biolink:Disease", + "biolink:DiseaseOrPhenotypicFeature", + "biolink:OntologyClass", + "biolink:BiologicalEntity", + "biolink:ThingWithTaxon", + "biolink:NamedThing" ], - "default": "top" - }, - "qedge_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" + "information_content": 74.9, + "taxa": [] }, - "qnode_keys": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_str_" - } - ], - "title": "Qnode Keys", - "default": [] - } - }, - "type": "object", - "required": [ - "edge_attribute" - ], - "title": "FilterKgraphTopNParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "FilterResultsTopNParameters": { - "properties": { - "max_results": { - "type": "integer", - "minimum": 0.0, - "title": "Max Results" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "max_results" - ], - "title": "FilterResultsTopNParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail" - } - }, - "type": "object", - "title": "HTTPValidationError" - }, - "HashableMapping_CURIE__Node_": { - "additionalProperties": { - "$ref": "#/components/schemas/Node" - }, - "type": "object", - "title": "HashableMapping[CURIE, Node]", - "description": "Custom class that implements MutableMapping and is hashable", - "default": {} - }, - "HashableMapping_EdgeIdentifier__Edge_": { - "additionalProperties": { - "$ref": "#/components/schemas/Edge" - }, - "type": "object", - "title": "HashableMapping[EdgeIdentifier, Edge]", - "description": "Custom class that implements MutableMapping and is hashable", - "default": {} - }, - "HashableMapping_str__AuxiliaryGraph_": { - "additionalProperties": { - "$ref": "#/components/schemas/AuxiliaryGraph" - }, - "type": "object", - "title": "HashableMapping[str, AuxiliaryGraph]", - "description": "Custom class that implements MutableMapping and is hashable", - "default": {} - }, - "HashableMapping_str__HashableSet_EdgeBinding__": { - "additionalProperties": { - "$ref": "#/components/schemas/HashableSet_EdgeBinding_" - }, - "type": "object", - "title": "HashableMapping[str, HashableSet[EdgeBinding]]", - "description": "Custom class that implements MutableMapping and is hashable", - "default": {} - }, - "HashableMapping_str__HashableSet_NodeBinding__": { - "additionalProperties": { - "$ref": "#/components/schemas/HashableSet_NodeBinding_" - }, - "type": "object", - "title": "HashableMapping[str, HashableSet[NodeBinding]]", - "description": "Custom class that implements MutableMapping and is hashable", - "default": {} - }, - "HashableMapping_str__QEdge_": { - "additionalProperties": { - "$ref": "#/components/schemas/QEdge" - }, - "type": "object", - "title": "HashableMapping[str, QEdge]", - "description": "Custom class that implements MutableMapping and is hashable", - "default": {} - }, - "HashableMapping_str__QNode_": { - "additionalProperties": { - "$ref": "#/components/schemas/QNode" - }, - "type": "object", - "title": "HashableMapping[str, QNode]", - "description": "Custom class that implements MutableMapping and is hashable", - "default": {} - }, - "HashableSequence_AttributeConstraint_": { - "items": { - "$ref": "#/components/schemas/AttributeConstraint" - }, - "type": "array", - "title": "HashableSequence[AttributeConstraint]", - "description": "Custom class that implements MutableSequence and is hashable", - "default": [] - }, - "HashableSequence_BiolinkEntity_": { - "items": { - "type": "string", - "pattern": "^biolink:[A-Z][a-zA-Z]*$" - }, - "type": "array", - "title": "HashableSequence[BiolinkEntity]", - "description": "Custom class that implements MutableSequence and is hashable", - "default": [] - }, - "HashableSequence_BiolinkPredicate_": { - "items": { - "type": "string", - "pattern": "^biolink:[a-z][a-z_]*$" - }, - "type": "array", - "title": "HashableSequence[BiolinkPredicate]", - "description": "Custom class that implements MutableSequence and is hashable", - "default": [] - }, - "HashableSequence_CURIE_": { - "items": { - "type": "string" - }, - "type": "array", - "title": "HashableSequence[CURIE]", - "description": "Custom class that implements MutableSequence and is hashable", - "default": [] - }, - "HashableSequence_Operation_": { - "items": { - "$ref": "#/components/schemas/Operation" - }, - "type": "array", - "title": "HashableSequence[Operation]", - "description": "Custom class that implements MutableSequence and is hashable", - "default": [] - }, - "HashableSequence_QualifierConstraint_": { - "items": { - "$ref": "#/components/schemas/QualifierConstraint" - }, - "type": "array", - "title": "HashableSequence[QualifierConstraint]", - "description": "Custom class that implements MutableSequence and is hashable", - "default": [] - }, - "HashableSequence_Qualifier_": { - "items": { - "$ref": "#/components/schemas/Qualifier" - }, - "type": "array", - "title": "HashableSequence[Qualifier]", - "description": "Custom class that implements MutableSequence and is hashable", - "default": [] - }, - "HashableSequence_RecursiveAttribute_": { - "items": { - "$ref": "#/components/schemas/RecursiveAttribute" - }, - "type": "array", - "title": "HashableSequence[RecursiveAttribute]", - "description": "Custom class that implements MutableSequence and is hashable", - "default": [] - }, - "HashableSequence_Result_": { - "items": { - "$ref": "#/components/schemas/Result" - }, - "type": "array", - "title": "HashableSequence[Result]", - "description": "Custom class that implements MutableSequence and is hashable", - "default": [] - }, - "HashableSequence_SubAttribute_": { - "items": { - "$ref": "#/components/schemas/SubAttribute" - }, - "type": "array", - "title": "HashableSequence[SubAttribute]", - "description": "Custom class that implements MutableSequence and is hashable", - "default": [] - }, - "HashableSequence_str_": { - "items": { - "type": "string" - }, - "type": "array", - "title": "HashableSequence[str]", - "description": "Custom class that implements MutableSequence and is hashable", - "default": [] - }, - "HashableSet_Analysis_": { - "items": { - "$ref": "#/components/schemas/Analysis" - }, - "type": "array", - "uniqueItems": true, - "title": "HashableSet[Analysis]", - "description": "Custom class that implements MutableSet and is hashable", - "default": [] - }, - "HashableSet_Attribute_": { - "items": { - "$ref": "#/components/schemas/Attribute" - }, - "type": "array", - "uniqueItems": true, - "title": "HashableSet[Attribute]", - "description": "Custom class that implements MutableSet and is hashable", - "default": [] - }, - "HashableSet_BiolinkEntity_": { - "items": { - "type": "string", - "pattern": "^biolink:[A-Z][a-zA-Z]*$" - }, - "type": "array", - "uniqueItems": true, - "title": "HashableSet[BiolinkEntity]", - "description": "Custom class that implements MutableSet and is hashable", - "default": [] - }, - "HashableSet_CURIE_": { - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": true, - "title": "HashableSet[CURIE]", - "description": "Custom class that implements MutableSet and is hashable", - "default": [] - }, - "HashableSet_EdgeBinding_": { - "items": { - "$ref": "#/components/schemas/EdgeBinding" - }, - "type": "array", - "uniqueItems": true, - "title": "HashableSet[EdgeBinding]", - "description": "Custom class that implements MutableSet and is hashable", - "default": [] - }, - "HashableSet_NodeBinding_": { - "items": { - "$ref": "#/components/schemas/NodeBinding" - }, - "type": "array", - "uniqueItems": true, - "title": "HashableSet[NodeBinding]", - "description": "Custom class that implements MutableSet and is hashable", - "default": [] - }, - "HashableSet_Qualifier_": { - "items": { - "$ref": "#/components/schemas/Qualifier" - }, - "type": "array", - "uniqueItems": true, - "title": "HashableSet[Qualifier]", - "description": "Custom class that implements MutableSet and is hashable", - "default": [] - }, - "HashableSet_RetrievalSource_": { - "items": { - "$ref": "#/components/schemas/RetrievalSource" - }, - "type": "array", - "uniqueItems": true, - "title": "HashableSet[RetrievalSource]", - "description": "Custom class that implements MutableSet and is hashable", - "default": [] - }, - "HashableSet_str_": { - "items": { - "type": "string" - }, - "type": "array", - "uniqueItems": true, - "title": "HashableSet[str]", - "description": "Custom class that implements MutableSet and is hashable", - "default": [] - }, - "KnowledgeGraph": { - "properties": { - "nodes": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableMapping_CURIE__Node_" - } - ], - "title": "nodes" - }, - "edges": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableMapping_EdgeIdentifier__Edge_" - } - ], - "title": "edges" - } - }, - "type": "object", - "required": [ - "nodes", - "edges" - ], - "title": "knowledge graph", - "description": "Knowledge graph." - }, - "KnowledgeType": { - "type": "string", - "enum": [ - "lookup", - "inferred" - ], - "title": "KnowledgeType", - "description": "Knowledge Type." - }, - "LogLevel": { - "$ref": "#/components/schemas/LogLevelEnum", - "title": "LogLevel", - "description": "Log level." - }, - "LogLevelEnum": { - "type": "string", - "enum": [ - "ERROR", - "WARNING", - "INFO", - "DEBUG" - ], - "title": "LogLevelEnum", - "description": "Log level." - }, - "Message": { - "properties": { - "query_graph": { - "allOf": [ - { - "$ref": "#/components/schemas/QueryGraph" - } - ], - "title": "query graph", - "nullable": true - }, - "knowledge_graph": { - "allOf": [ - { - "$ref": "#/components/schemas/KnowledgeGraph" - } - ], - "title": "knowledge graph", - "nullable": true - }, - "results": { - "allOf": [ - { - "$ref": "#/components/schemas/Results" - } - ], - "title": "list of results", - "nullable": true - }, - "auxiliary_graphs": { - "allOf": [ - { - "$ref": "#/components/schemas/AuxiliaryGraphs" - } - ], - "title": "dict of auxiliary graphs", - "nullable": true - } - }, - "additionalProperties": false, - "type": "object", - "title": "message", - "description": "Message." - }, - "Node": { - "properties": { - "categories": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_BiolinkEntity_" - } - ], - "title": "categories", - "nullable": true - }, - "name": { - "type": "string", - "title": "Name", - "nullable": true - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_Attribute_" - } - ], - "title": "Attributes", - "nullable": true - } - }, - "additionalProperties": false, - "type": "object", - "title": "knowledge-graph node", - "description": "Knowledge graph node.", - "example": { - "category": "string" - } - }, - "NodeBinding": { - "properties": { - "id": { - "type": "string", - "title": "knowledge graph id" - }, - "query_id": { - "type": "string", - "title": "query graph id" - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_Attribute_" - } - ], - "title": "Attributes", - "nullable": true - } - }, - "type": "object", - "required": [ - "id" - ], - "title": "node binding", - "description": "Node binding.", - "example": { - "id": "x:string" - } - }, - "Operation": { - "anyOf": [ - { - "$ref": "#/components/schemas/OperationAnnotate" - }, - { - "$ref": "#/components/schemas/OperationAnnotateEdges" - }, - { - "$ref": "#/components/schemas/OperationAnnotateNodes" - }, - { - "$ref": "#/components/schemas/OperationBind" - }, - { - "$ref": "#/components/schemas/OperationCompleteResults" - }, - { - "$ref": "#/components/schemas/OperationEnrichResults" - }, - { - "$ref": "#/components/schemas/OperationFill" - }, - { - "$ref": "#/components/schemas/OperationFilterKgraph" - }, - { - "$ref": "#/components/schemas/OperationFilterKgraphContinuousKedgeAttribute" - }, - { - "$ref": "#/components/schemas/OperationFilterKgraphDiscreteKedgeAttribute" - }, - { - "$ref": "#/components/schemas/OperationFilterKgraphDiscreteKnodeAttribute" - }, - { - "$ref": "#/components/schemas/OperationFilterKgraphOrphans" - }, - { - "$ref": "#/components/schemas/OperationFilterKgraphPercentile" - }, - { - "$ref": "#/components/schemas/OperationFilterKgraphStdDev" - }, - { - "$ref": "#/components/schemas/OperationFilterKgraphTopN" - }, - { - "$ref": "#/components/schemas/OperationFilterResults" - }, - { - "$ref": "#/components/schemas/OperationFilterResultsTopN" - }, - { - "$ref": "#/components/schemas/OperationLookup" - }, - { - "$ref": "#/components/schemas/OperationOverlay" - }, - { - "$ref": "#/components/schemas/OperationOverlayComputeJaccard" - }, - { - "$ref": "#/components/schemas/OperationOverlayComputeNgd" - }, - { - "$ref": "#/components/schemas/OperationOverlayConnectKnodes" - }, - { - "$ref": "#/components/schemas/OperationOverlayFisherExactTest" - }, - { - "$ref": "#/components/schemas/OperationRestate" - }, - { - "$ref": "#/components/schemas/OperationScore" - }, - { - "$ref": "#/components/schemas/OperationSortResults" - }, - { - "$ref": "#/components/schemas/OperationSortResultsEdgeAttribute" - }, - { - "$ref": "#/components/schemas/OperationSortResultsNodeAttribute" - }, - { - "$ref": "#/components/schemas/OperationSortResultsScore" - } - ], - "title": "Operation", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationAnnotate": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/annotate" - }, - "parameters": { - "title": "Parameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationAnnotate", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationAnnotateEdges": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/annotate_edges" - }, - "parameters": { - "$ref": "#/components/schemas/AnnotateEdgesParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationAnnotateEdges", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationAnnotateNodes": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/annotate_nodes" - }, - "parameters": { - "$ref": "#/components/schemas/AnnotateNodesParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationAnnotateNodes", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationBind": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/bind" - }, - "parameters": { - "title": "Parameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationBind", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationCompleteResults": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/complete_results" - }, - "parameters": { - "title": "Parameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationCompleteResults", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationEnrichResults": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/enrich_results" - }, - "parameters": { - "$ref": "#/components/schemas/EnrichResultsParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationEnrichResults", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationFill": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/fill" - }, - "parameters": { - "$ref": "#/components/schemas/FillParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationFill", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationFilterKgraph": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/filter_kgraph" - }, - "parameters": { - "title": "Parameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationFilterKgraph", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationFilterKgraphContinuousKedgeAttribute": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/filter_kgraph_continuous_kedge_attribute" - }, - "parameters": { - "$ref": "#/components/schemas/FilterKgraphContinuousKedgeAttributeParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id", - "parameters" - ], - "title": "OperationFilterKgraphContinuousKedgeAttribute", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationFilterKgraphDiscreteKedgeAttribute": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/filter_kgraph_discrete_kedge_attribute" - }, - "parameters": { - "$ref": "#/components/schemas/FilterKgraphDiscreteKedgeAttributeParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id", - "parameters" - ], - "title": "OperationFilterKgraphDiscreteKedgeAttribute", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationFilterKgraphDiscreteKnodeAttribute": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/filter_kgraph_discrete_knode_attribute" - }, - "parameters": { - "$ref": "#/components/schemas/FilterKgraphDiscreteKnodeAttributeParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id", - "parameters" - ], - "title": "OperationFilterKgraphDiscreteKnodeAttribute", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationFilterKgraphOrphans": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/filter_kgraph_orphans" - }, - "parameters": { - "title": "Parameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationFilterKgraphOrphans", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationFilterKgraphPercentile": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/filter_kgraph_percentile" - }, - "parameters": { - "$ref": "#/components/schemas/FilterKgraphPercentileParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id", - "parameters" - ], - "title": "OperationFilterKgraphPercentile", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationFilterKgraphStdDev": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/filter_kgraph_std_dev" - }, - "parameters": { - "$ref": "#/components/schemas/FilterKgraphStdDevParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id", - "parameters" - ], - "title": "OperationFilterKgraphStdDev", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationFilterKgraphTopN": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/filter_kgraph_top_n" - }, - "parameters": { - "$ref": "#/components/schemas/FilterKgraphTopNParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id", - "parameters" - ], - "title": "OperationFilterKgraphTopN", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationFilterResults": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/filter_results" - }, - "parameters": { - "title": "Parameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationFilterResults", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationFilterResultsTopN": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/filter_results_top_n" - }, - "parameters": { - "$ref": "#/components/schemas/FilterResultsTopNParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationFilterResultsTopN", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationLookup": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/lookup" - }, - "parameters": { - "title": "Parameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationLookup", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationOverlay": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/overlay" - }, - "parameters": { - "title": "Parameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationOverlay", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationOverlayComputeJaccard": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/overlay_compute_jaccard" - }, - "parameters": { - "$ref": "#/components/schemas/OverlayComputeJaccardParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id", - "parameters" - ], - "title": "OperationOverlayComputeJaccard", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationOverlayComputeNgd": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/overlay_compute_ngd" - }, - "parameters": { - "$ref": "#/components/schemas/OverlayComputeNgdParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id", - "parameters" - ], - "title": "OperationOverlayComputeNgd", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationOverlayConnectKnodes": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/overlay_connect_knodes" - }, - "parameters": { - "title": "Parameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationOverlayConnectKnodes", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationOverlayFisherExactTest": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/overlay_fisher_exact_test" - }, - "parameters": { - "$ref": "#/components/schemas/OverlayFisherExactTestParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id", - "parameters" - ], - "title": "OperationOverlayFisherExactTest", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationRestate": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/restate" - }, - "parameters": { - "title": "Parameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationRestate", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationScore": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/score" - }, - "parameters": { - "title": "Parameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationScore", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationSortResults": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/sort_results" - }, - "parameters": { - "title": "Parameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id" - ], - "title": "OperationSortResults", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationSortResultsEdgeAttribute": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/sort_results_edge_attribute" - }, - "parameters": { - "$ref": "#/components/schemas/SortResultsEdgeAttributeParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id", - "parameters" - ], - "title": "OperationSortResultsEdgeAttribute", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationSortResultsNodeAttribute": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/sort_results_node_attribute" - }, - "parameters": { - "$ref": "#/components/schemas/SortResultsNodeAttributeParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id", - "parameters" - ], - "title": "OperationSortResultsNodeAttribute", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OperationSortResultsScore": { - "properties": { - "runner_parameters": { - "$ref": "#/components/schemas/RunnerParameters" - }, - "id": { - "$ref": "#/components/schemas/sort_results_score" - }, - "parameters": { - "$ref": "#/components/schemas/SortResultsScoreParameters" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "id", - "parameters" - ], - "title": "OperationSortResultsScore", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "Operator": { - "type": "string", - "enum": [ - "==", - "===", - ">", - "<", - "matches" - ], - "title": "Operator", - "description": "Operator." - }, - "OverlayComputeJaccardParameters": { - "properties": { - "intermediate_node_key": { - "type": "string", - "title": "Intermediate Node Key" - }, - "end_node_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" - }, - "virtual_relation_label": { - "type": "string", - "title": "Virtual Relation Label" - } - }, - "type": "object", - "required": [ - "intermediate_node_key", - "end_node_keys", - "virtual_relation_label" - ], - "title": "OverlayComputeJaccardParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OverlayComputeNgdParameters": { - "properties": { - "qnode_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" - }, - "virtual_relation_label": { - "type": "string", - "title": "Virtual Relation Label" - } - }, - "type": "object", - "required": [ - "qnode_keys", - "virtual_relation_label" - ], - "title": "OverlayComputeNgdParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "OverlayFisherExactTestParameters": { - "properties": { - "subject_qnode_key": { - "type": "string", - "title": "Subject Qnode Key" - }, - "object_qnode_key": { - "type": "string", - "title": "Object Qnode Key" - }, - "virtual_relation_label": { - "type": "string", - "title": "Virtual Relation Label" - }, - "rel_edge_key": { - "type": "string", - "title": "Rel Edge Key" - } - }, - "type": "object", - "required": [ - "subject_qnode_key", - "object_qnode_key", - "virtual_relation_label" - ], - "title": "OverlayFisherExactTestParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "PlusOrMinusEnum": { - "type": "string", - "enum": [ - "plus", - "minus" - ], - "title": "PlusOrMinusEnum", - "description": "'plus' or 'minus'." - }, - "QEdge": { - "properties": { - "subject": { - "type": "string", - "title": "subject node id" - }, - "object": { - "type": "string", - "title": "object node id" - }, - "knowledge_type": { - "allOf": [ - { - "$ref": "#/components/schemas/KnowledgeType" - } - ], - "title": "knowledge type" - }, - "predicates": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_BiolinkPredicate_" - } - ], - "title": "predicates", - "nullable": true - }, - "attribute_constraints": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_AttributeConstraint_" - } - ], - "title": "attribute constraints", - "default": { - "__root__": [] - } - }, - "qualifier_constraints": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_QualifierConstraint_" - } - ], - "title": "qualifier constraint", - "default": { - "__root__": [] - } - } - }, - "type": "object", - "required": [ - "subject", - "object" - ], - "title": "query-graph edge", - "description": "Query edge." - }, - "QNode": { - "properties": { - "ids": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_CURIE_" - } - ], - "title": "ids", - "nullable": true - }, - "categories": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_BiolinkEntity_" - } - ], - "title": "categories", - "nullable": true - }, - "is_set": { - "type": "boolean", - "title": "Is Set", - "default": false - }, - "constraints": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_AttributeConstraint_" - } - ], - "title": "attribute constraints", - "default": { - "__root__": [] - } - } - }, - "type": "object", - "title": "query-graph node", - "description": "Query node." - }, - "Qualifier": { - "properties": { - "qualifier_type_id": { - "type": "string", - "pattern": "^biolink:[a-z][a-z_]*$", - "title": "type" - }, - "qualifier_value": { - "type": "string", - "title": "value" - } - }, - "type": "object", - "required": [ - "qualifier_type_id", - "qualifier_value" - ], - "title": "Qualifier", - "description": "Edge qualifier." - }, - "QualifierConstraint": { - "properties": { - "qualifier_set": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_Qualifier_" - } - ], - "title": "qualifier set", - "default": { - "__root__": [] - } - } - }, - "type": "object", - "title": "QualifierConstraint", - "description": "QEdge Qualifier constraint." - }, - "Query": { - "properties": { - "message": { - "allOf": [ - { - "$ref": "#/components/schemas/Message" - } - ], - "title": "message" - }, - "log_level": { - "allOf": [ - { - "$ref": "#/components/schemas/LogLevel" - } - ], - "title": "log_level", - "nullable": true - }, - "workflow": { - "$ref": "#/components/schemas/Workflow" - } - }, - "type": "object", - "required": [ - "message" - ], - "title": "query", - "description": "Request.", - "x-body-name": "request_body" - }, - "QueryGraph": { - "properties": { - "nodes": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableMapping_str__QNode_" - } - ], - "title": "dict of nodes" - }, - "edges": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableMapping_str__QEdge_" - } - ], - "title": "dict of edges" - } - }, - "type": "object", - "required": [ - "nodes", - "edges" - ], - "title": "simple query graph", - "description": "Query graph." - }, - "RecursiveAttribute": { - "properties": { - "attribute_type_id": { - "type": "string", - "title": "type" - }, - "value": { - "title": "value" - }, - "value_type_id": { - "type": "string", - "title": "value_type_id", - "nullable": true - }, - "original_attribute_name": { - "type": "string", - "title": "Original Attribute Name", - "nullable": true - }, - "value_url": { - "type": "string", - "title": "Value Url", - "nullable": true - }, - "attribute_source": { - "type": "string", - "title": "Attribute Source", - "nullable": true - }, - "description": { - "type": "string", - "title": "Description", - "nullable": true - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_RecursiveAttribute_" - } - ], - "title": "Attributes", - "nullable": true - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "attribute_type_id", - "value" - ], - "title": "RecursiveAttribute", - "description": "Attribute subattribute." - }, - "ResourceRoleEnum": { - "type": "string", - "enum": [ - "aggregator_knowledge_source", - "primary_knowledge_source", - "supporting_data_source" - ], - "title": "ResourceRoleEnum", - "description": "Types of resources" - }, - "Result": { - "properties": { - "node_bindings": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableMapping_str__HashableSet_NodeBinding__" - } - ], - "title": "list of node bindings" - }, - "analyses": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_Analysis_" - } - ], - "title": "list of anlysis blocks" - } - }, - "type": "object", - "required": [ - "node_bindings", - "analyses" - ], - "title": "result", - "description": "Result." - }, - "Results": { - "$ref": "#/components/schemas/HashableSequence_Result_", - "title": "results", - "description": "Results." - }, - "RetrievalSource": { - "properties": { - "resource_id": { - "type": "string", - "title": "infores for source" - }, - "resource_role": { - "allOf": [ - { - "$ref": "#/components/schemas/ResourceRoleEnum" - } - ], - "title": "source type" - }, - "upstream_resource_ids": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_CURIE_" - } - ], - "title": "Upstream Resource Ids", - "nullable": true - }, - "source_record_urls": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSet_str_" - } - ], - "title": "Source Record Urls", - "nullable": true - } - }, - "type": "object", - "required": [ - "resource_id", - "resource_role" - ], - "title": "RetrievalSource", - "description": "A component of source retrieval provenance" - }, - "RunnerAllowList": { - "properties": { - "allowlist": { - "$ref": "#/components/schemas/HashableSequence_str_" - }, - "timeout": { - "type": "number", - "title": "Timeout" - } - }, - "additionalProperties": false, - "type": "object", - "title": "RunnerAllowList", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "RunnerDenyList": { - "properties": { - "denylist": { - "$ref": "#/components/schemas/HashableSequence_str_" - }, - "timeout": { - "type": "number", - "title": "Timeout" - } - }, - "additionalProperties": false, - "type": "object", - "title": "RunnerDenyList", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "RunnerParameters": { - "anyOf": [ - { - "$ref": "#/components/schemas/RunnerAllowList" - }, - { - "$ref": "#/components/schemas/RunnerDenyList" - }, - { - "$ref": "#/components/schemas/RunnerTimeout" - } - ], - "title": "RunnerParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "RunnerTimeout": { - "properties": { - "timeout": { - "type": "number", - "title": "Timeout" - } - }, - "additionalProperties": false, - "type": "object", - "title": "RunnerTimeout", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "SemanticTypes": { - "properties": { - "semantic_types": { - "additionalProperties": { - "items": {}, - "type": "array" - }, - "type": "object", - "title": "Semantic Types" - } - }, - "type": "object", - "required": [ - "semantic_types" - ], - "title": "SemanticTypes", - "example": { - "semantic_types": { - "types": [ - "biolink:CellularComponent", - "biolink:NamedThing", - "etc." - ] - } - } - }, - "SemanticTypesInput": { - "properties": { - "semantic_types": { - "items": { - "type": "string" - }, - "type": "array", - "title": "list of semantic types" - } - }, - "type": "object", - "required": [ - "semantic_types" - ], - "title": "SemanticTypesInput", - "description": "Semantic type input model", - "example": { - "semantic_types": [ - "biolink:ChemicalEntity", - "biolink:AnatomicalEntity" - ] + "RUBBISH:1234": null } - }, - "SetIDQuery": { - "properties": { - "curies": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Curies", - "description": "Set of curies to normalize", - "example": [ - "MESH:D014867", - "NCIT:C34373" - ] - }, - "conflations": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Conflations", - "description": "Set of conflations to apply", - "default": [], - "example": [ - "GeneProtein", - "DrugChemical" - ] - } - }, - "type": "object", - "required": [ - "curies" - ], - "title": "SetIDQuery", - "description": "Query for a single SetID. Includes a set of CURIEs as well as a set of conflations to apply. " - }, - "SetIDResponse": { - "properties": { - "curies": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Curies" - }, - "conflations": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Conflations" - }, - "error": { - "type": "string", - "title": "Error" - }, - "normalized_curies": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Normalized Curies" - }, - "normalized_string": { - "type": "string", - "title": "Normalized String" - }, - "setid": { - "type": "string", - "title": "Setid" - } - }, - "type": "object", - "required": [ - "curies", - "conflations" - ], - "title": "SetIDResponse" - }, - "SortResultsEdgeAttributeParameters": { - "properties": { - "edge_attribute": { - "type": "string", - "title": "Edge Attribute" - }, - "ascending_or_descending": { - "$ref": "#/components/schemas/AscOrDescEnum" - }, - "qedge_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" - } - }, - "type": "object", - "required": [ - "edge_attribute", - "ascending_or_descending" - ], - "title": "SortResultsEdgeAttributeParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "SortResultsNodeAttributeParameters": { - "properties": { - "node_attribute": { - "type": "string", - "title": "Node Attribute" - }, - "ascending_or_descending": { - "$ref": "#/components/schemas/AscOrDescEnum" - }, - "qnode_keys": { - "$ref": "#/components/schemas/HashableSequence_str_" - } - }, - "type": "object", - "required": [ - "node_attribute", - "ascending_or_descending" - ], - "title": "SortResultsNodeAttributeParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "SortResultsScoreParameters": { - "properties": { - "ascending_or_descending": { - "$ref": "#/components/schemas/AscOrDescEnum" - } - }, - "type": "object", - "required": [ - "ascending_or_descending" - ], - "title": "SortResultsScoreParameters", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "SubAttribute": { - "properties": { - "attribute_type_id": { - "type": "string", - "title": "type" - }, - "value": { - "title": "value" - }, - "value_type_id": { - "type": "string", - "title": "value_type_id", - "nullable": true - }, - "original_attribute_name": { - "type": "string", - "title": "Original Attribute Name", - "nullable": true - }, - "value_url": { - "type": "string", - "title": "Value Url", - "nullable": true - }, - "attribute_source": { - "type": "string", - "title": "Attribute Source", - "nullable": true - }, - "description": { - "type": "string", - "title": "Description", - "nullable": true - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/HashableSequence_RecursiveAttribute_" - } - ], - "title": "Attributes", - "nullable": true - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "attribute_type_id", - "value" - ], - "title": "SubAttribute", - "description": "Attribute subattribute." - }, - "TopOrBottomEnum": { - "type": "string", - "enum": [ - "top", - "bottom" - ], - "title": "TopOrBottomEnum", - "description": "'top' or 'bottom'." - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - }, - "type": "array", - "title": "Location" - }, - "msg": { - "type": "string", - "title": "Message" - }, - "type": { - "type": "string", - "title": "Error Type" - } - }, - "type": "object", - "required": [ - "loc", - "msg", - "type" - ], - "title": "ValidationError" - }, - "Workflow": { - "$ref": "#/components/schemas/HashableSequence_Operation_", - "title": "Workflow", - "description": "Custom base model for all classes\n\nThis provides hash and equality methods." - }, - "annotate": { - "type": "string", - "enum": [ - "annotate" - ], - "title": "annotate", - "description": "An enumeration." - }, - "annotate_edges": { - "type": "string", - "enum": [ - "annotate_edges" - ], - "title": "annotate_edges", - "description": "An enumeration." - }, - "annotate_nodes": { - "type": "string", - "enum": [ - "annotate_nodes" - ], - "title": "annotate_nodes", - "description": "An enumeration." - }, - "bind": { - "type": "string", - "enum": [ - "bind" - ], - "title": "bind", - "description": "An enumeration." - }, - "complete_results": { - "type": "string", - "enum": [ - "complete_results" - ], - "title": "complete_results", - "description": "An enumeration." - }, - "enrich_results": { - "type": "string", - "enum": [ - "enrich_results" - ], - "title": "enrich_results", - "description": "An enumeration." - }, - "fill": { - "type": "string", - "enum": [ - "fill" - ], - "title": "fill", - "description": "An enumeration." - }, - "filter_kgraph": { - "type": "string", - "enum": [ - "filter_kgraph" - ], - "title": "filter_kgraph", - "description": "An enumeration." - }, - "filter_kgraph_continuous_kedge_attribute": { - "type": "string", - "enum": [ - "filter_kgraph_continuous_kedge_attribute" - ], - "title": "filter_kgraph_continuous_kedge_attribute", - "description": "An enumeration." - }, - "filter_kgraph_discrete_kedge_attribute": { - "type": "string", - "enum": [ - "filter_kgraph_discrete_kedge_attribute" - ], - "title": "filter_kgraph_discrete_kedge_attribute", - "description": "An enumeration." - }, - "filter_kgraph_discrete_knode_attribute": { - "type": "string", - "enum": [ - "filter_kgraph_discrete_knode_attribute" - ], - "title": "filter_kgraph_discrete_knode_attribute", - "description": "An enumeration." - }, - "filter_kgraph_orphans": { - "type": "string", - "enum": [ - "filter_kgraph_orphans" - ], - "title": "filter_kgraph_orphans", - "description": "An enumeration." - }, - "filter_kgraph_percentile": { - "type": "string", - "enum": [ - "filter_kgraph_percentile" - ], - "title": "filter_kgraph_percentile", - "description": "An enumeration." - }, - "filter_kgraph_std_dev": { - "type": "string", - "enum": [ - "filter_kgraph_std_dev" - ], - "title": "filter_kgraph_std_dev", - "description": "An enumeration." - }, - "filter_kgraph_top_n": { - "type": "string", - "enum": [ - "filter_kgraph_top_n" - ], - "title": "filter_kgraph_top_n", - "description": "An enumeration." - }, - "filter_results": { - "type": "string", - "enum": [ - "filter_results" - ], - "title": "filter_results", - "description": "An enumeration." - }, - "filter_results_top_n": { - "type": "string", - "enum": [ - "filter_results_top_n" - ], - "title": "filter_results_top_n", - "description": "An enumeration." - }, - "lookup": { - "type": "string", - "enum": [ - "lookup" - ], - "title": "lookup", - "description": "An enumeration." - }, - "overlay": { - "type": "string", - "enum": [ - "overlay" - ], - "title": "overlay", - "description": "An enumeration." - }, - "overlay_compute_jaccard": { - "type": "string", - "enum": [ - "overlay_compute_jaccard" - ], - "title": "overlay_compute_jaccard", - "description": "An enumeration." - }, - "overlay_compute_ngd": { - "type": "string", - "enum": [ - "overlay_compute_ngd" - ], - "title": "overlay_compute_ngd", - "description": "An enumeration." - }, - "overlay_connect_knodes": { - "type": "string", - "enum": [ - "overlay_connect_knodes" - ], - "title": "overlay_connect_knodes", - "description": "An enumeration." - }, - "overlay_fisher_exact_test": { - "type": "string", - "enum": [ - "overlay_fisher_exact_test" - ], - "title": "overlay_fisher_exact_test", - "description": "An enumeration." - }, - "restate": { - "type": "string", - "enum": [ - "restate" - ], - "title": "restate", - "description": "An enumeration." - }, - "score": { - "type": "string", - "enum": [ - "score" - ], - "title": "score", - "description": "An enumeration." - }, - "sort_results": { - "type": "string", - "enum": [ - "sort_results" - ], - "title": "sort_results", - "description": "An enumeration." - }, - "sort_results_edge_attribute": { - "type": "string", - "enum": [ - "sort_results_edge_attribute" - ], - "title": "sort_results_edge_attribute", - "description": "An enumeration." - }, - "sort_results_node_attribute": { - "type": "string", - "enum": [ - "sort_results_node_attribute" - ], - "title": "sort_results_node_attribute", - "description": "An enumeration." - }, - "sort_results_score": { - "type": "string", - "enum": [ - "sort_results_score" - ], - "title": "sort_results_score", - "description": "An enumeration." } } - }, - "tags": [ - { - "name": "translator" - }, - { - "name": "health" - }, - { - "name": "metadata" - } - ], - "servers": [ - { - "description": "Default server", - "url": "https://nodenorm-es.ci.transltr.io/", - "x-maturity": "development", - "x-location": "RENCI" - }, - { - "description": "Localhost", - "url": "http://localhost:8000/", - "x-maturity": "development", - "x-location": "RENCI" - } - ] + } } From 35c16a37a5052da3373d31a081608a574745e56c Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Mon, 12 Oct 2026 00:42:04 -0400 Subject: [PATCH 2/8] Make two endpoints answer the way the OpenAPI document says GET /get_allowed_conflations finished a bare list, and Tornado's write() refuses a top-level list ("Lists not accepted for security reasons"), so it returned HTTP 500 on every deployment. It now returns {"conflations": [...]}, which is also what NodeNorm Redis returns. GET /get_semantic_types lower-cased every class ("biolink:namedthing"); NodeNorm Redis returns the Biolink class URI as is ("biolink:NamedThing"). /openapi.json now redirects to /webapp/openapi.json so the FastAPI default address used by NodeNorm Redis, SmartAPI and Babel Validation works here too; it is excluded from OpenTelemetry like the other static paths. The POST set-ID test posted a single object where the handler expects a list of sets, and the normalized-nodes docstring showed a "curie" key where the handler reads "curies"; both corrected. POST /get_setid still answers with an object keyed "0", "1", ... (the same Tornado limitation); returning a list as NodeNorm Redis does is a breaking change, so it is left to its own pull request. Co-Authored-By: Claude Fable 5.1 Co-Authored-By: Claude Opus 5.5 --- docker/configuration/config.json | 2 +- src/nodenorm/config/config.default.json | 2 +- src/nodenorm/handlers/__init__.py | 4 ++++ src/nodenorm/handlers/conflations.py | 11 ++++------ src/nodenorm/handlers/normalized_nodes.py | 2 +- src/nodenorm/handlers/semantic_types.py | 2 +- tests/test_set_identifiers.py | 25 ++++++++++++++--------- 7 files changed, 27 insertions(+), 21 deletions(-) diff --git a/docker/configuration/config.json b/docker/configuration/config.json index a92fe45..627acb7 100644 --- a/docker/configuration/config.json +++ b/docker/configuration/config.json @@ -28,6 +28,6 @@ "OPENTELEMETRY_SERVICE_NAME": "NodeNorm", "OPENTELEMETRY_JAEGER_HOST": "http://jaeger-otel-collector.sri", "OPENTELEMETRY_JAEGER_PORT": 4318, - "OPENTELEMETRY_EXCLUDED_URLS": ["^/$", "^/status$", "^/version$", "^/webapp", "^/favicon\\.ico$"] + "OPENTELEMETRY_EXCLUDED_URLS": ["^/$", "^/status$", "^/version$", "^/webapp", "^/openapi\\.json$", "^/favicon\\.ico$"] } } diff --git a/src/nodenorm/config/config.default.json b/src/nodenorm/config/config.default.json index 54668f4..b071288 100644 --- a/src/nodenorm/config/config.default.json +++ b/src/nodenorm/config/config.default.json @@ -28,6 +28,6 @@ "OPENTELEMETRY_SERVICE_NAME": "NodeNorm", "OPENTELEMETRY_JAEGER_HOST": "http://localhost", "OPENTELEMETRY_JAEGER_PORT": 4318, - "OPENTELEMETRY_EXCLUDED_URLS": ["^/$", "^/status$", "^/version$", "^/webapp", "^/favicon\\.ico$"] + "OPENTELEMETRY_EXCLUDED_URLS": ["^/$", "^/status$", "^/version$", "^/webapp", "^/openapi\\.json$", "^/favicon\\.ico$"] } } diff --git a/src/nodenorm/handlers/__init__.py b/src/nodenorm/handlers/__init__.py index d30a44c..d2cfa7a 100644 --- a/src/nodenorm/handlers/__init__.py +++ b/src/nodenorm/handlers/__init__.py @@ -45,5 +45,9 @@ def build_handlers() -> dict[str, tuple[str, Callable]]: favicon_handler = (r"/favicon.ico", tornado.web.RedirectHandler, {"url": "/webapp/swaggerui/favicon-32x32.png"}) handler_collection.append(favicon_handler) + # NodeNorm Redis (FastAPI) serves its OpenAPI document at the root; keep that address working + openapi_handler = (r"/openapi.json", tornado.web.RedirectHandler, {"url": "/webapp/openapi.json"}) + handler_collection.append(openapi_handler) + handlers = {handler[0]: handler for handler in handler_collection} return handlers diff --git a/src/nodenorm/handlers/conflations.py b/src/nodenorm/handlers/conflations.py index 62cb57e..cebc8ba 100644 --- a/src/nodenorm/handlers/conflations.py +++ b/src/nodenorm/handlers/conflations.py @@ -1,17 +1,14 @@ -import logging - from nodenorm.handlers.base import NodeNormalizationBaseHandler -logger = logging.getLogger(__name__) +ALLOWED_CONFLATIONS = ["GeneProtein", "DrugChemical"] class ValidConflationsHandler(NodeNormalizationBaseHandler): name = "allowed-conflations" async def get(self): - conflations = ["GeneProtein", "DrugChemical"] - self.finish(conflations) + # Wrapped in an object, as NodeNorm Redis does. Tornado also refuses to write a bare list. + self.finish({"conflations": ALLOWED_CONFLATIONS}) async def head(self): - conflations = ["GeneProtein", "DrugChemical"] - self.finish(conflations) + await self.get() diff --git a/src/nodenorm/handlers/normalized_nodes.py b/src/nodenorm/handlers/normalized_nodes.py index 18835aa..284e610 100644 --- a/src/nodenorm/handlers/normalized_nodes.py +++ b/src/nodenorm/handlers/normalized_nodes.py @@ -76,7 +76,7 @@ async def post(self): Example body { - "curie": [ + "curies": [ "MESH:D014867", "NCIT:C34373" ] diff --git a/src/nodenorm/handlers/semantic_types.py b/src/nodenorm/handlers/semantic_types.py index 3201e1e..df0a5b8 100644 --- a/src/nodenorm/handlers/semantic_types.py +++ b/src/nodenorm/handlers/semantic_types.py @@ -33,7 +33,7 @@ async def get(self) -> dict: biolink_type = bucket["key"] semantic_types.add(biolink_type) for ancestor in toolkit.get_ancestors(biolink_type): - semantic_types.add(toolkit.get_element(ancestor)["class_uri"].lower()) + semantic_types.add(toolkit.get_element(ancestor)["class_uri"]) semantic_type_response = {"semantic_types": {"types": list(semantic_types)}} self.finish(semantic_type_response) diff --git a/tests/test_set_identifiers.py b/tests/test_set_identifiers.py index ea96679..2d82983 100644 --- a/tests/test_set_identifiers.py +++ b/tests/test_set_identifiers.py @@ -82,10 +82,12 @@ def test_post_endpoint(self): url = self.get_url(normalized_nodes_endpoint) body = json.dumps( - { - "curies": ["MESH:D014867", "NCIT:C34373", "UNII:63M8RYN44N", "RUBBISH:1234"], - "conflations": ["GeneProtein", "DrugChemical"], - } + [ + { + "curies": ["MESH:D014867", "NCIT:C34373", "UNII:63M8RYN44N", "RUBBISH:1234"], + "conflations": ["GeneProtein", "DrugChemical"], + } + ] ) headers = {"Content-Type": "application/json"} @@ -93,12 +95,15 @@ def test_post_endpoint(self): response = yield http_client.fetch(url, self.stop, method="POST", headers=headers, body=body, request_timeout=0) body = json.loads(response.body.decode("utf-8")) + # One SetIDResponse per input set, keyed by the set's index in the request. expected_body = { - "curies": ["MESH:D014867", "NCIT:C34373", "UNII:63M8RYN44N", "RUBBISH:1234"], - "conflations": ["GeneProtein", "DrugChemical"], - "error": None, - "normalized_curies": ["CHEBI:15377", "MONDO:0004976", "RUBBISH:1234"], - "normalized_string": "CHEBI:15377||MONDO:0004976||RUBBISH:1234", - "setid": "uuid:771d3c09-9a8c-5c46-8b85-97f481a90d40", + "0": { + "curies": ["MESH:D014867", "NCIT:C34373", "UNII:63M8RYN44N", "RUBBISH:1234"], + "conflations": ["GeneProtein", "DrugChemical"], + "error": None, + "normalized_curies": ["CHEBI:15377", "MONDO:0004976", "RUBBISH:1234"], + "normalized_string": "CHEBI:15377||MONDO:0004976||RUBBISH:1234", + "setid": "uuid:771d3c09-9a8c-5c46-8b85-97f481a90d40", + } } assert json.loads(response.body.decode("utf-8")) == expected_body From 8f6bc15a2f0c9598f8cf43ba1d04a407aadf4f98 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Mon, 5 Oct 2026 19:10:20 -0400 Subject: [PATCH 3/8] Test that the OpenAPI document matches the handlers The document is hand-written and served as a static file, so these tests are what stops it drifting: the spec validates; its paths are exactly the routes build_handlers() registers; each path documents the GET/POST methods its handler defines; each GET documents the query parameters its handler reads (found by scanning the handler source for get_argument calls); and the SetIDResponse schema lists the dataclass's fields. Each was checked to fail when the spec is mutated. Also a regression test for /get_allowed_conflations, which returned HTTP 500 until the previous commit: it mounts just that handler and validates the body against the documented ConflationList schema. Co-Authored-By: Claude Fable 5.1 --- pyproject.toml | 1 + tests/test_openapi.py | 76 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 tests/test_openapi.py diff --git a/pyproject.toml b/pyproject.toml index 5b38ada..e81b7ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ tests = [ "pytest >= 8.1.1", "pytest-asyncio >= 0.23.8", "pytest-randomly", + "openapi-spec-validator", ] telemetry = [ diff --git a/tests/test_openapi.py b/tests/test_openapi.py new file mode 100644 index 0000000..50ee53b --- /dev/null +++ b/tests/test_openapi.py @@ -0,0 +1,76 @@ +""" +Keep src/nodenorm/webapp/openapi.json in step with the handlers. + +The OpenAPI document is written by hand and served as a static file (Tornado has nothing like +FastAPI's schema generation), so nothing stops it drifting from the code except these tests. +If one fails after a change to a handler, edit the JSON; if it fails after a change to the +JSON, the document now promises something the code doesn't do. +""" + +import dataclasses +import importlib.resources +import inspect +import json +import re + +import jsonschema +import tornado.web +from openapi_spec_validator import validate +from tornado.testing import AsyncHTTPTestCase + +import nodenorm +from nodenorm.handlers import build_handlers +from nodenorm.handlers.base import NodeNormalizationBaseHandler +from nodenorm.handlers.conflations import ValidConflationsHandler +from nodenorm.handlers.set_identifiers import SetIDResponse + +SPEC = json.loads((importlib.resources.files(nodenorm) / "webapp" / "openapi.json").read_text(encoding="utf-8")) + +# Only the handlers that answer the API itself: not the static files, index or redirects. +API_HANDLERS = { + pattern.rstrip("?"): handler + for pattern, handler, *_ in build_handlers().values() + if isinstance(handler, type) and issubclass(handler, NodeNormalizationBaseHandler) +} + + +def test_document_is_valid_openapi(): + validate(SPEC) + + +def test_every_route_is_documented_and_every_documented_path_is_routed(): + assert set(SPEC["paths"]) == set(API_HANDLERS) + + +def test_documented_methods_match_the_handlers(): + for path, handler in API_HANDLERS.items(): + documented = {method for method in SPEC["paths"][path] if method in ("get", "post")} + implemented = {method for method in ("get", "post") if method in vars(handler)} + assert documented == implemented, path + + +def test_documented_query_parameters_match_the_handlers(): + for path, handler in API_HANDLERS.items(): + documented = {p["name"] for p in SPEC["paths"][path]["get"].get("parameters", []) if p["in"] == "query"} + implemented = set(re.findall(r'get_arguments?\("(\w+)"', inspect.getsource(handler.get))) + assert documented == implemented, path + + +def test_setid_response_schema_matches_the_dataclass(): + documented = set(SPEC["components"]["schemas"]["SetIDResponse"]["properties"]) + assert documented == {field.name for field in dataclasses.fields(SetIDResponse)} + + +class TestAllowedConflations(AsyncHTTPTestCase): + """Regression: this endpoint returned HTTP 500 because it finished a bare list.""" + + def get_app(self) -> tornado.web.Application: + return tornado.web.Application([(r"/get_allowed_conflations", ValidConflationsHandler)]) + + def test_response_matches_the_documented_schema(self): + response = self.fetch("/get_allowed_conflations") + + assert response.code == 200 + body = json.loads(response.body) + jsonschema.validate(body, SPEC["components"]["schemas"]["ConflationList"]) + assert {"GeneProtein", "DrugChemical"} <= set(body["conflations"]) From 5de9ee9359a3a68d88512fb91146f97e0312f8f7 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Mon, 5 Oct 2026 19:10:21 -0400 Subject: [PATCH 4/8] Document where the OpenAPI document lives and how it is kept in sync Co-Authored-By: Claude Fable 5.1 --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index de20ce8..fd79bac 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,12 @@ https://github.com/biothings/biothings_explorer/blob/main/docs/README-maintainin The documentation is maintained at the [biothings_explorer](https://github.com/biothings/biothings_explorer) repository, as each knowledgebase API will be integrated into the [BioThings Explorer application](https://explorer.biothings.io) become a Translator's standard KP (Knowledge Provider) API. + +## OpenAPI document + +The API is described by `src/nodenorm/webapp/openapi.json`, which is written by hand and served +verbatim at `/webapp/openapi.json` (with `/openapi.json` redirecting there); the Swagger UI at `/` +renders it. `/status` reports its `info.version` as the API version, so bump that when the API +changes. `tests/test_openapi.py` checks that the document validates and that its paths, methods, +query parameters and the `SetIDResponse` schema match the handlers, so a handler change that +isn't reflected in the JSON fails the test suite. From 10df39853c593c81fca1363afa9a4080280793cd Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Mon, 12 Oct 2026 00:42:31 -0400 Subject: [PATCH 5/8] Unit-test the semantic-type casing fix It ran only against the su10 Elasticsearch host before. This mounts the handler alone with a fake Elasticsearch client and validates the body against the documented schema. Co-Authored-By: Claude Fable 5.1 Co-Authored-By: Claude Opus 5.5 --- tests/test_openapi.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_openapi.py b/tests/test_openapi.py index 50ee53b..c5a9515 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -12,6 +12,7 @@ import inspect import json import re +from types import SimpleNamespace import jsonschema import tornado.web @@ -22,6 +23,7 @@ from nodenorm.handlers import build_handlers from nodenorm.handlers.base import NodeNormalizationBaseHandler from nodenorm.handlers.conflations import ValidConflationsHandler +from nodenorm.handlers.semantic_types import SemanticTypeHandler from nodenorm.handlers.set_identifiers import SetIDResponse SPEC = json.loads((importlib.resources.files(nodenorm) / "webapp" / "openapi.json").read_text(encoding="utf-8")) @@ -74,3 +76,31 @@ def test_response_matches_the_documented_schema(self): body = json.loads(response.body) jsonschema.validate(body, SPEC["components"]["schemas"]["ConflationList"]) assert {"GeneProtein", "DrugChemical"} <= set(body["conflations"]) + + +class FakeElasticsearch: + """Just enough of AsyncElasticsearch for the semantic-types aggregation.""" + + async def search(self, **kwargs): + return SimpleNamespace(body={"aggregations": {"unique_types": {"buckets": [{"key": "biolink:Disease"}]}}}) + + +class TestSemanticTypes(AsyncHTTPTestCase): + """Regression: the handler lower-cased every class ("biolink:namedthing").""" + + def get_app(self) -> tornado.web.Application: + app = tornado.web.Application([(r"/get_semantic_types", SemanticTypeHandler)]) + app.biothings = SimpleNamespace( + elasticsearch=SimpleNamespace(indices="nodenorm", async_client=FakeElasticsearch()) + ) + return app + + def test_types_are_biolink_class_curies(self): + response = self.fetch("/get_semantic_types") + + assert response.code == 200 + body = json.loads(response.body) + jsonschema.validate(body, SPEC["components"]["schemas"]["SemanticTypes"]) + types = body["semantic_types"]["types"] + assert "biolink:Disease" in types and "biolink:NamedThing" in types + assert all(re.fullmatch(r"biolink:[A-Z][A-Za-z]*", t) for t in types), types From 77b9f4f08861797b3df2cdc84430ed79eae745d6 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Mon, 12 Oct 2026 00:43:16 -0400 Subject: [PATCH 6/8] Match /openapi.json and /favicon.ico literally The dot in each redirect pattern was unescaped, so /openapiXjson and /faviconXico redirected too. The OpenTelemetry exclusions already escape it. A test now checks the /openapi.json redirect and that a near-miss path is a 404. Co-Authored-By: Claude Opus 5.5 --- src/nodenorm/handlers/__init__.py | 4 ++-- tests/test_openapi.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/nodenorm/handlers/__init__.py b/src/nodenorm/handlers/__init__.py index d2cfa7a..5c34aa2 100644 --- a/src/nodenorm/handlers/__init__.py +++ b/src/nodenorm/handlers/__init__.py @@ -42,11 +42,11 @@ def build_handlers() -> dict[str, tuple[str, Callable]]: handler_collection.append(index_handler) # This redirect ensures we default so the favicon icon can be found in the webapp directory - favicon_handler = (r"/favicon.ico", tornado.web.RedirectHandler, {"url": "/webapp/swaggerui/favicon-32x32.png"}) + favicon_handler = (r"/favicon\.ico", tornado.web.RedirectHandler, {"url": "/webapp/swaggerui/favicon-32x32.png"}) handler_collection.append(favicon_handler) # NodeNorm Redis (FastAPI) serves its OpenAPI document at the root; keep that address working - openapi_handler = (r"/openapi.json", tornado.web.RedirectHandler, {"url": "/webapp/openapi.json"}) + openapi_handler = (r"/openapi\.json", tornado.web.RedirectHandler, {"url": "/webapp/openapi.json"}) handler_collection.append(openapi_handler) handlers = {handler[0]: handler for handler in handler_collection} diff --git a/tests/test_openapi.py b/tests/test_openapi.py index c5a9515..6815d9a 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -63,6 +63,22 @@ def test_setid_response_schema_matches_the_dataclass(): assert documented == {field.name for field in dataclasses.fields(SetIDResponse)} +class TestOpenAPIRedirect(AsyncHTTPTestCase): + """/openapi.json is where NodeNorm Redis, SmartAPI and Babel Validation look for the document.""" + + def get_app(self) -> tornado.web.Application: + return tornado.web.Application(list(build_handlers().values())) + + def test_openapi_json_redirects_to_the_document(self): + response = self.fetch("/openapi.json", follow_redirects=False) + + assert response.code == 301 + assert response.headers["Location"] == "/webapp/openapi.json" + + def test_only_the_exact_path_redirects(self): + assert self.fetch("/openapiXjson", follow_redirects=False).code == 404 + + class TestAllowedConflations(AsyncHTTPTestCase): """Regression: this endpoint returned HTTP 500 because it finished a bare list.""" From e6a16738d506c1d9c4c8ff69bf66038659b83a89 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Mon, 12 Oct 2026 00:43:58 -0400 Subject: [PATCH 7/8] Check POST bodies and helper-read parameters against the OpenAPI document The query-parameter check scanned only get() for get_argument calls, so a parameter read in a helper method would drift unnoticed; it now scans the whole handler class. A new check compares the fields each post() reads with .get("...") against the properties of the documented request body (CurieList, and the SetIDQuery items of the /get_setid array). Both were confirmed to fail when a field is removed from the document or a parameter is renamed in a helper. The checks still only see literal names; the README now says so. Co-Authored-By: Claude Opus 5.5 --- README.md | 6 ++++-- tests/test_openapi.py | 24 +++++++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fd79bac..d82c03e 100644 --- a/README.md +++ b/README.md @@ -35,5 +35,7 @@ The API is described by `src/nodenorm/webapp/openapi.json`, which is written by verbatim at `/webapp/openapi.json` (with `/openapi.json` redirecting there); the Swagger UI at `/` renders it. `/status` reports its `info.version` as the API version, so bump that when the API changes. `tests/test_openapi.py` checks that the document validates and that its paths, methods, -query parameters and the `SetIDResponse` schema match the handlers, so a handler change that -isn't reflected in the JSON fails the test suite. +query parameters, POST body fields and the `SetIDResponse` schema match the handlers, so a handler +change that isn't reflected in the JSON fails the test suite. The parameter and body checks find +what a handler reads by scanning its source for `get_argument("…")` and `.get("…")` calls with a +literal name, so read new parameters and body fields that way. diff --git a/tests/test_openapi.py b/tests/test_openapi.py index 6815d9a..c307e81 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -52,9 +52,31 @@ def test_documented_methods_match_the_handlers(): def test_documented_query_parameters_match_the_handlers(): + # The whole class, not just get(), so a parameter read in a helper method still counts. for path, handler in API_HANDLERS.items(): documented = {p["name"] for p in SPEC["paths"][path]["get"].get("parameters", []) if p["in"] == "query"} - implemented = set(re.findall(r'get_arguments?\("(\w+)"', inspect.getsource(handler.get))) + implemented = set(re.findall(r'get_arguments?\("(\w+)"', inspect.getsource(handler))) + assert documented == implemented, path + + +def resolve(schema: dict) -> dict: + """Follow a local $ref, and an array to its items, down to the object schema of one body item.""" + if "$ref" in schema: + return resolve(SPEC["components"]["schemas"][schema["$ref"].rsplit("/", 1)[-1]]) + if schema.get("type") == "array": + return resolve(schema["items"]) + return schema + + +def test_documented_request_bodies_match_the_handlers(): + # post() reads each body field with .get("field", default), whether the body is one object + # (get_normalized_nodes) or a list of them (get_setid). + posting = {path: handler for path, handler in API_HANDLERS.items() if "post" in vars(handler)} + assert posting, "no handler defines post()" + for path, handler in posting.items(): + body_schema = SPEC["paths"][path]["post"]["requestBody"]["content"]["application/json"]["schema"] + documented = set(resolve(body_schema)["properties"]) + implemented = set(re.findall(r'\.get\("(\w+)"', inspect.getsource(handler.post))) assert documented == implemented, path From e33e84d8ed859459a95e3da231687219a5ac142a Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Mon, 12 Oct 2026 01:15:15 -0400 Subject: [PATCH 8/8] Serve the OpenAPI document at /openapi.json /openapi.json redirected to /webapp/openapi.json. It now serves the document itself, which is where NodeNorm Redis (FastAPI's default) and most Translator services serve theirs, and the Swagger UI at / loads it from there. /webapp/openapi.json keeps working through the existing /webapp asset handler, because Babel Validation's targets.ini and the Translator diagram's nodenorm-es component are configured with that address. Removing it would break them and gain nothing; they can switch to the default once this is deployed. Co-Authored-By: Claude Opus 5.5 --- README.md | 4 ++-- src/nodenorm/handlers/__init__.py | 6 ++++-- src/nodenorm/webapp/index.html | 2 +- tests/test_openapi.py | 20 +++++++++++++------- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d82c03e..12757ee 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,8 @@ a Translator's standard KP (Knowledge Provider) API. ## OpenAPI document The API is described by `src/nodenorm/webapp/openapi.json`, which is written by hand and served -verbatim at `/webapp/openapi.json` (with `/openapi.json` redirecting there); the Swagger UI at `/` -renders it. `/status` reports its `info.version` as the API version, so bump that when the API +verbatim at `/openapi.json`, as NodeNorm Redis and most Translator services serve theirs, and also +at the older `/webapp/openapi.json`; the Swagger UI at `/` renders it. `/status` reports its `info.version` as the API version, so bump that when the API changes. `tests/test_openapi.py` checks that the document validates and that its paths, methods, query parameters, POST body fields and the `SetIDResponse` schema match the handlers, so a handler change that isn't reflected in the JSON fails the test suite. The parameter and body checks find diff --git a/src/nodenorm/handlers/__init__.py b/src/nodenorm/handlers/__init__.py index 5c34aa2..5a24340 100644 --- a/src/nodenorm/handlers/__init__.py +++ b/src/nodenorm/handlers/__init__.py @@ -45,8 +45,10 @@ def build_handlers() -> dict[str, tuple[str, Callable]]: favicon_handler = (r"/favicon\.ico", tornado.web.RedirectHandler, {"url": "/webapp/swaggerui/favicon-32x32.png"}) handler_collection.append(favicon_handler) - # NodeNorm Redis (FastAPI) serves its OpenAPI document at the root; keep that address working - openapi_handler = (r"/openapi\.json", tornado.web.RedirectHandler, {"url": "/webapp/openapi.json"}) + # The OpenAPI document is served at the root, where NodeNorm Redis (FastAPI) and most Translator + # services serve theirs. The asset handler above also serves it at /webapp/openapi.json, the + # address Babel Validation's targets and the Translator diagram are configured with. + openapi_handler = (r"/(openapi\.json)", tornado.web.StaticFileHandler, {"path": str(webapp_directory)}) handler_collection.append(openapi_handler) handlers = {handler[0]: handler for handler in handler_collection} diff --git a/src/nodenorm/webapp/index.html b/src/nodenorm/webapp/index.html index 080984a..ab7a197 100644 --- a/src/nodenorm/webapp/index.html +++ b/src/nodenorm/webapp/index.html @@ -22,7 +22,7 @@ // Initialize Swagger UI window.onload = function() { const ui = SwaggerUIBundle({ - url: "/webapp/openapi.json", + url: "/openapi.json", dom_id: '#swagger-ui', presets: [ SwaggerUIBundle.presets.apis, diff --git a/tests/test_openapi.py b/tests/test_openapi.py index c307e81..4f93553 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -85,21 +85,27 @@ def test_setid_response_schema_matches_the_dataclass(): assert documented == {field.name for field in dataclasses.fields(SetIDResponse)} -class TestOpenAPIRedirect(AsyncHTTPTestCase): - """/openapi.json is where NodeNorm Redis, SmartAPI and Babel Validation look for the document.""" +class TestOpenAPIAddresses(AsyncHTTPTestCase): + """/openapi.json is where NodeNorm Redis serves its document; /webapp/openapi.json is the older address.""" def get_app(self) -> tornado.web.Application: return tornado.web.Application(list(build_handlers().values())) - def test_openapi_json_redirects_to_the_document(self): - response = self.fetch("/openapi.json", follow_redirects=False) + def test_the_document_is_served_at_both_addresses(self): + for path in ("/openapi.json", "/webapp/openapi.json"): + response = self.fetch(path, follow_redirects=False) - assert response.code == 301 - assert response.headers["Location"] == "/webapp/openapi.json" + assert response.code == 200, path + assert response.headers["Content-Type"].startswith("application/json"), path + assert json.loads(response.body) == SPEC, path - def test_only_the_exact_path_redirects(self): + def test_only_the_exact_path_matches(self): assert self.fetch("/openapiXjson", follow_redirects=False).code == 404 + def test_swagger_ui_loads_the_root_address(self): + index = (importlib.resources.files(nodenorm) / "webapp" / "index.html").read_text(encoding="utf-8") + assert 'url: "/openapi.json"' in index + class TestAllowedConflations(AsyncHTTPTestCase): """Regression: this endpoint returned HTTP 500 because it finished a bare list."""