Skip to content

Instantly share code, notes, and snippets.

@therightstuff
Last active April 12, 2021 09:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save therightstuff/cbdcbef4010c20acc70d2175a91a321f to your computer and use it in GitHub Desktop.
Save therightstuff/cbdcbef4010c20acc70d2175a91a321f to your computer and use it in GitHub Desktop.
Safe atomic file writes for JSON and YAML in Python 3
import json
import os
import shutil
import stat
import tempfile
import yaml
def copy_with_metadata(source, target):
"""Copy file with all its permissions and metadata.
Lifted from https://stackoverflow.com/a/43761127/2860309
:param source: source file name
:param target: target file name
"""
# copy content, stat-info (mode too), timestamps...
shutil.copy2(source, target)
# copy owner and group
st = os.stat(source)
os.chown(target, st[stat.ST_UID], st[stat.ST_GID])
def atomic_write(file_contents, target_file_path, mode="w"):
"""Write to a temporary file and rename it to avoid file corruption.
Attribution: @therightstuff, @deichrenner, @hrudham
:param file_contents: contents to be written to file
:param target_file_path: the file to be created or replaced
:param mode: the file mode defaults to "w", only "w" and "a" are supported
"""
# Use the same directory as the destination file so that moving it across
# file systems does not pose a problem.
temp_file = tempfile.NamedTemporaryFile(
delete=False,
dir=os.path.dirname(target_file_path))
try:
# preserve file metadata if it already exists
if os.path.exists(target_file_path):
copy_with_metadata(target_file_path, temp_file.name)
with open(temp_file.name, mode) as f:
f.write(file_contents)
f.flush()
os.fsync(f.fileno())
os.replace(temp_file.name, target_file_path)
finally:
if os.path.exists(temp_file.name):
try:
os.unlink(temp_file.name)
except:
pass
# example lifted from https://www.debugcn.com/en/article/37223857.html
data = [{'item': 'Food_eat', 'Food': {'foodNo': 42536216,'type': 'fruit','moreInfo': ['organic']}}]
yaml_content = yaml.safe_dump(data, default_flow_style=False)
atomic_write(yaml_content, "./sample.yaml")
json_content = json.dumps(data, indent=4)
atomic_write(json_content, "./sample.json")
[
{
"item": "Food_eat",
"Food": {
"foodNo": 42536216,
"type": "fruit",
"moreInfo": [
"organic"
]
}
}
]
- Food:
foodNo: 42536216
moreInfo:
- organic
type: fruit
item: Food_eat
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment