Skip to content
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

WIP: Set permissions for atomic open_file() #1382

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ Unreleased
- Remove unused compat shim for ``bytes``. (`#1195`_)
- Expand unit testing around termui, especially getchar (Windows). (`#1116`_)
- Fix output on Windows Python 2.7 built with MSVC 14. #1342
- ``open_file`` with ``atomic=True`` retains permissions of existing files and
respects the current umask for new files. (`#1376`_)

.. _#1167: https://github.com/pallets/click/pull/1167
.. _#1151: https://github.com/pallets/click/pull/1151
.. _#1185: https://github.com/pallets/click/issues/1185
.. _#1195: https://github.com/pallets/click/pull/1195
.. _#1116: https://github.com/pallets/click/issues/1116
.. _#1376: https://github.com/pallets/click/issues/1376

Version 7.0
-----------
Expand Down
32 changes: 27 additions & 5 deletions click/_compat.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re
import io
import os
import stat
import sys
import codecs
from weakref import WeakKeyDictionary
Expand Down Expand Up @@ -503,16 +504,34 @@ def open_stream(filename, mode='r', encoding=None, errors='strict',
# as a proxy in the same folder and then using the fdopen
# functionality to wrap it in a Python file. Then we wrap it in an
# atomic file that moves the file over on close.
import tempfile
fd, tmp_filename = tempfile.mkstemp(dir=os.path.dirname(filename),
prefix='.__atomic-write')
#
# The vendored tempfile modules are modified to respect the current umask when
# creating the file.
if PY2:
from . import _tempfile27 as _tempfile
else:
from . import _tempfile36 as _tempfile
fd, tmp_filename = _tempfile.mkstemp(dir=os.path.dirname(filename),
prefix='.__atomic-write')

if encoding is not None:
f = io.open(fd, mode, encoding=encoding, errors=errors)
else:
f = os.fdopen(fd, mode)

return _AtomicFile(f, tmp_filename, os.path.realpath(filename)), True
real_filename = os.path.realpath(filename)

# Get permissions to set on the target file. If the target file already
# exists, retain its current permissions. If the target file will be
# created, respect the permissions from the current umask.
permissions = None
if not WIN:
try:
permissions = stat.S_IMODE(os.stat(real_filename).st_mode)
except (OSError, IOError):
pass

return _AtomicFile(f, tmp_filename, real_filename, permissions), True


# Used in a destructor call, needs extra protection from interpreter cleanup.
Expand All @@ -526,10 +545,11 @@ def open_stream(filename, mode='r', encoding=None, errors='strict',

class _AtomicFile(object):

def __init__(self, f, tmp_filename, real_filename):
def __init__(self, f, tmp_filename, real_filename, permissions):
self._f = f
self._tmp_filename = tmp_filename
self._real_filename = real_filename
self._permissions = permissions
self.closed = False

@property
Expand All @@ -546,6 +566,8 @@ def close(self, delete=False):
except OSError:
pass
_replace(self._tmp_filename, self._real_filename)
if self._permissions is not None:
os.chmod(self._real_filename, self._permissions)
self.closed = True

def __getattr__(self, name):
Expand Down
Loading