From ea2a7cb47dff9fc672e761e93278c7b92c3fd733 Mon Sep 17 00:00:00 2001 From: tokebe <43009413+tokebe@users.noreply.github.com> Date: Thu, 10 Sep 2026 17:44:17 -0400 Subject: [PATCH 1/4] beta2 compliance --- .../v2_0/model_dicts/meta_knowledge_graph.py | 59 +++++++++++++--- .../v2_0/models/meta_knowledge_graph.py | 68 +++++++++++++++++-- .../test_meta_knowledge_graph_dicts.py | 20 ++++++ .../test_models/test_meta_knowledge_graph.py | 48 +++++++++++++ 4 files changed, 178 insertions(+), 17 deletions(-) diff --git a/src/translator_tom/v2_0/model_dicts/meta_knowledge_graph.py b/src/translator_tom/v2_0/model_dicts/meta_knowledge_graph.py index 7ed9c02..30e9b9e 100644 --- a/src/translator_tom/v2_0/model_dicts/meta_knowledge_graph.py +++ b/src/translator_tom/v2_0/model_dicts/meta_knowledge_graph.py @@ -4,7 +4,7 @@ from translator_tom.utils.biolink import Biolink from translator_tom.utils.dict_util_base import DictUtil -from translator_tom.utils.shared import KnowledgeType +from translator_tom.utils.shared import Infores, KnowledgeType from translator_tom.v2_0.model_dicts.attribute import ( AttributeConstraintDict, AttributeConstraintDictUtil, @@ -23,6 +23,7 @@ MetaKnowledgeGraph, MetaNode, merged_applicable_values, + merged_list, ) __all__ = [ @@ -74,6 +75,9 @@ class MetaEdgeDict(TypedDict): attributes: NotRequired[list[MetaAttributeDict] | None] qualifiers: NotRequired[list[MetaQualifierDict] | None] association: NotRequired[Biolink.Entity | None] + knowledge_levels: NotRequired[list[str] | None] + agent_types: NotRequired[list[str] | None] + sources: NotRequired[list[Infores] | None] class MetaEdgeDictUtil(DictUtil[MetaEdgeDict]): @@ -99,18 +103,48 @@ def qualifiers_list(meta_edge: MetaEdgeDict) -> list[MetaQualifierDict]: qualifiers = meta_edge.get("qualifiers") return qualifiers if qualifiers is not None else [] + @staticmethod + def knowledge_levels_list(meta_edge: MetaEdgeDict) -> list[str]: + """Get the knowledge levels as a guaranteed list, even if they are represented as None.""" + knowledge_levels = meta_edge.get("knowledge_levels") + return knowledge_levels if knowledge_levels is not None else [] + + @staticmethod + def agent_types_list(meta_edge: MetaEdgeDict) -> list[str]: + """Get the agent types as a guaranteed list, even if they are represented as None.""" + agent_types = meta_edge.get("agent_types") + return agent_types if agent_types is not None else [] + + @staticmethod + def sources_list(meta_edge: MetaEdgeDict) -> list[Infores]: + """Get the sources as a guaranteed list, even if they are represented as None.""" + sources = meta_edge.get("sources") + return sources if sources is not None else [] + @staticmethod def update(meta_edge: MetaEdgeDict, other: MetaEdgeDict) -> None: """Update the meta edge in-place with another meta edge.""" - edge_kt = meta_edge.get("knowledge_types") - other_kt = other.get("knowledge_types") - if (not edge_kt) and other_kt: - meta_edge["knowledge_types"] = other_kt - elif edge_kt and other_kt: - meta_edge["knowledge_types"] = list( - set(MetaEdgeDictUtil.knowledge_types_list(meta_edge)) - | set(MetaEdgeDictUtil.knowledge_types_list(other)) - ) + knowledge_types = merged_list( + meta_edge.get("knowledge_types"), other.get("knowledge_types") + ) + if knowledge_types is not None: + meta_edge["knowledge_types"] = knowledge_types + + knowledge_levels = merged_list( + meta_edge.get("knowledge_levels"), other.get("knowledge_levels") + ) + if knowledge_levels is not None: + meta_edge["knowledge_levels"] = knowledge_levels + + agent_types = merged_list( + meta_edge.get("agent_types"), other.get("agent_types") + ) + if agent_types is not None: + meta_edge["agent_types"] = agent_types + + sources = merged_list(meta_edge.get("sources"), other.get("sources")) + if sources is not None: + meta_edge["sources"] = sources edge_attrs = meta_edge.get("attributes") other_attrs = other.get("attributes") @@ -126,6 +160,11 @@ def update(meta_edge: MetaEdgeDict, other: MetaEdgeDict) -> None: attrs[MetaAttributeDictUtil.hash(attr)] = attr meta_edge["attributes"] = list(attrs.values()) + MetaEdgeDictUtil._merge_qualifiers(meta_edge, other) + + @staticmethod + def _merge_qualifiers(meta_edge: MetaEdgeDict, other: MetaEdgeDict) -> None: + """Union `other`'s qualifiers into `meta_edge` in place, merging shared types' values.""" other_quals = other.get("qualifiers") if not other_quals: return diff --git a/src/translator_tom/v2_0/models/meta_knowledge_graph.py b/src/translator_tom/v2_0/models/meta_knowledge_graph.py index 419da72..0b109ee 100644 --- a/src/translator_tom/v2_0/models/meta_knowledge_graph.py +++ b/src/translator_tom/v2_0/models/meta_knowledge_graph.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Annotated, ClassVar +from typing import Annotated, ClassVar, TypeVar from pydantic import ConfigDict, Field from typing_extensions import Self @@ -9,6 +9,7 @@ from translator_tom.utils.object_base import TOMBase from translator_tom.utils.shared import ( CURIE, + Infores, KnowledgeType, ) from translator_tom.v2_0.models.attribute import AttributeConstraint @@ -32,6 +33,20 @@ def merged_applicable_values( return list(set(a) | set(b)) +_ListItemT = TypeVar("_ListItemT", bound=str) + + +def merged_list( + current: list[_ListItemT] | None, other: list[_ListItemT] | None +) -> list[_ListItemT] | None: + """Union two optional value lists, treating None/empty as absent (order not preserved).""" + if not other: + return current + if not current: + return other + return list(set(current) | set(other)) + + class MetaKnowledgeGraph(TOMBase): """Knowledge-map representation of this TRAPI web service. @@ -125,6 +140,30 @@ class MetaEdge(TOMBase): correctly. """ + knowledge_levels: Annotated[list[str], Field(min_length=1)] | None = None + """The knowledge levels contributing to this meta edge. + + If provided, this property SHOULD contain all possible + knowledge_levels relevant edges might return. + (See https://biolink.github.io/biolink-model/KnowledgeLevelEnum/) + """ + + agent_types: Annotated[list[str], Field(min_length=1)] | None = None + """The agent types contributing to this meta edge. + + If provided, this property SHOULD contain all possible + agent_types relevant edges might return. + (See https://biolink.github.io/biolink-model/AgentTypeEnum/) + """ + + sources: Annotated[list[Infores], Field(min_length=1)] | None = None + """The infores CURIEs for sources contributing to this meta edge. + + If provided, this property SHOULD contain resource_ids for + all relevant resource_roles. Services MAY choose to omit + their own aggregator resource_ids. + """ + @property def knowledge_types_list(self) -> list[KnowledgeType]: """Get the knowledge types as a guaranteed list, even if they are represented as None.""" @@ -140,14 +179,29 @@ def qualifiers_list(self) -> list[MetaQualifier]: """Get the meta qualifiers as a guaranteed list, even if they are represented as None.""" return self.qualifiers if self.qualifiers is not None else [] + @property + def knowledge_levels_list(self) -> list[str]: + """Get the knowledge levels as a guaranteed list, even if they are represented as None.""" + return self.knowledge_levels if self.knowledge_levels is not None else [] + + @property + def agent_types_list(self) -> list[str]: + """Get the agent types as a guaranteed list, even if they are represented as None.""" + return self.agent_types if self.agent_types is not None else [] + + @property + def sources_list(self) -> list[Infores]: + """Get the sources as a guaranteed list, even if they are represented as None.""" + return self.sources if self.sources is not None else [] + def update(self, other: MetaEdge) -> None: """Update the meta edge in-place with another meta edge.""" - if (not self.knowledge_types) and other.knowledge_types: - self.knowledge_types = other.knowledge_types - elif self.knowledge_types and other.knowledge_types: - self.knowledge_types = list( - set(self.knowledge_types_list) | set(other.knowledge_types_list) - ) + self.knowledge_types = merged_list(self.knowledge_types, other.knowledge_types) + self.knowledge_levels = merged_list( + self.knowledge_levels, other.knowledge_levels + ) + self.agent_types = merged_list(self.agent_types, other.agent_types) + self.sources = merged_list(self.sources, other.sources) if (not self.attributes) and other.attributes: self.attributes = other.attributes diff --git a/tests/v2_0/test_model_dicts/test_meta_knowledge_graph_dicts.py b/tests/v2_0/test_model_dicts/test_meta_knowledge_graph_dicts.py index f646df5..b75ba2c 100644 --- a/tests/v2_0/test_model_dicts/test_meta_knowledge_graph_dicts.py +++ b/tests/v2_0/test_model_dicts/test_meta_knowledge_graph_dicts.py @@ -125,6 +125,26 @@ def test_update_parity(self): } assert merged_values == model_values + def test_update_new_list_fields_parity(self): + edge = _meta_edge( + knowledge_levels=["knowledge_assertion"], + agent_types=["manual_agent"], + sources=["infores:foo"], + ) + other = _meta_edge( + knowledge_levels=["prediction"], + agent_types=["automated_agent"], + sources=["infores:bar"], + ) + edge_dict = edge.to_dict() + edge.update(other) + MetaEdgeDictUtil.update(edge_dict, other.to_dict()) + # New list fields merge via sets; compare set-wise. + assert set(edge_dict["knowledge_levels"]) == set(edge.knowledge_levels_list) + assert set(edge_dict["agent_types"]) == set(edge.agent_types_list) + assert set(edge_dict["sources"]) == set(edge.sources_list) + assert MetaEdgeDictUtil.hash(edge_dict) == edge.hash() + def test_update_all_allowed_absorbs_concrete_parity(self): # applicable_values=None ("all allowed") must survive the merge on both sides, # not narrow to the concrete list. Asserts the absolute result, not just parity. diff --git a/tests/v2_0/test_models/test_meta_knowledge_graph.py b/tests/v2_0/test_models/test_meta_knowledge_graph.py index 2b6fb63..647ae0e 100644 --- a/tests/v2_0/test_models/test_meta_knowledge_graph.py +++ b/tests/v2_0/test_models/test_meta_knowledge_graph.py @@ -127,6 +127,15 @@ def test_attributes_list_when_none(self): def test_qualifiers_list_when_none(self): assert _meta_edge().qualifiers_list == [] + def test_knowledge_levels_list_when_none(self): + assert _meta_edge().knowledge_levels_list == [] + + def test_agent_types_list_when_none(self): + assert _meta_edge().agent_types_list == [] + + def test_sources_list_when_none(self): + assert _meta_edge().sources_list == [] + class TestMetaEdgeUpdate: def test_assigns_knowledge_types_when_self_empty(self): @@ -252,6 +261,45 @@ def test_qualifier_appends_new_type(self): "biolink:object_aspect_qualifier", } + def test_assigns_new_list_fields_when_self_empty(self): + a = _meta_edge() + b = _meta_edge( + knowledge_levels=["knowledge_assertion"], + agent_types=["manual_agent"], + sources=["infores:foo"], + ) + a.update(b) + assert a.knowledge_levels == ["knowledge_assertion"] + assert a.agent_types == ["manual_agent"] + assert a.sources == ["infores:foo"] + + def test_unions_new_list_fields(self): + a = _meta_edge( + knowledge_levels=["knowledge_assertion"], + agent_types=["manual_agent"], + sources=["infores:foo"], + ) + b = _meta_edge( + knowledge_levels=["prediction"], + agent_types=["automated_agent"], + sources=["infores:bar"], + ) + a.update(b) + assert set(a.knowledge_levels_list) == {"knowledge_assertion", "prediction"} + assert set(a.agent_types_list) == {"manual_agent", "automated_agent"} + assert set(a.sources_list) == {"infores:foo", "infores:bar"} + + def test_new_list_fields_no_op_when_other_empty(self): + a = _meta_edge( + knowledge_levels=["knowledge_assertion"], + agent_types=["manual_agent"], + sources=["infores:foo"], + ) + a.update(_meta_edge()) + assert a.knowledge_levels == ["knowledge_assertion"] + assert a.agent_types == ["manual_agent"] + assert a.sources == ["infores:foo"] + class TestMetaEdgeMeetsAttributeConstraints: def test_no_constraints_returns_true(self): From d14b0f991f1245756d226bbb2e22c34701ceba3b Mon Sep 17 00:00:00 2001 From: tokebe <43009413+tokebe@users.noreply.github.com> Date: Thu, 10 Sep 2026 17:45:36 -0400 Subject: [PATCH 2/4] fixes: typo, dict null allowance --- .../v1_6/model_dicts/workflow_operations.py | 22 +++--- .../v1_6/models/workflow_operations.py | 10 +-- .../v1_6/validation/_workflow_operations.py | 6 +- .../v2_0/model_dicts/analysis.py | 12 ++-- .../v2_0/model_dicts/asyncquery.py | 6 +- .../v2_0/model_dicts/attribute.py | 18 ++--- .../v2_0/model_dicts/constraints.py | 10 +-- .../v2_0/model_dicts/knowledge_graph.py | 24 ++++--- .../v2_0/model_dicts/log_entry.py | 4 +- .../v2_0/model_dicts/message.py | 8 +-- .../v2_0/model_dicts/meta_attribute.py | 6 +- .../v2_0/model_dicts/meta_knowledge_graph.py | 16 ++--- .../v2_0/model_dicts/meta_qualifier.py | 2 +- .../v2_0/model_dicts/path_constraint.py | 2 +- src/translator_tom/v2_0/model_dicts/query.py | 6 +- .../v2_0/model_dicts/query_graph.py | 24 +++---- .../v2_0/model_dicts/query_parameters.py | 4 +- .../v2_0/model_dicts/response.py | 16 ++--- src/translator_tom/v2_0/model_dicts/result.py | 8 ++- .../v2_0/model_dicts/retrieval_source.py | 4 +- .../v2_0/model_dicts/workflow_operations.py | 72 +++++++++---------- .../v2_0/models/workflow_operations.py | 10 +-- .../v2_0/validation/_workflow_operations.py | 6 +- .../test_workflow_operations_dicts.py | 8 +-- .../test_models/test_workflow_operations.py | 8 +-- .../test_meta_knowledge_graph_dicts.py | 23 ++++++ .../test_workflow_operations_dicts.py | 8 +-- .../test_models/test_workflow_operations.py | 8 +-- 28 files changed, 191 insertions(+), 160 deletions(-) diff --git a/src/translator_tom/v1_6/model_dicts/workflow_operations.py b/src/translator_tom/v1_6/model_dicts/workflow_operations.py index bd5c2d5..5c622c2 100644 --- a/src/translator_tom/v1_6/model_dicts/workflow_operations.py +++ b/src/translator_tom/v1_6/model_dicts/workflow_operations.py @@ -61,8 +61,8 @@ OverlayComputeNgdParameters, OverlayFisherExactTestParameters, PlusOrMinus, - SortResultNodeAttributeParameters, SortResultsEdgeAttributeParameters, + SortResultsNodeAttributeParameters, SortResultsScoreParameters, TopOrBottom, ) @@ -169,10 +169,10 @@ "OverlayFisherExactTestParametersDict", "OverlayFisherExactTestParametersDictUtil", "RunnerParametersDict", - "SortResultNodeAttributeParametersDict", - "SortResultNodeAttributeParametersDictUtil", "SortResultsEdgeAttributeParametersDict", "SortResultsEdgeAttributeParametersDictUtil", + "SortResultsNodeAttributeParametersDict", + "SortResultsNodeAttributeParametersDictUtil", "SortResultsScoreParametersDict", "SortResultsScoreParametersDictUtil", ] @@ -783,22 +783,22 @@ class OperationSortResultsEdgeAttributeDictUtil(BaseOperationDictUtil): _model = OperationSortResultsEdgeAttribute -class SortResultNodeAttributeParametersDict(OperationParametersDict): +class SortResultsNodeAttributeParametersDict(OperationParametersDict): node_attribute: str ascending_or_descending: AscendingOrDescending - qnode_keys: list[QNodeID] | None + qnode_keys: NotRequired[list[QNodeID] | None] -class SortResultNodeAttributeParametersDictUtil( - DictUtil[SortResultNodeAttributeParametersDict] +class SortResultsNodeAttributeParametersDictUtil( + DictUtil[SortResultsNodeAttributeParametersDict] ): - """Utility methods for `SortResultNodeAttributeParametersDict`, mirroring the model.""" + """Utility methods for `SortResultsNodeAttributeParametersDict`, mirroring the model.""" - _model = SortResultNodeAttributeParameters + _model = SortResultsNodeAttributeParameters @staticmethod def qnode_keys_list( - parameters: SortResultNodeAttributeParametersDict, + parameters: SortResultsNodeAttributeParametersDict, ) -> list[QNodeID]: """Return a guaranteed list of qnode_keys, empty if it is not defined.""" qnode_keys = parameters.get("qnode_keys") @@ -807,7 +807,7 @@ def qnode_keys_list( class OperationSortResultsNodeAttributeDict(BaseOperationDict): id: Literal["sort_results_node_attribute"] - parameters: SortResultNodeAttributeParametersDict + parameters: SortResultsNodeAttributeParametersDict class OperationSortResultsNodeAttributeDictUtil(BaseOperationDictUtil): diff --git a/src/translator_tom/v1_6/models/workflow_operations.py b/src/translator_tom/v1_6/models/workflow_operations.py index 5ccaf9f..816d850 100644 --- a/src/translator_tom/v1_6/models/workflow_operations.py +++ b/src/translator_tom/v1_6/models/workflow_operations.py @@ -68,8 +68,8 @@ "PlusOrMinus", "PlusOrMinusEnum", "RunnerParameters", - "SortResultNodeAttributeParameters", "SortResultsEdgeAttributeParameters", + "SortResultsNodeAttributeParameters", "SortResultsScoreParameters", "TopOrBottom", "TopOrBottomEnum", @@ -714,15 +714,15 @@ class OperationSortResultsEdgeAttribute(BaseOperation): parameters: SortResultsEdgeAttributeParameters -class SortResultNodeAttributeParameters(OperationParameters): - """Parameters for the SortResultNodeAttribute operation.""" +class SortResultsNodeAttributeParameters(OperationParameters): + """Parameters for the SortResultsNodeAttribute operation.""" node_attribute: Annotated[str, Field(examples=["normalized_google_distance"])] """The name of the node attribute to order by.""" ascending_or_descending: AscendingOrDescending - qnode_keys: Annotated[list[QNodeID] | None, Field(examples=["[e01]"])] + qnode_keys: Annotated[list[QNodeID] | None, Field(examples=["[e01]"])] = None """This indicates if you only want to consider nodes with specific node_keys. If not provided or empty, all nodes will be looked at. @@ -744,7 +744,7 @@ class OperationSortResultsNodeAttribute(BaseOperation): """ id: Literal["sort_results_node_attribute"] - parameters: SortResultNodeAttributeParameters + parameters: SortResultsNodeAttributeParameters class SortResultsScoreParameters(OperationParameters): diff --git a/src/translator_tom/v1_6/validation/_workflow_operations.py b/src/translator_tom/v1_6/validation/_workflow_operations.py index 47f1dac..9b4b044 100644 --- a/src/translator_tom/v1_6/validation/_workflow_operations.py +++ b/src/translator_tom/v1_6/validation/_workflow_operations.py @@ -27,8 +27,8 @@ OverlayComputeJaccardParameters, OverlayComputeNgdParameters, OverlayFisherExactTestParameters, - SortResultNodeAttributeParameters, SortResultsEdgeAttributeParameters, + SortResultsNodeAttributeParameters, ) from translator_tom.v1_6.validation._util import ( Location, @@ -300,9 +300,9 @@ def _validate_sort_edge_attr_params( ) -@semantic_validate.register(SortResultNodeAttributeParameters) +@semantic_validate.register(SortResultsNodeAttributeParameters) def _validate_sort_node_attr_params( - obj: SortResultNodeAttributeParameters, + obj: SortResultsNodeAttributeParameters, location: Location | None = None, *, qgraph: QueryGraph | PathfinderQueryGraph | None = None, diff --git a/src/translator_tom/v2_0/model_dicts/analysis.py b/src/translator_tom/v2_0/model_dicts/analysis.py index 1e5c75d..ddf54cd 100644 --- a/src/translator_tom/v2_0/model_dicts/analysis.py +++ b/src/translator_tom/v2_0/model_dicts/analysis.py @@ -26,12 +26,12 @@ class AnalysisDict(TypedDict): resource_id: CURIE - edge_bindings: NotRequired[dict[QEdgeID, EdgeBindingDict] | None] - path_bindings: NotRequired[dict[QPathID, PathBindingDict] | None] - score: NotRequired[float | None] - support_graphs: NotRequired[list[AuxGraphID] | None] - scoring_method: NotRequired[str | None] - attributes: NotRequired[list[AttributeDict] | None] + edge_bindings: NotRequired[dict[QEdgeID, EdgeBindingDict]] + path_bindings: NotRequired[dict[QPathID, PathBindingDict]] + score: NotRequired[float] + support_graphs: NotRequired[list[AuxGraphID]] + scoring_method: NotRequired[str] + attributes: NotRequired[list[AttributeDict]] class AnalysisDictUtil(DictUtil[AnalysisDict]): diff --git a/src/translator_tom/v2_0/model_dicts/asyncquery.py b/src/translator_tom/v2_0/model_dicts/asyncquery.py index 25922c4..eb011c6 100644 --- a/src/translator_tom/v2_0/model_dicts/asyncquery.py +++ b/src/translator_tom/v2_0/model_dicts/asyncquery.py @@ -35,8 +35,8 @@ class AsyncQueryDict(QueryDict): class AsyncQueryResponseDict(TypedDict): - status: NotRequired[str | None] - description: NotRequired[str | None] + status: NotRequired[str] + description: NotRequired[str] job_id: str @@ -44,7 +44,7 @@ class AsyncQueryStatusResponseDict(TypedDict): status: str description: str logs: list[LogEntryDict] - response_url: NotRequired[str | None] + response_url: NotRequired[str] class AsyncQueryDictUtil(DictUtil[AsyncQueryDict]): diff --git a/src/translator_tom/v2_0/model_dicts/attribute.py b/src/translator_tom/v2_0/model_dicts/attribute.py index a2220f7..d652c63 100644 --- a/src/translator_tom/v2_0/model_dicts/attribute.py +++ b/src/translator_tom/v2_0/model_dicts/attribute.py @@ -28,13 +28,13 @@ class AttributeDict(TypedDict): attribute_type_id: CURIE - original_attribute_name: NotRequired[str | None] + original_attribute_name: NotRequired[str] value: FastJsonValue - value_type_id: NotRequired[CURIE | None] - attribute_source: NotRequired[str | None] - value_url: NotRequired[str | None] - description: NotRequired[str | None] - attributes: NotRequired[list[AttributeDict] | None] + value_type_id: NotRequired[CURIE] + attribute_source: NotRequired[str] + value_url: NotRequired[str] + description: NotRequired[str] + attributes: NotRequired[list[AttributeDict]] class AttributeDictUtil(DictUtil[AttributeDict]): @@ -82,12 +82,12 @@ def merge_attribute_lists( "AttributeConstraintDict", { "id": CURIE, - "name": NotRequired[str | None], + "name": NotRequired[str], "not": NotRequired[bool], "operator": Operator, "value": FastJsonValue, - "unit_id": NotRequired[CURIE | None], - "unit_name": NotRequired[str | None], + "unit_id": NotRequired[CURIE], + "unit_name": NotRequired[str], }, ) diff --git a/src/translator_tom/v2_0/model_dicts/constraints.py b/src/translator_tom/v2_0/model_dicts/constraints.py index 397faf6..5fbdd09 100644 --- a/src/translator_tom/v2_0/model_dicts/constraints.py +++ b/src/translator_tom/v2_0/model_dicts/constraints.py @@ -120,11 +120,11 @@ def met_by( class QEdgeConstraintsDict(TypedDict): - knowledge_level: NotRequired[KnowledgeLevelConstraintDict | None] - agent_type: NotRequired[AgentTypeConstraintDict | None] - attributes: NotRequired[list[AttributeConstraintDict] | None] - qualifiers: NotRequired[list[QualifierSetConstraint] | None] - sources: NotRequired[SourceConstraintDict | None] + knowledge_level: NotRequired[KnowledgeLevelConstraintDict] + agent_type: NotRequired[AgentTypeConstraintDict] + attributes: NotRequired[list[AttributeConstraintDict]] + qualifiers: NotRequired[list[QualifierSetConstraint]] + sources: NotRequired[SourceConstraintDict] class QEdgeConstraintsDictUtil(DictUtil[QEdgeConstraintsDict]): diff --git a/src/translator_tom/v2_0/model_dicts/knowledge_graph.py b/src/translator_tom/v2_0/model_dicts/knowledge_graph.py index 8138624..2c7df3e 100644 --- a/src/translator_tom/v2_0/model_dicts/knowledge_graph.py +++ b/src/translator_tom/v2_0/model_dicts/knowledge_graph.py @@ -52,10 +52,10 @@ class NodeDict(TypedDict): - name: NotRequired[str | None] + name: NotRequired[str] categories: list[Biolink.Entity] - attributes: NotRequired[list[AttributeDict] | None] - is_set: NotRequired[bool | None] + attributes: NotRequired[list[AttributeDict]] + is_set: NotRequired[bool] class NodeDictUtil(DictUtil[NodeDict]): @@ -90,7 +90,9 @@ def update(node: NodeDict, other: NodeDict) -> None: Does not mutate `other`. """ - node["name"] = other.get("name") or node.get("name") + name = other.get("name") or node.get("name") + if name is not None: + node["name"] = name node["categories"] = list(set(node["categories"]) | set(other["categories"])) other_attrs = other.get("attributes") @@ -108,8 +110,8 @@ class EdgeDict(TypedDict): predicate: Biolink.Predicate subject: CURIE object: CURIE - attributes: NotRequired[list[AttributeDict] | None] - qualifiers: NotRequired[list[QualifierDict] | None] + attributes: NotRequired[list[AttributeDict]] + qualifiers: NotRequired[list[QualifierDict]] sources: list[RetrievalSourceDict] knowledge_level: str agent_type: str @@ -313,7 +315,7 @@ def append_aggregator(edge: EdgeDict, source: Infores) -> None: class KnowledgeGraphDict(TypedDict): nodes: dict[CURIE, NodeDict] - edges: NotRequired[dict[EdgeID, EdgeDict] | None] + edges: NotRequired[dict[EdgeID, EdgeDict]] class KnowledgeGraphDictUtil(DictUtil[KnowledgeGraphDict]): @@ -490,9 +492,11 @@ def prune( for aux_graph_id in cast("list[str]", edge_aux_graphs["value"]): edges_to_check.extend(aux_graphs[aux_graph_id]["edges"]) - knowledge_graph["edges"] = { - edge_id: edges[edge_id] for edge_id in bound_edges - } or None + bound = {edge_id: edges[edge_id] for edge_id in bound_edges} + if bound: + knowledge_graph["edges"] = bound + else: + knowledge_graph.pop("edges", None) knowledge_graph["nodes"] = { curie: knowledge_graph["nodes"][curie] for curie in bound_nodes } diff --git a/src/translator_tom/v2_0/model_dicts/log_entry.py b/src/translator_tom/v2_0/model_dicts/log_entry.py index 5fe3cfe..e8c3c24 100644 --- a/src/translator_tom/v2_0/model_dicts/log_entry.py +++ b/src/translator_tom/v2_0/model_dicts/log_entry.py @@ -12,8 +12,8 @@ class LogEntryDict(TypedDict): timestamp: str - level: NotRequired[LogLevel | None] - code: NotRequired[str | None] + level: NotRequired[LogLevel] + code: NotRequired[str] message: str diff --git a/src/translator_tom/v2_0/model_dicts/message.py b/src/translator_tom/v2_0/model_dicts/message.py index 00daeae..4067ce5 100644 --- a/src/translator_tom/v2_0/model_dicts/message.py +++ b/src/translator_tom/v2_0/model_dicts/message.py @@ -27,10 +27,10 @@ class MessageDict(TypedDict): - results: NotRequired[list[ResultDict] | None] - query_graph: NotRequired[QueryGraphDict | None] - knowledge_graph: NotRequired[KnowledgeGraphDict | None] - auxiliary_graphs: NotRequired[dict[AuxGraphID, AuxiliaryGraphDict] | None] + results: NotRequired[list[ResultDict]] + query_graph: NotRequired[QueryGraphDict] + knowledge_graph: NotRequired[KnowledgeGraphDict] + auxiliary_graphs: NotRequired[dict[AuxGraphID, AuxiliaryGraphDict]] def _query_graph_hash(query_graph: QueryGraphDict | None) -> str | None: diff --git a/src/translator_tom/v2_0/model_dicts/meta_attribute.py b/src/translator_tom/v2_0/model_dicts/meta_attribute.py index 1d1be9b..692568d 100644 --- a/src/translator_tom/v2_0/model_dicts/meta_attribute.py +++ b/src/translator_tom/v2_0/model_dicts/meta_attribute.py @@ -12,10 +12,10 @@ class MetaAttributeDict(TypedDict): attribute_type_id: CURIE - attribute_source: NotRequired[str | None] - original_attribute_names: NotRequired[list[str] | None] + attribute_source: NotRequired[str] + original_attribute_names: NotRequired[list[str]] constraint_use: NotRequired[bool] - constraint_name: NotRequired[str | None] + constraint_name: NotRequired[str] class MetaAttributeDictUtil(DictUtil[MetaAttributeDict]): diff --git a/src/translator_tom/v2_0/model_dicts/meta_knowledge_graph.py b/src/translator_tom/v2_0/model_dicts/meta_knowledge_graph.py index 30e9b9e..77f69ff 100644 --- a/src/translator_tom/v2_0/model_dicts/meta_knowledge_graph.py +++ b/src/translator_tom/v2_0/model_dicts/meta_knowledge_graph.py @@ -38,7 +38,7 @@ class MetaNodeDict(TypedDict): id_prefixes: list[str] - attributes: NotRequired[list[MetaAttributeDict] | None] + attributes: NotRequired[list[MetaAttributeDict]] class MetaNodeDictUtil(DictUtil[MetaNodeDict]): @@ -71,13 +71,13 @@ class MetaEdgeDict(TypedDict): subject: Biolink.Entity predicate: Biolink.Predicate object: Biolink.Entity - knowledge_types: NotRequired[list[KnowledgeType] | None] - attributes: NotRequired[list[MetaAttributeDict] | None] - qualifiers: NotRequired[list[MetaQualifierDict] | None] - association: NotRequired[Biolink.Entity | None] - knowledge_levels: NotRequired[list[str] | None] - agent_types: NotRequired[list[str] | None] - sources: NotRequired[list[Infores] | None] + knowledge_types: NotRequired[list[KnowledgeType]] + attributes: NotRequired[list[MetaAttributeDict]] + qualifiers: NotRequired[list[MetaQualifierDict]] + association: NotRequired[Biolink.Entity] + knowledge_levels: NotRequired[list[str]] + agent_types: NotRequired[list[str]] + sources: NotRequired[list[Infores]] class MetaEdgeDictUtil(DictUtil[MetaEdgeDict]): diff --git a/src/translator_tom/v2_0/model_dicts/meta_qualifier.py b/src/translator_tom/v2_0/model_dicts/meta_qualifier.py index b1b61b0..e1e1490 100644 --- a/src/translator_tom/v2_0/model_dicts/meta_qualifier.py +++ b/src/translator_tom/v2_0/model_dicts/meta_qualifier.py @@ -11,7 +11,7 @@ class MetaQualifierDict(TypedDict): qualifier_type_id: Biolink.Qualifier - applicable_values: NotRequired[list[str] | None] + applicable_values: NotRequired[list[str]] class MetaQualifierDictUtil(DictUtil[MetaQualifierDict]): diff --git a/src/translator_tom/v2_0/model_dicts/path_constraint.py b/src/translator_tom/v2_0/model_dicts/path_constraint.py index 945b180..63a0f7f 100644 --- a/src/translator_tom/v2_0/model_dicts/path_constraint.py +++ b/src/translator_tom/v2_0/model_dicts/path_constraint.py @@ -10,7 +10,7 @@ class PathConstraintDict(TypedDict): - required_intermediate_categories: NotRequired[list[Biolink.Entity] | None] + required_intermediate_categories: NotRequired[list[Biolink.Entity]] class PathConstraintDictUtil(DictUtil[PathConstraintDict]): diff --git a/src/translator_tom/v2_0/model_dicts/query.py b/src/translator_tom/v2_0/model_dicts/query.py index 1d35254..b81e860 100644 --- a/src/translator_tom/v2_0/model_dicts/query.py +++ b/src/translator_tom/v2_0/model_dicts/query.py @@ -12,10 +12,10 @@ class QueryDict(TypedDict): - submitter: NotRequired[str | None] - parameters: NotRequired[QueryParametersDict | None] + submitter: NotRequired[str] + parameters: NotRequired[QueryParametersDict] message: MessageDict - workflow: NotRequired[list[OperationDict] | None] + workflow: NotRequired[list[OperationDict]] class QueryDictUtil(DictUtil[QueryDict]): diff --git a/src/translator_tom/v2_0/model_dicts/query_graph.py b/src/translator_tom/v2_0/model_dicts/query_graph.py index 5852000..e87d50c 100644 --- a/src/translator_tom/v2_0/model_dicts/query_graph.py +++ b/src/translator_tom/v2_0/model_dicts/query_graph.py @@ -38,11 +38,11 @@ class QNodeDict(TypedDict): - ids: NotRequired[list[CURIE] | None] - categories: NotRequired[list[Biolink.Entity] | None] - set_interpretation: NotRequired[SetInterpretation | None] - member_ids: NotRequired[list[CURIE] | None] - constraints: NotRequired[list[AttributeConstraintDict] | None] + ids: NotRequired[list[CURIE]] + categories: NotRequired[list[Biolink.Entity]] + set_interpretation: NotRequired[SetInterpretation] + member_ids: NotRequired[list[CURIE]] + constraints: NotRequired[list[AttributeConstraintDict]] class QNodeDictUtil(DictUtil[QNodeDict]): @@ -76,11 +76,11 @@ def constraints_list(qnode: QNodeDict) -> list[AttributeConstraintDict]: class QEdgeDict(TypedDict): - knowledge_type: NotRequired[KnowledgeType | None] - predicates: NotRequired[list[Biolink.Predicate] | None] + knowledge_type: NotRequired[KnowledgeType] + predicates: NotRequired[list[Biolink.Predicate]] subject: QNodeID object: QNodeID - constraints: NotRequired[QEdgeConstraintsDict | None] + constraints: NotRequired[QEdgeConstraintsDict] class QEdgeDictUtil(DictUtil[QEdgeDict]): @@ -128,8 +128,8 @@ def get_inverse(qedge: QEdgeDict) -> QEdgeDict: class QPathDict(TypedDict): subject: QNodeID object: QNodeID - predicates: NotRequired[list[Biolink.Predicate] | None] - constraints: NotRequired[list[PathConstraintDict] | None] + predicates: NotRequired[list[Biolink.Predicate]] + constraints: NotRequired[list[PathConstraintDict]] class QPathDictUtil(DictUtil[QPathDict]): @@ -152,8 +152,8 @@ def constraints_list(qpath: QPathDict) -> list[PathConstraintDict]: class QueryGraphDict(TypedDict): nodes: dict[QNodeID, QNodeDict] - edges: NotRequired[dict[QEdgeID, QEdgeDict] | None] - paths: NotRequired[dict[QPathID, QPathDict] | None] + edges: NotRequired[dict[QEdgeID, QEdgeDict]] + paths: NotRequired[dict[QPathID, QPathDict]] class QueryGraphDictUtil(DictUtil[QueryGraphDict]): diff --git a/src/translator_tom/v2_0/model_dicts/query_parameters.py b/src/translator_tom/v2_0/model_dicts/query_parameters.py index 9c34cad..8266801 100644 --- a/src/translator_tom/v2_0/model_dicts/query_parameters.py +++ b/src/translator_tom/v2_0/model_dicts/query_parameters.py @@ -12,8 +12,8 @@ class QueryParametersDict(TypedDict): - timeout: NotRequired[float | None] - log_level: NotRequired[LogLevel | None] + timeout: NotRequired[float] + log_level: NotRequired[LogLevel] bypass_cache: NotRequired[bool] diff --git a/src/translator_tom/v2_0/model_dicts/response.py b/src/translator_tom/v2_0/model_dicts/response.py index 931355f..e1bfff7 100644 --- a/src/translator_tom/v2_0/model_dicts/response.py +++ b/src/translator_tom/v2_0/model_dicts/response.py @@ -15,15 +15,15 @@ class ResponseDict(TypedDict): - parameters: NotRequired[QueryParametersDict | None] + parameters: NotRequired[QueryParametersDict] message: MessageDict - status: NotRequired[str | None] - description: NotRequired[str | None] - logs: NotRequired[list[LogEntryDict] | None] - workflow: NotRequired[list[OperationDict] | None] - schema_version: NotRequired[str | None] - biolink_version: NotRequired[str | None] - data_release_versions: NotRequired[dict[str, str] | None] + status: NotRequired[str] + description: NotRequired[str] + logs: NotRequired[list[LogEntryDict]] + workflow: NotRequired[list[OperationDict]] + schema_version: NotRequired[str] + biolink_version: NotRequired[str] + data_release_versions: NotRequired[dict[str, str]] class ResponseDictUtil(DictUtil[ResponseDict]): diff --git a/src/translator_tom/v2_0/model_dicts/result.py b/src/translator_tom/v2_0/model_dicts/result.py index 8c18a5a..c3edae3 100644 --- a/src/translator_tom/v2_0/model_dicts/result.py +++ b/src/translator_tom/v2_0/model_dicts/result.py @@ -19,7 +19,7 @@ class ResultDict(TypedDict): node_bindings: dict[QNodeID, NodeBindingDict] - analyses: NotRequired[list[AnalysisDict] | None] + analyses: NotRequired[list[AnalysisDict]] class ResultDictUtil(DictUtil[ResultDict]): @@ -120,4 +120,8 @@ def merge_analyses_by_resource_id(result: ResultDict) -> None: else: AnalysisDictUtil.update(existing, analysis) - result["analyses"] = list(merged.values()) or None + analyses = list(merged.values()) + if analyses: + result["analyses"] = analyses + else: + result.pop("analyses", None) diff --git a/src/translator_tom/v2_0/model_dicts/retrieval_source.py b/src/translator_tom/v2_0/model_dicts/retrieval_source.py index d418a02..7e652f4 100644 --- a/src/translator_tom/v2_0/model_dicts/retrieval_source.py +++ b/src/translator_tom/v2_0/model_dicts/retrieval_source.py @@ -13,8 +13,8 @@ class RetrievalSourceDict(TypedDict): resource_id: Infores resource_role: ResourceRole - upstream_resource_ids: NotRequired[list[Infores] | None] - source_record_urls: NotRequired[list[str] | None] + upstream_resource_ids: NotRequired[list[Infores]] + source_record_urls: NotRequired[list[str]] class RetrievalSourceDictUtil(DictUtil[RetrievalSourceDict]): diff --git a/src/translator_tom/v2_0/model_dicts/workflow_operations.py b/src/translator_tom/v2_0/model_dicts/workflow_operations.py index 504a25e..c8bb2fb 100644 --- a/src/translator_tom/v2_0/model_dicts/workflow_operations.py +++ b/src/translator_tom/v2_0/model_dicts/workflow_operations.py @@ -61,8 +61,8 @@ OverlayComputeNgdParameters, OverlayFisherExactTestParameters, PlusOrMinus, - SortResultNodeAttributeParameters, SortResultsEdgeAttributeParameters, + SortResultsNodeAttributeParameters, SortResultsScoreParameters, TopOrBottom, ) @@ -169,10 +169,10 @@ "OverlayFisherExactTestParametersDict", "OverlayFisherExactTestParametersDictUtil", "RunnerParametersDict", - "SortResultNodeAttributeParametersDict", - "SortResultNodeAttributeParametersDictUtil", "SortResultsEdgeAttributeParametersDict", "SortResultsEdgeAttributeParametersDictUtil", + "SortResultsNodeAttributeParametersDict", + "SortResultsNodeAttributeParametersDictUtil", "SortResultsScoreParametersDict", "SortResultsScoreParametersDictUtil", ] @@ -206,7 +206,7 @@ class OperationParametersDict(TypedDict): class BaseOperationDict(TypedDict): - runner_parameters: NotRequired[RunnerParametersDict | None] + runner_parameters: NotRequired[RunnerParametersDict] class BaseOperationDictUtil(DictUtil[BaseOperationDict]): @@ -219,7 +219,7 @@ class BaseOperationDictUtil(DictUtil[BaseOperationDict]): class OperationAnnotateDict(BaseOperationDict): id: Literal["annotate"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationAnnotateDictUtil(BaseOperationDictUtil): @@ -230,7 +230,7 @@ class OperationAnnotateDictUtil(BaseOperationDictUtil): class AnnotateEdgesParametersDict(OperationParametersDict): - attributes: NotRequired[list[str] | None] + attributes: NotRequired[list[str]] class AnnotateEdgesParametersDictUtil(DictUtil[AnnotateEdgesParametersDict]): @@ -247,7 +247,7 @@ def attributes_list(parameters: AnnotateEdgesParametersDict) -> list[str]: class OperationAnnotateEdgesDict(BaseOperationDict): id: Literal["annotate_edges"] - parameters: NotRequired[AnnotateEdgesParametersDict | None] + parameters: NotRequired[AnnotateEdgesParametersDict] class OperationAnnotateEdgesDictUtil(BaseOperationDictUtil): @@ -258,7 +258,7 @@ class OperationAnnotateEdgesDictUtil(BaseOperationDictUtil): class AnnotateNodesParametersDict(OperationParametersDict): - attributes: NotRequired[list[str] | None] + attributes: NotRequired[list[str]] class AnnotateNodesParametersDictUtil(DictUtil[AnnotateNodesParametersDict]): @@ -275,7 +275,7 @@ def attributes_list(parameters: AnnotateNodesParametersDict) -> list[str]: class OperationAnnotateNodesDict(BaseOperationDict): id: Literal["annotate_nodes"] - parameters: NotRequired[AnnotateNodesParametersDict | None] + parameters: NotRequired[AnnotateNodesParametersDict] class OperationAnnotateNodesDictUtil(BaseOperationDictUtil): @@ -287,7 +287,7 @@ class OperationAnnotateNodesDictUtil(BaseOperationDictUtil): class OperationBindDict(BaseOperationDict): id: Literal["bind"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationBindDictUtil(BaseOperationDictUtil): @@ -298,7 +298,7 @@ class OperationBindDictUtil(BaseOperationDictUtil): class OperationCompleteResultsDict(BaseOperationDict): id: Literal["complete_results"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationCompleteResultsDictUtil(BaseOperationDictUtil): @@ -309,7 +309,7 @@ class OperationCompleteResultsDictUtil(BaseOperationDictUtil): class EnrichResultsParametersDict(OperationParametersDict): pvalue_threshold: NotRequired[int | float] - qnode_keys: NotRequired[list[QNodeID] | None] + qnode_keys: NotRequired[list[QNodeID]] class EnrichResultsParametersDictUtil(DictUtil[EnrichResultsParametersDict]): @@ -326,7 +326,7 @@ def qnode_keys_list(parameters: EnrichResultsParametersDict) -> list[QNodeID]: class OperationEnrichResultsDict(BaseOperationDict): id: Literal["enrich_results"] - parameters: NotRequired[EnrichResultsParametersDict | None] + parameters: NotRequired[EnrichResultsParametersDict] class OperationEnrichResultsDictUtil(BaseOperationDictUtil): @@ -337,7 +337,7 @@ class OperationEnrichResultsDictUtil(BaseOperationDictUtil): class FillAllowListParametersDict(AllowListDict): - qedge_keys: NotRequired[list[QEdgeID] | None] + qedge_keys: NotRequired[list[QEdgeID]] class FillAllowListParametersDictUtil(DictUtil[FillAllowListParametersDict]): @@ -353,7 +353,7 @@ def qedge_keys_list(parameters: FillAllowListParametersDict) -> list[QEdgeID]: class FillDenyListParametersDict(DenyListDict): - qedge_keys: NotRequired[list[QEdgeID] | None] + qedge_keys: NotRequired[list[QEdgeID]] class FillDenyListParametersDictUtil(DictUtil[FillDenyListParametersDict]): @@ -371,7 +371,7 @@ def qedge_keys_list(parameters: FillDenyListParametersDict) -> list[QEdgeID]: class OperationFillDict(BaseOperationDict): id: Literal["fill"] parameters: NotRequired[ - FillAllowListParametersDict | FillDenyListParametersDict | None + FillAllowListParametersDict | FillDenyListParametersDict ] @@ -384,7 +384,7 @@ class OperationFillDictUtil(BaseOperationDictUtil): class OperationFilterKgraphDict(BaseOperationDict): id: Literal["filter_kgraph"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationFilterKgraphDictUtil(BaseOperationDictUtil): @@ -394,7 +394,7 @@ class OperationFilterKgraphDictUtil(BaseOperationDictUtil): class FilterKgraphParametersBaseDict(OperationParametersDict): - qedge_keys: NotRequired[list[QEdgeID] | None] + qedge_keys: NotRequired[list[QEdgeID]] qnode_keys: NotRequired[list[QNodeID]] @@ -493,7 +493,7 @@ class OperationFilterKgraphDiscreteKnodeAttributeDictUtil(BaseOperationDictUtil) class OperationFilterKgraphOrphansDict(BaseOperationDict): id: Literal["filter_kgraph_orphans"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationFilterKgraphOrphansDictUtil(BaseOperationDictUtil): @@ -574,7 +574,7 @@ class OperationFilterKgraphTopNDictUtil(BaseOperationDictUtil): class OperationFilterResultsDict(BaseOperationDict): id: Literal["filter_results"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationFilterResultsDictUtil(BaseOperationDictUtil): @@ -606,7 +606,7 @@ class OperationFilterResultsTopNDictUtil(BaseOperationDictUtil): class OperationLookupDict(BaseOperationDict): id: Literal["lookup"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationLookupDictUtil(BaseOperationDictUtil): @@ -618,7 +618,7 @@ class OperationLookupDictUtil(BaseOperationDictUtil): class OperationLookupAndScoreDict(BaseOperationDict): id: Literal["lookup_and_score"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationLookupAndScoreDictUtil(BaseOperationDictUtil): @@ -630,7 +630,7 @@ class OperationLookupAndScoreDictUtil(BaseOperationDictUtil): class OperationOverlayDict(BaseOperationDict): id: Literal["overlay"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationOverlayDictUtil(BaseOperationDictUtil): @@ -688,7 +688,7 @@ class OperationOverlayComputeNgdDictUtil(BaseOperationDictUtil): class OperationOverlayConnectKnodesDict(BaseOperationDict): id: Literal["overlay_connect_knodes"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationOverlayConnectKnodesDictUtil(BaseOperationDictUtil): @@ -701,7 +701,7 @@ class OverlayFisherExactTestParametersDict(OperationParametersDict): subject_qnode_key: QNodeID object_qnode_key: QNodeID virtual_relation_label: str - rel_edge_key: NotRequired[QEdgeID | None] + rel_edge_key: NotRequired[QEdgeID] class OverlayFisherExactTestParametersDictUtil( @@ -725,7 +725,7 @@ class OperationOverlayFisherExactTestDictUtil(BaseOperationDictUtil): class OperationRestateDict(BaseOperationDict): id: Literal["restate"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationRestateDictUtil(BaseOperationDictUtil): @@ -737,7 +737,7 @@ class OperationRestateDictUtil(BaseOperationDictUtil): class OperationScoreDict(BaseOperationDict): id: Literal["score"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationScoreDictUtil(BaseOperationDictUtil): @@ -749,7 +749,7 @@ class OperationScoreDictUtil(BaseOperationDictUtil): class OperationSortResultsDict(BaseOperationDict): id: Literal["sort_results"] - parameters: NotRequired[dict[str, FastJsonValue] | None] + parameters: NotRequired[dict[str, FastJsonValue]] class OperationSortResultsDictUtil(BaseOperationDictUtil): @@ -783,22 +783,22 @@ class OperationSortResultsEdgeAttributeDictUtil(BaseOperationDictUtil): _model = OperationSortResultsEdgeAttribute -class SortResultNodeAttributeParametersDict(OperationParametersDict): +class SortResultsNodeAttributeParametersDict(OperationParametersDict): node_attribute: str ascending_or_descending: AscendingOrDescending - qnode_keys: list[QNodeID] | None + qnode_keys: NotRequired[list[QNodeID]] -class SortResultNodeAttributeParametersDictUtil( - DictUtil[SortResultNodeAttributeParametersDict] +class SortResultsNodeAttributeParametersDictUtil( + DictUtil[SortResultsNodeAttributeParametersDict] ): - """Utility methods for `SortResultNodeAttributeParametersDict`, mirroring the model.""" + """Utility methods for `SortResultsNodeAttributeParametersDict`, mirroring the model.""" - _model = SortResultNodeAttributeParameters + _model = SortResultsNodeAttributeParameters @staticmethod def qnode_keys_list( - parameters: SortResultNodeAttributeParametersDict, + parameters: SortResultsNodeAttributeParametersDict, ) -> list[QNodeID]: """Return a guaranteed list of qnode_keys, empty if it is not defined.""" qnode_keys = parameters.get("qnode_keys") @@ -807,7 +807,7 @@ def qnode_keys_list( class OperationSortResultsNodeAttributeDict(BaseOperationDict): id: Literal["sort_results_node_attribute"] - parameters: SortResultNodeAttributeParametersDict + parameters: SortResultsNodeAttributeParametersDict class OperationSortResultsNodeAttributeDictUtil(BaseOperationDictUtil): diff --git a/src/translator_tom/v2_0/models/workflow_operations.py b/src/translator_tom/v2_0/models/workflow_operations.py index 5ccaf9f..816d850 100644 --- a/src/translator_tom/v2_0/models/workflow_operations.py +++ b/src/translator_tom/v2_0/models/workflow_operations.py @@ -68,8 +68,8 @@ "PlusOrMinus", "PlusOrMinusEnum", "RunnerParameters", - "SortResultNodeAttributeParameters", "SortResultsEdgeAttributeParameters", + "SortResultsNodeAttributeParameters", "SortResultsScoreParameters", "TopOrBottom", "TopOrBottomEnum", @@ -714,15 +714,15 @@ class OperationSortResultsEdgeAttribute(BaseOperation): parameters: SortResultsEdgeAttributeParameters -class SortResultNodeAttributeParameters(OperationParameters): - """Parameters for the SortResultNodeAttribute operation.""" +class SortResultsNodeAttributeParameters(OperationParameters): + """Parameters for the SortResultsNodeAttribute operation.""" node_attribute: Annotated[str, Field(examples=["normalized_google_distance"])] """The name of the node attribute to order by.""" ascending_or_descending: AscendingOrDescending - qnode_keys: Annotated[list[QNodeID] | None, Field(examples=["[e01]"])] + qnode_keys: Annotated[list[QNodeID] | None, Field(examples=["[e01]"])] = None """This indicates if you only want to consider nodes with specific node_keys. If not provided or empty, all nodes will be looked at. @@ -744,7 +744,7 @@ class OperationSortResultsNodeAttribute(BaseOperation): """ id: Literal["sort_results_node_attribute"] - parameters: SortResultNodeAttributeParameters + parameters: SortResultsNodeAttributeParameters class SortResultsScoreParameters(OperationParameters): diff --git a/src/translator_tom/v2_0/validation/_workflow_operations.py b/src/translator_tom/v2_0/validation/_workflow_operations.py index 9f3498d..d10b815 100644 --- a/src/translator_tom/v2_0/validation/_workflow_operations.py +++ b/src/translator_tom/v2_0/validation/_workflow_operations.py @@ -27,8 +27,8 @@ OverlayComputeJaccardParameters, OverlayComputeNgdParameters, OverlayFisherExactTestParameters, - SortResultNodeAttributeParameters, SortResultsEdgeAttributeParameters, + SortResultsNodeAttributeParameters, ) from translator_tom.v2_0.validation._util import ( Location, @@ -298,9 +298,9 @@ def _validate_sort_edge_attr_params( ) -@semantic_validate.register(SortResultNodeAttributeParameters) +@semantic_validate.register(SortResultsNodeAttributeParameters) def _validate_sort_node_attr_params( - obj: SortResultNodeAttributeParameters, + obj: SortResultsNodeAttributeParameters, location: Location | None = None, *, qgraph: QueryGraph | None = None, diff --git a/tests/v1_6/test_model_dicts/test_workflow_operations_dicts.py b/tests/v1_6/test_model_dicts/test_workflow_operations_dicts.py index 80f765e..ee2c0e6 100644 --- a/tests/v1_6/test_model_dicts/test_workflow_operations_dicts.py +++ b/tests/v1_6/test_model_dicts/test_workflow_operations_dicts.py @@ -21,8 +21,8 @@ OperationOverlayFisherExactTestDictUtil, OperationScoreDictUtil, OverlayFisherExactTestParametersDictUtil, - SortResultNodeAttributeParametersDict, - SortResultNodeAttributeParametersDictUtil, + SortResultsNodeAttributeParametersDict, + SortResultsNodeAttributeParametersDictUtil, ) from translator_tom.v1_6.models.workflow_operations import ( AllowList, @@ -87,12 +87,12 @@ def test_filter_kgraph_base_accessors_inherited(self): ] def test_sort_node_attribute_qnode_keys(self): - params: SortResultNodeAttributeParametersDict = { + params: SortResultsNodeAttributeParametersDict = { "node_attribute": "x", "ascending_or_descending": "ascending", "qnode_keys": None, } - assert SortResultNodeAttributeParametersDictUtil.qnode_keys_list(params) == [] + assert SortResultsNodeAttributeParametersDictUtil.qnode_keys_list(params) == [] class TestOperationHashParity: diff --git a/tests/v1_6/test_models/test_workflow_operations.py b/tests/v1_6/test_models/test_workflow_operations.py index 11a3496..85708ac 100644 --- a/tests/v1_6/test_models/test_workflow_operations.py +++ b/tests/v1_6/test_models/test_workflow_operations.py @@ -31,7 +31,7 @@ OperationRestate, OperationScore, PlusOrMinusEnum, - SortResultNodeAttributeParameters, + SortResultsNodeAttributeParameters, TopOrBottomEnum, ) @@ -202,9 +202,9 @@ def test_qnode_keys_list_when_set(self): assert self._make(qnode_keys=["n0"]).qnode_keys_list == ["n0"] -class TestSortResultNodeAttributeParameters: +class TestSortResultsNodeAttributeParameters: def test_qnode_keys_list_when_none(self): - p = SortResultNodeAttributeParameters( + p = SortResultsNodeAttributeParameters( node_attribute="x", ascending_or_descending="ascending", qnode_keys=None, @@ -212,7 +212,7 @@ def test_qnode_keys_list_when_none(self): assert p.qnode_keys_list == [] def test_qnode_keys_list_when_set(self): - p = SortResultNodeAttributeParameters( + p = SortResultsNodeAttributeParameters( node_attribute="x", ascending_or_descending="ascending", qnode_keys=["n0"], diff --git a/tests/v2_0/test_model_dicts/test_meta_knowledge_graph_dicts.py b/tests/v2_0/test_model_dicts/test_meta_knowledge_graph_dicts.py index b75ba2c..9fcb7f8 100644 --- a/tests/v2_0/test_model_dicts/test_meta_knowledge_graph_dicts.py +++ b/tests/v2_0/test_model_dicts/test_meta_knowledge_graph_dicts.py @@ -2,6 +2,11 @@ from __future__ import annotations +import json + +import pytest +from pydantic import ValidationError + from translator_tom.v2_0.model_dicts.meta_knowledge_graph import ( MetaEdgeDictUtil, MetaKnowledgeGraphDictUtil, @@ -216,6 +221,24 @@ def test_hash_parity(self): assert MetaKnowledgeGraphDictUtil.hash(mkg.to_dict()) == mkg.hash() +_NULL_REJECT_BASE = { + "subject": "biolink:Gene", + "predicate": "biolink:affects", + "object": "biolink:Disease", +} + + +class TestMetaEdgeNullRejection: + def test_validate_accepts_omitted_optional_field(self): + MetaEdgeDictUtil.from_json(json.dumps(_NULL_REJECT_BASE), validate=True) + + def test_validate_rejects_explicit_null_on_optional_field(self): + # 2.0 canonical form forbids null: an optional field is omitted, never nulled. + nulled = {**_NULL_REJECT_BASE, "knowledge_levels": None} + with pytest.raises(ValidationError): + MetaEdgeDictUtil.from_json(json.dumps(nulled), validate=True) + + class TestMetaEdgeUpdateKlAtSkip: def test_update_skips_knowledge_level_and_agent_type(self): edge = _meta_edge(attributes=[MetaAttribute(attribute_type_id="biolink:a")]) diff --git a/tests/v2_0/test_model_dicts/test_workflow_operations_dicts.py b/tests/v2_0/test_model_dicts/test_workflow_operations_dicts.py index d7fc181..d3ab33f 100644 --- a/tests/v2_0/test_model_dicts/test_workflow_operations_dicts.py +++ b/tests/v2_0/test_model_dicts/test_workflow_operations_dicts.py @@ -21,8 +21,8 @@ OperationOverlayFisherExactTestDictUtil, OperationScoreDictUtil, OverlayFisherExactTestParametersDictUtil, - SortResultNodeAttributeParametersDict, - SortResultNodeAttributeParametersDictUtil, + SortResultsNodeAttributeParametersDict, + SortResultsNodeAttributeParametersDictUtil, ) from translator_tom.v2_0.models.workflow_operations import ( AllowList, @@ -87,12 +87,12 @@ def test_filter_kgraph_base_accessors_inherited(self): ] def test_sort_node_attribute_qnode_keys(self): - params: SortResultNodeAttributeParametersDict = { + params: SortResultsNodeAttributeParametersDict = { "node_attribute": "x", "ascending_or_descending": "ascending", "qnode_keys": None, } - assert SortResultNodeAttributeParametersDictUtil.qnode_keys_list(params) == [] + assert SortResultsNodeAttributeParametersDictUtil.qnode_keys_list(params) == [] class TestOperationHashParity: diff --git a/tests/v2_0/test_models/test_workflow_operations.py b/tests/v2_0/test_models/test_workflow_operations.py index 528b116..5c7e298 100644 --- a/tests/v2_0/test_models/test_workflow_operations.py +++ b/tests/v2_0/test_models/test_workflow_operations.py @@ -31,7 +31,7 @@ OperationRestate, OperationScore, PlusOrMinusEnum, - SortResultNodeAttributeParameters, + SortResultsNodeAttributeParameters, TopOrBottomEnum, ) @@ -197,9 +197,9 @@ def test_qnode_keys_list_when_set(self): assert self._make(qnode_keys=["n0"]).qnode_keys_list == ["n0"] -class TestSortResultNodeAttributeParameters: +class TestSortResultsNodeAttributeParameters: def test_qnode_keys_list_when_none(self): - p = SortResultNodeAttributeParameters( + p = SortResultsNodeAttributeParameters( node_attribute="x", ascending_or_descending="ascending", qnode_keys=None, @@ -207,7 +207,7 @@ def test_qnode_keys_list_when_none(self): assert p.qnode_keys_list == [] def test_qnode_keys_list_when_set(self): - p = SortResultNodeAttributeParameters( + p = SortResultsNodeAttributeParameters( node_attribute="x", ascending_or_descending="ascending", qnode_keys=["n0"], From a03d36f44e292c80d3ecb490552a2a78a24051f0 Mon Sep 17 00:00:00 2001 From: tokebe <43009413+tokebe@users.noreply.github.com> Date: Thu, 10 Sep 2026 17:45:56 -0400 Subject: [PATCH 3/4] bump version --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 17588b3..52a9cb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "uv_build" [project] name = "translator_tom" -version = "2.0.0" +version = "2.1.0" description = "TRAPI Object Models: A performant python data model and centralized utilities for the Translator Reasoner API." readme = "README.md" requires-python = ">=3.10" diff --git a/uv.lock b/uv.lock index 20ac5b1..4f1a8c4 100644 --- a/uv.lock +++ b/uv.lock @@ -2275,7 +2275,7 @@ wheels = [ [[package]] name = "translator-tom" -version = "2.0.0" +version = "2.1.0" source = { editable = "." } dependencies = [ { name = "bmt" }, From 99c923eeb32388233f5ed560b7997aac91d1d5d4 Mon Sep 17 00:00:00 2001 From: tokebe <43009413+tokebe@users.noreply.github.com> Date: Thu, 10 Sep 2026 17:46:38 -0400 Subject: [PATCH 4/4] format --- src/translator_tom/v2_0/model_dicts/workflow_operations.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/translator_tom/v2_0/model_dicts/workflow_operations.py b/src/translator_tom/v2_0/model_dicts/workflow_operations.py index c8bb2fb..c4e1cc5 100644 --- a/src/translator_tom/v2_0/model_dicts/workflow_operations.py +++ b/src/translator_tom/v2_0/model_dicts/workflow_operations.py @@ -370,9 +370,7 @@ def qedge_keys_list(parameters: FillDenyListParametersDict) -> list[QEdgeID]: class OperationFillDict(BaseOperationDict): id: Literal["fill"] - parameters: NotRequired[ - FillAllowListParametersDict | FillDenyListParametersDict - ] + parameters: NotRequired[FillAllowListParametersDict | FillDenyListParametersDict] class OperationFillDictUtil(BaseOperationDictUtil):