Coverage for python/lsst/pipe/base/configOverrides.py : 26%

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 pipe_base.
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/>.
22"""Module which defines ConfigOverrides class and related methods.
23"""
25__all__ = ["ConfigOverrides"]
27import ast
29import lsst.pex.exceptions as pexExceptions
30from lsst.utils import doImport
32from enum import Enum
34OverrideTypes = Enum("OverrideTypes", "Value File Python Instrument")
37class ConfigOverrides:
38 """Defines a set of overrides to be applied to a task config.
40 Overrides for task configuration need to be applied by activator when
41 creating task instances. This class represents an ordered set of such
42 overrides which activator receives from some source (e.g. command line
43 or some other configuration).
45 Methods
46 ----------
47 addFileOverride(filename)
48 Add overrides from a specified file.
49 addValueOverride(field, value)
50 Add override for a specific field.
51 applyTo(config)
52 Apply all overrides to a `config` instance.
54 Notes
55 -----
56 Serialization support for this class may be needed, will add later if
57 necessary.
58 """
60 def __init__(self):
61 self._overrides = []
63 def addFileOverride(self, filename):
64 """Add overrides from a specified file.
66 Parameters
67 ----------
68 filename : str
69 Path to the override file.
70 """
71 self._overrides.append((OverrideTypes.File, filename))
73 def addValueOverride(self, field, value):
74 """Add override for a specific field.
76 This method is not very type-safe as it is designed to support
77 use cases where input is given as string, e.g. command line
78 activators. If `value` has a string type and setting of the field
79 fails with `TypeError` the we'll attempt `eval()` the value and
80 set the field with that value instead.
82 Parameters
83 ----------
84 field : str
85 Fully-qualified field name.
86 value :
87 Value to be given to a filed.
88 """
89 self._overrides.append((OverrideTypes.Value, (field, value)))
91 def addPythonOverride(self, python_snippet: str):
92 """Add Overrides by running a snippit of python code against a config.
94 Parameters
95 ----------
96 python_snippet: str
97 A string which is valid python code to be executed. This is done
98 with config as the only local accessible value.
99 """
100 self._overrides.append((OverrideTypes.Python, python_snippet))
102 def addInstrumentOverride(self, instrument: str, task_name: str):
103 """Apply any overrides that an instrument has for a task
105 Parameters
106 ----------
107 instrument: str
108 A string containing the fully qualified name of an instrument from
109 which configs should be loaded and applied
110 task_name: str
111 The _DefaultName of a task associated with a config, used to look
112 up overrides from the instrument.
113 """
114 instrument_lib = doImport(instrument)()
115 self._overrides.append((OverrideTypes.Instrument, (instrument_lib, task_name)))
117 def applyTo(self, config):
118 """Apply all overrides to a task configuration object.
120 Parameters
121 ----------
122 config : `pex.Config`
124 Raises
125 ------
126 `Exception` is raised if operations on configuration object fail.
127 """
128 for otype, override in self._overrides:
129 if otype is OverrideTypes.File:
130 config.load(override)
131 elif otype is OverrideTypes.Value:
132 field, value = override
133 field = field.split('.')
134 # find object with attribute to set, throws if we name is wrong
135 obj = config
136 for attr in field[:-1]:
137 obj = getattr(obj, attr)
138 # If input is a string and field type is not a string then we
139 # have to convert string to an expected type. Implementing
140 # full string parser is non-trivial so we take a shortcut here
141 # and `eval` the string and assign the resulting value to a
142 # field. Type erroes can happen during both `eval` and field
143 # assignment.
144 if isinstance(value, str) and obj._fields[field[-1]].dtype is not str:
145 try:
146 # use safer ast.literal_eval, it only supports literals
147 value = ast.literal_eval(value)
148 except Exception:
149 # eval failed, wrap exception with more user-friendly
150 # message
151 raise pexExceptions.RuntimeError(f"Unable to parse `{value}' into a Python object")
153 # this can throw in case of type mismatch
154 setattr(obj, field[-1], value)
155 elif otype is OverrideTypes.Python:
156 exec(override, None, {"config": config})
157 elif otype is OverrideTypes.Instrument:
158 instrument, name = override
159 instrument.applyConfigOverrides(name, config)