16
16
# See the License for the specific language governing permissions and
17
17
# limitations under the License.
18
18
"""Client for handling a data storage."""
19
-
19
+ import functools
20
20
import shlex
21
21
from collections import defaultdict
22
+ from shutil import which
22
23
from subprocess import PIPE , STDOUT , call , run
23
24
24
25
import attr
26
+ from werkzeug .utils import cached_property
25
27
26
28
from renku import errors
27
29
from renku ._compat import Path
28
30
29
31
from ._git import _expand_directories
30
32
from .repository import RepositoryApiMixin
31
33
32
- HAS_LFS = call (['git' , 'lfs' ], stdout = PIPE , stderr = STDOUT ) == 0
33
-
34
34
# Batch size for when renku is expanding a large list
35
35
# of files into an argument string.
36
36
ARGUMENT_BATCH_SIZE = 100
37
37
38
38
39
+ def ensure_external_storage (fn ):
40
+ """Ensure management of external storage on methods which depend on it.
41
+
42
+ :raises: ``errors.ExternalStorageNotInstalled``
43
+ :raises: ``errors.ExternalStorageDisabled``
44
+ """
45
+ # noqa
46
+ @functools .wraps (fn )
47
+ def wrapper (self , * args , ** kwargs ):
48
+ if not self .has_external_storage :
49
+ pass
50
+ else :
51
+ return fn (self , * args , ** kwargs )
52
+
53
+ return wrapper
54
+
55
+
39
56
@attr .s
40
57
class StorageApiMixin (RepositoryApiMixin ):
41
58
"""Client for handling a data storage."""
@@ -53,6 +70,30 @@ class StorageApiMixin(RepositoryApiMixin):
53
70
54
71
_CMD_STORAGE_PULL = ['git' , 'lfs' , 'pull' , '-I' ]
55
72
73
+ @cached_property
74
+ def storage_installed (self ):
75
+ """Verify that git-lfs is installed and on system PATH."""
76
+ return bool (which ('git-lfs' ))
77
+
78
+ @cached_property
79
+ def has_external_storage (self ):
80
+ """Check if repository has external storage enabled.
81
+
82
+ :raises: ``errors.ExternalStorageNotInstalled``
83
+ :raises: ``errors.ExternalStorageDisabled``
84
+ """
85
+ repo_config = self .repo .config_reader (config_level = 'repository' )
86
+ lfs_enabled = repo_config .has_section ('filter "lfs"' )
87
+
88
+ storage_enabled = lfs_enabled and self .storage_installed
89
+ if self .use_external_storage and not storage_enabled :
90
+ raise errors .ExternalStorageDisabled (self .repo )
91
+
92
+ if lfs_enabled and not self .storage_installed :
93
+ raise errors .ExternalStorageNotInstalled (self .repo )
94
+
95
+ return lfs_enabled and self .storage_installed
96
+
56
97
def init_external_storage (self , force = False ):
57
98
"""Initialize the external storage for data."""
58
99
call (
@@ -62,19 +103,20 @@ def init_external_storage(self, force=False):
62
103
cwd = str (self .path .absolute ()),
63
104
)
64
105
65
- @property
66
- def external_storage_installed (self ):
67
- """Check that Large File Storage is installed."""
68
- return HAS_LFS
106
+ def init_repository (self , name = None , force = False ):
107
+ """Initialize a local Renku repository."""
108
+ result = super ().init_repository (name = name , force = force )
69
109
70
- def track_paths_in_storage (self , * paths ):
71
- """Track paths in the external storage."""
72
- if not self ._use_lfs ():
73
- return
110
+ # initialize LFS if it is requested and installed
111
+ if self .use_external_storage and self .storage_installed :
112
+ self .init_external_storage (force = force )
74
113
75
- if not self .external_storage_installed :
76
- raise errors .ExternalStorageNotInstalled (self .repo )
114
+ return result
77
115
116
+ @ensure_external_storage
117
+ def track_paths_in_storage (self , * paths ):
118
+ """Track paths in the external storage."""
119
+ # Calculate which paths can be tracked in lfs
78
120
track_paths = []
79
121
attrs = self .find_attr (* paths )
80
122
@@ -97,31 +139,20 @@ def track_paths_in_storage(self, *paths):
97
139
cwd = str (self .path ),
98
140
)
99
141
142
+ @ensure_external_storage
100
143
def untrack_paths_from_storage (self , * paths ):
101
144
"""Untrack paths from the external storage."""
102
- if not self ._use_lfs ():
103
- return
104
-
105
- if not self .external_storage_installed :
106
- raise errors .ExternalStorageNotInstalled (self .repo )
107
-
108
145
call (
109
146
self ._CMD_STORAGE_UNTRACK + list (paths ),
110
147
stdout = PIPE ,
111
148
stderr = STDOUT ,
112
149
cwd = str (self .path ),
113
150
)
114
151
152
+ @ensure_external_storage
115
153
def pull_paths_from_storage (self , * paths ):
116
154
"""Pull paths from LFS."""
117
155
import math
118
-
119
- if not self ._use_lfs ():
120
- return
121
-
122
- if not self .external_storage_installed :
123
- raise errors .ExternalStorageNotInstalled (self .repo )
124
-
125
156
client_dict = defaultdict (list )
126
157
127
158
for path in _expand_directories (paths ):
@@ -131,13 +162,14 @@ def pull_paths_from_storage(self, *paths):
131
162
client_dict [client .path ].append (str (path ))
132
163
133
164
for client_path , paths in client_dict .items ():
134
- for ibatch in range (math .ceil (len (paths ) / ARGUMENT_BATCH_SIZE )):
165
+ batch_size = math .ceil (len (paths ) / ARGUMENT_BATCH_SIZE )
166
+ for index in range (batch_size ):
135
167
run (
136
168
self ._CMD_STORAGE_PULL + [
137
169
shlex .quote (
138
170
',' .join (
139
- paths [ibatch * ARGUMENT_BATCH_SIZE :
140
- ( ibatch + 1 ) * ARGUMENT_BATCH_SIZE ]
171
+ paths [index * ARGUMENT_BATCH_SIZE :( index + 1 ) *
172
+ ARGUMENT_BATCH_SIZE ]
141
173
)
142
174
)
143
175
],
@@ -146,35 +178,13 @@ def pull_paths_from_storage(self, *paths):
146
178
stderr = STDOUT ,
147
179
)
148
180
181
+ @ensure_external_storage
149
182
def checkout_paths_from_storage (self , * paths ):
150
183
"""Checkout a paths from LFS."""
151
- if not self ._use_lfs ():
152
- return
153
-
154
- if not self .external_storage_installed :
155
- raise errors .ExternalStorageNotInstalled (self .repo )
156
-
157
184
run (
158
185
self ._CMD_STORAGE_CHECKOUT + list (paths ),
159
186
cwd = str (self .path .absolute ()),
160
187
stdout = PIPE ,
161
188
stderr = STDOUT ,
162
189
check = True ,
163
190
)
164
-
165
- def init_repository (self , name = None , force = False ):
166
- """Initialize a local Renku repository."""
167
- result = super ().init_repository (name = name , force = force )
168
-
169
- # initialize LFS if it is requested and installed
170
- if self .use_external_storage and self .external_storage_installed :
171
- self .init_external_storage (force = force )
172
-
173
- return result
174
-
175
- def _use_lfs (self ):
176
- renku_initialized_to_use_lfs = self .repo .config_reader (
177
- config_level = 'repository'
178
- ).has_section ('filter "lfs"' )
179
-
180
- return renku_initialized_to_use_lfs and self .use_external_storage
0 commit comments