mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
Merge pull request #2077 from pypa/atomic-lockfile-overwrite
Atomic lockfile overwrite
This commit is contained in:
+2
-3
@@ -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,
|
||||
@@ -1005,8 +1006,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(
|
||||
@@ -1167,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
|
||||
)
|
||||
|
||||
@@ -673,14 +673,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'
|
||||
|
||||
@@ -1323,3 +1323,44 @@ 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()
|
||||
try:
|
||||
os.remove(target) # This is needed on Windows.
|
||||
except OSError:
|
||||
pass
|
||||
os.rename(tmp, target) # No os.replace() on Python 2.
|
||||
|
||||
Reference in New Issue
Block a user