geoseeq 0.7.0__py3-none-any.whl → 0.7.2__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.
geoseeq/cli/main.py CHANGED
@@ -55,7 +55,7 @@ def version():
55
55
  Use of this tool implies acceptance of the GeoSeeq End User License Agreement.
56
56
  Run `geoseeq eula show` to view the EULA.
57
57
  """
58
- click.echo('0.7.0') # remember to update pyproject.toml
58
+ click.echo('0.7.2') # remember to update pyproject.toml
59
59
 
60
60
 
61
61
  @main.group('advanced')
@@ -128,9 +128,9 @@ def cli_upload_file(state, cores, threads_per_upload, num_retries, chunk_size_mb
128
128
  )
129
129
  for geoseeq_file_name, file_path in name_pairs:
130
130
  if isfile(file_path):
131
- upload_manager.add_local_file_to_result_folder(result_folder, file_path)
131
+ upload_manager.add_local_file_to_result_folder(result_folder, file_path, geoseeq_file_name=geoseeq_file_name)
132
132
  elif isdir(file_path) and recursive:
133
- upload_manager.add_local_folder_to_result_folder(result_folder, file_path, recursive=recursive, hidden_files=hidden, prefix=file_path)
133
+ upload_manager.add_local_folder_to_result_folder(result_folder, file_path, recursive=recursive, hidden_files=hidden, prefix=file_path, geoseeq_file_name=geoseeq_file_name)
134
134
  elif isdir(file_path) and not recursive:
135
135
  raise click.UsageError('Cannot upload a folder without --recursive')
136
136
  click.echo(upload_manager.get_preview_string(), err=True)
@@ -238,7 +238,7 @@ def cli_upload_reads_wizard(state, cores, overwrite, yes, regex, private, link_t
238
238
  filepaths = {basename(line): line for line in flatten_list_of_fastxs(fastq_files)}
239
239
  click.echo(f'Found {len(filepaths)} files to upload.', err=True)
240
240
  regex = _get_regex(knex, filepaths, module_name, proj, regex)
241
- groups = _group_files(knex, filepaths, module_name, regex, yes)
241
+ groups = _group_files(knex, filepaths, module_name, regex, yes, name_map)
242
242
  _do_upload(groups, module_name, link_type, proj, filepaths, overwrite, no_new_versions, cores, state)
243
243
 
244
244
 
@@ -0,0 +1,103 @@
1
+
2
+ import logging
3
+ import json
4
+ from typing import Literal
5
+ from geoseeq.remote_object import RemoteObject
6
+ from geoseeq.id_constructors import result_file_from_blob
7
+ from geoseeq import ProjectResultFile
8
+
9
+ logger = logging.getLogger("geoseeq_api")
10
+
11
+
12
+ class Dashboard(RemoteObject):
13
+ parent_field = "project"
14
+ remote_fields = ["is_default"]
15
+
16
+ def __init__(self, knex, project, name="Default dashboard", is_default=False):
17
+ super().__init__(self)
18
+ self.knex = knex
19
+ self.project = project
20
+ self._name = name
21
+ self.tiles = []
22
+ self.is_default = is_default
23
+
24
+ def _get(self, allow_overwrite=False):
25
+ blob = self.knex.get(f"sample_groups/{self.project.uuid}/dashboard-list")
26
+ blob = blob["dashboard_data"][self.name]
27
+ for tile_blob in blob["tiles"]:
28
+ tile = DashboardTile.from_blob(self, tile_blob)
29
+ self.tiles.append(tile)
30
+ blob.pop("tiles")
31
+ self.load_blob(blob, allow_overwrite=allow_overwrite)
32
+
33
+ def _save(self):
34
+ self.save_tiles()
35
+
36
+ def save_tiles(self):
37
+ post_data = {"tiles": [tile._get_post_data() for tile in self.tiles]}
38
+ blob = self.knex.post(f"sample_groups/{self.project.uuid}/dashboard/{self.name}/tiles", json=post_data, json_response=False)
39
+ print(blob)
40
+
41
+ def _create(self):
42
+ post_data = {"name": self.name, "is_default": self.is_default}
43
+ blob = self.knex.post(f"sample_groups/{self.project.uuid}/dashboard", json=post_data)
44
+ self.load_blob(blob)
45
+
46
+ def tile(self, title, result_file, style: Literal["col-span-1", "col-span-2"]="col-span-1"):
47
+ result_file.get()
48
+ tile = DashboardTile(self.knex, self, title, result_file, style=style)
49
+ self.tiles.append(tile)
50
+ self._modified = True
51
+ return tile
52
+
53
+ def add_tile(self, tile):
54
+ self.tiles.append(tile)
55
+ self._modified = True
56
+
57
+ @property
58
+ def name(self):
59
+ return self._name
60
+
61
+ def __str__(self):
62
+ return f"<Geoseeq Dashboard: {self.project.grn} \"{self.name}\"/>"
63
+
64
+ def __repr__(self):
65
+ return str(self)
66
+
67
+ @property
68
+ def grn(self):
69
+ return f"grn:dashboard:{self.project.uuid}:\"{self.name}\""
70
+
71
+ def pre_hash(self):
72
+ return "DASH" + self.project.uuid + self.name
73
+
74
+
75
+ class DashboardTile:
76
+
77
+ def __init__(self, knex, dashboard, title, result_file, style="col-span-1"):
78
+ self.knex = knex
79
+ self.dashboard = dashboard
80
+ self.title = title
81
+ self.style = style
82
+ self.result_file = result_file
83
+
84
+ def _get_post_data(self):
85
+ out = {
86
+ "field_uuid": self.result_file.uuid,
87
+ "field_type": "group" if isinstance(self.result_file, ProjectResultFile) else "sample",
88
+ "style": self.style,
89
+ "title": self.title,
90
+ "has_related_field": False,
91
+ }
92
+ return out
93
+
94
+ @classmethod
95
+ def from_blob(cls, dashboard, blob):
96
+ result_file = result_file_from_blob(blob["viz_field"])
97
+ return cls(dashboard.knex, dashboard, blob["title"], result_file, style=blob["style"])
98
+
99
+ def __str__(self) -> str:
100
+ return f"<Geoseeq DashboardTile: {self.dashboard.grn} \"{self.title}\" />"
101
+
102
+ def __repr__(self) -> str:
103
+ return str(self)
@@ -0,0 +1,434 @@
1
+
2
+ import os
3
+ import json
4
+ from geoseeq import (
5
+ result_file_from_id,
6
+ result_folder_from_id,
7
+ sample_from_id,
8
+ project_from_id,
9
+ )
10
+ from geoseeq.utils import md5_checksum
11
+ from time import time
12
+
13
+ FILE_STATUS_MODIFIED_REMOTE = 'MODIFIED_REMOTE'
14
+ FILE_STATUS_MODIFIED_LOCAL = 'MODIFIED_LOCAL'
15
+ FILE_STATUS_NEW_LOCAL = 'NEW_LOCAL'
16
+ FILE_STATUS_NEW_REMOTE = 'NEW_REMOTE'
17
+ FILE_STATUS_IS_LOCAL_STUB = 'IS_LOCAL_STUB'
18
+
19
+
20
+ def dedupe_modified_files(modified_files):
21
+ """Remove duplicates from a list of modified files.
22
+
23
+ This function will remove duplicates from a list of modified files
24
+ based on the path to the file. The first instance of the file will be
25
+ kept and all others will be removed.
26
+ """
27
+ seen = set()
28
+ deduped = []
29
+ for x in modified_files:
30
+ if x[2] not in seen:
31
+ deduped.append(x)
32
+ seen.add(x[2])
33
+ return deduped
34
+
35
+
36
+ class ResultFileOnFilesystem:
37
+ """
38
+
39
+ Note: unlike other filesystem classes the `path` is a file, not
40
+ a directory. This is because the file is downloaded directly to
41
+ the path.
42
+ """
43
+
44
+ def __init__(self, result_file, path, kind):
45
+ self.result_file = result_file
46
+ self.path = path
47
+ self.kind = kind
48
+
49
+ @property
50
+ def info_filepath(self):
51
+ dirpath = os.path.dirname(self.path)
52
+ basename = os.path.basename(self.path)
53
+ return os.path.join(dirpath, f'.gs_result_file__{basename}')
54
+
55
+ @property
56
+ def is_stub(self):
57
+ return os.path.exists(self.path) and os.path.getsize(self.path) == 0
58
+
59
+ def file_is_ok(self, stubs_are_ok=False):
60
+ if self.is_stub:
61
+ return stubs_are_ok
62
+ return self.result_file.download_needs_update(self.path)
63
+
64
+ def download(self, use_stubs=False, exists_ok=False):
65
+ if os.path.exists(self.info_filepath):
66
+ if exists_ok and self.file_is_ok(stubs_are_ok=use_stubs):
67
+ return
68
+ elif not exists_ok:
69
+ raise ValueError('Result file already exists at path: {}'.format(self.info_filepath))
70
+
71
+ # Download the file
72
+ if use_stubs:
73
+ open(self.path, 'w').close()
74
+ else:
75
+ self.result_file.download(self.path)
76
+
77
+ self.write_info_file()
78
+
79
+ def local_file_checksum(self):
80
+ if self.is_stub:
81
+ return "__STUB__"
82
+ return md5_checksum(self.path)
83
+
84
+ def locally_modified(self):
85
+ raise NotImplementedError('This function is not implemented')
86
+
87
+ def status_is_ok(self, stubs_are_ok=False):
88
+ # check for an info file
89
+ if not os.path.exists(self.info_filepath):
90
+ return False
91
+ if stubs_are_ok:
92
+ return True
93
+ return not self.result_file.download_needs_update(self.path)
94
+
95
+ def write_info_file(self):
96
+ result_file_info = {
97
+ "uuid": self.result_file.uuid,
98
+ "kind": self.kind,
99
+ "checksum": self.local_file_checksum(),
100
+ }
101
+ with open(self.info_filepath, 'w') as f:
102
+ json.dump(result_file_info, f)
103
+
104
+ @classmethod
105
+ def from_path(cls, path):
106
+ obj = cls(None, path, None)
107
+ try:
108
+ with open(obj.info_filepath, 'r') as f:
109
+ result_file_info = json.load(f)
110
+ obj.result_file = result_file_from_id(result_file_info['uuid'])
111
+ obj.kind = result_file_info['kind']
112
+ obj.stored_checksum = result_file_info['checksum']
113
+ except FileNotFoundError:
114
+ pass
115
+ return obj
116
+
117
+ def write_info_file(self):
118
+ result_file_info = {
119
+ "uuid": self.result_file.uuid,
120
+ "kind": self.kind,
121
+ "checksum": self.local_file_checksum(),
122
+ }
123
+ with open(self.info_filepath, 'w') as f:
124
+ json.dump(result_file_info, f)
125
+
126
+ def list_abnormal_objects(self):
127
+ """Return a list of files that have been modified.
128
+
129
+ Since this class is a single file the list will either be empty
130
+ or have one element.
131
+
132
+ Note that if a file was modified locally then uploaded to the server
133
+ the file will be marked as modified remote.
134
+ """
135
+ if self.result_file is None:
136
+ return [('FILE', FILE_STATUS_NEW_LOCAL, self.path, None)]
137
+ if not os.path.exists(self.path):
138
+ return [('FILE', FILE_STATUS_NEW_REMOTE, self.path, self.result_file)]
139
+ if self.is_stub:
140
+ return [('FILE', FILE_STATUS_IS_LOCAL_STUB, self.path, self.result_file)]
141
+ if self.result_file and self.result_file.download_needs_update(self.path):
142
+ return [('FILE', FILE_STATUS_MODIFIED_REMOTE, self.path, self.result_file)]
143
+ if self.locally_modified():
144
+ return [('FILE', FILE_STATUS_MODIFIED_LOCAL, self.path, self.result_file)]
145
+
146
+ return []
147
+
148
+
149
+ class ResultFolderOnFilesystem:
150
+
151
+ def __init__(self, result_folder, path, kind):
152
+ self.result_folder = result_folder
153
+ self.path = path
154
+ self.kind = kind
155
+
156
+ @property
157
+ def info_filepath(self):
158
+ return os.path.join(self.path, '.gs_result_folder')
159
+
160
+ def download(self, use_stubs=False, exists_ok=False):
161
+ if os.path.exists(self.info_filepath) and not exists_ok:
162
+ raise ValueError('Result folder already exists at path: {}'.format(self.info_filepath))
163
+
164
+ # Download the files in the result folder
165
+ for result_file in self.result_folder.get_fields():
166
+ result_file_local_path = os.path.join(self.path, result_file.name)
167
+ os.makedirs(os.path.dirname(result_file_local_path), exist_ok=True)
168
+ ResultFileOnFilesystem(result_file, result_file_local_path, self.kind)\
169
+ .download(use_stubs=use_stubs, exists_ok=exists_ok)
170
+
171
+ # Write the result folder data
172
+ result_folder_info = {
173
+ "uuid": self.result_folder.uuid,
174
+ "kind": self.kind
175
+ }
176
+ with open(self.info_filepath, 'w') as f:
177
+ json.dump(result_folder_info, f)
178
+
179
+ def status_is_ok(self):
180
+ # check for an info file
181
+ if not os.path.exists(self.info_filepath):
182
+ return False
183
+
184
+ # check that all files are downloaded
185
+ for result_file in self.result_folder.get_files():
186
+ result_file_path = os.path.join(self.path, result_file.name)
187
+ if not os.path.exists(result_file_path):
188
+ return False
189
+
190
+ return True
191
+
192
+ @classmethod
193
+ def from_path(cls, path):
194
+ obj = cls(None, path, None)
195
+ try:
196
+ with open(os.path.join(path, '.gs_result_folder'), 'r') as f:
197
+ result_folder_info = json.load(f)
198
+ obj.result_folder = result_folder_from_id(result_folder_info['uuid'])
199
+ obj.kind = result_folder_info['kind']
200
+ except FileNotFoundError:
201
+ pass
202
+ return obj
203
+
204
+ def list_abnormal_objects(self):
205
+ """Return a list of files that have been modified.
206
+
207
+ This function will return a list of tuples where the first element
208
+ is the status of the file and the second element is the path to the file.
209
+ """
210
+ modified_files = []
211
+ if not self.result_folder:
212
+ modified_files.append(('FOLDER', FILE_STATUS_NEW_LOCAL, self.path, None))
213
+ if not os.path.exists(self.path):
214
+ modified_files.append(('FOLDER', FILE_STATUS_NEW_REMOTE, self.path, self.result_folder))
215
+
216
+ # list local files
217
+ if os.path.exists(self.path):
218
+ for local_file in os.listdir(self.path):
219
+ if local_file.startswith('.gs_'):
220
+ continue
221
+ local_file_path = os.path.join(self.path, local_file)
222
+ result_file_on_fs = ResultFileOnFilesystem.from_path(local_file_path)
223
+ modified_files.extend(result_file_on_fs.list_abnormal_objects())
224
+
225
+ # list remote files
226
+ if self.result_folder:
227
+ for result_file in self.result_folder.get_fields():
228
+ result_file_path = os.path.join(self.path, result_file.name)
229
+ result_file_on_fs = ResultFileOnFilesystem(result_file, result_file_path, self.kind)
230
+ modified_files.extend(result_file_on_fs.list_abnormal_objects())
231
+
232
+ return dedupe_modified_files(modified_files)
233
+
234
+
235
+ class SampleOnFilesystem:
236
+
237
+ def __init__(self, sample, path):
238
+ self.sample = sample
239
+ self.path = path if path[-1] != '/' else path[:-1] # remove trailing slash
240
+
241
+ @property
242
+ def info_filepath(self):
243
+ return os.path.join(self.path, '.gs_sample')
244
+
245
+ def download(self, use_stubs=False, exists_ok=False):
246
+ if os.path.exists(self.info_filepath) and not exists_ok:
247
+ raise ValueError('Sample already exists at path: {}'.format(self.info_filepath))
248
+
249
+ # download result folders
250
+ for result_folder in self.sample.get_result_folders():
251
+ result_folder_local_path = os.path.join(self.path, result_folder.name)
252
+ os.makedirs(result_folder_local_path, exist_ok=True)
253
+ ResultFolderOnFilesystem(result_folder, result_folder_local_path, "sample")\
254
+ .download(use_stubs=use_stubs, exists_ok=exists_ok)
255
+
256
+ # Write the sample data
257
+ sample_info = {
258
+ "uuid": self.sample.uuid
259
+ }
260
+ with open(self.info_filepath, 'w') as f:
261
+ json.dump(sample_info, f)
262
+
263
+ def status_is_ok(self):
264
+ # check for an info file
265
+ if not os.path.exists(self.info_filepath):
266
+ return False
267
+
268
+ # check that all result folders are downloaded
269
+ for result_folder in self.sample.get_result_folders():
270
+ result_folder_local_path = os.path.join(self.path, result_folder.name)
271
+ result_folder_on_fs = ResultFolderOnFilesystem.from_path(result_folder_local_path, "sample")
272
+ if not result_folder_on_fs.status_is_ok():
273
+ return False
274
+
275
+ return True
276
+
277
+ @classmethod
278
+ def from_path(cls, path):
279
+ obj = cls(None, path)
280
+ try:
281
+ with open(os.path.join(path, '.gs_sample'), 'r') as f:
282
+ sample_info = json.load(f)
283
+ obj.sample = sample_from_id(sample_info['uuid'])
284
+ except FileNotFoundError:
285
+ pass
286
+ return obj
287
+
288
+ def list_abnormal_objects(self):
289
+ """Return a list of files that have been modified.
290
+
291
+ This function will return a list of tuples where the first element
292
+ is the status of the file and the second element is the path to the file.
293
+ """
294
+ modified_files = []
295
+ if not self.sample:
296
+ modified_files.append(('SAMPLE', FILE_STATUS_NEW_LOCAL, self.path, None))
297
+ if not os.path.exists(self.path):
298
+ modified_files.append(('SAMPLE', FILE_STATUS_NEW_REMOTE, self.path, self.sample))
299
+
300
+ # list local folders
301
+ if os.path.exists(self.path):
302
+ for local_folder in os.listdir(self.path):
303
+ local_folder_path = os.path.join(self.path, local_folder)
304
+ if not os.path.isdir(local_folder_path):
305
+ continue
306
+ result_folder_on_fs = ResultFolderOnFilesystem.from_path(local_folder_path)
307
+ modified_files.extend(result_folder_on_fs.list_abnormal_objects())
308
+
309
+ # list remote folders
310
+ if self.sample:
311
+ for result_folder in self.sample.get_result_folders():
312
+ result_folder_path = os.path.join(self.path, result_folder.name)
313
+ result_folder_on_fs = ResultFolderOnFilesystem(result_folder, result_folder_path, "sample")
314
+ modified_files.extend(result_folder_on_fs.list_abnormal_objects())
315
+
316
+ return dedupe_modified_files(modified_files)
317
+
318
+
319
+ class ProjectOnFilesystem:
320
+
321
+ def __init__(self, project, path):
322
+ self.project = project
323
+ self.path = path
324
+
325
+ @property
326
+ def info_filepath(self):
327
+ return os.path.join(self.path, '.gs_project')
328
+
329
+ def download(self, use_stubs=False, exists_ok=False):
330
+ if os.path.exists(self.info_filepath) and not exists_ok:
331
+ raise ValueError('Project already exists at path: {}'.format(self.info_filepath))
332
+
333
+ # download samples
334
+ for sample in self.project.get_samples():
335
+ sample_local_path = os.path.join(self.path, "sample_results", sample.name)
336
+ os.makedirs(sample_local_path, exist_ok=True)
337
+ SampleOnFilesystem(sample, sample_local_path)\
338
+ .download(use_stubs=use_stubs, exists_ok=exists_ok)
339
+
340
+ # download project result folders
341
+ for result_folder in self.project.get_result_folders():
342
+ result_folder_local_path = os.path.join(self.path, "project_results", result_folder.name)
343
+ os.makedirs(result_folder_local_path, exist_ok=True)
344
+ ResultFolderOnFilesystem(result_folder, result_folder_local_path, "project")\
345
+ .download(use_stubs=use_stubs, exists_ok=exists_ok)
346
+
347
+ # Write the project data
348
+ project_info = {
349
+ "uuid": self.project.uuid
350
+ }
351
+ with open(self.info_filepath, 'w') as f:
352
+ json.dump(project_info, f)
353
+
354
+ def status_is_ok(self):
355
+ # check for an info file
356
+ if not os.path.exists(self.info_filepath):
357
+ return False
358
+
359
+ # check that all samples are downloaded
360
+ for sample in self.project.get_samples():
361
+ sample_local_path = os.path.join(self.path, "sample_results", sample.name)
362
+ sample_on_fs = SampleOnFilesystem.from_path(sample_local_path)
363
+ if not sample_on_fs.status_is_ok():
364
+ return False
365
+
366
+ # check that all project result folders are downloaded
367
+ for result_folder in self.project.get_result_folders():
368
+ result_folder_local_path = os.path.join(self.path, "project_results", result_folder.name)
369
+ result_folder_on_fs = ResultFolderOnFilesystem.from_path(result_folder_local_path, "project")
370
+ if not result_folder_on_fs.status_is_ok():
371
+ return False
372
+
373
+ return True
374
+
375
+ @classmethod
376
+ def from_path(cls, path, recursive=False):
377
+ try:
378
+ with open(os.path.join(path, '.gs_project'), 'r') as f:
379
+ project_info = json.load(f)
380
+ project = project_from_id(project_info['uuid'])
381
+ return cls(project, path)
382
+ except FileNotFoundError:
383
+ if not recursive:
384
+ raise ValueError('No project found in path or parent directories')
385
+ updir = os.path.dirname(os.path.abspath(path))
386
+ if updir == path:
387
+ raise ValueError('No project found in path or parent directories')
388
+ return cls.from_path(updir, recursive=recursive)
389
+
390
+ def path_from_project_root(self, path):
391
+ if path[0] == "/":
392
+ return path.replace(self.path, "")[1:]
393
+ return path
394
+
395
+ def list_abnormal_objects(self):
396
+ """Return a list of files that have been modified.
397
+
398
+ This function will return a list of tuples where the first element
399
+ is the status of the file and the second element is the path to the file.
400
+ """
401
+ modified_files = []
402
+
403
+ # list remote samples
404
+ for sample in self.project.get_samples():
405
+ sample_path = os.path.join(self.path, "sample_results", sample.name)
406
+ sample_on_fs = SampleOnFilesystem(sample, sample_path)
407
+ modified_files.extend(sample_on_fs.list_abnormal_objects())
408
+
409
+ # list remote project result folders
410
+ for result_folder in self.project.get_result_folders():
411
+ result_folder_path = os.path.join(self.path, "project_results", result_folder.name)
412
+
413
+ result_folder_on_fs = ResultFolderOnFilesystem(result_folder, result_folder_path, "project")
414
+ modified_files.extend(result_folder_on_fs.list_abnormal_objects())
415
+
416
+ # list local samples
417
+ for local_sample in os.listdir(os.path.join(self.path, "sample_results")):
418
+ local_sample_path = os.path.join(self.path, "sample_results", local_sample)
419
+ if not os.path.isdir(local_sample_path):
420
+ continue
421
+ sample_on_fs = SampleOnFilesystem.from_path(local_sample_path)
422
+ modified_files.extend(sample_on_fs.list_abnormal_objects())
423
+
424
+ # list local project result folders
425
+ for local_result_folder in os.listdir(os.path.join(self.path, "project_results")):
426
+ local_result_folder_path = os.path.join(self.path, "project_results", local_result_folder)
427
+ if not os.path.isdir(local_result_folder_path):
428
+ continue
429
+ result_folder_on_fs = ResultFolderOnFilesystem.from_path(local_result_folder_path)
430
+ modified_files.extend(result_folder_on_fs.list_abnormal_objects())
431
+ return dedupe_modified_files(modified_files)
432
+
433
+
434
+
@@ -0,0 +1,122 @@
1
+ from fuse import FUSE, Operations
2
+ import os
3
+
4
+
5
+ class GeoSeeqProjectFileSystem(Operations):
6
+ """Mount a GeoSeeq project as a filesystem.
7
+
8
+ The project will automatically have this directory structure:
9
+ - <root>/project_results/<project_result_folder_name>/...
10
+ - <root>/sample_results/<sample_name>/...
11
+ - <root>/metadata/sample_metadata.csv
12
+ - <root>/.config/config.json
13
+ """
14
+
15
+ def __init__(self, root, project):
16
+ self.root = root
17
+ self.project = project
18
+
19
+ def access(self, path, mode):
20
+ pass
21
+
22
+ def chmod(self, path, mode):
23
+ pass
24
+
25
+ def chown(self, path, uid, gid):
26
+ pass
27
+
28
+ def getattr(self, path, fh=None):
29
+ pass
30
+
31
+ def readdir(self, path, fh):
32
+ pass
33
+
34
+ def readlink(self, path):
35
+ pass
36
+
37
+ def mknod(self, path, mode, dev):
38
+ pass
39
+
40
+ def rmdir(self, path):
41
+ pass
42
+
43
+ def mkdir(self, path, mode):
44
+ pass
45
+
46
+ def statfs(self, path):
47
+ pass
48
+
49
+ def unlink(self, path):
50
+ pass
51
+
52
+ def symlink(self, name, target):
53
+ pass
54
+
55
+ def rename(self, old, new):
56
+ pass
57
+
58
+ def link(self, target, name):
59
+ pass
60
+
61
+ def utimens(self, path, times=None):
62
+ pass
63
+
64
+ def open(self, path, flags):
65
+ tkns = path.split('/')
66
+ if tkns[0] == 'project_results':
67
+ result_folder_name, result_file_name = tkns[2], '/'.join(tkns[3:])
68
+ result_folder = self.project.get_result_folder(result_folder_name).get()
69
+ result_file = result_folder.get_file(result_file_name).get()
70
+ result_file.download(path)
71
+ elif tkns[0] == 'sample_results':
72
+ sample_name, result_folder_name, result_file_name = tkns[2], tkns[3], '/'.join(tkns[4:])
73
+ sample = self.project.get_sample(sample_name).get()
74
+ result_folder = sample.get_result_folder(result_folder_name).get()
75
+ result_file = result_folder.get_file(result_file_name).get()
76
+ result_file.download(path)
77
+ elif tkns[0] == 'metadata':
78
+ raise NotImplementedError('TODO')
79
+
80
+ return os.open(self._full_local_path(path), flags)
81
+
82
+ def create(self, path, mode, fi=None):
83
+ tkns = path.split('/')
84
+ if tkns[0] == 'project_results':
85
+ result_name, file_name = tkns[2], '/'.join(tkns[3:])
86
+ result_folder = self.project.get_result_folder(result_name).idem()
87
+ result_file = result_folder.get_file(file_name).create()
88
+ result_file.download(path) # nothing to download at this point
89
+ elif tkns[0] == 'sample_results':
90
+ sample_name, result_folder_name, result_file_name = tkns[2], tkns[3], '/'.join(tkns[4:])
91
+ sample = self.project.get_sample(sample_name).idem()
92
+ result_folder = sample.get_result_folder(result_folder_name).idem()
93
+ result_file = result_folder.get_file(result_file_name).create()
94
+ result_file.download(path) # nothing to download at this point
95
+ elif tkns[0] == 'metadata':
96
+ raise NotImplementedError('TODO')
97
+
98
+ def read(self, path, length, offset, fh):
99
+ os.lseek(fh, offset, os.SEEK_SET)
100
+ return os.read(fh, length)
101
+
102
+ def write(self, path, buf, offset, fh):
103
+ pass
104
+
105
+ def truncate(self, path, length, fh=None):
106
+ pass
107
+
108
+ def flush(self, path, fh):
109
+ pass
110
+
111
+ def release(self, path, fh):
112
+ pass
113
+
114
+ def fsync(self, path, fdatasync, fh):
115
+ pass
116
+
117
+ def _full_local_path(self, partial):
118
+ if partial.startswith("/"):
119
+ partial = partial[1:]
120
+ return os.path.join(self.root, partial)
121
+
122
+
@@ -0,0 +1,4 @@
1
+ # GeoSeeq Plotting
2
+
3
+ This subpackage contains functions related to plotting data.
4
+
geoseeq/vc/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # GeoSeeq Version Control
2
+
3
+ GeoSeeq VC is a toolkit that makes it easy to use geoseeq with version control systems like `git`.
4
+
5
+ Most of the files stored on GeoSeeq are too large to version control directly. This package creates lightweight stub files that record a files location and checksum. This package also contains tools to download files, and check that local files match those stored on the server. The stub files are small enough to version control directly.
6
+
7
+ ## CLI
8
+
9
+ You can clone a project or sample from GeoSeeq. You will need to get the `brn` number from the project or sample then run these commands from your CLI
10
+
11
+ ```
12
+ geoseeq-api vc clone brn:gsr1:project:bed763fb-f6b8-4739-8320-95c06d68f442 # creates a directory tree with stub files
13
+ geoseeq-api vc download # downloads the files that the stubs link to
14
+ geoseeq-api vc status # checks that the local files match the files on GeoSeeq
15
+ ```
16
+
17
+ If you are using git you probably want to add files from geoseeq to your `.gitignore`
18
+
19
+ ```
20
+ geoseeq-api vc list >> .gitignore
21
+ ```
22
+
23
+ ## Shared Cache
24
+
25
+ To avoid downloading or storing the same file multiple times users can specify a cache by setting the `GEOSEEQ_VC_CACHE_DIR` envvar to an absolute filepath. When set geoseeq will download files to a location in the cahce directory and create symlinks to those files.
26
+
27
+ ## Stub Files
28
+
29
+ GeoSeeq interfaces with version control by creating small stub files that represent a larger file (AKA result field) stored on GeoSeeq. This package can be used to download those files and validate that the checksum of local files matches them. Since these files are small they can be easily stored with a version control system while the larger files can be ignored.
30
+
31
+ The stub files are JSON and have the following structure:
32
+
33
+ ```
34
+ {
35
+ "__schema_version__": "v0",
36
+ "brn": "brn:<instance name>:result_field:<uuid>", # optional if stub is new and parent_info is set
37
+ "checksum": {
38
+ "value": "<value>",
39
+ "method": "md5",
40
+ },
41
+ "local_path": "<filename>", # a filepath relative to the stub file
42
+ "parent_info": { # optional if brn is set
43
+ "parent_obj_brn": "brn:<brn info>",
44
+ "result_module_name": <string>,
45
+ "result_module_replicate": <string>,
46
+ } # NB if brn and parent_info are both set but disagree `brn` is correct
47
+ }
48
+ ```
@@ -1,24 +1,26 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geoseeq
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: GeoSeeq command line tools and python API
5
- Author: David C. Danko
6
- Author-email: "David C. Danko" <dcdanko@biotia.io>
7
5
  Project-URL: Homepage, https://github.com/biotia/geoseeq_api_client
8
6
  Project-URL: Issues, https://github.com/biotia/geoseeq_api_client/issues
9
- Classifier: Programming Language :: Python :: 3
7
+ Author-email: "David C. Danko" <dcdanko@biotia.io>
8
+ License-File: LICENSE
10
9
  Classifier: License :: OSI Approved :: MIT License
11
10
  Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
12
  Requires-Python: >=3.8
13
- Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
- Requires-Dist: requests
13
+ Requires-Dist: biopython
16
14
  Requires-Dist: click
17
15
  Requires-Dist: pandas
18
- Requires-Dist: biopython
16
+ Requires-Dist: requests
19
17
  Requires-Dist: tqdm
20
- Dynamic: author
21
- Dynamic: license-file
18
+ Provides-Extra: test
19
+ Requires-Dist: pytest-cov>=4.0; extra == 'test'
20
+ Requires-Dist: pytest-mock>=3.10; extra == 'test'
21
+ Requires-Dist: pytest>=7.0; extra == 'test'
22
+ Requires-Dist: requests-mock>=1.11; extra == 'test'
23
+ Description-Content-Type: text/markdown
22
24
 
23
25
  # Geoseeq API Client
24
26
 
@@ -25,7 +25,7 @@ geoseeq/cli/download.py,sha256=Znjuc9IFOcIa5_Od9mFXHJdYAJtgw9Bc_wPPcOVXn7s,21298
25
25
  geoseeq/cli/fastq_utils.py,sha256=-bmeQLaiMBm57zWOF0R5OlWTU0_3sh1JBC1RYw2BOFM,3083
26
26
  geoseeq/cli/find_grn.py,sha256=oMDxkzGQBQb2_cCuvmwoeHOsFHqyO9RLeJzrB6bAe5M,439
27
27
  geoseeq/cli/get_eula.py,sha256=79mbUwyiF7O1r0g6UTxG9kJGQEqKuH805E6eLkPC6Y4,997
28
- geoseeq/cli/main.py,sha256=pkpjc_5dZ8T0F92777OKRXT_EFoHelfw4DhKjEcXqIE,3982
28
+ geoseeq/cli/main.py,sha256=uGCf-uREHdrDzQZDiBLFt6KcKcyDRHnN6vHZRSwZaYU,3982
29
29
  geoseeq/cli/manage.py,sha256=wGXAcVaXqE5JQEU8Jh6OlHr02nB396bpS_SFcOZdrEo,5929
30
30
  geoseeq/cli/progress_bar.py,sha256=p1Xl01nkYxSBZCB30ue2verIIi22W93m3ZAMAxipD0g,738
31
31
  geoseeq/cli/project.py,sha256=V5SdXm2Hwo2lxrkpwRDedw-mAE4XnM2uwT-Gj1D90VQ,3030
@@ -42,15 +42,19 @@ geoseeq/cli/shared_params/id_handlers.py,sha256=KtzflnplYVkXsyqI5Ej6r-_BwQnuXVHP
42
42
  geoseeq/cli/shared_params/obj_getters.py,sha256=ZSkt6LnDkVFlNVYKgLrjzg60-6BthZMr3eeD3HNqzac,2741
43
43
  geoseeq/cli/shared_params/opts_and_args.py,sha256=_DcJ-TqgrbBaeDd-kuHEx2gLZPQN6EHZYWh8Ag-d8Vg,2091
44
44
  geoseeq/cli/upload/__init__.py,sha256=tRrljqG7uBM6zVBKpDw1zevHz6Vy418HuIYZKwByyWg,862
45
- geoseeq/cli/upload/upload.py,sha256=U9w18sG6dZ0uJoAMcQDLdrEtgmRaSaPS7-8KM5LkVd4,14220
45
+ geoseeq/cli/upload/upload.py,sha256=tBga8TW_5qXM4avXH5BAfLrYNms_5eqdyJZex47hZ90,14294
46
46
  geoseeq/cli/upload/upload_advanced.py,sha256=1pjtbe8EidIo4eR1_oVD00rPCTxYj8CRqB8D9buA62M,12432
47
- geoseeq/cli/upload/upload_reads.py,sha256=m9ujcjSYOG88LhxMj1vEt8v7FpuhRciL4ntE6oNlVuM,11632
47
+ geoseeq/cli/upload/upload_reads.py,sha256=dZK_Mh3a3NdC4zVB4Yyfz-NViCK9_rROQbmLWTrh8Pc,11642
48
48
  geoseeq/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
+ geoseeq/contrib/ncbi/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
50
  geoseeq/contrib/ncbi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
51
  geoseeq/contrib/ncbi/api.py,sha256=WQeLoGA_-Zha-QeSO8_i7HpvXyD8UkV0qc5okm11KiA,1056
51
52
  geoseeq/contrib/ncbi/bioproject.py,sha256=_oThTd_iLDOC8cLOlJKAatSr362OBYZCEV3YrqodhFg,4341
52
53
  geoseeq/contrib/ncbi/cli.py,sha256=j9zEcaZPTryK3a4xluRxigcJKDhRpRxbp3KZSx-Bfhk,2400
53
54
  geoseeq/contrib/ncbi/setup_logging.py,sha256=Tp1bY1U0f-o739aHpvVYriG2qdd1lFvCYBXZeXQgt-w,175
55
+ geoseeq/dashboard/dashboard.py,sha256=HOUJoQcFGLc3JL5UVIYETq6CkQEMP2I4WaPr2u9YV3Q,3297
56
+ geoseeq/file_system/filesystem_download.py,sha256=8bcnxjWltekmCvb5N0b1guBIjLp4-CL2VtsEok-snv4,16963
57
+ geoseeq/file_system/main.py,sha256=4HgYGq7WhlF96JlVIf16iFBTDujlBpxImmtoh4VCzDA,3627
54
58
  geoseeq/id_constructors/__init__.py,sha256=w5E0PNQ9UuAxBeZbDI7KBnUoERd85gGz3nScz45bd2o,126
55
59
  geoseeq/id_constructors/from_blobs.py,sha256=wZp6P6m7X0lhZAQxorHKJQn1CTBvKyfANzu2VFexp44,6315
56
60
  geoseeq/id_constructors/from_ids.py,sha256=SRylajToFdnRON5EhPIvcAi64XuIzseaasZlOooqTpg,3984
@@ -58,6 +62,7 @@ geoseeq/id_constructors/from_names.py,sha256=RqgFjDsAwQcidMkZwX7oB00OvBAKTiilHYe
58
62
  geoseeq/id_constructors/from_uuids.py,sha256=loqTeZqDZuvfVVHpS7m-Y_spNaY3h9VRjt0wDiMxHbs,3522
59
63
  geoseeq/id_constructors/resolvers.py,sha256=8hp5xJSCoZrAXtMT54Hp4okt63l909XqJU3IQx-VCgc,2676
60
64
  geoseeq/id_constructors/utils.py,sha256=Up_vXoZDwa2mJGllJ6CmcbaOGUvA-sM0gMmFnXK8wn4,1255
65
+ geoseeq/plotting/README.md,sha256=qlPnEzJWVxBeLvgyFXIN2du_-6GprvX8lRk4ROw6aMs,82
61
66
  geoseeq/plotting/__init__.py,sha256=RkGoXxgu7jEfK0B7NmdalPS2AbU7I7dZwDbi4rn9CKM,154
62
67
  geoseeq/plotting/constants.py,sha256=CGUlm8WAFG3YRKdicc9Rcy5hFxUdUm2RgK0iXZWLuX8,285
63
68
  geoseeq/plotting/highcharts.py,sha256=AGzdW4VSUsL_rfEI-RiVbbtaTLFMARvzLzVUDrKTlnU,4096
@@ -77,6 +82,7 @@ geoseeq/result/resumable_download_tracker.py,sha256=YEzqHBBnE7L3XokTvlTAhHZ8TcDT
77
82
  geoseeq/result/resumable_upload_tracker.py,sha256=2aI09gYz2yw63jEXqs8lmCRKQ79TIc3YuPETvP0Jeek,3811
78
83
  geoseeq/result/smart_objects.py,sha256=-krK9h9HcznIglf189uPghOidF_BzOeSxmBWxrtFFo8,1991
79
84
  geoseeq/result/utils.py,sha256=C-CxGzB3WddlnRiqFSkrY78I_m0yFgNqsTBRzGU-y8Q,2772
85
+ geoseeq/vc/README.md,sha256=igLGXl1jpp2A_tTfPutspTejwE1lkObeCsSvCYPBqOM,2316
80
86
  geoseeq/vc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
87
  geoseeq/vc/checksum.py,sha256=y8rh1asUZNbE_NLiFO0-9hImLNiTOc2YXQBRKORWK7k,710
82
88
  geoseeq/vc/cli.py,sha256=rUspr6FUJiUWIVMvf-pmyC5psKfufN45alk8392fAfA,2803
@@ -86,12 +92,8 @@ geoseeq/vc/vc_cache.py,sha256=P4LXTbq2zOIv1OhP7Iw5MmypR2vXuy29Pq5K6gRvi-M,730
86
92
  geoseeq/vc/vc_dir.py,sha256=A9CLTh2wWCRzZjiLyqXD1vhtsWZGD3OjaMT5KqlfAXI,457
87
93
  geoseeq/vc/vc_sample.py,sha256=qZeioWydXvfu4rGMs20nICfNcp46y_XkND-bHdV6P5M,3850
88
94
  geoseeq/vc/vc_stub.py,sha256=IQr8dI0zsWKVAeY_5ybDD6n49_3othcgfHS3P0O9tuY,3110
89
- geoseeq-0.7.0.dist-info/licenses/LICENSE,sha256=IuhIl1XCxXLPLJT_coN1CNqQU4Khlq7x4IdW7ioOJD8,1067
90
- tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
- tests/test_api_client.py,sha256=TS5njc5pcPP_Ycy-ljcfPVT1hQRBsFVdQ0lCqBmoesU,12810
92
- tests/test_plotting.py,sha256=TcTu-2ARr8sxZJ7wPQxmbs3-gHw7uRvsgrhhhg0qKik,784
93
- geoseeq-0.7.0.dist-info/METADATA,sha256=lEr_Lja53CYN53DTQTR3CKG3cqPdwgnuB03sbiwtzTs,4953
94
- geoseeq-0.7.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
95
- geoseeq-0.7.0.dist-info/entry_points.txt,sha256=yF-6KDM8zXib4Al0qn49TX-qM7PUkWUIcYtsgt36rjM,45
96
- geoseeq-0.7.0.dist-info/top_level.txt,sha256=zZk7mmeaqAYqFJG8nq2DTgSQPbflRjJwkDIhNURPDEU,14
97
- geoseeq-0.7.0.dist-info/RECORD,,
95
+ geoseeq-0.7.2.dist-info/METADATA,sha256=nBGfEYFfG9yWP6LWxczthpgwC5vZ5f7xi7VyaxVmz4Y,5107
96
+ geoseeq-0.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
97
+ geoseeq-0.7.2.dist-info/entry_points.txt,sha256=yF-6KDM8zXib4Al0qn49TX-qM7PUkWUIcYtsgt36rjM,45
98
+ geoseeq-0.7.2.dist-info/licenses/LICENSE,sha256=IuhIl1XCxXLPLJT_coN1CNqQU4Khlq7x4IdW7ioOJD8,1067
99
+ geoseeq-0.7.2.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
@@ -1,2 +0,0 @@
1
- geoseeq
2
- tests
tests/test_api_client.py DELETED
@@ -1,283 +0,0 @@
1
- """Test suite for experimental functions."""
2
- import random
3
- import sys
4
- from os import environ
5
- from unittest import TestCase, skip
6
-
7
- from geoseeq import (
8
- Knex,
9
- Organization,
10
- GeoseeqOtherError,
11
- GeoseeqNotFoundError,
12
- )
13
- from requests.exceptions import ConnectionError
14
-
15
- ENDPOINT = environ.get("GEOSEEQ_API_TESTING_ENDPOINT", "http://127.0.0.1:8000")
16
- TOKEN = environ.get("GEOSEEQ_API_TOKEN", "<no_token>")
17
-
18
-
19
- def random_str(len=12):
20
- """Return a random alphanumeric string of length `len`."""
21
- out = random.choices("abcdefghijklmnopqrtuvwxyzABCDEFGHIJKLMNOPQRTUVWXYZ0123456789", k=len)
22
- return "".join(out)
23
-
24
-
25
- class TestGeoseeqApiClient(TestCase):
26
- """Test suite for packet building."""
27
-
28
- def setUp(self):
29
- self.knex = Knex(ENDPOINT)
30
- # Creates a test user and an API token for the user in database. Returns the token.
31
- if TOKEN == "<no_token>":
32
- try:
33
- api_token = self.knex.post("/users/test-user",
34
- json={"email": f"clitestuser_{random_str()}@gmail.com"})
35
- except GeoseeqOtherError:
36
- print(f"Could not create test user on \"{ENDPOINT}\". If you are running this test suite "\
37
- "against a live server, please set the GEOSEEQ_API_TOKEN environment variable to a "\
38
- "valid API token.",
39
- file=sys.stderr)
40
- raise
41
- except ConnectionError:
42
- print(f"Could not connect to GeoSeeq Server at \"{ENDPOINT}\".",
43
- file=sys.stderr)
44
- raise
45
- self.knex.add_api_token(api_token)
46
- else:
47
- self.knex.add_api_token(TOKEN)
48
- try:
49
- me = self.knex.get("/users/me") # Test that the token is valid
50
- self.username = me["name"]
51
- self.org_name = f"API_TEST_ORG 1 {self.username}"
52
- except GeoseeqNotFoundError:
53
- print(f"Could not connect to GeoSeeq Server at \"{ENDPOINT}\" with the provided token. "\
54
- "Is it possible that you set thd testing endpoint to a front end url instead of "\
55
- "the corresponding backend url?",
56
- file=sys.stderr)
57
- raise
58
-
59
- def test_create_org(self):
60
- """Test that we can create an org."""
61
- org = Organization(self.knex, self.org_name)
62
- org.idem()
63
- self.assertTrue(org.uuid)
64
-
65
-
66
- def test_create_project(self):
67
- """Test that we can create a project."""
68
- key = random_str()
69
- org = Organization(self.knex, self.org_name)
70
-
71
- proj = org.project(f"my_client_test_project {key}")
72
- proj.create()
73
- self.assertTrue(org.uuid)
74
- self.assertTrue(proj.uuid)
75
-
76
- def test_create_project_result_folder(self):
77
- """Test that we can create a result folder in a project."""
78
- key = random_str()
79
- org = Organization(self.knex, self.org_name)
80
- proj = org.project(f"my_client_test_proj {key}")
81
- # N.B. It should NOT be necessary to call <parent>.create()
82
- result_folder = proj.result_folder(f"my_client_test_module_name") # no {key} necessary
83
- result_folder.create()
84
- self.assertTrue(org.uuid)
85
- self.assertTrue(proj.uuid)
86
- self.assertTrue(result_folder.uuid)
87
-
88
- def test_create_project_result_file(self):
89
- """Test that we can create a result file in a project."""
90
- key = random_str()
91
- org = Organization(self.knex, self.org_name)
92
- proj = org.project(f"my_client_test_proj {key}")
93
- result_folder = proj.result_folder(f"my_client_test_module_name") # no {key} necessary
94
- # N.B. It should NOT be necessary to call <parent>.create()
95
- result_file = result_folder.result_file("my_client_test_field_name", {"foo": "bar"})
96
- result_file.create()
97
- self.assertTrue(org.uuid)
98
- self.assertTrue(proj.uuid)
99
- self.assertTrue(result_folder.uuid)
100
- self.assertTrue(result_file.uuid)
101
-
102
- def test_create_sample(self):
103
- """Test that we can create a sample."""
104
- key = random_str()
105
- org = Organization(self.knex, self.org_name)
106
- proj = org.project(f"my_client_test_proj {key}")
107
- # N.B. It should NOT be necessary to call <parent>.create()
108
- samp = proj.sample(f"my_client_test_sample {key}")
109
- samp.create()
110
- self.assertTrue(org.uuid)
111
- self.assertTrue(proj.uuid)
112
- self.assertTrue(samp.uuid)
113
-
114
- def test_add_sample(self):
115
- """Test that we can create a sample and add it to a different project."""
116
- key = random_str()
117
- org = Organization(self.knex, self.org_name)
118
- proj1 = org.project(f"my_client_test_proj1 {key}")
119
- samp = proj1.sample(f"my_client_test_sample {key}").create()
120
-
121
- proj2 = org.project(f"my_client_test_proj2 {key}").create()
122
- proj2.add_sample(samp).save()
123
- self.assertIn(samp.uuid, {samp.uuid for samp in proj2.get_samples()})
124
-
125
- def test_get_samples_project(self):
126
- """Test that we can get the samples in a project."""
127
- key = random_str()
128
- org = Organization(self.knex, self.org_name)
129
- proj = org.project(f"my_client_test_proj {key}")
130
- samp_names = [f"my_client_test_sample_{i} {key}" for i in range(10)]
131
- for samp_name in samp_names:
132
- proj.sample(samp_name).create()
133
- retrieved_proj = org.project(f"my_client_test_proj {key}").get()
134
- retrieved_names = set()
135
- for samp in retrieved_proj.get_samples():
136
- retrieved_names.add(samp.name)
137
- self.assertTrue(samp.uuid)
138
- for samp_name in samp_names:
139
- self.assertIn(samp_name, retrieved_names)
140
-
141
- def test_get_result_folders_in_project(self):
142
- """Test that we can get the result folders in a project."""
143
- key = random_str()
144
- org = Organization(self.knex, self.org_name)
145
- proj = org.project(f"my_client_test_proj {key}")
146
- result_names = [("my_client_test_module", f"replicate_{i}") for i in range(10)]
147
- for module_name, replicate in result_names:
148
- proj.result_folder(module_name, replicate=replicate).create()
149
- retrieved_proj = org.project(f"my_client_test_proj {key}").get()
150
- retrieved_names = set()
151
- for result in retrieved_proj.get_result_folders():
152
- retrieved_names.add((result.module_name, result.replicate))
153
- self.assertTrue(result.uuid)
154
- for result_name_rep in result_names:
155
- self.assertIn(result_name_rep, retrieved_names)
156
-
157
- def test_get_result_folders_in_sample(self):
158
- """Test that we can get the result folders in a sample."""
159
- key = random_str()
160
- org = Organization(self.knex, self.org_name)
161
- proj = org.project(f"my_client_test_proj {key}")
162
- samp = proj.sample(f"my_client_test_sample {key}").create()
163
- result_names = [("my_client_test_module", f"replicate_{i}") for i in range(10)]
164
- for module_name, replicate in result_names:
165
- samp.result_folder(module_name, replicate=replicate).create()
166
- retrieved = proj.sample(f"my_client_test_sample {key}").get()
167
- retrieved_names = set()
168
- for result in retrieved.get_result_folders():
169
- retrieved_names.add((result.module_name, result.replicate))
170
- self.assertTrue(result.uuid)
171
- for result_name_rep in result_names:
172
- self.assertIn(result_name_rep, retrieved_names)
173
-
174
- def test_get_result_files(self):
175
- """Test that we can get the files in a result folder."""
176
- key = random_str()
177
- org = Organization(self.knex, self.org_name)
178
- proj = org.project(f"my_client_test_proj {key}")
179
- samp = proj.sample(f"my_client_test_sample {key}")
180
- result_folder = samp.result_folder("my_client_test_module").create()
181
- self.assertTrue(proj.uuid)
182
-
183
- field_names = [f"field_{i}" for i in range(10)]
184
- for field_name in field_names:
185
- result_folder.field(field_name).create()
186
-
187
- retrieved = samp.result_folder("my_client_test_module").get()
188
- retrieved_names = set()
189
- for result in retrieved.get_fields():
190
- retrieved_names.add(result.name)
191
- self.assertTrue(result.uuid)
192
- for result_name_rep in field_names:
193
- self.assertIn(result_name_rep, retrieved_names)
194
-
195
- def test_modify_sample(self):
196
- """Test that we can modify a sample after creation"""
197
- key = random_str()
198
- org = Organization(self.knex, self.org_name)
199
- proj = org.project(f"my_client_test_proj {key}")
200
- # N.B. It should NOT be necessary to call <parent>.create()
201
- samp = proj.sample(f"my_client_test_sample {key}")
202
- samp.create()
203
- self.assertTrue(samp.uuid)
204
- self.assertTrue(samp._already_fetched)
205
- self.assertFalse(samp._modified)
206
- samp.metadata = {f"metadata_{key}": "some_new_metadata"}
207
- self.assertTrue(samp._modified)
208
- samp.save()
209
- self.assertTrue(samp._already_fetched)
210
- self.assertFalse(samp._modified)
211
- retrieved = proj.sample(f"my_client_test_sample {key}").get()
212
- self.assertIn(f"metadata_{key}", retrieved.metadata)
213
-
214
- def test_create_sample_result_folder(self):
215
- """Test that we can create a result folder in a sample."""
216
- key = random_str()
217
- org = Organization(self.knex, self.org_name)
218
- proj = org.project(f"my_client_test_proj {key}")
219
- samp = proj.sample(f"my_client_test_sample {key}")
220
- # N.B. It should NOT be necessary to call <parent>.create()
221
- result_folder = samp.result_folder(f"my_client_test_module_name") # no {key} necessary
222
- result_folder.create()
223
- self.assertTrue(org.uuid)
224
- self.assertTrue(proj.uuid)
225
- self.assertTrue(samp.uuid)
226
- self.assertTrue(result_folder.uuid)
227
-
228
- def test_create_sample_result_file(self):
229
- """Test that we can create a result file in a sample."""
230
- key = random_str()
231
- org = Organization(self.knex, self.org_name)
232
- proj = org.project(f"my_client_test_proj {key}")
233
- samp = proj.sample(f"my_client_test_sample {key}")
234
- result_folder = samp.result_folder(f"my_client_test_module_name") # no {key} necessary
235
- # N.B. It should NOT be necessary to call <parent>.create()
236
- result_file = result_folder.result_file("my_client_test_field_name", {"foo": "bar"})
237
- result_file.create()
238
- self.assertTrue(org.uuid)
239
- self.assertTrue(proj.uuid)
240
- self.assertTrue(samp.uuid)
241
- self.assertTrue(result_folder.uuid)
242
- self.assertTrue(result_file.uuid)
243
-
244
- @skip("failing on server (2023-10-24)")
245
- def test_modify_sample_result_file(self):
246
- """Test that we can modify a result file in a sample."""
247
- key = random_str()
248
- org = Organization(self.knex, self.org_name)
249
- proj = org.project(f"my_client_test_proj {key}")
250
- samp = proj.sample(f"my_client_test_sample {key}")
251
- result_folder = samp.result_folder(f"my_client_test_module_name") # no {key} necessary
252
- # N.B. It should NOT be necessary to call <parent>.create()
253
- result_file = result_folder.result_file(f"my_client_test_file_name {key}", {"foo": "bar"})
254
- result_file.create()
255
- self.assertTrue(result_file.uuid)
256
- result_file.stored_data = {"foo": "bizz"} # TODO: handle deep modifications
257
- result_file.save()
258
- retrieved = result_folder.result_file(f"my_client_test_file_name {key}").get()
259
- self.assertEqual(retrieved.stored_data["foo"], "bizz")
260
-
261
- def test_get_project_manifest(self):
262
- """Test that we can get a project manifest."""
263
- key = random_str()
264
- org = Organization(self.knex, self.org_name)
265
- proj = org.project(f"my_client_test_proj {key}")
266
- samp = proj.sample(f"my_client_test_sample {key}")
267
- result_folder = samp.result_folder(f"my_client_test_module_name") # no {key} necessary
268
- result_file = result_folder.result_file("my_client_test_field_name", {"foo": "bar"})
269
- result_file.create()
270
- manifest = proj.get_manifest()
271
- self.assertTrue(manifest)
272
-
273
- def test_get_sample_manifest(self):
274
- """Test that we can get a sample manifest."""
275
- key = random_str()
276
- org = Organization(self.knex, self.org_name)
277
- proj = org.project(f"my_client_test_proj {key}")
278
- samp = proj.sample(f"my_client_test_sample {key}")
279
- result_folder = samp.result_folder(f"my_client_test_module_name") # no {key} necessary
280
- result_file = result_folder.result_file("my_client_test_field_name", {"foo": "bar"})
281
- result_file.create()
282
- manifest = samp.get_manifest()
283
- self.assertTrue(manifest)
tests/test_plotting.py DELETED
@@ -1,29 +0,0 @@
1
- """Test suite for plotting library."""
2
- import random
3
- from os import environ
4
- from os.path import dirname, join
5
- from unittest import TestCase, skip
6
-
7
- from geoseeq.plotting.map import Map
8
-
9
-
10
- class TestGeoseeqPlotting(TestCase):
11
- """Test suite for packet building."""
12
-
13
- def test_make_map_complex(self):
14
- """Test that we can create a map and turn it into a dict."""
15
- map = Map()\
16
- .set_center(0, 0)\
17
- .set_zoom(2)\
18
- .add_light_base_map()\
19
- .add_administrative_overlay()\
20
- .add_places_overlay()
21
- map.to_dict()
22
-
23
- def test_make_map_simple(self):
24
- """Test that we can create a map and turn it into a dict."""
25
- map = Map()\
26
- .add_light_base_map()
27
- map.to_dict()
28
-
29
-
File without changes