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

57 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-08 01:12 -0800

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 os 

27import warnings 

28from collections.abc import Mapping 

29from typing import Any, ClassVar, cast 

30 

31import yaml 

32from pydantic import BaseModel, Field 

33 

34from lsst.resources import ResourcePath, ResourcePathExpression 

35 

36from .apdbIndex import ApdbIndex 

37from .factory import config_type_for_name 

38 

39 

40def _data_file_name(basename: str) -> str: 

41 """Return path name of a data file in sdm_schemas package.""" 

42 return os.path.join("${SDM_SCHEMAS_DIR}", "yml", basename) 

43 

44 

45class ApdbConfig(BaseModel): 

46 """Base class for APDB configuration types. 

47 

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

49 implementations. Implementation-specific parameters are declared in 

50 sub-classes. 

51 """ 

52 

53 _implementation_type: ClassVar[str] 

54 

55 schema_file: str = Field( 

56 default=_data_file_name("apdb.yaml"), 

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

58 ) 

59 

60 schema_name: str = Field( 

61 default="ApdbSchema", 

62 description="Name of the schema in YAML configuration file.", 

63 ) 

64 

65 read_sources_months: int = Field( 

66 default=12, 

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

68 ) 

69 

70 read_forced_sources_months: int = Field( 

71 default=12, 

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

73 ) 

74 

75 enable_replica: bool = Field( 

76 default=False, 

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

78 ) 

79 

80 replica_chunk_seconds: int = Field( 

81 default=600, 

82 description=( 

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

84 ), 

85 ) 

86 

87 @classmethod 

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

89 """Load configuration object from external file. 

90 

91 Parameters 

92 ---------- 

93 uri : `~lsst.resources.ResourcePathExpression` 

94 Location of the file containing serialized configuration in YAML 

95 format. 

96 

97 Returns 

98 ------- 

99 config : `ApdbConfig` 

100 Apdb configuration object. 

101 """ 

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

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

104 index = ApdbIndex() 

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

106 # found. During transitional period we support conversion of 

107 # pex_config format to a new pydantic format. 

108 try: 

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

110 except ValueError as yaml_exc: 

111 try: 

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

113 except ValueError: 

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

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

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

117 raise yaml_exc from None 

118 

119 path = ResourcePath(uri) 

120 config_bytes = path.read() 

121 

122 # During transitional period we support loading of configurations from 

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

124 # contents for figure out which is which. 

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

126 from . import legacy_config 

127 

128 pex_config = legacy_config.ApdbConfig.legacy_load(config_bytes) 

129 new_config = pex_config.to_model() 

130 warnings.warn( 

131 ( 

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

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

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

135 ), 

136 category=FutureWarning, 

137 stacklevel=2, 

138 ) 

139 return new_config 

140 

141 config_object = yaml.full_load(config_bytes) 

142 if not isinstance(config_object, Mapping): 

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

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

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

146 if not type_name: 

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

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

149 return klass.model_validate(config_dict) 

150 

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

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

153 

154 Parameters 

155 ---------- 

156 uri : `ResourcePathExpression` 

157 Location to save configuration 

158 """ 

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

160 config_dict["implementation_type"] = self._implementation_type 

161 config_yaml = yaml.dump(config_dict) 

162 

163 path = ResourcePath(uri) 

164 path.write(config_yaml.encode())