Coverage for python/lsst/daf/butler/registry/queries/expressions/categorize.py: 28%
30 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-01 19:55 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-01 19:55 +0000
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21from __future__ import annotations
23__all__ = () # all symbols intentionally private; for internal package use.
25import enum
26from typing import (
27 Optional,
28 Tuple,
29)
31from ....core import (
32 DimensionUniverse,
33 Dimension,
34 DimensionElement,
35)
38class ExpressionConstant(enum.Enum):
39 """Enumeration for constants recognized in all expressions.
40 """
41 NULL = "null"
42 INGEST_DATE = "ingest_date"
45def categorizeConstant(name: str) -> Optional[ExpressionConstant]:
46 """Categorize an identifier in a parsed expression as one of a few global
47 constants.
49 Parameters
50 ----------
51 name : `str`
52 Identifier to categorize. Case-insensitive.
54 Returns
55 -------
56 categorized : `ExpressionConstant` or `None`
57 Enumeration value if the string represents a constant, `None`
58 otherwise.
59 """
60 try:
61 return ExpressionConstant(name.lower())
62 except ValueError:
63 return None
66def categorizeElementId(universe: DimensionUniverse, name: str) -> Tuple[DimensionElement, Optional[str]]:
67 """Categorize an identifier in a parsed expression as either a `Dimension`
68 name (indicating the primary key for that dimension) or a non-primary-key
69 column in a `DimensionElement` table.
71 Parameters
72 ----------
73 universe : `DimensionUniverse`
74 All known dimensions.
75 name : `str`
76 Identifier to categorize.
78 Returns
79 -------
80 element : `DimensionElement`
81 The `DimensionElement` the identifier refers to.
82 column : `str` or `None`
83 The name of a column in the table for ``element``, or `None` if
84 ``element`` is a `Dimension` and the requested column is its primary
85 key.
87 Raises
88 ------
89 LookupError
90 Raised if the identifier refers to a nonexistent `DimensionElement`
91 or column.
92 RuntimeError
93 Raised if the expression refers to a primary key in an illegal way.
94 This exception includes a suggestion for how to rewrite the expression,
95 so at least its message should generally be propagated up to a context
96 where the error can be interpreted by a human.
97 """
98 table, sep, column = name.partition('.')
99 if column:
100 try:
101 element = universe[table]
102 except KeyError as err:
103 raise LookupError(f"No dimension element with name '{table}'.") from err
104 if isinstance(element, Dimension) and column == element.primaryKey.name:
105 # Allow e.g. "visit.id = x" instead of just "visit = x"; this
106 # can be clearer.
107 return element, None
108 elif column in element.graph.names:
109 # User said something like "patch.tract = x" or
110 # "tract.tract = x" instead of just "tract = x" or
111 # "tract.id = x", which is at least needlessly confusing and
112 # possibly not actually a column name, though we can guess
113 # what they were trying to do.
114 # Encourage them to clean that up and try again.
115 raise RuntimeError(
116 f"Invalid reference to '{table}.{column}' " # type: ignore
117 f"in expression; please use '{column}' or "
118 f"'{column}.{universe[column].primaryKey.name}' instead."
119 )
120 else:
121 return element, column
122 else:
123 try:
124 dimension = universe[table]
125 except KeyError as err:
126 raise LookupError(f"No dimension with name '{table}'.") from err
127 return dimension, None