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.
- geoseeq/dashboard/dashboard.py +149 -66
- geoseeq/project.py +172 -54
- geoseeq/sample.py +88 -15
- {geoseeq-0.7.3.dev0.dist-info → geoseeq-0.7.3.dev2.dist-info}/METADATA +1 -1
- {geoseeq-0.7.3.dev0.dist-info → geoseeq-0.7.3.dev2.dist-info}/RECORD +8 -8
- {geoseeq-0.7.3.dev0.dist-info → geoseeq-0.7.3.dev2.dist-info}/WHEEL +0 -0
- {geoseeq-0.7.3.dev0.dist-info → geoseeq-0.7.3.dev2.dist-info}/entry_points.txt +0 -0
- {geoseeq-0.7.3.dev0.dist-info → geoseeq-0.7.3.dev2.dist-info}/licenses/LICENSE +0 -0
geoseeq/dashboard/dashboard.py
CHANGED
@@ -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__(
|
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.
|
20
|
-
self.
|
21
|
-
self.
|
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
|
-
|
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
|
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
|
-
|
38
|
-
f"sample_groups/{self.project.uuid}/dashboard/{self.
|
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.
|
46
|
-
|
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
|
67
|
+
def add_tile(
|
52
68
|
self,
|
53
|
-
title,
|
54
69
|
result_file,
|
55
|
-
|
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
|
64
|
-
self.
|
65
|
-
|
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.
|
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.
|
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.
|
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 = ["
|
136
|
+
remote_fields = ["uuid", "title", "default", "created_at", "updated_at"]
|
90
137
|
|
91
|
-
def __init__(self, knex, sample,
|
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.
|
142
|
+
self.title = title
|
96
143
|
self.tiles = []
|
97
|
-
self.
|
144
|
+
self.default = default
|
98
145
|
|
99
146
|
def _get(self, allow_overwrite=False):
|
100
|
-
blob = self.knex.get(f"samples/{self.sample.uuid}/
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
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.
|
114
|
-
f"samples/{self.
|
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 = {
|
121
|
-
|
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
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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 =
|
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
|
-
|
137
|
-
|
138
|
-
|
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.
|
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
|
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.
|
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
|
-
"
|
170
|
-
"
|
171
|
-
|
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
|
-
"
|
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 =
|
259
|
+
result_file = result_file_from_id(dashboard.knex, blob["field_obj"]["uuid"])
|
182
260
|
return cls(
|
183
|
-
dashboard.knex,
|
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 = {
|
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[
|
65
|
-
|
66
|
-
|
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(
|
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(
|
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(
|
203
|
-
|
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 =
|
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,
|
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(
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
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(
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
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 = {
|
309
|
-
|
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[
|
312
|
-
merged_response[
|
313
|
-
merged_response[
|
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(
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
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
|
-
|
343
|
-
|
344
|
-
|
376
|
+
"pipeline_id": app.uuid,
|
377
|
+
"sample_uuids": list(self.get_sample_uuids()),
|
378
|
+
"input_parameters": input_parameters,
|
345
379
|
}
|
346
|
-
self.knex.post(
|
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
|
-
|
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
|
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 = {
|
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[
|
74
|
-
data.pop(
|
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 =
|
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.
|
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=
|
10
|
+
geoseeq/project.py,sha256=GMAO3BQKz_L5Se4JmK0wpj7dp6ell8a-rF9tHNy2gfg,17068
|
11
11
|
geoseeq/remote_object.py,sha256=GYN6PKU7Zz3htIdpFjfZiFejzGqqJHbJyKlefM1Eixk,7151
|
12
|
-
geoseeq/sample.py,sha256=
|
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=
|
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.
|
96
|
-
geoseeq-0.7.3.
|
97
|
-
geoseeq-0.7.3.
|
98
|
-
geoseeq-0.7.3.
|
99
|
-
geoseeq-0.7.3.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|