Projet

Général

Profil

0001-utils-add-an-atomic_write-context-manager-32413.patch

Serghei Mihai (congés, retour 15/05), 17 avril 2019 18:08

Télécharger (4,23 ko)

Voir les différences:

Subject: [PATCH 1/3] utils: add an atomic_write() context manager (#32413)

 passerelle/utils/files.py | 49 +++++++++++++++++++++++++++++++++++++
 tests/test_utils_files.py | 51 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 100 insertions(+)
 create mode 100644 passerelle/utils/files.py
 create mode 100644 tests/test_utils_files.py
passerelle/utils/files.py
1
# Copyright (C) 2018  Entr'ouvert
2
#
3
# This program is free software: you can redistribute it and/or modify it
4
# under the terms of the GNU Affero General Public License as published
5
# by the Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU Affero General Public License for more details.
12
#
13
# You should have received a copy of the GNU Affero General Public License
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15

  
16
import os
17
import contextlib
18
import tempfile
19
import stat
20

  
21

  
22
@contextlib.contextmanager
23
def atomic_write(filepath, **kwargs):
24
    '''Return a file descriptor to a temporary file using NamedTemporaryFile
25
       which will be atomically renamed to filepath if possible.
26

  
27
       Atomic renaming is only possible on the same filesystem, so the
28
       temporary file will be created in the same directory as the target file
29

  
30
       You can pass any possible argument to NamedTemporaryFile with kwargs.
31
    '''
32

  
33
    target_dir = kwargs.get('dir') or os.path.dirname(filepath)
34
    if not os.path.exists(target_dir):
35
        os.makedirs(target_dir)
36

  
37
    fd = tempfile.NamedTemporaryFile(dir=target_dir, delete=False)
38

  
39
    try:
40
        with fd:
41
            yield fd
42
            fd.flush()
43
            os.fsync(fd.fileno())
44
        os.rename(fd.name, filepath)
45
        # set read only permission for owner and group
46
        os.chmod(filepath, stat.S_IRUSR|stat.S_IRGRP)
47
    except Exception:
48
        os.unlink(fd.name)
49
        raise
tests/test_utils_files.py
1
# Copyright (C) 2018  Entr'ouvert
2
#
3
# This program is free software: you can redistribute it and/or modify it
4
# under the terms of the GNU Affero General Public License as published
5
# by the Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU Affero General Public License for more details.
12
#
13
# You should have received a copy of the GNU Affero General Public License
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15

  
16
import os
17
import stat
18

  
19
import pytest
20

  
21
from passerelle.utils.files import atomic_write
22

  
23

  
24
def test_atomic_write(tmpdir):
25
    filepath = str(tmpdir.join('test'))
26

  
27
    with pytest.raises(Exception):
28
        with atomic_write(filepath) as fd:
29
            fd.write('coucou')
30
            raise Exception()
31
    assert not os.path.exists(filepath)
32
    assert os.listdir(str(tmpdir)) == []
33

  
34
    with atomic_write(filepath) as fd:
35
        fd.write('coucou')
36

  
37
    assert os.path.exists(filepath)
38
    # make sure only owner and group can read file
39
    assert bool(os.stat(filepath).st_mode & stat.S_IRUSR)
40
    assert bool(os.stat(filepath).st_mode & stat.S_IRGRP)
41
    with open(filepath) as fd:
42
        assert fd.read() == 'coucou'
43

  
44

  
45
def test_atomic_write_in_subdirectory(tmpdir):
46
    filepath = str(tmpdir.join('subdir', 'test1'))
47
    with atomic_write(filepath) as fd:
48
        fd.write('coucou')
49

  
50
    with open(filepath) as fd:
51
        assert fd.read() == 'coucou'
0
-