comparison env/lib/python3.7/site-packages/planemo/training/tutorial.py @ 5:9b1c78e6ba9c draft default tip

"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author shellac
date Mon, 01 Jun 2020 08:59:25 -0400
parents 79f47841a781
children
comparison
equal deleted inserted replaced
4:79f47841a781 5:9b1c78e6ba9c
1 """Module contains code for the Tutorial class, dealing with the creation of a training tutorial."""
2
3 import collections
4 import json
5 import os
6 import re
7 import shutil
8
9 import oyaml as yaml
10 import requests
11 import six
12
13 from planemo import templates
14 from planemo.bioblend import galaxy
15 from planemo.engine import (
16 engine_context,
17 is_galaxy_engine,
18 )
19 from planemo.io import info
20 from planemo.runnable import for_path
21 from .tool_input import (
22 get_empty_input,
23 get_empty_param,
24 ToolInput
25 )
26 from .utils import (
27 load_yaml,
28 save_to_yaml
29 )
30
31 TUTO_HAND_ON_TEMPLATE = """---
32 layout: tutorial_hands_on
33
34 {{ metadata }}
35 ---
36
37 {{ body }}
38 """
39
40 TUTO_SLIDES_TEMPLATE = """---
41 layout: tutorial_slides
42 logo: "GTN"
43
44 {{ metadata }}
45 ---
46
47 ### How to fill the slide decks?
48
49 Please follow our
50 [tutorial to learn how to fill the slides]({{ '{{' }} site.baseurl {{ '}}' }}/topics/contributing/tutorials/create-new-tutorial-slides/slides.html)
51 """
52
53
54 HANDS_ON_TOOL_BOX_TEMPLATE = """
55 ## Sub-step with **{{tool_name}}**
56
57 > ### {{ '{%' }} icon hands_on {{ '%}' }} Hands-on: Task description
58 >
59 > 1. **{{tool_name}}** {{ '{%' }} icon tool {{ '%}' }} with the following parameters:{{inputlist}}{{paramlist}}
60 >
61 > ***TODO***: *Check parameter descriptions*
62 >
63 > ***TODO***: *Consider adding a comment or tip box*
64 >
65 > > ### {{ '{%' }} icon comment {{ '%}' }} Comment
66 > >
67 > > A comment about the tool or something else. This box can also be in the main text
68 > {: .comment}
69 >
70 {: .hands_on}
71
72 ***TODO***: *Consider adding a question to test the learners understanding of the previous exercise*
73
74 > ### {{ '{%' }} icon question {{ '%}' }} Questions
75 >
76 > 1. Question1?
77 > 2. Question2?
78 >
79 > > ### {{ '{%' }} icon solution {{ '%}' }} Solution
80 > >
81 > > 1. Answer for question1
82 > > 2. Answer for question2
83 > >
84 > {: .solution}
85 >
86 {: .question}
87
88 """
89
90 TUTO_BIBLIOGRAPHY_TEMPLATE = """
91 # This is the bibliography file for your tutorial.
92 #
93 # To add bibliography (bibtex) entries here, follow these steps:
94 # 1) Find the DOI for the article you want to cite
95 # 2) Go to https://doi2bib.org and fill in the DOI
96 # 3) Copy the resulting bibtex entry into this file
97 #
98 # To cite the example below, in your tutorial.md file
99 # use {{ '{%' }} Batut2018 {{ '%}' }}
100
101 @article{Batut2018,
102 doi = {10.1016/j.cels.2018.05.012},
103 url = {https://doi.org/10.1016/j.cels.2018.05.012},
104 year = {2018},
105 month = jun,
106 publisher = {Elsevier {BV}},
107 volume = {6},
108 number = {6},
109 pages = {752--758.e1},
110 author = {B{\\'{e}}r{\\'{e}}nice Batut and Saskia Hiltemann and Andrea Bagnacani and Dannon Baker and Vivek Bhardwaj and
111 Clemens Blank and Anthony Bretaudeau and Loraine Brillet-Gu{\\'{e}}guen and Martin {\\v{C}}ech and John Chilton
112 and Dave Clements and Olivia Doppelt-Azeroual and Anika Erxleben and Mallory Ann Freeberg and Simon Gladman and
113 Youri Hoogstrate and Hans-Rudolf Hotz and Torsten Houwaart and Pratik Jagtap and Delphine Larivi{\\`{e}}re and
114 Gildas Le Corguill{\\'{e}} and Thomas Manke and Fabien Mareuil and Fidel Ram{\\'{i}}rez and Devon Ryan and
115 Florian Christoph Sigloch and Nicola Soranzo and Joachim Wolff and Pavankumar Videm and Markus Wolfien and
116 Aisanjiang Wubuli and Dilmurat Yusuf and James Taylor and Rolf Backofen and Anton Nekrutenko and Bj\\"{o}rn Gr\\"{u}ning},
117 title = {Community-Driven Data Analysis Training for Biology},
118 journal = {Cell Systems}
119 }
120 """
121
122 TUTO_HAND_ON_BODY_TEMPLATE = """
123 # Introduction
124 {:.no_toc}
125
126 <!-- This is a comment. -->
127
128 General introduction about the topic and then an introduction of the
129 tutorial (the questions and the objectives). It is nice also to have a
130 scheme to sum up the pipeline used during the tutorial. The idea is to
131 give to trainees insight into the content of the tutorial and the (theoretical
132 and technical) key concepts they will learn.
133
134 You may want to cite some publications; this can be done by adding citations to the
135 bibliography file (`tutorial.bib` file next to your `tutorial.md` file). These citations
136 must be in bibtex format. If you have the DOI for the paper you wish to cite, you can
137 get the corresponding bibtex entry using [doi2bib.org](https://doi2bib.org).
138
139 With the example you will find in the `tutorial.bib` file, you can add a citation to
140 this article here in your tutorial like this:
141 {{ '{%' }} raw {{ '%}' }} `{{ '{%' }} cite Batut2018 {{ '%}' }}`{{ '{%' }} endraw {{ '%}' }}.
142 This will be rendered like this: {{ '{%' }} cite Batut2018 {{ '%}' }}, and links to a
143 [bibliography section](#bibliography) which will automatically be created at the end of the
144 tutorial.
145
146
147 **Please follow our
148 [tutorial to learn how to fill the Markdown]({{ '{{' }} site.baseurl {{ '}}' }}/topics/contributing/tutorials/\
149 create-new-tutorial-content/tutorial.html)**
150
151 > ### Agenda
152 >
153 > In this tutorial, we will cover:
154 >
155 > 1. TOC
156 > {:toc}
157 >
158 {: .agenda}
159
160 # Title for your first section
161
162 Give some background about what the trainees will be doing in the section.
163 Remember that many people reading your materials will likely be novices,
164 so make sure to explain all the relevant concepts.
165
166 ## Title for a subsection
167 Section and subsection titles will be displayed in the tutorial index on the left side of
168 the page, so try to make them informative and concise!
169
170 # Hands-on Sections
171 Below are a series of hand-on boxes, one for each tool in your workflow file.
172 Often you may wish to combine several boxes into one or make other adjustments such
173 as breaking the tutorial into sections, we encourage you to make such changes as you
174 see fit, this is just a starting point :)
175
176 Anywhere you find the word "***TODO***", there is something that needs to be changed
177 depending on the specifics of your tutorial.
178
179 have fun!
180
181 ## Get data
182
183 > ### {{ '{%' }} icon hands_on {{ '%}' }} Hands-on: Data upload
184 >
185 > 1. Create a new history for this tutorial
186 > 2. Import the files from [Zenodo]({{ zenodo_link }}) or from the shared data library
187 >
188 > ```
189 > {{ z_file_links }}
190 > ```
191 > ***TODO***: *Add the files by the ones on Zenodo here (if not added)*
192 >
193 > ***TODO***: *Remove the useless files (if added)*
194 >
195 > {{ '{%' }} include snippets/import_via_link.md {{ '%}' }}
196 > {{ '{%' }} include snippets/import_from_data_library.md {{ '%}' }}
197 >
198 > 3. Rename the datasets
199 > 4. Check that the datatype
200 >
201 > {{ '{%' }} include snippets/change_datatype.md datatype="datatypes" {{ '%}' }}
202 >
203 > 5. Add to each database a tag corresponding to ...
204 >
205 > {{ '{%' }} include snippets/add_tag.md {{ '%}' }}
206 >
207 {: .hands_on}
208
209 # Title of the section usually corresponding to a big step in the analysis
210
211 It comes first a description of the step: some background and some theory.
212 Some image can be added there to support the theory explanation:
213
214 ![Alternative text](../../images/image_name "Legend of the image")
215
216 The idea is to keep the theory description before quite simple to focus more on the practical part.
217
218 ***TODO***: *Consider adding a detail box to expand the theory*
219
220 > ### {{ '{%' }} icon details {{ '%}' }} More details about the theory
221 >
222 > But to describe more details, it is possible to use the detail boxes which are expandable
223 >
224 {: .details}
225
226 A big step can have several subsections or sub steps:
227
228 {{ body }}
229
230 ## Re-arrange
231
232 To create the template, each step of the workflow had its own subsection.
233
234 ***TODO***: *Re-arrange the generated subsections into sections or other subsections.
235 Consider merging some hands-on boxes to have a meaningful flow of the analyses*
236
237 # Conclusion
238 {:.no_toc}
239
240 Sum up the tutorial and the key takeaways here. We encourage adding an overview image of the
241 pipeline used.
242 """
243
244
245 class Tutorial(object):
246 """Class to describe a training tutorial."""
247
248 def __init__(self, training, topic, name="new_tuto", title="The new tutorial", zenodo_link=""):
249 """Init a tutorial instance."""
250 self.training = training
251 self.topic = topic
252 self.name = name
253 self.title = title
254 self.zenodo_link = zenodo_link
255 self.zenodo_file_links = []
256 self.questions = []
257 self.objectives = []
258 self.time = ""
259 self.key_points = []
260 self.contributors = []
261 self.body = ""
262 self.init_wf_fp = None
263 self.init_wf_id = None
264 self.hands_on = True
265 self.slides = False
266 self.datatype_fp = ""
267 self.set_dir_name()
268 self.init_data_lib()
269 self.body = templates.render(HANDS_ON_TOOL_BOX_TEMPLATE, **{
270 'tool_name': "My Tool",
271 'inputlist': get_empty_input(),
272 'paramlist': get_empty_param()
273 })
274
275 def init_from_kwds(self, kwds):
276 """Init a tutorial instance from a kwds dictionary."""
277 self.name = kwds["tutorial_name"]
278 self.title = kwds["tutorial_title"]
279 self.zenodo_link = kwds["zenodo_link"] if kwds["zenodo_link"] else ''
280 self.questions = [
281 "Which biological questions are addressed by the tutorial?",
282 "Which bioinformatics techniques are important to know for this type of data?"]
283 self.objectives = [
284 "The learning objectives are the goals of the tutorial",
285 "They will be informed by your audience and will communicate to them and to yourself what you should focus on during the course",
286 "They are single sentences describing what a learner should be able to do once they have completed the tutorial",
287 "You can use Bloom's Taxonomy to write effective learning objectives"]
288 self.time = "3H"
289 self.key_points = [
290 "The take-home messages",
291 "They will appear at the end of the tutorial"]
292 self.contributors = ["contributor1", "contributor2"]
293 self.init_wf_fp = kwds['workflow']
294 self.init_wf_id = kwds['workflow_id']
295 self.hands_on = kwds['hands_on']
296 self.slides = kwds['slides']
297 self.datatype_fp = kwds['datatypes']
298 self.set_dir_name()
299 self.init_data_lib()
300
301 def init_from_existing_tutorial(self, tuto_name):
302 """Init a tutorial instance from an existing tutorial (data library and tutorial.md)."""
303 self.name = tuto_name
304 self.set_dir_name()
305
306 if not self.exists():
307 raise Exception("The tutorial %s does not exists. It should be created" % self.name)
308
309 # get the metadata information of the tutorial (from the top of the tutorial.md)
310 with open(self.tuto_fp, "r") as tuto_f:
311 tuto_content = tuto_f.read()
312 regex = r'^---\n(?P<metadata>[\s\S]*)\n---(?P<body>[\s\S]*)'
313 tuto_split_regex = re.search(regex, tuto_content)
314 if not tuto_split_regex:
315 raise Exception("No metadata found at the top of the tutorial")
316 metadata = yaml.safe_load(tuto_split_regex.group("metadata"))
317 self.title = metadata["title"]
318 self.zenodo_link = metadata["zenodo_link"]
319 self.questions = metadata["questions"]
320 self.objectives = metadata["objectives"]
321 self.time_estimation = metadata["time_estimation"]
322 self.key_points = metadata["key_points"]
323 self.contributors = metadata["contributors"]
324
325 # the tutorial content
326 self.body = tuto_split_regex.group("body")
327
328 # get the data library
329 self.init_data_lib()
330
331 def init_data_lib(self):
332 """Init the data library dictionary."""
333 if os.path.exists(self.data_lib_fp):
334 self.data_lib = load_yaml(self.data_lib_fp)
335 else:
336 self.data_lib = collections.OrderedDict()
337 # set default information
338 self.data_lib.setdefault('destination', collections.OrderedDict())
339 self.data_lib['destination']['type'] = 'library'
340 self.data_lib['destination']['name'] = 'GTN - Material'
341 self.data_lib['destination']['description'] = 'Galaxy Training Network Material'
342 self.data_lib['destination']['synopsis'] = 'Galaxy Training Network Material. See https://training.galaxyproject.org'
343 self.data_lib.setdefault('items', [])
344 self.data_lib.pop('libraries', None)
345 # get topic or create new one
346 topic = collections.OrderedDict()
347 for item in self.data_lib['items']:
348 if item['name'] == self.topic.title:
349 topic = item
350 if not topic:
351 self.data_lib['items'].append(topic)
352 topic['name'] = self.topic.title
353 topic['description'] = self.topic.summary
354 topic['items'] = []
355 # get tutorial or create new one
356 self.tuto_data_lib = collections.OrderedDict()
357 for item in topic['items']:
358 if item['name'] == self.title:
359 self.tuto_data_lib = item
360 if not self.tuto_data_lib:
361 topic['items'].append(self.tuto_data_lib)
362 self.tuto_data_lib['name'] = self.title
363 self.tuto_data_lib['items'] = []
364
365 # GETTERS
366 def get_tuto_metata(self):
367 """Return the string corresponding to the tutorial metadata."""
368 metadata = collections.OrderedDict()
369 metadata['title'] = self.title
370 metadata['zenodo_link'] = self.zenodo_link
371 metadata['questions'] = self.questions
372 metadata['objectives'] = self.objectives
373 metadata['time_estimation'] = self.time
374 metadata['key_points'] = self.key_points
375 metadata['contributors'] = self.contributors
376 return yaml.safe_dump(
377 metadata,
378 indent=2,
379 default_flow_style=False,
380 default_style='',
381 explicit_start=False)
382
383 # SETTERS
384 def set_dir_name(self):
385 """Set the path to dir and files of a tutorial."""
386 self.dir = os.path.join(self.topic.dir, "tutorials", self.name)
387 self.tuto_fp = os.path.join(self.dir, "tutorial.md")
388 self.bib_fp = os.path.join(self.dir, "tutorial.bib")
389 self.slide_fp = os.path.join(self.dir, 'slides.html')
390 self.data_lib_fp = os.path.join(self.dir, "data-library.yaml")
391 self.wf_dir = os.path.join(self.dir, "workflows")
392 self.wf_fp = os.path.join(self.wf_dir, "main_workflow.ga")
393 self.tour_dir = os.path.join(self.dir, "tours")
394 # remove empty workflow file if there
395 empty_wf_filepath = os.path.join(self.wf_dir, "empty_workflow.ga")
396 if os.path.exists(empty_wf_filepath):
397 os.remove(empty_wf_filepath)
398
399 # TEST METHODS
400 def exists(self):
401 """Test if the tutorial exists."""
402 return os.path.isdir(self.dir)
403
404 def has_workflow(self):
405 """Test if a workflow is provided for the tutorial."""
406 return self.init_wf_fp or self.init_wf_id
407
408 # EXPORT METHODS
409 def export_workflow_file(self):
410 """Copy or extract workflow file and add it to the tutorial directory."""
411 if not os.path.exists(self.wf_dir):
412 os.makedirs(self.wf_dir)
413 if not os.path.exists(os.path.join(self.wf_dir, 'index.md')):
414 with open(os.path.join(self.wf_dir, 'index.md'), 'w') as handle:
415 handle.write('---\nlayout: workflow-list\n---\n')
416 if self.init_wf_fp:
417 shutil.copy(self.init_wf_fp, self.wf_fp)
418 elif self.init_wf_id:
419 gi = galaxy.GalaxyInstance(self.training.galaxy_url, key=self.training.galaxy_api_key)
420 gi.workflows.export_workflow_to_local_path(
421 self.init_wf_id,
422 self.wf_fp,
423 use_default_filename=False)
424
425 # OTHER METHODS
426 def get_files_from_zenodo(self):
427 """Extract a list of URLs and dictionary describing the files from the JSON output of the Zenodo API."""
428 z_record, req_res = get_zenodo_record(self.zenodo_link)
429
430 self.zenodo_file_links = []
431 if 'files' not in req_res:
432 raise ValueError("No files in the Zenodo record")
433
434 files = []
435 for f in req_res['files']:
436 file_dict = {'url': '', 'src': 'url', 'ext': '', 'info': self.zenodo_link}
437 if 'type' in f:
438 file_dict['ext'] = get_galaxy_datatype(f['type'], self.datatype_fp)
439 if 'links' not in f and 'self' not in f['links']:
440 raise ValueError("No link for file %s" % f)
441 file_dict['url'] = f['links']['self']
442 self.zenodo_file_links.append(f['links']['self'])
443 files.append(file_dict)
444
445 return (files, z_record)
446
447 def prepare_data_library_from_zenodo(self):
448 """Get the list of URLs of the files on Zenodo, fill the data library, save it into the file."""
449 self.zenodo_file_links = []
450 if self.zenodo_link != '':
451 files, z_record = self.get_files_from_zenodo()
452 if z_record:
453 # get current data library and/or previous data library for the tutorial
454 # remove the latest tag of any existing library
455 # remove the any other existing library
456 current_data_lib = collections.OrderedDict()
457 previous_data_lib = collections.OrderedDict()
458 for item in self.tuto_data_lib['items']:
459 if item['name'] == "DOI: 10.5281/zenodo.%s" % z_record:
460 current_data_lib = item
461 elif item['description'] == 'latest':
462 previous_data_lib = item
463 previous_data_lib['description'] = ''
464 if not current_data_lib:
465 current_data_lib['name'] = "DOI: 10.5281/zenodo.%s" % z_record
466 current_data_lib['description'] = 'latest'
467 current_data_lib['items'] = []
468 current_data_lib['items'] = files
469
470 self.tuto_data_lib['items'] = [current_data_lib]
471 if previous_data_lib:
472 self.tuto_data_lib['items'].append(previous_data_lib)
473 save_to_yaml(self.data_lib, self.data_lib_fp)
474
475 def write_hands_on_tutorial(self, add_z_file_links=True):
476 """Write the content of the hands-on tutorial in the corresponding file."""
477 if add_z_file_links:
478 self.body = templates.render(TUTO_HAND_ON_BODY_TEMPLATE, **{
479 "z_file_links": "\n> ".join(self.zenodo_file_links),
480 "body": self.body
481 })
482 # write in the tutorial file with the metadata on the top
483 metadata = self.get_tuto_metata()
484 with open(self.tuto_fp, 'w') as md:
485 md.write(templates.render(TUTO_HAND_ON_TEMPLATE, **{
486 "metadata": metadata,
487 "body": self.body
488 }))
489
490 # create the bibliography file
491 self.write_bibliography()
492
493 def write_bibliography(self):
494 """Write the content of the bibliography file for the tutorial."""
495 with open(self.bib_fp, 'w') as bib:
496 bib.write(templates.render(TUTO_BIBLIOGRAPHY_TEMPLATE, **{
497 "body": self.body
498 }))
499
500 def create_hands_on_tutorial(self, ctx):
501 """Create tutorial structure from the workflow file (if it is provided)."""
502 # load workflow and get hands-on body from the workflow
503 if self.init_wf_id:
504 if not self.training.galaxy_url:
505 raise ValueError("No Galaxy URL given")
506 if not self.training.galaxy_api_key:
507 raise ValueError("No API key to access the given Galaxy instance")
508 self.body = get_hands_on_boxes_from_running_galaxy(self.init_wf_id, self.training.galaxy_url, self.training.galaxy_api_key)
509 elif self.init_wf_fp:
510 self.body = get_hands_on_boxes_from_local_galaxy(self.training.kwds, self.init_wf_fp, ctx)
511 # write tutorial body
512 self.write_hands_on_tutorial()
513
514 def create_tutorial(self, ctx):
515 """Create the skeleton of a new tutorial."""
516 # create tuto folder and empty files
517 os.makedirs(self.dir)
518 os.makedirs(self.tour_dir)
519 os.makedirs(self.wf_dir)
520
521 # extract the data library from Zenodo and the links for the tutorial
522 if self.zenodo_link != '':
523 info("Create the data library from Zenodo")
524 self.prepare_data_library_from_zenodo()
525
526 # create tutorial skeleton from workflow and copy workflow file
527 if self.hands_on:
528 info("Create tutorial skeleton from workflow (if it is provided)")
529 self.create_hands_on_tutorial(ctx)
530 self.export_workflow_file()
531
532 # create slide skeleton
533 if self.slides:
534 with open(self.slide_fp, 'w') as slide_f:
535 slide_f.write(
536 templates.render(TUTO_SLIDES_TEMPLATE, **{"metadata": self.get_tuto_metata()}))
537
538
539 def get_galaxy_datatype(z_ext, datatype_fp):
540 """Get the Galaxy datatype corresponding to a Zenodo file type."""
541 g_datatype = ''
542 datatypes = load_yaml(datatype_fp)
543 if z_ext in datatypes:
544 g_datatype = datatypes[z_ext]
545 if g_datatype == '':
546 g_datatype = '# Please add a Galaxy datatype or update the shared/datatypes.yaml file'
547 info("Get Galaxy datatypes: %s --> %s" % (z_ext, g_datatype))
548 return g_datatype
549
550
551 def get_zenodo_record(zenodo_link):
552 """Get the content of a Zenodo record."""
553 # get the record in the Zenodo link
554 if 'doi' in zenodo_link:
555 z_record = zenodo_link.split('.')[-1]
556 else:
557 z_record = zenodo_link.split('/')[-1]
558 # get JSON corresponding to the record from Zenodo API
559 req = "https://zenodo.org/api/records/%s" % (z_record)
560 r = requests.get(req)
561 if r:
562 req_res = r.json()
563 else:
564 info("The Zenodo link (%s) seems invalid" % (zenodo_link))
565 req_res = {'files': []}
566 z_record = None
567 return(z_record, req_res)
568
569
570 def get_wf_inputs(step_inp):
571 """Get the inputs from a workflow step and format them into a hierarchical dictionary."""
572 inputs = {}
573 for inp_n, inp in step_inp.items():
574 if '|' in inp_n:
575 repeat_regex = r'(?P<prefix>[^\|]*)_(?P<nb>\d+)\|(?P<suffix>.+).+'
576 repeat_search = re.search(repeat_regex, inp_n)
577 hier_regex = r'(?P<prefix>[^\|]*)\|(?P<suffix>.+)'
578 hier_regex = re.search(hier_regex, inp_n)
579 if repeat_search and repeat_search.start(0) <= hier_regex.start(0):
580 inputs.setdefault(repeat_search.group('prefix'), {})
581 inputs[repeat_search.group('prefix')].setdefault(
582 repeat_search.group('nb'),
583 get_wf_inputs({hier_regex.group('suffix'): inp}))
584 else:
585 inputs.setdefault(hier_regex.group('prefix'), {})
586 inputs[hier_regex.group('prefix')].update(
587 get_wf_inputs({hier_regex.group('suffix'): inp}))
588 else:
589 inputs.setdefault(inp_n, inp)
590 return inputs
591
592
593 def get_wf_param_values(init_params, inp_connections):
594 """Get the param values from a workflow step and format them into a hierarchical dictionary."""
595 if not isinstance(init_params, six.string_types) or '": ' not in init_params:
596 form_params = init_params
597 else:
598 form_params = json.loads(init_params)
599 if isinstance(form_params, dict):
600 if '__class__' in form_params and (form_params['__class__'] == 'RuntimeValue' or form_params['__class__'] == 'ConnectedValue'):
601 form_params = inp_connections
602 else:
603 for p in form_params:
604 inp = inp_connections[p] if p in inp_connections else {}
605 form_params[p] = get_wf_param_values(form_params[p], inp)
606 elif isinstance(form_params, list):
607 json_params = form_params
608 form_params = []
609 for i, p in enumerate(json_params):
610 inp = inp_connections[str(i)] if str(i) in inp_connections else {}
611 form_params.append(get_wf_param_values(p, inp))
612 elif isinstance(form_params, six.string_types) and '"' in form_params:
613 form_params = form_params.replace('"', '')
614 return form_params
615
616
617 def format_wf_steps(wf, gi):
618 """Get a string with the hands-on boxes describing the different steps of the worklow."""
619 body = ''
620 steps = wf['steps']
621
622 for s in range(len(steps)):
623 wf_step = steps[str(s)]
624 # get params in workflow
625 wf_param_values = {}
626 if wf_step['tool_state'] and wf_step['input_connections']:
627 wf_param_values = get_wf_param_values(wf_step['tool_state'], get_wf_inputs(wf_step['input_connections']))
628 if not wf_param_values:
629 continue
630 # get tool description
631 try:
632 tool_desc = gi.tools.show_tool(wf_step['tool_id'], io_details=True)
633 except Exception:
634 tool_desc = {'inputs': []}
635 # get formatted param description
636 paramlist = ''
637 for inp in tool_desc["inputs"]:
638 tool_inp = ToolInput(inp, wf_param_values, steps, 1, should_be_there=True)
639 paramlist += tool_inp.get_formatted_desc()
640 # format the hands-on box
641 body += templates.render(HANDS_ON_TOOL_BOX_TEMPLATE, **{
642 "tool_name": wf_step['name'],
643 "paramlist": paramlist})
644 return body
645
646
647 def get_hands_on_boxes_from_local_galaxy(kwds, wf_filepath, ctx):
648 """Server local Galaxy and get the workflow dictionary."""
649 assert is_galaxy_engine(**kwds)
650 runnable = for_path(wf_filepath)
651 tuto_body = ''
652 with engine_context(ctx, **kwds) as galaxy_engine:
653 with galaxy_engine.ensure_runnables_served([runnable]) as config:
654 workflow_id = config.workflow_id(wf_filepath)
655 wf = config.gi.workflows.export_workflow_dict(workflow_id)
656 tuto_body = format_wf_steps(wf, config.gi)
657 return tuto_body
658
659
660 def get_hands_on_boxes_from_running_galaxy(wf_id, galaxy_url, galaxy_api_key):
661 """Get the workflow dictionary from a running Galaxy instance with the workflow installed on it."""
662 gi = galaxy.GalaxyInstance(galaxy_url, key=galaxy_api_key)
663 wf = gi.workflows.export_workflow_dict(wf_id)
664 tuto_body = format_wf_steps(wf, gi)
665 return tuto_body