Skip to content

Commit

Permalink
Docs and cleanups for RelationQuery and relation tree objects.
Browse files Browse the repository at this point in the history
  • Loading branch information
TallJimbo committed Dec 10, 2023
1 parent 31333f9 commit 1f3ca64
Show file tree
Hide file tree
Showing 12 changed files with 599 additions and 138 deletions.
183 changes: 135 additions & 48 deletions python/lsst/daf/butler/queries/_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ def dimension_records(
# Docstring inherited.
raise NotImplementedError("TODO")

# TODO: add general, dict-row results method and QueryResults.

# TODO: methods below are not part of the base Query, but they have
# counterparts on at least some QueryResults objects. We need to think
# about which should be duplicated in Query and QueryResults, and which
Expand Down Expand Up @@ -261,18 +263,18 @@ def explain_no_results(self, execute: bool = True) -> Iterable[str]:
"""
return self._driver.explain_no_results(self._tree, execute=execute)

Check warning on line 264 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L264

Added line #L264 was not covered by tests

def order_by(self, *args: str | OrderExpression | ExpressionProxy) -> Query:
"""Sort any results returned by this query.
def order_by(self, *args: str | OrderExpression | ExpressionProxy) -> RelationQuery:

Check warning on line 266 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L266

Added line #L266 was not covered by tests
"""Return a new query that sorts any results returned.
Parameters
----------
*args : `str` or `OrderExpression`
Names of the columns/dimensions to use for ordering. Column name
can be prefixed with minus (``-``) to use descending ordering.
Column names or expression objects to use for ordering. Names can
be prefixed with minus (``-``) to use descending ordering.
Returns
-------
result : `Query`
query : `Query`
A new query object whose results will be sorted.
Notes
Expand All @@ -287,15 +289,20 @@ def order_by(self, *args: str | OrderExpression | ExpressionProxy) -> Query:
Note that this is consistent with sorting first by ``a`` and then by
``b``.
If an expression references a dimension or dimension element that is
not already present in the query, it will be joined in, but dataset
searches must already be joined into a query in order to reference
their fields in expressions.
"""
return RelationQuery(

Check warning on line 298 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L298

Added line #L298 was not covered by tests
tree=self._tree.order_by(*self._convert_order_by_args(*args)),
driver=self._driver,
include_dimension_records=self._include_dimension_records,
)

def limit(self, limit: int | None = None, offset: int = 0) -> Query:
"""Limit the results returned by this query via positional slicing.
def limit(self, limit: int | None = None, offset: int = 0) -> RelationQuery:

Check warning on line 304 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L304

Added line #L304 was not covered by tests
"""Return a new query with results limited by positional slicing.
Parameters
----------
Expand All @@ -307,7 +314,7 @@ def limit(self, limit: int | None = None, offset: int = 0) -> Query:
Returns
-------
result : `Query`
query : `Query`
A new query object whose results will be sliced.
Notes
Expand All @@ -325,35 +332,54 @@ def limit(self, limit: int | None = None, offset: int = 0) -> Query:
# DataCoordinateQueryResults, but the signature should probably change,
# too, and that requires more thought.

def join_dataset(
def join_dataset_search(

Check warning on line 335 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L335

Added line #L335 was not covered by tests
self,
dataset_type: str,
collections: Iterable[str],
*,
spatial: JoinArg = frozenset(),
temporal: JoinArg = frozenset(),
) -> RelationQuery:
"""Return a new query with a search for a dataset joined in.
Parameters
----------
dataset_type : `str`
Name of the dataset type. May not refer to a dataset component.
collections : `~collections.abc.Iterable` [ `str` ]
Iterable of collections to search. Order is preserved, but will
not matter if the dataset search is only used as a constraint on
dimensions or if ``find_first=False`` when requesting results.
Returns
-------
query : `Query`
A new query object with dataset columns available and rows
restricted to those consistent with the found data IDs.
"""
return RelationQuery(

Check warning on line 357 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L357

Added line #L357 was not covered by tests
tree=self._tree.join(
DatasetSearch.model_construct(
dataset_type=dataset_type,
collections=tuple(collections),
dimensions=self._driver.get_dataset_dimensions(dataset_type),
),
spatial=spatial,
temporal=temporal,
)
),
driver=self._driver,
include_dimension_records=self._include_dimension_records,
)

def join_data_coordinates(
self,
iterable: Iterable[DataCoordinate],
*,
spatial: JoinArg = frozenset(),
temporal: JoinArg = frozenset(),
) -> RelationQuery:
def join_data_coordinates(self, iterable: Iterable[DataCoordinate]) -> RelationQuery:

Check warning on line 369 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L369

Added line #L369 was not covered by tests
"""Return a new query that joins in an explicit table of data IDs.
Parameters
----------
iterable : `~collections.abc.Iterable` [ `DataCoordinate` ]
Iterable of `DataCoordinate`. All items must have the same
dimensions. Must have at least one item.
Returns
-------
query : `Query`
A new query object with the data IDs joined in.
"""
rows: set[tuple[DataIdValue, ...]] = set()
dimensions: DimensionGroup | None = None

Check warning on line 384 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L383-L384

Added lines #L383 - L384 were not covered by tests
for data_coordinate in iterable:
Expand All @@ -365,28 +391,69 @@ def join_data_coordinates(
if dimensions is None:
raise RuntimeError("Cannot upload an empty data coordinate set.")
return RelationQuery(

Check warning on line 393 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L392-L393

Added lines #L392 - L393 were not covered by tests
tree=self._tree.join(
DataCoordinateUpload(dimensions=dimensions, rows=rows), spatial=spatial, temporal=temporal
),
tree=self._tree.join(DataCoordinateUpload(dimensions=dimensions, rows=rows)),
driver=self._driver,
include_dimension_records=self._include_dimension_records,
)

def join_dimensions(
self,
dimensions: Iterable[str] | DimensionGroup,
*,
spatial: JoinArg = frozenset(),
temporal: JoinArg = frozenset(),
) -> RelationQuery:
def join_dimensions(self, dimensions: Iterable[str] | DimensionGroup) -> RelationQuery:

Check warning on line 399 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L399

Added line #L399 was not covered by tests
"""Return a new query that joins the logical tables for additional
dimensions.
Parameters
----------
iterable : `~collections.abc.Iterable` [ `str` ] or `DimensionGroup`
Names of dimensions to join in.
Returns
-------
query : `Query`
A new query object with the dimensions joined in.
"""
dimensions = self._driver.universe.conform(dimensions)
return RelationQuery(

Check warning on line 414 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L413-L414

Added lines #L413 - L414 were not covered by tests
tree=self._tree.join(make_dimension_relation(dimensions), spatial=spatial, temporal=temporal),
tree=self._tree.join(make_dimension_relation(dimensions)),
driver=self._driver,
include_dimension_records=self._include_dimension_records,
)

def joined_on(self, *, spatial: JoinArg = frozenset(), temporal: JoinArg = frozenset()) -> RelationQuery:

Check warning on line 420 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L420

Added line #L420 was not covered by tests
"""Return a new query with new spatial or temporal join constraints.
Parameters
----------
spatial : `tuple` [ `str`, `str` ] or `~collections.abc.Iterable` \
[ `tuple [ `str`, `str` ], optional
A pair or pairs of dimension element names whose regions must
overlap.
temporal : `tuple` [ `str`, `str` ] or `~collections.abc.Iterable` \
[ `tuple [ `str`, `str` ], optional
A pair or pairs of dimension element names and/or calibration
dataset type names whose timespans must overlap. Datasets in
collections other than `~CollectionType.CALIBRATION` collections
are associated with an unbounded timespan.
Returns
-------
query : `Query`
A new query object with the given join criteria as well as any
join criteria already present in ``self``.
Notes
-----
Implicit spatial and temporal joins are also added to queries (when
they are actually executed) when an explicit join (of the sort added by
this method) is absent between any pair of spatial/temporal "families"
present in the query, and these implicit joins use the most
fine-grained overlap possible. For example, in a query with dimensions
``{instrument, visit, tract, patch}``, the implicit spatial join would
be between ``visit`` and ``patch``, but an explicit join between
``visit`` and `tract`` would override it, because "tract" and "patch"
are in the same family (as are ``visit`` and ``visit_detector_region``,
but the latter is not used here because it is not present in the
example query dimensions). For "skypix" dimensions like ``healpixN``
or `htmN`, all levels in the same system are in the same family.
"""
return RelationQuery(

Check warning on line 457 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L457

Added line #L457 was not covered by tests
tree=self._tree.joined_on(spatial=spatial, temporal=temporal),
driver=self._driver,
Expand All @@ -395,33 +462,53 @@ def joined_on(self, *, spatial: JoinArg = frozenset(), temporal: JoinArg = froze

def where(

Check warning on line 463 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L463

Added line #L463 was not covered by tests
self,
*args: str | Predicate | DataCoordinate,
*args: str | Predicate | DataId,
bind: Mapping[str, Any] | None = None,
**kwargs: Any,
) -> RelationQuery:
return RelationQuery(
tree=self._tree.where(*self._convert_predicate_args(*args, bind=bind, **kwargs)),
driver=self._driver,
include_dimension_records=self._include_dimension_records,
)
"""Return a query with a boolean-expression filter on its rows.
def find_first(
self, dataset_type: str, dimensions: Iterable[str] | DimensionGroup | None
) -> RelationQuery:
if dimensions is None:
dimensions = self._driver.get_dataset_dimensions(dataset_type)
else:
dimensions = self._driver.universe.conform(dimensions)
Parameters
----------
*args
Constraints to apply, combined with logical AND. Arguments may be
`str` expressions to parse, `Predicate` objects (these are
typically constructed via `expression_factory`) or data IDs.
bind : `~collections.abc.Mapping`
Mapping from string identifier appearing in a string expression to
a literal value that should be substituted for it. This is
recommended instead of embedding literals directly into the
expression, especially for strings, timespans, or other types where
quoting or formatting is nontrivial.
**kwargs
Data ID key value pairs that extend and override any present in
``*args``.
Returns
-------
query : `Query`
A new query object with the given row filters as well as any
already present in ``self`` (combined with logical AND).
Notes
-----
If an expression references a dimension or dimension element that is
not already present in the query, it will be joined in, but dataset
searches must already be joined into a query in order to reference
their fields in expressions.
"""
return RelationQuery(

Check warning on line 500 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L500

Added line #L500 was not covered by tests
tree=self._tree.find_first(dataset_type, dimensions),
tree=self._tree.where(*self._convert_predicate_args(*args, bind=bind, **kwargs)),
driver=self._driver,
include_dimension_records=self._include_dimension_records,
)

def _convert_order_by_args(self, *args: str | OrderExpression | ExpressionProxy) -> list[OrderExpression]:

Check warning on line 506 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L506

Added line #L506 was not covered by tests
"""Convert ``order_by`` arguments to a list of column expressions."""
raise NotImplementedError("TODO: Parse string expression.")

def _convert_predicate_args(

Check warning on line 510 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L510

Added line #L510 was not covered by tests
self, *args: str | Predicate | DataCoordinate, bind: Mapping[str, Any] | None = None
self, *args: str | Predicate | DataId, bind: Mapping[str, Any] | None = None
) -> list[Predicate]:
"""Convert ``where`` arguments to a list of column expressions."""
raise NotImplementedError("TODO: Parse string expression.")
Loading

0 comments on commit 1f3ca64

Please sign in to comment.