-
Notifications
You must be signed in to change notification settings - Fork 13
/
path.py
412 lines (337 loc) · 12.4 KB
/
path.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# coding: utf-8
# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
# Distributed under the terms of "New BSD License", see the LICENSE file.
"""
Classes for representing the file system path in pyiron
"""
from copy import copy
import os
import posixpath
from pyiron_base.state import state
__author__ = "Jan Janssen, Joerg Neugebauer"
__copyright__ = (
"Copyright 2020, Max-Planck-Institut für Eisenforschung GmbH - "
"Computational Materials Design (CM) Department"
)
__version__ = "1.0"
__maintainer__ = "Jan Janssen"
__email__ = "janssen@mpie.de"
__status__ = "production"
__date__ = "Sep 1, 2017"
class GenericPath(object):
"""
Basic class to represent a project path in PyIron. A path consists of two parts, the root
part which defines directory path where the project repository is located (top_level_path)
and the project part which defines the relative path from the root_path to the project.
This class is meant for storing and accessing a path, not for moving around which is done by
the ProjectPath class.
Args:
root_path (str): absolute path name of the repository
project_path (str): relative path to the specific project
Attributes:
.. attribute:: root_path
the pyiron user directory, defined in the .pyiron configuration
.. attribute:: project_path
the relative path of the current project / folder starting from the root path
of the pyiron user directory
.. attribute:: path
the absolute path of the current project / folder
.. attribute:: base_name
the name of the current project / folder
Author: Jan Janssen
"""
def __init__(self, root_path, project_path):
self._root_path = None
self._project_path = None
self.root_path = root_path
self.project_path = project_path
@property
def root_path(self):
"""
the pyiron user directory, defined in the .pyiron configuration
Returns:
str: pyiron user directory of the current project
"""
return self._root_path
@root_path.setter
def root_path(self, new_path):
"""
the pyiron user directory, defined in the .pyiron configuration
Args:
new_path (str): new pyiron root path
"""
self._root_path = self._windows_path_to_unix_path(new_path)
@property
def project_path(self):
"""
the relative path of the current project / folder starting from the root path
of the pyiron user directory
Returns:
str: relative path of the current project / folder
"""
if self._project_path[-1] != "/":
self._project_path += "/"
return self._project_path
@project_path.setter
def project_path(self, new_path):
"""
the relative path of the current project / folder starting from the root path
of the pyiron user directory
Args:
new_path (str): new pyiron project path
"""
self._project_path = self._windows_path_to_unix_path(
posixpath.normpath(new_path)
)
@property
def path(self):
"""
The absolute path to of the current object.
Returns:
str: current project path
"""
if self.root_path is not None:
return posixpath.join(self.root_path, self.project_path)
else:
return self.project_path
@property
def base_name(self):
"""
The name of the current project folder
Returns:
str: name of the current project folder
"""
if self.project_path[-1] in ["/", "\\"]:
return self.project_path.split("/")[-2]
else:
return self.project_path.split("/")[-1]
def copy(self):
"""
Copy the GenericPath object
Returns:
GenericPath: independent GenericPath object pointing to the same project folder
"""
return copy(self)
def __copy__(self):
"""
Copy the GenericPath object
Returns:
GenericPath: independent GenericPath object pointing to the same project folder
"""
return GenericPath(self.root_path, self.project_path)
def __repr__(self):
"""
String representation of the GenericPath object
Returns:
str: string representation of the root and the project path
"""
project_str = "Project path: \n"
project_str += " root: " + str(self.root_path) + "\n"
project_str += " project: " + str(self.project_path) + "\n"
return project_str
def __str__(self):
"""
String representation of the GenericPath object
Returns:
str: string representation of the absolute path
"""
return self.path
@staticmethod
def _windows_path_to_unix_path(path):
"""
Helperfunction to covert windows path into unix path
Args:
path (str): input path in windows or unix format
Returns:
str: output path in unix format
"""
if path is not None:
linux_path = path.replace("\\", "/")
if linux_path[-1] != "/":
linux_path += "/"
return linux_path
else:
return None
class ProjectPath(GenericPath):
def __init__(self, path):
"""
Open a new or an existing project. The project is defined by providing a relative or an
absolute path. If no path is provided the current working directory is used. This is the
main class to walk inside the project structure, create new jobs, subprojects etc.
Note: Changing the path has no effect on the current working directory
Args:
path (GenericPath, str): path of the project defined by GenericPath, absolute or relative (with respect to
current working directory) path
.. attribute:: root_path
the pyiron user directory, defined in the .pyiron configuration
.. attribute:: project_path
the relative path of the current project / folder starting from the root path
of the pyiron user directory
.. attribute:: path
the absolute path of the current project / folder
.. attribute:: base_name
the name of the current project / folder
.. attribute:: history
previously opened projects / folders
"""
if path == "":
raise ValueError("ProjectPath: path is not allowed to be empty!")
generic_path = self._convert_str_to_generic_path(path)
super(ProjectPath, self).__init__(
generic_path.root_path, generic_path.project_path
)
self._history = []
@property
def history(self):
"""
The history of the previously opened paths
Returns:
list: list of previously opened relative paths
"""
return self._history
def open(self, rel_path, history=True):
"""
if rel_path exist set the project path to this directory
if not create it and go there
Args:
rel_path (str): path relative to the current project path
history (bool): By default pyiron stores a history of previously opened paths
Returns:
ProjectPath: New ProjectPath object pointing to the relative path
"""
new_project = self.copy()
new_project._create_path(new_project.path, rel_path)
new_project.project_path = os.path.normpath(
os.path.join(new_project.project_path, rel_path)
).replace("\\", "/")
if history:
new_project.history.append(rel_path)
return new_project
def close(self):
"""
return to the path before the last open if no history exists nothing happens
"""
if self.history:
path_lst = self.project_path.split("/")
hist_lst = self.history[-1].split("/")
self.project_path = "/".join(path_lst[: -len(hist_lst)])
del self.history[-1]
def copy(self):
"""
copy the path without the history, i.e., to going back with close is not possible
Returns:
ProjectPath:
"""
return ProjectPath(path=self.path)
def removedirs(self, project_name=None):
"""
equivalent to os.removedirs -> remove empty dirs
Args:
project_name (str): relative path to the project folder to be deleted
"""
try:
if project_name:
os.removedirs(os.path.join(self.path, project_name))
else:
os.removedirs(os.path.join(self.path))
except OSError:
pass
def listdir(self):
"""
equivalent to os.listdir
list all files and directories in this path
Returns:
list: list of folders and files in the current project path
"""
try:
return os.listdir(self.path)
except OSError:
return []
def walk(self):
"""
equivalent to os.listdir
list all files and directories in this path
Returns:
Generator: Directory tree generator.
"""
return os.walk(self.path)
def __repr__(self):
"""
String representation of the ProjectPath object
Returns:
str: string representation of the root and the project path including the path history
"""
project_str = super(ProjectPath, self).__repr__()
if len(self.history) > 0:
project_str += " history: " + str(self.history) + "\n"
return project_str
def __enter__(self):
"""
Helper function to support with calls
Returns:
ProjectPath: The object itself
"""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""
Helper function to support with calls
Args:
exc_type: Python internal
exc_val: Python internal
exc_tb: Python internal
"""
self.close()
def _convert_str_to_generic_path(self, path):
"""
Convert path in string representation to an GenericPath object. If argument is string and the given path does
not exist, create it.
Args:
path (str, GenericPath): absolute path, if GenericPath, returned unchanged
Returns:
GenericPath: GenericPath object pointing to the absolute path
"""
if isinstance(path, GenericPath):
return path
elif isinstance(path, str):
path = os.path.normpath(path)
if not os.path.isabs(path):
path_local = self._windows_path_to_unix_path(
posixpath.abspath(os.curdir)
)
self._create_path(path_local, path)
path = posixpath.join(path_local, path)
elif not os.path.exists(path):
self._create_path(path)
# else:
# raise ValueError(path, ' does not exist!')
path = self._windows_path_to_unix_path(path)
root_path, project_path = self._get_project_from_path(path)
return GenericPath(root_path, project_path)
else:
raise TypeError("Only string and GenericPath objects are supported.")
def _create_path(self, path, rel_path=None):
"""
Create the directory if it does not exist already using os.makedirs()
Args:
path (str): absolute path
rel_path (str): relative path starting from the absolute path (optional)
"""
if rel_path:
rel_path = self._windows_path_to_unix_path(rel_path)
path = posixpath.join(path, rel_path)
os.makedirs(path, exist_ok=True)
@staticmethod
def _get_project_from_path(full_path):
"""
Split the absolute path in root_path and project_path using the top_path function in Settings()
Args:
full_path (str): absolute path
Returns:
str, str: root_path, project_path
"""
root = state.database.top_path(full_path)
if root is not None:
pr_path = posixpath.relpath(full_path, root)
return root, pr_path
else:
return None, full_path