Save config files atomically

Fixes #506.

In PR #377 (save GlobalPrefs atomically), I created a new codepath by
passing a file into yaml.dump. Unfortunately I forgot to mark it as
UTF-8 (regressing #311). This breaks saving settings after opening a
config in a Unicode folder.

To prevent this from recurring, I restructured the code to always pass a
Path into yaml.dump(), and write the file atomically.
pull/507/head
nyanpasu64 2025-05-13 21:02:12 -07:00
rodzic 6a504c51db
commit 4fa08c4f25
3 zmienionych plików z 13 dodań i 8 usunięć

Wyświetl plik

@ -14,6 +14,8 @@
### Changelog ### Changelog
- When opening missing file via CLI, show dialog rather than crashing (#499) - When opening missing file via CLI, show dialog rather than crashing (#499)
- Fix saving global settings after opening config in Unicode folder (#507)
- Save config files atomically (#507)
## 0.10.1 ## 0.10.1

Wyświetl plik

@ -19,10 +19,10 @@ from typing import (
Any, Any,
TextIO, TextIO,
Union, Union,
IO,
) )
import attr import attr
from atomicwrites import atomic_write
from ruamel.yaml import ( from ruamel.yaml import (
yaml_object, yaml_object,
YAML, YAML,
@ -61,9 +61,14 @@ class MyYAML(YAML):
# https://bitbucket.org/ruamel/yaml/issues/316/unicode-encoding-decoding-errors-on # https://bitbucket.org/ruamel/yaml/issues/316/unicode-encoding-decoding-errors-on
# Both are bad, so use UTF-8. # Both are bad, so use UTF-8.
if isinstance(stream, Path): if isinstance(stream, Path):
with stream.open("w", encoding="utf-8") as f: path = stream
with atomic_write(path, overwrite=True, encoding="utf-8") as f:
self.dump_without_corrupting(data, f, **kwargs) self.dump_without_corrupting(data, f, **kwargs)
elif isinstance(stream, TextIO):
# Nobody actually calls dump(..., open()). This branch is never taken.
self.dump_without_corrupting(data, stream, **kwargs)
elif stream is None: elif stream is None:
# Possibly only called in unit tests, not in production. # Possibly only called in unit tests, not in production.
stream = StringIO() stream = StringIO()
@ -71,8 +76,9 @@ class MyYAML(YAML):
return stream.getvalue() return stream.getvalue()
else: else:
# with atomic_write(...) as f: dump(..., f) raise TypeError(
self.dump_without_corrupting(data, stream, **kwargs) f"stream must be {{Path, TextIO=open(), None}}, but is {type(stream)}"
)
def dump_without_corrupting(self, *args, **kwargs): def dump_without_corrupting(self, *args, **kwargs):
YAML.dump(self, *args, **kwargs) YAML.dump(self, *args, **kwargs)

Wyświetl plik

@ -1,8 +1,6 @@
from typing import * from typing import *
import attr import attr
from atomicwrites import atomic_write
from corrscope.config import DumpableAttrs, yaml from corrscope.config import DumpableAttrs, yaml
from corrscope.settings import paths from corrscope.settings import paths
@ -75,5 +73,4 @@ def load_prefs() -> GlobalPrefs:
def dump_prefs(pref: GlobalPrefs) -> None: def dump_prefs(pref: GlobalPrefs) -> None:
with atomic_write(_PREF_PATH, overwrite=True, encoding="utf-8") as f: yaml.dump(pref, _PREF_PATH)
yaml.dump(pref, f)