Coverage for python/lsst/daf/butler/core/dimensions/_dataCoordinateIterable.py: 37%
205 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-25 15:14 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-25 15:14 +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 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/>.
22from __future__ import annotations
24__all__ = (
25 "DataCoordinateIterable",
26 "DataCoordinateSet",
27 "DataCoordinateSequence",
28)
30from abc import abstractmethod
31from collections.abc import Collection, Iterable, Iterator, Sequence, Set
32from typing import Any, overload
34from ._coordinate import DataCoordinate
35from ._graph import DimensionGraph
36from ._universe import DimensionUniverse
39class DataCoordinateIterable(Iterable[DataCoordinate]):
40 """An abstract base class for homogeneous iterables of data IDs.
42 All elements of a `DataCoordinateIterable` identify the same set of
43 dimensions (given by the `graph` property) and generally have the same
44 `DataCoordinate.hasFull` and `DataCoordinate.hasRecords` flag values.
45 """
47 __slots__ = ()
49 @staticmethod
50 def fromScalar(dataId: DataCoordinate) -> _ScalarDataCoordinateIterable:
51 """Return a `DataCoordinateIterable` containing the single data ID.
53 Parameters
54 ----------
55 dataId : `DataCoordinate`
56 Data ID to adapt. Must be a true `DataCoordinate` instance, not
57 an arbitrary mapping. No runtime checking is performed.
59 Returns
60 -------
61 iterable : `DataCoordinateIterable`
62 A `DataCoordinateIterable` instance of unspecified (i.e.
63 implementation-detail) subclass. Guaranteed to implement
64 the `collections.abc.Sized` (i.e. `__len__`) and
65 `collections.abc.Container` (i.e. `__contains__`) interfaces as
66 well as that of `DataCoordinateIterable`.
67 """
68 return _ScalarDataCoordinateIterable(dataId)
70 @property
71 @abstractmethod
72 def graph(self) -> DimensionGraph:
73 """Dimensions identified by these data IDs (`DimensionGraph`)."""
74 raise NotImplementedError()
76 @property
77 def universe(self) -> DimensionUniverse:
78 """Universe that defines all known compatible dimensions.
80 (`DimensionUniverse`).
81 """
82 return self.graph.universe
84 @abstractmethod
85 def hasFull(self) -> bool:
86 """Indicate if all data IDs in this iterable identify all dimensions.
88 Not just required dimensions.
90 Returns
91 -------
92 state : `bool`
93 If `True`, ``all(d.hasFull() for d in iterable)`` is guaranteed.
94 If `False`, no guarantees are made.
95 """
96 raise NotImplementedError()
98 @abstractmethod
99 def hasRecords(self) -> bool:
100 """Return whether all data IDs in this iterable contain records.
102 Returns
103 -------
104 state : `bool`
105 If `True`, ``all(d.hasRecords() for d in iterable)`` is guaranteed.
106 If `False`, no guarantees are made.
107 """
108 raise NotImplementedError()
110 def toSet(self) -> DataCoordinateSet:
111 """Transform this iterable into a `DataCoordinateSet`.
113 Returns
114 -------
115 set : `DataCoordinateSet`
116 A `DatasetCoordinateSet` instance with the same elements as
117 ``self``, after removing any duplicates. May be ``self`` if it is
118 already a `DataCoordinateSet`.
119 """
120 return DataCoordinateSet(
121 frozenset(self),
122 graph=self.graph,
123 hasFull=self.hasFull(),
124 hasRecords=self.hasRecords(),
125 check=False,
126 )
128 def toSequence(self) -> DataCoordinateSequence:
129 """Transform this iterable into a `DataCoordinateSequence`.
131 Returns
132 -------
133 seq : `DataCoordinateSequence`
134 A new `DatasetCoordinateSequence` with the same elements as
135 ``self``, in the same order. May be ``self`` if it is already a
136 `DataCoordinateSequence`.
137 """
138 return DataCoordinateSequence(
139 tuple(self), graph=self.graph, hasFull=self.hasFull(), hasRecords=self.hasRecords(), check=False
140 )
142 @abstractmethod
143 def subset(self, graph: DimensionGraph) -> DataCoordinateIterable:
144 """Return a subset iterable.
146 This subset iterable returns data IDs that identify a subset of the
147 dimensions that this one's do.
149 Parameters
150 ----------
151 graph : `DimensionGraph`
152 Dimensions to be identified by the data IDs in the returned
153 iterable. Must be a subset of ``self.graph``.
155 Returns
156 -------
157 iterable : `DataCoordinateIterable`
158 A `DataCoordinateIterable` with ``iterable.graph == graph``.
159 May be ``self`` if ``graph == self.graph``. Elements are
160 equivalent to those that would be created by calling
161 `DataCoordinate.subset` on all elements in ``self``, possibly
162 with deduplication and/or reordering (depending on the subclass,
163 which may make more specific guarantees).
164 """
165 raise NotImplementedError()
168class _ScalarDataCoordinateIterable(DataCoordinateIterable):
169 """An iterable for a single `DataCoordinate`.
171 A `DataCoordinateIterable` implementation that adapts a single
172 `DataCoordinate` instance.
174 This class should only be used directly by other code in the module in
175 which it is defined; all other code should interact with it only through
176 the `DataCoordinateIterable` interface.
178 Parameters
179 ----------
180 dataId : `DataCoordinate`
181 The data ID to adapt.
182 """
184 def __init__(self, dataId: DataCoordinate):
185 self._dataId = dataId
187 __slots__ = ("_dataId",)
189 def __iter__(self) -> Iterator[DataCoordinate]:
190 yield self._dataId
192 def __len__(self) -> int:
193 return 1
195 def __contains__(self, key: Any) -> bool:
196 if isinstance(key, DataCoordinate):
197 return key == self._dataId
198 else:
199 return False
201 @property
202 def graph(self) -> DimensionGraph:
203 # Docstring inherited from DataCoordinateIterable.
204 return self._dataId.graph
206 def hasFull(self) -> bool:
207 # Docstring inherited from DataCoordinateIterable.
208 return self._dataId.hasFull()
210 def hasRecords(self) -> bool:
211 # Docstring inherited from DataCoordinateIterable.
212 return self._dataId.hasRecords()
214 def subset(self, graph: DimensionGraph) -> _ScalarDataCoordinateIterable:
215 # Docstring inherited from DataCoordinateIterable.
216 return _ScalarDataCoordinateIterable(self._dataId.subset(graph))
219class _DataCoordinateCollectionBase(DataCoordinateIterable):
220 """A partial iterable implementation backed by native Python collection.
222 A partial `DataCoordinateIterable` implementation that is backed by a
223 native Python collection.
225 This class is intended only to be used as an intermediate base class for
226 `DataCoordinateIterables` that assume a more specific type of collection
227 and can hence make more informed choices for how to implement some methods.
229 Parameters
230 ----------
231 dataIds : `collections.abc.Collection` [ `DataCoordinate` ]
232 A collection of `DataCoordinate` instances, with dimensions equal to
233 ``graph``.
234 graph : `DimensionGraph`
235 Dimensions identified by all data IDs in the set.
236 hasFull : `bool`, optional
237 If `True`, the caller guarantees that `DataCoordinate.hasFull` returns
238 `True` for all given data IDs. If `False`, no such guarantee is made,
239 and `hasFull` will always return `False`. If `None` (default),
240 `hasFull` will be computed from the given data IDs, immediately if
241 ``check`` is `True`, or on first use if ``check`` is `False`.
242 hasRecords : `bool`, optional
243 If `True`, the caller guarantees that `DataCoordinate.hasRecords`
244 returns `True` for all given data IDs. If `False`, no such guarantee
245 is made and `hasRecords` will always return `False`. If `None`
246 (default), `hasRecords` will be computed from the given data IDs,
247 immediately if ``check`` is `True`, or on first use if ``check`` is
248 `False`.
249 check: `bool`, optional
250 If `True` (default) check that all data IDs are consistent with the
251 given ``graph`` and state flags at construction. If `False`, no
252 checking will occur.
253 """
255 def __init__(
256 self,
257 dataIds: Collection[DataCoordinate],
258 graph: DimensionGraph,
259 *,
260 hasFull: bool | None = None,
261 hasRecords: bool | None = None,
262 check: bool = True,
263 ):
264 self._dataIds = dataIds
265 self._graph = graph
266 if check:
267 for dataId in self._dataIds:
268 if hasFull and not dataId.hasFull():
269 raise ValueError(f"{dataId} is not complete, but is required to be.")
270 if hasRecords and not dataId.hasRecords():
271 raise ValueError(f"{dataId} has no records, but is required to.")
272 if dataId.graph != self._graph:
273 raise ValueError(f"Bad dimensions {dataId.graph}; expected {self._graph}.")
274 if hasFull is None:
275 hasFull = all(dataId.hasFull() for dataId in self._dataIds)
276 if hasRecords is None:
277 hasRecords = all(dataId.hasRecords() for dataId in self._dataIds)
278 self._hasFull = hasFull
279 self._hasRecords = hasRecords
281 __slots__ = ("_graph", "_dataIds", "_hasFull", "_hasRecords")
283 @property
284 def graph(self) -> DimensionGraph:
285 # Docstring inherited from DataCoordinateIterable.
286 return self._graph
288 def hasFull(self) -> bool:
289 # Docstring inherited from DataCoordinateIterable.
290 if self._hasFull is None:
291 self._hasFull = all(dataId.hasFull() for dataId in self._dataIds)
292 return self._hasFull
294 def hasRecords(self) -> bool:
295 # Docstring inherited from DataCoordinateIterable.
296 if self._hasRecords is None:
297 self._hasRecords = all(dataId.hasRecords() for dataId in self._dataIds)
298 return self._hasRecords
300 def toSet(self) -> DataCoordinateSet:
301 # Docstring inherited from DataCoordinateIterable.
302 # Override base class to pass in attributes instead of results of
303 # method calls for _hasFull and _hasRecords - those can be None,
304 # and hence defer checking if that's what the user originally wanted.
305 return DataCoordinateSet(
306 frozenset(self._dataIds),
307 graph=self._graph,
308 hasFull=self._hasFull,
309 hasRecords=self._hasRecords,
310 check=False,
311 )
313 def toSequence(self) -> DataCoordinateSequence:
314 # Docstring inherited from DataCoordinateIterable.
315 # Override base class to pass in attributes instead of results of
316 # method calls for _hasFull and _hasRecords - those can be None,
317 # and hence defer checking if that's what the user originally wanted.
318 return DataCoordinateSequence(
319 tuple(self._dataIds),
320 graph=self._graph,
321 hasFull=self._hasFull,
322 hasRecords=self._hasRecords,
323 check=False,
324 )
326 def __iter__(self) -> Iterator[DataCoordinate]:
327 return iter(self._dataIds)
329 def __len__(self) -> int:
330 return len(self._dataIds)
332 def __contains__(self, key: Any) -> bool:
333 key = DataCoordinate.standardize(key, universe=self.universe)
334 return key in self._dataIds
336 def _subsetKwargs(self, graph: DimensionGraph) -> dict[str, Any]:
337 """Return constructor kwargs useful for subclasses implementing subset.
339 Parameters
340 ----------
341 graph : `DimensionGraph`
342 Dimensions passed to `subset`.
344 Returns
345 -------
346 **kwargs
347 A dict with `hasFull`, `hasRecords`, and `check` keys, associated
348 with the appropriate values for a `subset` operation with the given
349 dimensions.
350 """
351 hasFull: bool | None
352 if graph.dimensions <= self.graph.required:
353 hasFull = True
354 else:
355 hasFull = self._hasFull
356 return dict(hasFull=hasFull, hasRecords=self._hasRecords, check=False)
359class DataCoordinateSet(_DataCoordinateCollectionBase):
360 """Iterable iteration that is set-like.
362 A `DataCoordinateIterable` implementation that adds some set-like
363 functionality, and is backed by a true set-like object.
365 Parameters
366 ----------
367 dataIds : `collections.abc.Set` [ `DataCoordinate` ]
368 A set of `DataCoordinate` instances, with dimensions equal to
369 ``graph``. If this is a mutable object, the caller must be able to
370 guarantee that it will not be modified by any other holders.
371 graph : `DimensionGraph`
372 Dimensions identified by all data IDs in the set.
373 hasFull : `bool`, optional
374 If `True`, the caller guarantees that `DataCoordinate.hasFull` returns
375 `True` for all given data IDs. If `False`, no such guarantee is made,
376 and `DataCoordinateSet.hasFull` will always return `False`. If `None`
377 (default), `DataCoordinateSet.hasFull` will be computed from the given
378 data IDs, immediately if ``check`` is `True`, or on first use if
379 ``check`` is `False`.
380 hasRecords : `bool`, optional
381 If `True`, the caller guarantees that `DataCoordinate.hasRecords`
382 returns `True` for all given data IDs. If `False`, no such guarantee
383 is made and `DataCoordinateSet.hasRecords` will always return `False`.
384 If `None` (default), `DataCoordinateSet.hasRecords` will be computed
385 from the given data IDs, immediately if ``check`` is `True`, or on
386 first use if ``check`` is `False`.
387 check: `bool`, optional
388 If `True` (default) check that all data IDs are consistent with the
389 given ``graph`` and state flags at construction. If `False`, no
390 checking will occur.
392 Notes
393 -----
394 `DataCoordinateSet` does not formally implement the `collections.abc.Set`
395 interface, because that requires many binary operations to accept any
396 set-like object as the other argument (regardless of what its elements
397 might be), and it's much easier to ensure those operations never behave
398 surprisingly if we restrict them to `DataCoordinateSet` or (sometimes)
399 `DataCoordinateIterable`, and in most cases restrict that they identify
400 the same dimensions. In particular:
402 - a `DataCoordinateSet` will compare as not equal to any object that is
403 not a `DataCoordinateSet`, even native Python sets containing the exact
404 same elements;
406 - subset/superset comparison _operators_ (``<``, ``>``, ``<=``, ``>=``)
407 require both operands to be `DataCoordinateSet` instances that have the
408 same dimensions (i.e. ``graph`` attribute);
410 - `issubset`, `issuperset`, and `isdisjoint` require the other argument to
411 be a `DataCoordinateIterable` with the same dimensions;
413 - operators that create new sets (``&``, ``|``, ``^``, ``-``) require both
414 operands to be `DataCoordinateSet` instances that have the same
415 dimensions _and_ the same ``dtype``;
417 - named methods that create new sets (`intersection`, `union`,
418 `symmetric_difference`, `difference`) require the other operand to be a
419 `DataCoordinateIterable` with the same dimensions _and_ the same
420 ``dtype``.
422 In addition, when the two operands differ in the return values of `hasFull`
423 and/or `hasRecords`, we make no guarantees about what those methods will
424 return on the new `DataCoordinateSet` (other than that they will accurately
425 reflect what elements are in the new set - we just don't control which
426 elements are contributed by each operand).
427 """
429 def __init__(
430 self,
431 dataIds: Set[DataCoordinate],
432 graph: DimensionGraph,
433 *,
434 hasFull: bool | None = None,
435 hasRecords: bool | None = None,
436 check: bool = True,
437 ):
438 super().__init__(dataIds, graph, hasFull=hasFull, hasRecords=hasRecords, check=check)
440 _dataIds: Set[DataCoordinate]
442 __slots__ = ()
444 def __str__(self) -> str:
445 return str(set(self._dataIds))
447 def __repr__(self) -> str:
448 return (
449 f"DataCoordinateSet({set(self._dataIds)}, {self._graph!r}, "
450 f"hasFull={self._hasFull}, hasRecords={self._hasRecords})"
451 )
453 def __eq__(self, other: Any) -> bool:
454 if isinstance(other, DataCoordinateSet):
455 return self._graph == other._graph and self._dataIds == other._dataIds
456 return False
458 def __le__(self, other: DataCoordinateSet) -> bool:
459 if self.graph != other.graph:
460 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
461 return self._dataIds <= other._dataIds
463 def __ge__(self, other: DataCoordinateSet) -> bool:
464 if self.graph != other.graph:
465 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
466 return self._dataIds >= other._dataIds
468 def __lt__(self, other: DataCoordinateSet) -> bool:
469 if self.graph != other.graph:
470 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
471 return self._dataIds < other._dataIds
473 def __gt__(self, other: DataCoordinateSet) -> bool:
474 if self.graph != other.graph:
475 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
476 return self._dataIds > other._dataIds
478 def issubset(self, other: DataCoordinateIterable) -> bool:
479 """Test whether ``self`` contains all data IDs in ``other``.
481 Parameters
482 ----------
483 other : `DataCoordinateIterable`
484 An iterable of data IDs with ``other.graph == self.graph``.
486 Returns
487 -------
488 issubset : `bool`
489 `True` if all data IDs in ``self`` are also in ``other``, and
490 `False` otherwise.
491 """
492 if self.graph != other.graph:
493 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
494 return self._dataIds <= other.toSet()._dataIds
496 def issuperset(self, other: DataCoordinateIterable) -> bool:
497 """Test whether ``other`` contains all data IDs in ``self``.
499 Parameters
500 ----------
501 other : `DataCoordinateIterable`
502 An iterable of data IDs with ``other.graph == self.graph``.
504 Returns
505 -------
506 issuperset : `bool`
507 `True` if all data IDs in ``other`` are also in ``self``, and
508 `False` otherwise.
509 """
510 if self.graph != other.graph:
511 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
512 return self._dataIds >= other.toSet()._dataIds
514 def isdisjoint(self, other: DataCoordinateIterable) -> bool:
515 """Test whether there are no data IDs in both ``self`` and ``other``.
517 Parameters
518 ----------
519 other : `DataCoordinateIterable`
520 An iterable of data IDs with ``other.graph == self.graph``.
522 Returns
523 -------
524 isdisjoint : `bool`
525 `True` if there are no data IDs in both ``self`` and ``other``, and
526 `False` otherwise.
527 """
528 if self.graph != other.graph:
529 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
530 return self._dataIds.isdisjoint(other.toSet()._dataIds)
532 def __and__(self, other: DataCoordinateSet) -> DataCoordinateSet:
533 if self.graph != other.graph:
534 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
535 return DataCoordinateSet(self._dataIds & other._dataIds, self.graph, check=False)
537 def __or__(self, other: DataCoordinateSet) -> DataCoordinateSet:
538 if self.graph != other.graph:
539 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
540 return DataCoordinateSet(self._dataIds | other._dataIds, self.graph, check=False)
542 def __xor__(self, other: DataCoordinateSet) -> DataCoordinateSet:
543 if self.graph != other.graph:
544 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
545 return DataCoordinateSet(self._dataIds ^ other._dataIds, self.graph, check=False)
547 def __sub__(self, other: DataCoordinateSet) -> DataCoordinateSet:
548 if self.graph != other.graph:
549 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
550 return DataCoordinateSet(self._dataIds - other._dataIds, self.graph, check=False)
552 def intersection(self, other: DataCoordinateIterable) -> DataCoordinateSet:
553 """Return a new set that contains all data IDs from parameters.
555 Parameters
556 ----------
557 other : `DataCoordinateIterable`
558 An iterable of data IDs with ``other.graph == self.graph``.
560 Returns
561 -------
562 intersection : `DataCoordinateSet`
563 A new `DataCoordinateSet` instance.
564 """
565 if self.graph != other.graph:
566 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
567 return DataCoordinateSet(self._dataIds & other.toSet()._dataIds, self.graph, check=False)
569 def union(self, other: DataCoordinateIterable) -> DataCoordinateSet:
570 """Return a new set that contains all data IDs in either parameters.
572 Parameters
573 ----------
574 other : `DataCoordinateIterable`
575 An iterable of data IDs with ``other.graph == self.graph``.
577 Returns
578 -------
579 intersection : `DataCoordinateSet`
580 A new `DataCoordinateSet` instance.
581 """
582 if self.graph != other.graph:
583 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
584 return DataCoordinateSet(self._dataIds | other.toSet()._dataIds, self.graph, check=False)
586 def symmetric_difference(self, other: DataCoordinateIterable) -> DataCoordinateSet:
587 """Return a new set with all data IDs in either parameters, not both.
589 Parameters
590 ----------
591 other : `DataCoordinateIterable`
592 An iterable of data IDs with ``other.graph == self.graph``.
594 Returns
595 -------
596 intersection : `DataCoordinateSet`
597 A new `DataCoordinateSet` instance.
598 """
599 if self.graph != other.graph:
600 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
601 return DataCoordinateSet(self._dataIds ^ other.toSet()._dataIds, self.graph, check=False)
603 def difference(self, other: DataCoordinateIterable) -> DataCoordinateSet:
604 """Return a new set with all data IDs in this that are not in other.
606 Parameters
607 ----------
608 other : `DataCoordinateIterable`
609 An iterable of data IDs with ``other.graph == self.graph``.
611 Returns
612 -------
613 intersection : `DataCoordinateSet`
614 A new `DataCoordinateSet` instance.
615 """
616 if self.graph != other.graph:
617 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
618 return DataCoordinateSet(self._dataIds - other.toSet()._dataIds, self.graph, check=False)
620 def toSet(self) -> DataCoordinateSet:
621 # Docstring inherited from DataCoordinateIterable.
622 return self
624 def subset(self, graph: DimensionGraph) -> DataCoordinateSet:
625 """Return a set whose data IDs identify a subset.
627 Parameters
628 ----------
629 graph : `DimensionGraph`
630 Dimensions to be identified by the data IDs in the returned
631 iterable. Must be a subset of ``self.graph``.
633 Returns
634 -------
635 set : `DataCoordinateSet`
636 A `DataCoordinateSet` with ``set.graph == graph``.
637 Will be ``self`` if ``graph == self.graph``. Elements are
638 equivalent to those that would be created by calling
639 `DataCoordinate.subset` on all elements in ``self``, with
640 deduplication but and in arbitrary order.
641 """
642 if graph == self.graph:
643 return self
644 return DataCoordinateSet(
645 {dataId.subset(graph) for dataId in self._dataIds}, graph, **self._subsetKwargs(graph)
646 )
649class DataCoordinateSequence(_DataCoordinateCollectionBase, Sequence[DataCoordinate]):
650 """Iterable supporting the full Sequence interface.
652 A `DataCoordinateIterable` implementation that supports the full
653 `collections.abc.Sequence` interface.
655 Parameters
656 ----------
657 dataIds : `collections.abc.Sequence` [ `DataCoordinate` ]
658 A sequence of `DataCoordinate` instances, with dimensions equal to
659 ``graph``.
660 graph : `DimensionGraph`
661 Dimensions identified by all data IDs in the set.
662 hasFull : `bool`, optional
663 If `True`, the caller guarantees that `DataCoordinate.hasFull` returns
664 `True` for all given data IDs. If `False`, no such guarantee is made,
665 and `DataCoordinateSet.hasFull` will always return `False`. If `None`
666 (default), `DataCoordinateSet.hasFull` will be computed from the given
667 data IDs, immediately if ``check`` is `True`, or on first use if
668 ``check`` is `False`.
669 hasRecords : `bool`, optional
670 If `True`, the caller guarantees that `DataCoordinate.hasRecords`
671 returns `True` for all given data IDs. If `False`, no such guarantee
672 is made and `DataCoordinateSet.hasRecords` will always return `False`.
673 If `None` (default), `DataCoordinateSet.hasRecords` will be computed
674 from the given data IDs, immediately if ``check`` is `True`, or on
675 first use if ``check`` is `False`.
676 check: `bool`, optional
677 If `True` (default) check that all data IDs are consistent with the
678 given ``graph`` and state flags at construction. If `False`, no
679 checking will occur.
680 """
682 def __init__(
683 self,
684 dataIds: Sequence[DataCoordinate],
685 graph: DimensionGraph,
686 *,
687 hasFull: bool | None = None,
688 hasRecords: bool | None = None,
689 check: bool = True,
690 ):
691 super().__init__(tuple(dataIds), graph, hasFull=hasFull, hasRecords=hasRecords, check=check)
693 _dataIds: Sequence[DataCoordinate]
695 __slots__ = ()
697 def __str__(self) -> str:
698 return str(tuple(self._dataIds))
700 def __repr__(self) -> str:
701 return (
702 f"DataCoordinateSequence({tuple(self._dataIds)}, {self._graph!r}, "
703 f"hasFull={self._hasFull}, hasRecords={self._hasRecords})"
704 )
706 def __eq__(self, other: Any) -> bool:
707 if isinstance(other, DataCoordinateSequence):
708 return self._graph == other._graph and self._dataIds == other._dataIds
709 return False
711 @overload
712 def __getitem__(self, index: int) -> DataCoordinate:
713 pass
715 @overload
716 def __getitem__(self, index: slice) -> DataCoordinateSequence:
717 pass
719 def __getitem__(self, index: Any) -> Any:
720 r = self._dataIds[index]
721 if isinstance(index, slice):
722 return DataCoordinateSequence(
723 r, self._graph, hasFull=self._hasFull, hasRecords=self._hasRecords, check=False
724 )
725 return r
727 def toSequence(self) -> DataCoordinateSequence:
728 # Docstring inherited from DataCoordinateIterable.
729 return self
731 def subset(self, graph: DimensionGraph) -> DataCoordinateSequence:
732 """Return a sequence whose data IDs identify a subset.
734 Parameters
735 ----------
736 graph : `DimensionGraph`
737 Dimensions to be identified by the data IDs in the returned
738 iterable. Must be a subset of ``self.graph``.
740 Returns
741 -------
742 set : `DataCoordinateSequence`
743 A `DataCoordinateSequence` with ``set.graph == graph``.
744 Will be ``self`` if ``graph == self.graph``. Elements are
745 equivalent to those that would be created by calling
746 `DataCoordinate.subset` on all elements in ``self``, in the same
747 order and with no deduplication.
748 """
749 if graph == self.graph:
750 return self
751 return DataCoordinateSequence(
752 tuple(dataId.subset(graph) for dataId in self._dataIds), graph, **self._subsetKwargs(graph)
753 )