Projet

Général

Profil

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

Benjamin Dauvergne, 17 avril 2019 12:06

Télécharger (3,62 ko)

Voir les différences:

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

 passerelle/utils/files.py | 42 +++++++++++++++++++++++++++++++++++++++
 tests/test_utils_files.py | 38 +++++++++++++++++++++++++++++++++++
 2 files changed, 80 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.path
17
import contextlib
18
import tempfile
19

  
20

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

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

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

  
32
    target_dir = kwargs.get('dir') or os.path.dirname(filepath)
33
    fd = tempfile.NamedTemporaryFile(dir=target_dir, delete=False)
34
    try:
35
        with fd:
36
            yield fd
37
            fd.flush()
38
            os.fsync(fd.fileno())
39
        os.rename(fd.name, filepath)
40
    except Exception:
41
        os.unlink(fd.name)
42
        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

  
18
import pytest
19

  
20
from passerelle.utils.files import atomic_write
21

  
22

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

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

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

  
36
    assert os.path.exists(filepath)
37
    with open(filepath) as fd:
38
        assert fd.read() == 'coucou'
0
-