geoseeq 0.7.3.dev0__py3-none-any.whl → 0.7.3.dev2__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,7 +3,9 @@ from typing import Literal
3
3
 
4
4
  from geoseeq import ProjectResultFile
5
5
  from geoseeq.id_constructors import result_file_from_blob
6
+ from geoseeq.id_constructors.from_ids import result_file_from_id
6
7
  from geoseeq.remote_object import RemoteObject
8
+ from geoseeq import GeoseeqNotFoundError
7
9
 
8
10
  logger = logging.getLogger("geoseeq_api")
9
11
 
@@ -12,134 +14,212 @@ class Dashboard(RemoteObject):
12
14
  parent_field = "project"
13
15
  remote_fields = ["is_default"]
14
16
 
15
- def __init__(self, knex, project, name="Default dashboard", is_default=False):
17
+ def __init__(
18
+ self, knex, project, title="Default dashboard", default=False, tiles=[]
19
+ ):
16
20
  super().__init__(self)
17
21
  self.knex = knex
18
22
  self.project = project
19
- self._name = name
20
- self.tiles = []
21
- self.is_default = is_default
23
+ self.title = title
24
+ self._original_title = title
25
+ self.tiles = tiles
26
+ self.default = default
22
27
 
23
28
  def _get(self, allow_overwrite=False):
24
29
  blob = self.knex.get(f"sample_groups/{self.project.uuid}/dashboard-list")
25
- blob = blob["dashboard_data"][self.name]
30
+ if self.title not in blob["dashboard_data"].keys():
31
+ raise GeoseeqNotFoundError(f"Dashboard '{self.title}' not found.")
32
+ blob = blob["dashboard_data"][self.title]
26
33
  for tile_blob in blob["tiles"]:
27
34
  tile = DashboardTile.from_blob(self, tile_blob)
28
35
  self.tiles.append(tile)
29
36
  blob.pop("tiles")
30
37
  self.load_blob(blob, allow_overwrite=allow_overwrite)
31
38
 
32
- def _save(self):
39
+ def save(self):
40
+ put_data = {
41
+ "name": self._original_title,
42
+ "is_default": self.default,
43
+ "new_name": self.title,
44
+ }
45
+ self.knex.put(
46
+ f"sample_groups/{self.project.uuid}/dashboard-list",
47
+ json=put_data,
48
+ json_response=False,
49
+ )
50
+ self._original_title = self.title
33
51
  self.save_tiles()
34
52
 
35
53
  def save_tiles(self):
36
54
  post_data = {"tiles": [tile._get_post_data() for tile in self.tiles]}
37
- blob = self.knex.post(
38
- f"sample_groups/{self.project.uuid}/dashboard/{self.name}/tiles",
55
+ self.knex.post(
56
+ f"sample_groups/{self.project.uuid}/dashboard/{self._original_title}/tiles",
39
57
  json=post_data,
40
58
  json_response=False,
41
59
  )
42
- print(blob)
43
60
 
44
61
  def _create(self):
45
- post_data = {"name": self.name, "is_default": self.is_default}
46
- blob = self.knex.post(
47
- f"sample_groups/{self.project.uuid}/dashboard", json=post_data
62
+ post_data = {"name": self.title, "is_default": self.default}
63
+ self.knex.post(
64
+ f"sample_groups/{self.project.uuid}/dashboard-list", json=post_data
48
65
  )
49
- self.load_blob(blob)
50
66
 
51
- def tile(
67
+ def add_tile(
52
68
  self,
53
- title,
54
69
  result_file,
55
- style: Literal["col-span-1", "col-span-2"] = "col-span-1",
70
+ title,
71
+ width: Literal["hafl", "full"] = "half",
56
72
  ):
73
+ style = "col-span-1" if width == "half" else "col-span-2"
57
74
  result_file.get()
58
75
  tile = DashboardTile(self.knex, self, title, result_file, style=style)
59
76
  self.tiles.append(tile)
60
77
  self._modified = True
61
- return tile
62
78
 
63
- def add_tile(self, tile):
64
- self.tiles.append(tile)
65
- self._modified = True
66
-
67
- @property
68
- def name(self):
69
- return self._name
79
+ def delete(self):
80
+ self.knex.delete(
81
+ f"sample_groups/{self.project.uuid}/dashboard-list?name={self._original_title}"
82
+ )
70
83
 
71
84
  def __str__(self):
72
- return f'<Geoseeq Dashboard: {self.project.grn} "{self.name}"/>'
85
+ return f'<Geoseeq Dashboard: {self.project.grn} "{self._original_title}"/>'
73
86
 
74
87
  def __repr__(self):
75
88
  return str(self)
76
89
 
77
90
  @property
78
91
  def grn(self):
79
- return f'grn:dashboard:{self.project.uuid}:"{self.name}"'
92
+ return f'grn:dashboard:{self.project.uuid}:"{self._original_title}"'
80
93
 
81
94
  def pre_hash(self):
82
- return "DASH" + self.project.uuid + self.name
95
+ return "DASH" + self.project.uuid + self._original_title
96
+
97
+
98
+ class DashboardTile:
99
+ def __init__(self, knex, dashboard, title, result_file, style="col-span-1"):
100
+ self.knex = knex
101
+ self.dashboard = dashboard
102
+ self.title = title
103
+ self.style = style
104
+ self.result_file = result_file
105
+
106
+ def _get_post_data(self):
107
+ out = {
108
+ "field_uuid": self.result_file.uuid,
109
+ "field_type": (
110
+ "group" if isinstance(self.result_file, ProjectResultFile) else "sample"
111
+ ),
112
+ "style": self.style,
113
+ "title": self.title,
114
+ "has_related_field": False,
115
+ }
116
+ return out
117
+
118
+ @classmethod
119
+ def from_blob(cls, dashboard, blob):
120
+ result_file = result_file_from_blob(dashboard.knex, blob["viz_field"])
121
+ return cls(
122
+ dashboard.knex, dashboard, blob["title"], result_file, style=blob["style"]
123
+ )
124
+
125
+ def __str__(self) -> str:
126
+ return f'<Geoseeq DashboardTile: {self.dashboard.grn} "{self.title}" />'
127
+
128
+ def __repr__(self) -> str:
129
+ return str(self)
83
130
 
84
131
 
85
132
  class SampleDashboard(RemoteObject):
86
133
  """Dashboard client for a single sample."""
87
134
 
88
135
  parent_field = "sample"
89
- remote_fields = ["is_default"]
136
+ remote_fields = ["uuid", "title", "default", "created_at", "updated_at"]
90
137
 
91
- def __init__(self, knex, sample, name="Default dashboard", is_default=False):
138
+ def __init__(self, knex, sample, title="Default dashboard", default=False):
92
139
  super().__init__(self)
93
140
  self.knex = knex
94
141
  self.sample = sample
95
- self._name = name
142
+ self.title = title
96
143
  self.tiles = []
97
- self.is_default = is_default
144
+ self.default = default
98
145
 
99
146
  def _get(self, allow_overwrite=False):
100
- blob = self.knex.get(f"samples/{self.sample.uuid}/dashboard-list")
101
- blob = blob["dashboard_data"][self.name]
102
- for tile_blob in blob["tiles"]:
103
- tile = DashboardTile.from_blob(self, tile_blob)
104
- self.tiles.append(tile)
105
- blob.pop("tiles")
147
+ blob = self.knex.get(f"samples/{self.sample.uuid}/dashboards")
148
+ try:
149
+ blob = [
150
+ dashboard_blob
151
+ for dashboard_blob in blob["results"]
152
+ if dashboard_blob["title"] == self.title
153
+ ][0]
154
+ except IndexError:
155
+ raise ValueError(f"There is no existing dashboard with title {self.title}")
156
+
106
157
  self.load_blob(blob, allow_overwrite=allow_overwrite)
107
158
 
108
- def _save(self):
159
+ # Load tiles
160
+ tiles_res = self.knex.get(f"samples/dashboards/{self.uuid}/tiles")
161
+ for tile_blob in tiles_res["results"]:
162
+ tile = SampleDashboardTile.from_blob(self, tile_blob)
163
+ self.tiles.append(tile)
164
+
165
+ def save(self):
166
+ data = self._get_post_data()
167
+ url = f"samples/{self.sample.uuid}/dashboards/{self.uuid}"
168
+ self.knex.put(url, json=data)
109
169
  self.save_tiles()
110
170
 
111
171
  def save_tiles(self):
112
172
  post_data = {"tiles": [tile._get_post_data() for tile in self.tiles]}
113
- self.knex.post(
114
- f"samples/{self.sample.uuid}/dashboard/{self.name}/tiles",
173
+ self.knex.put(
174
+ f"samples/dashboards/{self.uuid}/tiles",
115
175
  json=post_data,
116
176
  json_response=False,
117
177
  )
118
178
 
179
+ def delete(self):
180
+ self.knex.delete(f"samples/{self.sample.uuid}/dashboards/{self.uuid}")
181
+ self._already_fetched = False
182
+ self._deleted = True
183
+
119
184
  def _create(self):
120
- post_data = {"name": self.name, "is_default": self.is_default}
121
- blob = self.knex.post(f"samples/{self.sample.uuid}/dashboard", json=post_data)
185
+ post_data = {
186
+ "title": self.title,
187
+ "sample": self.sample.uuid,
188
+ "default": self.default,
189
+ }
190
+ blob = self.knex.post(f"samples/{self.sample.uuid}/dashboards", json=post_data)
122
191
  self.load_blob(blob)
123
192
 
124
- def tile(
125
- self,
126
- title,
127
- result_file,
128
- style: Literal["col-span-1", "col-span-2"] = "col-span-1",
129
- ):
193
+ def _get_post_data(self):
194
+ out = {
195
+ "sample": self.sample.uuid,
196
+ "title": self.title,
197
+ "default": self.default,
198
+ }
199
+ return out
200
+
201
+ def add_tile(self, result_file, title, width="half", order=None):
130
202
  result_file.get()
131
- tile = DashboardTile(self.knex, self, title, result_file, style=style)
203
+ tile = SampleDashboardTile(
204
+ self.knex, self, title, result_file, width=width, order=order
205
+ )
132
206
  self.tiles.append(tile)
133
207
  self._modified = True
134
- return tile
135
208
 
136
- def add_tile(self, tile):
137
- self.tiles.append(tile)
138
- self._modified = True
209
+ @classmethod
210
+ def from_blob(cls, sample, blob):
211
+ instance = cls(
212
+ sample.knex,
213
+ sample,
214
+ blob["title"],
215
+ blob["default"],
216
+ )
217
+ instance.uuid = blob["uuid"]
218
+ return instance
139
219
 
140
220
  @property
141
221
  def name(self):
142
- return self._name
222
+ return self.title
143
223
 
144
224
  def __str__(self):
145
225
  return f'<Geoseeq SampleDashboard: {self.sample.brn} "{self.name}"/>'
@@ -155,32 +235,35 @@ class SampleDashboard(RemoteObject):
155
235
  return "DASH" + self.sample.uuid + self.name
156
236
 
157
237
 
158
- class DashboardTile:
159
-
160
- def __init__(self, knex, dashboard, title, result_file, style="col-span-1"):
238
+ class SampleDashboardTile:
239
+ def __init__(self, knex, dashboard, title, result_file, width="half", order=None):
161
240
  self.knex = knex
162
241
  self.dashboard = dashboard
163
242
  self.title = title
164
- self.style = style
243
+ self.width = width
165
244
  self.result_file = result_file
245
+ self.order = order
166
246
 
167
247
  def _get_post_data(self):
168
248
  out = {
169
- "field_uuid": self.result_file.uuid,
170
- "field_type": (
171
- "group" if isinstance(self.result_file, ProjectResultFile) else "sample"
172
- ),
173
- "style": self.style,
249
+ "field": self.result_file.uuid,
250
+ "dashboard": self.dashboard.uuid,
251
+ "width": self.width,
174
252
  "title": self.title,
175
- "has_related_field": False,
253
+ "order": self.order,
176
254
  }
177
255
  return out
178
256
 
179
257
  @classmethod
180
258
  def from_blob(cls, dashboard, blob):
181
- result_file = result_file_from_blob(dashboard.knex, blob["viz_field"])
259
+ result_file = result_file_from_id(dashboard.knex, blob["field_obj"]["uuid"])
182
260
  return cls(
183
- dashboard.knex, dashboard, blob["title"], result_file, style=blob["style"]
261
+ dashboard.knex,
262
+ dashboard,
263
+ blob["title"],
264
+ result_file,
265
+ width=blob["width"],
266
+ order=blob["order"],
184
267
  )
185
268
 
186
269
  def __str__(self) -> str:
geoseeq/project.py CHANGED
@@ -3,6 +3,7 @@ import urllib
3
3
 
4
4
  import pandas as pd
5
5
 
6
+
6
7
  from .pipeline import Pipeline
7
8
  from .remote_object import RemoteObject
8
9
  from .result import ProjectResultFolder
@@ -59,11 +60,23 @@ class Project(RemoteObject):
59
60
  self._modified = True
60
61
 
61
62
  def get_post_data(self):
62
- data = {field: getattr(self, field) for field in self.remote_fields if hasattr(self, field)}
63
+ data = {
64
+ field: getattr(self, field)
65
+ for field in self.remote_fields
66
+ if hasattr(self, field)
67
+ }
63
68
  data["organization"] = self.org.uuid
64
- data['description'] = self.description if hasattr(self, 'description') and self.description else self.name
65
- data['privacy_level'] = self.privacy_level if hasattr(self, 'privacy_level') and self.privacy_level else 'private'
66
- data['storage_provider_name'] = self.storage_provider
69
+ data["description"] = (
70
+ self.description
71
+ if hasattr(self, "description") and self.description
72
+ else self.name
73
+ )
74
+ data["privacy_level"] = (
75
+ self.privacy_level
76
+ if hasattr(self, "privacy_level") and self.privacy_level
77
+ else "private"
78
+ )
79
+ data["storage_provider_name"] = self.storage_provider
67
80
  if self.new_org:
68
81
  if isinstance(self.new_org, RemoteObject):
69
82
  data["organization"] = self.new_org.uuid
@@ -90,7 +103,9 @@ class Project(RemoteObject):
90
103
  url = f"sample_groups/{self.uuid}/samples"
91
104
  chunk_size = 100
92
105
  for i in range(0, len(sample_uuids), chunk_size):
93
- self.knex.post(url, json={"sample_uuids": sample_uuids[i : i + chunk_size]})
106
+ self.knex.post(
107
+ url, json={"sample_uuids": sample_uuids[i : i + chunk_size]}
108
+ )
94
109
  self._sample_cache = []
95
110
 
96
111
  def _delete_sample_list(self):
@@ -126,7 +141,7 @@ class Project(RemoteObject):
126
141
  json=post_data,
127
142
  )
128
143
  self.load_blob(blob)
129
-
144
+
130
145
  def add_sample_uuids(self, sample_uuids):
131
146
  """Return this group and add a sample to this group.
132
147
 
@@ -166,7 +181,7 @@ class Project(RemoteObject):
166
181
 
167
182
  def analysis_result(self, *args, **kwargs):
168
183
  """Return a ProjectResultFolder object for this project.
169
-
184
+
170
185
  Alias for result_folder."""
171
186
  return self.result_folder(*args, **kwargs)
172
187
 
@@ -177,7 +192,9 @@ class Project(RemoteObject):
177
192
  yield sample
178
193
  return
179
194
  url = f"sample_groups/{self.uuid}/samples-list?page=1&page_size=100"
180
- for sample_blob in paginated_iterator(self.knex, url, error_handler=error_handler):
195
+ for sample_blob in paginated_iterator(
196
+ self.knex, url, error_handler=error_handler
197
+ ):
181
198
  sample = self.sample(sample_blob["name"])
182
199
  sample.uuid = sample_blob["uuid"]
183
200
  sample.metadata = sample_blob["metadata"]
@@ -199,12 +216,16 @@ class Project(RemoteObject):
199
216
  yield sample.uuid
200
217
  return
201
218
  url = f"sample_groups/{self.uuid}/samples-list?page=1"
202
- for sample_blob in paginated_iterator(self.knex, url, error_handler=error_handler):
203
- yield sample_blob['uuid']
219
+ for sample_blob in paginated_iterator(
220
+ self.knex, url, error_handler=error_handler
221
+ ):
222
+ yield sample_blob["uuid"]
204
223
 
205
224
  def _batch_sample_uuids(self, batch_size, input_sample_uuids=[]):
206
225
  """Yield batches of sample uuids."""
207
- uuids_to_batch = input_sample_uuids if input_sample_uuids else self.get_sample_uuids()
226
+ uuids_to_batch = (
227
+ input_sample_uuids if input_sample_uuids else self.get_sample_uuids()
228
+ )
208
229
  sample_uuids = []
209
230
  for sample_uuid in uuids_to_batch:
210
231
  sample_uuids.append(sample_uuid)
@@ -216,7 +237,7 @@ class Project(RemoteObject):
216
237
 
217
238
  def get_analysis_results(self, cache=True):
218
239
  """Yield ProjectResultFolder objects for this project fetched from the server.
219
-
240
+
220
241
  Alias for get_result_folders."""
221
242
  return self.get_result_folders(cache=cache)
222
243
 
@@ -262,24 +283,25 @@ class Project(RemoteObject):
262
283
  rows.extend(blob["results"])
263
284
  url = blob["next"]
264
285
  return pd.DataFrame(rows)
265
-
266
-
286
+
267
287
  @property
268
288
  def n_samples(self):
269
289
  """Return the number of samples in this project."""
270
- if hasattr(self, 'samples_count') and self.samples_count is not None:
290
+ if hasattr(self, "samples_count") and self.samples_count is not None:
271
291
  return self.samples_count
272
292
  return len(list(self.get_sample_uuids()))
273
-
274
- def bulk_find_files(self,
275
- sample_uuids=[],
276
- sample_name_includes=[],
277
- folder_types="all",
278
- folder_names=[],
279
- file_names=[],
280
- extensions=[],
281
- with_versions=False,
282
- use_batches_cutoff=500):
293
+
294
+ def bulk_find_files(
295
+ self,
296
+ sample_uuids=[],
297
+ sample_name_includes=[],
298
+ folder_types="all",
299
+ folder_names=[],
300
+ file_names=[],
301
+ extensions=[],
302
+ with_versions=False,
303
+ use_batches_cutoff=500,
304
+ ):
283
305
  """Return a dict with links to download files that match the given criteria.
284
306
 
285
307
  Options:
@@ -291,36 +313,48 @@ class Project(RemoteObject):
291
313
  - extensions: list of strings; finds files with these file extensions
292
314
  - with_versions: bool; if True, include all versions of files in results
293
315
  """
316
+
294
317
  def _my_bulk_find(sample_uuids=None): # curry to save typing
295
- return self._bulk_find_files_batch(sample_uuids=sample_uuids or [],
296
- sample_name_includes=sample_name_includes,
297
- folder_types=folder_types,
298
- folder_names=folder_names,
299
- file_names=file_names,
300
- extensions=extensions,
301
- with_versions=with_versions)
318
+ return self._bulk_find_files_batch(
319
+ sample_uuids=sample_uuids or [],
320
+ sample_name_includes=sample_name_includes,
321
+ folder_types=folder_types,
322
+ folder_names=folder_names,
323
+ file_names=file_names,
324
+ extensions=extensions,
325
+ with_versions=with_versions,
326
+ )
327
+
302
328
  n_samples = len(sample_uuids) if sample_uuids else self.n_samples
303
329
  if n_samples < use_batches_cutoff:
304
330
  logger.debug(f"Using single batch bulk_find for {n_samples} samples")
305
331
  return _my_bulk_find(sample_uuids=sample_uuids)
306
332
  else:
307
333
  logger.debug(f"Using multi batch bulk_find for {n_samples} samples")
308
- merged_response = {'file_size_bytes': 0, 'links': {}, 'no_size_info_count': 0}
309
- for batch in self._batch_sample_uuids(use_batches_cutoff - 1, input_sample_uuids=sample_uuids):
334
+ merged_response = {
335
+ "file_size_bytes": 0,
336
+ "links": {},
337
+ "no_size_info_count": 0,
338
+ }
339
+ for batch in self._batch_sample_uuids(
340
+ use_batches_cutoff - 1, input_sample_uuids=sample_uuids
341
+ ):
310
342
  response = _my_bulk_find(sample_uuids=batch)
311
- merged_response['file_size_bytes'] += response['file_size_bytes']
312
- merged_response['links'].update(response['links'])
313
- merged_response['no_size_info_count'] += response['no_size_info_count']
343
+ merged_response["file_size_bytes"] += response["file_size_bytes"]
344
+ merged_response["links"].update(response["links"])
345
+ merged_response["no_size_info_count"] += response["no_size_info_count"]
314
346
  return merged_response
315
-
316
- def _bulk_find_files_batch(self,
317
- sample_uuids=None,
318
- sample_name_includes=None,
319
- folder_types=None,
320
- folder_names=None,
321
- file_names=None,
322
- extensions=None,
323
- with_versions=False):
347
+
348
+ def _bulk_find_files_batch(
349
+ self,
350
+ sample_uuids=None,
351
+ sample_name_includes=None,
352
+ folder_types=None,
353
+ folder_names=None,
354
+ file_names=None,
355
+ extensions=None,
356
+ with_versions=False,
357
+ ):
324
358
  data = {
325
359
  "sample_uuids": sample_uuids or [],
326
360
  "sample_names": sample_name_includes or [],
@@ -328,22 +362,105 @@ class Project(RemoteObject):
328
362
  "folder_names": folder_names or [],
329
363
  "file_names": file_names or [],
330
364
  "extensions": extensions or [],
331
- "with_versions": with_versions
365
+ "with_versions": with_versions,
332
366
  }
333
367
  url = f"sample_groups/{self.uuid}/download"
334
368
  response = self.knex.post(url, data)
335
369
  return response
336
-
370
+
337
371
  def run_app(self, app: Pipeline, input_parameters=None):
338
372
  """Run an app on this group."""
339
373
  if not input_parameters:
340
374
  input_parameters = app.get_input_parameters()
341
375
  params = {
342
- 'pipeline_id': app.uuid,
343
- 'sample_uuids': list(self.get_sample_uuids()),
344
- 'input_parameters': input_parameters,
376
+ "pipeline_id": app.uuid,
377
+ "sample_uuids": list(self.get_sample_uuids()),
378
+ "input_parameters": input_parameters,
345
379
  }
346
- self.knex.post(f"sample_groups/{self.uuid}/run_app", json=params, json_response=False)
380
+ self.knex.post(
381
+ f"sample_groups/{self.uuid}/run_app", json=params, json_response=False
382
+ )
383
+
384
+ def create_dashboard(self, title="Default dashboard", default=False):
385
+ """Create a dashboard for this project."""
386
+ from geoseeq.dashboard.dashboard import Dashboard
387
+
388
+ post_data = {"name": title, "is_default": default}
389
+ self.knex.post(f"sample_groups/{self.uuid}/dashboard-list", json=post_data)
390
+
391
+ return Dashboard(knex=self.knex, project=self, title=title, default=default)
392
+
393
+ def get_default_dashbaord(self):
394
+ """Get the default dashboard for this project."""
395
+ from geoseeq.dashboard.dashboard import Dashboard, DashboardTile
396
+
397
+ resp = self.knex.get(f"sample_groups/{self.uuid}/dashboard-list")
398
+ try:
399
+ dashboard_name = [
400
+ name
401
+ for name, data in resp["dashboard_data"].items()
402
+ if data["is_default"] == True
403
+ ][0]
404
+ blob = resp["dashboard_data"][dashboard_name]
405
+ tiles = []
406
+ for tile_blob in blob["tiles"]:
407
+ tile = DashboardTile.from_blob(self, tile_blob)
408
+ tiles.append(tile)
409
+ dashboard = Dashboard(
410
+ self.knex,
411
+ project=self,
412
+ title=dashboard_name,
413
+ default=blob["is_default"],
414
+ tiles=tiles,
415
+ )
416
+ return dashboard
417
+ except IndexError:
418
+ logger.warning("Default dashboard not found.")
419
+ pass
420
+ return None
421
+
422
+ def get_or_create_default_dashbaord(self):
423
+ """Get the default dashboard for this project or create it if does not exist."""
424
+ default_dashboard = self.get_default_dashbaord()
425
+ if default_dashboard:
426
+ return default_dashboard
427
+ else:
428
+ return self.create_dashboard(default=True)
429
+
430
+ def get_dashbaord_by_title(self, title):
431
+ """Get dashboard for this project by the title of the dashboard."""
432
+ from geoseeq.dashboard.dashboard import Dashboard, DashboardTile
433
+
434
+ resp = self.knex.get(f"sample_groups/{self.uuid}/dashboard-list")
435
+ try:
436
+ dashboard_name = [
437
+ name for name in resp["dashboard_data"].keys() if name == title
438
+ ][0]
439
+ blob = resp["dashboard_data"][dashboard_name]
440
+ tiles = []
441
+ for tile_blob in blob["tiles"]:
442
+ tile = DashboardTile.from_blob(self, tile_blob)
443
+ tiles.append(tile)
444
+ dashboard = Dashboard(
445
+ knex=self.knex,
446
+ project=self,
447
+ title=dashboard_name,
448
+ default=blob["is_default"],
449
+ tiles=tiles,
450
+ )
451
+ return dashboard
452
+ except IndexError:
453
+ logger.warning(f"Dashboard {title} not found.")
454
+ pass
455
+ return None
456
+
457
+ def get_or_create_dashbaord_by_title(self, title: str):
458
+ """Get dashboard by title for this project or create it if does not exist."""
459
+ default_dashboard = self.get_dashbaord_by_title(title)
460
+ if default_dashboard:
461
+ return default_dashboard
462
+ else:
463
+ return self.create_dashboard(title=title, default=False)
347
464
 
348
465
  def __str__(self):
349
466
  return f"<Geoseeq::Project {self.name} {self.uuid} />"
@@ -353,7 +470,7 @@ class Project(RemoteObject):
353
470
 
354
471
  def pre_hash(self):
355
472
  return "PROJ" + self.name + self.org.pre_hash()
356
-
473
+
357
474
  @property
358
475
  def grn(self):
359
476
  return f"grn::project:{self.uuid}"
@@ -363,4 +480,5 @@ class Project(RemoteObject):
363
480
  self._modified = True
364
481
  return self
365
482
 
366
- SampleGroup = Project # alias for backwards compatibility
483
+
484
+ SampleGroup = Project # alias for backwards compatibility
geoseeq/sample.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import urllib
2
-
3
2
  from .remote_object import RemoteObject
4
3
  from .result import SampleResultFile, SampleResultFolder
5
4
 
@@ -32,7 +31,7 @@ class Sample(RemoteObject):
32
31
 
33
32
  @property
34
33
  def brn(self):
35
- return f'brn:{self.knex.instance_code()}:sample:{self.uuid}'
34
+ return f"brn:{self.knex.instance_code()}:sample:{self.uuid}"
36
35
 
37
36
  def nested_url(self):
38
37
  escaped_name = urllib.parse.quote(self.name, safe="")
@@ -63,15 +62,19 @@ class Sample(RemoteObject):
63
62
  self.load_blob(blob, allow_overwrite=allow_overwrite)
64
63
 
65
64
  def get_post_data(self):
66
- data = {field: getattr(self, field) for field in self.remote_fields if hasattr(self, field)}
65
+ data = {
66
+ field: getattr(self, field)
67
+ for field in self.remote_fields
68
+ if hasattr(self, field)
69
+ }
67
70
  data["library"] = self.lib.uuid
68
71
  if self.new_lib:
69
72
  if isinstance(self.new_lib, RemoteObject):
70
73
  data["library"] = self.new_lib.uuid
71
74
  else:
72
75
  data["library"] = self.new_lib
73
- if data['uuid'] is None:
74
- data.pop('uuid')
76
+ if data["uuid"] is None:
77
+ data.pop("uuid")
75
78
  return data
76
79
 
77
80
  def _create(self):
@@ -96,10 +99,10 @@ class Sample(RemoteObject):
96
99
 
97
100
  def analysis_result(self, *args, **kwargs):
98
101
  """Return a SampleResultFolder for this sample.
99
-
102
+
100
103
  This is an alias for result_folder."""
101
104
  return self.result_folder(*args, **kwargs)
102
-
105
+
103
106
  def get_result_folders(self, cache=True):
104
107
  """Yield sample analysis results fetched from the server."""
105
108
  self.get()
@@ -107,7 +110,7 @@ class Sample(RemoteObject):
107
110
  for ar in self._get_result_cache:
108
111
  yield ar
109
112
  return
110
- url = f"sample_ars?sample_id={self.uuid}"
113
+ url = f"sample_ars?sample_id={self.uuid}"
111
114
  result = self.knex.get(url)
112
115
  for result_blob in result["results"]:
113
116
  result = self.analysis_result(result_blob["module_name"])
@@ -126,7 +129,7 @@ class Sample(RemoteObject):
126
129
 
127
130
  def get_analysis_results(self, cache=True):
128
131
  """Yield sample analysis results fetched from the server.
129
-
132
+
130
133
  This is an alias for get_result_folders.
131
134
  """
132
135
  return self.get_result_folders(cache=cache)
@@ -135,14 +138,15 @@ class Sample(RemoteObject):
135
138
  """Return a manifest for this sample."""
136
139
  url = f"samples/{self.uuid}/manifest"
137
140
  return self.knex.get(url)
138
-
141
+
139
142
  def _grn_to_file(self, grn):
140
143
  from geoseeq.id_constructors.from_blobs import sample_result_file_from_blob
144
+
141
145
  file_uuid = grn.split(":")[-1]
142
146
  file_blob = self.knex.get(f"sample_ar_fields/{file_uuid}")
143
147
  file = sample_result_file_from_blob(self.knex, file_blob)
144
148
  return file
145
-
149
+
146
150
  def get_one_fastq(self):
147
151
  """Return a 2-ple, a fastq ResultFile and a string with the read type.
148
152
 
@@ -152,10 +156,10 @@ class Sample(RemoteObject):
152
156
  blob = self.knex.get(url)
153
157
  file = self._grn_to_file(blob["grn"])
154
158
  return file, blob["read_type"]
155
-
159
+
156
160
  def get_one_fastq_folder(self, preference_order=None):
157
161
  """Return a 3-ple, <read_type:str>, <folder_name:str>, a list with reads.
158
-
162
+
159
163
  If the read type is paired end, the list will contain 2-ples with reads.
160
164
 
161
165
  Default preference order is:
@@ -175,7 +179,7 @@ class Sample(RemoteObject):
175
179
  for folder_name, reads in all_fastqs[read_type].items():
176
180
  return read_type, folder_name, reads
177
181
  raise ValueError("No suitable fastq found")
178
-
182
+
179
183
  def get_all_fastqs(self):
180
184
  """Return a dict with the following structure:
181
185
 
@@ -217,7 +221,7 @@ class Sample(RemoteObject):
217
221
  self._grn_to_file(file_grns[0])
218
222
  )
219
223
  return files
220
-
224
+
221
225
  def get_one_fasta(self):
222
226
  """Return a 2-ple, a fasta ResultFile and a string with the read type.
223
227
 
@@ -228,6 +232,75 @@ class Sample(RemoteObject):
228
232
  file = self._grn_to_file(blob["grn"])
229
233
  return file, blob["read_type"]
230
234
 
235
+ def create_dashboard(self, title="Default dashboard", default=False):
236
+ "Create a new dashboard for the sample"
237
+ from geoseeq.dashboard.dashboard import SampleDashboard
238
+
239
+ post_data = {
240
+ "title": title,
241
+ "sample": self.uuid,
242
+ "default": default,
243
+ }
244
+ blob = self.knex.post(f"samples/{self.uuid}/dashboards", json=post_data)
245
+ dashboard = SampleDashboard(
246
+ self.knex, self, title=blob["title"], default=blob["default"]
247
+ )
248
+ dashboard.uuid = blob["uuid"]
249
+ dashboard._already_fetched = True
250
+ return dashboard
251
+
252
+ def get_default_dashbaord(self):
253
+ """Get the default dashboard for this sample."""
254
+ from geoseeq.dashboard.dashboard import SampleDashboard
255
+
256
+ dashboard_resp = self.knex.get(f"samples/{self.uuid}/dashboards")
257
+ try:
258
+ blob = [
259
+ dashboard_blob
260
+ for dashboard_blob in dashboard_resp["results"]
261
+ if dashboard_blob["default"] == True
262
+ ][0]
263
+ dashboard = SampleDashboard.from_blob(self, blob)
264
+ dashboard.get() # Tiles are not in the blob
265
+ return dashboard
266
+ except IndexError:
267
+ pass
268
+ return None
269
+
270
+ def get_or_create_default_dashbaord(self):
271
+ """Get the default dashboard for this sample or create it if does not exist."""
272
+ default_dashboard = self.get_default_dashbaord()
273
+ if default_dashboard:
274
+ return default_dashboard
275
+ else:
276
+ return self.create_dashboard(default=True)
277
+
278
+ def get_dashbaord_by_title(self, title):
279
+ """Get dashboard by title for this sample."""
280
+ from geoseeq.dashboard.dashboard import SampleDashboard
281
+
282
+ dashboard_resp = self.knex.get(f"samples/{self.uuid}/dashboards")
283
+ try:
284
+ blob = [
285
+ dashboard_blob
286
+ for dashboard_blob in dashboard_resp["results"]
287
+ if dashboard_blob["title"] == title
288
+ ][0]
289
+ dashboard = SampleDashboard.from_blob(self, blob)
290
+ dashboard.get() # Tiles are not in the blob
291
+ return dashboard
292
+ except IndexError:
293
+ pass
294
+ return None
295
+
296
+ def get_or_create_dashbaord_by_title(self, title):
297
+ """Get dashboard by title for this sample or create it if does not exist."""
298
+ default_dashboard = self.get_dashbaord_by_title(title)
299
+ if default_dashboard:
300
+ return default_dashboard
301
+ else:
302
+ return self.create_dashboard(title=title)
303
+
231
304
  def __str__(self):
232
305
  return f"<Geoseeq::Sample {self.name} {self.uuid} />"
233
306
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geoseeq
3
- Version: 0.7.3.dev0
3
+ Version: 0.7.3.dev2
4
4
  Summary: GeoSeeq command line tools and python API
5
5
  Project-URL: Homepage, https://github.com/biotia/geoseeq_api_client
6
6
  Project-URL: Issues, https://github.com/biotia/geoseeq_api_client/issues
@@ -7,9 +7,9 @@ geoseeq/file_system_cache.py,sha256=HzVZWtwLD2fjWWSo_UfWmGeBltm9He4lP_OqzKwNGWg,
7
7
  geoseeq/knex.py,sha256=zcjafsmUn9SC3LlRnvvaXpr-pHYZ0IXk7LpzuUoE3MI,8312
8
8
  geoseeq/organization.py,sha256=bJkYL8_D-k6IYAaii2ZbxjwYnXy6lvu6iLXscxKlA3w,2542
9
9
  geoseeq/pipeline.py,sha256=89mhWaecsKnm6tyRkdkaVp4dmZh62_v42Ze0oXf8OTY,9873
10
- geoseeq/project.py,sha256=_8uttLKc6yVGDdJhPRmQOzzPeMYmUg0zBBPOAJc3yyo,14014
10
+ geoseeq/project.py,sha256=GMAO3BQKz_L5Se4JmK0wpj7dp6ell8a-rF9tHNy2gfg,17068
11
11
  geoseeq/remote_object.py,sha256=GYN6PKU7Zz3htIdpFjfZiFejzGqqJHbJyKlefM1Eixk,7151
12
- geoseeq/sample.py,sha256=HAfMiDPHp1UJgIA2lI6oGnNit4YKyj7nx9X07CCN98U,8316
12
+ geoseeq/sample.py,sha256=fBHpVDf8tRH8PBCW-nuNv0sNA4epvwUrlcTPy5im8FY,10895
13
13
  geoseeq/search.py,sha256=gawad6Cx5FxJBPlYkXWb-UKAO-UC0_yhvyU9Ca1kaNI,3388
14
14
  geoseeq/smart_table.py,sha256=rihMsFUIn-vn4w6ukVZTHI9bjDSEr8xHExBfX8mwCHM,6169
15
15
  geoseeq/smart_tree.py,sha256=bSjDlwmOuNXutYJhytA1RovwRCHV6ZxXXJPiIGFhPaA,1825
@@ -52,7 +52,7 @@ geoseeq/contrib/ncbi/api.py,sha256=WQeLoGA_-Zha-QeSO8_i7HpvXyD8UkV0qc5okm11KiA,1
52
52
  geoseeq/contrib/ncbi/bioproject.py,sha256=_oThTd_iLDOC8cLOlJKAatSr362OBYZCEV3YrqodhFg,4341
53
53
  geoseeq/contrib/ncbi/cli.py,sha256=j9zEcaZPTryK3a4xluRxigcJKDhRpRxbp3KZSx-Bfhk,2400
54
54
  geoseeq/contrib/ncbi/setup_logging.py,sha256=Tp1bY1U0f-o739aHpvVYriG2qdd1lFvCYBXZeXQgt-w,175
55
- geoseeq/dashboard/dashboard.py,sha256=0ISzcTby35oJVsOCeg4b_7Em6hRsh3QzsnbChf946Eo,5564
55
+ geoseeq/dashboard/dashboard.py,sha256=mIlaqrSdy0C2EqpJVFvUKJkU1EiHluAT1DqfO0wvHb8,8336
56
56
  geoseeq/file_system/filesystem_download.py,sha256=8bcnxjWltekmCvb5N0b1guBIjLp4-CL2VtsEok-snv4,16963
57
57
  geoseeq/file_system/main.py,sha256=4HgYGq7WhlF96JlVIf16iFBTDujlBpxImmtoh4VCzDA,3627
58
58
  geoseeq/id_constructors/__init__.py,sha256=w5E0PNQ9UuAxBeZbDI7KBnUoERd85gGz3nScz45bd2o,126
@@ -92,8 +92,8 @@ geoseeq/vc/vc_cache.py,sha256=P4LXTbq2zOIv1OhP7Iw5MmypR2vXuy29Pq5K6gRvi-M,730
92
92
  geoseeq/vc/vc_dir.py,sha256=A9CLTh2wWCRzZjiLyqXD1vhtsWZGD3OjaMT5KqlfAXI,457
93
93
  geoseeq/vc/vc_sample.py,sha256=qZeioWydXvfu4rGMs20nICfNcp46y_XkND-bHdV6P5M,3850
94
94
  geoseeq/vc/vc_stub.py,sha256=IQr8dI0zsWKVAeY_5ybDD6n49_3othcgfHS3P0O9tuY,3110
95
- geoseeq-0.7.3.dev0.dist-info/METADATA,sha256=GqNnxtN09jNv5HRMkxGp5D8ZfrJUyJdxlzL5-6N2Ogw,5415
96
- geoseeq-0.7.3.dev0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
97
- geoseeq-0.7.3.dev0.dist-info/entry_points.txt,sha256=yF-6KDM8zXib4Al0qn49TX-qM7PUkWUIcYtsgt36rjM,45
98
- geoseeq-0.7.3.dev0.dist-info/licenses/LICENSE,sha256=IuhIl1XCxXLPLJT_coN1CNqQU4Khlq7x4IdW7ioOJD8,1067
99
- geoseeq-0.7.3.dev0.dist-info/RECORD,,
95
+ geoseeq-0.7.3.dev2.dist-info/METADATA,sha256=EMB21hKhfr8zcdmuWTe3dOWyi2GZe1-50DdGOwtUDB8,5415
96
+ geoseeq-0.7.3.dev2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
97
+ geoseeq-0.7.3.dev2.dist-info/entry_points.txt,sha256=yF-6KDM8zXib4Al0qn49TX-qM7PUkWUIcYtsgt36rjM,45
98
+ geoseeq-0.7.3.dev2.dist-info/licenses/LICENSE,sha256=IuhIl1XCxXLPLJT_coN1CNqQU4Khlq7x4IdW7ioOJD8,1067
99
+ geoseeq-0.7.3.dev2.dist-info/RECORD,,