Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 

22 

23__all__ = () # all symbols intentionally private; for internal package use. 

24 

25import enum 

26from typing import ( 

27 Optional, 

28 Tuple, 

29) 

30 

31from ....core import ( 

32 DimensionUniverse, 

33 Dimension, 

34 DimensionElement, 

35) 

36 

37 

38class ExpressionConstant(enum.Enum): 

39 """Enumeration for constants recognized in all expressions. 

40 """ 

41 NULL = "null" 

42 INGEST_DATE = "ingest_date" 

43 

44 

45def categorizeConstant(name: str) -> Optional[ExpressionConstant]: 

46 """Categorize an identifier in a parsed expression as one of a few global 

47 constants. 

48 

49 Parameters 

50 ---------- 

51 name : `str` 

52 Identifier to categorize. Case-insensitive. 

53 

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 

64 

65 

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. 

70 

71 Parameters 

72 ---------- 

73 universe : `DimensionUniverse` 

74 All known dimensions. 

75 name : `str` 

76 Identifier to categorize. 

77 

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. 

86 

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