pybiolib 1.1.1957__py3-none-any.whl → 1.1.1971__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.
@@ -0,0 +1 @@
1
+ from .experiment_fuse_mount import ExperimentFuseMount
@@ -0,0 +1,198 @@
1
+ import errno
2
+ import os
3
+ import stat
4
+ from datetime import datetime, timezone
5
+ from time import time
6
+ from typing import Iterable
7
+
8
+ from biolib._internal.libs.fusepy import FUSE, FuseOSError, Operations
9
+ from biolib.biolib_errors import BioLibError
10
+ from biolib.jobs import Job
11
+ from biolib.typing_utils import Dict, Optional, Tuple, TypedDict
12
+
13
+
14
+ class _AttributeDict(TypedDict):
15
+ st_atime: int
16
+ st_ctime: int
17
+ st_gid: int
18
+ st_mode: int
19
+ st_mtime: int
20
+ st_nlink: int
21
+ st_size: int
22
+ st_uid: int
23
+
24
+
25
+ class ExperimentFuseMount(Operations):
26
+ def __init__(self, experiment):
27
+ self._experiment = experiment
28
+ self._job_names_map: Optional[Dict[str, Job]] = None
29
+ self._jobs_last_fetched_at: float = 0.0
30
+ self._root_path: str = '/'
31
+ self._mounted_at_epoch_seconds: int = int(time())
32
+
33
+ @staticmethod
34
+ def mount(experiment, path: str) -> None:
35
+ FUSE(
36
+ operations=ExperimentFuseMount(experiment),
37
+ mountpoint=path,
38
+ nothreads=True,
39
+ foreground=True,
40
+ allow_other=False,
41
+ )
42
+
43
+ def getattr(self, path: str, fh=None) -> _AttributeDict:
44
+ full_path = self._full_path(path)
45
+ if full_path == '/':
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)
54
+
55
+ job_finished_at_epoch_seconds: int = int(
56
+ datetime.fromisoformat(job.to_dict()['finished_at'].rstrip('Z')).replace(tzinfo=timezone.utc).timestamp()
57
+ )
58
+ if not job_path or job_path == '/':
59
+ # job root path
60
+ return self._get_folder_attr(timestamp_epoch_seconds=job_finished_at_epoch_seconds)
61
+
62
+ try:
63
+ file = job.get_output_file(job_path)
64
+ except BioLibError:
65
+ # file not found
66
+ raise FuseOSError(errno.ENOENT) from None
67
+
68
+ return _AttributeDict(
69
+ st_atime=job_finished_at_epoch_seconds,
70
+ st_ctime=job_finished_at_epoch_seconds,
71
+ st_gid=os.getgid(),
72
+ st_mode=stat.S_IFREG | 0o444, # Regular file with read permissions for owner, group, and others.
73
+ st_mtime=job_finished_at_epoch_seconds,
74
+ st_nlink=1,
75
+ st_size=file.length,
76
+ st_uid=os.getuid(),
77
+ )
78
+
79
+ def readdir(self, path: str, fh: int) -> Iterable[str]:
80
+ directory_entries = ['.', '..']
81
+ if path == '/':
82
+ for name in self._get_job_names_map(refresh_jobs=True):
83
+ directory_entries.append(name)
84
+ else:
85
+ job_name, job_path = self._parse_path(path)
86
+ job = self._get_job_names_map()[job_name]
87
+ in_target_directory = set(
88
+ [k.path.split('/')[1] for k in job.list_output_files() if k.path.startswith(job_path)]
89
+ )
90
+
91
+ for key in in_target_directory:
92
+ directory_entries.append(key)
93
+
94
+ yield from directory_entries
95
+
96
+ def open(self, path: str, flags: int) -> int:
97
+ job_name, job_path = self._parse_path(path)
98
+ job = self._get_job_names_map()[job_name]
99
+ try:
100
+ job.get_output_file(job_path)
101
+ return 0 # return dummy file handle
102
+ except BioLibError:
103
+ # file not found
104
+ raise FuseOSError(errno.ENOENT) from None
105
+
106
+ def read(self, path: str, size: int, offset: int, fh: int) -> bytes:
107
+ job_name, job_path = self._parse_path(path)
108
+ job = self._get_job_names_map()[job_name]
109
+ try:
110
+ file = job.get_output_file(job_path)
111
+ except BioLibError:
112
+ # file not found
113
+ raise FuseOSError(errno.ENOENT) from None
114
+
115
+ return file.get_data(start=offset, length=size)
116
+
117
+ def _get_folder_attr(self, timestamp_epoch_seconds: int) -> _AttributeDict:
118
+ return _AttributeDict(
119
+ st_atime=timestamp_epoch_seconds,
120
+ st_ctime=timestamp_epoch_seconds,
121
+ st_gid=os.getgid(),
122
+ st_mode=stat.S_IFDIR | 0o555, # Directory that is readable and executable by owner, group, and others.
123
+ st_mtime=timestamp_epoch_seconds,
124
+ st_nlink=1,
125
+ st_size=1,
126
+ st_uid=os.getuid(),
127
+ )
128
+
129
+ def _get_job_names_map(self, refresh_jobs=False) -> Dict[str, Job]:
130
+ current_time = time()
131
+ if not self._job_names_map or (current_time - self._jobs_last_fetched_at > 1 and refresh_jobs):
132
+ self._jobs_last_fetched_at = current_time
133
+ self._job_names_map = {job.get_name(): job for job in self._experiment.get_jobs(status='completed')}
134
+
135
+ return self._job_names_map
136
+
137
+ def _full_path(self, partial: str) -> str:
138
+ if partial.startswith('/'):
139
+ partial = partial[1:]
140
+
141
+ return os.path.join(self._root_path, partial)
142
+
143
+ def _parse_path(self, path: str) -> Tuple[str, str]:
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
149
+
150
+ # ----------------------------------- File system methods not implemented below -----------------------------------
151
+
152
+ def chmod(self, path, mode):
153
+ raise FuseOSError(errno.EACCES)
154
+
155
+ def chown(self, path, uid, gid):
156
+ raise FuseOSError(errno.EACCES)
157
+
158
+ def mknod(self, path, mode, dev):
159
+ raise FuseOSError(errno.EACCES)
160
+
161
+ def rmdir(self, path):
162
+ raise FuseOSError(errno.EACCES)
163
+
164
+ def mkdir(self, path, mode):
165
+ raise FuseOSError(errno.EACCES)
166
+
167
+ def unlink(self, path):
168
+ raise FuseOSError(errno.EACCES)
169
+
170
+ def symlink(self, target, source):
171
+ raise FuseOSError(errno.EACCES)
172
+
173
+ def rename(self, old, new):
174
+ raise FuseOSError(errno.EACCES)
175
+
176
+ def link(self, target, source):
177
+ raise FuseOSError(errno.EACCES)
178
+
179
+ def utimens(self, path, times=None):
180
+ raise FuseOSError(errno.EACCES)
181
+
182
+ def create(self, path, mode, fi=None):
183
+ raise FuseOSError(errno.EACCES)
184
+
185
+ def write(self, path, data, offset, fh):
186
+ raise FuseOSError(errno.EACCES)
187
+
188
+ def truncate(self, path, length, fh=None):
189
+ raise FuseOSError(errno.EACCES)
190
+
191
+ def flush(self, path, fh):
192
+ raise FuseOSError(errno.EACCES)
193
+
194
+ def release(self, path, fh):
195
+ raise FuseOSError(errno.EACCES)
196
+
197
+ def fsync(self, path, datasync, fh):
198
+ raise FuseOSError(errno.EACCES)
@@ -0,0 +1 @@
1
+ # Note: this directory is purely for libraries to be directly included instead of as dependencies