Skip to content

Commit 095464d

Browse files
authored
Merge pull request #3093 from iterative/move-fs-utilities
Move fs utilities to fs.py from __init__.py
2 parents 238b4e8 + 74850fe commit 095464d

File tree

17 files changed

+154
-151
lines changed

17 files changed

+154
-151
lines changed

dvc/analytics.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
from dvc.lock import Lock, LockError
1717
from dvc.repo import Repo
1818
from dvc.scm import SCM
19-
from dvc.utils import env2bool, is_binary, makedirs
19+
from dvc.utils import env2bool, is_binary
20+
from dvc.utils.fs import makedirs
2021

2122

2223
logger = logging.getLogger(__name__)

dvc/remote/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
from dvc.progress import Tqdm
2525
from dvc.remote.slow_link_detection import slow_link_guard
2626
from dvc.state import StateNoop
27-
from dvc.utils import makedirs, relpath, tmp_fname
28-
from dvc.utils.fs import move
27+
from dvc.utils import relpath, tmp_fname
28+
from dvc.utils.fs import move, makedirs
2929
from dvc.utils.http import open_url
3030

3131
logger = logging.getLogger(__name__)

dvc/remote/local.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,13 @@
2121
from dvc.scheme import Schemes
2222
from dvc.scm.tree import is_working_tree
2323
from dvc.system import System
24-
from dvc.utils import copyfile
24+
from dvc.utils.fs import copyfile
2525
from dvc.utils import file_md5
26-
from dvc.utils import makedirs
2726
from dvc.utils import relpath
2827
from dvc.utils import tmp_fname
29-
from dvc.utils import walk_files
3028
from dvc.compat import fspath_py35
31-
from dvc.utils.fs import move
32-
from dvc.utils.fs import remove
29+
from dvc.utils.fs import move, makedirs, remove
30+
from dvc.utils.fs import walk_files
3331

3432
logger = logging.getLogger(__name__)
3533

dvc/repo/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def __init__(self, root_dir=None):
7272
from dvc.repo.metrics import Metrics
7373
from dvc.scm.tree import WorkingTree
7474
from dvc.repo.tag import Tag
75-
from dvc.utils import makedirs
75+
from dvc.utils.fs import makedirs
7676

7777
root_dir = self.find_root(root_dir)
7878

dvc/utils/__init__.py

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -105,53 +105,6 @@ def dict_md5(d, exclude=()):
105105
return bytes_md5(byts)
106106

107107

108-
def copyfile(src, dest, no_progress_bar=False, name=None):
109-
"""Copy file with progress bar"""
110-
from dvc.exceptions import DvcException
111-
from dvc.progress import Tqdm
112-
from dvc.system import System
113-
114-
src = fspath_py35(src)
115-
dest = fspath_py35(dest)
116-
117-
name = name if name else os.path.basename(dest)
118-
total = os.stat(src).st_size
119-
120-
if os.path.isdir(dest):
121-
dest = os.path.join(dest, os.path.basename(src))
122-
123-
try:
124-
System.reflink(src, dest)
125-
except DvcException:
126-
with Tqdm(
127-
desc=name, disable=no_progress_bar, total=total, bytes=True
128-
) as pbar:
129-
with open(src, "rb") as fsrc, open(dest, "wb+") as fdest:
130-
while True:
131-
buf = fsrc.read(LOCAL_CHUNK_SIZE)
132-
if not buf:
133-
break
134-
fdest.write(buf)
135-
pbar.update(len(buf))
136-
137-
138-
def makedirs(path, exist_ok=False, mode=None):
139-
path = fspath_py35(path)
140-
141-
if mode is None:
142-
os.makedirs(path, exist_ok=exist_ok)
143-
return
144-
145-
# utilize umask to set proper permissions since Python 3.7 the `mode`
146-
# `makedirs` argument no longer affects the file permission bits of
147-
# newly-created intermediate-level directories.
148-
umask = os.umask(0o777 - mode)
149-
try:
150-
os.makedirs(path, exist_ok=exist_ok)
151-
finally:
152-
os.umask(umask)
153-
154-
155108
def _split(list_to_split, chunk_size):
156109
return [
157110
list_to_split[i : i + chunk_size]
@@ -278,12 +231,6 @@ def to_yaml_string(data):
278231
return stream.getvalue()
279232

280233

281-
def walk_files(directory):
282-
for root, _, files in os.walk(fspath(directory)):
283-
for f in files:
284-
yield os.path.join(root, f)
285-
286-
287234
def colorize(message, color=None):
288235
"""Returns a message in a specified color."""
289236
if not color:

dvc/utils/fs.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
logger = logging.getLogger(__name__)
2020

21+
LOCAL_CHUNK_SIZE = 2 ** 20 # 1 MB
22+
2123

2224
def fs_copy(src, dst):
2325
if os.path.isdir(src):
@@ -152,3 +154,56 @@ def normalize_path(path):
152154
parent = os.path.join(normalize_path(parent), "")
153155
child = normalize_path(child)
154156
return child != parent and child.startswith(parent)
157+
158+
159+
def makedirs(path, exist_ok=False, mode=None):
160+
path = fspath_py35(path)
161+
162+
if mode is None:
163+
os.makedirs(path, exist_ok=exist_ok)
164+
return
165+
166+
# utilize umask to set proper permissions since Python 3.7 the `mode`
167+
# `makedirs` argument no longer affects the file permission bits of
168+
# newly-created intermediate-level directories.
169+
umask = os.umask(0o777 - mode)
170+
try:
171+
os.makedirs(path, exist_ok=exist_ok)
172+
finally:
173+
os.umask(umask)
174+
175+
176+
def copyfile(src, dest, no_progress_bar=False, name=None):
177+
"""Copy file with progress bar"""
178+
from dvc.exceptions import DvcException
179+
from dvc.progress import Tqdm
180+
from dvc.system import System
181+
182+
src = fspath_py35(src)
183+
dest = fspath_py35(dest)
184+
185+
name = name if name else os.path.basename(dest)
186+
total = os.stat(src).st_size
187+
188+
if os.path.isdir(dest):
189+
dest = os.path.join(dest, os.path.basename(src))
190+
191+
try:
192+
System.reflink(src, dest)
193+
except DvcException:
194+
with Tqdm(
195+
desc=name, disable=no_progress_bar, total=total, bytes=True
196+
) as pbar:
197+
with open(src, "rb") as fsrc, open(dest, "wb+") as fdest:
198+
while True:
199+
buf = fsrc.read(LOCAL_CHUNK_SIZE)
200+
if not buf:
201+
break
202+
fdest.write(buf)
203+
pbar.update(len(buf))
204+
205+
206+
def walk_files(directory):
207+
for root, _, files in os.walk(fspath(directory)):
208+
for f in files:
209+
yield os.path.join(root, f)

tests/dir_helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
import pytest
5151
from funcy.py3 import lmap, retry
5252

53-
from dvc.utils import makedirs
53+
from dvc.utils.fs import makedirs
5454
from dvc.compat import fspath, fspath_py35
5555

5656

tests/func/test_checkout.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
from dvc.stage import StageFileBadNameError
1919
from dvc.stage import StageFileDoesNotExistError
2020
from dvc.system import System
21-
from dvc.utils import relpath, walk_files
21+
from dvc.utils import relpath
22+
from dvc.utils.fs import walk_files
2223
from dvc.utils.stage import dump_stage_file
2324
from dvc.utils.stage import load_stage_file
2425
from tests.basic_env import TestDvc

tests/func/test_fs.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import os
2+
import stat
3+
4+
import pytest
5+
6+
from dvc.utils.fs import makedirs, copyfile
7+
8+
9+
@pytest.mark.skipif(os.name == "nt", reason="Not supported for Windows.")
10+
def test_makedirs_permissions(tmp_dir):
11+
dir_mode = 0o755
12+
intermediate_dir = "тестовая-директория"
13+
test_dir = os.path.join(intermediate_dir, "data")
14+
15+
assert not os.path.exists(intermediate_dir)
16+
17+
makedirs(test_dir, mode=dir_mode)
18+
19+
assert stat.S_IMODE(os.stat(test_dir).st_mode) == dir_mode
20+
assert stat.S_IMODE(os.stat(intermediate_dir).st_mode) == dir_mode
21+
22+
23+
def test_copyfile(tmp_dir):
24+
src = "file1"
25+
dest = "file2"
26+
dest_dir = "testdir"
27+
28+
tmp_dir.gen(src, "file1contents")
29+
30+
os.mkdir(dest_dir)
31+
32+
copyfile(src, dest)
33+
assert (tmp_dir / dest).read_text() == "file1contents"
34+
35+
copyfile(src, dest_dir)
36+
assert (tmp_dir / dest_dir / src).read_text() == "file1contents"

tests/func/test_get.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from dvc.repo.get import GetDVCFileError, PathMissingError
1010
from dvc.repo import Repo
1111
from dvc.system import System
12-
from dvc.utils import makedirs
12+
from dvc.utils.fs import makedirs
1313
from dvc.compat import fspath
1414
from tests.utils import trees_equal
1515

0 commit comments

Comments
 (0)