Coverage for python / lsst / daf / butler / queries / convert_args.py: 16%
49 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-01 08:18 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-01 08:18 +0000
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
28from __future__ import annotations
30__all__ = (
31 "convert_order_by_args",
32 "convert_where_args",
33)
35from collections.abc import Mapping, Set
36from typing import Any
38from .._exceptions import InvalidQueryError
39from ..dimensions import DataCoordinate, DataId, DimensionGroup
40from ._expression_strings import convert_expression_string_to_predicate
41from ._identifiers import IdentifierContext, interpret_identifier
42from .expression_factory import ExpressionFactory, ExpressionProxy
43from .tree import (
44 DimensionKeyReference,
45 OrderExpression,
46 Predicate,
47 Reversed,
48 make_column_literal,
49 validate_order_expression,
50)
53def convert_where_args(
54 dimensions: DimensionGroup,
55 datasets: Set[str],
56 *args: str | Predicate | DataId,
57 bind: Mapping[str, Any] | None = None,
58 **kwargs: Any,
59) -> Predicate:
60 """Convert ``where`` arguments to a sequence of column expressions.
62 Parameters
63 ----------
64 dimensions : `DimensionGroup`
65 Dimensions already present in the query this filter is being applied
66 to. Returned predicates may reference dimensions outside this set.
67 datasets : `~collections.abc.Set` [ `str` ]
68 Dataset types already present in the query this filter is being applied
69 to. Returned predicates may reference datasets outside this set; this
70 may be an error at a higher level, but it is not necessarily checked
71 here.
72 *args : `str`, `Predicate`, `DataCoordinate`, or `~collections.abc.Mapping`
73 Expressions to convert into predicates.
74 bind : `~collections.abc.Mapping`, optional
75 Mapping from identifier to literal value used when parsing string
76 expressions.
77 **kwargs : `object`
78 Additional data ID key-value pairs.
80 Returns
81 -------
82 predicate : `Predicate`
83 Standardized predicate object.
85 Notes
86 -----
87 Data ID values are not checked for consistency; they are extracted from
88 args and then kwargs and combined, with later extractions taking
89 precedence.
90 """
91 context = IdentifierContext(dimensions, datasets, bind)
92 result = Predicate.from_bool(True)
93 data_id_dict: dict[str, Any] = {}
94 for arg in args:
95 match arg:
96 case str():
97 result = result.logical_and(
98 convert_expression_string_to_predicate(arg, context=context, universe=dimensions.universe)
99 )
100 case Predicate():
101 result = result.logical_and(arg)
102 case DataCoordinate():
103 data_id_dict.update(arg.mapping)
104 case _:
105 data_id_dict.update(arg)
106 data_id_dict.update(kwargs)
107 for k, v in data_id_dict.items():
108 result = result.logical_and(
109 Predicate.compare(
110 DimensionKeyReference.model_construct(dimension=dimensions.universe.dimensions[k]),
111 "==",
112 make_column_literal(v),
113 )
114 )
115 return result
118def convert_order_by_args(
119 dimensions: DimensionGroup, datasets: Set[str], *args: str | OrderExpression | ExpressionProxy
120) -> tuple[OrderExpression, ...]:
121 """Convert ``order_by`` arguments to a sequence of column expressions.
123 Parameters
124 ----------
125 dimensions : `DimensionGroup`
126 Dimensions already present in the query whose rows are being sorted.
127 Returned expressions may reference dimensions outside this set; this
128 may be an error at a higher level, but it is not necessarily checked
129 here.
130 datasets : `~collections.abc.Set` [ `str` ]
131 Dataset types already present in the query whose rows are being sorted.
132 Returned expressions may reference datasets outside this set; this may
133 be an error at a higher level, but it is not necessarily checked here.
134 *args : `OrderExpression`, `str`, or `ExpressionObject`
135 Expression or column names to sort by.
137 Returns
138 -------
139 expressions : `tuple` [ `OrderExpression`, ... ]
140 Standardized expression objects.
141 """
142 context = IdentifierContext(dimensions, datasets)
143 result: list[OrderExpression] = []
144 for arg in args:
145 match arg:
146 case str():
147 reverse = False
148 if arg.startswith("-"):
149 reverse = True
150 arg = arg[1:]
151 if len(arg) == 0:
152 raise InvalidQueryError("Empty dimension name in ORDER BY")
153 arg = interpret_identifier(context, arg)
154 if reverse:
155 arg = Reversed(operand=arg)
156 case ExpressionProxy():
157 arg = ExpressionFactory.unwrap(arg)
158 if not hasattr(arg, "expression_type"):
159 raise TypeError(f"Unrecognized order-by argument: {arg!r}.")
160 result.append(validate_order_expression(arg))
161 return tuple(result)