From 8bf64918e36044021b276e4aebf41aac1c76bee3 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Fri, 20 Apr 2018 21:19:38 +0800 Subject: [PATCH 1/3] Do not destroy lock early --- pipenv/core.py | 2 -- pipenv/project.py | 8 -------- 2 files changed, 10 deletions(-) diff --git a/pipenv/core.py b/pipenv/core.py index 9e761ff7..d8948b4c 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1000,8 +1000,6 @@ def do_lock( ) sys.exit(1) cached_lockfile = project.lockfile_content - if write: - project.destroy_lockfile() if write: # Alert the user of progress. click.echo( diff --git a/pipenv/project.py b/pipenv/project.py index 70e05006..ade9cd87 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -668,14 +668,6 @@ class Project(object): return found_source raise SourceNotFound(name or url) - def destroy_lockfile(self): - """Deletes the lockfile.""" - try: - return os.remove(self.lockfile_location) - - except OSError: - pass - def get_package_name_in_pipfile(self, package_name, dev=False): """Get the equivalent package name in pipfile""" key = 'dev-packages' if dev else 'packages' From bd057000b2ce4a1af8466f6f9745bf3ad9d49249 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 27 Apr 2018 15:13:23 +0800 Subject: [PATCH 2/3] Implement utility to write to lockfile atomically --- pipenv/core.py | 3 ++- pipenv/utils.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/pipenv/core.py b/pipenv/core.py index 423176e1..f6bebbcb 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -25,6 +25,7 @@ import six from .cmdparse import ScriptEmptyError from .project import Project, SourceNotFound from .utils import ( + atomic_open_for_write, convert_deps_from_pip, convert_deps_to_pip, is_required_version, @@ -1165,7 +1166,7 @@ def do_lock( ] if write: # Write out the lockfile. - with open(project.lockfile_location, 'w') as f: + with atomic_open_for_write(project.lockfile_location) as f: simplejson.dump( lockfile, f, indent=4, separators=(',', ': '), sort_keys=True ) diff --git a/pipenv/utils.py b/pipenv/utils.py index 10e3ae75..4e7fc028 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -1323,3 +1323,41 @@ def split_argument(req, short=None, long_=None): index, more_req = remaining_line[0], ' '.join(remaining_line[1:]) req = '{0} {1}'.format(req, more_req) return req, index + + +@contextmanager +def atomic_open_for_write(target, binary=False): + """Atomically open `target` for writing. + + This is based on Lektor's `atomic_open()` utility, but simplified a lot + to handle only writing, and skip many multi-process/thread edge cases + handled by Werkzeug. + + How this works: + + * Create a temp file (in the same directory of the actual target), and + yield for surrounding code to write to it. + * If some thing goes wrong, try to remove the temp file. The actual target + is not touched whatsoever. + * If everything goes well, close the temp file, and replace the actual + target with this new file. + """ + fd, tmp = tempfile.mkstemp( + dir=os.path.dirname(target), + prefix='.__atomic-write', + ) + os.chmod(tmp, 0o644) + f = os.fdopen(fd, 'wb' if binary else 'w') + try: + yield f + except BaseException: + f.close() + try: + os.remove(tmp) + except OSError: + pass + raise + else: + f.close() + os.remove(target) # This is needed on Windows. + os.rename(tmp, target) # No os.replace() on Python 2. From 0944eb56d84ee1060a8e8b8292f36839d761c66c Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 27 Apr 2018 15:31:46 +0800 Subject: [PATCH 3/3] Ignore errors when removing old file --- pipenv/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pipenv/utils.py b/pipenv/utils.py index 4e7fc028..2c815b6f 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -1359,5 +1359,8 @@ def atomic_open_for_write(target, binary=False): raise else: f.close() - os.remove(target) # This is needed on Windows. + try: + os.remove(target) # This is needed on Windows. + except OSError: + pass os.rename(tmp, target) # No os.replace() on Python 2.