-
-
Notifications
You must be signed in to change notification settings - Fork 241
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle errors when saving files gracefully #120
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,21 @@ | ||
import itertools | ||
import datetime | ||
import itertools | ||
import json | ||
import os | ||
import tempfile | ||
|
||
import click | ||
import arrow | ||
|
||
from click.exceptions import UsageError | ||
|
||
|
||
try: | ||
text_type = (str, unicode) | ||
except NameError: | ||
text_type = str | ||
|
||
|
||
def style(name, element): | ||
def _style_tags(tags): | ||
if not tags: | ||
|
@@ -138,3 +147,53 @@ def get_start_time_for_period(period): | |
raise ValueError('Unsupported period value: {}'.format(period)) | ||
|
||
return start_time | ||
|
||
|
||
def make_json_writer(func, *args, **kwargs): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't get it, why expecting a function and not an object? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to handle the two cases of I wanted to avoid having to store the data, which the function writes, in memory, so instead of passing the data to |
||
""" | ||
Return a function that receives a file-like object and writes the return | ||
value of func(*args, **kwargs) as JSON to it. | ||
""" | ||
def writer(f): | ||
json.dump(func(*args, **kwargs), f, indent=1, ensure_ascii=False) | ||
return writer | ||
|
||
|
||
def safe_save(path, content, ext='.bak'): | ||
""" | ||
Save given content to file at given path safely. | ||
|
||
`content` may either be a (unicode) string to write to the file, or a | ||
function taking one argument, a file object opened for writing. The | ||
function may write (unicode) strings to the file object (but doesn't need | ||
to close it). | ||
|
||
The file to write to is created at a temporary location first. If there is | ||
an error creating or writing to the temp file or calling `content`, the | ||
destination file is left untouched. Otherwise, if all is well, an existing | ||
destination file is backed up to `path` + `ext` (defaults to '.bak') and | ||
the temporary file moved into its place. | ||
|
||
""" | ||
tmpfp = tempfile.NamedTemporaryFile(mode='w+', delete=False) | ||
try: | ||
with tmpfp: | ||
if isinstance(content, text_type): | ||
tmpfp.write(content) | ||
else: | ||
content(tmpfp) | ||
except: | ||
try: | ||
os.unlink(tmpfp.name) | ||
except (IOError, OSError): | ||
pass | ||
raise | ||
else: | ||
if os.path.exists(path): | ||
try: | ||
os.unlink(path + ext) | ||
except OSError: | ||
pass | ||
os.rename(path, path + ext) | ||
|
||
os.rename(tmpfp.name, path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍,
io.StringIO
doesn't have the right behavior in Python 2.7. This is very confusing.