Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/bioblend/galaxy/objects/wrappers.py @ 0:26e78fe6e8c4 draft
"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author | shellac |
---|---|
date | Sat, 02 May 2020 07:14:21 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:26e78fe6e8c4 |
---|---|
1 # pylint: disable=W0622,E1101 | |
2 | |
3 """ | |
4 A basic object-oriented interface for Galaxy entities. | |
5 """ | |
6 | |
7 import abc | |
8 import collections | |
9 import json | |
10 | |
11 import six | |
12 | |
13 import bioblend | |
14 | |
15 __all__ = ( | |
16 'Wrapper', | |
17 'Step', | |
18 'Workflow', | |
19 'ContentInfo', | |
20 'LibraryContentInfo', | |
21 'HistoryContentInfo', | |
22 'DatasetContainer', | |
23 'History', | |
24 'Library', | |
25 'Folder', | |
26 'Dataset', | |
27 'HistoryDatasetAssociation', | |
28 'DatasetCollection', | |
29 'HistoryDatasetCollectionAssociation', | |
30 'LibraryDatasetDatasetAssociation', | |
31 'LibraryDataset', | |
32 'Tool', | |
33 'Job', | |
34 'Preview', | |
35 'LibraryPreview', | |
36 'HistoryPreview', | |
37 'WorkflowPreview', | |
38 ) | |
39 | |
40 | |
41 @six.add_metaclass(abc.ABCMeta) | |
42 class Wrapper(object): | |
43 """ | |
44 Abstract base class for Galaxy entity wrappers. | |
45 | |
46 Wrapper instances wrap deserialized JSON dictionaries such as the | |
47 ones obtained by the Galaxy web API, converting key-based access to | |
48 attribute-based access (e.g., ``library['name'] -> library.name``). | |
49 | |
50 Dict keys that are converted to attributes are listed in the | |
51 ``BASE_ATTRS`` class variable: this is the 'stable' interface. | |
52 Note that the wrapped dictionary is accessible via the ``wrapped`` | |
53 attribute. | |
54 """ | |
55 BASE_ATTRS = ('id', 'name') | |
56 | |
57 @abc.abstractmethod | |
58 def __init__(self, wrapped, parent=None, gi=None): | |
59 """ | |
60 :type wrapped: dict | |
61 :param wrapped: JSON-serializable dictionary | |
62 | |
63 :type parent: :class:`Wrapper` | |
64 :param parent: the parent of this wrapper | |
65 | |
66 :type gi: :class:`GalaxyInstance` | |
67 :param gi: the GalaxyInstance through which we can access this wrapper | |
68 """ | |
69 if not isinstance(wrapped, collections.Mapping): | |
70 raise TypeError('wrapped object must be a mapping type') | |
71 # loads(dumps(x)) is a bit faster than deepcopy and allows type checks | |
72 try: | |
73 dumped = json.dumps(wrapped) | |
74 except (TypeError, ValueError): | |
75 raise ValueError('wrapped object must be JSON-serializable') | |
76 object.__setattr__(self, 'wrapped', json.loads(dumped)) | |
77 for k in self.BASE_ATTRS: | |
78 object.__setattr__(self, k, self.wrapped.get(k)) | |
79 object.__setattr__(self, '_cached_parent', parent) | |
80 object.__setattr__(self, 'is_modified', False) | |
81 object.__setattr__(self, 'gi', gi) | |
82 | |
83 @abc.abstractproperty | |
84 def gi_module(self): | |
85 """ | |
86 The GalaxyInstance module that deals with objects of this type. | |
87 """ | |
88 pass | |
89 | |
90 @property | |
91 def parent(self): | |
92 """ | |
93 The parent of this wrapper. | |
94 """ | |
95 return self._cached_parent | |
96 | |
97 @property | |
98 def is_mapped(self): | |
99 """ | |
100 ``True`` if this wrapper is mapped to an actual Galaxy entity. | |
101 """ | |
102 return self.id is not None | |
103 | |
104 def unmap(self): | |
105 """ | |
106 Disconnect this wrapper from Galaxy. | |
107 """ | |
108 object.__setattr__(self, 'id', None) | |
109 | |
110 def clone(self): | |
111 """ | |
112 Return an independent copy of this wrapper. | |
113 """ | |
114 return self.__class__(self.wrapped) | |
115 | |
116 def touch(self): | |
117 """ | |
118 Mark this wrapper as having been modified since its creation. | |
119 """ | |
120 object.__setattr__(self, 'is_modified', True) | |
121 if self.parent: | |
122 self.parent.touch() | |
123 | |
124 def to_json(self): | |
125 """ | |
126 Return a JSON dump of this wrapper. | |
127 """ | |
128 return json.dumps(self.wrapped) | |
129 | |
130 @classmethod | |
131 def from_json(cls, jdef): | |
132 """ | |
133 Build a new wrapper from a JSON dump. | |
134 """ | |
135 return cls(json.loads(jdef)) | |
136 | |
137 # FIXME: things like self.x[0] = 'y' do NOT call self.__setattr__ | |
138 def __setattr__(self, name, value): | |
139 if name not in self.wrapped: | |
140 raise AttributeError("can't set attribute") | |
141 else: | |
142 self.wrapped[name] = value | |
143 object.__setattr__(self, name, value) | |
144 self.touch() | |
145 | |
146 def __repr__(self): | |
147 return "%s(%r)" % (self.__class__.__name__, self.wrapped) | |
148 | |
149 | |
150 class Step(Wrapper): | |
151 """ | |
152 Abstract base class for workflow steps. | |
153 | |
154 Steps are the main building blocks of a Galaxy workflow. A step can be: an | |
155 input (type 'data_collection_input` or 'data_input`), a computational tool | |
156 (type 'tool`) or a pause (type 'pause`). | |
157 """ | |
158 BASE_ATTRS = Wrapper.BASE_ATTRS + ( | |
159 'input_steps', 'tool_id', 'tool_inputs', 'tool_version', 'type' | |
160 ) | |
161 | |
162 def __init__(self, step_dict, parent): | |
163 super(Step, self).__init__(step_dict, parent=parent, gi=parent.gi) | |
164 try: | |
165 stype = step_dict['type'] | |
166 except KeyError: | |
167 raise ValueError('not a step dict') | |
168 if stype not in set(['data_collection_input', 'data_input', 'pause', | |
169 'tool']): | |
170 raise ValueError('Unknown step type: %r' % stype) | |
171 if self.type == 'tool' and self.tool_inputs: | |
172 for k, v in six.iteritems(self.tool_inputs): | |
173 # In Galaxy before release_17.05, v is a JSON-encoded string | |
174 if not isinstance(v, six.string_types): | |
175 break | |
176 try: | |
177 self.tool_inputs[k] = json.loads(v) | |
178 except ValueError: | |
179 break | |
180 | |
181 @property | |
182 def gi_module(self): | |
183 return self.gi.workflows | |
184 | |
185 | |
186 class Workflow(Wrapper): | |
187 """ | |
188 Workflows represent ordered sequences of computations on Galaxy. | |
189 | |
190 A workflow defines a sequence of steps that produce one or more | |
191 results from an input dataset. | |
192 """ | |
193 BASE_ATTRS = Wrapper.BASE_ATTRS + ( | |
194 'deleted', 'inputs', 'published', 'steps', 'tags' | |
195 ) | |
196 POLLING_INTERVAL = 10 # for output state monitoring | |
197 | |
198 def __init__(self, wf_dict, gi=None): | |
199 super(Workflow, self).__init__(wf_dict, gi=gi) | |
200 missing_ids = [] | |
201 if gi: | |
202 tools_list_by_id = [t.id for t in gi.tools.get_previews()] | |
203 else: | |
204 tools_list_by_id = [] | |
205 tool_labels_to_ids = {} | |
206 for k, v in six.iteritems(self.steps): | |
207 # convert step ids to str for consistency with outer keys | |
208 v['id'] = str(v['id']) | |
209 for i in six.itervalues(v['input_steps']): | |
210 i['source_step'] = str(i['source_step']) | |
211 step = Step(v, self) | |
212 self.steps[k] = step | |
213 if step.type == 'tool': | |
214 if not step.tool_inputs or step.tool_id not in tools_list_by_id: | |
215 missing_ids.append(k) | |
216 tool_labels_to_ids.setdefault(step.tool_id, set()).add(step.id) | |
217 input_labels_to_ids = {} | |
218 for id_, d in six.iteritems(self.inputs): | |
219 input_labels_to_ids.setdefault(d['label'], set()).add(id_) | |
220 object.__setattr__(self, 'input_labels_to_ids', input_labels_to_ids) | |
221 object.__setattr__(self, 'tool_labels_to_ids', tool_labels_to_ids) | |
222 dag, inv_dag = self._get_dag() | |
223 heads, tails = set(dag), set(inv_dag) | |
224 object.__setattr__(self, 'dag', dag) | |
225 object.__setattr__(self, 'inv_dag', inv_dag) | |
226 object.__setattr__(self, 'source_ids', heads - tails) | |
227 assert set(self.inputs) == self.data_collection_input_ids | self.data_input_ids, \ | |
228 "inputs is %r, while data_collection_input_ids is %r and data_input_ids is %r" % (self.inputs, self.data_collection_input_ids, self.data_input_ids) | |
229 object.__setattr__(self, 'sink_ids', tails - heads) | |
230 object.__setattr__(self, 'missing_ids', missing_ids) | |
231 | |
232 @property | |
233 def gi_module(self): | |
234 return self.gi.workflows | |
235 | |
236 def _get_dag(self): | |
237 """ | |
238 Return the workflow's DAG. | |
239 | |
240 For convenience, this method computes a 'direct' (step => | |
241 successors) and an 'inverse' (step => predecessors) | |
242 representation of the same DAG. | |
243 | |
244 For instance, a workflow with a single tool *c*, two inputs | |
245 *a, b* and three outputs *d, e, f* is represented by (direct):: | |
246 | |
247 {'a': {'c'}, 'b': {'c'}, 'c': set(['d', 'e', 'f'])} | |
248 | |
249 and by (inverse):: | |
250 | |
251 {'c': set(['a', 'b']), 'd': {'c'}, 'e': {'c'}, 'f': {'c'}} | |
252 """ | |
253 dag, inv_dag = {}, {} | |
254 for s in six.itervalues(self.steps): | |
255 for i in six.itervalues(s.input_steps): | |
256 head, tail = i['source_step'], s.id | |
257 dag.setdefault(head, set()).add(tail) | |
258 inv_dag.setdefault(tail, set()).add(head) | |
259 return dag, inv_dag | |
260 | |
261 def sorted_step_ids(self): | |
262 """ | |
263 Return a topological sort of the workflow's DAG. | |
264 """ | |
265 ids = [] | |
266 source_ids = self.source_ids.copy() | |
267 inv_dag = dict((k, v.copy()) for k, v in six.iteritems(self.inv_dag)) | |
268 while source_ids: | |
269 head = source_ids.pop() | |
270 ids.append(head) | |
271 for tail in self.dag.get(head, []): | |
272 incoming = inv_dag[tail] | |
273 incoming.remove(head) | |
274 if not incoming: | |
275 source_ids.add(tail) | |
276 return ids | |
277 | |
278 @property | |
279 def data_input_ids(self): | |
280 """ | |
281 Return the ids of data input steps for this workflow. | |
282 """ | |
283 return set(id_ for id_, s in six.iteritems(self.steps) | |
284 if s.type == 'data_input') | |
285 | |
286 @property | |
287 def data_collection_input_ids(self): | |
288 """ | |
289 Return the ids of data collection input steps for this workflow. | |
290 """ | |
291 return set(id_ for id_, s in six.iteritems(self.steps) | |
292 if s.type == 'data_collection_input') | |
293 | |
294 @property | |
295 def tool_ids(self): | |
296 """ | |
297 Return the ids of tool steps for this workflow. | |
298 """ | |
299 return set(id_ for id_, s in six.iteritems(self.steps) | |
300 if s.type == 'tool') | |
301 | |
302 @property | |
303 def input_labels(self): | |
304 """ | |
305 Return the labels of this workflow's input steps. | |
306 """ | |
307 return set(self.input_labels_to_ids) | |
308 | |
309 @property | |
310 def is_runnable(self): | |
311 """ | |
312 Return True if the workflow can be run on Galaxy. | |
313 | |
314 A workflow is considered runnable on a Galaxy instance if all | |
315 of the tools it uses are installed in that instance. | |
316 """ | |
317 return not self.missing_ids | |
318 | |
319 def convert_input_map(self, input_map): | |
320 """ | |
321 Convert ``input_map`` to the format required by the Galaxy web API. | |
322 | |
323 :type input_map: dict | |
324 :param input_map: a mapping from input labels to datasets | |
325 | |
326 :rtype: dict | |
327 :return: a mapping from input slot ids to dataset ids in the | |
328 format required by the Galaxy web API. | |
329 """ | |
330 m = {} | |
331 for label, slot_ids in six.iteritems(self.input_labels_to_ids): | |
332 datasets = input_map.get(label, []) | |
333 if not isinstance(datasets, collections.Iterable): | |
334 datasets = [datasets] | |
335 if len(datasets) < len(slot_ids): | |
336 raise RuntimeError('not enough datasets for "%s"' % label) | |
337 for id_, ds in zip(slot_ids, datasets): | |
338 m[id_] = {'id': ds.id, 'src': ds.SRC} | |
339 return m | |
340 | |
341 def preview(self): | |
342 getf = self.gi.workflows.get_previews | |
343 try: | |
344 p = [_ for _ in getf(published=True) if _.id == self.id][0] | |
345 except IndexError: | |
346 raise ValueError('no object for id %s' % self.id) | |
347 return p | |
348 | |
349 def run(self, input_map=None, history='', params=None, import_inputs=False, | |
350 replacement_params=None, wait=False, | |
351 polling_interval=POLLING_INTERVAL, break_on_error=True): | |
352 """ | |
353 Run the workflow in the current Galaxy instance. | |
354 | |
355 :type input_map: dict | |
356 :param input_map: a mapping from workflow input labels to | |
357 datasets, e.g.: ``dict(zip(workflow.input_labels, | |
358 library.get_datasets()))`` | |
359 | |
360 :type history: :class:`History` or str | |
361 :param history: either a valid history object (results will be | |
362 stored there) or a string (a new history will be created with | |
363 the given name). | |
364 | |
365 :type params: dict | |
366 :param params: a mapping of non-datasets tool parameters (see below) | |
367 | |
368 :type import_inputs: bool | |
369 :param import_inputs: If ``True``, workflow inputs will be imported into | |
370 the history; if ``False``, only workflow outputs will be visible in | |
371 the history. | |
372 | |
373 :type replacement_params: dict | |
374 :param replacement_params: pattern-based replacements for | |
375 post-job actions (see the docs for | |
376 :meth:`~bioblend.galaxy.workflows.WorkflowClient.invoke_workflow`) | |
377 | |
378 :type wait: bool | |
379 :param wait: whether to wait while the returned datasets are | |
380 in a pending state | |
381 | |
382 :type polling_interval: float | |
383 :param polling_interval: polling interval in seconds | |
384 | |
385 :type break_on_error: bool | |
386 :param break_on_error: whether to break as soon as at least one | |
387 of the returned datasets is in the 'error' state | |
388 | |
389 :rtype: tuple | |
390 :return: list of output datasets, output history | |
391 | |
392 The ``params`` dict should be specified as follows:: | |
393 | |
394 {STEP_ID: PARAM_DICT, ...} | |
395 | |
396 where PARAM_DICT is:: | |
397 | |
398 {PARAM_NAME: VALUE, ...} | |
399 | |
400 For backwards compatibility, the following (deprecated) format is | |
401 also supported for ``params``:: | |
402 | |
403 {TOOL_ID: PARAM_DICT, ...} | |
404 | |
405 in which case PARAM_DICT affects all steps with the given tool id. | |
406 If both by-tool-id and by-step-id specifications are used, the | |
407 latter takes precedence. | |
408 | |
409 Finally (again, for backwards compatibility), PARAM_DICT can also | |
410 be specified as:: | |
411 | |
412 {'param': PARAM_NAME, 'value': VALUE} | |
413 | |
414 Note that this format allows only one parameter to be set per step. | |
415 | |
416 Example: set 'a' to 1 for the third workflow step:: | |
417 | |
418 params = {workflow.steps[2].id: {'a': 1}} | |
419 | |
420 .. warning:: | |
421 | |
422 This is a blocking operation that can take a very long time. If | |
423 ``wait`` is set to ``False``, the method will return as soon as the | |
424 workflow has been *scheduled*, otherwise it will wait until the | |
425 workflow has been *run*. With a large number of steps, however, the | |
426 delay may not be negligible even in the former case (e.g. minutes for | |
427 100 steps). | |
428 """ | |
429 if not self.is_mapped: | |
430 raise RuntimeError('workflow is not mapped to a Galaxy object') | |
431 if not self.is_runnable: | |
432 raise RuntimeError('workflow has missing tools: %s' % ', '.join( | |
433 '%s[%s]' % (self.steps[_].tool_id, _) | |
434 for _ in self.missing_ids)) | |
435 kwargs = { | |
436 'dataset_map': self.convert_input_map(input_map or {}), | |
437 'params': params, | |
438 'import_inputs_to_history': import_inputs, | |
439 'replacement_params': replacement_params, | |
440 } | |
441 if isinstance(history, History): | |
442 try: | |
443 kwargs['history_id'] = history.id | |
444 except AttributeError: | |
445 raise RuntimeError('history does not have an id') | |
446 elif isinstance(history, six.string_types): | |
447 kwargs['history_name'] = history | |
448 else: | |
449 raise TypeError( | |
450 'history must be either a history wrapper or a string') | |
451 res = self.gi.gi.workflows.run_workflow(self.id, **kwargs) | |
452 # res structure: {'history': HIST_ID, 'outputs': [CI_ID, CI_ID, ...]} | |
453 out_hist = self.gi.histories.get(res['history']) | |
454 content_infos_dict = dict() | |
455 for ci in out_hist.content_infos: | |
456 content_infos_dict[ci.id] = ci | |
457 outputs = [] | |
458 for output_id in res['outputs']: | |
459 if content_infos_dict[output_id].type == 'file': | |
460 outputs.append(out_hist.get_dataset(output_id)) | |
461 elif content_infos_dict[output_id].type == 'collection': | |
462 outputs.append(out_hist.get_dataset_collection(output_id)) | |
463 | |
464 if wait: | |
465 self.gi._wait_datasets(outputs, polling_interval=polling_interval, | |
466 break_on_error=break_on_error) | |
467 return outputs, out_hist | |
468 | |
469 def export(self): | |
470 """ | |
471 Export a re-importable representation of the workflow. | |
472 | |
473 :rtype: dict | |
474 :return: a JSON-serializable dump of the workflow | |
475 """ | |
476 return self.gi.gi.workflows.export_workflow_dict(self.id) | |
477 | |
478 def delete(self): | |
479 """ | |
480 Delete this workflow. | |
481 | |
482 .. warning:: | |
483 Deleting a workflow is irreversible - all of the data from | |
484 the workflow will be permanently deleted. | |
485 """ | |
486 self.gi.workflows.delete(id_=self.id) | |
487 self.unmap() | |
488 | |
489 | |
490 @six.add_metaclass(abc.ABCMeta) | |
491 class Dataset(Wrapper): | |
492 """ | |
493 Abstract base class for Galaxy datasets. | |
494 """ | |
495 BASE_ATTRS = Wrapper.BASE_ATTRS + ( | |
496 'data_type', 'file_ext', 'file_name', 'file_size', 'genome_build', 'misc_info', 'state' | |
497 ) | |
498 POLLING_INTERVAL = 1 # for state monitoring | |
499 | |
500 @abc.abstractmethod | |
501 def __init__(self, ds_dict, container, gi=None): | |
502 super(Dataset, self).__init__(ds_dict, gi=gi) | |
503 object.__setattr__(self, 'container', container) | |
504 | |
505 @property | |
506 def container_id(self): | |
507 """ | |
508 Deprecated property. | |
509 | |
510 Id of the dataset container. Use :attr:`.container.id` instead. | |
511 """ | |
512 return self.container.id | |
513 | |
514 @abc.abstractproperty | |
515 def _stream_url(self): | |
516 """ | |
517 Return the URL to stream this dataset. | |
518 """ | |
519 pass | |
520 | |
521 def get_stream(self, chunk_size=bioblend.CHUNK_SIZE): | |
522 """ | |
523 Open dataset for reading and return an iterator over its contents. | |
524 | |
525 :type chunk_size: int | |
526 :param chunk_size: read this amount of bytes at a time | |
527 """ | |
528 kwargs = {'stream': True} | |
529 if isinstance(self, LibraryDataset): | |
530 kwargs['params'] = {'ld_ids%5B%5D': self.id} | |
531 r = self.gi.gi.make_get_request(self._stream_url, **kwargs) | |
532 if isinstance(self, LibraryDataset) and r.status_code == 500: | |
533 # compatibility with older Galaxy releases | |
534 kwargs['params'] = {'ldda_ids%5B%5D': self.id} | |
535 r = self.gi.gi.make_get_request(self._stream_url, **kwargs) | |
536 r.raise_for_status() | |
537 return r.iter_content(chunk_size) # FIXME: client can't close r | |
538 | |
539 def peek(self, chunk_size=bioblend.CHUNK_SIZE): | |
540 """ | |
541 Open dataset for reading and return the first chunk. | |
542 | |
543 See :meth:`.get_stream` for param info. | |
544 """ | |
545 try: | |
546 return next(self.get_stream(chunk_size=chunk_size)) | |
547 except StopIteration: | |
548 return b'' | |
549 | |
550 def download(self, file_object, chunk_size=bioblend.CHUNK_SIZE): | |
551 """ | |
552 Open dataset for reading and save its contents to ``file_object``. | |
553 | |
554 :type file_object: file | |
555 :param file_object: output file object | |
556 | |
557 See :meth:`.get_stream` for info on other params. | |
558 """ | |
559 for chunk in self.get_stream(chunk_size=chunk_size): | |
560 file_object.write(chunk) | |
561 | |
562 def get_contents(self, chunk_size=bioblend.CHUNK_SIZE): | |
563 """ | |
564 Open dataset for reading and return its **full** contents. | |
565 | |
566 See :meth:`.get_stream` for param info. | |
567 """ | |
568 return b''.join(self.get_stream(chunk_size=chunk_size)) | |
569 | |
570 def refresh(self): | |
571 """ | |
572 Re-fetch the attributes pertaining to this object. | |
573 | |
574 Returns: self | |
575 """ | |
576 gi_client = getattr(self.gi.gi, self.container.API_MODULE) | |
577 ds_dict = gi_client.show_dataset(self.container.id, self.id) | |
578 self.__init__(ds_dict, self.container, self.gi) | |
579 return self | |
580 | |
581 def wait(self, polling_interval=POLLING_INTERVAL, break_on_error=True): | |
582 """ | |
583 Wait for this dataset to come out of the pending states. | |
584 | |
585 :type polling_interval: float | |
586 :param polling_interval: polling interval in seconds | |
587 | |
588 :type break_on_error: bool | |
589 :param break_on_error: if ``True``, raise a RuntimeError exception if | |
590 the dataset ends in the 'error' state. | |
591 | |
592 .. warning:: | |
593 | |
594 This is a blocking operation that can take a very long time. Also, | |
595 note that this method does not return anything; however, this dataset | |
596 is refreshed (possibly multiple times) during the execution. | |
597 """ | |
598 self.gi._wait_datasets([self], polling_interval=polling_interval, | |
599 break_on_error=break_on_error) | |
600 | |
601 | |
602 class HistoryDatasetAssociation(Dataset): | |
603 """ | |
604 Maps to a Galaxy ``HistoryDatasetAssociation``. | |
605 """ | |
606 BASE_ATTRS = Dataset.BASE_ATTRS + ('annotation', 'deleted', 'purged', 'tags', 'visible') | |
607 SRC = 'hda' | |
608 | |
609 def __init__(self, ds_dict, container, gi=None): | |
610 super(HistoryDatasetAssociation, self).__init__( | |
611 ds_dict, container, gi=gi) | |
612 | |
613 @property | |
614 def gi_module(self): | |
615 return self.gi.histories | |
616 | |
617 @property | |
618 def _stream_url(self): | |
619 base_url = self.gi.gi._make_url( | |
620 self.gi.gi.histories, module_id=self.container.id, contents=True) | |
621 return "%s/%s/display" % (base_url, self.id) | |
622 | |
623 def update(self, **kwds): | |
624 """ | |
625 Update this history dataset metadata. Some of the attributes that can be | |
626 modified are documented below. | |
627 | |
628 :type name: str | |
629 :param name: Replace history dataset name with the given string | |
630 | |
631 :type genome_build: str | |
632 :param genome_build: Replace history dataset genome build (dbkey) | |
633 | |
634 :type annotation: str | |
635 :param annotation: Replace history dataset annotation with given string | |
636 | |
637 :type deleted: bool | |
638 :param deleted: Mark or unmark history dataset as deleted | |
639 | |
640 :type visible: bool | |
641 :param visible: Mark or unmark history dataset as visible | |
642 """ | |
643 res = self.gi.gi.histories.update_dataset(self.container.id, self.id, **kwds) | |
644 # Refresh also the history because the dataset may have been (un)deleted | |
645 self.container.refresh() | |
646 if 'id' in res: | |
647 self.__init__(res, self.container, gi=self.gi) | |
648 else: | |
649 # for Galaxy < release_15.03 res contains only the updated fields | |
650 self.refresh() | |
651 return self | |
652 | |
653 def delete(self, purge=False): | |
654 """ | |
655 Delete this history dataset. | |
656 | |
657 :type purge: bool | |
658 :param purge: if ``True``, also purge (permanently delete) the dataset | |
659 | |
660 .. note:: | |
661 For the purge option to work, the Galaxy instance must have the | |
662 ``allow_user_dataset_purge`` option set to ``true`` in the | |
663 ``config/galaxy.yml`` configuration file. | |
664 | |
665 .. warning:: | |
666 If you purge a dataset which has not been previously deleted, | |
667 Galaxy from release_15.03 to release_17.01 does not set the | |
668 ``deleted`` attribute of the dataset to ``True``, see | |
669 https://github.com/galaxyproject/galaxy/issues/3548 | |
670 """ | |
671 self.gi.gi.histories.delete_dataset(self.container.id, self.id, purge=purge) | |
672 self.container.refresh() | |
673 self.refresh() | |
674 | |
675 | |
676 @six.add_metaclass(abc.ABCMeta) | |
677 class DatasetCollection(Wrapper): | |
678 """ | |
679 Abstract base class for Galaxy dataset collections. | |
680 """ | |
681 BASE_ATTRS = Wrapper.BASE_ATTRS + ( | |
682 'state', 'deleted', 'collection_type' | |
683 ) | |
684 | |
685 @abc.abstractmethod | |
686 def __init__(self, dsc_dict, container, gi=None): | |
687 super(DatasetCollection, self).__init__(dsc_dict, gi=gi) | |
688 object.__setattr__(self, 'container', container) | |
689 | |
690 def refresh(self): | |
691 """ | |
692 Re-fetch the attributes pertaining to this object. | |
693 | |
694 Returns: self | |
695 """ | |
696 gi_client = getattr(self.gi.gi, self.container.API_MODULE) | |
697 dsc_dict = gi_client.show_dataset_collection(self.container.id, self.id) | |
698 self.__init__(dsc_dict, self.container, self.gi) | |
699 return self | |
700 | |
701 | |
702 class HistoryDatasetCollectionAssociation(DatasetCollection): | |
703 """ | |
704 Maps to a Galaxy ``HistoryDatasetCollectionAssociation``. | |
705 """ | |
706 BASE_ATTRS = DatasetCollection.BASE_ATTRS + ('tags', 'visible', 'elements') | |
707 SRC = 'hdca' | |
708 | |
709 def __init__(self, dsc_dict, container, gi=None): | |
710 super(HistoryDatasetCollectionAssociation, self).__init__( | |
711 dsc_dict, container, gi=gi) | |
712 | |
713 @property | |
714 def gi_module(self): | |
715 return self.gi.histories | |
716 | |
717 def delete(self): | |
718 """ | |
719 Delete this dataset collection. | |
720 """ | |
721 self.gi.gi.histories.delete_dataset_collection(self.container.id, self.id) | |
722 self.container.refresh() | |
723 self.refresh() | |
724 | |
725 | |
726 class LibRelatedDataset(Dataset): | |
727 """ | |
728 Base class for LibraryDatasetDatasetAssociation and LibraryDataset classes. | |
729 """ | |
730 | |
731 def __init__(self, ds_dict, container, gi=None): | |
732 super(LibRelatedDataset, self).__init__(ds_dict, container, gi=gi) | |
733 | |
734 @property | |
735 def gi_module(self): | |
736 return self.gi.libraries | |
737 | |
738 @property | |
739 def _stream_url(self): | |
740 base_url = self.gi.gi._make_url(self.gi.gi.libraries) | |
741 return "%s/datasets/download/uncompressed" % base_url | |
742 | |
743 | |
744 class LibraryDatasetDatasetAssociation(LibRelatedDataset): | |
745 """ | |
746 Maps to a Galaxy ``LibraryDatasetDatasetAssociation``. | |
747 """ | |
748 BASE_ATTRS = LibRelatedDataset.BASE_ATTRS + ('deleted',) | |
749 SRC = 'ldda' | |
750 | |
751 | |
752 class LibraryDataset(LibRelatedDataset): | |
753 """ | |
754 Maps to a Galaxy ``LibraryDataset``. | |
755 """ | |
756 SRC = 'ld' | |
757 | |
758 def delete(self, purged=False): | |
759 """ | |
760 Delete this library dataset. | |
761 | |
762 :type purged: bool | |
763 :param purged: if ``True``, also purge (permanently delete) the dataset | |
764 """ | |
765 self.gi.gi.libraries.delete_library_dataset( | |
766 self.container.id, self.id, purged=purged) | |
767 self.container.refresh() | |
768 self.refresh() | |
769 | |
770 def update(self, **kwds): | |
771 """ | |
772 Update this library dataset metadata. Some of the attributes that can be | |
773 modified are documented below. | |
774 | |
775 :type name: str | |
776 :param name: Replace history dataset name with the given string | |
777 | |
778 :type genome_build: str | |
779 :param genome_build: Replace history dataset genome build (dbkey) | |
780 """ | |
781 res = self.gi.gi.libraries.update_library_dataset(self.id, **kwds) | |
782 self.container.refresh() | |
783 self.__init__(res, self.container, gi=self.gi) | |
784 return self | |
785 | |
786 | |
787 @six.add_metaclass(abc.ABCMeta) | |
788 class ContentInfo(Wrapper): | |
789 """ | |
790 Instances of this class wrap dictionaries obtained by getting | |
791 ``/api/{histories,libraries}/<ID>/contents`` from Galaxy. | |
792 """ | |
793 BASE_ATTRS = Wrapper.BASE_ATTRS + ('type',) | |
794 | |
795 @abc.abstractmethod | |
796 def __init__(self, info_dict, gi=None): | |
797 super(ContentInfo, self).__init__(info_dict, gi=gi) | |
798 | |
799 | |
800 class LibraryContentInfo(ContentInfo): | |
801 """ | |
802 Instances of this class wrap dictionaries obtained by getting | |
803 ``/api/libraries/<ID>/contents`` from Galaxy. | |
804 """ | |
805 def __init__(self, info_dict, gi=None): | |
806 super(LibraryContentInfo, self).__init__(info_dict, gi=gi) | |
807 | |
808 @property | |
809 def gi_module(self): | |
810 return self.gi.libraries | |
811 | |
812 | |
813 class HistoryContentInfo(ContentInfo): | |
814 """ | |
815 Instances of this class wrap dictionaries obtained by getting | |
816 ``/api/histories/<ID>/contents`` from Galaxy. | |
817 """ | |
818 BASE_ATTRS = ContentInfo.BASE_ATTRS + ('deleted', 'state', 'visible') | |
819 | |
820 def __init__(self, info_dict, gi=None): | |
821 super(HistoryContentInfo, self).__init__(info_dict, gi=gi) | |
822 | |
823 @property | |
824 def gi_module(self): | |
825 return self.gi.histories | |
826 | |
827 | |
828 @six.add_metaclass(abc.ABCMeta) | |
829 class DatasetContainer(Wrapper): | |
830 """ | |
831 Abstract base class for dataset containers (histories and libraries). | |
832 """ | |
833 BASE_ATTRS = Wrapper.BASE_ATTRS + ('deleted',) | |
834 | |
835 @abc.abstractmethod | |
836 def __init__(self, c_dict, content_infos=None, gi=None): | |
837 """ | |
838 :type content_infos: list of :class:`ContentInfo` | |
839 :param content_infos: info objects for the container's contents | |
840 """ | |
841 super(DatasetContainer, self).__init__(c_dict, gi=gi) | |
842 if content_infos is None: | |
843 content_infos = [] | |
844 object.__setattr__(self, 'content_infos', content_infos) | |
845 | |
846 @property | |
847 def dataset_ids(self): | |
848 """ | |
849 Return the ids of the contained datasets. | |
850 """ | |
851 return [_.id for _ in self.content_infos if _.type == 'file'] | |
852 | |
853 def preview(self): | |
854 getf = self.gi_module.get_previews | |
855 # self.state could be stale: check both regular and deleted containers | |
856 try: | |
857 p = [_ for _ in getf() if _.id == self.id][0] | |
858 except IndexError: | |
859 try: | |
860 p = [_ for _ in getf(deleted=True) if _.id == self.id][0] | |
861 except IndexError: | |
862 raise ValueError('no object for id %s' % self.id) | |
863 return p | |
864 | |
865 def refresh(self): | |
866 """ | |
867 Re-fetch the attributes pertaining to this object. | |
868 | |
869 Returns: self | |
870 """ | |
871 fresh = self.gi_module.get(self.id) | |
872 self.__init__( | |
873 fresh.wrapped, content_infos=fresh.content_infos, gi=self.gi) | |
874 return self | |
875 | |
876 def get_dataset(self, ds_id): | |
877 """ | |
878 Retrieve the dataset corresponding to the given id. | |
879 | |
880 :type ds_id: str | |
881 :param ds_id: dataset id | |
882 | |
883 :rtype: :class:`~.HistoryDatasetAssociation` or | |
884 :class:`~.LibraryDataset` | |
885 :return: the dataset corresponding to ``ds_id`` | |
886 """ | |
887 gi_client = getattr(self.gi.gi, self.API_MODULE) | |
888 ds_dict = gi_client.show_dataset(self.id, ds_id) | |
889 return self.DS_TYPE(ds_dict, self, gi=self.gi) | |
890 | |
891 def get_datasets(self, name=None): | |
892 """ | |
893 Get all datasets contained inside this dataset container. | |
894 | |
895 :type name: str | |
896 :param name: return only datasets with this name | |
897 | |
898 :rtype: list of :class:`~.HistoryDatasetAssociation` or list of | |
899 :class:`~.LibraryDataset` | |
900 :return: datasets with the given name contained inside this | |
901 container | |
902 | |
903 .. note:: | |
904 | |
905 when filtering library datasets by name, specify their full | |
906 paths starting from the library's root folder, e.g., | |
907 ``/seqdata/reads.fastq``. Full paths are available through | |
908 the ``content_infos`` attribute of | |
909 :class:`~.Library` objects. | |
910 """ | |
911 if name is None: | |
912 ds_ids = self.dataset_ids | |
913 else: | |
914 ds_ids = [_.id for _ in self.content_infos if _.name == name] | |
915 return [self.get_dataset(_) for _ in ds_ids] | |
916 | |
917 | |
918 class History(DatasetContainer): | |
919 """ | |
920 Maps to a Galaxy history. | |
921 """ | |
922 BASE_ATTRS = DatasetContainer.BASE_ATTRS + ('annotation', 'published', 'state', 'state_ids', 'state_details', 'tags') | |
923 DS_TYPE = HistoryDatasetAssociation | |
924 DSC_TYPE = HistoryDatasetCollectionAssociation | |
925 CONTENT_INFO_TYPE = HistoryContentInfo | |
926 API_MODULE = 'histories' | |
927 | |
928 def __init__(self, hist_dict, content_infos=None, gi=None): | |
929 super(History, self).__init__( | |
930 hist_dict, content_infos=content_infos, gi=gi) | |
931 | |
932 @property | |
933 def gi_module(self): | |
934 return self.gi.histories | |
935 | |
936 def update(self, **kwds): | |
937 """ | |
938 Update history metadata information. Some of the attributes that can be | |
939 modified are documented below. | |
940 | |
941 :type name: str | |
942 :param name: Replace history name with the given string | |
943 | |
944 :type annotation: str | |
945 :param annotation: Replace history annotation with the given string | |
946 | |
947 :type deleted: bool | |
948 :param deleted: Mark or unmark history as deleted | |
949 | |
950 :type purged: bool | |
951 :param purged: If True, mark history as purged (permanently deleted). | |
952 Ignored on Galaxy release_15.01 and earlier | |
953 | |
954 :type published: bool | |
955 :param published: Mark or unmark history as published | |
956 | |
957 :type importable: bool | |
958 :param importable: Mark or unmark history as importable | |
959 | |
960 :type tags: list | |
961 :param tags: Replace history tags with the given list | |
962 """ | |
963 # TODO: wouldn't it be better if name and annotation were attributes? | |
964 self.gi.gi.histories.update_history(self.id, **kwds) | |
965 self.refresh() | |
966 return self | |
967 | |
968 def delete(self, purge=False): | |
969 """ | |
970 Delete this history. | |
971 | |
972 :type purge: bool | |
973 :param purge: if ``True``, also purge (permanently delete) the history | |
974 | |
975 .. note:: | |
976 For the purge option to work, the Galaxy instance must have the | |
977 ``allow_user_dataset_purge`` option set to ``true`` in the | |
978 ``config/galaxy.yml`` configuration file. | |
979 """ | |
980 self.gi.histories.delete(id_=self.id, purge=purge) | |
981 try: | |
982 self.refresh() | |
983 except Exception: | |
984 # Galaxy release_15.01 and earlier requires passing 'deleted=False' | |
985 # when getting the details of a deleted history | |
986 pass | |
987 self.unmap() | |
988 | |
989 def import_dataset(self, lds): | |
990 """ | |
991 Import a dataset into the history from a library. | |
992 | |
993 :type lds: :class:`~.LibraryDataset` | |
994 :param lds: the library dataset to import | |
995 | |
996 :rtype: :class:`~.HistoryDatasetAssociation` | |
997 :return: the imported history dataset | |
998 """ | |
999 if not self.is_mapped: | |
1000 raise RuntimeError('history is not mapped to a Galaxy object') | |
1001 if not isinstance(lds, LibraryDataset): | |
1002 raise TypeError('lds is not a LibraryDataset') | |
1003 res = self.gi.gi.histories.upload_dataset_from_library(self.id, lds.id) | |
1004 if not isinstance(res, collections.Mapping): | |
1005 raise RuntimeError( | |
1006 'upload_dataset_from_library: unexpected reply: %r' % res) | |
1007 self.refresh() | |
1008 return self.get_dataset(res['id']) | |
1009 | |
1010 def upload_file(self, path, **kwargs): | |
1011 """ | |
1012 Upload the file specified by ``path`` to this history. | |
1013 | |
1014 :type path: str | |
1015 :param path: path of the file to upload | |
1016 | |
1017 See :meth:`~bioblend.galaxy.tools.ToolClient.upload_file` for | |
1018 the optional parameters. | |
1019 | |
1020 :rtype: :class:`~.HistoryDatasetAssociation` | |
1021 :return: the uploaded dataset | |
1022 """ | |
1023 out_dict = self.gi.gi.tools.upload_file(path, self.id, **kwargs) | |
1024 self.refresh() | |
1025 return self.get_dataset(out_dict['outputs'][0]['id']) | |
1026 | |
1027 upload_dataset = upload_file | |
1028 | |
1029 def upload_from_ftp(self, path, **kwargs): | |
1030 """ | |
1031 Upload the file specified by ``path`` from the user's FTP directory to | |
1032 this history. | |
1033 | |
1034 :type path: str | |
1035 :param path: path of the file in the user's FTP directory | |
1036 | |
1037 See :meth:`~bioblend.galaxy.tools.ToolClient.upload_file` for | |
1038 the optional parameters. | |
1039 | |
1040 :rtype: :class:`~.HistoryDatasetAssociation` | |
1041 :return: the uploaded dataset | |
1042 """ | |
1043 out_dict = self.gi.gi.tools.upload_from_ftp(path, self.id, **kwargs) | |
1044 self.refresh() | |
1045 return self.get_dataset(out_dict['outputs'][0]['id']) | |
1046 | |
1047 def paste_content(self, content, **kwargs): | |
1048 """ | |
1049 Upload a string to a new dataset in this history. | |
1050 | |
1051 :type content: str | |
1052 :param content: content of the new dataset to upload | |
1053 | |
1054 See :meth:`~bioblend.galaxy.tools.ToolClient.upload_file` for | |
1055 the optional parameters (except file_name). | |
1056 | |
1057 :rtype: :class:`~.HistoryDatasetAssociation` | |
1058 :return: the uploaded dataset | |
1059 """ | |
1060 out_dict = self.gi.gi.tools.paste_content(content, self.id, **kwargs) | |
1061 self.refresh() | |
1062 return self.get_dataset(out_dict['outputs'][0]['id']) | |
1063 | |
1064 def export(self, gzip=True, include_hidden=False, include_deleted=False, | |
1065 wait=False, maxwait=None): | |
1066 """ | |
1067 Start a job to create an export archive for this history. See | |
1068 :meth:`~bioblend.galaxy.histories.HistoryClient.export_history` | |
1069 for parameter and return value info. | |
1070 """ | |
1071 return self.gi.gi.histories.export_history( | |
1072 self.id, gzip=gzip, include_hidden=include_hidden, | |
1073 include_deleted=include_deleted, wait=wait, maxwait=maxwait) | |
1074 | |
1075 def download(self, jeha_id, outf, chunk_size=bioblend.CHUNK_SIZE): | |
1076 """ | |
1077 Download an export archive for this history. Use :meth:`export` | |
1078 to create an export and get the required ``jeha_id``. See | |
1079 :meth:`~bioblend.galaxy.histories.HistoryClient.download_history` | |
1080 for parameter and return value info. | |
1081 """ | |
1082 return self.gi.gi.histories.download_history( | |
1083 self.id, jeha_id, outf, chunk_size=chunk_size) | |
1084 | |
1085 def create_dataset_collection(self, collection_description): | |
1086 """ | |
1087 Create a new dataset collection in the history by providing a collection description. | |
1088 | |
1089 :type collection_description: bioblend.galaxy.dataset_collections.CollectionDescription | |
1090 :param collection_description: a description of the dataset collection | |
1091 | |
1092 :rtype: :class:`~.HistoryDatasetCollectionAssociation` | |
1093 :return: the new dataset collection | |
1094 """ | |
1095 dataset_collection = self.gi.gi.histories.create_dataset_collection(self.id, collection_description) | |
1096 self.refresh() | |
1097 return self.get_dataset_collection(dataset_collection['id']) | |
1098 | |
1099 def get_dataset_collection(self, dsc_id): | |
1100 """ | |
1101 Retrieve the dataset collection corresponding to the given id. | |
1102 | |
1103 :type dsc_id: str | |
1104 :param dsc_id: dataset collection id | |
1105 | |
1106 :rtype: :class:`~.HistoryDatasetCollectionAssociation` | |
1107 :return: the dataset collection corresponding to ``dsc_id`` | |
1108 """ | |
1109 dsc_dict = self.gi.gi.histories.show_dataset_collection(self.id, dsc_id) | |
1110 return self.DSC_TYPE(dsc_dict, self, gi=self.gi) | |
1111 | |
1112 | |
1113 class Library(DatasetContainer): | |
1114 """ | |
1115 Maps to a Galaxy library. | |
1116 """ | |
1117 BASE_ATTRS = DatasetContainer.BASE_ATTRS + ('description', 'synopsis') | |
1118 DS_TYPE = LibraryDataset | |
1119 CONTENT_INFO_TYPE = LibraryContentInfo | |
1120 API_MODULE = 'libraries' | |
1121 | |
1122 def __init__(self, lib_dict, content_infos=None, gi=None): | |
1123 super(Library, self).__init__( | |
1124 lib_dict, content_infos=content_infos, gi=gi) | |
1125 | |
1126 @property | |
1127 def gi_module(self): | |
1128 return self.gi.libraries | |
1129 | |
1130 @property | |
1131 def folder_ids(self): | |
1132 """ | |
1133 Return the ids of the contained folders. | |
1134 """ | |
1135 return [_.id for _ in self.content_infos if _.type == 'folder'] | |
1136 | |
1137 def delete(self): | |
1138 """ | |
1139 Delete this library. | |
1140 """ | |
1141 self.gi.libraries.delete(id_=self.id) | |
1142 self.refresh() | |
1143 self.unmap() | |
1144 | |
1145 def _pre_upload(self, folder): | |
1146 """ | |
1147 Return the id of the given folder, after sanity checking. | |
1148 """ | |
1149 if not self.is_mapped: | |
1150 raise RuntimeError('library is not mapped to a Galaxy object') | |
1151 return None if folder is None else folder.id | |
1152 | |
1153 def upload_data(self, data, folder=None, **kwargs): | |
1154 """ | |
1155 Upload data to this library. | |
1156 | |
1157 :type data: str | |
1158 :param data: dataset contents | |
1159 | |
1160 :type folder: :class:`~.Folder` | |
1161 :param folder: a folder object, or ``None`` to upload to the root folder | |
1162 | |
1163 :rtype: :class:`~.LibraryDataset` | |
1164 :return: the dataset object that represents the uploaded content | |
1165 | |
1166 Optional keyword arguments: ``file_type``, ``dbkey``. | |
1167 """ | |
1168 fid = self._pre_upload(folder) | |
1169 res = self.gi.gi.libraries.upload_file_contents( | |
1170 self.id, data, folder_id=fid, **kwargs) | |
1171 self.refresh() | |
1172 return self.get_dataset(res[0]['id']) | |
1173 | |
1174 def upload_from_url(self, url, folder=None, **kwargs): | |
1175 """ | |
1176 Upload data to this library from the given URL. | |
1177 | |
1178 :type url: str | |
1179 :param url: URL from which data should be read | |
1180 | |
1181 See :meth:`.upload_data` for info on other params. | |
1182 """ | |
1183 fid = self._pre_upload(folder) | |
1184 res = self.gi.gi.libraries.upload_file_from_url( | |
1185 self.id, url, folder_id=fid, **kwargs) | |
1186 self.refresh() | |
1187 return self.get_dataset(res[0]['id']) | |
1188 | |
1189 def upload_from_local(self, path, folder=None, **kwargs): | |
1190 """ | |
1191 Upload data to this library from a local file. | |
1192 | |
1193 :type path: str | |
1194 :param path: local file path from which data should be read | |
1195 | |
1196 See :meth:`.upload_data` for info on other params. | |
1197 """ | |
1198 fid = self._pre_upload(folder) | |
1199 res = self.gi.gi.libraries.upload_file_from_local_path( | |
1200 self.id, path, folder_id=fid, **kwargs) | |
1201 self.refresh() | |
1202 return self.get_dataset(res[0]['id']) | |
1203 | |
1204 def upload_from_galaxy_fs(self, paths, folder=None, link_data_only=None, **kwargs): | |
1205 """ | |
1206 Upload data to this library from filesystem paths on the server. | |
1207 | |
1208 .. note:: | |
1209 For this method to work, the Galaxy instance must have the | |
1210 ``allow_path_paste`` (``allow_library_path_paste`` in Galaxy | |
1211 ``release_17.05`` and earlier) option set to ``true`` in the | |
1212 ``config/galaxy.yml`` configuration file. | |
1213 | |
1214 :type paths: str or :class:`~collections.Iterable` of str | |
1215 :param paths: server-side file paths from which data should be read | |
1216 | |
1217 :type link_data_only: str | |
1218 :param link_data_only: either 'copy_files' (default) or | |
1219 'link_to_files'. Setting to 'link_to_files' symlinks instead of | |
1220 copying the files | |
1221 | |
1222 :rtype: list of :class:`~.LibraryDataset` | |
1223 :return: the dataset objects that represent the uploaded content | |
1224 | |
1225 See :meth:`.upload_data` for info on other params. | |
1226 """ | |
1227 fid = self._pre_upload(folder) | |
1228 if isinstance(paths, six.string_types): | |
1229 paths = (paths,) | |
1230 paths = '\n'.join(paths) | |
1231 res = self.gi.gi.libraries.upload_from_galaxy_filesystem( | |
1232 self.id, paths, folder_id=fid, link_data_only=link_data_only, | |
1233 **kwargs) | |
1234 if res is None: | |
1235 raise RuntimeError('upload_from_galaxy_filesystem: no reply') | |
1236 if not isinstance(res, collections.Sequence): | |
1237 raise RuntimeError( | |
1238 'upload_from_galaxy_filesystem: unexpected reply: %r' % res) | |
1239 new_datasets = [ | |
1240 self.get_dataset(ds_info['id']) for ds_info in res | |
1241 ] | |
1242 self.refresh() | |
1243 return new_datasets | |
1244 | |
1245 def copy_from_dataset(self, hda, folder=None, message=''): | |
1246 """ | |
1247 Copy a history dataset into this library. | |
1248 | |
1249 :type hda: :class:`~.HistoryDatasetAssociation` | |
1250 :param hda: history dataset to copy into the library | |
1251 | |
1252 See :meth:`.upload_data` for info on other params. | |
1253 """ | |
1254 fid = self._pre_upload(folder) | |
1255 res = self.gi.gi.libraries.copy_from_dataset( | |
1256 self.id, hda.id, folder_id=fid, message=message) | |
1257 self.refresh() | |
1258 return self.get_dataset(res['library_dataset_id']) | |
1259 | |
1260 def create_folder(self, name, description=None, base_folder=None): | |
1261 """ | |
1262 Create a folder in this library. | |
1263 | |
1264 :type name: str | |
1265 :param name: folder name | |
1266 | |
1267 :type description: str | |
1268 :param description: optional folder description | |
1269 | |
1270 :type base_folder: :class:`~.Folder` | |
1271 :param base_folder: parent folder, or ``None`` to create in the root | |
1272 folder | |
1273 | |
1274 :rtype: :class:`~.Folder` | |
1275 :return: the folder just created | |
1276 """ | |
1277 bfid = None if base_folder is None else base_folder.id | |
1278 res = self.gi.gi.libraries.create_folder( | |
1279 self.id, name, description=description, base_folder_id=bfid) | |
1280 self.refresh() | |
1281 return self.get_folder(res[0]['id']) | |
1282 | |
1283 def get_folder(self, f_id): | |
1284 """ | |
1285 Retrieve the folder corresponding to the given id. | |
1286 | |
1287 :rtype: :class:`~.Folder` | |
1288 :return: the folder corresponding to ``f_id`` | |
1289 """ | |
1290 f_dict = self.gi.gi.libraries.show_folder(self.id, f_id) | |
1291 return Folder(f_dict, self, gi=self.gi) | |
1292 | |
1293 @property | |
1294 def root_folder(self): | |
1295 """ | |
1296 The root folder of this library. | |
1297 | |
1298 :rtype: :class:`~.Folder` | |
1299 :return: the root folder of this library | |
1300 """ | |
1301 return self.get_folder(self.gi.gi.libraries._get_root_folder_id(self.id)) | |
1302 | |
1303 | |
1304 class Folder(Wrapper): | |
1305 """ | |
1306 Maps to a folder in a Galaxy library. | |
1307 """ | |
1308 BASE_ATTRS = Wrapper.BASE_ATTRS + ('description', 'deleted', 'item_count') | |
1309 | |
1310 def __init__(self, f_dict, container, gi=None): | |
1311 super(Folder, self).__init__(f_dict, gi=gi) | |
1312 object.__setattr__(self, 'container', container) | |
1313 | |
1314 @property | |
1315 def parent(self): | |
1316 """ | |
1317 The parent folder of this folder. The parent of the root folder is | |
1318 ``None``. | |
1319 | |
1320 :rtype: :class:`~.Folder` | |
1321 :return: the parent of this folder | |
1322 """ | |
1323 if self._cached_parent is None: | |
1324 object.__setattr__(self, | |
1325 '_cached_parent', | |
1326 self._get_parent()) | |
1327 return self._cached_parent | |
1328 | |
1329 def _get_parent(self): | |
1330 """ | |
1331 Return the parent folder of this folder. | |
1332 """ | |
1333 # Galaxy release_13.04 and earlier does not have parent_id in the folder | |
1334 # dictionary, may be implemented by searching for the folder with the | |
1335 # correct name | |
1336 if 'parent_id' not in self.wrapped: | |
1337 raise NotImplementedError('This method has not been implemented for Galaxy release_13.04 and earlier') | |
1338 parent_id = self.wrapped['parent_id'] | |
1339 if parent_id is None: | |
1340 return None | |
1341 # Galaxy from release_14.02 to release_15.01 returns a dummy parent_id | |
1342 # for the root folder instead of None, so check if this is the root | |
1343 if self.id == self.gi.gi.libraries._get_root_folder_id(self.container.id): | |
1344 return None | |
1345 # Galaxy release_13.11 and earlier returns a parent_id without the | |
1346 # initial 'F' | |
1347 if not parent_id.startswith('F'): | |
1348 parent_id = 'F' + parent_id | |
1349 return self.container.get_folder(parent_id) | |
1350 | |
1351 @property | |
1352 def gi_module(self): | |
1353 return self.gi.libraries | |
1354 | |
1355 @property | |
1356 def container_id(self): | |
1357 """ | |
1358 Deprecated property. | |
1359 | |
1360 Id of the folder container. Use :attr:`.container.id` instead. | |
1361 """ | |
1362 return self.container.id | |
1363 | |
1364 def refresh(self): | |
1365 """ | |
1366 Re-fetch the attributes pertaining to this object. | |
1367 | |
1368 Returns: self | |
1369 """ | |
1370 f_dict = self.gi.gi.libraries.show_folder(self.container.id, self.id) | |
1371 self.__init__(f_dict, self.container, gi=self.gi) | |
1372 return self | |
1373 | |
1374 | |
1375 class Tool(Wrapper): | |
1376 """ | |
1377 Maps to a Galaxy tool. | |
1378 """ | |
1379 BASE_ATTRS = Wrapper.BASE_ATTRS + ('version',) | |
1380 POLLING_INTERVAL = 10 # for output state monitoring | |
1381 | |
1382 def __init__(self, t_dict, gi=None): | |
1383 super(Tool, self).__init__(t_dict, gi=gi) | |
1384 | |
1385 @property | |
1386 def gi_module(self): | |
1387 return self.gi.tools | |
1388 | |
1389 def run(self, inputs, history, wait=False, | |
1390 polling_interval=POLLING_INTERVAL): | |
1391 """ | |
1392 Execute this tool in the given history with inputs from dict | |
1393 ``inputs``. | |
1394 | |
1395 :type inputs: dict | |
1396 :param inputs: dictionary of input datasets and parameters for | |
1397 the tool (see below) | |
1398 | |
1399 :type history: :class:`History` | |
1400 :param history: the history where to execute the tool | |
1401 | |
1402 :type wait: bool | |
1403 :param wait: whether to wait while the returned datasets are | |
1404 in a pending state | |
1405 | |
1406 :type polling_interval: float | |
1407 :param polling_interval: polling interval in seconds | |
1408 | |
1409 :rtype: list of :class:`HistoryDatasetAssociation` | |
1410 :return: list of output datasets | |
1411 | |
1412 The ``inputs`` dict should contain input datasets and parameters | |
1413 in the (largely undocumented) format used by the Galaxy API. | |
1414 Some examples can be found in `Galaxy's API test suite | |
1415 <https://github.com/galaxyproject/galaxy/blob/dev/test/api/test_tools.py>`_. | |
1416 The value of an input dataset can also be a :class:`Dataset` | |
1417 object, which will be automatically converted to the needed | |
1418 format. | |
1419 """ | |
1420 for k, v in six.iteritems(inputs): | |
1421 if isinstance(v, Dataset): | |
1422 inputs[k] = {'src': v.SRC, 'id': v.id} | |
1423 out_dict = self.gi.gi.tools.run_tool(history.id, self.id, inputs) | |
1424 outputs = [history.get_dataset(_['id']) for _ in out_dict['outputs']] | |
1425 if wait: | |
1426 self.gi._wait_datasets(outputs, polling_interval=polling_interval) | |
1427 return outputs | |
1428 | |
1429 | |
1430 class Job(Wrapper): | |
1431 """ | |
1432 Maps to a Galaxy job. | |
1433 """ | |
1434 BASE_ATTRS = ('id', 'state') | |
1435 | |
1436 def __init__(self, j_dict, gi=None): | |
1437 super(Job, self).__init__(j_dict, gi=gi) | |
1438 | |
1439 @property | |
1440 def gi_module(self): | |
1441 return self.gi.jobs | |
1442 | |
1443 | |
1444 @six.add_metaclass(abc.ABCMeta) | |
1445 class Preview(Wrapper): | |
1446 """ | |
1447 Abstract base class for Galaxy entity 'previews'. | |
1448 | |
1449 Classes derived from this one model the short summaries returned | |
1450 by global getters such as ``/api/libraries``. | |
1451 """ | |
1452 BASE_ATTRS = Wrapper.BASE_ATTRS + ('deleted',) | |
1453 | |
1454 @abc.abstractmethod | |
1455 def __init__(self, pw_dict, gi=None): | |
1456 super(Preview, self).__init__(pw_dict, gi=gi) | |
1457 | |
1458 | |
1459 class LibraryPreview(Preview): | |
1460 """ | |
1461 Models Galaxy library 'previews'. | |
1462 | |
1463 Instances of this class wrap dictionaries obtained by getting | |
1464 ``/api/libraries`` from Galaxy. | |
1465 """ | |
1466 def __init__(self, pw_dict, gi=None): | |
1467 super(LibraryPreview, self).__init__(pw_dict, gi=gi) | |
1468 | |
1469 @property | |
1470 def gi_module(self): | |
1471 return self.gi.libraries | |
1472 | |
1473 | |
1474 class HistoryPreview(Preview): | |
1475 """ | |
1476 Models Galaxy history 'previews'. | |
1477 | |
1478 Instances of this class wrap dictionaries obtained by getting | |
1479 ``/api/histories`` from Galaxy. | |
1480 """ | |
1481 BASE_ATTRS = Preview.BASE_ATTRS + ('tags',) | |
1482 | |
1483 def __init__(self, pw_dict, gi=None): | |
1484 super(HistoryPreview, self).__init__(pw_dict, gi=gi) | |
1485 | |
1486 @property | |
1487 def gi_module(self): | |
1488 return self.gi.histories | |
1489 | |
1490 | |
1491 class WorkflowPreview(Preview): | |
1492 """ | |
1493 Models Galaxy workflow 'previews'. | |
1494 | |
1495 Instances of this class wrap dictionaries obtained by getting | |
1496 ``/api/workflows`` from Galaxy. | |
1497 """ | |
1498 BASE_ATTRS = Preview.BASE_ATTRS + ('published', 'tags') | |
1499 | |
1500 def __init__(self, pw_dict, gi=None): | |
1501 super(WorkflowPreview, self).__init__(pw_dict, gi=gi) | |
1502 | |
1503 @property | |
1504 def gi_module(self): | |
1505 return self.gi.workflows | |
1506 | |
1507 | |
1508 class JobPreview(Preview): | |
1509 """ | |
1510 Models Galaxy job 'previews'. | |
1511 | |
1512 Instances of this class wrap dictionaries obtained by getting | |
1513 ``/api/jobs`` from Galaxy. | |
1514 """ | |
1515 BASE_ATTRS = ('id', 'state') | |
1516 | |
1517 def __init__(self, pw_dict, gi=None): | |
1518 super(JobPreview, self).__init__(pw_dict, gi=gi) | |
1519 | |
1520 @property | |
1521 def gi_module(self): | |
1522 return self.gi.jobs |