Coverage for python/lsst/daf/butler/core/dimensions/_dataCoordinateIterable.py: 37%
205 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-10-02 08:00 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-10-02 08:00 +0000
1# This file is part of daf_butler.
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 software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
28from __future__ import annotations
30__all__ = (
31 "DataCoordinateIterable",
32 "DataCoordinateSet",
33 "DataCoordinateSequence",
34)
36from abc import abstractmethod
37from collections.abc import Collection, Iterable, Iterator, Sequence, Set
38from typing import Any, overload
40from ._coordinate import DataCoordinate
41from ._graph import DimensionGraph
42from ._universe import DimensionUniverse
45class DataCoordinateIterable(Iterable[DataCoordinate]):
46 """An abstract base class for homogeneous iterables of data IDs.
48 All elements of a `DataCoordinateIterable` identify the same set of
49 dimensions (given by the `graph` property) and generally have the same
50 `DataCoordinate.hasFull` and `DataCoordinate.hasRecords` flag values.
51 """
53 __slots__ = ()
55 @staticmethod
56 def fromScalar(dataId: DataCoordinate) -> _ScalarDataCoordinateIterable:
57 """Return a `DataCoordinateIterable` containing the single data ID.
59 Parameters
60 ----------
61 dataId : `DataCoordinate`
62 Data ID to adapt. Must be a true `DataCoordinate` instance, not
63 an arbitrary mapping. No runtime checking is performed.
65 Returns
66 -------
67 iterable : `DataCoordinateIterable`
68 A `DataCoordinateIterable` instance of unspecified (i.e.
69 implementation-detail) subclass. Guaranteed to implement
70 the `collections.abc.Sized` (i.e. `__len__`) and
71 `collections.abc.Container` (i.e. `__contains__`) interfaces as
72 well as that of `DataCoordinateIterable`.
73 """
74 return _ScalarDataCoordinateIterable(dataId)
76 @property
77 @abstractmethod
78 def graph(self) -> DimensionGraph:
79 """Dimensions identified by these data IDs (`DimensionGraph`)."""
80 raise NotImplementedError()
82 @property
83 def universe(self) -> DimensionUniverse:
84 """Universe that defines all known compatible dimensions.
86 (`DimensionUniverse`).
87 """
88 return self.graph.universe
90 @abstractmethod
91 def hasFull(self) -> bool:
92 """Indicate if all data IDs in this iterable identify all dimensions.
94 Not just required dimensions.
96 Returns
97 -------
98 state : `bool`
99 If `True`, ``all(d.hasFull() for d in iterable)`` is guaranteed.
100 If `False`, no guarantees are made.
101 """
102 raise NotImplementedError()
104 @abstractmethod
105 def hasRecords(self) -> bool:
106 """Return whether all data IDs in this iterable contain records.
108 Returns
109 -------
110 state : `bool`
111 If `True`, ``all(d.hasRecords() for d in iterable)`` is guaranteed.
112 If `False`, no guarantees are made.
113 """
114 raise NotImplementedError()
116 def toSet(self) -> DataCoordinateSet:
117 """Transform this iterable into a `DataCoordinateSet`.
119 Returns
120 -------
121 set : `DataCoordinateSet`
122 A `DatasetCoordinateSet` instance with the same elements as
123 ``self``, after removing any duplicates. May be ``self`` if it is
124 already a `DataCoordinateSet`.
125 """
126 return DataCoordinateSet(
127 frozenset(self),
128 graph=self.graph,
129 hasFull=self.hasFull(),
130 hasRecords=self.hasRecords(),
131 check=False,
132 )
134 def toSequence(self) -> DataCoordinateSequence:
135 """Transform this iterable into a `DataCoordinateSequence`.
137 Returns
138 -------
139 seq : `DataCoordinateSequence`
140 A new `DatasetCoordinateSequence` with the same elements as
141 ``self``, in the same order. May be ``self`` if it is already a
142 `DataCoordinateSequence`.
143 """
144 return DataCoordinateSequence(
145 tuple(self), graph=self.graph, hasFull=self.hasFull(), hasRecords=self.hasRecords(), check=False
146 )
148 @abstractmethod
149 def subset(self, graph: DimensionGraph) -> DataCoordinateIterable:
150 """Return a subset iterable.
152 This subset iterable returns data IDs that identify a subset of the
153 dimensions that this one's do.
155 Parameters
156 ----------
157 graph : `DimensionGraph`
158 Dimensions to be identified by the data IDs in the returned
159 iterable. Must be a subset of ``self.graph``.
161 Returns
162 -------
163 iterable : `DataCoordinateIterable`
164 A `DataCoordinateIterable` with ``iterable.graph == graph``.
165 May be ``self`` if ``graph == self.graph``. Elements are
166 equivalent to those that would be created by calling
167 `DataCoordinate.subset` on all elements in ``self``, possibly
168 with deduplication and/or reordering (depending on the subclass,
169 which may make more specific guarantees).
170 """
171 raise NotImplementedError()
174class _ScalarDataCoordinateIterable(DataCoordinateIterable):
175 """An iterable for a single `DataCoordinate`.
177 A `DataCoordinateIterable` implementation that adapts a single
178 `DataCoordinate` instance.
180 This class should only be used directly by other code in the module in
181 which it is defined; all other code should interact with it only through
182 the `DataCoordinateIterable` interface.
184 Parameters
185 ----------
186 dataId : `DataCoordinate`
187 The data ID to adapt.
188 """
190 def __init__(self, dataId: DataCoordinate):
191 self._dataId = dataId
193 __slots__ = ("_dataId",)
195 def __iter__(self) -> Iterator[DataCoordinate]:
196 yield self._dataId
198 def __len__(self) -> int:
199 return 1
201 def __contains__(self, key: Any) -> bool:
202 if isinstance(key, DataCoordinate):
203 return key == self._dataId
204 else:
205 return False
207 @property
208 def graph(self) -> DimensionGraph:
209 # Docstring inherited from DataCoordinateIterable.
210 return self._dataId.graph
212 def hasFull(self) -> bool:
213 # Docstring inherited from DataCoordinateIterable.
214 return self._dataId.hasFull()
216 def hasRecords(self) -> bool:
217 # Docstring inherited from DataCoordinateIterable.
218 return self._dataId.hasRecords()
220 def subset(self, graph: DimensionGraph) -> _ScalarDataCoordinateIterable:
221 # Docstring inherited from DataCoordinateIterable.
222 return _ScalarDataCoordinateIterable(self._dataId.subset(graph))
225class _DataCoordinateCollectionBase(DataCoordinateIterable):
226 """A partial iterable implementation backed by native Python collection.
228 A partial `DataCoordinateIterable` implementation that is backed by a
229 native Python collection.
231 This class is intended only to be used as an intermediate base class for
232 `DataCoordinateIterables` that assume a more specific type of collection
233 and can hence make more informed choices for how to implement some methods.
235 Parameters
236 ----------
237 dataIds : `collections.abc.Collection` [ `DataCoordinate` ]
238 A collection of `DataCoordinate` instances, with dimensions equal to
239 ``graph``.
240 graph : `DimensionGraph`
241 Dimensions identified by all data IDs in the set.
242 hasFull : `bool`, optional
243 If `True`, the caller guarantees that `DataCoordinate.hasFull` returns
244 `True` for all given data IDs. If `False`, no such guarantee is made,
245 and `hasFull` will always return `False`. If `None` (default),
246 `hasFull` will be computed from the given data IDs, immediately if
247 ``check`` is `True`, or on first use if ``check`` is `False`.
248 hasRecords : `bool`, optional
249 If `True`, the caller guarantees that `DataCoordinate.hasRecords`
250 returns `True` for all given data IDs. If `False`, no such guarantee
251 is made and `hasRecords` will always return `False`. If `None`
252 (default), `hasRecords` will be computed from the given data IDs,
253 immediately if ``check`` is `True`, or on first use if ``check`` is
254 `False`.
255 check: `bool`, optional
256 If `True` (default) check that all data IDs are consistent with the
257 given ``graph`` and state flags at construction. If `False`, no
258 checking will occur.
259 """
261 def __init__(
262 self,
263 dataIds: Collection[DataCoordinate],
264 graph: DimensionGraph,
265 *,
266 hasFull: bool | None = None,
267 hasRecords: bool | None = None,
268 check: bool = True,
269 ):
270 self._dataIds = dataIds
271 self._graph = graph
272 if check:
273 for dataId in self._dataIds:
274 if hasFull and not dataId.hasFull():
275 raise ValueError(f"{dataId} is not complete, but is required to be.")
276 if hasRecords and not dataId.hasRecords():
277 raise ValueError(f"{dataId} has no records, but is required to.")
278 if dataId.graph != self._graph:
279 raise ValueError(f"Bad dimensions {dataId.graph}; expected {self._graph}.")
280 if hasFull is None:
281 hasFull = all(dataId.hasFull() for dataId in self._dataIds)
282 if hasRecords is None:
283 hasRecords = all(dataId.hasRecords() for dataId in self._dataIds)
284 self._hasFull = hasFull
285 self._hasRecords = hasRecords
287 __slots__ = ("_graph", "_dataIds", "_hasFull", "_hasRecords")
289 @property
290 def graph(self) -> DimensionGraph:
291 # Docstring inherited from DataCoordinateIterable.
292 return self._graph
294 def hasFull(self) -> bool:
295 # Docstring inherited from DataCoordinateIterable.
296 if self._hasFull is None:
297 self._hasFull = all(dataId.hasFull() for dataId in self._dataIds)
298 return self._hasFull
300 def hasRecords(self) -> bool:
301 # Docstring inherited from DataCoordinateIterable.
302 if self._hasRecords is None:
303 self._hasRecords = all(dataId.hasRecords() for dataId in self._dataIds)
304 return self._hasRecords
306 def toSet(self) -> DataCoordinateSet:
307 # Docstring inherited from DataCoordinateIterable.
308 # Override base class to pass in attributes instead of results of
309 # method calls for _hasFull and _hasRecords - those can be None,
310 # and hence defer checking if that's what the user originally wanted.
311 return DataCoordinateSet(
312 frozenset(self._dataIds),
313 graph=self._graph,
314 hasFull=self._hasFull,
315 hasRecords=self._hasRecords,
316 check=False,
317 )
319 def toSequence(self) -> DataCoordinateSequence:
320 # Docstring inherited from DataCoordinateIterable.
321 # Override base class to pass in attributes instead of results of
322 # method calls for _hasFull and _hasRecords - those can be None,
323 # and hence defer checking if that's what the user originally wanted.
324 return DataCoordinateSequence(
325 tuple(self._dataIds),
326 graph=self._graph,
327 hasFull=self._hasFull,
328 hasRecords=self._hasRecords,
329 check=False,
330 )
332 def __iter__(self) -> Iterator[DataCoordinate]:
333 return iter(self._dataIds)
335 def __len__(self) -> int:
336 return len(self._dataIds)
338 def __contains__(self, key: Any) -> bool:
339 key = DataCoordinate.standardize(key, universe=self.universe)
340 return key in self._dataIds
342 def _subsetKwargs(self, graph: DimensionGraph) -> dict[str, Any]:
343 """Return constructor kwargs useful for subclasses implementing subset.
345 Parameters
346 ----------
347 graph : `DimensionGraph`
348 Dimensions passed to `subset`.
350 Returns
351 -------
352 **kwargs
353 A dict with `hasFull`, `hasRecords`, and `check` keys, associated
354 with the appropriate values for a `subset` operation with the given
355 dimensions.
356 """
357 hasFull: bool | None
358 if graph.dimensions <= self.graph.required:
359 hasFull = True
360 else:
361 hasFull = self._hasFull
362 return dict(hasFull=hasFull, hasRecords=self._hasRecords, check=False)
365class DataCoordinateSet(_DataCoordinateCollectionBase):
366 """Iterable iteration that is set-like.
368 A `DataCoordinateIterable` implementation that adds some set-like
369 functionality, and is backed by a true set-like object.
371 Parameters
372 ----------
373 dataIds : `collections.abc.Set` [ `DataCoordinate` ]
374 A set of `DataCoordinate` instances, with dimensions equal to
375 ``graph``. If this is a mutable object, the caller must be able to
376 guarantee that it will not be modified by any other holders.
377 graph : `DimensionGraph`
378 Dimensions identified by all data IDs in the set.
379 hasFull : `bool`, optional
380 If `True`, the caller guarantees that `DataCoordinate.hasFull` returns
381 `True` for all given data IDs. If `False`, no such guarantee is made,
382 and `DataCoordinateSet.hasFull` will always return `False`. If `None`
383 (default), `DataCoordinateSet.hasFull` will be computed from the given
384 data IDs, immediately if ``check`` is `True`, or on first use if
385 ``check`` is `False`.
386 hasRecords : `bool`, optional
387 If `True`, the caller guarantees that `DataCoordinate.hasRecords`
388 returns `True` for all given data IDs. If `False`, no such guarantee
389 is made and `DataCoordinateSet.hasRecords` will always return `False`.
390 If `None` (default), `DataCoordinateSet.hasRecords` will be computed
391 from the given data IDs, immediately if ``check`` is `True`, or on
392 first use if ``check`` is `False`.
393 check: `bool`, optional
394 If `True` (default) check that all data IDs are consistent with the
395 given ``graph`` and state flags at construction. If `False`, no
396 checking will occur.
398 Notes
399 -----
400 `DataCoordinateSet` does not formally implement the `collections.abc.Set`
401 interface, because that requires many binary operations to accept any
402 set-like object as the other argument (regardless of what its elements
403 might be), and it's much easier to ensure those operations never behave
404 surprisingly if we restrict them to `DataCoordinateSet` or (sometimes)
405 `DataCoordinateIterable`, and in most cases restrict that they identify
406 the same dimensions. In particular:
408 - a `DataCoordinateSet` will compare as not equal to any object that is
409 not a `DataCoordinateSet`, even native Python sets containing the exact
410 same elements;
412 - subset/superset comparison _operators_ (``<``, ``>``, ``<=``, ``>=``)
413 require both operands to be `DataCoordinateSet` instances that have the
414 same dimensions (i.e. ``graph`` attribute);
416 - `issubset`, `issuperset`, and `isdisjoint` require the other argument to
417 be a `DataCoordinateIterable` with the same dimensions;
419 - operators that create new sets (``&``, ``|``, ``^``, ``-``) require both
420 operands to be `DataCoordinateSet` instances that have the same
421 dimensions _and_ the same ``dtype``;
423 - named methods that create new sets (`intersection`, `union`,
424 `symmetric_difference`, `difference`) require the other operand to be a
425 `DataCoordinateIterable` with the same dimensions _and_ the same
426 ``dtype``.
428 In addition, when the two operands differ in the return values of `hasFull`
429 and/or `hasRecords`, we make no guarantees about what those methods will
430 return on the new `DataCoordinateSet` (other than that they will accurately
431 reflect what elements are in the new set - we just don't control which
432 elements are contributed by each operand).
433 """
435 def __init__(
436 self,
437 dataIds: Set[DataCoordinate],
438 graph: DimensionGraph,
439 *,
440 hasFull: bool | None = None,
441 hasRecords: bool | None = None,
442 check: bool = True,
443 ):
444 super().__init__(dataIds, graph, hasFull=hasFull, hasRecords=hasRecords, check=check)
446 _dataIds: Set[DataCoordinate]
448 __slots__ = ()
450 def __str__(self) -> str:
451 return str(set(self._dataIds))
453 def __repr__(self) -> str:
454 return (
455 f"DataCoordinateSet({set(self._dataIds)}, {self._graph!r}, "
456 f"hasFull={self._hasFull}, hasRecords={self._hasRecords})"
457 )
459 def __eq__(self, other: Any) -> bool:
460 if isinstance(other, DataCoordinateSet):
461 return self._graph == other._graph and self._dataIds == other._dataIds
462 return False
464 def __le__(self, other: DataCoordinateSet) -> bool:
465 if self.graph != other.graph:
466 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
467 return self._dataIds <= other._dataIds
469 def __ge__(self, other: DataCoordinateSet) -> bool:
470 if self.graph != other.graph:
471 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
472 return self._dataIds >= other._dataIds
474 def __lt__(self, other: DataCoordinateSet) -> bool:
475 if self.graph != other.graph:
476 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
477 return self._dataIds < other._dataIds
479 def __gt__(self, other: DataCoordinateSet) -> bool:
480 if self.graph != other.graph:
481 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
482 return self._dataIds > other._dataIds
484 def issubset(self, other: DataCoordinateIterable) -> bool:
485 """Test whether ``self`` contains all data IDs in ``other``.
487 Parameters
488 ----------
489 other : `DataCoordinateIterable`
490 An iterable of data IDs with ``other.graph == self.graph``.
492 Returns
493 -------
494 issubset : `bool`
495 `True` if all data IDs in ``self`` are also in ``other``, and
496 `False` otherwise.
497 """
498 if self.graph != other.graph:
499 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
500 return self._dataIds <= other.toSet()._dataIds
502 def issuperset(self, other: DataCoordinateIterable) -> bool:
503 """Test whether ``other`` contains all data IDs in ``self``.
505 Parameters
506 ----------
507 other : `DataCoordinateIterable`
508 An iterable of data IDs with ``other.graph == self.graph``.
510 Returns
511 -------
512 issuperset : `bool`
513 `True` if all data IDs in ``other`` are also in ``self``, and
514 `False` otherwise.
515 """
516 if self.graph != other.graph:
517 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
518 return self._dataIds >= other.toSet()._dataIds
520 def isdisjoint(self, other: DataCoordinateIterable) -> bool:
521 """Test whether there are no data IDs in both ``self`` and ``other``.
523 Parameters
524 ----------
525 other : `DataCoordinateIterable`
526 An iterable of data IDs with ``other.graph == self.graph``.
528 Returns
529 -------
530 isdisjoint : `bool`
531 `True` if there are no data IDs in both ``self`` and ``other``, and
532 `False` otherwise.
533 """
534 if self.graph != other.graph:
535 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
536 return self._dataIds.isdisjoint(other.toSet()._dataIds)
538 def __and__(self, other: DataCoordinateSet) -> DataCoordinateSet:
539 if self.graph != other.graph:
540 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
541 return DataCoordinateSet(self._dataIds & other._dataIds, self.graph, check=False)
543 def __or__(self, other: DataCoordinateSet) -> DataCoordinateSet:
544 if self.graph != other.graph:
545 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
546 return DataCoordinateSet(self._dataIds | other._dataIds, self.graph, check=False)
548 def __xor__(self, other: DataCoordinateSet) -> DataCoordinateSet:
549 if self.graph != other.graph:
550 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
551 return DataCoordinateSet(self._dataIds ^ other._dataIds, self.graph, check=False)
553 def __sub__(self, other: DataCoordinateSet) -> DataCoordinateSet:
554 if self.graph != other.graph:
555 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
556 return DataCoordinateSet(self._dataIds - other._dataIds, self.graph, check=False)
558 def intersection(self, other: DataCoordinateIterable) -> DataCoordinateSet:
559 """Return a new set that contains all data IDs from parameters.
561 Parameters
562 ----------
563 other : `DataCoordinateIterable`
564 An iterable of data IDs with ``other.graph == self.graph``.
566 Returns
567 -------
568 intersection : `DataCoordinateSet`
569 A new `DataCoordinateSet` instance.
570 """
571 if self.graph != other.graph:
572 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
573 return DataCoordinateSet(self._dataIds & other.toSet()._dataIds, self.graph, check=False)
575 def union(self, other: DataCoordinateIterable) -> DataCoordinateSet:
576 """Return a new set that contains all data IDs in either parameters.
578 Parameters
579 ----------
580 other : `DataCoordinateIterable`
581 An iterable of data IDs with ``other.graph == self.graph``.
583 Returns
584 -------
585 intersection : `DataCoordinateSet`
586 A new `DataCoordinateSet` instance.
587 """
588 if self.graph != other.graph:
589 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
590 return DataCoordinateSet(self._dataIds | other.toSet()._dataIds, self.graph, check=False)
592 def symmetric_difference(self, other: DataCoordinateIterable) -> DataCoordinateSet:
593 """Return a new set with all data IDs in either parameters, not both.
595 Parameters
596 ----------
597 other : `DataCoordinateIterable`
598 An iterable of data IDs with ``other.graph == self.graph``.
600 Returns
601 -------
602 intersection : `DataCoordinateSet`
603 A new `DataCoordinateSet` instance.
604 """
605 if self.graph != other.graph:
606 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
607 return DataCoordinateSet(self._dataIds ^ other.toSet()._dataIds, self.graph, check=False)
609 def difference(self, other: DataCoordinateIterable) -> DataCoordinateSet:
610 """Return a new set with all data IDs in this that are not in other.
612 Parameters
613 ----------
614 other : `DataCoordinateIterable`
615 An iterable of data IDs with ``other.graph == self.graph``.
617 Returns
618 -------
619 intersection : `DataCoordinateSet`
620 A new `DataCoordinateSet` instance.
621 """
622 if self.graph != other.graph:
623 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
624 return DataCoordinateSet(self._dataIds - other.toSet()._dataIds, self.graph, check=False)
626 def toSet(self) -> DataCoordinateSet:
627 # Docstring inherited from DataCoordinateIterable.
628 return self
630 def subset(self, graph: DimensionGraph) -> DataCoordinateSet:
631 """Return a set whose data IDs identify a subset.
633 Parameters
634 ----------
635 graph : `DimensionGraph`
636 Dimensions to be identified by the data IDs in the returned
637 iterable. Must be a subset of ``self.graph``.
639 Returns
640 -------
641 set : `DataCoordinateSet`
642 A `DataCoordinateSet` with ``set.graph == graph``.
643 Will be ``self`` if ``graph == self.graph``. Elements are
644 equivalent to those that would be created by calling
645 `DataCoordinate.subset` on all elements in ``self``, with
646 deduplication but and in arbitrary order.
647 """
648 if graph == self.graph:
649 return self
650 return DataCoordinateSet(
651 {dataId.subset(graph) for dataId in self._dataIds}, graph, **self._subsetKwargs(graph)
652 )
655class DataCoordinateSequence(_DataCoordinateCollectionBase, Sequence[DataCoordinate]):
656 """Iterable supporting the full Sequence interface.
658 A `DataCoordinateIterable` implementation that supports the full
659 `collections.abc.Sequence` interface.
661 Parameters
662 ----------
663 dataIds : `collections.abc.Sequence` [ `DataCoordinate` ]
664 A sequence of `DataCoordinate` instances, with dimensions equal to
665 ``graph``.
666 graph : `DimensionGraph`
667 Dimensions identified by all data IDs in the set.
668 hasFull : `bool`, optional
669 If `True`, the caller guarantees that `DataCoordinate.hasFull` returns
670 `True` for all given data IDs. If `False`, no such guarantee is made,
671 and `DataCoordinateSet.hasFull` will always return `False`. If `None`
672 (default), `DataCoordinateSet.hasFull` will be computed from the given
673 data IDs, immediately if ``check`` is `True`, or on first use if
674 ``check`` is `False`.
675 hasRecords : `bool`, optional
676 If `True`, the caller guarantees that `DataCoordinate.hasRecords`
677 returns `True` for all given data IDs. If `False`, no such guarantee
678 is made and `DataCoordinateSet.hasRecords` will always return `False`.
679 If `None` (default), `DataCoordinateSet.hasRecords` will be computed
680 from the given data IDs, immediately if ``check`` is `True`, or on
681 first use if ``check`` is `False`.
682 check: `bool`, optional
683 If `True` (default) check that all data IDs are consistent with the
684 given ``graph`` and state flags at construction. If `False`, no
685 checking will occur.
686 """
688 def __init__(
689 self,
690 dataIds: Sequence[DataCoordinate],
691 graph: DimensionGraph,
692 *,
693 hasFull: bool | None = None,
694 hasRecords: bool | None = None,
695 check: bool = True,
696 ):
697 super().__init__(tuple(dataIds), graph, hasFull=hasFull, hasRecords=hasRecords, check=check)
699 _dataIds: Sequence[DataCoordinate]
701 __slots__ = ()
703 def __str__(self) -> str:
704 return str(tuple(self._dataIds))
706 def __repr__(self) -> str:
707 return (
708 f"DataCoordinateSequence({tuple(self._dataIds)}, {self._graph!r}, "
709 f"hasFull={self._hasFull}, hasRecords={self._hasRecords})"
710 )
712 def __eq__(self, other: Any) -> bool:
713 if isinstance(other, DataCoordinateSequence):
714 return self._graph == other._graph and self._dataIds == other._dataIds
715 return False
717 @overload
718 def __getitem__(self, index: int) -> DataCoordinate:
719 pass
721 @overload
722 def __getitem__(self, index: slice) -> DataCoordinateSequence:
723 pass
725 def __getitem__(self, index: Any) -> Any:
726 r = self._dataIds[index]
727 if isinstance(index, slice):
728 return DataCoordinateSequence(
729 r, self._graph, hasFull=self._hasFull, hasRecords=self._hasRecords, check=False
730 )
731 return r
733 def toSequence(self) -> DataCoordinateSequence:
734 # Docstring inherited from DataCoordinateIterable.
735 return self
737 def subset(self, graph: DimensionGraph) -> DataCoordinateSequence:
738 """Return a sequence whose data IDs identify a subset.
740 Parameters
741 ----------
742 graph : `DimensionGraph`
743 Dimensions to be identified by the data IDs in the returned
744 iterable. Must be a subset of ``self.graph``.
746 Returns
747 -------
748 set : `DataCoordinateSequence`
749 A `DataCoordinateSequence` with ``set.graph == graph``.
750 Will be ``self`` if ``graph == self.graph``. Elements are
751 equivalent to those that would be created by calling
752 `DataCoordinate.subset` on all elements in ``self``, in the same
753 order and with no deduplication.
754 """
755 if graph == self.graph:
756 return self
757 return DataCoordinateSequence(
758 tuple(dataId.subset(graph) for dataId in self._dataIds), graph, **self._subsetKwargs(graph)
759 )