Coverage for python/lsst/daf/butler/core/dimensions/_dataCoordinateIterable.py: 30%
215 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-04 02:19 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-04 02:19 -0700
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 typing import (
32 AbstractSet,
33 Any,
34 Callable,
35 Collection,
36 Dict,
37 Iterable,
38 Iterator,
39 List,
40 Optional,
41 Sequence,
42 overload,
43)
45import sqlalchemy
47from ..simpleQuery import SimpleQuery
48from ._coordinate import DataCoordinate
49from ._graph import DimensionGraph
50from ._universe import DimensionUniverse
53class DataCoordinateIterable(Iterable[DataCoordinate]):
54 """An abstract base class for homogeneous iterables of data IDs.
56 All elements of a `DataCoordinateIterable` identify the same set of
57 dimensions (given by the `graph` property) and generally have the same
58 `DataCoordinate.hasFull` and `DataCoordinate.hasRecords` flag values.
59 """
61 __slots__ = ()
63 @staticmethod
64 def fromScalar(dataId: DataCoordinate) -> _ScalarDataCoordinateIterable:
65 """Return a `DataCoordinateIterable` containing the single data ID.
67 Parameters
68 ----------
69 dataId : `DataCoordinate`
70 Data ID to adapt. Must be a true `DataCoordinate` instance, not
71 an arbitrary mapping. No runtime checking is performed.
73 Returns
74 -------
75 iterable : `DataCoordinateIterable`
76 A `DataCoordinateIterable` instance of unspecified (i.e.
77 implementation-detail) subclass. Guaranteed to implement
78 the `collections.abc.Sized` (i.e. `__len__`) and
79 `collections.abc.Container` (i.e. `__contains__`) interfaces as
80 well as that of `DataCoordinateIterable`.
81 """
82 return _ScalarDataCoordinateIterable(dataId)
84 @property
85 @abstractmethod
86 def graph(self) -> DimensionGraph:
87 """Dimensions identified by these data IDs (`DimensionGraph`)."""
88 raise NotImplementedError()
90 @property
91 def universe(self) -> DimensionUniverse:
92 """Universe that defines all known compatible dimensions.
94 (`DimensionUniverse`).
95 """
96 return self.graph.universe
98 @abstractmethod
99 def hasFull(self) -> bool:
100 """Indicate if all data IDs in this iterable identify all dimensions.
102 Not just required dimensions.
104 Returns
105 -------
106 state : `bool`
107 If `True`, ``all(d.hasFull() for d in iterable)`` is guaranteed.
108 If `False`, no guarantees are made.
109 """
110 raise NotImplementedError()
112 @abstractmethod
113 def hasRecords(self) -> bool:
114 """Return whether all data IDs in this iterable contain records.
116 Returns
117 -------
118 state : `bool`
119 If `True`, ``all(d.hasRecords() for d in iterable)`` is guaranteed.
120 If `False`, no guarantees are made.
121 """
122 raise NotImplementedError()
124 def toSet(self) -> DataCoordinateSet:
125 """Transform this iterable into a `DataCoordinateSet`.
127 Returns
128 -------
129 set : `DataCoordinateSet`
130 A `DatasetCoordinateSet` instance with the same elements as
131 ``self``, after removing any duplicates. May be ``self`` if it is
132 already a `DataCoordinateSet`.
133 """
134 return DataCoordinateSet(
135 frozenset(self),
136 graph=self.graph,
137 hasFull=self.hasFull(),
138 hasRecords=self.hasRecords(),
139 check=False,
140 )
142 def toSequence(self) -> DataCoordinateSequence:
143 """Transform this iterable into a `DataCoordinateSequence`.
145 Returns
146 -------
147 seq : `DataCoordinateSequence`
148 A new `DatasetCoordinateSequence` with the same elements as
149 ``self``, in the same order. May be ``self`` if it is already a
150 `DataCoordinateSequence`.
151 """
152 return DataCoordinateSequence(
153 tuple(self), graph=self.graph, hasFull=self.hasFull(), hasRecords=self.hasRecords(), check=False
154 )
156 def constrain(self, query: SimpleQuery, columns: Callable[[str], sqlalchemy.sql.ColumnElement]) -> None:
157 """Constrain a SQL query to include or relate to only known data IDs.
159 Parameters
160 ----------
161 query : `SimpleQuery`
162 Struct that represents the SQL query to constrain, either by
163 appending to its WHERE clause, joining a new table or subquery,
164 or both.
165 columns : `Callable`
166 A callable that accepts `str` dimension names and returns
167 SQLAlchemy objects representing a column for that dimension's
168 primary key value in the query.
169 """
170 toOrTogether: List[sqlalchemy.sql.ColumnElement] = []
171 for dataId in self:
172 toOrTogether.append(
173 sqlalchemy.sql.and_(
174 *[columns(dimension.name) == dataId[dimension.name] for dimension in self.graph.required]
175 )
176 )
177 query.where.append(sqlalchemy.sql.or_(*toOrTogether))
179 @abstractmethod
180 def subset(self, graph: DimensionGraph) -> DataCoordinateIterable:
181 """Return a subset iterable.
183 This subset iterable returns data IDs that identify a subset of the
184 dimensions that this one's do.
186 Parameters
187 ----------
188 graph : `DimensionGraph`
189 Dimensions to be identified by the data IDs in the returned
190 iterable. Must be a subset of ``self.graph``.
192 Returns
193 -------
194 iterable : `DataCoordinateIterable`
195 A `DataCoordinateIterable` with ``iterable.graph == graph``.
196 May be ``self`` if ``graph == self.graph``. Elements are
197 equivalent to those that would be created by calling
198 `DataCoordinate.subset` on all elements in ``self``, possibly
199 with deduplication and/or reordering (depending on the subclass,
200 which may make more specific guarantees).
201 """
202 raise NotImplementedError()
205class _ScalarDataCoordinateIterable(DataCoordinateIterable):
206 """An iterable for a single `DataCoordinate`.
208 A `DataCoordinateIterable` implementation that adapts a single
209 `DataCoordinate` instance.
211 This class should only be used directly by other code in the module in
212 which it is defined; all other code should interact with it only through
213 the `DataCoordinateIterable` interface.
215 Parameters
216 ----------
217 dataId : `DataCoordinate`
218 The data ID to adapt.
219 """
221 def __init__(self, dataId: DataCoordinate):
222 self._dataId = dataId
224 __slots__ = ("_dataId",)
226 def __iter__(self) -> Iterator[DataCoordinate]:
227 yield self._dataId
229 def __len__(self) -> int:
230 return 1
232 def __contains__(self, key: Any) -> bool:
233 if isinstance(key, DataCoordinate):
234 return key == self._dataId
235 else:
236 return False
238 @property
239 def graph(self) -> DimensionGraph:
240 # Docstring inherited from DataCoordinateIterable.
241 return self._dataId.graph
243 def hasFull(self) -> bool:
244 # Docstring inherited from DataCoordinateIterable.
245 return self._dataId.hasFull()
247 def hasRecords(self) -> bool:
248 # Docstring inherited from DataCoordinateIterable.
249 return self._dataId.hasRecords()
251 def subset(self, graph: DimensionGraph) -> _ScalarDataCoordinateIterable:
252 # Docstring inherited from DataCoordinateIterable.
253 return _ScalarDataCoordinateIterable(self._dataId.subset(graph))
256class _DataCoordinateCollectionBase(DataCoordinateIterable):
257 """A partial iterable implementation backed by native Python collection.
259 A partial `DataCoordinateIterable` implementation that is backed by a
260 native Python collection.
262 This class is intended only to be used as an intermediate base class for
263 `DataCoordinateIterables` that assume a more specific type of collection
264 and can hence make more informed choices for how to implement some methods.
266 Parameters
267 ----------
268 dataIds : `collections.abc.Collection` [ `DataCoordinate` ]
269 A collection of `DataCoordinate` instances, with dimensions equal to
270 ``graph``.
271 graph : `DimensionGraph`
272 Dimensions identified by all data IDs in the set.
273 hasFull : `bool`, optional
274 If `True`, the caller guarantees that `DataCoordinate.hasFull` returns
275 `True` for all given data IDs. If `False`, no such guarantee is made,
276 and `hasFull` will always return `False`. If `None` (default),
277 `hasFull` will be computed from the given data IDs, immediately if
278 ``check`` is `True`, or on first use if ``check`` is `False`.
279 hasRecords : `bool`, optional
280 If `True`, the caller guarantees that `DataCoordinate.hasRecords`
281 returns `True` for all given data IDs. If `False`, no such guarantee
282 is made and `hasRecords` will always return `False`. If `None`
283 (default), `hasRecords` will be computed from the given data IDs,
284 immediately if ``check`` is `True`, or on first use if ``check`` is
285 `False`.
286 check: `bool`, optional
287 If `True` (default) check that all data IDs are consistent with the
288 given ``graph`` and state flags at construction. If `False`, no
289 checking will occur.
290 """
292 def __init__(
293 self,
294 dataIds: Collection[DataCoordinate],
295 graph: DimensionGraph,
296 *,
297 hasFull: Optional[bool] = None,
298 hasRecords: Optional[bool] = None,
299 check: bool = True,
300 ):
301 self._dataIds = dataIds
302 self._graph = graph
303 if check:
304 for dataId in self._dataIds:
305 if hasFull and not dataId.hasFull():
306 raise ValueError(f"{dataId} is not complete, but is required to be.")
307 if hasRecords and not dataId.hasRecords():
308 raise ValueError(f"{dataId} has no records, but is required to.")
309 if dataId.graph != self._graph:
310 raise ValueError(f"Bad dimensions {dataId.graph}; expected {self._graph}.")
311 if hasFull is None:
312 hasFull = all(dataId.hasFull() for dataId in self._dataIds)
313 if hasRecords is None:
314 hasRecords = all(dataId.hasRecords() for dataId in self._dataIds)
315 self._hasFull = hasFull
316 self._hasRecords = hasRecords
318 __slots__ = ("_graph", "_dataIds", "_hasFull", "_hasRecords")
320 @property
321 def graph(self) -> DimensionGraph:
322 # Docstring inherited from DataCoordinateIterable.
323 return self._graph
325 def hasFull(self) -> bool:
326 # Docstring inherited from DataCoordinateIterable.
327 if self._hasFull is None:
328 self._hasFull = all(dataId.hasFull() for dataId in self._dataIds)
329 return self._hasFull
331 def hasRecords(self) -> bool:
332 # Docstring inherited from DataCoordinateIterable.
333 if self._hasRecords is None:
334 self._hasRecords = all(dataId.hasRecords() for dataId in self._dataIds)
335 return self._hasRecords
337 def toSet(self) -> DataCoordinateSet:
338 # Docstring inherited from DataCoordinateIterable.
339 # Override base class to pass in attributes instead of results of
340 # method calls for _hasFull and _hasRecords - those can be None,
341 # and hence defer checking if that's what the user originally wanted.
342 return DataCoordinateSet(
343 frozenset(self._dataIds),
344 graph=self._graph,
345 hasFull=self._hasFull,
346 hasRecords=self._hasRecords,
347 check=False,
348 )
350 def toSequence(self) -> DataCoordinateSequence:
351 # Docstring inherited from DataCoordinateIterable.
352 # Override base class to pass in attributes instead of results of
353 # method calls for _hasFull and _hasRecords - those can be None,
354 # and hence defer checking if that's what the user originally wanted.
355 return DataCoordinateSequence(
356 tuple(self._dataIds),
357 graph=self._graph,
358 hasFull=self._hasFull,
359 hasRecords=self._hasRecords,
360 check=False,
361 )
363 def __iter__(self) -> Iterator[DataCoordinate]:
364 return iter(self._dataIds)
366 def __len__(self) -> int:
367 return len(self._dataIds)
369 def __contains__(self, key: Any) -> bool:
370 key = DataCoordinate.standardize(key, universe=self.universe)
371 return key in self._dataIds
373 def _subsetKwargs(self, graph: DimensionGraph) -> Dict[str, Any]:
374 """Return constructor kwargs useful for subclasses implementing subset.
376 Parameters
377 ----------
378 graph : `DimensionGraph`
379 Dimensions passed to `subset`.
381 Returns
382 -------
383 **kwargs
384 A dict with `hasFull`, `hasRecords`, and `check` keys, associated
385 with the appropriate values for a `subset` operation with the given
386 dimensions.
387 """
388 hasFull: Optional[bool]
389 if graph.dimensions <= self.graph.required:
390 hasFull = True
391 else:
392 hasFull = self._hasFull
393 return dict(hasFull=hasFull, hasRecords=self._hasRecords, check=False)
396class DataCoordinateSet(_DataCoordinateCollectionBase):
397 """Iterable iteration that is set-like.
399 A `DataCoordinateIterable` implementation that adds some set-like
400 functionality, and is backed by a true set-like object.
402 Parameters
403 ----------
404 dataIds : `collections.abc.Set` [ `DataCoordinate` ]
405 A set of `DataCoordinate` instances, with dimensions equal to
406 ``graph``. If this is a mutable object, the caller must be able to
407 guarantee that it will not be modified by any other holders.
408 graph : `DimensionGraph`
409 Dimensions identified by all data IDs in the set.
410 hasFull : `bool`, optional
411 If `True`, the caller guarantees that `DataCoordinate.hasFull` returns
412 `True` for all given data IDs. If `False`, no such guarantee is made,
413 and `DataCoordinateSet.hasFull` will always return `False`. If `None`
414 (default), `DataCoordinateSet.hasFull` will be computed from the given
415 data IDs, immediately if ``check`` is `True`, or on first use if
416 ``check`` is `False`.
417 hasRecords : `bool`, optional
418 If `True`, the caller guarantees that `DataCoordinate.hasRecords`
419 returns `True` for all given data IDs. If `False`, no such guarantee
420 is made and `DataCoordinateSet.hasRecords` will always return `False`.
421 If `None` (default), `DataCoordinateSet.hasRecords` will be computed
422 from the given data IDs, immediately if ``check`` is `True`, or on
423 first use if ``check`` is `False`.
424 check: `bool`, optional
425 If `True` (default) check that all data IDs are consistent with the
426 given ``graph`` and state flags at construction. If `False`, no
427 checking will occur.
429 Notes
430 -----
431 `DataCoordinateSet` does not formally implement the `collections.abc.Set`
432 interface, because that requires many binary operations to accept any
433 set-like object as the other argument (regardless of what its elements
434 might be), and it's much easier to ensure those operations never behave
435 surprisingly if we restrict them to `DataCoordinateSet` or (sometimes)
436 `DataCoordinateIterable`, and in most cases restrict that they identify
437 the same dimensions. In particular:
439 - a `DataCoordinateSet` will compare as not equal to any object that is
440 not a `DataCoordinateSet`, even native Python sets containing the exact
441 same elements;
443 - subset/superset comparison _operators_ (``<``, ``>``, ``<=``, ``>=``)
444 require both operands to be `DataCoordinateSet` instances that have the
445 same dimensions (i.e. ``graph`` attribute);
447 - `issubset`, `issuperset`, and `isdisjoint` require the other argument to
448 be a `DataCoordinateIterable` with the same dimensions;
450 - operators that create new sets (``&``, ``|``, ``^``, ``-``) require both
451 operands to be `DataCoordinateSet` instances that have the same
452 dimensions _and_ the same ``dtype``;
454 - named methods that create new sets (`intersection`, `union`,
455 `symmetric_difference`, `difference`) require the other operand to be a
456 `DataCoordinateIterable` with the same dimensions _and_ the same
457 ``dtype``.
459 In addition, when the two operands differ in the return values of `hasFull`
460 and/or `hasRecords`, we make no guarantees about what those methods will
461 return on the new `DataCoordinateSet` (other than that they will accurately
462 reflect what elements are in the new set - we just don't control which
463 elements are contributed by each operand).
464 """
466 def __init__(
467 self,
468 dataIds: AbstractSet[DataCoordinate],
469 graph: DimensionGraph,
470 *,
471 hasFull: Optional[bool] = None,
472 hasRecords: Optional[bool] = None,
473 check: bool = True,
474 ):
475 super().__init__(dataIds, graph, hasFull=hasFull, hasRecords=hasRecords, check=check)
477 _dataIds: AbstractSet[DataCoordinate]
479 __slots__ = ()
481 def __str__(self) -> str:
482 return str(set(self._dataIds))
484 def __repr__(self) -> str:
485 return (
486 f"DataCoordinateSet({set(self._dataIds)}, {self._graph!r}, "
487 f"hasFull={self._hasFull}, hasRecords={self._hasRecords})"
488 )
490 def __eq__(self, other: Any) -> bool:
491 if isinstance(other, DataCoordinateSet):
492 return self._graph == other._graph and self._dataIds == other._dataIds
493 return False
495 def __le__(self, other: DataCoordinateSet) -> bool:
496 if self.graph != other.graph:
497 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
498 return self._dataIds <= other._dataIds
500 def __ge__(self, other: DataCoordinateSet) -> bool:
501 if self.graph != other.graph:
502 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
503 return self._dataIds >= other._dataIds
505 def __lt__(self, other: DataCoordinateSet) -> bool:
506 if self.graph != other.graph:
507 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
508 return self._dataIds < other._dataIds
510 def __gt__(self, other: DataCoordinateSet) -> bool:
511 if self.graph != other.graph:
512 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
513 return self._dataIds > other._dataIds
515 def issubset(self, other: DataCoordinateIterable) -> bool:
516 """Test whether ``self`` contains all data IDs in ``other``.
518 Parameters
519 ----------
520 other : `DataCoordinateIterable`
521 An iterable of data IDs with ``other.graph == self.graph``.
523 Returns
524 -------
525 issubset : `bool`
526 `True` if all data IDs in ``self`` are also in ``other``, and
527 `False` otherwise.
528 """
529 if self.graph != other.graph:
530 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
531 return self._dataIds <= other.toSet()._dataIds
533 def issuperset(self, other: DataCoordinateIterable) -> bool:
534 """Test whether ``other`` contains all data IDs in ``self``.
536 Parameters
537 ----------
538 other : `DataCoordinateIterable`
539 An iterable of data IDs with ``other.graph == self.graph``.
541 Returns
542 -------
543 issuperset : `bool`
544 `True` if all data IDs in ``other`` are also in ``self``, and
545 `False` otherwise.
546 """
547 if self.graph != other.graph:
548 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
549 return self._dataIds >= other.toSet()._dataIds
551 def isdisjoint(self, other: DataCoordinateIterable) -> bool:
552 """Test whether there are no data IDs in both ``self`` and ``other``.
554 Parameters
555 ----------
556 other : `DataCoordinateIterable`
557 An iterable of data IDs with ``other.graph == self.graph``.
559 Returns
560 -------
561 isdisjoint : `bool`
562 `True` if there are no data IDs in both ``self`` and ``other``, and
563 `False` otherwise.
564 """
565 if self.graph != other.graph:
566 raise ValueError(f"Inconsistent dimensions in set comparision: {self.graph} != {other.graph}.")
567 return self._dataIds.isdisjoint(other.toSet()._dataIds)
569 def __and__(self, other: DataCoordinateSet) -> DataCoordinateSet:
570 if self.graph != other.graph:
571 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
572 return DataCoordinateSet(self._dataIds & other._dataIds, self.graph, check=False)
574 def __or__(self, other: DataCoordinateSet) -> DataCoordinateSet:
575 if self.graph != other.graph:
576 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
577 return DataCoordinateSet(self._dataIds | other._dataIds, self.graph, check=False)
579 def __xor__(self, other: DataCoordinateSet) -> DataCoordinateSet:
580 if self.graph != other.graph:
581 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
582 return DataCoordinateSet(self._dataIds ^ other._dataIds, self.graph, check=False)
584 def __sub__(self, other: DataCoordinateSet) -> DataCoordinateSet:
585 if self.graph != other.graph:
586 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
587 return DataCoordinateSet(self._dataIds - other._dataIds, self.graph, check=False)
589 def intersection(self, other: DataCoordinateIterable) -> DataCoordinateSet:
590 """Return a new set that contains all data IDs from parameters.
592 Parameters
593 ----------
594 other : `DataCoordinateIterable`
595 An iterable of data IDs with ``other.graph == self.graph``.
597 Returns
598 -------
599 intersection : `DataCoordinateSet`
600 A new `DataCoordinateSet` instance.
601 """
602 if self.graph != other.graph:
603 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
604 return DataCoordinateSet(self._dataIds & other.toSet()._dataIds, self.graph, check=False)
606 def union(self, other: DataCoordinateIterable) -> DataCoordinateSet:
607 """Return a new set that contains all data IDs in either parameters.
609 Parameters
610 ----------
611 other : `DataCoordinateIterable`
612 An iterable of data IDs with ``other.graph == self.graph``.
614 Returns
615 -------
616 intersection : `DataCoordinateSet`
617 A new `DataCoordinateSet` instance.
618 """
619 if self.graph != other.graph:
620 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
621 return DataCoordinateSet(self._dataIds | other.toSet()._dataIds, self.graph, check=False)
623 def symmetric_difference(self, other: DataCoordinateIterable) -> DataCoordinateSet:
624 """Return a new set with all data IDs in either parameters, not both.
626 Parameters
627 ----------
628 other : `DataCoordinateIterable`
629 An iterable of data IDs with ``other.graph == self.graph``.
631 Returns
632 -------
633 intersection : `DataCoordinateSet`
634 A new `DataCoordinateSet` instance.
635 """
636 if self.graph != other.graph:
637 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
638 return DataCoordinateSet(self._dataIds ^ other.toSet()._dataIds, self.graph, check=False)
640 def difference(self, other: DataCoordinateIterable) -> DataCoordinateSet:
641 """Return a new set with all data IDs in this that are not in other.
643 Parameters
644 ----------
645 other : `DataCoordinateIterable`
646 An iterable of data IDs with ``other.graph == self.graph``.
648 Returns
649 -------
650 intersection : `DataCoordinateSet`
651 A new `DataCoordinateSet` instance.
652 """
653 if self.graph != other.graph:
654 raise ValueError(f"Inconsistent dimensions in set operation: {self.graph} != {other.graph}.")
655 return DataCoordinateSet(self._dataIds - other.toSet()._dataIds, self.graph, check=False)
657 def toSet(self) -> DataCoordinateSet:
658 # Docstring inherited from DataCoordinateIterable.
659 return self
661 def subset(self, graph: DimensionGraph) -> DataCoordinateSet:
662 """Return a set whose data IDs identify a subset.
664 Parameters
665 ----------
666 graph : `DimensionGraph`
667 Dimensions to be identified by the data IDs in the returned
668 iterable. Must be a subset of ``self.graph``.
670 Returns
671 -------
672 set : `DataCoordinateSet`
673 A `DataCoordinateSet` with ``set.graph == graph``.
674 Will be ``self`` if ``graph == self.graph``. Elements are
675 equivalent to those that would be created by calling
676 `DataCoordinate.subset` on all elements in ``self``, with
677 deduplication but and in arbitrary order.
678 """
679 if graph == self.graph:
680 return self
681 return DataCoordinateSet(
682 {dataId.subset(graph) for dataId in self._dataIds}, graph, **self._subsetKwargs(graph)
683 )
686class DataCoordinateSequence(_DataCoordinateCollectionBase, Sequence[DataCoordinate]):
687 """Iterable supporting the full Sequence interface.
689 A `DataCoordinateIterable` implementation that supports the full
690 `collections.abc.Sequence` interface.
692 Parameters
693 ----------
694 dataIds : `collections.abc.Sequence` [ `DataCoordinate` ]
695 A sequence of `DataCoordinate` instances, with dimensions equal to
696 ``graph``.
697 graph : `DimensionGraph`
698 Dimensions identified by all data IDs in the set.
699 hasFull : `bool`, optional
700 If `True`, the caller guarantees that `DataCoordinate.hasFull` returns
701 `True` for all given data IDs. If `False`, no such guarantee is made,
702 and `DataCoordinateSet.hasFull` will always return `False`. If `None`
703 (default), `DataCoordinateSet.hasFull` will be computed from the given
704 data IDs, immediately if ``check`` is `True`, or on first use if
705 ``check`` is `False`.
706 hasRecords : `bool`, optional
707 If `True`, the caller guarantees that `DataCoordinate.hasRecords`
708 returns `True` for all given data IDs. If `False`, no such guarantee
709 is made and `DataCoordinateSet.hasRecords` will always return `False`.
710 If `None` (default), `DataCoordinateSet.hasRecords` will be computed
711 from the given data IDs, immediately if ``check`` is `True`, or on
712 first use if ``check`` is `False`.
713 check: `bool`, optional
714 If `True` (default) check that all data IDs are consistent with the
715 given ``graph`` and state flags at construction. If `False`, no
716 checking will occur.
717 """
719 def __init__(
720 self,
721 dataIds: Sequence[DataCoordinate],
722 graph: DimensionGraph,
723 *,
724 hasFull: Optional[bool] = None,
725 hasRecords: Optional[bool] = None,
726 check: bool = True,
727 ):
728 super().__init__(tuple(dataIds), graph, hasFull=hasFull, hasRecords=hasRecords, check=check)
730 _dataIds: Sequence[DataCoordinate]
732 __slots__ = ()
734 def __str__(self) -> str:
735 return str(tuple(self._dataIds))
737 def __repr__(self) -> str:
738 return (
739 f"DataCoordinateSequence({tuple(self._dataIds)}, {self._graph!r}, "
740 f"hasFull={self._hasFull}, hasRecords={self._hasRecords})"
741 )
743 def __eq__(self, other: Any) -> bool:
744 if isinstance(other, DataCoordinateSequence):
745 return self._graph == other._graph and self._dataIds == other._dataIds
746 return False
748 @overload
749 def __getitem__(self, index: int) -> DataCoordinate:
750 pass
752 @overload # noqa: F811 (FIXME: remove for py 3.8+)
753 def __getitem__(self, index: slice) -> DataCoordinateSequence: # noqa: F811
754 pass
756 def __getitem__(self, index: Any) -> Any: # noqa: F811
757 r = self._dataIds[index]
758 if isinstance(index, slice):
759 return DataCoordinateSequence(
760 r, self._graph, hasFull=self._hasFull, hasRecords=self._hasRecords, check=False
761 )
762 return r
764 def toSequence(self) -> DataCoordinateSequence:
765 # Docstring inherited from DataCoordinateIterable.
766 return self
768 def subset(self, graph: DimensionGraph) -> DataCoordinateSequence:
769 """Return a sequence whose data IDs identify a subset.
771 Parameters
772 ----------
773 graph : `DimensionGraph`
774 Dimensions to be identified by the data IDs in the returned
775 iterable. Must be a subset of ``self.graph``.
777 Returns
778 -------
779 set : `DataCoordinateSequence`
780 A `DataCoordinateSequence` with ``set.graph == graph``.
781 Will be ``self`` if ``graph == self.graph``. Elements are
782 equivalent to those that would be created by calling
783 `DataCoordinate.subset` on all elements in ``self``, in the same
784 order and with no deduplication.
785 """
786 if graph == self.graph:
787 return self
788 return DataCoordinateSequence(
789 tuple(dataId.subset(graph) for dataId in self._dataIds), graph, **self._subsetKwargs(graph)
790 )