API reference

Render Click commands from parsed CWL tools.

get_base_command(clt)

Return the executable name from a CWL tool.

Raises:
  • ValueError –

    If the tool does not define a string base command.

Source code in src/cwl2click/__init__.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def get_base_command(clt: CommandLineTool) -> str:
    """Return the executable name from a CWL tool.

    Raises:
        ValueError: If the tool does not define a string base command.
    """
    if clt.baseCommand:
        if isinstance(clt.baseCommand, list) and len(clt.baseCommand) > 0:
            command = clt.baseCommand[0]
            if isinstance(command, str):
                return command

        if isinstance(clt.baseCommand, str):
            return clt.baseCommand

    raise ValueError(
        f"CommandLineTool '{clt.id}' does not define a 'baseCommand' property, impossible to map it to a `click.Command`"
    )

get_command_name(clt)

Return the subcommand name, or None when no string name is defined.

Source code in src/cwl2click/__init__.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def get_command_name(clt: CommandLineTool) -> str | None:
    """Return the subcommand name, or None when no string name is defined."""
    if clt.baseCommand:
        if isinstance(clt.baseCommand, list) and len(clt.baseCommand) > 1:
            command = clt.baseCommand[1]
            if isinstance(command, str):
                return command

        if clt.arguments:
            if isinstance(clt.arguments, list):
                command = clt.arguments[0]
                if isinstance(command, str):
                    return command
                return None
            return str(clt.arguments)

    return None

is_flag(type_)

Indicate whether a CWL type includes a boolean flag.

Source code in src/cwl2click/__init__.py
94
95
96
def is_flag(type_: Any) -> bool:
    """Indicate whether a CWL type includes a boolean flag."""
    return (isinstance(type_, list) and "boolean" in type_) or type_ == "boolean"

is_multiple(type_)

Indicate whether a CWL input accepts multiple values.

Source code in src/cwl2click/__init__.py
83
84
85
86
87
88
89
90
91
def is_multiple(type_: Any) -> bool:
    """Indicate whether a CWL input accepts multiple values."""
    if not is_array(type_):
        return False

    array_size = _get_array_size(type_)
    nullable_union_size = 2

    return not (array_size == nullable_union_size and is_nullable(type_))

to_click(command_line_tools, module_name, output_stream, bundle=True)

Write generated Click command source to the supplied output stream.

Source code in src/cwl2click/__init__.py
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
def to_click(
    command_line_tools: Iterable[Process],
    module_name: str,
    output_stream: TextIO,
    bundle: bool = True,
) -> None:
    """Write generated Click command source to the supplied output stream."""
    template = _jinja_environment.get_template("command_line_tools.py.jinja")

    output_stream.write(
        template.render(
            version=_get_version(),
            timestamp=datetime.fromtimestamp(time.time()).isoformat(timespec="milliseconds"),
            bundle=bundle,
            module_name=module_name,
            command_line_tools=command_line_tools,
        )
    )

to_click_type(type_)

Map a parsed CWL type to a Click parameter type expression.

Source code in src/cwl2click/__init__.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
def to_click_type(type_: object) -> str:
    """Map a parsed CWL type to a Click parameter type expression."""
    key: object = None

    if isinstance(type_, str):
        key = type_
    elif isinstance(type_, list):
        key = next(item_type for item_type in type_ if item_type != "null")
    elif hasattr(type_, "items"):
        key = type_.items
    elif hasattr(type_, "class_"):
        key = type_.class_
    elif hasattr(type_, "symbols"):
        return f"Choice({[symbol.split('/')[-1] for symbol in type_.symbols]})"

    if key and not isinstance(key, str) and hasattr(key, "symbols"):
        return f"Choice({[symbol.split('/')[-1] for symbol in key.symbols]})"

    mapped_type: str = _CWL_CLICK_MAP_.get(key, "STRING")

    logger.debug(f"Type {type_}, represented by key type {key}, mapped to {mapped_type}")

    return mapped_type

to_python_type(type_)

Map a parsed CWL type to its Python type name.

Source code in src/cwl2click/__init__.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
def to_python_type(type_: object) -> str:
    """Map a parsed CWL type to its Python type name."""
    logger.debug(f"Converting {type_} CWL type to the related Python type...")

    key: object
    if isinstance(type_, str):
        key = type_
    elif isinstance(type_, list):
        key = next(item_type for item_type in type_ if item_type != "null")
    elif hasattr(type_, "class_"):
        key = type_.class_

        if key == "enum":
            key = "string"

    else:
        return str(type_)

    return _CWL_PYTHON_MAP_.get(key, str(type_))

Plugin registration

Serialize the resolved CWL document to options.output.

Source code in src/cwl2click/plugin.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
@transpiler_plugin(
    name="cwl2click",
    description="Boostrap a Python CLI using click from a CWL CommandLineTool(s).",
    options_model=Cwl2ClickOptions,
)
def cwl2click(context: TranspilerContext, options: Cwl2ClickOptions) -> None:
    """Serialize the resolved CWL document to ``options.output``."""

    try:
        command_line_tools = list(
            context.get_processes_by_type(
                CommandLineTool, options.clt_id if options.clt_id else None
            )
        )
        if options.bundle:
            targets = [(_get_target(context.source, options.output), command_line_tools)]
        else:
            targets = [
                (
                    options.output / clt.id / "src" / to_snake_case(clt.id) / "cli.py",
                    [clt],
                )
                for clt in command_line_tools
            ]

        for target, tools in targets:
            target.parent.mkdir(parents=True, exist_ok=True)
            with target.open("w") as stream:
                to_click(
                    command_line_tools=tools,
                    module_name=target.parent.absolute().name,
                    output_stream=stream,
                    bundle=options.bundle,
                )

            logger.success(
                f"'{context.source}' successfully converted to Click Python application in "
                f"'{target.absolute()}'."
            )
    except Exception as error:
        raise PluginExecutionError(
            f"An unexpected error occurred while generating CommandLineTool(s) found in input {context.source} CWL document"
        ) from error

Options

Bases: BaseModel

Options accepted by the cwl2click plugin.

Source code in src/cwl2click/plugin.py
33
34
35
36
37
38
39
40
41
42
43
class Cwl2ClickOptions(BaseModel):
    """Options accepted by the cwl2click plugin."""

    model_config = ConfigDict(extra="forbid")

    clt_id: list[str] = Field(default_factory=list, description="ID(s) of the CommandLineTools")

    output: Path = Field(description="Output directory path")
    bundle: bool = Field(
        default=False, description="Bundle all tools into one module with subcommands"
    )