1import os 2import warnings 3 4import toml 5from more_itertools import always_iterable 6 7from yt._maintenance.deprecation import issue_deprecation_warning 8from yt.utilities.configuration_tree import ConfigLeaf, ConfigNode 9 10ytcfg_defaults = {} 11 12ytcfg_defaults["yt"] = dict( 13 serialize=False, 14 only_deserialize=False, 15 time_functions=False, 16 colored_logs=False, 17 suppress_stream_logging=False, 18 stdout_stream_logging=False, 19 log_level=20, 20 inline=False, 21 num_threads=-1, 22 store_parameter_files=False, 23 parameter_file_store="parameter_files.csv", 24 maximum_stored_datasets=500, 25 skip_dataset_cache=True, 26 load_field_plugins=False, 27 plugin_filename="my_plugins.py", 28 parallel_traceback=False, 29 pasteboard_repo="", 30 reconstruct_index=True, 31 test_storage_dir="/does/not/exist", 32 test_data_dir="/does/not/exist", 33 enzo_db="", 34 notebook_password="", 35 answer_testing_tolerance=3, 36 answer_testing_bitwise=False, 37 gold_standard_filename="gold311", 38 local_standard_filename="local001", 39 answer_tests_url="http://answers.yt-project.org/{1}_{2}", 40 sketchfab_api_key="None", 41 imagebin_api_key="e1977d9195fe39e", 42 imagebin_upload_url="https://api.imgur.com/3/image", 43 imagebin_delete_url="https://api.imgur.com/3/image/{delete_hash}", 44 curldrop_upload_url="http://use.yt/upload", 45 thread_field_detection=False, 46 ignore_invalid_unit_operation_errors=False, 47 chunk_size=1000, 48 xray_data_dir="/does/not/exist", 49 supp_data_dir="/does/not/exist", 50 default_colormap="arbre", 51 ray_tracing_engine="embree", 52 internals=dict( 53 within_testing=False, 54 within_pytest=False, 55 parallel=False, 56 strict_requires=False, 57 global_parallel_rank=0, 58 global_parallel_size=1, 59 topcomm_parallel_rank=0, 60 topcomm_parallel_size=1, 61 command_line=False, 62 ), 63) 64 65 66def config_dir(): 67 config_root = os.environ.get( 68 "XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config") 69 ) 70 conf_dir = os.path.join(config_root, "yt") 71 72 if not os.path.exists(conf_dir): 73 try: 74 os.makedirs(conf_dir) 75 except OSError: 76 warnings.warn("unable to create yt config directory") 77 return conf_dir 78 79 80def old_config_file(): 81 return os.path.join(config_dir(), "ytrc") 82 83 84def old_config_dir(): 85 return os.path.join(os.path.expanduser("~"), ".yt") 86 87 88# For backward compatibility, do not use these vars internally in yt 89CONFIG_DIR = config_dir() 90_OLD_CONFIG_FILE = old_config_file() 91 92 93class YTConfig: 94 def __init__(self, defaults=None): 95 if defaults is None: 96 defaults = {} 97 self.config_root = ConfigNode(None) 98 99 def get(self, section, *keys, callback=None): 100 node_or_leaf = self.config_root.get(section, *keys) 101 if isinstance(node_or_leaf, ConfigLeaf): 102 if callback is not None: 103 return callback(node_or_leaf) 104 return node_or_leaf.value 105 return node_or_leaf 106 107 def get_most_specific(self, section, *keys, **kwargs): 108 use_fallback = "fallback" in kwargs 109 fallback = kwargs.pop("fallback", None) 110 try: 111 return self.config_root.get_deepest_leaf(section, *keys) 112 except KeyError as err: 113 if use_fallback: 114 return fallback 115 else: 116 raise err 117 118 def update(self, new_values, metadata=None): 119 if metadata is None: 120 metadata = {} 121 self.config_root.update(new_values, metadata) 122 123 def has_section(self, section): 124 try: 125 self.config_root.get_child(section) 126 return True 127 except KeyError: 128 return False 129 130 def add_section(self, section): 131 self.config_root.add_child(section) 132 133 def remove_section(self, section): 134 if self.has_section(section): 135 self.config_root.remove_child(section) 136 return True 137 else: 138 return False 139 140 def set(self, *args, metadata=None): 141 section, *keys, value = args 142 if metadata is None: 143 metadata = {"source": "runtime"} 144 self.config_root.upsert_from_list( 145 [section] + list(keys), value, extra_data=metadata 146 ) 147 148 def remove(self, *args): 149 self.config_root.pop_leaf(args) 150 151 def read(self, file_names): 152 file_names_read = [] 153 for fname in always_iterable(file_names): 154 if not os.path.exists(fname): 155 continue 156 metadata = {"source": f"file: {fname}"} 157 self.update(toml.load(fname), metadata=metadata) 158 file_names_read.append(fname) 159 160 return file_names_read 161 162 def write(self, file_handler): 163 value = self.config_root.as_dict() 164 config_as_str = toml.dumps(value) 165 166 try: 167 # Assuming file_handler has a write attribute 168 file_handler.write(config_as_str) 169 except AttributeError: 170 # Otherwise we expect a path to a file 171 with open(file_handler, mode="w") as fh: 172 fh.write(config_as_str) 173 174 @staticmethod 175 def get_global_config_file(): 176 return os.path.join(config_dir(), "yt.toml") 177 178 @staticmethod 179 def get_local_config_file(): 180 return os.path.join(os.path.abspath(os.curdir), "yt.toml") 181 182 def __setitem__(self, args, value): 183 section, *keys = always_iterable(args) 184 self.set(section, *keys, value, metadata=None) 185 186 def __getitem__(self, key): 187 section, *keys = always_iterable(key) 188 return self.get(section, *keys) 189 190 def __contains__(self, item): 191 return item in self.config_root 192 193 # Add support for IPython rich display 194 # see https://ipython.readthedocs.io/en/stable/config/integrating.html 195 def _repr_json_(self): 196 return self.config_root._repr_json_() 197 198 199_global_config_file = YTConfig.get_global_config_file() 200_local_config_file = YTConfig.get_local_config_file() 201 202if os.path.exists(old_config_file()): 203 if os.path.exists(_global_config_file): 204 issue_deprecation_warning( 205 f"The configuration file {old_config_file()} is deprecated in " 206 f"favor of {_global_config_file}. Currently, both are present. " 207 "Please manually remove the deprecated one to silence " 208 "this warning.", 209 since="4.0.0", 210 removal="4.1.0", 211 ) 212 else: 213 issue_deprecation_warning( 214 f"The configuration file {_OLD_CONFIG_FILE} is deprecated. " 215 f"Please migrate your config to {_global_config_file} by running: " 216 "'yt config migrate'", 217 since="4.0.0", 218 removal="4.1.0", 219 ) 220 221 222if not os.path.exists(_global_config_file): 223 cfg = {"yt": {}} 224 try: 225 with open(_global_config_file, mode="w") as fd: 226 toml.dump(cfg, fd) 227 except OSError: 228 warnings.warn("unable to write new config file") 229 230 231# Load the config 232ytcfg = YTConfig() 233ytcfg.update(ytcfg_defaults, metadata={"source": "defaults"}) 234 235# Try loading the local config first, otherwise fall back to global config 236if os.path.exists(_local_config_file): 237 ytcfg.read(_local_config_file) 238elif os.path.exists(_global_config_file): 239 ytcfg.read(_global_config_file) 240