pybiolib 1.1.1979__py3-none-any.whl → 1.1.1986__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- biolib/_internal/fuse_mount/experiment_fuse_mount.py +73 -65
- biolib/experiments/experiment.py +2 -2
- {pybiolib-1.1.1979.dist-info → pybiolib-1.1.1986.dist-info}/METADATA +1 -1
- {pybiolib-1.1.1979.dist-info → pybiolib-1.1.1986.dist-info}/RECORD +7 -7
- {pybiolib-1.1.1979.dist-info → pybiolib-1.1.1986.dist-info}/LICENSE +0 -0
- {pybiolib-1.1.1979.dist-info → pybiolib-1.1.1986.dist-info}/WHEEL +0 -0
- {pybiolib-1.1.1979.dist-info → pybiolib-1.1.1986.dist-info}/entry_points.txt +0 -0
@@ -3,12 +3,11 @@ import os
|
|
3
3
|
import stat
|
4
4
|
from datetime import datetime, timezone
|
5
5
|
from time import time
|
6
|
-
from typing import Iterable
|
7
6
|
|
8
7
|
from biolib._internal.libs.fusepy import FUSE, FuseOSError, Operations
|
9
8
|
from biolib.biolib_errors import BioLibError
|
10
9
|
from biolib.jobs import Job
|
11
|
-
from biolib.typing_utils import Dict, Optional, Tuple, TypedDict
|
10
|
+
from biolib.typing_utils import Dict, List, Optional, Tuple, TypedDict
|
12
11
|
|
13
12
|
|
14
13
|
class _AttributeDict(TypedDict):
|
@@ -27,94 +26,96 @@ class ExperimentFuseMount(Operations):
|
|
27
26
|
self._experiment = experiment
|
28
27
|
self._job_names_map: Optional[Dict[str, Job]] = None
|
29
28
|
self._jobs_last_fetched_at: float = 0.0
|
30
|
-
self._root_path: str = '/'
|
31
29
|
self._mounted_at_epoch_seconds: int = int(time())
|
32
30
|
|
33
31
|
@staticmethod
|
34
|
-
def
|
32
|
+
def mount_experiment(experiment, mount_path: str) -> None:
|
35
33
|
FUSE(
|
36
34
|
operations=ExperimentFuseMount(experiment),
|
37
|
-
mountpoint=
|
35
|
+
mountpoint=mount_path,
|
38
36
|
nothreads=True,
|
39
37
|
foreground=True,
|
40
38
|
allow_other=False,
|
41
39
|
)
|
42
40
|
|
43
41
|
def getattr(self, path: str, fh=None) -> _AttributeDict:
|
44
|
-
|
45
|
-
|
46
|
-
# return folder dir
|
47
|
-
return self._get_folder_attr(timestamp_epoch_seconds=self._mounted_at_epoch_seconds)
|
48
|
-
|
49
|
-
job_name, job_path = self._parse_path(path)
|
50
|
-
job = self._get_job_names_map().get(job_name)
|
51
|
-
if not job:
|
52
|
-
# job not found
|
53
|
-
raise FuseOSError(errno.ENOENT)
|
42
|
+
if path == '/':
|
43
|
+
return self._get_directory_attributes(timestamp_epoch_seconds=self._mounted_at_epoch_seconds)
|
54
44
|
|
45
|
+
job, path_in_job = self._parse_path(path)
|
55
46
|
job_finished_at_epoch_seconds: int = int(
|
56
47
|
datetime.fromisoformat(job.to_dict()['finished_at'].rstrip('Z')).replace(tzinfo=timezone.utc).timestamp()
|
57
48
|
)
|
58
|
-
|
59
|
-
|
60
|
-
return self.
|
49
|
+
|
50
|
+
if path_in_job == '/':
|
51
|
+
return self._get_directory_attributes(timestamp_epoch_seconds=job_finished_at_epoch_seconds)
|
61
52
|
|
62
53
|
try:
|
63
|
-
file = job.get_output_file(
|
54
|
+
file = job.get_output_file(path_in_job)
|
55
|
+
return self._get_file_attributes(
|
56
|
+
timestamp_epoch_seconds=job_finished_at_epoch_seconds,
|
57
|
+
size_in_bytes=file.length,
|
58
|
+
)
|
64
59
|
except BioLibError:
|
65
60
|
# file not found
|
66
|
-
|
61
|
+
pass
|
67
62
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
st_size=file.length,
|
76
|
-
st_uid=os.getuid(),
|
77
|
-
)
|
63
|
+
file_paths_in_job = [file.path for file in job.list_output_files()]
|
64
|
+
|
65
|
+
for file_path_in_job in file_paths_in_job:
|
66
|
+
if file_path_in_job.startswith(path_in_job):
|
67
|
+
return self._get_directory_attributes(timestamp_epoch_seconds=job_finished_at_epoch_seconds)
|
68
|
+
|
69
|
+
raise FuseOSError(errno.ENOENT) from None # No such file or directory
|
78
70
|
|
79
|
-
def readdir(self, path: str, fh: int) ->
|
71
|
+
def readdir(self, path: str, fh: int) -> List[str]:
|
80
72
|
directory_entries = ['.', '..']
|
73
|
+
|
81
74
|
if path == '/':
|
82
|
-
|
83
|
-
directory_entries.append(name)
|
75
|
+
directory_entries.extend(self._get_job_names_map(refresh_jobs=True).keys())
|
84
76
|
else:
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
77
|
+
job, path_in_job = self._parse_path(path)
|
78
|
+
dir_path_in_job = '/' if path_in_job == '/' else path_in_job + '/'
|
79
|
+
depth = dir_path_in_job.count('/')
|
80
|
+
directory_entries.extend(
|
81
|
+
set(
|
82
|
+
[
|
83
|
+
file.path.split('/')[depth]
|
84
|
+
for file in job.list_output_files()
|
85
|
+
if file.path.startswith(dir_path_in_job)
|
86
|
+
]
|
87
|
+
)
|
89
88
|
)
|
90
89
|
|
91
|
-
|
92
|
-
directory_entries.append(key)
|
93
|
-
|
94
|
-
yield from directory_entries
|
90
|
+
return directory_entries
|
95
91
|
|
96
92
|
def open(self, path: str, flags: int) -> int:
|
97
|
-
|
98
|
-
job = self._get_job_names_map()[job_name]
|
93
|
+
job, path_in_job = self._parse_path(path)
|
99
94
|
try:
|
100
|
-
job.get_output_file(
|
101
|
-
return 0 # return dummy file handle
|
95
|
+
job.get_output_file(path_in_job)
|
102
96
|
except BioLibError:
|
103
97
|
# file not found
|
104
98
|
raise FuseOSError(errno.ENOENT) from None
|
105
99
|
|
100
|
+
return 0 # return dummy file handle
|
101
|
+
|
106
102
|
def read(self, path: str, size: int, offset: int, fh: int) -> bytes:
|
107
|
-
|
108
|
-
job = self._get_job_names_map()[job_name]
|
103
|
+
job, path_in_job = self._parse_path(path)
|
109
104
|
try:
|
110
|
-
file = job.get_output_file(
|
105
|
+
file = job.get_output_file(path_in_job)
|
111
106
|
except BioLibError:
|
112
|
-
# file
|
113
|
-
raise FuseOSError(errno.ENOENT) from None
|
107
|
+
raise FuseOSError(errno.ENOENT) from None # No such file or directory
|
114
108
|
|
115
109
|
return file.get_data(start=offset, length=size)
|
116
110
|
|
117
|
-
def
|
111
|
+
def release(self, path: str, fh: int) -> int:
|
112
|
+
return 0
|
113
|
+
|
114
|
+
def releasedir(self, path: str, fh: int) -> int:
|
115
|
+
return 0
|
116
|
+
|
117
|
+
@staticmethod
|
118
|
+
def _get_directory_attributes(timestamp_epoch_seconds: int) -> _AttributeDict:
|
118
119
|
return _AttributeDict(
|
119
120
|
st_atime=timestamp_epoch_seconds,
|
120
121
|
st_ctime=timestamp_epoch_seconds,
|
@@ -126,6 +127,19 @@ class ExperimentFuseMount(Operations):
|
|
126
127
|
st_uid=os.getuid(),
|
127
128
|
)
|
128
129
|
|
130
|
+
@staticmethod
|
131
|
+
def _get_file_attributes(timestamp_epoch_seconds: int, size_in_bytes: int) -> _AttributeDict:
|
132
|
+
return _AttributeDict(
|
133
|
+
st_atime=timestamp_epoch_seconds,
|
134
|
+
st_ctime=timestamp_epoch_seconds,
|
135
|
+
st_gid=os.getgid(),
|
136
|
+
st_mode=stat.S_IFREG | 0o444, # Regular file with read permissions for owner, group, and others.
|
137
|
+
st_mtime=timestamp_epoch_seconds,
|
138
|
+
st_nlink=1,
|
139
|
+
st_size=size_in_bytes,
|
140
|
+
st_uid=os.getuid(),
|
141
|
+
)
|
142
|
+
|
129
143
|
def _get_job_names_map(self, refresh_jobs=False) -> Dict[str, Job]:
|
130
144
|
current_time = time()
|
131
145
|
if not self._job_names_map or (current_time - self._jobs_last_fetched_at > 1 and refresh_jobs):
|
@@ -134,18 +148,15 @@ class ExperimentFuseMount(Operations):
|
|
134
148
|
|
135
149
|
return self._job_names_map
|
136
150
|
|
137
|
-
def
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
151
|
+
def _parse_path(self, path: str) -> Tuple[Job, str]:
|
152
|
+
path_splitted = path.split('/')
|
153
|
+
job_name = path_splitted[1]
|
154
|
+
path_in_job = '/' + '/'.join(path_splitted[2:])
|
155
|
+
job = self._get_job_names_map().get(job_name)
|
156
|
+
if not job:
|
157
|
+
raise FuseOSError(errno.ENOENT) # No such file or directory
|
142
158
|
|
143
|
-
|
144
|
-
full_path = self._full_path(path)
|
145
|
-
full_path_splitted = full_path.split('/')
|
146
|
-
job_name = full_path_splitted[1]
|
147
|
-
job_path = '/'.join(full_path_splitted[2:])
|
148
|
-
return job_name, job_path
|
159
|
+
return job, path_in_job
|
149
160
|
|
150
161
|
# ----------------------------------- File system methods not implemented below -----------------------------------
|
151
162
|
|
@@ -191,8 +202,5 @@ class ExperimentFuseMount(Operations):
|
|
191
202
|
def flush(self, path, fh):
|
192
203
|
raise FuseOSError(errno.EACCES)
|
193
204
|
|
194
|
-
def release(self, path, fh):
|
195
|
-
raise FuseOSError(errno.EACCES)
|
196
|
-
|
197
205
|
def fsync(self, path, datasync, fh):
|
198
206
|
raise FuseOSError(errno.EACCES)
|
biolib/experiments/experiment.py
CHANGED
@@ -73,7 +73,7 @@ class Experiment:
|
|
73
73
|
def add_job(self, job_id: str) -> None:
|
74
74
|
api.client.patch(path=f'/jobs/{job_id}/', data={'experiment_uuid': self.uuid})
|
75
75
|
|
76
|
-
def mount_files(self,
|
76
|
+
def mount_files(self, mount_path: str) -> None:
|
77
77
|
try:
|
78
78
|
# Only attempt to import FUSE dependencies when strictly necessary
|
79
79
|
from biolib._internal.fuse_mount import ( # pylint: disable=import-outside-toplevel
|
@@ -84,7 +84,7 @@ class Experiment:
|
|
84
84
|
'Failed to import FUSE mounting utils. Please ensure FUSE is installed on your system.'
|
85
85
|
) from error
|
86
86
|
|
87
|
-
_ExperimentFuseMount.
|
87
|
+
_ExperimentFuseMount.mount_experiment(experiment=self, mount_path=mount_path)
|
88
88
|
|
89
89
|
def export_job_list(self, export_format='dicts'):
|
90
90
|
valid_formats = ('dicts', 'dataframe')
|
@@ -6,7 +6,7 @@ biolib/_internal/data_record/__init__.py,sha256=1Bk303i3rFet9veS56fIsrBYtT5X3n9v
|
|
6
6
|
biolib/_internal/data_record/data_record.py,sha256=ctijrrZ-LfUxtwzS8PEVa1VBuBLVWEhmo2yHcEDkC0A,7178
|
7
7
|
biolib/_internal/data_record/remote_storage_endpoint.py,sha256=LPq8Lr5FhKF9_o5K-bUdT7TeLe5XFUD0AAeTkNEVZug,1133
|
8
8
|
biolib/_internal/fuse_mount/__init__.py,sha256=B_tM6RM2dBw-vbpoHJC4X3tOAaN1H2RDvqYJOw3xFwg,55
|
9
|
-
biolib/_internal/fuse_mount/experiment_fuse_mount.py,sha256=
|
9
|
+
biolib/_internal/fuse_mount/experiment_fuse_mount.py,sha256=MjGY9vNL-QivDF8w4qKuF3P7auv5cGK9qKKYYyDYQY4,6966
|
10
10
|
biolib/_internal/http_client.py,sha256=DdooXei93JKGYGV4aQmzue_oFzvHkozg2UCxgk9dfDM,5081
|
11
11
|
biolib/_internal/libs/__init__.py,sha256=Jdf4tNPqe_oIIf6zYml6TiqhL_02Vyqwge6IELrAFhw,98
|
12
12
|
biolib/_internal/libs/fusepy/__init__.py,sha256=AWDzNFS-XV_5yKb0Qx7kggIhPzq1nj_BZS5y2Nso08k,41944
|
@@ -86,7 +86,7 @@ biolib/compute_node/webserver/webserver_types.py,sha256=Vmt1ZDecYhGBVEYWcW1DVxee
|
|
86
86
|
biolib/compute_node/webserver/webserver_utils.py,sha256=XWvwYPbWNR3qS0FYbLLp-MDDfVk0QdaAmg3xPrT0H2s,4234
|
87
87
|
biolib/compute_node/webserver/worker_thread.py,sha256=26tG73TADnOcXsAr7Iyf6smrLlCqB4x-vvmpUb8WqnA,11569
|
88
88
|
biolib/experiments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
89
|
-
biolib/experiments/experiment.py,sha256=
|
89
|
+
biolib/experiments/experiment.py,sha256=_ied3A3U6NFa6_ewEYn2TCozqeHvDxDqdRhMtZbFL-M,6380
|
90
90
|
biolib/experiments/types.py,sha256=n9GxdFA7cLMfHvLLqLmZzX31ELeSSkMXFoEEdFsdWGY,171
|
91
91
|
biolib/jobs/__init__.py,sha256=aIb2H2DHjQbM2Bs-dysFijhwFcL58Blp0Co0gimED3w,32
|
92
92
|
biolib/jobs/job.py,sha256=aWKnf_2pYdr76gh3hxPiVs2iuXlpwZkKPTK81Pz4G2U,19072
|
@@ -109,8 +109,8 @@ biolib/utils/cache_state.py,sha256=u256F37QSRIVwqKlbnCyzAX4EMI-kl6Dwu6qwj-Qmag,3
|
|
109
109
|
biolib/utils/multipart_uploader.py,sha256=XvGP1I8tQuKhAH-QugPRoEsCi9qvbRk-DVBs5PNwwJo,8452
|
110
110
|
biolib/utils/seq_util.py,sha256=jC5WhH63FTD7SLFJbxQGA2hOt9NTwq9zHl_BEec1Z0c,4907
|
111
111
|
biolib/utils/zip/remote_zip.py,sha256=0wErYlxir5921agfFeV1xVjf29l9VNgGQvNlWOlj2Yc,23232
|
112
|
-
pybiolib-1.1.
|
113
|
-
pybiolib-1.1.
|
114
|
-
pybiolib-1.1.
|
115
|
-
pybiolib-1.1.
|
116
|
-
pybiolib-1.1.
|
112
|
+
pybiolib-1.1.1986.dist-info/LICENSE,sha256=F2h7gf8i0agDIeWoBPXDMYScvQOz02pAWkKhTGOHaaw,1067
|
113
|
+
pybiolib-1.1.1986.dist-info/METADATA,sha256=p82i2WtF6K3qM7lWMT00MKh7Eqp2HuFOrqZEeEU98_0,1508
|
114
|
+
pybiolib-1.1.1986.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
115
|
+
pybiolib-1.1.1986.dist-info/entry_points.txt,sha256=p6DyaP_2kctxegTX23WBznnrDi4mz6gx04O5uKtRDXg,42
|
116
|
+
pybiolib-1.1.1986.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|