diff --git a/doc/changes/DM-41326.removal.md b/doc/changes/DM-41326.removal.md new file mode 100644 index 0000000000..22414a1493 --- /dev/null +++ b/doc/changes/DM-41326.removal.md @@ -0,0 +1,3 @@ +Remove `DimensionGraph` and the `Mapping` interface to `DataCoordinate`, along with most other public interfaces that utilize `DimensionElement` instances instead of just their string names. + +See [RFC-834](https://rubinobs.atlassian.net/browse/RFC-834) for full details and rationale. diff --git a/python/lsst/daf/butler/_dataset_ref.py b/python/lsst/daf/butler/_dataset_ref.py index 58c9d131ca..8414a75952 100644 --- a/python/lsst/daf/butler/_dataset_ref.py +++ b/python/lsst/daf/butler/_dataset_ref.py @@ -50,7 +50,7 @@ from ._dataset_type import DatasetType, SerializedDatasetType from ._named import NamedKeyDict from .datastore.stored_file_info import StoredDatastoreItemInfo -from .dimensions import DataCoordinate, DimensionGraph, DimensionUniverse, SerializedDataCoordinate +from .dimensions import DataCoordinate, DimensionGroup, DimensionUniverse, SerializedDataCoordinate from .json import from_json_pydantic, to_json_pydantic from .persistence_context import PersistenceContextVars @@ -363,7 +363,7 @@ def __hash__(self) -> int: return hash((self.datasetType, self.dataId, self.id)) @property - def dimensions(self) -> DimensionGraph: + def dimensions(self) -> DimensionGroup: """Dimensions associated with the underlying `DatasetType`.""" return self.datasetType.dimensions diff --git a/python/lsst/daf/butler/_dataset_type.py b/python/lsst/daf/butler/_dataset_type.py index 8c6e9f7d2f..aa736a951c 100644 --- a/python/lsst/daf/butler/_dataset_type.py +++ b/python/lsst/daf/butler/_dataset_type.py @@ -39,12 +39,12 @@ from ._config_support import LookupKey from ._storage_class import StorageClass, StorageClassFactory -from .dimensions import DimensionGraph, DimensionGroup, SerializedDimensionGraph +from .dimensions import DimensionGroup from .json import from_json_pydantic, to_json_pydantic from .persistence_context import PersistenceContextVars if TYPE_CHECKING: - from .dimensions import Dimension, DimensionUniverse + from .dimensions import DimensionUniverse from .registry import Registry @@ -59,7 +59,7 @@ class SerializedDatasetType(BaseModel): name: StrictStr storageClass: StrictStr | None = None - dimensions: SerializedDimensionGraph | list[StrictStr] | None = None + dimensions: list[StrictStr] | None = None parentStorageClass: StrictStr | None = None isCalibration: StrictBool = False @@ -69,7 +69,7 @@ def direct( *, name: str, storageClass: str | None = None, - dimensions: list | dict | None = None, + dimensions: list | None = None, parentStorageClass: str | None = None, isCalibration: bool = False, ) -> SerializedDatasetType: @@ -88,7 +88,7 @@ def direct( The name of the dataset type. storageClass : `str` or `None` The name of the storage class. - dimensions : `list` or `dict` or `None` + dimensions : `list` or `None` The dimensions associated with this dataset type. parentStorageClass : `str` or `None` The parent storage class name if this is a component. @@ -104,16 +104,7 @@ def direct( key = (name, storageClass or "") if cache is not None and (type_ := cache.get(key, None)) is not None: return type_ - - serialized_dimensions: list[str] | None - match dimensions: - case list(): - serialized_dimensions = dimensions - case dict(): - serialized_dimensions = SerializedDimensionGraph.direct(**dimensions).names - case None: - serialized_dimensions = None - + serialized_dimensions = dimensions if dimensions is not None else None node = cls.model_construct( name=name, storageClass=storageClass, @@ -148,11 +139,9 @@ class DatasetType: and underscores. Component dataset types should contain a single period separating the base dataset type name from the component name (and may be recursive). - dimensions : `DimensionGroup`, `DimensionGraph`, or \ - `~collections.abc.Iterable` [ `Dimension` or `str` ] + dimensions : `DimensionGroup` or `~collections.abc.Iterable` [ `str` ] Dimensions used to label and relate instances of this `DatasetType`. - If not a `DimensionGraph` or `DimensionGroup`, ``universe`` must be - provided as well. + If not a `DimensionGroup`, ``universe`` must be provided as well. storageClass : `StorageClass` or `str` Instance of a `StorageClass` or name of `StorageClass` that defines how this `DatasetType` is persisted. @@ -162,7 +151,7 @@ class DatasetType: is not a component. universe : `DimensionUniverse`, optional Set of all known dimensions, used to normalize ``dimensions`` if it - is not already a `DimensionGraph`. + is not already a `DimensionGroup`. isCalibration : `bool`, optional If `True`, this dataset type may be included in `~CollectionType.CALIBRATION` collections. @@ -209,7 +198,7 @@ def nameWithComponent(datasetTypeName: str, componentName: str) -> str: def __init__( self, name: str, - dimensions: DimensionGroup | DimensionGraph | Iterable[Dimension | str], + dimensions: DimensionGroup | Iterable[str], storageClass: StorageClass | str, parentStorageClass: StorageClass | str | None = None, *, @@ -221,9 +210,7 @@ def __init__( self._name = name universe = universe or getattr(dimensions, "universe", None) if universe is None: - raise ValueError( - "If dimensions is not a DimensionGroup or DimensionGraph, a universe must be provided." - ) + raise ValueError("If dimensions is not a DimensionGroup, a universe must be provided.") self._dimensions = universe.conform(dimensions) if name in self._dimensions.universe.governor_dimensions: raise ValueError(f"Governor dimension name {name} cannot be used as a dataset type name.") @@ -373,12 +360,12 @@ def name(self) -> str: return self._name @property - def dimensions(self) -> DimensionGraph: - """Return the dimensions of this dataset type (`DimensionGraph`). + def dimensions(self) -> DimensionGroup: + """Return the dimensions of this dataset type (`DimensionGroup`). The dimensions of a define the keys of its datasets' data IDs.. """ - return self._dimensions._as_graph() + return self._dimensions @property def storageClass(self) -> StorageClass: @@ -744,14 +731,9 @@ def from_simple( if universe is None: # this is for mypy raise ValueError("Unable to determine a usable universe") - - match simple.dimensions: - case list(): - dimensions = universe.conform(simple.dimensions) - case SerializedDimensionGraph(): - dimensions = universe.conform(simple.dimensions.names) - case None: - raise ValueError(f"Dimensions must be specified in {simple}") + if simple.dimensions is None: + raise ValueError(f"Dimensions must be specified in {simple}") + dimensions = universe.conform(simple.dimensions) newType = cls( name=simple.name, diff --git a/python/lsst/daf/butler/_registry_shim.py b/python/lsst/daf/butler/_registry_shim.py index 2bbe5a9fb6..45010c1b83 100644 --- a/python/lsst/daf/butler/_registry_shim.py +++ b/python/lsst/daf/butler/_registry_shim.py @@ -36,14 +36,11 @@ from ._dataset_association import DatasetAssociation from ._dataset_ref import DatasetId, DatasetIdGenEnum, DatasetRef from ._dataset_type import DatasetType -from ._named import NameLookupMapping from ._timespan import Timespan from .dimensions import ( DataCoordinate, DataId, - Dimension, DimensionElement, - DimensionGraph, DimensionGroup, DimensionRecord, DimensionUniverse, @@ -248,15 +245,14 @@ def expandDataId( self, dataId: DataId | None = None, *, - dimensions: Iterable[str] | DimensionGroup | DimensionGraph | None = None, - graph: DimensionGraph | None = None, - records: NameLookupMapping[DimensionElement, DimensionRecord | None] | None = None, + dimensions: Iterable[str] | DimensionGroup | None = None, + records: Mapping[str, DimensionRecord | None] | None = None, withDefaults: bool = True, **kwargs: Any, ) -> DataCoordinate: # Docstring inherited from a base class. return self._registry.expandDataId( - dataId, dimensions=dimensions, graph=graph, records=records, withDefaults=withDefaults, **kwargs + dataId, dimensions=dimensions, records=records, withDefaults=withDefaults, **kwargs ) def insertDimensionData( @@ -310,7 +306,7 @@ def queryDatasets( datasetType: Any, *, collections: CollectionArgType | None = None, - dimensions: Iterable[Dimension | str] | None = None, + dimensions: Iterable[str] | None = None, dataId: DataId | None = None, where: str = "", findFirst: bool = False, @@ -335,8 +331,7 @@ def queryDatasets( def queryDataIds( self, - # TODO: Drop Dimension support on DM-41326. - dimensions: DimensionGroup | Iterable[Dimension | str] | Dimension | str, + dimensions: DimensionGroup | Iterable[str] | str, *, dataId: DataId | None = None, datasets: Any = None, diff --git a/python/lsst/daf/butler/datastore/file_templates.py b/python/lsst/daf/butler/datastore/file_templates.py index 60ae424c89..85cbc8d93a 100644 --- a/python/lsst/daf/butler/datastore/file_templates.py +++ b/python/lsst/daf/butler/datastore/file_templates.py @@ -43,7 +43,7 @@ from .._dataset_ref import DatasetId, DatasetRef from .._exceptions import ValidationError from .._storage_class import StorageClass -from ..dimensions import DataCoordinate, DimensionGraph, DimensionGroup +from ..dimensions import DataCoordinate, DimensionGroup if TYPE_CHECKING: from .._dataset_type import DatasetType @@ -394,9 +394,7 @@ def __str__(self) -> str: def __repr__(self) -> str: return f'{self.__class__.__name__}("{self.template}")' - def grouped_fields( - self, dimensions: DimensionGroup | DimensionGraph | None = None - ) -> tuple[FieldDict, FieldDict]: + def grouped_fields(self, dimensions: DimensionGroup | None = None) -> tuple[FieldDict, FieldDict]: """Return all the fields, grouped by their type. Parameters @@ -723,7 +721,7 @@ def validateTemplate(self, entity: DatasetRef | DatasetType | StorageClass | Non ----- Validation will always include a check that mandatory fields are present and that at least one field refers to a dimension. - If the supplied entity includes a `DimensionGraph` then it will be + If the supplied entity includes a `DimensionGroup` then it will be used to compare the available dimensions with those specified in the template. """ @@ -821,7 +819,7 @@ def validateTemplate(self, entity: DatasetRef | DatasetType | StorageClass | Non # Fall back to dataId keys if we have them but no links. # dataId keys must still be present in the template try: - minimal = set(entity.dimensions.required.names) + minimal = set(entity.dimensions.required) maximal = set(entity.dimensions.names) except AttributeError: try: @@ -890,5 +888,5 @@ def _determine_skypix_alias(self, entity: DatasetRef | DatasetType) -> str | Non # update to be more like the real world while still providing our # only tests of important behavior. if len(entity.dimensions.skypix) == 1: - (alias,) = entity.dimensions.skypix.names + (alias,) = entity.dimensions.skypix return alias diff --git a/python/lsst/daf/butler/dimensions/__init__.py b/python/lsst/daf/butler/dimensions/__init__.py index 5494a7efed..810b218a0b 100644 --- a/python/lsst/daf/butler/dimensions/__init__.py +++ b/python/lsst/daf/butler/dimensions/__init__.py @@ -40,7 +40,6 @@ from ._database import * from ._elements import * from ._governor import * -from ._graph import * from ._group import * from ._packer import * from ._record_set import * diff --git a/python/lsst/daf/butler/dimensions/_coordinate.py b/python/lsst/daf/butler/dimensions/_coordinate.py index 3594adc2d1..1817248fe8 100644 --- a/python/lsst/daf/butler/dimensions/_coordinate.py +++ b/python/lsst/daf/butler/dimensions/_coordinate.py @@ -42,23 +42,17 @@ ) import numbers -import warnings from abc import abstractmethod -from collections.abc import Iterable, Iterator, Mapping, Set -from typing import TYPE_CHECKING, Any, ClassVar, cast +from collections.abc import Iterable, Iterator, Mapping +from typing import TYPE_CHECKING, Any, ClassVar, overload import pydantic -from deprecated.sphinx import deprecated from lsst.sphgeom import IntersectionRegion, Region -from lsst.utils.introspection import find_outside_stacklevel from .._exceptions import DimensionNameError -from .._named import NamedKeyMapping, NamedValueAbstractSet, NameLookupMapping from .._timespan import Timespan from ..json import from_json_pydantic, to_json_pydantic from ..persistence_context import PersistenceContextVars -from ._elements import Dimension, DimensionElement -from ._graph import DimensionGraph from ._group import DimensionGroup from ._records import DimensionRecord, SerializedDimensionRecord @@ -66,7 +60,7 @@ from ..registry import Registry from ._universe import DimensionUniverse -DataIdKey = str | Dimension +DataIdKey = str """Type annotation alias for the keys that can be used to index a DataCoordinate. """ @@ -141,7 +135,7 @@ def _intersectRegions(*args: Region) -> Region | None: return result -class DataCoordinate(NamedKeyMapping[Dimension, DataIdValue]): +class DataCoordinate: """A validated data ID. DataCoordinate guarantees that its key-value pairs identify at least all @@ -171,10 +165,9 @@ class DataCoordinate(NamedKeyMapping[Dimension, DataIdValue]): @staticmethod def standardize( - mapping: NameLookupMapping[Dimension, DataIdValue] | None = None, + mapping: Mapping[str, DataIdValue] | DataCoordinate | None = None, *, - dimensions: Iterable[str] | DimensionGroup | DimensionGraph | None = None, - graph: DimensionGraph | None = None, + dimensions: Iterable[str] | DimensionGroup | None = None, universe: DimensionUniverse | None = None, defaults: DataCoordinate | None = None, **kwargs: Any, @@ -186,25 +179,21 @@ def standardize( Parameters ---------- - mapping : `~collections.abc.Mapping`, optional + mapping : `~collections.abc.Mapping` or `DataCoordinate`, optional An informal data ID that maps dimensions or dimension names to their primary key values (may also be a true `DataCoordinate`). - dimensions : `~collections.abc.Iterable` [ `str` ], `DimensionGroup` \ - or `DimensionGraph`, optional + dimensions : `~collections.abc.Iterable` [ `str` ], `DimensionGroup`, \ + optional The dimensions to be identified by the new `DataCoordinate`. If not provided, will be inferred from the keys of ``mapping`` and ``**kwargs``, and ``universe`` must be provided unless ``mapping`` is already a `DataCoordinate`. - graph : `DimensionGraph`, optional - Like ``dimensions``, but requires a ``DimensionGraph`` instance. - Ignored if ``dimensions`` is provided. Deprecated and will be - removed after v27. universe : `DimensionUniverse` All known dimensions and their relationships; used to expand and - validate dependencies when ``graph`` is not provided. + validate dependencies when ``dimensions`` is not provided. defaults : `DataCoordinate`, optional Default dimension key-value pairs to use when needed. These are - never used to infer ``graph``, and are ignored if a different value + never used to infer ``group``, and are ignored if a different value is provided for the same key in ``mapping`` or `**kwargs``. **kwargs Additional keyword arguments are treated like additional key-value @@ -222,28 +211,11 @@ def standardize( DimensionNameError Raised if a key-value pair for a required dimension is missing. """ - universe = ( - universe - or getattr(dimensions, "universe", None) - or getattr(graph, "universe", None) - or getattr(mapping, "universe", None) - ) + universe = universe or getattr(dimensions, "universe", None) or getattr(mapping, "universe", None) if universe is None: - raise TypeError( - "universe must be provided, either directly or via dimensions, mapping, or graph." - ) - if graph is not None: - # TODO: remove argument on DM-41326. - warnings.warn( - "The 'graph' argument to DataCoordinate.standardize is deprecated in favor of the " - "'dimensions' argument, and will be removed after v27.", - category=FutureWarning, - stacklevel=find_outside_stacklevel("lsst.daf.butler"), - ) - dimensions = graph.names + raise TypeError("universe must be provided, either directly or via dimensions or mapping.") if dimensions is not None: dimensions = universe.conform(dimensions) - del graph # make sure we don't actualy use this below new_mapping: dict[str, DataIdValue] = {} if isinstance(mapping, DataCoordinate): if dimensions is None: @@ -260,14 +232,6 @@ def standardize( new_mapping.update((name, mapping[name]) for name in mapping.dimensions.required) if mapping.hasFull(): new_mapping.update((name, mapping[name]) for name in mapping.dimensions.implied) - elif isinstance(mapping, NamedKeyMapping): - warnings.warn( - "Passing a NamedKeyMapping to DataCoordinate.standardize is deprecated, and will be " - "removed after v27.", - category=FutureWarning, - stacklevel=find_outside_stacklevel("lsst.daf.butler"), - ) - new_mapping.update(mapping.byName()) elif mapping is not None: new_mapping.update(mapping) new_mapping.update(kwargs) @@ -275,7 +239,7 @@ def standardize( if defaults is not None: universe = defaults.universe elif universe is None: - raise TypeError("universe must be provided if graph is not.") + raise TypeError("universe must be provided if dimensions is not.") dimensions = DimensionGroup(universe, new_mapping.keys()) if not dimensions: return DataCoordinate.make_empty(universe) @@ -376,42 +340,7 @@ def make_empty(universe: DimensionUniverse) -> DataCoordinate: `hasRecords` are guaranteed to return `True`, because both `full` and `records` are just empty mappings. """ - return _ExpandedTupleDataCoordinate(universe.empty.as_group(), (), {}) - - # TODO: remove on DM-41326. - @staticmethod - @deprecated( - "fromRequiredValues is deprecated in favor of from_required_values, " - "which takes a DimensionGroup instead of a DimensionGraph. It will be " - "removed after v27.", - version="v27", - category=FutureWarning, - ) - def fromRequiredValues(graph: DimensionGraph, values: tuple[DataIdValue, ...]) -> DataCoordinate: - """Construct a `DataCoordinate` from required dimension values. - - This method is deprecated in favor of `from_required_values`. - - This is a low-level interface with at most assertion-level checking of - inputs. Most callers should use `standardize` instead. - - Parameters - ---------- - graph : `DimensionGraph` - Dimensions this data ID will identify. - values : `tuple` [ `int` or `str` ] - Tuple of primary key values corresponding to ``graph.required``, - in that order. - - Returns - ------- - dataId : `DataCoordinate` - A data ID object that identifies the given dimensions. - ``dataId.hasFull()`` will return `True` only if ``graph.implied`` - is empty. ``dataId.hasRecords()`` will return `True` - if and only if ``graph`` is empty. - """ - return DataCoordinate.from_required_values(graph._group, values) + return _ExpandedTupleDataCoordinate(universe.empty, (), {}) @staticmethod def from_required_values(dimensions: DimensionGroup, values: tuple[DataIdValue, ...]) -> DataCoordinate: @@ -425,8 +354,8 @@ def from_required_values(dimensions: DimensionGroup, values: tuple[DataIdValue, dimensions : `DimensionGroup` Dimensions this data ID will identify. values : `tuple` [ `int` or `str` ] - Tuple of primary key values corresponding to ``graph.required``, in - that order. + Tuple of primary key values corresponding to + ``dimensions.required``, in that order. Returns ------- @@ -434,7 +363,7 @@ def from_required_values(dimensions: DimensionGroup, values: tuple[DataIdValue, A data ID object that identifies the given dimensions. ``dataId.hasFull()`` will return `True` only if ``dimensions.implied`` is empty. ``dataId.hasRecords()`` will - return `True` if and only if ``graph`` is empty. + return `True` if and only if ``dimensions`` is empty. """ assert len(dimensions.required) == len( values @@ -445,43 +374,6 @@ def from_required_values(dimensions: DimensionGroup, values: tuple[DataIdValue, return _FullTupleDataCoordinate(dimensions, values) return _RequiredTupleDataCoordinate(dimensions, values) - # TODO: remove on DM-41326. - @staticmethod - @deprecated( - "fromFullValues is deprecated in favor of from_full_values, " - "which takes a DimensionGroup instead of a DimensionGraph. It will be " - "removed after v27.", - version="v27", - category=FutureWarning, - ) - def fromFullValues(graph: DimensionGraph, values: tuple[DataIdValue, ...]) -> DataCoordinate: - """Construct a `DataCoordinate` from all dimension values. - - This method is deprecated in favor of `from_full_values`. - - This is a low-level interface with at most assertion-level checking of - inputs. Most callers should use `standardize` instead. - - Parameters - ---------- - graph : `DimensionGraph` - Dimensions this data ID will identify. - values : `tuple` [ `int` or `str` ] - Tuple of primary key values corresponding to - ``itertools.chain(graph.required, graph.implied)``, in that order. - Note that this is _not_ the same order as ``graph.dimensions``, - though these contain the same elements. - - Returns - ------- - dataId : `DataCoordinate` - A data ID object that identifies the given dimensions. - ``dataId.hasFull()`` will always return `True`. - ``dataId.hasRecords()`` will only return `True` if ``graph`` is - empty. - """ - return DataCoordinate.from_full_values(graph._group, values) - @staticmethod def from_full_values(dimensions: DimensionGroup, values: tuple[DataIdValue, ...]) -> DataCoordinate: """Construct a `DataCoordinate` from all dimension values. @@ -495,9 +387,9 @@ def from_full_values(dimensions: DimensionGroup, values: tuple[DataIdValue, ...] Dimensions this data ID will identify. values : `tuple` [ `int` or `str` ] Tuple of primary key values corresponding to - ``itertools.chain(graph.required, graph.implied)``, in that order. - Note that this is _not_ the same order as ``graph.dimensions``, - though these contain the same elements. + ``itertools.chain(dimensions.required, dimensions.implied)``, in + that order. Note that this is _not_ the same order as + ``dimensions.names``, though these contain the same elements. Returns ------- @@ -525,6 +417,32 @@ def __eq__(self, other: Any) -> bool: other = DataCoordinate.standardize(other, universe=self.universe) return self.dimensions == other.dimensions and self.required_values == other.required_values + @abstractmethod + def __getitem__(self, key: str) -> DataIdValue: + raise NotImplementedError() + + def __contains__(self, key: str) -> bool: + try: + self.__getitem__(key) + return True + except KeyError: + return False + + @overload + def get(self, key: str) -> DataIdValue | None: ... + + @overload + def get(self, key: str, default: int) -> int: ... + + @overload + def get(self, key: str, default: str) -> str: ... + + def get(self, key: str, default: DataIdValue | None = None) -> DataIdValue | None: + try: + return self.__getitem__(key) + except KeyError: + return default + def __repr__(self) -> str: # We can't make repr yield something that could be exec'd here without # printing out the whole DimensionUniverse. @@ -537,69 +455,21 @@ def __lt__(self, other: Any) -> bool: # can not be true simultaneously with __lt__ being true. return self.required_values < other.required_values - # TODO: remove on DM-41326. - @deprecated( - "Using DataCoordinate as a Mapping is deprecated in favor of the " - ".mapping and .required attributes, and will be dropped after v27.", - version="v27", - category=FutureWarning, - ) - def __iter__(self) -> Iterator[Dimension]: - return iter(self.keys()) - - # TODO: remove on DM-41326. - @deprecated( - "Using DataCoordinate as a Mapping is deprecated in favor of the " - ".mapping and .required attributes, and will be dropped after v27.", - version="v27", - category=FutureWarning, - ) - def __len__(self) -> int: - return len(self.keys()) - - # TODO: remove on DM-41326. - @deprecated( - "Using DataCoordinate as a Mapping is deprecated in favor of the " - ".mapping and .required attributes, and will be dropped after v27.", - version="v27", - category=FutureWarning, - ) - def keys(self) -> NamedValueAbstractSet[Dimension]: # type: ignore - return self.graph.required - - # TODO: remove on DM-41326. - @property - @deprecated( - "DataCoordinate.names is deprecated in favor of the .dimensions " - "attribute, and will be dropped after v27.", - version="v27", - category=FutureWarning, - ) - def names(self) -> Set[str]: - """Names of the required dimensions identified by this data ID. - - They are returned in the same order as `keys` - (`collections.abc.Set` [ `str` ]). - """ - return self.keys().names - @abstractmethod - def subset(self, dimensions: DimensionGraph | DimensionGroup | Iterable[str]) -> DataCoordinate: - """Return a `DataCoordinate` whose graph is a subset of ``self.graph``. + def subset(self, dimensions: DimensionGroup | Iterable[str]) -> DataCoordinate: + """Return a `DataCoordinate` whose diensions are a subset of + ``self.dimensions``. Parameters ---------- - dimensions : `DimensionGraph`, `DimensionGroup`, or \ - `~collections.abc.Iterable` [ `str` ] + dimensions : `DimensionGroup` or `~collections.abc.Iterable` [ `str` ] The dimensions identified by the returned `DataCoordinate`. - Passing a `DimensionGraph` is deprecated and support will be - dropped after v27. Returns ------- coordinate : `DataCoordinate` A `DataCoordinate` instance that identifies only the given - dimensions. May be ``self`` if ``graph == self.graph``. + dimensions. May be ``self`` if ``dimensions == self.dimensions``. Raises ------ @@ -619,7 +489,6 @@ def subset(self, dimensions: DimensionGraph | DimensionGroup | Iterable[str]) -> return `True` (respectively) on the returned `DataCoordinate` as well. The converse does not hold. """ - # TODO: update docs r.e. deprecation on DM-41326. raise NotImplementedError() @abstractmethod @@ -650,9 +519,7 @@ def union(self, other: DataCoordinate) -> DataCoordinate: raise NotImplementedError() @abstractmethod - def expanded( - self, records: NameLookupMapping[DimensionElement, DimensionRecord | None] - ) -> DataCoordinate: + def expanded(self, records: Mapping[str, DimensionRecord | None]) -> DataCoordinate: """Return a `DataCoordinate` that holds the given records. Guarantees that `hasRecords` returns `True`. @@ -664,15 +531,12 @@ def expanded( ---------- records : `~collections.abc.Mapping` [ `str`, `DimensionRecord` or \ `None` ] - A `NamedKeyMapping` with `DimensionElement` keys or a regular - `~collections.abc.Mapping` with `str` (`DimensionElement` name) + A`~collections.abc.Mapping` with `str` (dimension element name) keys and `DimensionRecord` values. Keys must cover all elements in - ``self.graph.elements``. Values may be `None`, but only to reflect - actual NULL values in the database, not just records that have not - been fetched. Passing a `NamedKeyMapping` is deprecated and will - not be supported after v27. + ``self.dimensions.elements``. Values may be `None`, but only to + reflect actual NULL values in the database, not just records that + have not been fetched. """ - # TODO: update docs r.e. deprecation on DM-41326. raise NotImplementedError() @property @@ -695,22 +559,6 @@ def dimensions(self) -> DimensionGroup: """ raise NotImplementedError() - # TODO: remove on DM-41326. - @property - @deprecated( - "DataCoordinate.graph is deprecated in favor of .dimensions, and will be dropped after v27.", - version="v27", - category=FutureWarning, - ) - def graph(self) -> DimensionGraph: - """Dimensions identified by this data ID (`DimensionGraph`). - - Note that values are only required to be present for dimensions in - ``self.graph.required``; all others may be retrieved (from a - `Registry`) given these. - """ - return self.dimensions._as_graph() - @abstractmethod def hasFull(self) -> bool: """Whether this data ID contains implied and required values. @@ -727,43 +575,6 @@ def hasFull(self) -> bool: """ raise NotImplementedError() - # TODO: remove on DM-41326. - @property - @deprecated( - "DataCoordinate.full is deprecated in favor of .mapping, and will be dropped after v27.", - version="v27", - category=FutureWarning, - ) - @abstractmethod - def full(self) -> NamedKeyMapping[Dimension, DataIdValue]: - """Return mapping for all dimensions in ``self.dimensions``. - - The mapping includes key-value pairs for all dimensions in - ``self.dimensions``, including implied. - - Accessing this attribute if `hasFull` returns `False` is a logic error - that may raise an exception of unspecified type either immediately or - when implied keys are accessed via the returned mapping, depending on - the implementation and whether assertions are enabled. - """ - raise NotImplementedError() - - # TODO: remove on DM-41326. - @deprecated( - "DataCoordinate.values_tuple() is deprecated in favor of .required_values, and will be dropped " - "after v27.", - version="v27", - category=FutureWarning, - ) - def values_tuple(self) -> tuple[DataIdValue, ...]: - """Return the required values (only) of this data ID as a tuple. - - In contexts where all data IDs have the same dimensions, comparing and - hashing these tuples can be *much* faster than comparing the original - `DataCoordinate` instances. - """ - return self.required_values - @abstractmethod def hasRecords(self) -> bool: """Whether this data ID contains records. @@ -785,13 +596,10 @@ def hasRecords(self) -> bool: raise NotImplementedError() @property - def records(self) -> NamedKeyMapping[DimensionElement, DimensionRecord | None]: + def records(self) -> Mapping[str, DimensionRecord | None]: """A mapping that contains `DimensionRecord` objects for all elements identified by this data ID. - This mapping will become a regular `~collections.abc.Mapping` with - `str` keys after v27. - Notes ----- The values of this mapping may be `None` if and only if there is no @@ -961,48 +769,7 @@ def from_simple( """ -# Deprecated by having its only public access (DataCoordinate.full) deprecated. -# TODO: remove on DM-41326. -class _DataCoordinateFullView(NamedKeyMapping[Dimension, DataIdValue]): - """View class for `DataCoordinate.full`. - - Provides the default implementation for - `DataCoordinate.full`. - - Parameters - ---------- - target : `DataCoordinate` - The `DataCoordinate` instance this object provides a view of. - """ - - def __init__(self, target: _BasicTupleDataCoordinate): - self._target = target - - __slots__ = ("_target",) - - def __repr__(self) -> str: - return repr(self._target) - - def __getitem__(self, key: DataIdKey) -> DataIdValue: - return self._target[key] - - def __iter__(self) -> Iterator[Dimension]: - return iter(self.keys()) - - def __len__(self) -> int: - return len(self.keys()) - - def keys(self) -> NamedValueAbstractSet[Dimension]: # type: ignore - return self._target.graph.dimensions - - @property - def names(self) -> Set[str]: - # Docstring inherited from `NamedKeyMapping`. - return self.keys().names - - -# TODO: Make a Mapping[str, DimensionRecord | None] on DM-41326. -class _DataCoordinateRecordsView(NamedKeyMapping[DimensionElement, DimensionRecord | None]): +class _DataCoordinateRecordsView(Mapping[str, DimensionRecord | None]): """View class for `DataCoordinate.records`. Provides the default implementation for @@ -1020,50 +787,20 @@ def __init__(self, target: DataCoordinate): __slots__ = ("_target",) def __repr__(self) -> str: - terms = [f"{d}: {self[d]!r}" for d in self._target.graph.elements.names] + terms = [f"{d}: {self[d]!r}" for d in self._target.dimensions.elements] return "{{{}}}".format(", ".join(terms)) def __str__(self) -> str: return "\n".join(str(v) for v in self.values()) - def __getitem__(self, key: DimensionElement | str) -> DimensionRecord | None: - if isinstance(key, DimensionElement): - warnings.warn( - "Using Dimension keys in DataCoordinate is deprecated and will not be supported after v27.", - category=FutureWarning, - stacklevel=find_outside_stacklevel("lsst.daf.butler"), - ) - key = key.name + def __getitem__(self, key: str) -> DimensionRecord | None: return self._target._record(key) - # TODO: fix on DM-41326. - @deprecated( - "Iteration over DataCoordinate.records is deprecated as the key type will change to 'str' after " - "v27. Use DataCoordinate.dimensions.elements to get the names of all dimension elements instead.", - version="v27", - category=FutureWarning, - ) - def __iter__(self) -> Iterator[DimensionElement]: - return iter(self.keys()) + def __iter__(self) -> Iterator[str]: + return iter(self._target.dimensions.elements) def __len__(self) -> int: - return len(self.keys()) - - # TODO: remove on DM-41326. - # Deprecation warning will come from using .graph. - def keys(self) -> NamedValueAbstractSet[DimensionElement]: # type: ignore - return self._target.graph.elements - - @property - @deprecated( - "DataCoordinate.records.names is deprecated in favor of DataCoordinate.dimensions.elements and " - "will be removed after v27.", - version="v27", - category=FutureWarning, - ) - def names(self) -> Set[str]: - # Docstring inherited from `NamedKeyMapping`. - return self.keys().names + return len(self._target.dimensions.elements) class _BasicTupleDataCoordinate(DataCoordinate): @@ -1101,16 +838,9 @@ def required(self) -> Mapping[str, DataIdValue]: # Docstring inherited from DataCoordinate. return _DataCoordinateRequiredMappingView(self) - def __getitem__(self, key: DataIdKey) -> DataIdValue: + def __getitem__(self, key: str) -> DataIdValue: # Docstring inherited from DataCoordinate. - # TODO: remove on DM-41326. - if isinstance(key, Dimension): - warnings.warn( - "Using Dimension keys in DataCoordinate is deprecated and will not be supported after v27.", - category=FutureWarning, - stacklevel=find_outside_stacklevel("lsst.daf.butler"), - ) - key = key.name + index = self._dimensions._data_coordinate_indices[key] try: return self._values[index] @@ -1119,21 +849,6 @@ def __getitem__(self, key: DataIdKey) -> DataIdValue: # values for the required ones. raise KeyError(key) from None - # TODO: remove on DM-41326. - @deprecated( - "Using DataCoordinate as a NamedKeyMapping is deprecated in favor of the " - ".mapping and .required attributes, and will be dropped after v27. " - "Use `dict(data_id.required)` as an exact replacement for `data_id.byName()`.", - version="v27", - category=FutureWarning, - ) - def byName(self) -> dict[str, DataIdValue]: - # Docstring inheritance. - # Reimplementation is for optimization; `required_values` is much - # faster to iterate over than values() because it doesn't go through - # `__getitem__`. - return dict(zip(self.names, self.required_values, strict=True)) - def hasRecords(self) -> bool: # Docstring inherited from DataCoordinate. return False @@ -1220,7 +935,7 @@ def required_values(self) -> tuple[DataIdValue, ...]: # Docstring inherited from DataCoordinate. return self._values - def subset(self, dimensions: DimensionGraph | DimensionGroup | Iterable[str]) -> DataCoordinate: + def subset(self, dimensions: DimensionGroup | Iterable[str]) -> DataCoordinate: # Docstring inherited from DataCoordinate. dimensions = self.universe.conform(dimensions) if self._dimensions == dimensions: @@ -1248,30 +963,15 @@ def union(self, other: DataCoordinate) -> DataCoordinate: values.update(other.mapping) return DataCoordinate.standardize(values, dimensions=dimensions) - # TODO: remove on DM-41326. - @property - def full(self) -> NamedKeyMapping[Dimension, DataIdValue]: - # Docstring inherited. - raise AssertionError("full may only be accessed if hasFull() returns True.") - - def expanded( - self, records: NameLookupMapping[DimensionElement, DimensionRecord | None] - ) -> DataCoordinate: + def expanded(self, records: Mapping[str, DimensionRecord | None]) -> DataCoordinate: # Docstring inherited from DataCoordinate # Extract a complete values tuple from the attributes of the given # records. It's possible for these to be inconsistent with # self._values (which is a serious problem, of course), but we've # documented this as a no-checking API. values = self._values + tuple( - getattr(records[d], cast(Dimension, self.universe[d]).primaryKey.name) - for d in self._dimensions.implied + getattr(records[d], self.universe.dimensions[d].primaryKey.name) for d in self._dimensions.implied ) - if isinstance(records, NamedKeyMapping): - warnings.warn( - "NamedKeyMappings will not be accepted after v27; pass a Mapping with str keys instead.", - stacklevel=find_outside_stacklevel("lsst.daf.butler"), - category=FutureWarning, - ) return _ExpandedTupleDataCoordinate(self._dimensions, values, records) def hasFull(self) -> bool: @@ -1307,7 +1007,7 @@ def full_values(self) -> tuple[DataIdValue, ...]: # Docstring inherited from DataCoordinate. return self._values - def subset(self, dimensions: DimensionGraph | DimensionGroup | Iterable[str]) -> DataCoordinate: + def subset(self, dimensions: DimensionGroup | Iterable[str]) -> DataCoordinate: # Docstring inherited from DataCoordinate. dimensions = self.universe.conform(dimensions) if self._dimensions == dimensions: @@ -1331,27 +1031,8 @@ def union(self, other: DataCoordinate) -> DataCoordinate: values.update(other.mapping) return DataCoordinate.standardize(values, dimensions=dimensions) - # TODO: remove on DM-41326. - @property - @deprecated( - "DataCoordinate.full is deprecated in favor of .mapping, and will be dropped after v27.", - version="v27", - category=FutureWarning, - ) - def full(self) -> NamedKeyMapping[Dimension, DataIdValue]: - # Docstring inherited. - return _DataCoordinateFullView(self) - - def expanded( - self, records: NameLookupMapping[DimensionElement, DimensionRecord | None] - ) -> DataCoordinate: + def expanded(self, records: Mapping[str, DimensionRecord | None]) -> DataCoordinate: # Docstring inherited from DataCoordinate - if isinstance(records, NamedKeyMapping): - warnings.warn( - "NamedKeyMappings will not be accepted after v27; pass a Mapping with str keys instead.", - stacklevel=find_outside_stacklevel("lsst.daf.butler"), - category=FutureWarning, - ) return _ExpandedTupleDataCoordinate(self._dimensions, self._values, records) def hasFull(self) -> bool: @@ -1379,8 +1060,7 @@ class _ExpandedTupleDataCoordinate(_FullTupleDataCoordinate): ``dimensions._data_coordinate_indices``. Just include values for all dimensions. records : `~collections.abc.Mapping` [ `str`, `DimensionRecord` or `None` ] - A `NamedKeyMapping` with `DimensionElement` keys or a regular - `~collections.abc.Mapping` with `str` (`DimensionElement` name) keys + A `~collections.abc.Mapping` with `str` (dimension element name) keys and `DimensionRecord` values. Keys must cover all elements in ``self.dimensions.elements``. Values may be `None`, but only to reflect actual NULL values in the database, not just records that have @@ -1391,7 +1071,7 @@ def __init__( self, dimensions: DimensionGroup, values: tuple[DataIdValue, ...], - records: NameLookupMapping[DimensionElement, DimensionRecord | None], + records: Mapping[str, DimensionRecord | None], ): super().__init__(dimensions, values) assert super().hasFull(), "This implementation requires full dimension records." @@ -1399,20 +1079,12 @@ def __init__( __slots__ = ("_records",) - def subset(self, dimensions: DimensionGraph | DimensionGroup | Iterable[str]) -> DataCoordinate: + def subset(self, dimensions: DimensionGroup | Iterable[str]) -> DataCoordinate: # Docstring inherited from DataCoordinate. return super().subset(dimensions).expanded(self._records) - def expanded( - self, records: NameLookupMapping[DimensionElement, DimensionRecord | None] - ) -> DataCoordinate: + def expanded(self, records: Mapping[str, DimensionRecord | None]) -> DataCoordinate: # Docstring inherited from DataCoordinate. - if isinstance(records, NamedKeyMapping): - warnings.warn( - "NamedKeyMappings will not be accepted after v27; pass a Mapping with str keys instead.", - stacklevel=find_outside_stacklevel("lsst.daf.butler"), - category=FutureWarning, - ) return self def union(self, other: DataCoordinate) -> DataCoordinate: diff --git a/python/lsst/daf/butler/dimensions/_data_coordinate_iterable.py b/python/lsst/daf/butler/dimensions/_data_coordinate_iterable.py index 845c9e8cad..0d5e0e9c23 100644 --- a/python/lsst/daf/butler/dimensions/_data_coordinate_iterable.py +++ b/python/lsst/daf/butler/dimensions/_data_coordinate_iterable.py @@ -33,16 +33,11 @@ "DataCoordinateSequence", ) -import warnings from abc import abstractmethod from collections.abc import Collection, Iterable, Iterator, Sequence, Set from typing import Any, overload -from deprecated.sphinx import deprecated -from lsst.utils.introspection import find_outside_stacklevel - from ._coordinate import DataCoordinate -from ._graph import DimensionGraph from ._group import DimensionGroup from ._universe import DimensionUniverse @@ -51,7 +46,7 @@ class DataCoordinateIterable(Iterable[DataCoordinate]): """An abstract base class for homogeneous iterables of data IDs. All elements of a `DataCoordinateIterable` identify the same set of - dimensions (given by the `graph` property) and generally have the same + dimensions (given by the `dimensions` property) and generally have the same `DataCoordinate.hasFull` and `DataCoordinate.hasRecords` flag values. """ @@ -78,17 +73,6 @@ def fromScalar(dataId: DataCoordinate) -> _ScalarDataCoordinateIterable: """ return _ScalarDataCoordinateIterable(dataId) - # TODO: remove on DM-41326. - @property - @deprecated( - "Deprecated in favor of .dimensions; will be removed after v26.", - category=FutureWarning, - version="v27", - ) - def graph(self) -> DimensionGraph: - """Dimensions identified by these data IDs (`DimensionGraph`).""" - return self.dimensions._as_graph() - @property @abstractmethod def dimensions(self) -> DimensionGroup: @@ -166,7 +150,7 @@ def toSequence(self) -> DataCoordinateSequence: ) @abstractmethod - def subset(self, dimensions: DimensionGraph | DimensionGroup | Iterable[str]) -> DataCoordinateIterable: + def subset(self, dimensions: DimensionGroup | Iterable[str]) -> DataCoordinateIterable: """Return a subset iterable. This subset iterable returns data IDs that identify a subset of the @@ -174,8 +158,7 @@ def subset(self, dimensions: DimensionGraph | DimensionGroup | Iterable[str]) -> Parameters ---------- - dimensions : `DimensionGraph`, `DimensionGroup`, or \ - `~collections.abc.Iterable` [ `str` ] + dimensions : `DimensionGroup` or `~collections.abc.Iterable` [ `str` ] Dimensions to be identified by the data IDs in the returned iterable. Must be a subset of ``self.dimensions``. @@ -239,9 +222,7 @@ def hasRecords(self) -> bool: # Docstring inherited from DataCoordinateIterable. return self._dataId.hasRecords() - def subset( - self, dimensions: DimensionGraph | DimensionGroup | Iterable[str] - ) -> _ScalarDataCoordinateIterable: + def subset(self, dimensions: DimensionGroup | Iterable[str]) -> _ScalarDataCoordinateIterable: # Docstring inherited from DataCoordinateIterable. dimensions = self.universe.conform(dimensions) return _ScalarDataCoordinateIterable(self._dataId.subset(dimensions)) @@ -262,13 +243,8 @@ class _DataCoordinateCollectionBase(DataCoordinateIterable): dataIds : `collections.abc.Collection` [ `DataCoordinate` ] A collection of `DataCoordinate` instances, with dimensions equal to ``dimensions``. - graph : `DimensionGraph`, optional - Dimensions identified by all data IDs in the collection. Ignored if - ``dimensions`` is provided, and deprecated with removal after v27. - dimensions : `~collections.abc.Iterable` [ `str` ], `DimensionGroup`, \ - or `DimensionGraph`, optional - Dimensions identified by all data IDs in the collection. Must be - provided unless ``graph`` is. + dimensions : `~collections.abc.Iterable` [ `str` ], `DimensionGroup` + Dimensions identified by all data IDs in the collection. hasFull : `bool`, optional If `True`, the caller guarantees that `DataCoordinate.hasFull` returns `True` for all given data IDs. If `False`, no such guarantee is made, @@ -284,7 +260,7 @@ class _DataCoordinateCollectionBase(DataCoordinateIterable): `False`. check: `bool`, optional If `True` (default) check that all data IDs are consistent with the - given ``graph`` and state flags at construction. If `False`, no + given ``dimensions`` and state flags at construction. If `False`, no checking will occur. universe : `DimensionUniverse` Object that manages all dimension definitions. @@ -293,39 +269,20 @@ class _DataCoordinateCollectionBase(DataCoordinateIterable): def __init__( self, dataIds: Collection[DataCoordinate], - graph: DimensionGraph | None = None, *, - dimensions: Iterable[str] | DimensionGroup | DimensionGraph | None = None, + dimensions: Iterable[str] | DimensionGroup | None = None, hasFull: bool | None = None, hasRecords: bool | None = None, check: bool = True, universe: DimensionUniverse | None = None, ): - universe = ( - universe - or getattr(dimensions, "universe", None) - or getattr(graph, "universe", None) - or getattr(dataIds, "universe", None) - ) + universe = universe or getattr(dimensions, "universe", None) or getattr(dataIds, "universe", None) if universe is None: - raise TypeError( - "universe must be provided, either directly or via dimensions, dataIds, or graph." - ) - if graph is not None: - warnings.warn( - "The 'graph' argument to DataCoordinateIterable constructors is deprecated in favor of " - " passing an iterable of dimension names as the 'dimensions' argument, and wil be removed " - "after v27.", - stacklevel=find_outside_stacklevel("lsst.daf.butler"), - category=FutureWarning, - ) + raise TypeError("universe must be provided, either directly or via dimensions or dataIds.") if dimensions is not None: dimensions = universe.conform(dimensions) - elif graph is not None: - dimensions = graph.as_group() - del graph # Avoid accidental use later. - if dimensions is None: - raise TypeError("Exactly one of 'graph' or (preferably) 'dimensions' must be provided.") + else: + raise TypeError("'dimensions' must be provided.") self._dataIds = dataIds self._dimensions = dimensions if check: @@ -431,15 +388,10 @@ class DataCoordinateSet(_DataCoordinateCollectionBase): ---------- dataIds : `collections.abc.Set` [ `DataCoordinate` ] A set of `DataCoordinate` instances, with dimensions equal to - ``graph``. If this is a mutable object, the caller must be able to - guarantee that it will not be modified by any other holders. - graph : `DimensionGraph`, optional - Dimensions identified by all data IDs in the collection. Ignored if - ``dimensions`` is provided, and deprecated with removal after v27. - dimensions : `~collections.abc.Iterable` [ `str` ], `DimensionGroup`, \ - or `DimensionGraph`, optional - Dimensions identified by all data IDs in the collection. Must be - provided unless ``graph`` is. + ``dimensions``. If this is a mutable object, the caller must be able + to guarantee that it will not be modified by any other holders. + dimensions : `~collections.abc.Iterable` [ `str` ], `DimensionGroup` + Dimensions identified by all data IDs in the collection. hasFull : `bool`, optional If `True`, the caller guarantees that `DataCoordinate.hasFull` returns `True` for all given data IDs. If `False`, no such guarantee is made, @@ -456,7 +408,7 @@ class DataCoordinateSet(_DataCoordinateCollectionBase): first use if ``check`` is `False`. check : `bool`, optional If `True` (default) check that all data IDs are consistent with the - given ``graph`` and state flags at construction. If `False`, no + given ``dimensions`` and state flags at construction. If `False`, no checking will occur. universe : `DimensionUniverse` Object that manages all dimension definitions. @@ -501,9 +453,8 @@ class DataCoordinateSet(_DataCoordinateCollectionBase): def __init__( self, dataIds: Set[DataCoordinate], - graph: DimensionGraph | None = None, *, - dimensions: Iterable[str] | DimensionGroup | DimensionGraph | None = None, + dimensions: Iterable[str] | DimensionGroup | None = None, hasFull: bool | None = None, hasRecords: bool | None = None, check: bool = True, @@ -511,7 +462,6 @@ def __init__( ): super().__init__( dataIds, - graph, dimensions=dimensions, hasFull=hasFull, hasRecords=hasRecords, @@ -571,7 +521,8 @@ def issubset(self, other: DataCoordinateIterable) -> bool: Parameters ---------- other : `DataCoordinateIterable` - An iterable of data IDs with ``other.graph == self.graph``. + An iterable of data IDs with + ``other.dimensions == self.dimensions``. Returns ------- @@ -747,13 +698,12 @@ def toSet(self) -> DataCoordinateSet: # Docstring inherited from DataCoordinateIterable. return self - def subset(self, dimensions: DimensionGraph | DimensionGroup | Iterable[str]) -> DataCoordinateSet: + def subset(self, dimensions: DimensionGroup | Iterable[str]) -> DataCoordinateSet: """Return a set whose data IDs identify a subset. Parameters ---------- - dimensions : `DimensionGraph`, `DimensionGroup`, or \ - `~collections.abc.Iterable` [ `str` ] + dimensions : `DimensionGroup` or `~collections.abc.Iterable` [ `str` ] Dimensions to be identified by the data IDs in the returned iterable. Must be a subset of ``self.dimensions``. @@ -786,14 +736,9 @@ class DataCoordinateSequence(_DataCoordinateCollectionBase, Sequence[DataCoordin ---------- dataIds : `collections.abc.Sequence` [ `DataCoordinate` ] A sequence of `DataCoordinate` instances, with dimensions equal to - ``graph``. - graph : `DimensionGraph`, optional - Dimensions identified by all data IDs in the collection. Ignored if - ``dimensions`` is provided, and deprecated with removal after v27. - dimensions : `~collections.abc.Iterable` [ `str` ], `DimensionGroup`, \ - `DimensionGraph`, optional - Dimensions identified by all data IDs in the collection. Must be - provided unless ``graph`` is. + ``dimensions``. + dimensions : `~collections.abc.Iterable` [ `str` ], `DimensionGroup` + Dimensions identified by all data IDs in the collection. hasFull : `bool`, optional If `True`, the caller guarantees that `DataCoordinate.hasFull` returns `True` for all given data IDs. If `False`, no such guarantee is made, @@ -810,7 +755,7 @@ class DataCoordinateSequence(_DataCoordinateCollectionBase, Sequence[DataCoordin first use if ``check`` is `False`. check : `bool`, optional If `True` (default) check that all data IDs are consistent with the - given ``graph`` and state flags at construction. If `False`, no + given ``dimensions`` and state flags at construction. If `False`, no checking will occur. universe : `DimensionUniverse` Object that manages all dimension definitions. @@ -819,9 +764,8 @@ class DataCoordinateSequence(_DataCoordinateCollectionBase, Sequence[DataCoordin def __init__( self, dataIds: Sequence[DataCoordinate], - graph: DimensionGraph | None = None, *, - dimensions: Iterable[str] | DimensionGroup | DimensionGraph | None = None, + dimensions: Iterable[str] | DimensionGroup | None = None, hasFull: bool | None = None, hasRecords: bool | None = None, check: bool = True, @@ -829,7 +773,6 @@ def __init__( ): super().__init__( tuple(dataIds), - graph, dimensions=dimensions, hasFull=hasFull, hasRecords=hasRecords, @@ -879,22 +822,21 @@ def toSequence(self) -> DataCoordinateSequence: # Docstring inherited from DataCoordinateIterable. return self - def subset(self, dimensions: DimensionGraph | DimensionGroup | Iterable[str]) -> DataCoordinateSequence: + def subset(self, dimensions: DimensionGroup | Iterable[str]) -> DataCoordinateSequence: """Return a sequence whose data IDs identify a subset. Parameters ---------- - dimensions : `DimensionGraph`, `DimensionGroup`, \ - or `~collections.abc.Iterable` [ `str` ] + dimensions : `DimensionGroup` or `~collections.abc.Iterable` [ `str` ] Dimensions to be identified by the data IDs in the returned iterable. Must be a subset of ``self.dimensions``. Returns ------- set : `DataCoordinateSequence` - A `DataCoordinateSequence` with ``set.graph == graph``. - Will be ``self`` if ``graph == self.graph``. Elements are - equivalent to those that would be created by calling + A `DataCoordinateSequence` with ``set.dimensions == dimensions``. + Will be ``self`` if ``dimensions == self.dimensions``. Elements + are equivalent to those that would be created by calling `DataCoordinate.subset` on all elements in ``self``, in the same order and with no deduplication. """ diff --git a/python/lsst/daf/butler/dimensions/_elements.py b/python/lsst/daf/butler/dimensions/_elements.py index 11e6330c63..8d0f0db91b 100644 --- a/python/lsst/daf/butler/dimensions/_elements.py +++ b/python/lsst/daf/butler/dimensions/_elements.py @@ -48,7 +48,6 @@ if TYPE_CHECKING: # Imports needed only for type annotations; may be circular. from ..registry import Registry from ._governor import GovernorDimension - from ._graph import DimensionGraph from ._group import DimensionGroup from ._records import DimensionRecord from ._schema import DimensionRecordSchema @@ -304,20 +303,6 @@ def dimensions(self) -> NamedValueAbstractSet[Dimension]: """ return NamedValueSet(list(self.required) + list(self.implied)).freeze() - # Deprecated via a warning from its implementation. - # TODO: remove on DM-41326. - @property - def graph(self) -> DimensionGraph: - """Return minimal graph that includes this element (`DimensionGraph`). - - ``self.graph.required`` includes all dimensions whose primary key - values are sufficient (often necessary) to uniquely identify ``self`` - (including ``self`` if ``isinstance(self, Dimension)``. - ``self.graph.implied`` includes all dimensions also identified - (possibly recursively) by this set. - """ - return self.minimal_group._as_graph() - @property @cached_getter def minimal_group(self) -> DimensionGroup: diff --git a/python/lsst/daf/butler/dimensions/_graph.py b/python/lsst/daf/butler/dimensions/_graph.py deleted file mode 100644 index 81598ca0e3..0000000000 --- a/python/lsst/daf/butler/dimensions/_graph.py +++ /dev/null @@ -1,623 +0,0 @@ -# This file is part of daf_butler. -# -# Developed for the LSST Data Management System. -# This product includes software developed by the LSST Project -# (http://www.lsst.org). -# See the COPYRIGHT file at the top-level directory of this distribution -# for details of code ownership. -# -# This software is dual licensed under the GNU General Public License and also -# under a 3-clause BSD license. Recipients may choose which of these licenses -# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, -# respectively. If you choose the GPL option then the following text applies -# (but note that there is still no warranty even if you opt for BSD instead): -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from __future__ import annotations - -__all__ = ["DimensionGraph", "SerializedDimensionGraph"] - -import warnings -from collections.abc import Iterable, Iterator, Mapping, Set -from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast - -import pydantic -from deprecated.sphinx import deprecated -from lsst.utils.classes import cached_getter, immutable -from lsst.utils.introspection import find_outside_stacklevel - -from .._named import NamedValueAbstractSet, NameMappingSetView -from .._topology import TopologicalFamily, TopologicalSpace -from ..json import from_json_pydantic, to_json_pydantic -from ._group import DimensionGroup - -if TYPE_CHECKING: # Imports needed only for type annotations; may be circular. - from ..registry import Registry - from ._elements import Dimension, DimensionElement - from ._governor import GovernorDimension - from ._skypix import SkyPixDimension - from ._universe import DimensionUniverse - - -class SerializedDimensionGraph(pydantic.BaseModel): - """Simplified model of a `DimensionGraph` suitable for serialization.""" - - names: list[str] - - @classmethod - def direct(cls, *, names: list[str]) -> SerializedDimensionGraph: - """Construct a `SerializedDimensionGraph` directly without validators. - - Parameters - ---------- - names : `list` [`str`] - The names of the dimensions to include. - - Returns - ------- - graph : `SerializedDimensionGraph` - Model representing these dimensions. - - Notes - ----- - This differs from the pydantic "construct" method in that the arguments - are explicitly what the model requires, and it will recurse through - members, constructing them from their corresponding `direct` methods. - - This method should only be called when the inputs are trusted. - """ - return cls.model_construct(names=names) - - -_T = TypeVar("_T", bound="DimensionElement", covariant=True) - - -# TODO: Remove on DM-41326. -_NVAS_DEPRECATION_MSG = """DimensionGraph is deprecated in favor of -DimensionGroup, which uses sets of str names instead of NamedValueAbstractSets -of Dimension or DimensionElement instances. Support for the -NamedValueAbstractSet interfaces on this object will be dropped after v27. -""" - - -class _DimensionGraphNamedValueSet(NameMappingSetView[_T]): - def __init__(self, keys: Set[str], universe: DimensionUniverse): - super().__init__({k: cast(_T, universe[k]) for k in keys}) - - # TODO: Remove on DM-41326. - @deprecated( - _NVAS_DEPRECATION_MSG - + "Use a dict comprehension and DimensionUniverse indexing to construct a mapping when needed.", - version="v27", - category=FutureWarning, - ) - def asMapping(self) -> Mapping[str, _T]: - return super().asMapping() - - # TODO: Remove on DM-41326. - @deprecated( - _NVAS_DEPRECATION_MSG + "Use DimensionUniverse for DimensionElement lookups.", - version="v27", - category=FutureWarning, - ) - def __getitem__(self, key: str | _T) -> _T: - return super().__getitem__(key) - - def __contains__(self, key: Any) -> bool: - from ._elements import DimensionElement - - if isinstance(key, DimensionElement): - warnings.warn( - _NVAS_DEPRECATION_MSG + "'in' expressions must use str keys.", - category=FutureWarning, - stacklevel=find_outside_stacklevel("lsst.daf.butler."), - ) - return super().__contains__(key) - - def __iter__(self) -> Iterator[_T]: - # TODO: Remove on DM-41326. - warnings.warn( - _NVAS_DEPRECATION_MSG - + ( - "In the future, iteration will yield str names; for now, use .names " - "to do the same without triggering this warning." - ), - category=FutureWarning, - stacklevel=find_outside_stacklevel("lsst.daf.butler."), - ) - return super().__iter__() - - def __eq__(self, other: Any) -> bool: - # TODO: Remove on DM-41326. - warnings.warn( - _NVAS_DEPRECATION_MSG - + ( - "In the future, set-equality will assume str keys; for now, use .names " - "to do the same without triggering this warning." - ), - category=FutureWarning, - stacklevel=find_outside_stacklevel("lsst.daf.butler."), - ) - return super().__eq__(other) - - def __le__(self, other: Set[Any]) -> bool: - # TODO: Remove on DM-41326. - warnings.warn( - _NVAS_DEPRECATION_MSG - + ( - "In the future, subset tests will assume str keys; for now, use .names " - "to do the same without triggering this warning." - ), - category=FutureWarning, - stacklevel=find_outside_stacklevel("lsst.daf.butler."), - ) - return super().__le__(other) - - def __ge__(self, other: Set[Any]) -> bool: - # TODO: Remove on DM-41326. - warnings.warn( - _NVAS_DEPRECATION_MSG - + ( - "In the future, superset tests will assume str keys; for now, use .names " - "to do the same without triggering this warning." - ), - category=FutureWarning, - stacklevel=find_outside_stacklevel("lsst.daf.butler."), - ) - return super().__ge__(other) - - -# TODO: Remove on DM-41326. -@deprecated( - "DimensionGraph is deprecated in favor of DimensionGroup and will be removed after v27.", - category=FutureWarning, - version="v27", -) -@immutable -class DimensionGraph: # numpydoc ignore=PR02 - """An immutable, dependency-complete collection of dimensions. - - `DimensionGraph` is deprecated in favor of `DimensionGroup` and will be - removed after v27. The two types have very similar interfaces, but - `DimensionGroup` does not support direct iteration and its set-like - attributes are of dimension element names, not `DimensionElement` - instances. `DimensionGraph` objects are still returned by certain - non-deprecated methods and properties (most prominently - `DatasetType.dimensions`), and to handle these cases deprecation warnings - are only emitted for operations on `DimensionGraph` that are not - supported by `DimensionGroup` as well. - - Parameters - ---------- - universe : `DimensionUniverse` - The special graph of all known dimensions of which this graph will be a - subset. - dimensions : iterable of `Dimension`, optional - An iterable of `Dimension` instances that must be included in the - graph. All (recursive) dependencies of these dimensions will also be - included. At most one of ``dimensions`` and ``names`` must be - provided. - names : iterable of `str`, optional - An iterable of the names of dimensions that must be included in the - graph. All (recursive) dependencies of these dimensions will also be - included. At most one of ``dimensions`` and ``names`` must be - provided. - conform : `bool`, optional - If `True` (default), expand to include dependencies. `False` should - only be used for callers that can guarantee that other arguments are - already correctly expanded, and is primarily for internal use. - - Notes - ----- - `DimensionGraph` should be used instead of other collections in most - contexts where a collection of dimensions is required and a - `DimensionUniverse` is available. Exceptions include cases where order - matters (and is different from the consistent ordering defined by the - `DimensionUniverse`), or complete `~collection.abc.Set` semantics are - required. - """ - - _serializedType: ClassVar[type[pydantic.BaseModel]] = SerializedDimensionGraph - - def __new__( - cls, - universe: DimensionUniverse, - dimensions: Iterable[Dimension] | None = None, - names: Iterable[str] | None = None, - conform: bool = True, - ) -> DimensionGraph: - if names is None: - if dimensions is None: - group = DimensionGroup(universe) - else: - group = DimensionGroup(universe, {d.name for d in dimensions}, _conform=conform) - else: - if dimensions is not None: - raise TypeError("Only one of 'dimensions' and 'names' may be provided.") - group = DimensionGroup(universe, names, _conform=conform) - return group._as_graph() - - @property - def universe(self) -> DimensionUniverse: - """Object that manages all known dimensions.""" - return self._group.universe - - @property - @deprecated( - _NVAS_DEPRECATION_MSG + "Use '.names' instead of '.dimensions' or '.dimensions.names'.", - version="v27", - category=FutureWarning, - ) - @cached_getter - def dimensions(self) -> NamedValueAbstractSet[Dimension]: - """A true `~collections.abc.Set` of all true `Dimension` instances in - the graph. - """ - return _DimensionGraphNamedValueSet(self._group.names, self._group.universe) - - @property - @cached_getter - def elements(self) -> NamedValueAbstractSet[DimensionElement]: - """A true `~collections.abc.Set` of all `DimensionElement` instances in - the graph; a superset of `dimensions` (`NamedValueAbstractSet` of - `DimensionElement`). - """ - return _DimensionGraphNamedValueSet(self._group.elements, self._group.universe) - - @property - @cached_getter - def governors(self) -> NamedValueAbstractSet[GovernorDimension]: - """A true `~collections.abc.Set` of all `GovernorDimension` instances - in the graph. - """ - return _DimensionGraphNamedValueSet(self._group.governors, self._group.universe) - - @property - @cached_getter - def skypix(self) -> NamedValueAbstractSet[SkyPixDimension]: - """A true `~collections.abc.Set` of all `SkyPixDimension` instances - in the graph. - """ - return _DimensionGraphNamedValueSet(self._group.skypix, self._group.universe) - - @property - @cached_getter - def required(self) -> NamedValueAbstractSet[Dimension]: - """The subset of `dimensions` whose elements must be directly - identified via their primary keys in a data ID in order to identify the - rest of the elements in the graph. - """ - return _DimensionGraphNamedValueSet(self._group.required, self._group.universe) - - @property - @cached_getter - def implied(self) -> NamedValueAbstractSet[Dimension]: - """The subset of `dimensions` whose elements need not be directly - identified via their primary keys in a data ID. - """ - return _DimensionGraphNamedValueSet(self._group.implied, self._group.universe) - - def __getnewargs__(self) -> tuple: - return (self.universe, None, tuple(self._group.names), False) - - def __deepcopy__(self, memo: dict) -> DimensionGraph: - # DimensionGraph is recursively immutable; see note in @immutable - # decorator. - return self - - @property - def names(self) -> Set[str]: - """Set of the names of all dimensions in the graph.""" - return self._group.names - - def to_simple(self, minimal: bool = False) -> SerializedDimensionGraph: - """Convert this class to a simple python type. - - This type is suitable for serialization. - - Parameters - ---------- - minimal : `bool`, optional - Use minimal serialization. Has no effect on for this class. - - Returns - ------- - names : `list` - The names of the dimensions. - """ - # Names are all we can serialize. - return SerializedDimensionGraph(names=list(self.names)) - - @classmethod - def from_simple( - cls, - names: SerializedDimensionGraph, - universe: DimensionUniverse | None = None, - registry: Registry | None = None, - ) -> DimensionGraph: - """Construct a new object from the simplified form. - - This is assumed to support data data returned from the `to_simple` - method. - - Parameters - ---------- - names : `list` of `str` - The names of the dimensions. - universe : `DimensionUniverse` - The special graph of all known dimensions of which this graph will - be a subset. Can be `None` if `Registry` is provided. - registry : `lsst.daf.butler.Registry`, optional - Registry from which a universe can be extracted. Can be `None` - if universe is provided explicitly. - - Returns - ------- - graph : `DimensionGraph` - Newly-constructed object. - """ - if universe is None and registry is None: - raise ValueError("One of universe or registry is required to convert names to a DimensionGraph") - if universe is None and registry is not None: - universe = registry.dimensions - if universe is None: - # this is for mypy - raise ValueError("Unable to determine a usable universe") - - return cls(names=names.names, universe=universe) - - to_json = to_json_pydantic - from_json: ClassVar = classmethod(from_json_pydantic) - - def __iter__(self) -> Iterator[Dimension]: - """Iterate over all dimensions in the graph. - - (and true `Dimension` instances only). - """ - return iter(self.dimensions) - - def __len__(self) -> int: - """Return the number of dimensions in the graph. - - (and true `Dimension` instances only). - """ - return len(self._group) - - def __contains__(self, element: str | DimensionElement) -> bool: - """Return `True` if the given element or element name is in the graph. - - This test covers all `DimensionElement` instances in ``self.elements``, - not just true `Dimension` instances). - """ - return element in self.elements - - def __getitem__(self, name: str) -> DimensionElement: - """Return the element with the given name. - - This lookup covers all `DimensionElement` instances in - ``self.elements``, not just true `Dimension` instances). - """ - return self.elements[name] - - def get(self, name: str, default: Any = None) -> DimensionElement: - """Return the element with the given name. - - This lookup covers all `DimensionElement` instances in - ``self.elements``, not just true `Dimension` instances). - - Parameters - ---------- - name : `str` - Name of element to return. - default : `typing.Any` or `None` - Default value if named element is not present. - - Returns - ------- - element : `DimensionElement` or `None` - The element found, or the default. - """ - return self.elements.get(name, default) - - def __str__(self) -> str: - return str(self.as_group()) - - def __repr__(self) -> str: - return f"DimensionGraph({str(self)})" - - def as_group(self) -> DimensionGroup: - """Return a `DimensionGroup` that represents the same set of - dimensions. - - Returns - ------- - group : `DimensionGroup` - Group that represents the same set of dimensions. - """ - return self._group - - def isdisjoint(self, other: DimensionGroup | DimensionGraph) -> bool: - """Test whether the intersection of two graphs is empty. - - Parameters - ---------- - other : `DimensionGroup` or `DimensionGraph` - Other graph to compare with. - - Returns - ------- - is_disjoint : `bool` - Returns `True` if either operand is the empty. - """ - return self._group.isdisjoint(other.as_group()) - - def issubset(self, other: DimensionGroup | DimensionGraph) -> bool: - """Test whether all dimensions in ``self`` are also in ``other``. - - Parameters - ---------- - other : `DimensionGroup` or `DimensionGraph` - Other graph to compare with. - - Returns - ------- - is_subset : `bool` - Returns `True` if ``self`` is empty. - """ - return self._group <= other.as_group() - - def issuperset(self, other: DimensionGroup | DimensionGraph) -> bool: - """Test whether all dimensions in ``other`` are also in ``self``. - - Parameters - ---------- - other : `DimensionGroup` or `DimensionGraph` - Other graph to compare with. - - Returns - ------- - is_superset : `bool` - Returns `True` if ``other`` is empty. - """ - return self._group >= other.as_group() - - def __eq__(self, other: Any) -> bool: - """Test the arguments have exactly the same dimensions & elements.""" - if isinstance(other, DimensionGraph | DimensionGroup): - return self._group == other.as_group() - return False - - def __hash__(self) -> int: - return hash(self.as_group()) - - def __le__(self, other: DimensionGroup | DimensionGraph) -> bool: - """Test whether ``self`` is a subset of ``other``.""" - return self._group <= other.as_group() - - def __ge__(self, other: DimensionGroup | DimensionGraph) -> bool: - """Test whether ``self`` is a superset of ``other``.""" - return self._group >= other.as_group() - - def __lt__(self, other: DimensionGroup | DimensionGraph) -> bool: - """Test whether ``self`` is a strict subset of ``other``.""" - return self._group < other.as_group() - - def __gt__(self, other: DimensionGroup | DimensionGraph) -> bool: - """Test whether ``self`` is a strict superset of ``other``.""" - return self._group > other.as_group() - - def union(self, *others: DimensionGroup | DimensionGraph) -> DimensionGraph: - """Construct a new graph with all dimensions in any of the operands. - - Parameters - ---------- - *others : `DimensionGroup` or `DimensionGraph` - Other graphs to join with. - - Returns - ------- - union : `DimensionGraph` - The union of this graph wit hall the others. - - Notes - ----- - The elements of the returned graph may exceed the naive union of - their elements, as some `DimensionElement` instances are included - in graphs whenever multiple dimensions are present, and those - dependency dimensions could have been provided by different operands. - """ - names = set(self.names).union(*[other.names for other in others]) - return self.universe.conform(names)._as_graph() - - def intersection(self, *others: DimensionGroup | DimensionGraph) -> DimensionGraph: - """Construct a new graph with only dimensions in all of the operands. - - Parameters - ---------- - *others : `DimensionGroup` or `DimensionGraph` - Other graphs to use. - - Returns - ------- - inter : `DimensionGraph` - Intersection of all the graphs. - - Notes - ----- - See also `union`. - """ - names = set(self.names).intersection(*[other.names for other in others]) - return self.universe.conform(names)._as_graph() - - def __or__(self, other: DimensionGroup | DimensionGraph) -> DimensionGraph: - """Construct a new graph with all dimensions in any of the operands. - - See `union`. - """ - return self.union(other) - - def __and__(self, other: DimensionGroup | DimensionGraph) -> DimensionGraph: - """Construct a new graph with only dimensions in all of the operands. - - See `intersection`. - """ - return self.intersection(other) - - # TODO: Remove on DM-41326. - @property - @deprecated( - "DimensionGraph is deprecated in favor of DimensionGroup, which does not have this attribute; " - "use .lookup_order. DimensionGraph will be removed after v27.", - category=FutureWarning, - version="v27", - ) - def primaryKeyTraversalOrder(self) -> tuple[DimensionElement, ...]: - """A tuple of all elements in specific order. - - The order allows records to be found given their primary keys, starting - from only the primary keys of required dimensions (`tuple` [ - `DimensionRecord` ]). - - Unlike the table definition/topological order (which is what - DimensionUniverse.sorted gives you), when dimension A implies dimension - B, dimension A appears first. - """ - return tuple(self.universe[element_name] for element_name in self._group.lookup_order) - - @property - def spatial(self) -> NamedValueAbstractSet[TopologicalFamily]: - """Families represented by the spatial elements in this graph.""" - return self._group.spatial - - @property - def temporal(self) -> NamedValueAbstractSet[TopologicalFamily]: - """Families represented by the temporal elements in this graph.""" - return self._group.temporal - - # TODO: Remove on DM-41326. - @property - @deprecated( - "DimensionGraph is deprecated in favor of DimensionGroup, which does not have this attribute; " - "use .spatial or .temporal. DimensionGraph will be removed after v27.", - category=FutureWarning, - version="v27", - ) - def topology(self) -> Mapping[TopologicalSpace, NamedValueAbstractSet[TopologicalFamily]]: - """Families of elements in this graph that can participate in - topological relationships. - """ - return self._group._space_families - - _group: DimensionGroup diff --git a/python/lsst/daf/butler/dimensions/_group.py b/python/lsst/daf/butler/dimensions/_group.py index 52c68a856c..eee6d9cec0 100644 --- a/python/lsst/daf/butler/dimensions/_group.py +++ b/python/lsst/daf/butler/dimensions/_group.py @@ -35,6 +35,7 @@ from typing import TYPE_CHECKING, Any, TypeAlias import pydantic +from deprecated.sphinx import deprecated from lsst.utils.classes import cached_getter, immutable from pydantic_core import core_schema @@ -44,7 +45,6 @@ if TYPE_CHECKING: # Imports needed only for type annotations; may be circular. from ._elements import DimensionElement - from ._graph import DimensionGraph from ._universe import DimensionUniverse @@ -102,14 +102,20 @@ def as_tuple(self) -> tuple[str, ...]: """ return self._seq + # TODO: remove on DM-45185 @property + @deprecated( + "Deprecated in favor of direct iteration over the parent set. Will be removed after v28.", + version="v28", + category=FutureWarning, + ) def names(self) -> Set[str]: """An alias to ``self``. - This is a backwards-compatibility API that allows `DimensionGroup` - to mimic the `DimensionGraph` object it is intended to replace, by - permitting expressions like ``x.required.names`` when ``x`` can be - an object of either type. + This is a backwards-compatibility API that allows `DimensionGroup` to + mimic the old ``DimensionGraph`` object it replaced, by permitting + expressions like ``x.required.names`` when ``x`` can be an object of + either type. """ return self @@ -266,6 +272,12 @@ def __str__(self) -> str: def __repr__(self) -> str: return f"DimensionGroup({self.names})" + # TODO: remove on DM-45185 + @deprecated( + "Deprecated as no longer necessary (this method always returns 'self'). Will be removed after v28.", + version="v28", + category=FutureWarning, + ) def as_group(self) -> DimensionGroup: """Return ``self``. @@ -276,33 +288,12 @@ def as_group(self) -> DimensionGroup: Notes ----- - This is a backwards-compatibility API that allows both `DimensionGraph` - and `DimensionGroup` to be coerced to the latter. + This is a backwards-compatibility API that allowed both the old + ``DimensionGraph`` class and `DimensionGroup` to be coerced to the + latter. """ return self - @cached_getter - def _as_graph(self) -> DimensionGraph: - """Return a view of ``self`` as a `DimensionGraph`. - - Returns - ------- - graph : `DimensionGraph` - The deprecated form of `DimensionGroup`. - - Notes - ----- - This is provided as a convenience for methods and properties that must - return a `DimensionGraph` for backwards compatibility (until v27). It - is the only way of making a `DimensionGraph` that does not produce - a warning. - """ - from ._graph import DimensionGraph - - result = object.__new__(DimensionGraph) - result._group = self - return result - def isdisjoint(self, other: DimensionGroup) -> bool: """Test whether the intersection of two groups is empty. @@ -349,10 +340,7 @@ def issuperset(self, other: DimensionGroup) -> bool: return self.names >= other.names def __eq__(self, other: Any) -> bool: - from ._graph import DimensionGraph - - # TODO: Drop DimensionGraph support here on DM-41326. - if isinstance(other, DimensionGroup | DimensionGraph): + if isinstance(other, DimensionGroup): return self.names == other.names else: return False diff --git a/python/lsst/daf/butler/dimensions/_packer.py b/python/lsst/daf/butler/dimensions/_packer.py index 4c4cc32c62..e3ee596e37 100644 --- a/python/lsst/daf/butler/dimensions/_packer.py +++ b/python/lsst/daf/butler/dimensions/_packer.py @@ -33,7 +33,7 @@ from typing import TYPE_CHECKING, Any from ._coordinate import DataCoordinate, DataId -from ._graph import DimensionGraph, DimensionGroup +from ._group import DimensionGroup if TYPE_CHECKING: # Imports needed only for type annotations; may be circular. from ._universe import DimensionUniverse @@ -52,12 +52,11 @@ class DimensionPacker(metaclass=ABCMeta): (to these values) in all calls to `pack`, and are used in the results of calls to `unpack`. Subclasses may ignore particular dimensions, and are permitted to require that ``fixed.hasRecords()`` return `True`. - dimensions : `DimensionGroup` or `DimensionGraph` - The dimensions of data IDs packed by this instance. Only - `DimensionGroup` will be supported after v27. + dimensions : `DimensionGroup` + The dimensions of data IDs packed by this instance. """ - def __init__(self, fixed: DataCoordinate, dimensions: DimensionGroup | DimensionGraph): + def __init__(self, fixed: DataCoordinate, dimensions: DimensionGroup): self.fixed = fixed self._dimensions = self.fixed.universe.conform(dimensions) @@ -67,13 +66,11 @@ def universe(self) -> DimensionUniverse: return self.fixed.universe @property - def dimensions(self) -> DimensionGraph: + def dimensions(self) -> DimensionGroup: """The dimensions of data IDs packed by this instance - (`DimensionGraph`). - - After v27 this will be a `DimensionGroup`. + (`DimensionGroup`). """ - return self._dimensions._as_graph() + return self._dimensions @property @abstractmethod diff --git a/python/lsst/daf/butler/dimensions/_records.py b/python/lsst/daf/butler/dimensions/_records.py index 25b8fb183b..278fcb14b5 100644 --- a/python/lsst/daf/butler/dimensions/_records.py +++ b/python/lsst/daf/butler/dimensions/_records.py @@ -428,7 +428,7 @@ def from_simple( Newly-constructed object. """ if universe is None and registry is None: - raise ValueError("One of universe or registry is required to convert names to a DimensionGraph") + raise ValueError("One of universe or registry is required to convert names to a DimensionGroup") if universe is None and registry is not None: universe = registry.dimensions if universe is None: diff --git a/python/lsst/daf/butler/dimensions/_universe.py b/python/lsst/daf/butler/dimensions/_universe.py index 64fc1efb41..5b08a6b881 100644 --- a/python/lsst/daf/butler/dimensions/_universe.py +++ b/python/lsst/daf/butler/dimensions/_universe.py @@ -30,13 +30,11 @@ __all__ = ["DimensionUniverse"] import logging -import math import pickle from collections import defaultdict from collections.abc import Iterable, Mapping, Sequence -from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast, overload +from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, overload -from deprecated.sphinx import deprecated from lsst.utils.classes import cached_getter, immutable from .._config import Config @@ -47,7 +45,6 @@ from ._database import DatabaseDimensionElement from ._elements import Dimension, DimensionElement from ._governor import GovernorDimension -from ._graph import DimensionGraph from ._group import DimensionGroup from ._skypix import SkyPixDimension, SkyPixSystem @@ -424,91 +421,19 @@ def getDimensionIndex(self, name: str) -> int: """ return self._dimensionIndices[name] - # TODO: remove on DM-41326. - @deprecated( - "Deprecated in favor of DimensionUniverse.conform, and will be removed after v27.", - version="v27", - category=FutureWarning, - ) - def expandDimensionNameSet(self, names: set[str]) -> None: - """Expand a set of dimension names in-place. - - Includes recursive dependencies. - - This is an advanced interface for cases where constructing a - `DimensionGraph` (which also expands required dependencies) is - impossible or undesirable. - - Parameters - ---------- - names : `set` [ `str` ] - A true `set` of dimension names, to be expanded in-place. - """ - # Keep iterating until the set of names stops growing. This is not as - # efficient as it could be, but we work pretty hard cache - # DimensionGraph instances to keep actual construction rare, so that - # shouldn't matter. - oldSize = len(names) - while True: - # iterate over a temporary copy so we can modify the original - for name in tuple(names): - names.update(self._dimensions[name].required.names) - names.update(self._dimensions[name].implied.names) - if oldSize == len(names): - break - else: - oldSize = len(names) - - # TODO: remove on DM-41326. - @deprecated( - "DimensionUniverse.extract and DimensionGraph are deprecated in favor of DimensionUniverse.conform " - "and DimensionGroup, and will be removed after v27.", - version="v27", - category=FutureWarning, - ) - def extract(self, iterable: Iterable[Dimension | str]) -> DimensionGraph: - """Construct graph from iterable. - - Constructs a `DimensionGraph` from a possibly-heterogenous iterable - of `Dimension` instances and string names thereof. - - Constructing `DimensionGraph` directly from names or dimension - instances is slightly more efficient when it is known in advance that - the iterable is not heterogenous. - - Parameters - ---------- - iterable : iterable of `Dimension` or `str` - Dimensions that must be included in the returned graph (their - dependencies will be as well). - - Returns - ------- - graph : `DimensionGraph` - A `DimensionGraph` instance containing all given dimensions. - """ - return self.conform(iterable)._as_graph() - def conform( self, - dimensions: Iterable[str | Dimension] | str | DimensionElement | DimensionGroup | DimensionGraph, + dimensions: Iterable[str] | str | DimensionGroup, /, ) -> DimensionGroup: """Construct a dimension group from an iterable of dimension names. Parameters ---------- - dimensions : `~collections.abc.Iterable` [ `str` or `Dimension` ], \ - `str`, `DimensionElement`, `DimensionGroup`, or \ - `DimensionGraph` + dimensions : `~collections.abc.Iterable` [ `str` ], `str`, or \ + `DimensionGroup` Dimensions that must be included in the returned group; their - dependencies will be as well. Support for `Dimension`, - `DimensionElement` and `DimensionGraph` objects is deprecated and - will be removed after v27. Passing `DimensionGraph` objects will - not yield a deprecation warning to allow non-deprecated methods and - properties that return `DimensionGraph` objects to be passed - though, since these will be changed to return `DimensionGroup` in - the future. + dependencies will be as well. Returns ------- @@ -518,15 +443,10 @@ def conform( match dimensions: case DimensionGroup(): return dimensions - case DimensionGraph(): - return dimensions.as_group() - case DimensionElement() as d: - return d.minimal_group case str() as name: return self[name].minimal_group case iterable: - names: set[str] = {getattr(d, "name", cast(str, d)) for d in iterable} - return DimensionGroup(self, names) + return DimensionGroup(self, set(iterable)) @overload def sorted(self, elements: Iterable[Dimension], *, reverse: bool = False) -> Sequence[Dimension]: ... @@ -562,17 +482,6 @@ def sorted(self, elements: Iterable[Any], *, reverse: bool = False) -> list[Any] result.reverse() return result - def getEncodeLength(self) -> int: - """Return encoded size of graph. - - Returns the size (in bytes) of the encoded size of `DimensionGraph` - instances in this universe. - - See `DimensionGraph.encode` and `DimensionGraph.decode` for more - information. - """ - return math.ceil(len(self._dimensions) / 8) - def get_elements_populated_by(self, dimension: Dimension) -> NamedValueAbstractSet[DimensionElement]: """Return the set of `DimensionElement` objects whose `~DimensionElement.populated_by` attribute is the given dimension. @@ -591,12 +500,9 @@ def get_elements_populated_by(self, dimension: Dimension) -> NamedValueAbstractS return self._populates[dimension.name] @property - def empty(self) -> DimensionGraph: - """The `DimensionGraph` that contains no dimensions. - - After v27 this will be a `DimensionGroup`. - """ - return self._empty._as_graph() + def empty(self) -> DimensionGroup: + """The `DimensionGroup` that contains no dimensions.""" + return self._empty @classmethod def _unpickle(cls, version: int, namespace: str | None = None) -> DimensionUniverse: diff --git a/python/lsst/daf/butler/direct_butler/_direct_butler.py b/python/lsst/daf/butler/direct_butler/_direct_butler.py index a71865aed4..68ddec7932 100644 --- a/python/lsst/daf/butler/direct_butler/_direct_butler.py +++ b/python/lsst/daf/butler/direct_butler/_direct_butler.py @@ -25,8 +25,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -"""Butler top level classes. -""" +"""Butler top level classes.""" + from __future__ import annotations __all__ = ( @@ -460,28 +460,27 @@ def _rewrite_data_id( **kwargs : `dict` Any unused keyword arguments (would normally be empty dict). """ - # Do nothing if we have a standalone DataCoordinate. - if isinstance(dataId, DataCoordinate) and not kwargs: - return dataId, kwargs - # Process dimension records that are using record information # rather than ids newDataId: dict[str, DataIdValue] = {} byRecord: dict[str, dict[str, Any]] = defaultdict(dict) - # if all the dataId comes from keyword parameters we do not need - # to do anything here because they can't be of the form - # exposure.obs_id because a "." is not allowed in a keyword parameter. - if dataId: + if isinstance(dataId, DataCoordinate): + # Do nothing if we have a DataCoordinate and no kwargs. + if not kwargs: + return dataId, kwargs + # If we have a DataCoordinate with kwargs, we know the + # DataCoordinate only has values for real dimensions. + newDataId.update(dataId.mapping) + elif dataId: + # The data is mapping, which means it might have keys like + # "exposure.obs_id" (unlike kwargs, because a "." is not allowed in + # a keyword parameter). for k, v in dataId.items(): - # If we have a Dimension we do not need to do anything - # because it cannot be a compound key. if isinstance(k, str) and "." in k: # Someone is using a more human-readable dataId dimensionName, record = k.split(".", 1) byRecord[dimensionName][record] = v - elif isinstance(k, Dimension): - newDataId[k.name] = v else: newDataId[k] = v diff --git a/python/lsst/daf/butler/direct_query_driver/_driver.py b/python/lsst/daf/butler/direct_query_driver/_driver.py index d25aa6b610..9ad5c9bf4d 100644 --- a/python/lsst/daf/butler/direct_query_driver/_driver.py +++ b/python/lsst/daf/butler/direct_query_driver/_driver.py @@ -371,7 +371,7 @@ def count( # count deduplicated rows. builder = builder.nested() # Replace the columns of the query with just COUNT(*). - builder.columns = qt.ColumnSet(self._universe.empty.as_group()) + builder.columns = qt.ColumnSet(self._universe.empty) count_func: sqlalchemy.ColumnElement[int] = sqlalchemy.func.count() builder.joiner.special["_ROWCOUNT"] = count_func # Render and run the query. @@ -894,7 +894,7 @@ def _analyze_query_tree(self, tree: qt.QueryTree) -> tuple[QueryJoinsPlan, Query # without constraining their governor dimensions, since that's a # particularly easy mistake to make and it's almost never intentional. # We also allow the registry data ID values to provide governor values. - where_columns = qt.ColumnSet(self.universe.empty.as_group()) + where_columns = qt.ColumnSet(self.universe.empty) result.predicate.gather_required_columns(where_columns) for governor in where_columns.dimensions.governors: if governor not in result.constraint_data_id: @@ -1048,13 +1048,13 @@ def _resolve_dataset_search( if collection_record.type is CollectionType.CALIBRATION: result.is_calibration_search = True result.collection_records.append(collection_record) - if result.dimensions != self.get_dataset_type(dataset_type_name).dimensions.as_group(): + if result.dimensions != self.get_dataset_type(dataset_type_name).dimensions: # This is really for server-side defensiveness; it's hard to # imagine the query getting different dimensions for a dataset # type in two calls to the same query driver. raise InvalidQueryError( f"Incorrect dimensions {result.dimensions} for dataset {dataset_type_name} " - f"in query (vs. {self.get_dataset_type(dataset_type_name).dimensions.as_group()})." + f"in query (vs. {self.get_dataset_type(dataset_type_name).dimensions})." ) return result diff --git a/python/lsst/daf/butler/direct_query_driver/_sql_column_visitor.py b/python/lsst/daf/butler/direct_query_driver/_sql_column_visitor.py index 42d25613c6..987e5bc0bb 100644 --- a/python/lsst/daf/butler/direct_query_driver/_sql_column_visitor.py +++ b/python/lsst/daf/butler/direct_query_driver/_sql_column_visitor.py @@ -226,7 +226,7 @@ def visit_in_query_tree( flags: PredicateVisitFlags, ) -> sqlalchemy.ColumnElement[bool]: # Docstring inherited. - columns = qt.ColumnSet(self._driver.universe.empty.as_group()) + columns = qt.ColumnSet(self._driver.universe.empty) column.gather_required_columns(columns) _, builder = self._driver.build_query(query_tree, columns) if builder.postprocessing: @@ -235,7 +235,7 @@ def visit_in_query_tree( ) subquery_visitor = SqlColumnVisitor(builder.joiner, self._driver) builder.joiner.special["_MEMBER"] = subquery_visitor.expect_scalar(column) - builder.columns = qt.ColumnSet(self._driver.universe.empty.as_group()) + builder.columns = qt.ColumnSet(self._driver.universe.empty) subquery_select = builder.select() sql_member = self.expect_scalar(member) return sql_member.in_(subquery_select) diff --git a/python/lsst/daf/butler/queries/_dataset_query_results.py b/python/lsst/daf/butler/queries/_dataset_query_results.py index 5dc379a671..462f328ebc 100644 --- a/python/lsst/daf/butler/queries/_dataset_query_results.py +++ b/python/lsst/daf/butler/queries/_dataset_query_results.py @@ -94,7 +94,7 @@ def data_ids(self) -> DataCoordinateQueryResults: self._driver, tree=self._tree, spec=DataCoordinateResultSpec.model_construct( - dimensions=self.dataset_type.dimensions.as_group(), + dimensions=self.dataset_type.dimensions, include_dimension_records=self._spec.include_dimension_records, ), ) diff --git a/python/lsst/daf/butler/queries/_query.py b/python/lsst/daf/butler/queries/_query.py index 517c094a6c..dedb8ee36e 100644 --- a/python/lsst/daf/butler/queries/_query.py +++ b/python/lsst/daf/butler/queries/_query.py @@ -520,7 +520,7 @@ def _join_dataset_search_impl( # Handle DatasetType vs. str arg. if isinstance(dataset_type, DatasetType): dataset_type_name = dataset_type.name - dimensions = dataset_type.dimensions.as_group() + dimensions = dataset_type.dimensions storage_class_name = dataset_type.storageClass_name elif isinstance(dataset_type, str): dataset_type_name = dataset_type @@ -548,7 +548,7 @@ def _join_dataset_search_impl( # for consistency, or get dimensions and storage class if we don't have # them. resolved_dataset_type = self._driver.get_dataset_type(dataset_type_name) - resolved_dimensions = resolved_dataset_type.dimensions.as_group() + resolved_dimensions = resolved_dataset_type.dimensions if dimensions is not None and dimensions != resolved_dimensions: raise DatasetTypeError( f"Given dimensions {dimensions} for dataset type {dataset_type_name!r} do not match the " diff --git a/python/lsst/daf/butler/queries/tree/_query_tree.py b/python/lsst/daf/butler/queries/tree/_query_tree.py index 5d39e1aca4..61e4e65a45 100644 --- a/python/lsst/daf/butler/queries/tree/_query_tree.py +++ b/python/lsst/daf/butler/queries/tree/_query_tree.py @@ -73,7 +73,7 @@ def make_identity_query_tree(universe: DimensionUniverse) -> QueryTree: tree : `QueryTree` A tree with empty dimensions. """ - return QueryTree(dimensions=universe.empty.as_group()) + return QueryTree(dimensions=universe.empty) @final diff --git a/python/lsst/daf/butler/registry/_registry.py b/python/lsst/daf/butler/registry/_registry.py index ff6f5b355e..f934abc898 100644 --- a/python/lsst/daf/butler/registry/_registry.py +++ b/python/lsst/daf/butler/registry/_registry.py @@ -40,15 +40,12 @@ from .._dataset_association import DatasetAssociation from .._dataset_ref import DatasetId, DatasetIdGenEnum, DatasetRef from .._dataset_type import DatasetType -from .._named import NameLookupMapping from .._storage_class import StorageClassFactory from .._timespan import Timespan from ..dimensions import ( DataCoordinate, DataId, - Dimension, DimensionElement, - DimensionGraph, DimensionGroup, DimensionRecord, DimensionUniverse, @@ -881,9 +878,8 @@ def expandDataId( self, dataId: DataId | None = None, *, - dimensions: Iterable[str] | DimensionGroup | DimensionGraph | None = None, - graph: DimensionGraph | None = None, - records: NameLookupMapping[DimensionElement, DimensionRecord | None] | None = None, + dimensions: Iterable[str] | DimensionGroup | None = None, + records: Mapping[str, DimensionRecord | None] | None = None, withDefaults: bool = True, **kwargs: Any, ) -> DataCoordinate: @@ -893,16 +889,12 @@ def expandDataId( ---------- dataId : `DataCoordinate` or `dict`, optional Data ID to be expanded; augmented and overridden by ``kwargs``. - dimensions : `~collections.abc.Iterable` [ `str` ], \ - `DimensionGroup`, or `DimensionGraph`, optional + dimensions : `~collections.abc.Iterable` [ `str` ] or \ + `DimensionGroup` optional The dimensions to be identified by the new `DataCoordinate`. - If not provided, will be inferred from the keys of ``mapping`` and - ``**kwargs``, and ``universe`` must be provided unless ``mapping`` + If not provided, will be inferred from the keys of ``dataId`` and + ``**kwargs``, and ``universe`` must be provided unless ``dataId`` is already a `DataCoordinate`. - graph : `DimensionGraph`, optional - Like ``dimensions``, but as a ``DimensionGraph`` instance. Ignored - if ``dimensions`` is provided. Deprecated and will be removed - after v27. records : `~collections.abc.Mapping` [`str`, `DimensionRecord`], \ optional Dimension record data to use before querying the database for that @@ -1118,7 +1110,7 @@ def queryDatasets( datasetType: Any, *, collections: CollectionArgType | None = None, - dimensions: Iterable[Dimension | str] | None = None, + dimensions: Iterable[str] | None = None, dataId: DataId | None = None, where: str = "", findFirst: bool = False, @@ -1146,7 +1138,7 @@ def queryDatasets( collections, because this will still find all datasets). If not provided, ``self.default.collections`` is used. See :ref:`daf_butler_collection_expressions` for more information. - dimensions : `~collections.abc.Iterable` of `Dimension` or `str` + dimensions : `~collections.abc.Iterable` [ `str` ] Dimensions to include in the query (in addition to those used to identify the queried dataset type(s)), either to constrain the resulting datasets to those for which a matching dimension @@ -1229,8 +1221,7 @@ def queryDatasets( @abstractmethod def queryDataIds( self, - # TODO: Drop `Dimension` objects on DM-41326. - dimensions: DimensionGroup | Iterable[Dimension | str] | Dimension | str, + dimensions: DimensionGroup | Iterable[str] | str, *, dataId: DataId | None = None, datasets: Any = None, @@ -1245,12 +1236,10 @@ def queryDataIds( Parameters ---------- - dimensions : `DimensionGroup`, `Dimension`, or `str`, or \ - `~collections.abc.Iterable` [ `Dimension` or `str` ] - The dimensions of the data IDs to yield, as either `Dimension` - instances or `str`. Will be automatically expanded to a complete - `DimensionGroup`. Support for `Dimension` instances is deprecated - and will not be supported after v27. + dimensions : `DimensionGroup`, `str`, or \ + `~collections.abc.Iterable` [ `str` ] + The dimensions of the data IDs to yield. Will be automatically + expanded to a complete `DimensionGroup`. dataId : `dict` or `DataCoordinate`, optional A data ID whose key-value pairs are used as equality constraints in the query. diff --git a/python/lsst/daf/butler/registry/datasets/byDimensions/_manager.py b/python/lsst/daf/butler/registry/datasets/byDimensions/_manager.py index dafe862a0b..909761ea19 100644 --- a/python/lsst/daf/butler/registry/datasets/byDimensions/_manager.py +++ b/python/lsst/daf/butler/registry/datasets/byDimensions/_manager.py @@ -322,7 +322,7 @@ def register(self, datasetType: DatasetType) -> bool: ) record = self._fetch_dataset_type_record(datasetType.name) if record is None: - dimensionsKey = self._dimensions.save_dimension_group(datasetType.dimensions.as_group()) + dimensionsKey = self._dimensions.save_dimension_group(datasetType.dimensions) tagTableName = makeTagTableName(datasetType, dimensionsKey) self._db.ensureTableExists( tagTableName, diff --git a/python/lsst/daf/butler/registry/datasets/byDimensions/_storage.py b/python/lsst/daf/butler/registry/datasets/byDimensions/_storage.py index 0f7b2efba4..8d409d6921 100644 --- a/python/lsst/daf/butler/registry/datasets/byDimensions/_storage.py +++ b/python/lsst/daf/butler/registry/datasets/byDimensions/_storage.py @@ -197,9 +197,9 @@ def _buildCalibOverlapQuery( ) if data_ids is not None: relation = relation.join( - context.make_data_id_relation( - data_ids, self.datasetType.dimensions.required.names - ).transferred_to(context.sql_engine), + context.make_data_id_relation(data_ids, self.datasetType.dimensions.required).transferred_to( + context.sql_engine + ), ) return relation @@ -314,9 +314,7 @@ def decertify( calib_pkey_tag = DatasetColumnTag(self.datasetType.name, "calib_pkey") dataset_id_tag = DatasetColumnTag(self.datasetType.name, "dataset_id") timespan_tag = DatasetColumnTag(self.datasetType.name, "timespan") - data_id_tags = [ - (name, DimensionKeyColumnTag(name)) for name in self.datasetType.dimensions.required.names - ] + data_id_tags = [(name, DimensionKeyColumnTag(name)) for name in self.datasetType.dimensions.required] # Set up collections to populate with the rows we'll want to modify. # The insert rows will have the same values for collection and # dataset type. @@ -509,7 +507,7 @@ def _finish_single_relation( value=collection_col, ) # Add more column definitions, starting with the data ID. - for dimension_name in self.datasetType.dimensions.required.names: + for dimension_name in self.datasetType.dimensions.required: payload.columns_available[DimensionKeyColumnTag(dimension_name)] = payload.from_clause.columns[ dimension_name ] @@ -596,7 +594,7 @@ def make_query_joiner(self, collections: Sequence[CollectionRecord], fields: Set # using very important indexes. At present, we don't include those # redundant columns in the JOIN ON expression, however, because the # FOREIGN KEY (and its index) are defined only on dataset_id. - columns = qt.ColumnSet(self.datasetType.dimensions.as_group()) + columns = qt.ColumnSet(self.datasetType.dimensions) columns.drop_implied_dimension_keys() columns.dataset_fields[self.datasetType.name].update(fields) tags_builder: QueryBuilder | None = None @@ -692,7 +690,7 @@ def _finish_query_builder( collections, collection_col ) # Add more column definitions, starting with the data ID. - sql_projection.joiner.extract_dimensions(self.datasetType.dimensions.required.names) + sql_projection.joiner.extract_dimensions(self.datasetType.dimensions.required) # We can always get the dataset_id from the tags/calibs table, even if # could also get it from the 'static' dataset table. if "dataset_id" in fields: @@ -793,8 +791,8 @@ def getDataId(self, id: DatasetId) -> DataCoordinate: row = sql_result.mappings().fetchone() assert row is not None, "Should be guaranteed by caller and foreign key constraints." return DataCoordinate.from_required_values( - self.datasetType.dimensions.as_group(), - tuple(row[dimension] for dimension in self.datasetType.dimensions.required.names), + self.datasetType.dimensions, + tuple(row[dimension] for dimension in self.datasetType.dimensions.required), ) def refresh_collection_summaries(self) -> None: @@ -1062,11 +1060,8 @@ def _validateImport(self, tmp_tags: sqlalchemy.schema.Table, run: RunRecord) -> tags.columns.dataset_id, tags.columns.dataset_type_id.label("type_id"), tmp_tags.columns.dataset_type_id.label("new_type_id"), - *[tags.columns[dim] for dim in self.datasetType.dimensions.required.names], - *[ - tmp_tags.columns[dim].label(f"new_{dim}") - for dim in self.datasetType.dimensions.required.names - ], + *[tags.columns[dim] for dim in self.datasetType.dimensions.required], + *[tmp_tags.columns[dim].label(f"new_{dim}") for dim in self.datasetType.dimensions.required], ) .select_from(tags.join(tmp_tags, tags.columns.dataset_id == tmp_tags.columns.dataset_id)) .where( @@ -1074,7 +1069,7 @@ def _validateImport(self, tmp_tags: sqlalchemy.schema.Table, run: RunRecord) -> tags.columns.dataset_type_id != tmp_tags.columns.dataset_type_id, *[ tags.columns[dim] != tmp_tags.columns[dim] - for dim in self.datasetType.dimensions.required.names + for dim in self.datasetType.dimensions.required ], ) ) @@ -1091,7 +1086,7 @@ def _validateImport(self, tmp_tags: sqlalchemy.schema.Table, run: RunRecord) -> # Check that matching run+dataId have the same dataset ID. query = ( sqlalchemy.sql.select( - *[tags.columns[dim] for dim in self.datasetType.dimensions.required.names], + *[tags.columns[dim] for dim in self.datasetType.dimensions.required], tags.columns.dataset_id, tmp_tags.columns.dataset_id.label("new_dataset_id"), tags.columns[collFkName], @@ -1105,7 +1100,7 @@ def _validateImport(self, tmp_tags: sqlalchemy.schema.Table, run: RunRecord) -> tags.columns[collFkName] == tmp_tags.columns[collFkName], *[ tags.columns[dim] == tmp_tags.columns[dim] - for dim in self.datasetType.dimensions.required.names + for dim in self.datasetType.dimensions.required ], ), ) @@ -1116,7 +1111,7 @@ def _validateImport(self, tmp_tags: sqlalchemy.schema.Table, run: RunRecord) -> with self._db.query(query) as result: # only include the first one in the exception message if (row := result.first()) is not None: - data_id = {dim: getattr(row, dim) for dim in self.datasetType.dimensions.required.names} + data_id = {dim: getattr(row, dim) for dim in self.datasetType.dimensions.required} existing_collection = self._collections[getattr(row, collFkName)].name new_collection = self._collections[getattr(row, f"new_{collFkName}")].name raise ConflictingDefinitionError( diff --git a/python/lsst/daf/butler/registry/datasets/byDimensions/tables.py b/python/lsst/daf/butler/registry/datasets/byDimensions/tables.py index 706fc78d54..5729bd2b80 100644 --- a/python/lsst/daf/butler/registry/datasets/byDimensions/tables.py +++ b/python/lsst/daf/butler/registry/datasets/byDimensions/tables.py @@ -353,7 +353,7 @@ def makeTagTableSpec( target=(collectionFieldSpec.name, "dataset_type_id"), ) ) - for dimension_name in datasetType.dimensions.required.names: + for dimension_name in datasetType.dimensions.required: dimension = datasetType.dimensions.universe.dimensions[dimension_name] fieldSpec = addDimensionForeignKey( tableSpec, dimension=dimension, nullable=False, primaryKey=False, constraint=constraints @@ -439,7 +439,7 @@ def makeCalibTableSpec( ) ) # Add dimension fields (part of the temporal lookup index.constraint). - for dimension_name in datasetType.dimensions.required.names: + for dimension_name in datasetType.dimensions.required: dimension = datasetType.dimensions.universe.dimensions[dimension_name] fieldSpec = addDimensionForeignKey(tableSpec, dimension=dimension, nullable=False, primaryKey=False) index.append(fieldSpec.name) diff --git a/python/lsst/daf/butler/registry/dimensions/static.py b/python/lsst/daf/butler/registry/dimensions/static.py index ae449dd9dd..6e6178fa57 100644 --- a/python/lsst/daf/butler/registry/dimensions/static.py +++ b/python/lsst/daf/butler/registry/dimensions/static.py @@ -176,7 +176,7 @@ def initialize( # dimensions. We've never used these and no longer plan to, but we # have to keep creating them to keep schema versioning consistent. cls._make_legacy_overlap_tables(context, spatial) - # Create tables that store DimensionGraph definitions. + # Create tables that store DimensionGroup definitions. dimension_group_storage = _DimensionGroupStorage.initialize(db, context, universe=universe) return cls( db=db, @@ -529,7 +529,7 @@ def _make_common_skypix_join_relation( payload.columns_available[DimensionKeyColumnTag(self.universe.commonSkyPix.name)] = ( payload.from_clause.columns.skypix_index ) - for dimension_name in element.graph.required.names: + for dimension_name in element.minimal_group.required: payload.columns_available[DimensionKeyColumnTag(dimension_name)] = payload.from_clause.columns[ dimension_name ] @@ -596,7 +596,7 @@ def _make_skypix_overlap_tables( "skypix_system", "skypix_level", "skypix_index", - *element.graph.required.names, + *element.minimal_group.required, ), }, foreignKeys=[ @@ -843,8 +843,8 @@ def __init__( self._idTable = idTable self._definitionTable = definitionTable self._universe = universe - self._keysByGroup: dict[DimensionGroup, int] = {universe.empty.as_group(): 0} - self._groupsByKey: dict[int, DimensionGroup] = {0: universe.empty.as_group()} + self._keysByGroup: dict[DimensionGroup, int] = {universe.empty: 0} + self._groupsByKey: dict[int, DimensionGroup] = {0: universe.empty} def clone(self, db: Database) -> _DimensionGroupStorage: """Make an independent copy of this manager instance bound to a new @@ -926,7 +926,7 @@ def initialize( return cls(db, idTable, definitionTable, universe=universe) def refresh(self) -> None: - """Refresh the in-memory cache of saved DimensionGraph definitions. + """Refresh the in-memory cache of saved DimensionGroup definitions. This should be done automatically whenever needed, but it can also be called explicitly. @@ -937,8 +937,8 @@ def refresh(self) -> None: for row in sql_rows: key = row[self._definitionTable.columns.dimension_graph_id] dimensionNamesByKey[key].add(row[self._definitionTable.columns.dimension_name]) - keysByGraph: dict[DimensionGroup, int] = {self._universe.empty.as_group(): 0} - graphsByKey: dict[int, DimensionGroup] = {0: self._universe.empty.as_group()} + keysByGraph: dict[DimensionGroup, int] = {self._universe.empty: 0} + graphsByKey: dict[int, DimensionGroup] = {0: self._universe.empty} for key, dimensionNames in dimensionNamesByKey.items(): graph = DimensionGroup(self._universe, names=dimensionNames) keysByGraph[graph] = key @@ -947,7 +947,7 @@ def refresh(self) -> None: self._keysByGroup = keysByGraph def save(self, group: DimensionGroup) -> int: - """Save a `DimensionGraph` definition to the database, allowing it to + """Save a `DimensionGroup` definition to the database, allowing it to be retrieved later via the returned key. Parameters @@ -958,7 +958,7 @@ def save(self, group: DimensionGroup) -> int: Returns ------- key : `int` - Integer used as the unique key for this `DimensionGraph` in the + Integer used as the unique key for this `DimensionGroup` in the database. """ key = self._keysByGroup.get(group) @@ -983,18 +983,18 @@ def save(self, group: DimensionGroup) -> int: return key def load(self, key: int) -> DimensionGroup: - """Retrieve a `DimensionGraph` that was previously saved in the + """Retrieve a `DimensionGroup` that was previously saved in the database. Parameters ---------- key : `int` - Integer used as the unique key for this `DimensionGraph` in the + Integer used as the unique key for this `DimensionGroup` in the database. Returns ------- - graph : `DimensionGraph` + graph : `DimensionGroup` Retrieved graph. """ graph = self._groupsByKey.get(key) diff --git a/python/lsst/daf/butler/registry/obscore/_records.py b/python/lsst/daf/butler/registry/obscore/_records.py index 3b8b1b3721..c89a1074b6 100644 --- a/python/lsst/daf/butler/registry/obscore/_records.py +++ b/python/lsst/daf/butler/registry/obscore/_records.py @@ -221,7 +221,7 @@ def __call__(self, ref: DatasetRef, context: SqlQueryContext) -> Record | None: if em_range: record["em_min"], record["em_max"] = em_range else: - _LOG.warning("could not find spectral range for dataId=%s", dataId.full) + _LOG.warning("could not find spectral range for dataId=%s", dataId) record["em_filter_name"] = dataId["band"] # Dictionary to use for substitutions when formatting various diff --git a/python/lsst/daf/butler/registry/queries/_builder.py b/python/lsst/daf/butler/registry/queries/_builder.py index 4264111b13..ff6f8c7d72 100644 --- a/python/lsst/daf/butler/registry/queries/_builder.py +++ b/python/lsst/daf/butler/registry/queries/_builder.py @@ -227,8 +227,8 @@ def finish(self, joinMissing: bool = True) -> Query: for family1, family2 in itertools.combinations(self.summary.dimensions.spatial, 2): spatial_joins.append( ( - family1.choose(self.summary.dimensions.elements.names, self.summary.universe).name, - family2.choose(self.summary.dimensions.elements.names, self.summary.universe).name, + family1.choose(self.summary.dimensions.elements, self.summary.universe).name, + family2.choose(self.summary.dimensions.elements, self.summary.universe).name, ) ) self.relation = self._backend.make_dimension_relation( diff --git a/python/lsst/daf/butler/registry/queries/_query.py b/python/lsst/daf/butler/registry/queries/_query.py index 5d05bb73ab..6bd5474472 100644 --- a/python/lsst/daf/butler/registry/queries/_query.py +++ b/python/lsst/daf/butler/registry/queries/_query.py @@ -706,7 +706,7 @@ def find_datasets( # If the dataset type has dimensions not in the current query, or we # need a temporal join for a calibration collection, either restore # those columns or join them in. - full_dimensions = dataset_type.dimensions.as_group().union(self._dimensions) + full_dimensions = dataset_type.dimensions.union(self._dimensions) relation = self._relation record_caches = self._record_caches base_columns_required: set[ColumnTag] = { @@ -746,12 +746,8 @@ def find_datasets( # present in each family (e.g. patch beats tract). spatial_joins.append( ( - lhs_spatial_family.choose( - full_dimensions.elements.names, self.dimensions.universe - ).name, - rhs_spatial_family.choose( - full_dimensions.elements.names, self.dimensions.universe - ).name, + lhs_spatial_family.choose(full_dimensions.elements, self.dimensions.universe).name, + rhs_spatial_family.choose(full_dimensions.elements, self.dimensions.universe).name, ) ) # Set up any temporal join between the query dimensions and CALIBRATION @@ -759,7 +755,7 @@ def find_datasets( temporal_join_on: set[ColumnTag] = set() if any(r.type is CollectionType.CALIBRATION for r in collection_records): for family in self._dimensions.temporal: - endpoint = family.choose(self._dimensions.elements.names, self.dimensions.universe) + endpoint = family.choose(self._dimensions.elements, self.dimensions.universe) temporal_join_on.add(DimensionRecordColumnTag(endpoint.name, "timespan")) base_columns_required.update(temporal_join_on) # Note which of the many kinds of potentially-missing columns we have diff --git a/python/lsst/daf/butler/registry/queries/_query_backend.py b/python/lsst/daf/butler/registry/queries/_query_backend.py index 515d5084e5..bac06c3c3c 100644 --- a/python/lsst/daf/butler/registry/queries/_query_backend.py +++ b/python/lsst/daf/butler/registry/queries/_query_backend.py @@ -579,9 +579,7 @@ def make_doomed_dataset_relation( relation : `lsst.daf.relation.Relation` Relation with the requested columns and no rows. """ - column_tags: set[ColumnTag] = set( - DimensionKeyColumnTag.generate(dataset_type.dimensions.required.names) - ) + column_tags: set[ColumnTag] = set(DimensionKeyColumnTag.generate(dataset_type.dimensions.required)) column_tags.update(DatasetColumnTag.generate(dataset_type.name, columns)) return context.preferred_engine.make_doomed_relation(columns=column_tags, messages=list(messages)) diff --git a/python/lsst/daf/butler/registry/queries/_readers.py b/python/lsst/daf/butler/registry/queries/_readers.py index 4b12f5be10..ccb7262497 100644 --- a/python/lsst/daf/butler/registry/queries/_readers.py +++ b/python/lsst/daf/butler/registry/queries/_readers.py @@ -142,7 +142,7 @@ class _BasicDataCoordinateReader(DataCoordinateReader): def __init__(self, dimensions: DimensionGroup): self._dimensions = dimensions - self._tags = tuple(DimensionKeyColumnTag(name) for name in self._dimensions.required.names) + self._tags = tuple(DimensionKeyColumnTag(name) for name in self._dimensions.required) __slots__ = ("_dimensions", "_tags") @@ -169,9 +169,7 @@ class _FullDataCoordinateReader(DataCoordinateReader): def __init__(self, dimensions: DimensionGroup): self._dimensions = dimensions - self._tags = tuple( - DimensionKeyColumnTag(name) for name in self._dimensions.as_group().data_coordinate_keys - ) + self._tags = tuple(DimensionKeyColumnTag(name) for name in self._dimensions.data_coordinate_keys) __slots__ = ("_dimensions", "_tags") @@ -265,7 +263,7 @@ def __init__( record_caches: Mapping[str, DimensionRecordSet] | None = None, ): self._data_coordinate_reader = DataCoordinateReader.make( - dataset_type.dimensions.as_group(), full=full, records=records, record_caches=record_caches + dataset_type.dimensions, full=full, records=records, record_caches=record_caches ) self._dataset_type = dataset_type self._translate_collection = translate_collection diff --git a/python/lsst/daf/butler/registry/queries/_results.py b/python/lsst/daf/butler/registry/queries/_results.py index b66f8428d2..4627d246e9 100644 --- a/python/lsst/daf/butler/registry/queries/_results.py +++ b/python/lsst/daf/butler/registry/queries/_results.py @@ -44,8 +44,6 @@ from contextlib import AbstractContextManager, ExitStack, contextmanager from typing import Any, Self -from deprecated.sphinx import deprecated - from ..._dataset_ref import DatasetRef from ..._dataset_type import DatasetType from ..._exceptions_legacy import DatasetTypeError @@ -53,7 +51,6 @@ DataCoordinate, DataCoordinateIterable, DimensionElement, - DimensionGraph, DimensionGroup, DimensionRecord, ) @@ -262,7 +259,7 @@ def expanded(self) -> DataCoordinateQueryResults: @abstractmethod def subset( self, - dimensions: DimensionGroup | DimensionGraph | Iterable[str] | None = None, + dimensions: DimensionGroup | Iterable[str] | None = None, *, unique: bool = False, ) -> DataCoordinateQueryResults: @@ -274,7 +271,7 @@ def subset( Parameters ---------- - dimensions : `DimensionGroup`, `DimensionGraph`, or \ + dimensions : `DimensionGroup` or \ `~collections.abc.Iterable` [ `str`], optional Dimensions to include in the new results object. If `None`, ``self.dimensions`` is used. @@ -370,7 +367,7 @@ def findRelatedDatasets( collections: Any, *, findFirst: bool = True, - dimensions: DimensionGroup | DimensionGraph | Iterable[str] | None = None, + dimensions: DimensionGroup | Iterable[str] | None = None, ) -> Iterable[tuple[DataCoordinate, DatasetRef]]: """Find datasets using the data IDs identified by this query, and return them along with the original data IDs. @@ -398,7 +395,7 @@ def findRelatedDatasets( expressions and may not be ``...``. Note that this is not the same as yielding one `DatasetRef` for each yielded data ID if ``dimensions`` is not `None`. - dimensions : `DimensionGroup`, `DimensionGraph`, or \ + dimensions : `DimensionGroup` or \ `~collections.abc.Iterable` [ `str` ], optional The dimensions of the data IDs returned. Must be a subset of ``self.dimensions``. @@ -443,17 +440,7 @@ def __iter__(self) -> Iterator[DataCoordinate]: return self._query.iter_data_ids() def __repr__(self) -> str: - return f"" - - @property - @deprecated( - "Deprecated in favor of .dimensions. Will be removed after v27.", - version="v27", - category=FutureWarning, - ) - def graph(self) -> DimensionGraph: - # Docstring inherited from DataCoordinateIterable. - return self._query.dimensions._as_graph() + return f"" @property def dimensions(self) -> DimensionGroup: @@ -478,7 +465,7 @@ def expanded(self) -> DataCoordinateQueryResults: def subset( self, - dimensions: DimensionGroup | DimensionGraph | Iterable[str] | None = None, + dimensions: DimensionGroup | Iterable[str] | None = None, *, unique: bool = False, ) -> DataCoordinateQueryResults: @@ -518,7 +505,7 @@ def findRelatedDatasets( collections: Any, *, findFirst: bool = True, - dimensions: DimensionGroup | DimensionGraph | Iterable[str] | None = None, + dimensions: DimensionGroup | Iterable[str] | None = None, ) -> Iterable[tuple[DataCoordinate, DatasetRef]]: if dimensions is None: dimensions = self.dimensions diff --git a/python/lsst/daf/butler/registry/queries/_sql_query_backend.py b/python/lsst/daf/butler/registry/queries/_sql_query_backend.py index 0f0f51c930..07d0c35432 100644 --- a/python/lsst/daf/butler/registry/queries/_sql_query_backend.py +++ b/python/lsst/daf/butler/registry/queries/_sql_query_backend.py @@ -271,7 +271,7 @@ def make_dimension_relation( "out if they have already been added or will be added later." ) for element_name in missing_columns.dimension_records: - if element_name not in dimensions.elements.names: + if element_name not in dimensions.elements: raise ColumnError( f"Cannot join dimension element {element_name} whose dimensions are not a " f"subset of {dimensions}." diff --git a/python/lsst/daf/butler/registry/queries/_structs.py b/python/lsst/daf/butler/registry/queries/_structs.py index d470798337..fca11e3700 100644 --- a/python/lsst/daf/butler/registry/queries/_structs.py +++ b/python/lsst/daf/butler/registry/queries/_structs.py @@ -500,7 +500,7 @@ def _compute_columns_required( missing_common_skypix = False if region is not None: for family in dimensions.spatial: - element = family.choose(dimensions.elements.names, self.universe) + element = family.choose(dimensions.elements, self.universe) tags.add(DimensionRecordColumnTag(element.name, "region")) if ( not isinstance(element, SkyPixDimension) diff --git a/python/lsst/daf/butler/registry/queries/expressions/check.py b/python/lsst/daf/butler/registry/queries/expressions/check.py index 7e5dda1fba..179d71750f 100644 --- a/python/lsst/daf/butler/registry/queries/expressions/check.py +++ b/python/lsst/daf/butler/registry/queries/expressions/check.py @@ -435,7 +435,7 @@ def visitInner(self, branches: Sequence[TreeSummary], form: NormalForm) -> Inner if missing <= self.defaults.dimensions.required: summary.defaultsNeeded.update(missing) elif not self._allow_orphans: - still_missing = missing - self.defaults.names + still_missing = missing - self.defaults.dimensions.names raise UserExpressionError( f"No value(s) for governor dimensions {still_missing} in expression " "that references dependent dimensions. 'Governor' dimensions must always be specified " diff --git a/python/lsst/daf/butler/registry/sql_registry.py b/python/lsst/daf/butler/registry/sql_registry.py index e20fbf41ad..08ff18519c 100644 --- a/python/lsst/daf/butler/registry/sql_registry.py +++ b/python/lsst/daf/butler/registry/sql_registry.py @@ -54,16 +54,13 @@ DimensionNameError, InconsistentDataIdError, ) -from .._named import NamedKeyMapping, NameLookupMapping from .._storage_class import StorageClassFactory from .._timespan import Timespan from ..dimensions import ( DataCoordinate, DataId, - Dimension, DimensionConfig, DimensionElement, - DimensionGraph, DimensionGroup, DimensionRecord, DimensionUniverse, @@ -1488,9 +1485,8 @@ def expandDataId( self, dataId: DataId | None = None, *, - dimensions: Iterable[str] | DimensionGroup | DimensionGraph | None = None, - graph: DimensionGraph | None = None, - records: NameLookupMapping[DimensionElement, DimensionRecord | None] | None = None, + dimensions: Iterable[str] | DimensionGroup | None = None, + records: Mapping[str, DimensionRecord | None] | None = None, withDefaults: bool = True, **kwargs: Any, ) -> DataCoordinate: @@ -1501,15 +1497,11 @@ def expandDataId( dataId : `DataCoordinate` or `dict`, optional Data ID to be expanded; augmented and overridden by ``kwargs``. dimensions : `~collections.abc.Iterable` [ `str` ], \ - `DimensionGroup`, or `DimensionGraph`, optional + `DimensionGroup`, optional The dimensions to be identified by the new `DataCoordinate`. - If not provided, will be inferred from the keys of ``mapping`` and - ``**kwargs``, and ``universe`` must be provided unless ``mapping`` + If not provided, will be inferred from the keys of ``dataId`` and + ``**kwargs``, and ``universe`` must be provided unless ``dataId`` is already a `DataCoordinate`. - graph : `DimensionGraph`, optional - Like ``dimensions``, but as a ``DimensionGraph`` instance. Ignored - if ``dimensions`` is provided. Deprecated and will be removed - after v27. records : `~collections.abc.Mapping` [`str`, `DimensionRecord`], \ optional Dimension record data to use before querying the database for that @@ -1551,7 +1543,6 @@ def expandDataId( defaults = self.defaults.dataId standardized = DataCoordinate.standardize( dataId, - graph=graph, dimensions=dimensions, universe=self.dimensions, defaults=defaults, @@ -1561,8 +1552,6 @@ def expandDataId( return standardized if records is None: records = {} - elif isinstance(records, NamedKeyMapping): - records = records.byName() else: records = dict(records) if isinstance(dataId, DataCoordinate) and dataId.hasRecords(): @@ -1982,7 +1971,7 @@ def queryDatasets( datasetType: Any, *, collections: CollectionArgType | None = None, - dimensions: Iterable[Dimension | str] | None = None, + dimensions: Iterable[str] | None = None, dataId: DataId | None = None, where: str = "", findFirst: bool = False, @@ -2010,7 +1999,7 @@ def queryDatasets( collections, because this will still find all datasets). If not provided, ``self.default.collections`` is used. See :ref:`daf_butler_collection_expressions` for more information. - dimensions : `~collections.abc.Iterable` of `Dimension` or `str` + dimensions : `~collections.abc.Iterable` of `str` Dimensions to include in the query (in addition to those used to identify the queried dataset type(s)), either to constrain the resulting datasets to those for which a matching dimension @@ -2156,8 +2145,7 @@ def queryDatasets( def queryDataIds( self, - # TODO: Drop Dimension support on DM-41326. - dimensions: DimensionGroup | Iterable[Dimension | str] | Dimension | str, + dimensions: DimensionGroup | Iterable[str] | str, *, dataId: DataId | None = None, datasets: Any = None, @@ -2172,12 +2160,11 @@ def queryDataIds( Parameters ---------- - dimensions : `DimensionGroup`, `Dimension`, or `str`, or \ - `~collections.abc.Iterable` [ `Dimension` or `str` ] + dimensions : `DimensionGroup`, `str`, or \ + `~collections.abc.Iterable` [ `str` ] The dimensions of the data IDs to yield, as either `Dimension` instances or `str`. Will be automatically expanded to a complete - `DimensionGroup`. Support for `Dimension` instances is deprecated - and will not be supported after v27. + `DimensionGroup`. dataId : `dict` or `DataCoordinate`, optional A data ID whose key-value pairs are used as equality constraints in the query. diff --git a/python/lsst/daf/butler/registry/tests/_registry.py b/python/lsst/daf/butler/registry/tests/_registry.py index 8ef96d5746..25c9bdee53 100644 --- a/python/lsst/daf/butler/registry/tests/_registry.py +++ b/python/lsst/daf/butler/registry/tests/_registry.py @@ -1211,9 +1211,7 @@ def testInstrumentDimensions(self): (ref,) = registry.insertDatasets(rawType, dataIds=[dataId], run=run2) registry.associate(tagged2, [ref]) - dimensions = registry.dimensions.conform( - rawType.dimensions.required.names | calexpType.dimensions.required.names - ) + dimensions = registry.dimensions.conform(rawType.dimensions.required | calexpType.dimensions.required) # Test that single dim string works as well as list of str rows = registry.queryDataIds("visit", datasets=rawType, collections=run1).expanded().toSet() rowsI = registry.queryDataIds(["visit"], datasets=rawType, collections=run1).expanded().toSet() @@ -1337,9 +1335,7 @@ def testSkyMapDimensions(self): registry.registerDatasetType(measType) dimensions = registry.dimensions.conform( - calexpType.dimensions.required.names - | mergeType.dimensions.required.names - | measType.dimensions.required.names + calexpType.dimensions.required | mergeType.dimensions.required | measType.dimensions.required ) # add pre-existing datasets diff --git a/python/lsst/daf/butler/remote_butler/_registry.py b/python/lsst/daf/butler/remote_butler/_registry.py index 1c0db3d5e2..168665992c 100644 --- a/python/lsst/daf/butler/remote_butler/_registry.py +++ b/python/lsst/daf/butler/remote_butler/_registry.py @@ -36,15 +36,12 @@ from .._dataset_association import DatasetAssociation from .._dataset_ref import DatasetId, DatasetIdGenEnum, DatasetRef from .._dataset_type import DatasetType -from .._named import NameLookupMapping from .._storage_class import StorageClassFactory from .._timespan import Timespan from ..dimensions import ( DataCoordinate, DataId, - Dimension, DimensionElement, - DimensionGraph, DimensionGroup, DimensionRecord, DimensionUniverse, @@ -281,15 +278,13 @@ def expandDataId( self, dataId: DataId | None = None, *, - dimensions: Iterable[str] | DimensionGroup | DimensionGraph | None = None, - graph: DimensionGraph | None = None, - records: NameLookupMapping[DimensionElement, DimensionRecord | None] | None = None, + dimensions: Iterable[str] | DimensionGroup | None = None, + records: Mapping[str, DimensionRecord | None] | None = None, withDefaults: bool = True, **kwargs: Any, ) -> DataCoordinate: standardized = DataCoordinate.standardize( dataId, - graph=graph, dimensions=dimensions, universe=self.dimensions, defaults=self.defaults.dataId if withDefaults else None, @@ -373,7 +368,7 @@ def queryDatasets( datasetType: Any, *, collections: CollectionArgType | None = None, - dimensions: Iterable[Dimension | str] | None = None, + dimensions: Iterable[str] | None = None, dataId: DataId | None = None, where: str = "", findFirst: bool = False, @@ -442,8 +437,7 @@ def queryDatasets( def queryDataIds( self, - # TODO: Drop `Dimension` objects on DM-41326. - dimensions: DimensionGroup | Iterable[Dimension | str] | Dimension | str, + dimensions: DimensionGroup | Iterable[str] | str, *, dataId: DataId | None = None, datasets: Any = None, diff --git a/python/lsst/daf/butler/remote_butler/registry/_query_data_coordinates.py b/python/lsst/daf/butler/remote_butler/registry/_query_data_coordinates.py index 1a9dc9aab6..7daae2a68f 100644 --- a/python/lsst/daf/butler/remote_butler/registry/_query_data_coordinates.py +++ b/python/lsst/daf/butler/remote_butler/registry/_query_data_coordinates.py @@ -33,7 +33,7 @@ from ..._dataset_ref import DatasetRef from ..._dataset_type import DatasetType -from ...dimensions import DataCoordinate, DimensionGraph, DimensionGroup +from ...dimensions import DataCoordinate, DimensionGroup from ...queries import DataCoordinateQueryResults, Query from ...registry.queries import DataCoordinateQueryResults as LegacyDataCoordinateQueryResults from ...registry.queries import ParentDatasetQueryResults @@ -106,7 +106,7 @@ def expanded(self) -> LegacyDataCoordinateQueryResults: def subset( self, - dimensions: DimensionGroup | DimensionGraph | Iterable[str] | None = None, + dimensions: DimensionGroup | Iterable[str] | None = None, *, unique: bool = False, ) -> LegacyDataCoordinateQueryResults: @@ -139,6 +139,6 @@ def findRelatedDatasets( collections: Any, *, findFirst: bool = True, - dimensions: DimensionGroup | DimensionGraph | Iterable[str] | None = None, + dimensions: DimensionGroup | Iterable[str] | None = None, ) -> Iterable[tuple[DataCoordinate, DatasetRef]]: raise NotImplementedError() diff --git a/python/lsst/daf/butler/script/queryDataIds.py b/python/lsst/daf/butler/script/queryDataIds.py index e312d2c533..3bcd597c94 100644 --- a/python/lsst/daf/butler/script/queryDataIds.py +++ b/python/lsst/daf/butler/script/queryDataIds.py @@ -142,13 +142,11 @@ def queryDataIds( for dataset_type in dataset_types: if dataset_type_dimensions is None: # Seed with dimensions of first dataset type. - dataset_type_dimensions = dataset_type.dimensions.as_group() + dataset_type_dimensions = dataset_type.dimensions else: # Only retain dimensions that are in the current # set AND the set from this dataset type. - dataset_type_dimensions = dataset_type_dimensions.intersection( - dataset_type.dimensions.as_group() - ) + dataset_type_dimensions = dataset_type_dimensions.intersection(dataset_type.dimensions) _LOG.debug("Dimensions now %s from %s", set(dataset_type_dimensions.names), dataset_type.name) # Break out of the loop early. No additional dimensions diff --git a/python/lsst/daf/butler/tests/_datasetsHelper.py b/python/lsst/daf/butler/tests/_datasetsHelper.py index 2f73c57b36..fef710a7c3 100644 --- a/python/lsst/daf/butler/tests/_datasetsHelper.py +++ b/python/lsst/daf/butler/tests/_datasetsHelper.py @@ -44,7 +44,7 @@ from lsst.daf.butler.formatters.yaml import YamlFormatter if TYPE_CHECKING: - from lsst.daf.butler import Config, DatasetId, Dimension, DimensionGraph + from lsst.daf.butler import Config, DatasetId class DatasetTestHelper: @@ -53,7 +53,7 @@ class DatasetTestHelper: def makeDatasetRef( self, datasetTypeName: str, - dimensions: DimensionGroup | DimensionGraph | Iterable[str | Dimension], + dimensions: DimensionGroup | Iterable[str], storageClass: StorageClass | str, dataId: DataCoordinate | Mapping[str, Any], *, @@ -67,8 +67,7 @@ def makeDatasetRef( ---------- datasetTypeName : `str` The name of the dataset type. - dimensions : `DimensionGroup` or `~collections.abc.Iterable` of `str` \ - or `Dimension` + dimensions : `DimensionGroup` or `~collections.abc.Iterable` of `str` The dimensions to use for this dataset type. storageClass : `StorageClass` or `str` The relevant storage class. @@ -100,7 +99,7 @@ def makeDatasetRef( def _makeDatasetRef( self, datasetTypeName: str, - dimensions: DimensionGroup | DimensionGraph | Iterable[str | Dimension], + dimensions: DimensionGroup | Iterable[str], storageClass: StorageClass | str, dataId: DataCoordinate | Mapping, *, diff --git a/python/lsst/daf/butler/tests/hybrid_butler_registry.py b/python/lsst/daf/butler/tests/hybrid_butler_registry.py index cbd4b1f5c0..450e831952 100644 --- a/python/lsst/daf/butler/tests/hybrid_butler_registry.py +++ b/python/lsst/daf/butler/tests/hybrid_butler_registry.py @@ -34,15 +34,12 @@ from .._dataset_association import DatasetAssociation from .._dataset_ref import DatasetId, DatasetIdGenEnum, DatasetRef from .._dataset_type import DatasetType -from .._named import NameLookupMapping from .._storage_class import StorageClassFactory from .._timespan import Timespan from ..dimensions import ( DataCoordinate, DataId, - Dimension, DimensionElement, - DimensionGraph, DimensionGroup, DimensionRecord, DimensionUniverse, @@ -225,14 +222,13 @@ def expandDataId( self, dataId: DataId | None = None, *, - dimensions: Iterable[str] | DimensionGroup | DimensionGraph | None = None, - graph: DimensionGraph | None = None, - records: NameLookupMapping[DimensionElement, DimensionRecord | None] | None = None, + dimensions: Iterable[str] | DimensionGroup | None = None, + records: Mapping[str, DimensionRecord | None] | None = None, withDefaults: bool = True, **kwargs: Any, ) -> DataCoordinate: return self._remote.expandDataId( - dataId, dimensions=dimensions, graph=graph, records=records, withDefaults=withDefaults, **kwargs + dataId, dimensions=dimensions, records=records, withDefaults=withDefaults, **kwargs ) def insertDimensionData( @@ -282,7 +278,7 @@ def queryDatasets( datasetType: Any, *, collections: CollectionArgType | None = None, - dimensions: Iterable[Dimension | str] | None = None, + dimensions: Iterable[str] | None = None, dataId: DataId | None = None, where: str = "", findFirst: bool = False, @@ -305,8 +301,7 @@ def queryDatasets( def queryDataIds( self, - # TODO: Drop `Dimension` objects on DM-41326. - dimensions: DimensionGroup | Iterable[Dimension | str] | Dimension | str, + dimensions: DimensionGroup | Iterable[str] | str, *, dataId: DataId | None = None, datasets: Any = None, @@ -424,7 +419,7 @@ def expanded(self) -> _HybridDataCoordinateQueryResults: def subset( self, - dimensions: DimensionGroup | DimensionGraph | Iterable[str] | None = None, + dimensions: DimensionGroup | Iterable[str] | None = None, *, unique: bool = False, ) -> _HybridDataCoordinateQueryResults: @@ -449,7 +444,7 @@ def findRelatedDatasets( collections: Any, *, findFirst: bool = True, - dimensions: DimensionGroup | DimensionGraph | Iterable[str] | None = None, + dimensions: DimensionGroup | Iterable[str] | None = None, ) -> Iterable[tuple[DataCoordinate, DatasetRef]]: return self._direct.findRelatedDatasets( datasetType, collections, findFirst=findFirst, dimensions=dimensions diff --git a/tests/test_dimensions.py b/tests/test_dimensions.py index 091e7e70c5..09f7e133ac 100644 --- a/tests/test_dimensions.py +++ b/tests/test_dimensions.py @@ -257,7 +257,7 @@ def testConfigRead(self): ) def testGraphs(self): - self.checkGroupInvariants(self.universe.empty.as_group()) + self.checkGroupInvariants(self.universe.empty) for element in self.universe.getStaticElements(): self.checkGroupInvariants(element.minimal_group) diff --git a/tests/test_query_interface.py b/tests/test_query_interface.py index 06be5f5e2f..7ca492fbcb 100644 --- a/tests/test_query_interface.py +++ b/tests/test_query_interface.py @@ -430,7 +430,7 @@ def test_int_literals(self) -> None: self.assertEqual(expr.column_type, "int") self.assertEqual(str(expr), "5") self.assertTrue(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertFalse(columns) self.assertEqual(expr.visit(_TestVisitor()), 5) @@ -443,7 +443,7 @@ def test_string_literals(self) -> None: self.assertEqual(expr.column_type, "string") self.assertEqual(str(expr), "'five'") self.assertTrue(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertFalse(columns) self.assertEqual(expr.visit(_TestVisitor()), "five") @@ -456,7 +456,7 @@ def test_float_literals(self) -> None: self.assertEqual(expr.column_type, "float") self.assertEqual(str(expr), "0.5") self.assertTrue(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertFalse(columns) self.assertEqual(expr.visit(_TestVisitor()), 0.5) @@ -469,7 +469,7 @@ def test_hash_literals(self) -> None: self.assertEqual(expr.column_type, "hash") self.assertEqual(str(expr), "(bytes)") self.assertTrue(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertFalse(columns) self.assertEqual(expr.visit(_TestVisitor()), b"eleven") @@ -483,7 +483,7 @@ def test_uuid_literals(self) -> None: self.assertEqual(expr.column_type, "uuid") self.assertEqual(str(expr), str(value)) self.assertTrue(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertFalse(columns) self.assertEqual(expr.visit(_TestVisitor()), value) @@ -497,7 +497,7 @@ def test_datetime_literals(self) -> None: self.assertEqual(expr.column_type, "datetime") self.assertEqual(str(expr), "2020-01-01T00:00:00") self.assertTrue(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertFalse(columns) self.assertEqual(expr.visit(_TestVisitor()), value) @@ -513,7 +513,7 @@ def test_timespan_literals(self) -> None: self.assertEqual(expr.column_type, "timespan") self.assertEqual(str(expr), "[2020-01-01T00:00:00, 2020-01-01T00:01:00)") self.assertTrue(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertFalse(columns) self.assertEqual(expr.visit(_TestVisitor()), value) @@ -528,7 +528,7 @@ def test_region_literals(self) -> None: self.assertEqual(expr.column_type, "region") self.assertEqual(str(expr), "(region)") self.assertTrue(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertFalse(columns) self.assertEqual(expr.visit(_TestVisitor()), value) @@ -544,7 +544,7 @@ def test_dimension_key_reference(self) -> None: self.assertEqual(expr.column_type, "int") self.assertEqual(str(expr), "detector") self.assertFalse(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["detector"])) self.assertEqual(expr.visit(_TestVisitor(dimension_keys={"detector": 3})), 3) @@ -556,7 +556,7 @@ def test_dimension_field_reference(self) -> None: self.assertEqual(expr.column_type, "string") self.assertEqual(str(expr), "detector.purpose") self.assertFalse(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["detector"])) self.assertEqual(columns.dimension_fields["detector"], {"purpose"}) @@ -572,9 +572,9 @@ def test_dataset_field_reference(self) -> None: self.assertEqual(expr.expression_type, "dataset_field") self.assertEqual(str(expr), "raw.ingest_date") self.assertFalse(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) - self.assertEqual(columns.dimensions, self.universe.empty.as_group()) + self.assertEqual(columns.dimensions, self.universe.empty) self.assertEqual(columns.dataset_fields["raw"], {"ingest_date"}) self.assertEqual(qt.DatasetFieldReference(dataset_type="raw", field="dataset_id").column_type, "uuid") self.assertEqual( @@ -597,7 +597,7 @@ def test_unary_negation(self) -> None: self.assertEqual(expr.column_type, "float") self.assertEqual(str(expr), "-visit.exposure_time") self.assertFalse(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["visit"])) self.assertEqual(columns.dimension_fields["visit"], {"exposure_time"}) @@ -617,7 +617,7 @@ def test_unary_timespan_begin(self) -> None: self.assertEqual(expr.column_type, "datetime") self.assertEqual(str(expr), "visit.timespan.begin") self.assertFalse(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["visit"])) self.assertEqual(columns.dimension_fields["visit"], {"timespan"}) @@ -642,7 +642,7 @@ def test_unary_timespan_end(self) -> None: self.assertEqual(expr.column_type, "datetime") self.assertEqual(str(expr), "visit.timespan.end") self.assertFalse(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["visit"])) self.assertEqual(columns.dimension_fields["visit"], {"timespan"}) @@ -686,7 +686,7 @@ def test_binary_expression_float(self) -> None: self.assertEqual(expr.column_type, "float") self.assertEqual(str(expr), string) self.assertFalse(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["visit"])) self.assertEqual(columns.dimension_fields["visit"], {"exposure_time"}) @@ -706,7 +706,7 @@ def test_binary_modulus(self) -> None: self.assertEqual(expr.column_type, "int") self.assertEqual(str(expr), string) self.assertFalse(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["visit"])) self.assertFalse(columns.dimension_fields["visit"]) @@ -732,7 +732,7 @@ def test_reversed(self) -> None: self.assertEqual(expr.column_type, "int") self.assertEqual(str(expr), "detector DESC") self.assertFalse(expr.is_literal) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) expr.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["detector"])) self.assertFalse(columns.dimension_fields["detector"]) @@ -805,7 +805,7 @@ def test_comparison(self) -> None: with self.subTest(string=string, detector=detector): self.assertEqual(predicate.column_type, "bool") self.assertEqual(str(predicate), string) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) predicate.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["detector"])) self.assertFalse(columns.dimension_fields["detector"]) @@ -818,7 +818,7 @@ def test_comparison(self) -> None: self.assertEqual( inverted.visit(_TestVisitor(dimension_keys={"detector": detector})), not value ) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) inverted.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["detector"])) self.assertFalse(columns.dimension_fields["detector"]) @@ -829,7 +829,7 @@ def test_overlap_comparison(self) -> None: predicate = self.x.visit.region.overlaps(region1) self.assertEqual(predicate.column_type, "bool") self.assertEqual(str(predicate), "visit.region OVERLAPS (region)") - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) predicate.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["visit"])) self.assertEqual(columns.dimension_fields["visit"], {"region"}) @@ -839,7 +839,7 @@ def test_overlap_comparison(self) -> None: self.assertEqual(inverted.column_type, "bool") self.assertEqual(str(inverted), "NOT visit.region OVERLAPS (region)") self.assertTrue(inverted.visit(_TestVisitor(dimension_fields={("visit", "region"): region2}))) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) inverted.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["visit"])) self.assertEqual(columns.dimension_fields["visit"], {"region"}) @@ -858,7 +858,7 @@ def test_is_null(self) -> None: predicate = self.x.visit.region.is_null self.assertEqual(predicate.column_type, "bool") self.assertEqual(str(predicate), "visit.region IS NULL") - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) predicate.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["visit"])) self.assertEqual(columns.dimension_fields["visit"], {"region"}) @@ -875,7 +875,7 @@ def test_in_container(self) -> None: predicate: qt.Predicate = self.x.visit.in_iterable([3, 4, self.x.exposure.id]) self.assertEqual(predicate.column_type, "bool") self.assertEqual(str(predicate), "visit IN [3, 4, exposure]") - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) predicate.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["visit", "exposure"])) self.assertFalse(columns.dimension_fields["visit"]) @@ -887,7 +887,7 @@ def test_in_container(self) -> None: self.assertEqual(str(inverted), "NOT visit IN [3, 4, exposure]") self.assertFalse(inverted.visit(_TestVisitor(dimension_keys={"visit": 2, "exposure": 2}))) self.assertTrue(inverted.visit(_TestVisitor(dimension_keys={"visit": 2, "exposure": 5}))) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) inverted.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["visit", "exposure"])) self.assertFalse(columns.dimension_fields["visit"]) @@ -907,7 +907,7 @@ def test_in_range(self) -> None: predicate: qt.Predicate = self.x.visit.in_range(2, 8, 2) self.assertEqual(predicate.column_type, "bool") self.assertEqual(str(predicate), "visit IN 2:8:2") - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) predicate.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["visit"])) self.assertFalse(columns.dimension_fields["visit"]) @@ -918,7 +918,7 @@ def test_in_range(self) -> None: self.assertEqual(str(inverted), "NOT visit IN 2:8:2") self.assertFalse(inverted.visit(_TestVisitor(dimension_keys={"visit": 2}))) self.assertTrue(inverted.visit(_TestVisitor(dimension_keys={"visit": 8}))) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) inverted.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["visit"])) self.assertFalse(columns.dimension_fields["visit"]) @@ -937,7 +937,7 @@ def test_in_query(self) -> None: predicate: qt.Predicate = self.x.exposure.in_query(self.x.visit, query) self.assertEqual(predicate.column_type, "bool") self.assertEqual(str(predicate), "exposure IN (query).visit") - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) predicate.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["exposure"])) self.assertFalse(columns.dimension_fields["exposure"]) @@ -956,7 +956,7 @@ def test_in_query(self) -> None: self.assertTrue( inverted.visit(_TestVisitor(dimension_keys={"exposure": 8}, query_tree_items={1, 2, 3})) ) - columns = qt.ColumnSet(self.universe.empty.as_group()) + columns = qt.ColumnSet(self.universe.empty) inverted.gather_required_columns(columns) self.assertEqual(columns.dimensions, self.universe.conform(["exposure"])) self.assertFalse(columns.dimension_fields["exposure"]) @@ -1118,7 +1118,7 @@ def test_dataset_join(self) -> None: def check( query: Query, - dimensions: DimensionGroup = self.raw.dimensions.as_group(), + dimensions: DimensionGroup = self.raw.dimensions, ) -> None: """Run a battery of tests on one of a set of very similar queries constructed in different ways (see below). @@ -1139,16 +1139,14 @@ def check_query_tree( Dimensions to expect in the `QueryTree`, not necessarily including those in the test 'raw' dataset type. """ - self.assertEqual(tree.dimensions, dimensions | self.raw.dimensions.as_group()) + self.assertEqual(tree.dimensions, dimensions | self.raw.dimensions) self.assertEqual(str(tree.predicate), "raw.run == 'DummyCam/raw/all'") self.assertFalse(tree.materializations) self.assertFalse(tree.data_coordinate_uploads) self.assertEqual(tree.datasets.keys(), {"raw"}) - self.assertEqual(tree.datasets["raw"].dimensions, self.raw.dimensions.as_group()) + self.assertEqual(tree.datasets["raw"].dimensions, self.raw.dimensions) self.assertEqual(tree.datasets["raw"].collections, ("DummyCam/defaults",)) - self.assertEqual( - tree.get_joined_dimension_groups(), frozenset({self.raw.dimensions.as_group()}) - ) + self.assertEqual(tree.get_joined_dimension_groups(), frozenset({self.raw.dimensions})) def check_data_id_results(*args, query: Query, dimensions: DimensionGroup = dimensions) -> None: """Construct a DataCoordinateQueryResults object from the query @@ -1197,7 +1195,7 @@ def check_dataset_results( cm.exception.result_spec, qrs.DatasetRefResultSpec( dataset_type_name="raw", - dimensions=self.raw.dimensions.as_group(), + dimensions=self.raw.dimensions, storage_class_name=storage_class_name, find_first=find_first, ), @@ -1242,7 +1240,7 @@ def check_materialization( # might need to re-join for some result columns in a # derived query. self.assertTrue(derived_tree.datasets.keys(), {"raw"}) - self.assertEqual(derived_tree.datasets["raw"].dimensions, self.raw.dimensions.as_group()) + self.assertEqual(derived_tree.datasets["raw"].dimensions, self.raw.dimensions) self.assertEqual(derived_tree.datasets["raw"].collections, ("DummyCam/defaults",)) else: self.assertFalse(derived_tree.datasets) @@ -1263,7 +1261,7 @@ def check_materialization( # Actual logic for the check() function begins here. self.assertEqual(query.constraint_dataset_types, {"raw"}) - self.assertEqual(query.constraint_dimensions, self.raw.dimensions.as_group()) + self.assertEqual(query.constraint_dimensions, self.raw.dimensions) # Adding a constraint on a field for this dataset type should work # (this constraint will be present in all downstream tests). @@ -1624,14 +1622,14 @@ def check( self.assertEqual(str(tree.predicate), "instrument == 'DummyCam' AND visit == 4") self.assertEqual( tree.dimensions, - self.universe.conform(["visit"]).union(results.dataset_type.dimensions.as_group()), + self.universe.conform(["visit"]).union(results.dataset_type.dimensions), ) self.assertFalse(tree.materializations) self.assertEqual(tree.datasets.keys(), {results.dataset_type.name}) self.assertEqual(tree.datasets[results.dataset_type.name].collections, ("DummyCam/defaults",)) self.assertEqual( tree.datasets[results.dataset_type.name].dimensions, - results.dataset_type.dimensions.as_group(), + results.dataset_type.dimensions, ) self.assertFalse(tree.data_coordinate_uploads) result_spec = cm.exception.result_spec @@ -1650,7 +1648,7 @@ def check( self.assertEqual( cm.exception.result_spec, qrs.DataCoordinateResultSpec( - dimensions=results.dataset_type.dimensions.as_group(), + dimensions=results.dataset_type.dimensions, include_dimension_records=include_dimension_records, ), ) @@ -1755,7 +1753,7 @@ def test_invalid_models(self) -> None: datasets={ "raw": qt.DatasetSearch( collections=("DummyCam/raw/all",), - dimensions=self.raw.dimensions.as_group(), + dimensions=self.raw.dimensions, ) }, ) @@ -1782,10 +1780,10 @@ def test_invalid_models(self) -> None: # ResultSpec's datasets are not a subset of the query tree's. DatasetRefQueryResults( _TestQueryDriver(), - qt.QueryTree(dimensions=self.raw.dimensions.as_group()), + qt.QueryTree(dimensions=self.raw.dimensions), qrs.DatasetRefResultSpec( dataset_type_name="raw", - dimensions=self.raw.dimensions.as_group(), + dimensions=self.raw.dimensions, storage_class_name=self.raw.storageClass_name, find_first=True, ), diff --git a/tests/test_query_utilities.py b/tests/test_query_utilities.py index 30bcdbef82..df52cef497 100644 --- a/tests/test_query_utilities.py +++ b/tests/test_query_utilities.py @@ -66,7 +66,7 @@ def test_basics(self) -> None: + [("detector", "purpose"), ("bias", "dataset_id")], ) self.assertEqual(str(columns), "{instrument, detector, detector:purpose, bias:dataset_id}") - empty = qt.ColumnSet(self.universe.empty.as_group()) + empty = qt.ColumnSet(self.universe.empty) self.assertFalse(empty) self.assertFalse(columns.issubset(empty)) self.assertTrue(columns.issuperset(empty)) diff --git a/tests/test_simpleButler.py b/tests/test_simpleButler.py index e88802d84c..098ddf06ff 100644 --- a/tests/test_simpleButler.py +++ b/tests/test_simpleButler.py @@ -580,7 +580,7 @@ def testRegistryDefaults(self): butler.registry.insertDimensionData("instrument", {"name": "Cam2"}) camera = DatasetType( "camera", - dimensions=butler.dimensions["instrument"].graph, + dimensions=butler.dimensions["instrument"].minimal_group, storageClass="Camera", ) butler.registry.registerDatasetType(camera)