Coverage for python/lsst/daf/butler/_butlerConfig.py: 25%

Shortcuts 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

39 statements  

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 

31from typing import Optional, Sequence, Union 

32 

33from .core import ButlerURI, Config, DatastoreConfig, StorageClassConfig 

34from .registry import RegistryConfig 

35from .transfers import RepoTransferFormatConfig 

36 

37CONFIG_COMPONENT_CLASSES = (RegistryConfig, StorageClassConfig, DatastoreConfig, RepoTransferFormatConfig) 

38 

39 

40class ButlerConfig(Config): 

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

42 

43 The configuration is read and merged with default configurations for 

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

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

46 with a configuration class reads its own defaults. 

47 

48 Parameters 

49 ---------- 

50 other : `str`, `Config`, `ButlerConfig`, optional 

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

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

53 be configured based entirely on defaults read from the environment 

54 or from ``searchPaths``. 

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

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

57 Explicit additional paths to search for defaults. They should 

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

59 than those read from the environment in 

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

61 refers to a configuration file or directory. 

62 """ 

63 

64 def __init__( 

65 self, 

66 other: Optional[Union[str, ButlerURI, Config]] = None, 

67 searchPaths: Sequence[Union[str, ButlerURI]] = None, 

68 ): 

69 

70 self.configDir: Optional[ButlerURI] = None 

71 

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

73 # have already been loaded. 

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

75 super().__init__(other) 

76 # Ensure that the configuration directory propagates 

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

78 return 

79 

80 if isinstance(other, (str, os.PathLike)): 

81 # This will only allow supported schemes 

82 uri = ButlerURI(other) 

83 

84 # We allow the butler configuration file to be left off the 

85 # URI supplied by the user. If a directory-like URI is given 

86 # we add the default configuration name. 

87 

88 # It's easy to miss a trailing / for remote URIs so try to guess 

89 # we have been given a directory-like URI if there is no 

90 # file extension. Local URIs do not need any guess work. 

91 if not uri.isLocal and not uri.getExtension(): 

92 uri = ButlerURI(other, forceDirectory=True) 

93 

94 if uri.isdir(): 

95 # Could also be butler.json (for example in the butler 

96 # server) but checking for existence will slow things 

97 # down given that this might involve two checks and then 

98 # the config read below would still do the read. 

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

100 

101 # Create an empty config for us to populate 

102 super().__init__() 

103 

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

105 # defaults to use. 

106 butlerConfig = Config(other) 

107 

108 configFile = butlerConfig.configFile 

109 if configFile is not None: 

110 uri = ButlerURI(configFile) 

111 self.configFile = uri 

112 self.configDir = uri.dirname() 

113 

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

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

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

117 for configClass in CONFIG_COMPONENT_CLASSES: 

118 # Only send the parent config if the child 

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

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

121 localOverrides = None 

122 if configClass.component in butlerConfig: 

123 localOverrides = butlerConfig 

124 config = configClass(localOverrides, searchPaths=searchPaths) 

125 # Re-attach it using the global namespace 

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

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

128 # merged that information. 

129 if configClass.component in butlerConfig: 

130 del butlerConfig[configClass.component] 

131 

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

133 # provided config into the defaults. 

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

135 # not present in component configurations 

136 self.update(butlerConfig)