Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/planemo/training/tutorial.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 """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  | |
| 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 |
