From b68411dd7427e303c5f5e389a90185976127c78b Mon Sep 17 00:00:00 2001 From: Daniel Himmelstein Date: Wed, 12 Jul 2023 13:40:40 -0400 Subject: [PATCH] rename NodeInfo and NodeT To follow more standard python naming conventions. Will require libraries using nxontology to update imports. --- nxontology/imports.py | 12 ++++---- nxontology/node.py | 26 ++++++++-------- nxontology/ontology.py | 48 ++++++++++++++--------------- nxontology/similarity.py | 24 +++++++-------- nxontology/tests/ontology_test.py | 4 +-- nxontology/tests/similarity_test.py | 4 +-- nxontology/viz.py | 8 ++--- 7 files changed, 63 insertions(+), 63 deletions(-) diff --git a/nxontology/imports.py b/nxontology/imports.py index c408bfa..eb180fa 100644 --- a/nxontology/imports.py +++ b/nxontology/imports.py @@ -11,7 +11,7 @@ from nxontology import NXOntology from nxontology.exceptions import NodeNotFound -from nxontology.node import Node +from nxontology.node import NodeT logger = logging.getLogger(__name__) @@ -86,21 +86,21 @@ def from_file(handle: BinaryIO | str | PathLike[AnyStr]) -> NXOntology[str]: def _pronto_edges_for_term( term: Term, default_rel_type: str = "is a" -) -> list[tuple[Node, Node, str]]: +) -> list[tuple[NodeT, NodeT, str]]: """ Extract edges including "is a" relationships for a Pronto term. https://github.com/althonos/pronto/issues/119#issuecomment-956541286 """ rels = [] - source_id = cast(Node, term.id) + source_id = cast(NodeT, term.id) for target in term.superclasses(distance=1, with_self=False): - rels.append((source_id, cast(Node, target.id), default_rel_type)) + rels.append((source_id, cast(NodeT, target.id), default_rel_type)) for rel_type, targets in term.relationships.items(): for target in sorted(targets): rels.append( ( - cast(Node, term.id), - cast(Node, target.id), + cast(NodeT, term.id), + cast(NodeT, target.id), rel_type.name or rel_type.id, ) ) diff --git a/nxontology/node.py b/nxontology/node.py index b1ec4c4..c921b1d 100644 --- a/nxontology/node.py +++ b/nxontology/node.py @@ -15,10 +15,10 @@ # Type definitions. networkx does not declare types. # https://github.com/networkx/networkx/issues/3988#issuecomment-639969263 -Node = TypeVar("Node", bound=Hashable) +NodeT = TypeVar("NodeT", bound=Hashable) -class Node_Info(Freezable, Generic[Node]): +class NodeInfo(Freezable, Generic[NodeT]): """ Compute metrics and values for a node of an NXOntology. Includes intrinsic information content (IC) metrics. @@ -35,7 +35,7 @@ class Node_Info(Freezable, Generic[Node]): Each ic_metric has a scaled version accessible by adding a _scaled suffix. """ - def __init__(self, nxo: NXOntology[Node], node: Node): + def __init__(self, nxo: NXOntology[NodeT], node: NodeT): if node not in nxo.graph: raise NodeNotFound(f"{node} not in graph.") self.nxo = nxo @@ -98,12 +98,12 @@ def data(self) -> dict[Any, Any]: return data @property - def parents(self) -> set[Node]: + def parents(self) -> set[NodeT]: """Direct parent nodes of this node.""" return set(self.nxo.graph.predecessors(self.node)) @property - def parent(self) -> Node | None: + def parent(self) -> NodeT | None: """ Sole parent of this node, or None if this node is a root. If this node has multiple parents, raise ValueError. @@ -118,13 +118,13 @@ def parent(self) -> Node | None: raise ValueError(f"Node {self!r} has multiple parents.") @property - def children(self) -> set[Node]: + def children(self) -> set[NodeT]: """Direct child nodes of this node.""" return set(self.nxo.graph.successors(self.node)) @property @cache_on_frozen - def ancestors(self) -> set[Node]: + def ancestors(self) -> set[NodeT]: """ Get ancestors of node in graph, including the node itself. Ancestors refers to more general concepts in an ontology, @@ -137,7 +137,7 @@ def ancestors(self) -> set[Node]: @property @cache_on_frozen - def descendants(self) -> set[Node]: + def descendants(self) -> set[NodeT]: """ Get descendants of node in graph, including the node itself. Descendants refers to more specific concepts in an ontology, @@ -160,13 +160,13 @@ def n_descendants(self) -> int: @property @cache_on_frozen - def roots(self) -> set[Node]: + def roots(self) -> set[NodeT]: """Ancestors of this node that are roots (top-level).""" return self.ancestors & self.nxo.roots @property - def leaves(self) -> set[Node]: - """Descendents of this node that are leaves.""" + def leaves(self) -> set[NodeT]: + """Descendants of this node that are leaves.""" return self.descendants & self.nxo.leaves @property @@ -181,14 +181,14 @@ def depth(self) -> int: return depth @property - def paths_from_roots(self) -> Iterator[list[Node]]: + def paths_from_roots(self) -> Iterator[list[NodeT]]: for root in self.roots: yield from nx.all_simple_paths( self.nxo.graph, source=root, target=self.node ) @property - def paths_to_leaves(self) -> Iterator[list[Node]]: + def paths_to_leaves(self) -> Iterator[list[NodeT]]: yield from nx.all_simple_paths( self.nxo.graph, source=self.node, target=self.leaves ) diff --git a/nxontology/ontology.py b/nxontology/ontology.py index e44ecdc..55c3d34 100644 --- a/nxontology/ontology.py +++ b/nxontology/ontology.py @@ -12,17 +12,17 @@ from networkx.algorithms.isolate import isolates from networkx.readwrite.json_graph import node_link_data, node_link_graph -from nxontology.node import Node +from nxontology.node import NodeT from .exceptions import DuplicateError, NodeNotFound -from .node import Node_Info +from .node import NodeInfo from .similarity import SimilarityIC from .utils import Freezable, cache_on_frozen, get_datetime_now, get_nxontology_version logger = logging.getLogger(__name__) -class NXOntology(Freezable, Generic[Node]): +class NXOntology(Freezable, Generic[NodeT]): """ Encapsulate a networkx.DiGraph to represent an ontology. Regarding edge directionality, parent terms should point to child term. @@ -39,7 +39,7 @@ def __init__( # in case there are compatability issues in the future. self._add_nxontology_metadata() self.check_is_dag() - self._node_info_cache: dict[Node, Node_Info[Node]] = {} + self._node_info_cache: dict[NodeT, NodeInfo[NodeT]] = {} def _add_nxontology_metadata(self) -> None: self.graph.graph["nxontology_version"] = get_nxontology_version() @@ -77,7 +77,7 @@ def write_node_link_json(self, path: str | PathLike[str]) -> None: write_file.write("\n") # json.dump does not include a trailing newline @classmethod - def read_node_link_json(cls, path: str | PathLike[str]) -> NXOntology[Node]: + def read_node_link_json(cls, path: str | PathLike[str]) -> NXOntology[NodeT]: """ Retrun a new graph from node-link format as written by `write_node_link_json`. """ @@ -90,7 +90,7 @@ def read_node_link_json(cls, path: str | PathLike[str]) -> NXOntology[Node]: nxo = cls(digraph) return nxo - def add_node(self, node_for_adding: Node, **attr: Any) -> None: + def add_node(self, node_for_adding: NodeT, **attr: Any) -> None: """ Like networkx.DiGraph.add_node but raises a DuplicateError if the node already exists. @@ -99,7 +99,7 @@ def add_node(self, node_for_adding: Node, **attr: Any) -> None: raise DuplicateError(f"node already in graph: {node_for_adding}") self.graph.add_node(node_for_adding, **attr) - def add_edge(self, u_of_edge: Node, v_of_edge: Node, **attr: Any) -> None: + def add_edge(self, u_of_edge: NodeT, v_of_edge: NodeT, **attr: Any) -> None: """ Like networkx.DiGraph.add_edge but raises a NodeNotFound if either node does not exist @@ -116,7 +116,7 @@ def add_edge(self, u_of_edge: Node, v_of_edge: Node, **attr: Any) -> None: @property @cache_on_frozen - def roots(self) -> set[Node]: + def roots(self) -> set[NodeT]: """ Return all top-level nodes, including isolates. """ @@ -127,7 +127,7 @@ def roots(self) -> set[Node]: return roots @property - def root(self) -> Node: + def root(self) -> NodeT: """ Sole root of this directed acyclic graph. If this ontology has multiple roots, raise ValueError. @@ -142,7 +142,7 @@ def root(self) -> Node: @property @cache_on_frozen - def leaves(self) -> set[Node]: + def leaves(self) -> set[NodeT]: """ Return all bottom-level nodes, including isolates. """ @@ -154,7 +154,7 @@ def leaves(self) -> set[Node]: @property @cache_on_frozen - def isolates(self) -> set[Node]: + def isolates(self) -> set[NodeT]: """ Return disconnected nodes. """ @@ -175,17 +175,17 @@ def frozen(self) -> bool: def similarity( self, - node_0: Node, - node_1: Node, + node_0: NodeT, + node_1: NodeT, ic_metric: str = "intrinsic_ic_sanchez", - ) -> SimilarityIC[Node]: + ) -> SimilarityIC[NodeT]: """SimilarityIC instance for the specified nodes""" return SimilarityIC(self, node_0, node_1, ic_metric) def similarity_metrics( self, - node_0: Node, - node_1: Node, + node_0: NodeT, + node_1: NodeT, ic_metric: str = "intrinsic_ic_sanchez", keys: list[str] | None = None, ) -> dict[str, Any]: @@ -197,8 +197,8 @@ def similarity_metrics( def compute_similarities( self, - source_nodes: Iterable[Node], - target_nodes: Iterable[Node], + source_nodes: Iterable[NodeT], + target_nodes: Iterable[NodeT], ic_metrics: list[str] | tuple[str, ...] = ("intrinsic_ic_sanchez",), ) -> Iterable[dict[str, Any]]: """ @@ -213,16 +213,16 @@ def compute_similarities( yield metrics @classmethod - def _get_node_info_cls(cls) -> type[Node_Info[Node]]: + def _get_node_info_cls(cls) -> type[NodeInfo[NodeT]]: """ Return the Node_Info class to use for this ontology. Subclasses can override this to use a custom Node_Info class. For the complexity of typing this method, see . """ - return Node_Info + return NodeInfo - def node_info(self, node: Node) -> Node_Info[Node]: + def node_info(self, node: NodeT) -> NodeInfo[NodeT]: """ Return Node_Info instance for `node`. If frozen, cache node info in `self._node_info_cache`. @@ -235,8 +235,8 @@ def node_info(self, node: Node) -> Node_Info[Node]: return self._node_info_cache[node] @cache_on_frozen - def _get_name_to_node_info(self) -> dict[str, Node_Info[Node]]: - name_to_node_info: dict[str, Node_Info[Node]] = {} + def _get_name_to_node_info(self) -> dict[str, NodeInfo[NodeT]]: + name_to_node_info: dict[str, NodeInfo[NodeT]] = {} for node in self.graph: info = self.node_info(node) name = info.name @@ -249,7 +249,7 @@ def _get_name_to_node_info(self) -> dict[str, Node_Info[Node]]: name_to_node_info[name] = info return name_to_node_info - def node_info_by_name(self, name: str) -> Node_Info[Node]: + def node_info_by_name(self, name: str) -> NodeInfo[NodeT]: """ Return Node_Info instance using a lookup by name. """ diff --git a/nxontology/similarity.py b/nxontology/similarity.py index 5fbe596..cf88253 100644 --- a/nxontology/similarity.py +++ b/nxontology/similarity.py @@ -8,11 +8,11 @@ from networkx import shortest_path_length -from nxontology.node import Node, Node_Info +from nxontology.node import NodeInfo, NodeT from nxontology.utils import Freezable, cache_on_frozen -class Similarity(Freezable, Generic[Node]): +class Similarity(Freezable, Generic[NodeT]): """ Compute intrinsic similarity metrics for a pair of nodes. """ @@ -29,7 +29,7 @@ class Similarity(Freezable, Generic[Node]): "batet_log", ] - def __init__(self, nxo: NXOntology[Node], node_0: Node, node_1: Node): + def __init__(self, nxo: NXOntology[NodeT], node_0: NodeT, node_1: NodeT): self.nxo = nxo self.node_0 = node_0 self.node_1 = node_1 @@ -68,12 +68,12 @@ def depth(self) -> int | None: @property @cache_on_frozen - def common_ancestors(self) -> set[Node]: + def common_ancestors(self) -> set[NodeT]: return self.info_0.ancestors & self.info_1.ancestors @property @cache_on_frozen - def union_ancestors(self) -> set[Node]: + def union_ancestors(self) -> set[NodeT]: return self.info_0.ancestors | self.info_1.ancestors @property @@ -116,7 +116,7 @@ def results(self, keys: list[str] | None = None) -> dict[str, Any]: return {key: getattr(self, key) for key in keys} -class SimilarityIC(Similarity[Node]): +class SimilarityIC(Similarity[NodeT]): """ Compute intrinsic similarity metrics for a pair of nodes, including Information Content (IC) derived metrics. @@ -125,9 +125,9 @@ class SimilarityIC(Similarity[Node]): def __init__( self, - nxo: NXOntology[Node], - node_0: Node, - node_1: Node, + nxo: NXOntology[NodeT], + node_0: NodeT, + node_1: NodeT, ic_metric: str = "intrinsic_ic_sanchez", ): super().__init__(nxo, node_0, node_1) @@ -151,7 +151,7 @@ def __init__( "jiang_seco", ] - def _get_ic(self, node_info: Node_Info[Node], ic_metric: str) -> float: + def _get_ic(self, node_info: NodeInfo[NodeT], ic_metric: str) -> float: ic = getattr(node_info, ic_metric) assert isinstance(ic, float) return ic @@ -174,7 +174,7 @@ def node_1_ic_scaled(self) -> float: @property @cache_on_frozen - def _resnik_mica(self) -> tuple[float, Node | None]: + def _resnik_mica(self) -> tuple[float, NodeT | None]: if not self.common_ancestors: return 0.0, None resnik, mica = max( @@ -185,7 +185,7 @@ def _resnik_mica(self) -> tuple[float, Node | None]: return resnik, mica @property - def mica(self) -> Node | None: + def mica(self) -> NodeT | None: """ Most informative common ancestor. None if no common ancestors exist. diff --git a/nxontology/tests/ontology_test.py b/nxontology/tests/ontology_test.py index 2f26767..02a77fc 100644 --- a/nxontology/tests/ontology_test.py +++ b/nxontology/tests/ontology_test.py @@ -6,7 +6,7 @@ import pytest from nxontology.exceptions import DuplicateError, NodeNotFound -from nxontology.node import Node_Info +from nxontology.node import NodeInfo from nxontology.ontology import NXOntology @@ -156,7 +156,7 @@ def test_node_info_not_found(metal_nxo_frozen: NXOntology[str]) -> None: def test_custom_node_info_class() -> None: - class CustomNodeInfo(Node_Info[str]): + class CustomNodeInfo(NodeInfo[str]): @property def custom_property(self) -> str: return "custom" diff --git a/nxontology/tests/similarity_test.py b/nxontology/tests/similarity_test.py index 5750b7c..d617354 100644 --- a/nxontology/tests/similarity_test.py +++ b/nxontology/tests/similarity_test.py @@ -6,7 +6,7 @@ import pytest from nxontology.examples import create_disconnected_nxo, create_metal_nxo -from nxontology.node import Node_Info +from nxontology.node import NodeInfo from nxontology.ontology import NXOntology from nxontology.similarity import Similarity, SimilarityIC @@ -74,7 +74,7 @@ def get_similarity_tsv(nxo: NXOntology[str]) -> str: sims = nxo.compute_similarities( source_nodes=nodes, target_nodes=nodes, - ic_metrics=Node_Info.ic_metrics, + ic_metrics=NodeInfo.ic_metrics, ) sim_df = pd.DataFrame(sims) tsv = sim_df.to_csv(sep="\t", index=False, float_format="%.3g", lineterminator="\n") diff --git a/nxontology/viz.py b/nxontology/viz.py index 6a3be1b..f6bfe5c 100644 --- a/nxontology/viz.py +++ b/nxontology/viz.py @@ -5,13 +5,13 @@ from networkx.drawing.nx_agraph import to_agraph from pygraphviz.agraph import AGraph -from nxontology.node import Node, Node_Info +from nxontology.node import NodeInfo, NodeT from nxontology.similarity import SimilarityIC def create_similarity_graphviz( - sim: SimilarityIC[Node], - nodes: Iterable[Node] | None = None, + sim: SimilarityIC[NodeT], + nodes: Iterable[NodeT] | None = None, ) -> AGraph: """ Create a pygraphviz AGraph to render the similarity subgraph with graphviz. @@ -81,7 +81,7 @@ def create_similarity_graphviz( return gviz -def get_verbose_node_label(info: Node_Info[Node]) -> str: +def get_verbose_node_label(info: NodeInfo[NodeT]) -> str: """Return verbose label like 'name (identifier)'.""" verbose_label = info.name assert isinstance(verbose_label, str)