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.
@@ -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 mount(experiment, path: str) -> None:
32
+ def mount_experiment(experiment, mount_path: str) -> None:
35
33
  FUSE(
36
34
  operations=ExperimentFuseMount(experiment),
37
- mountpoint=path,
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
- 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)
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
- 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)
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(job_path)
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
- raise FuseOSError(errno.ENOENT) from None
61
+ pass
67
62
 
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
- )
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) -> Iterable[str]:
71
+ def readdir(self, path: str, fh: int) -> List[str]:
80
72
  directory_entries = ['.', '..']
73
+
81
74
  if path == '/':
82
- for name in self._get_job_names_map(refresh_jobs=True):
83
- directory_entries.append(name)
75
+ directory_entries.extend(self._get_job_names_map(refresh_jobs=True).keys())
84
76
  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)]
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
- for key in in_target_directory:
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
- job_name, job_path = self._parse_path(path)
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(job_path)
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
- job_name, job_path = self._parse_path(path)
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(job_path)
105
+ file = job.get_output_file(path_in_job)
111
106
  except BioLibError:
112
- # file not found
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 _get_folder_attr(self, timestamp_epoch_seconds: int) -> _AttributeDict:
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 _full_path(self, partial: str) -> str:
138
- if partial.startswith('/'):
139
- partial = partial[1:]
140
-
141
- return os.path.join(self._root_path, partial)
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
- 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
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)
@@ -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, path: str) -> None:
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.mount(experiment=self, path=path)
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')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pybiolib
3
- Version: 1.1.1979
3
+ Version: 1.1.1986
4
4
  Summary: BioLib Python Client
5
5
  Home-page: https://github.com/biolib
6
6
  License: MIT
@@ -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=tRkgPncbf9Nl22adcjBTJuFfiYMkayYJSJ--WQrB-Ps,6629
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=kUQsH9AGAckPKT_nzaRuTh8Mb2pVUpxnuX9IstRTOEo,6351
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.1979.dist-info/LICENSE,sha256=F2h7gf8i0agDIeWoBPXDMYScvQOz02pAWkKhTGOHaaw,1067
113
- pybiolib-1.1.1979.dist-info/METADATA,sha256=gaN82MLET8VrqRkqovA4ARsoe1iBCt5eWuA0YoalPaY,1508
114
- pybiolib-1.1.1979.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
115
- pybiolib-1.1.1979.dist-info/entry_points.txt,sha256=p6DyaP_2kctxegTX23WBznnrDi4mz6gx04O5uKtRDXg,42
116
- pybiolib-1.1.1979.dist-info/RECORD,,
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,,