Coverage for /builds/ase/ase/ase/config.py: 60.00%
90 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3import configparser
4import os
5import shlex
6import warnings
7from collections.abc import Mapping
8from pathlib import Path
10from ase.calculators.names import builtin, names, templates
12ASE_CONFIG_FILE = Path.home() / ".config/ase/config.ini"
15class ASEEnvDeprecationWarning(DeprecationWarning):
16 def __init__(self, message):
17 self.message = message
20class Config(Mapping):
21 def __init__(self):
22 def argv_converter(argv):
23 return shlex.split(argv)
25 self.parser = configparser.ConfigParser(
26 converters={"argv": argv_converter},
27 interpolation=configparser.ExtendedInterpolation())
28 self.paths = []
30 def _env(self):
31 if self.parser.has_section('environment'):
32 return self.parser['environment']
33 else:
34 return {}
36 def __iter__(self):
37 yield from self._env()
39 def __getitem__(self, item):
40 # XXX We should replace the mapping behaviour with individual
41 # methods to get from cfg or environment, or only from cfg.
42 #
43 # We cannot be a mapping very correctly without getting trouble
44 # with mutable state needing synchronization with os.environ.
46 env = self._env()
47 try:
48 return env[item]
49 except KeyError:
50 pass
52 value = os.environ[item]
53 warnings.warn(f'Loaded {item} from environment. '
54 'Please use configfile.',
55 ASEEnvDeprecationWarning)
57 return value
59 def __len__(self):
60 return len(self._env())
62 def check_calculators(self):
63 print("Calculators")
64 print("===========")
65 print()
66 print("Configured in ASE")
67 print(" | Installed on machine")
68 print(" | | Name & version")
69 print(" | | |")
70 for name in names:
71 # configured = False
72 # installed = False
73 template = templates.get(name)
74 # if template is None:
75 # XXX no template for this calculator.
76 # We need templates for all calculators somehow,
77 # but we can probably generate those for old FileIOCalculators
78 # automatically.
79 # continue
81 fullname = name
82 try:
83 codeconfig = self[name]
84 except KeyError:
85 codeconfig = None
86 version = None
87 else:
88 if template is None:
89 # XXX we should not be executing this
90 if codeconfig is not None and "builtin" in codeconfig:
91 # builtin calculators
92 version = "builtin"
93 else:
94 version = None
95 else:
96 profile = template.load_profile(codeconfig)
97 # XXX should be made robust to failure here:
98 with warnings.catch_warnings():
99 warnings.simplefilter("ignore")
100 version = profile.version()
102 fullname = name
103 if version is not None:
104 fullname += f"--{version}"
106 def tickmark(thing):
107 return "[ ]" if thing is None else "[x]"
109 msg = " {configured} {installed} {fullname}".format(
110 configured=tickmark(codeconfig),
111 installed=tickmark(version),
112 fullname=fullname,
113 )
114 print(msg)
116 def print_header(self):
117 print("Configuration")
118 print("-------------")
119 print()
120 if not self.paths:
121 print("No configuration loaded.")
123 for path in self.paths:
124 print(f"Loaded: {path}")
126 def as_dict(self):
127 return {key: dict(val) for key, val in self.parser.items()}
129 def _read_paths(self, paths):
130 self.paths += self.parser.read(paths)
132 @classmethod
133 def read(cls):
134 envpath = os.environ.get("ASE_CONFIG_PATH")
135 if envpath is None:
136 paths = [ASE_CONFIG_FILE, ]
137 else:
138 paths = [Path(p) for p in envpath.split(":")]
140 cfg = cls()
141 cfg._read_paths(paths)
143 # add sections for builtin calculators
144 for name in builtin:
145 cfg.parser.add_section(name)
146 cfg.parser[name]["builtin"] = "True"
147 return cfg
150cfg = Config.read()