Coverage for python/lsst/verify/spec/base.py: 55%

47 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-19 02:12 -0700

1# This file is part of verify. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://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 <https://www.gnu.org/licenses/>. 

21__all__ = ["Specification"] 

22 

23import abc 

24 

25from ..jsonmixin import JsonSerializationMixin 

26from ..metaquery import MetadataQuery 

27from ..naming import Name 

28 

29 

30class Specification(JsonSerializationMixin, metaclass=abc.ABCMeta): 

31 """Specification base class. 

32 

33 Specification classes must implement: 

34 

35 - `type` 

36 - `_serialize_type` 

37 - `check` 

38 

39 Subclasses should also call ``Specification.__init__`` to initialize 

40 the specifications ``name`` attribute (a `~lsst.verify.Name` 

41 instance). 

42 """ 

43 

44 def __init__(self, name, **kwargs): 

45 # interal object behind self.tags 

46 self._tags = set() 

47 

48 # name attibute must be a Name instance representing a specification 

49 if not isinstance(name, Name): 

50 self._name = Name(spec=name) 

51 else: 

52 self._name = name 

53 if not self._name.is_spec: 

54 message = 'name {0!r} does not represent a specification' 

55 raise TypeError(message.format(self._name)) 

56 

57 if 'metadata_query' in kwargs: 

58 self._metadata_query = MetadataQuery(kwargs['metadata_query']) 

59 else: 

60 self._metadata_query = MetadataQuery() 

61 

62 if 'tags' in kwargs: 

63 self.tags = kwargs['tags'] 

64 

65 @property 

66 def name(self): 

67 """Specification name (`lsst.verify.Name`).""" 

68 return self._name 

69 

70 @property 

71 def metric_name(self): 

72 """Name of the metric this specification corresponds to 

73 (`lsst.verify.Name`).""" 

74 return Name(package=self.name.package, metric=self.name.metric) 

75 

76 @property 

77 def tags(self): 

78 """Tag labels (`set` of `str`).""" 

79 return self._tags 

80 

81 @tags.setter 

82 def tags(self, t): 

83 # Ensure that tags is always a set. 

84 if isinstance(t, str): 

85 t = [t] 

86 self._tags = set(t) 

87 

88 @abc.abstractproperty 

89 def type(self): 

90 """Specification type (`str`).""" 

91 pass 

92 

93 @abc.abstractmethod 

94 def _serialize_type(self): 

95 """Serialize type-specific specification data to a JSON-serializable 

96 `dict`. 

97 

98 This method is used by the `json` property as the value associated 

99 with the key named for `type`. 

100 """ 

101 pass 

102 

103 @property 

104 def json(self): 

105 """`dict` that can be serialized as semantic JSON, compatible with 

106 the SQUASH metric service. 

107 """ 

108 return JsonSerializationMixin.jsonify_dict( 

109 { 

110 'name': str(self.name), 

111 'type': self.type, 

112 self.type: self._serialize_type(), 

113 'metadata_query': self._metadata_query, 

114 'tags': self.tags 

115 } 

116 ) 

117 

118 @abc.abstractmethod 

119 def check(self, measurement): 

120 """Check if a measurement passes this specification. 

121 

122 Parameters 

123 ---------- 

124 measurement : `astropy.units.Quantity` 

125 The measurement value. The measurement `~astropy.units.Quantity` 

126 must have units *compatible* with the specification. 

127 

128 Returns 

129 ------- 

130 passed : `bool` 

131 `True` if the measurement meets the specification, 

132 `False` otherwise. 

133 """ 

134 pass 

135 

136 def query_metadata(self, metadata, arg_driven=False): 

137 """Query a Job's metadata to determine if this specification applies. 

138 

139 Parameters 

140 ---------- 

141 metadata : `lsst.verify.Metadata` or `dict`-type 

142 Metadata mapping. Typically this is the `lsst.verify.Job.meta` 

143 attribute. 

144 arg_driven : `bool`, optional 

145 If `False` (default), ``metadata`` matches the ``MetadataQuery`` 

146 if ``metadata`` has all the terms defined in ``MetadataQuery``, 

147 and those terms match. If ``metadata`` has more terms than 

148 ``MetadataQuery``, it can still match. This behavior is 

149 appropriate for finding if a specification applies to a Job 

150 given metadata. 

151 

152 If `True`, the orientation of the matching is reversed. Now 

153 ``metadata`` matches the ``MetadataQuery`` if ``MetadataQuery`` 

154 has all the terms defined in ``metadata`` and those terms match. 

155 If ``MetadataQuery`` has more terms than ``metadata``, it can 

156 still match. This behavior is appropriate for discovering 

157 specifications. 

158 

159 Returns 

160 ------- 

161 matched : `bool` 

162 `True` if this specification matches, `False` otherwise. 

163 

164 See also 

165 -------- 

166 lsst.verify.MetadataQuery 

167 """ 

168 return self._metadata_query(metadata, arg_driven=arg_driven)