Source code for sbg.cwl.v1_0.context
import sys
import inspect
import functools
from contextlib import contextmanager
from sbg.cwl.v1_0.util import from_file
from sbg.cwl.v1_0.schema import InputBinding
from sbg.cwl.v1_0.wf.workflow import Workflow
from sbg.cwl.v1_0.cmd.tool import CommandLineTool
from sbg.cwl.v1_0.requirement.docker import Docker
from sbg.cwl.v1_0.requirement.shell_command import ShellCommand
from sbg.cwl.v1_0.requirement.inline_javascript import (
InlineJavascript
)
def _tool_from(t, f):
"""Generates an instance of CommandLineTool from annotated function."""
doc = inspect.getdoc(f)
if doc:
t.doc = doc
if hasattr(f, '__name__'):
t.id = f.__name__
t.label = f.__name__
t._set_inputs_from(f)
t._set_outputs_from(f)
py_info = sys.version_info
py = "python{major}.{minor}".format(
major=py_info.major,
minor=py_info.minor
)
encoded_script = '{}.py.b64'.format(f.__name__)
t.arguments = [
InputBinding(
shell_quote=False,
value_from='cat {encoded_script}|'.format(
encoded_script=encoded_script
)
),
InputBinding(
shell_quote=False,
value_from='base64 --decode > {script};'.format(
script=encoded_script.rstrip('.b64')
)
),
py, "{}.py".format(f.__name__)
]
t._create_file_from(f)
t.add_input_json()
return t
def ctx_enter(cls, path, access):
"""
Function on context enter.
:param cls: ``class`` object
:param path: file path
:param access: access permission ('r' - read, 'w' - write, 'rw' - edit)
:return: an instance of ``cls``
"""
obj = None
if path and 'r' in access:
cwl = from_file(path)
if isinstance(cwl, dict):
if "class" in cwl:
if cwl['class'] == cls.__name__:
obj = cls(**cwl)
else:
raise ValueError(
"Expected class={}, got class={}".format(
cls.__name__, cwl['class'])
)
else:
raise ValueError("Missing class key.")
else:
raise ValueError('Type Error, got: {}'.format(type(cwl)))
if not obj:
obj = cls()
return obj
def ctx_exit(obj, path, access):
"""
Called on context exit. if ``access='w'`` dumps ``obj`` into
file specified by ``path``.
:param obj: object to be dumped
:param path: file path
:param access: access permission ('r' - read, 'w' - write, 'rw' - edit)
"""
if 'w' in access:
obj.dump(path)
[docs]@contextmanager
def tool(path=None, access='r'):
"""
Class that can be used with ``with`` statement for reading/writing/editing
tool.
:param path: file path
:param access: access permission ('r' - read, 'w' - write, 'rw' - edit)
Example of writing:
.. code-block:: python
from sbg import cwl
with cwl.tool('example.cwl', 'w') as t:
t.id = 'tool_id' # set id
t.label = 'Dummy tool' # set label
Example of reading:
.. code-block:: python
from sbg import cwl
with cwl.tool('example.cwl', 'r') as t:
print(' '.join(t.base_command)) # print base command
Example of editing:
.. code-block:: python
from sbg import cwl
with cwl.tool('example.cwl', 'rw') as t:
t.doc = 'New description...' # edit tool description
"""
obj = ctx_enter(CommandLineTool, path, access)
yield obj
ctx_exit(obj, path, access)
[docs]@contextmanager
def workflow(path=None, access='r'):
"""
Class that can be used with ``with`` statement for reading/writing/editing
workflow.
:param path: file path
:param access: access permission ('r' - read, 'w' - write, 'rw' - edit)
Example of writing:
.. code-block:: python
from sbg import cwl
with cwl.workflow('example.cwl', 'w') as wf:
wf.id = 'workflow_id' # set id
wf.label = 'Dummy workflow' # set label
Example of reading:
.. code-block:: python
from sbg import cwl
with cwl.workflow('example.cwl', 'r') as wf:
for step in wf.steps: # print workflow steps
print(step)
Example of editing:
.. code-block:: python
from sbg import cwl
with cwl.workflow('example.cwl', 'rw') as wf:
wf.doc = 'New description...' # edit workflow description
"""
obj = ctx_enter(Workflow, path, access)
yield obj
ctx_exit(obj, path, access)
[docs]@contextmanager
def tool_from(f, path=None, access='r'):
"""
Class that can be used with ``with`` statement for reading/writing/editing
tool from function ``f``.
:param f: function
:param path: file path
:param access: access permission ('r' - read, 'w' - write, 'rw' - edit)
Example:
.. code-block:: python
from sbg import cwl
def multiply(x: cwl.Int(), y: cwl.Int()) -> dict(out=cwl.Int()):
return dict(out=x * y)
# creates CWL application from function multiply
with cwl.tool_from(multiply, 'multiply.cwl', 'w') as t:
t.add_docker_requirement(
docker_pull='<docker_with_python>'
)
"""
obj = _tool_from(ctx_enter(CommandLineTool, path, access), f)
yield obj
ctx_exit(obj, path, access)
[docs]def to_tool(
# constructor arguments
inputs=None, outputs=None, id=None, requirements=None,
hints=None, label=None, doc=None, base_command=None,
arguments=None, stdin=None, stdout=None, stderr=None,
success_codes=None, temporary_fail_codes=None,
permanent_fail_codes=None,
# helpers
docker=None, # Docker image
js=True, # Add InlineJavascriptRequirement
sh=True # Add ShellCommandRequirement
):
"""
.. decorator:: to_tool
Decorator for creating tool from a function.
:param inputs: annotated inputs represented as a dictionary, where
keys=inputs and values=types
:param outputs: annotated outputs represented as a dictionary, where
keys=outputs and values=types
:param id: id of a tool (default name of a decorated function)
:param requirements: list of a tool requirements
:param hints: list of a tool hints
:param label: label of a tool
:param doc: description of a tool
:param base_command: base command of a tool
:param arguments: command line bindings which are not directly associated
with input parameters
:param stdin: a path to a file whose contents must be piped into
the command's standard input stream
:param stdout: capture the command's standard output stream to a file
written to the designated output directory
:param stderr: capture the command's standard error stream to a file
written to the designated output directory.
:param success_codes: exit codes that indicate the process completed
successfully.
:param temporary_fail_codes: Exit codes that indicate the process failed
due to a possibly temporary condition
:param permanent_fail_codes: exit codes that indicate the process failed
due to a permanent logic error
:param docker: specify a Docker image to retrieve using docker pull
:param js: include ``InlineJavascriptRequirement``
:param sh: include ``ShellCommandRequirement``
:return: typing.Callable[..., CommandLineTool]
Example:
.. code-block:: python
from sbg import cwl
@cwl.to_tool(
inputs=dict(x=cwl.Float(), n=cwl.Int()),
outputs=dict(out=cwl.Float()),
docker='images.sbgenomics.com/filip_tubic/ubuntu1604py'
)
def times_n(x, n=10):
\"""Returns x * n\"""
return dict(out=x * n)
t = times_n()
print(t.label) # prints 'to_tool'
print(t.doc) # prints 'Returns x * n'
print(t.inputs) # prints inputs
print(t.outputs) # prints outputs
print(t.base_command) # prints [python{major}.{minor}, 'to_tool.py']
"""
def deco(f):
@functools.wraps(f)
def wrapper():
# monkey patch arguments
f.to_tool_args = dict(
inputs=inputs, outputs=outputs, id=id,
requirements=requirements,
hints=hints, label=label, doc=doc, base_command=base_command,
arguments=arguments, stdin=stdin, stdout=stdout, stderr=stderr,
success_codes=success_codes,
temporary_fail_codes=temporary_fail_codes,
permanent_fail_codes=permanent_fail_codes,
docker=docker, js=js, sh=sh
)
t = CommandLineTool(
inputs=[], outputs=[], id=id, requirements=requirements,
hints=hints, label=label, doc=doc, base_command=base_command,
arguments=arguments, stdin=stdin, stdout=stdout,
stderr=stderr, success_codes=success_codes,
temporary_fail_codes=temporary_fail_codes,
permanent_fail_codes=permanent_fail_codes
)
if docker:
t.add_requirement(Docker(docker_pull=docker))
if js:
t.add_requirement(InlineJavascript())
if sh:
t.add_requirement(ShellCommand())
return _tool_from(t, f)
return wrapper
return deco