Coverage for python / lsst / dax / apdb / config.py: 35%

55 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-22 08:56 +0000

1# This file is part of dax_apdb 

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 

22from __future__ import annotations 

23 

24__all__ = ["ApdbConfig"] 

25 

26import warnings 

27from collections.abc import Mapping 

28from typing import Any, ClassVar, cast 

29 

30import yaml 

31from pydantic import BaseModel, Field 

32 

33from lsst.resources import ResourcePath, ResourcePathExpression 

34 

35from .apdbIndex import ApdbIndex 

36from .factory import config_type_for_name 

37 

38 

39class ApdbConfig(BaseModel): 

40 """Base class for APDB configuration types. 

41 

42 This class contains a set of parameters that are common to all 

43 implementations. Implementation-specific parameters are declared in 

44 sub-classes. 

45 """ 

46 

47 _implementation_type: ClassVar[str] 

48 

49 schema_file: str = Field( 

50 default="resource://lsst.sdm.schemas/apdb.yaml", 

51 description="Location of (YAML) configuration file with standard APDB schema.", 

52 ) 

53 

54 ss_schema_file: str = Field( 

55 default="resource://lsst.sdm.schemas/sso.yaml", 

56 description=( 

57 "Location of (YAML) configuration file with SSO schema. " 

58 "This file is only loaded if SSObject/SSSource tables are not present in APDB schema file. " 

59 "Can be set to empty string to avoid loading even if tables are not in APDB schema." 

60 ), 

61 ) 

62 

63 schema_name: str = Field( 

64 default="ApdbSchema", 

65 description="Name of the schema in YAML configuration file (not used and deprecated).", 

66 ) 

67 

68 read_sources_months: int = Field( 

69 default=12, 

70 description="Number of months of history to read from DiaSource.", 

71 ) 

72 

73 read_forced_sources_months: int = Field( 

74 default=12, 

75 description="Number of months of history to read from DiaForcedSource", 

76 ) 

77 

78 enable_replica: bool = Field( 

79 default=False, 

80 description="If True, make and fill additional tables used for replication.", 

81 ) 

82 

83 replica_chunk_seconds: int = Field( 

84 default=600, 

85 description=( 

86 "Time extent for replica chunks, new chunks are created every specified number of seconds." 

87 ), 

88 ) 

89 

90 @classmethod 

91 def from_uri(cls, uri: ResourcePathExpression) -> ApdbConfig: 

92 """Load configuration object from external file. 

93 

94 Parameters 

95 ---------- 

96 uri : `~lsst.resources.ResourcePathExpression` 

97 Location of the file containing serialized configuration in YAML 

98 format. 

99 

100 Returns 

101 ------- 

102 config : `ApdbConfig` 

103 Apdb configuration object. 

104 """ 

105 if isinstance(uri, str) and uri.startswith("label:"): 

106 tag, _, label = uri.partition(":") 

107 index = ApdbIndex() 

108 # Try to find YAML format first, and pex_config if YAML is not 

109 # found. During transitional period we support conversion of 

110 # pex_config format to a new pydantic format. 

111 try: 

112 uri = index.get_apdb_uri(label, "yaml") 

113 except ValueError as yaml_exc: 

114 try: 

115 uri = index.get_apdb_uri(label, "pex_config") 

116 except ValueError: 

117 # If none is found then re-raise exception from yaml 

118 # attempt, but add a note that pex_config is missing too. 

119 yaml_exc.add_note(f"Legacy label {label}/pex_config is also missing.") 

120 raise yaml_exc from None 

121 

122 path = ResourcePath(uri) 

123 config_bytes = path.read() 

124 

125 # During transitional period we support loading of configurations from 

126 # both pex_config and pydantic/YAML formats. We have to look at the 

127 # contents for figure out which is which. 

128 if config_bytes.startswith(b"import lsst.dax.apdb"): 

129 from . import legacy_config 

130 

131 pex_config = legacy_config.ApdbConfig.legacy_load(config_bytes) 

132 new_config = pex_config.to_model() 

133 warnings.warn( 

134 ( 

135 f"APDB is instantiated using legacy pex_config format from file {path}. " 

136 "Support for pex_config format will be removed after v29. Please update " 

137 "configuration to new YAML format with `apdb-cli convert-legacy-config` command." 

138 ), 

139 category=FutureWarning, 

140 stacklevel=2, 

141 ) 

142 return new_config 

143 

144 config_object = yaml.full_load(config_bytes) 

145 if not isinstance(config_object, Mapping): 

146 raise TypeError("YAML configuration file does not represent valid object") 

147 config_dict: dict[str, Any] = dict(config_object) 

148 type_name = config_dict.pop("implementation_type", None) 

149 if not type_name: 

150 raise LookupError("YAML configuration file does not have `implementation_type` key") 

151 klass = config_type_for_name(cast(str, type_name)) 

152 return klass.model_validate(config_dict) 

153 

154 def save(self, uri: ResourcePathExpression) -> None: 

155 """Save configuration to a specified location in YAML format. 

156 

157 Parameters 

158 ---------- 

159 uri : `ResourcePathExpression` 

160 Location to save configuration 

161 """ 

162 config_dict = self.model_dump(exclude_unset=True, exclude_defaults=True) 

163 config_dict["implementation_type"] = self._implementation_type 

164 config_yaml = yaml.dump(config_dict) 

165 

166 path = ResourcePath(uri) 

167 path.write(config_yaml.encode())