Hide keyboard shortcuts

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 daf_butler. 

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/>. 

21 

22""" 

23Configuration classes specific to the Butler 

24""" 

25from __future__ import annotations 

26 

27__all__ = ("ButlerConfig",) 

28 

29import copy 

30import os.path 

31from typing import ( 

32 Optional 

33) 

34 

35from .core import ( 

36 ButlerURI, 

37 Config, 

38 DatastoreConfig, 

39 DimensionConfig, 

40 StorageClassConfig, 

41) 

42from .registry import RegistryConfig 

43from .transfers import RepoTransferFormatConfig 

44 

45CONFIG_COMPONENT_CLASSES = (RegistryConfig, StorageClassConfig, 

46 DatastoreConfig, DimensionConfig, 

47 RepoTransferFormatConfig) 

48 

49 

50class ButlerConfig(Config): 

51 """Contains the configuration for a `Butler` 

52 

53 The configuration is read and merged with default configurations for 

54 the particular classes. The defaults are read according to the rules 

55 outlined in `ConfigSubset`. Each component of the configuration associated 

56 with a configuration class reads its own defaults. 

57 

58 Parameters 

59 ---------- 

60 other : `str`, `Config`, optional 

61 Path to butler configuration YAML file or a directory containing a 

62 "butler.yaml" file. If `None` the butler will 

63 be configured based entirely on defaults read from the environment 

64 or from ``searchPaths``. 

65 No defaults will be read if a `ButlerConfig` is supplied directly. 

66 searchPaths : `list` or `tuple`, optional 

67 Explicit additional paths to search for defaults. They should 

68 be supplied in priority order. These paths have higher priority 

69 than those read from the environment in 

70 `ConfigSubset.defaultSearchPaths()`. They are only read if ``other`` 

71 refers to a configuration file or directory. 

72 """ 

73 

74 def __init__(self, other=None, searchPaths=None): 

75 

76 self.configDir: Optional[ButlerURI] = None 

77 

78 # If this is already a ButlerConfig we assume that defaults 

79 # have already been loaded. 

80 if other is not None and isinstance(other, ButlerConfig): 

81 super().__init__(other) 

82 # Ensure that the configuration directory propagates 

83 self.configDir = copy.copy(other.configDir) 

84 return 

85 

86 if isinstance(other, str): 

87 # This will only allow supported schemes 

88 uri = ButlerURI(other) 

89 if uri.scheme == "file" or not uri.scheme: 

90 # Check explicitly that we have a directory 

91 if os.path.isdir(uri.ospath): 

92 other = uri.join("butler.yaml") 

93 else: 

94 # For generic URI assume that we have a directory 

95 # if the basename does not have a file extension 

96 # This heuristic is needed since we can not rely on 

97 # external users to include the trailing / and we 

98 # can't always check that the remote resource is a directory. 

99 if not uri.dirLike and not uri.getExtension(): 

100 # Force to a directory and add the default config name 

101 uri = ButlerURI(other, forceDirectory=True).join("butler.yaml") 

102 other = uri 

103 

104 # Create an empty config for us to populate 

105 super().__init__() 

106 

107 # Read the supplied config so that we can work out which other 

108 # defaults to use. 

109 butlerConfig = Config(other) 

110 

111 configFile = butlerConfig.configFile 

112 if configFile is not None: 

113 uri = ButlerURI(configFile) 

114 self.configDir = uri.dirname() 

115 

116 # A Butler config contains defaults defined by each of the component 

117 # configuration classes. We ask each of them to apply defaults to 

118 # the values we have been supplied by the user. 

119 for configClass in CONFIG_COMPONENT_CLASSES: 

120 # Only send the parent config if the child 

121 # config component is present (otherwise it assumes that the 

122 # keys from other components are part of the child) 

123 localOverrides = None 

124 if configClass.component in butlerConfig: 

125 localOverrides = butlerConfig 

126 config = configClass(localOverrides, searchPaths=searchPaths) 

127 # Re-attach it using the global namespace 

128 self.update({configClass.component: config}) 

129 # Remove the key from the butlerConfig since we have already 

130 # merged that information. 

131 if configClass.component in butlerConfig: 

132 del butlerConfig[configClass.component] 

133 

134 # Now that we have all the defaults we can merge the externally 

135 # provided config into the defaults. 

136 # Not needed if there is never information in a butler config file 

137 # not present in component configurations 

138 self.update(butlerConfig)