Coverage for python/lsst/pipe/base/graph/quantumNode.py: 64%
65 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-12 11:14 -0700
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-12 11:14 -0700
1# This file is part of pipe_base.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21from __future__ import annotations
23__all__ = ("QuantumNode", "NodeId", "BuildId")
25import uuid
26import warnings
27from dataclasses import dataclass
28from typing import Any, NewType
30from lsst.daf.butler import (
31 DatasetRef,
32 DimensionRecord,
33 DimensionRecordsAccumulator,
34 DimensionUniverse,
35 Quantum,
36 SerializedQuantum,
37)
38from lsst.utils.introspection import find_outside_stacklevel
40try:
41 from pydantic.v1 import BaseModel
42except ModuleNotFoundError:
43 from pydantic import BaseModel # type: ignore
45from ..pipeline import TaskDef
47BuildId = NewType("BuildId", str)
50def _hashDsRef(ref: DatasetRef) -> int:
51 return hash((ref.datasetType, ref.dataId))
54@dataclass(frozen=True, eq=True)
55class NodeId:
56 """Deprecated, this class is used with QuantumGraph save formats of
57 1 and 2 when unpicking objects and must be retained until those formats
58 are considered unloadable.
60 This represents an unique identifier of a node within an individual
61 construction of a `QuantumGraph`. This identifier will stay constant
62 through a pickle, and any `QuantumGraph` methods that return a new
63 `QuantumGraph`.
65 A `NodeId` will not be the same if a new graph is built containing the same
66 information in a `QuantumNode`, or even built from exactly the same inputs.
68 `NodeId`s do not play any role in deciding the equality or identity (hash)
69 of a `QuantumNode`, and are mainly useful in debugging or working with
70 various subsets of the same graph.
72 This interface is a convenance only, and no guarantees on long term
73 stability are made. New implementations might change the `NodeId`, or
74 provide more or less guarantees.
75 """
77 number: int
78 """The unique position of the node within the graph assigned at graph
79 creation.
80 """
81 buildId: BuildId
82 """Unique identifier created at the time the originating graph was created
83 """
86@dataclass(frozen=True)
87class QuantumNode:
88 """Class representing a node in the quantum graph.
90 The ``quantum`` attribute represents the data that is to be processed at
91 this node.
92 """
94 quantum: Quantum
95 """The unit of data that is to be processed by this graph node"""
96 taskDef: TaskDef
97 """Definition of the task that will process the `Quantum` associated with
98 this node.
99 """
100 nodeId: uuid.UUID
101 """The unique position of the node within the graph assigned at graph
102 creation.
103 """
105 __slots__ = ("quantum", "taskDef", "nodeId", "_precomputedHash")
107 def __post_init__(self) -> None:
108 # use setattr here to preserve the frozenness of the QuantumNode
109 self._precomputedHash: int
110 object.__setattr__(self, "_precomputedHash", hash((self.taskDef.label, self.quantum)))
112 def __eq__(self, other: object) -> bool:
113 if not isinstance(other, QuantumNode):
114 return False
115 if self.quantum != other.quantum:
116 return False
117 return self.taskDef == other.taskDef
119 def __hash__(self) -> int:
120 """For graphs it is useful to have a more robust hash than provided
121 by the default quantum id based hashing
122 """
123 return self._precomputedHash
125 def __repr__(self) -> str:
126 """Make more human readable string representation."""
127 return (
128 f"{self.__class__.__name__}(quantum={self.quantum}, taskDef={self.taskDef}, nodeId={self.nodeId})"
129 )
131 def to_simple(self, accumulator: DimensionRecordsAccumulator | None = None) -> SerializedQuantumNode:
132 return SerializedQuantumNode(
133 quantum=self.quantum.to_simple(accumulator=accumulator),
134 taskLabel=self.taskDef.label,
135 nodeId=self.nodeId,
136 )
138 @classmethod
139 def from_simple(
140 cls,
141 simple: SerializedQuantumNode,
142 taskDefMap: dict[str, TaskDef],
143 universe: DimensionUniverse,
144 recontitutedDimensions: dict[int, tuple[str, DimensionRecord]] | None = None,
145 ) -> QuantumNode:
146 if recontitutedDimensions is not None:
147 warnings.warn(
148 "The recontitutedDimensions argument is now ignored and may be removed after v 27",
149 category=FutureWarning,
150 stacklevel=find_outside_stacklevel("lsst.pipe.base"),
151 )
152 return QuantumNode(
153 quantum=Quantum.from_simple(simple.quantum, universe),
154 taskDef=taskDefMap[simple.taskLabel],
155 nodeId=simple.nodeId,
156 )
159_fields_set = {"quantum", "taskLabel", "nodeId"}
162class SerializedQuantumNode(BaseModel):
163 """Model representing a `QuantumNode` in serializable form."""
165 quantum: SerializedQuantum
166 taskLabel: str
167 nodeId: uuid.UUID
169 @classmethod
170 def direct(cls, *, quantum: dict[str, Any], taskLabel: str, nodeId: str) -> SerializedQuantumNode:
171 node = SerializedQuantumNode.__new__(cls)
172 setter = object.__setattr__
173 setter(node, "quantum", SerializedQuantum.direct(**quantum))
174 setter(node, "taskLabel", taskLabel)
175 setter(node, "nodeId", uuid.UUID(nodeId))
176 setter(node, "__fields_set__", _fields_set)
177 return node