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