Coverage for /builds/kinetik161/ase/ase/config.py: 68.00%
100 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-12-10 11:04 +0000
1import os
2import configparser
3from collections.abc import Mapping
4from pathlib import Path
5import shlex
6import warnings
8from ase.utils import lazymethod
9from ase.calculators.names import names, builtin, 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 _env(self):
22 if self.parser.has_section('environment'):
23 return self.parser['environment']
24 else:
25 return {}
27 def __iter__(self):
28 yield from self._env()
30 def __getitem__(self, item):
31 # XXX We should replace the mapping behaviour with individual
32 # methods to get from cfg or environment, or only from cfg.
33 #
34 # We cannot be a mapping very correctly without getting trouble
35 # with mutable state needing synchronization with os.environ.
37 env = self._env()
38 try:
39 return env[item]
40 except KeyError:
41 pass
43 value = os.environ[item]
44 warnings.warn(f'Loaded {item} from environment. '
45 'Please use configfile.',
46 ASEEnvDeprecationWarning)
48 return value
50 def __len__(self):
51 return len(self._env())
53 @lazymethod
54 def _paths_and_parser(self):
55 def argv_converter(argv):
56 return shlex.split(argv)
58 parser = configparser.ConfigParser(converters={"argv": argv_converter})
59 envpath = os.environ.get("ASE_CONFIG_PATH")
60 if envpath is not None:
61 paths = [Path(p) for p in envpath.split(":")]
62 else:
63 paths = [ASE_CONFIG_FILE, ]
64 loaded_paths = parser.read(paths)
65 # add sections for builtin calculators
66 for name in builtin:
67 parser.add_section(name)
68 parser[name]["builtin"] = "True"
69 return loaded_paths, parser
71 @property
72 def paths(self):
73 return self._paths_and_parser()[0]
75 @property
76 def parser(self):
77 return self._paths_and_parser()[1]
79 def check_calculators(self):
81 print("Calculators")
82 print("===========")
83 print()
84 print("Configured in ASE")
85 print(" | Installed on machine")
86 print(" | | Name & version")
87 print(" | | |")
88 for name in names:
89 # configured = False
90 # installed = False
91 template = templates.get(name)
92 # if template is None:
93 # XXX no template for this calculator.
94 # We need templates for all calculators somehow,
95 # but we can probably generate those for old FileIOCalculators
96 # automatically.
97 # continue
99 fullname = name
100 try:
101 codeconfig = self[name]
102 except KeyError:
103 codeconfig = None
104 version = None
105 else:
106 if template is None:
107 # XXX we should not be executing this
108 if codeconfig is not None and "builtin" in codeconfig:
109 # builtin calculators
110 version = "builtin"
111 else:
112 version = None
113 else:
114 profile = template.load_profile(codeconfig)
115 # XXX should be made robust to failure here:
116 with warnings.catch_warnings():
117 warnings.simplefilter("ignore")
118 version = profile.version()
120 fullname = name
121 if version is not None:
122 fullname += f"--{version}"
124 def tickmark(thing):
125 return "[ ]" if thing is None else "[x]"
127 msg = " {configured} {installed} {fullname}".format(
128 configured=tickmark(codeconfig),
129 installed=tickmark(version),
130 fullname=fullname,
131 )
132 print(msg)
134 def print_everything(self):
135 print("Configuration")
136 print("-------------")
137 print()
138 if not self.paths:
139 print("No configuration loaded.")
141 for path in self.paths:
142 print(f"Loaded: {path}")
144 print()
145 for name, section in self.parser.items():
146 print(name)
147 if not section:
148 print(" (Nothing configured)")
149 for key, val in section.items():
150 print(f" {key}: {val}")
151 print()
153 def as_dict(self):
154 return {key: dict(val) for key, val in self.parser.items()}
157cfg = Config()