Coverage for python/lsst/dax/apdb/config.py: 37%
57 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-12 01:00 -0800
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-12 01:00 -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/>.
22from __future__ import annotations
24__all__ = ["ApdbConfig"]
26import os
27import warnings
28from collections.abc import Mapping
29from typing import Any, ClassVar, cast
31import yaml
32from pydantic import BaseModel, Field
34from lsst.resources import ResourcePath, ResourcePathExpression
36from .apdbIndex import ApdbIndex
37from .factory import config_type_for_name
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)
45class ApdbConfig(BaseModel):
46 """Base class for APDB configuration types.
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 """
53 _implementation_type: ClassVar[str]
55 schema_file: str = Field(
56 default=_data_file_name("apdb.yaml"),
57 description="Location of (YAML) configuration file with standard schema.",
58 )
60 schema_name: str = Field(
61 default="ApdbSchema",
62 description="Name of the schema in YAML configuration file.",
63 )
65 read_sources_months: int = Field(
66 default=12,
67 description="Number of months of history to read from DiaSource.",
68 )
70 read_forced_sources_months: int = Field(
71 default=12,
72 description="Number of months of history to read from DiaForcedSource",
73 )
75 enable_replica: bool = Field(
76 default=False,
77 description="If True, make and fill additional tables used for replication.",
78 )
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 )
87 @classmethod
88 def from_uri(cls, uri: ResourcePathExpression) -> ApdbConfig:
89 """Load configuration object from external file.
91 Parameters
92 ----------
93 uri : `~lsst.resources.ResourcePathExpression`
94 Location of the file containing serialized configuration in YAML
95 format.
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
119 path = ResourcePath(uri)
120 config_bytes = path.read()
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
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
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)
151 def save(self, uri: ResourcePathExpression) -> None:
152 """Save configuration to a specified location in YAML format.
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)
163 path = ResourcePath(uri)
164 path.write(config_yaml.encode())