|
1 | 1 | import json |
2 | 2 | import ast |
| 3 | +from functools import wraps |
3 | 4 | from contextlib import contextmanager |
4 | 5 | from hashlib import sha256 |
5 | 6 | from pathlib import Path |
|
12 | 13 | from idom.cli import console |
13 | 14 |
|
14 | 15 |
|
| 16 | +_Method = TypeVar("_Method") |
| 17 | + |
| 18 | + |
| 19 | +def _requires_open_transaction(method: _Method) -> _Method: |
| 20 | + @wraps(method) |
| 21 | + def wrapper(self: BuildConfigFile, *args: Any, **kwargs: Any) -> Any: |
| 22 | + if not self._transaction_open: |
| 23 | + raise RuntimeError("Cannot modify BuildConfigFile without transaction.") |
| 24 | + return method(self, *args, **kwargs) |
| 25 | + |
| 26 | + return wrapper |
| 27 | + |
| 28 | + |
15 | 29 | class BuildConfigFile: |
16 | 30 |
|
17 | | - __slots__ = "configs", "_path" |
18 | | - _filename = ".idom-build-configs.json" |
| 31 | + __slots__ = "_configs", "_path", "_transaction_open" |
| 32 | + _filename = "idom-build-config.json" |
19 | 33 |
|
20 | 34 | def __init__(self, path: Path) -> None: |
21 | 35 | self._path = path / self._filename |
22 | | - self.configs = self._load_configs() |
| 36 | + self._config_items = self._load_config_items() |
| 37 | + self._transaction_open = False |
23 | 38 |
|
24 | 39 | @contextmanager |
25 | 40 | def transaction(self) -> Iterator[None]: |
26 | | - old_configs = self.configs |
27 | | - self.configs = old_configs.copy() |
| 41 | + """Open a transaction to modify the config file state""" |
| 42 | + self._transaction_open = True |
| 43 | + old_configs = self._config_items |
| 44 | + self._config_items = old_configs.copy() |
28 | 45 | try: |
29 | 46 | yield |
30 | 47 | except Exception: |
31 | | - self.configs = old_configs |
| 48 | + self._config_items = old_configs |
32 | 49 | raise |
33 | 50 | else: |
34 | 51 | self.save() |
| 52 | + finally: |
| 53 | + self._transaction_open = False |
35 | 54 |
|
36 | | - def add(self, build_configs: Iterable[Any], ignore_existing: bool = False) -> None: |
37 | | - for config in map(BuildConfigItem.cast, build_configs): |
38 | | - source_name = config.source_name |
39 | | - if not ignore_existing and source_name in self.configs: |
40 | | - raise ValueError(f"A build config for {source_name!r} already exists") |
41 | | - self.configs[source_name] = config |
42 | | - return None |
| 55 | + @property |
| 56 | + def configs(self) -> Dict[str, "BuildConfigItem"]: |
| 57 | + """A dictionary of config items""" |
| 58 | + return self._config_items.copy() |
43 | 59 |
|
44 | 60 | def save(self) -> None: |
| 61 | + """Save config state to file""" |
45 | 62 | with self._path.open("w") as f: |
46 | | - json.dump({name: conf._asdict() for name, conf in self.configs.items()}, f) |
| 63 | + json.dump( |
| 64 | + {name: conf._asdict() for name, conf in self._config_items.items()}, f |
| 65 | + ) |
47 | 66 |
|
48 | 67 | def show(self, indent: int = 2) -> str: |
| 68 | + """Return string repr of config state""" |
49 | 69 | return json.dumps( |
50 | | - {name: conf._asdict() for name, conf in self.configs.items()}, |
| 70 | + {name: conf._asdict() for name, conf in self._config_items.items()}, |
51 | 71 | indent=indent, |
52 | 72 | ) |
53 | 73 |
|
| 74 | + @_requires_open_transaction |
| 75 | + def add(self, build_configs: Iterable[Any], ignore_existing: bool = False) -> None: |
| 76 | + """Add a config item""" |
| 77 | + for config in map(BuildConfigItem.cast, build_configs): |
| 78 | + source_name = config.source_name |
| 79 | + if not ignore_existing and source_name in self._config_items: |
| 80 | + raise ValueError(f"A build config for {source_name!r} already exists") |
| 81 | + self._config_items[source_name] = config |
| 82 | + return None |
| 83 | + |
| 84 | + @_requires_open_transaction |
| 85 | + def remove(self, source_name: str, ignore_missing: bool = False) -> None: |
| 86 | + """Remove a config item""" |
| 87 | + if ignore_missing: |
| 88 | + self._config_items.pop(source_name, None) |
| 89 | + else: |
| 90 | + del self._config_items[source_name] |
| 91 | + |
| 92 | + @_requires_open_transaction |
54 | 93 | def clear(self) -> None: |
55 | | - self.configs = {} |
| 94 | + """Clear all config items""" |
| 95 | + self._config_items = {} |
56 | 96 |
|
57 | | - def _load_configs(self) -> Dict[str, "BuildConfigItem"]: |
| 97 | + def _load_config_items(self) -> Dict[str, "BuildConfigItem"]: |
58 | 98 | if not self._path.exists(): |
59 | 99 | return {} |
60 | 100 | with self._path.open() as f: |
|
0 commit comments