snk-cli 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snk_cli/__about__.py +4 -0
- snk_cli/__init__.py +5 -0
- snk_cli/cli.py +231 -0
- snk_cli/config/__init__.py +1 -0
- snk_cli/config/config.py +219 -0
- snk_cli/config/utils.py +41 -0
- snk_cli/dynamic_typer.py +249 -0
- snk_cli/options/__init__.py +1 -0
- snk_cli/options/option.py +18 -0
- snk_cli/options/utils.py +117 -0
- snk_cli/subcommands/__init__.py +5 -0
- snk_cli/subcommands/config.py +64 -0
- snk_cli/subcommands/env.py +211 -0
- snk_cli/subcommands/profile.py +60 -0
- snk_cli/subcommands/run.py +471 -0
- snk_cli/subcommands/script.py +134 -0
- snk_cli/subcommands/utils.py +175 -0
- snk_cli/testing.py +19 -0
- snk_cli/utils.py +149 -0
- snk_cli/validate.py +66 -0
- snk_cli/workflow.py +177 -0
- snk_cli-0.0.1.dist-info/METADATA +29 -0
- snk_cli-0.0.1.dist-info/RECORD +25 -0
- snk_cli-0.0.1.dist-info/WHEEL +4 -0
- snk_cli-0.0.1.dist-info/licenses/LICENSE.txt +9 -0
snk_cli/__about__.py
ADDED
snk_cli/__init__.py
ADDED
snk_cli/cli.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import platform
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from snakemake import SNAKEFILE_CHOICES
|
|
11
|
+
from art import text2art
|
|
12
|
+
|
|
13
|
+
from snk_cli.dynamic_typer import DynamicTyper
|
|
14
|
+
from snk_cli.subcommands import EnvApp, ConfigApp, RunApp, ScriptApp, ProfileApp
|
|
15
|
+
|
|
16
|
+
from snk_cli.config.config import (
|
|
17
|
+
SnkConfig,
|
|
18
|
+
load_workflow_snakemake_config,
|
|
19
|
+
)
|
|
20
|
+
from snk_cli.options.utils import build_dynamic_cli_options
|
|
21
|
+
from snk_cli.workflow import Workflow
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CLI(DynamicTyper):
|
|
25
|
+
"""
|
|
26
|
+
Constructor for the dynamic Snk CLI class.
|
|
27
|
+
Args:
|
|
28
|
+
workflow_dir_path (Path): Path to the workflow directory.
|
|
29
|
+
Side Effects:
|
|
30
|
+
Initializes the CLI class.
|
|
31
|
+
Examples:
|
|
32
|
+
>>> CLI(Path('/path/to/workflow'))
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, workflow_dir_path: Path = None, *, pipeline_dir_path: Path = None, snk_config: SnkConfig = None) -> None:
|
|
36
|
+
if pipeline_dir_path is not None:
|
|
37
|
+
# raise a deprecation warning
|
|
38
|
+
import warnings
|
|
39
|
+
warnings.warn(
|
|
40
|
+
"The `pipeline_dir_path` argument is deprecated and will be removed in a future release. Use `workflow_dir_path` instead.",
|
|
41
|
+
DeprecationWarning,
|
|
42
|
+
)
|
|
43
|
+
workflow_dir_path = pipeline_dir_path
|
|
44
|
+
if workflow_dir_path is None:
|
|
45
|
+
# get the calling frame (the frame of the function that called this function)
|
|
46
|
+
calling_frame = inspect.currentframe().f_back
|
|
47
|
+
# get the file path from the calling frame
|
|
48
|
+
workflow_dir_path = Path(calling_frame.f_globals["__file__"])
|
|
49
|
+
else:
|
|
50
|
+
workflow_dir_path = Path(workflow_dir_path)
|
|
51
|
+
if workflow_dir_path.is_file():
|
|
52
|
+
workflow_dir_path = workflow_dir_path.parent
|
|
53
|
+
self.workflow = Workflow(path=workflow_dir_path)
|
|
54
|
+
self.snakemake_config = load_workflow_snakemake_config(workflow_dir_path)
|
|
55
|
+
if snk_config is None:
|
|
56
|
+
self.snk_config = SnkConfig.from_workflow_dir(
|
|
57
|
+
workflow_dir_path, create_if_not_exists=True
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
self.snk_config = snk_config
|
|
61
|
+
if self.snk_config.version:
|
|
62
|
+
self.version = self.snk_config.version
|
|
63
|
+
else:
|
|
64
|
+
self.version = self.workflow.version
|
|
65
|
+
self.options = build_dynamic_cli_options(self.snakemake_config, self.snk_config)
|
|
66
|
+
self.snakefile = self._find_snakefile()
|
|
67
|
+
self.conda_prefix_dir = self.workflow.conda_prefix_dir
|
|
68
|
+
self.singularity_prefix_dir = self.workflow.singularity_prefix_dir
|
|
69
|
+
self.name = self.workflow.name
|
|
70
|
+
self.verbose = False
|
|
71
|
+
if (
|
|
72
|
+
platform.system() == "Darwin"
|
|
73
|
+
and platform.processor() == "arm"
|
|
74
|
+
and not os.environ.get("CONDA_SUBDIR")
|
|
75
|
+
):
|
|
76
|
+
os.environ["CONDA_SUBDIR"] = "osx-64"
|
|
77
|
+
|
|
78
|
+
# dynamically create the logo
|
|
79
|
+
self.logo = self._create_logo(
|
|
80
|
+
tagline=self.snk_config.tagline, font=self.snk_config.font
|
|
81
|
+
)
|
|
82
|
+
callback = self._create_callback()
|
|
83
|
+
callback.__doc__ = self.logo
|
|
84
|
+
|
|
85
|
+
# registration
|
|
86
|
+
self.register_callback(
|
|
87
|
+
callback,
|
|
88
|
+
invoke_without_command=True,
|
|
89
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
90
|
+
)
|
|
91
|
+
self.register_command(self.info, help="Show information about the workflow.")
|
|
92
|
+
|
|
93
|
+
run_app = RunApp(
|
|
94
|
+
conda_prefix_dir=self.conda_prefix_dir,
|
|
95
|
+
snk_config=self.snk_config,
|
|
96
|
+
singularity_prefix_dir=self.singularity_prefix_dir,
|
|
97
|
+
snakefile=self.snakefile,
|
|
98
|
+
workflow=self.workflow,
|
|
99
|
+
verbose=self.verbose,
|
|
100
|
+
logo=self.logo,
|
|
101
|
+
dynamic_run_options=self.options,
|
|
102
|
+
)
|
|
103
|
+
# Subcommands
|
|
104
|
+
self.register_command(
|
|
105
|
+
run_app,
|
|
106
|
+
name="run",
|
|
107
|
+
)
|
|
108
|
+
self.register_command(
|
|
109
|
+
ConfigApp(
|
|
110
|
+
workflow=self.workflow,
|
|
111
|
+
options=self.options,
|
|
112
|
+
),
|
|
113
|
+
name="config",
|
|
114
|
+
)
|
|
115
|
+
if self.workflow.environments:
|
|
116
|
+
self.register_group(
|
|
117
|
+
EnvApp(
|
|
118
|
+
workflow=self.workflow,
|
|
119
|
+
conda_prefix_dir=self.conda_prefix_dir,
|
|
120
|
+
snakemake_config=self.snakemake_config,
|
|
121
|
+
snakefile=self.snakefile,
|
|
122
|
+
),
|
|
123
|
+
name="env",
|
|
124
|
+
help="Access the workflow conda environments.",
|
|
125
|
+
)
|
|
126
|
+
if self.workflow.scripts:
|
|
127
|
+
self.register_group(
|
|
128
|
+
ScriptApp(
|
|
129
|
+
workflow=self.workflow,
|
|
130
|
+
conda_prefix_dir=self.conda_prefix_dir,
|
|
131
|
+
snakemake_config=self.snakemake_config,
|
|
132
|
+
snakefile=self.snakefile,
|
|
133
|
+
),
|
|
134
|
+
name="script",
|
|
135
|
+
help="Access the workflow scripts.",
|
|
136
|
+
)
|
|
137
|
+
if self.workflow.profiles:
|
|
138
|
+
self.register_group(
|
|
139
|
+
ProfileApp(
|
|
140
|
+
workflow=self.workflow,
|
|
141
|
+
),
|
|
142
|
+
name="profile",
|
|
143
|
+
help="Access the workflow profiles.",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def _print_pipline_version(self, ctx: typer.Context, value: bool):
|
|
147
|
+
if value:
|
|
148
|
+
typer.echo(self.version)
|
|
149
|
+
raise typer.Exit()
|
|
150
|
+
|
|
151
|
+
def _print_pipline_path(self, ctx: typer.Context, value: bool):
|
|
152
|
+
if value:
|
|
153
|
+
typer.echo(self.workflow.path)
|
|
154
|
+
raise typer.Exit()
|
|
155
|
+
|
|
156
|
+
def _create_callback(self):
|
|
157
|
+
def callback(
|
|
158
|
+
ctx: typer.Context,
|
|
159
|
+
version: Optional[bool] = typer.Option(
|
|
160
|
+
None,
|
|
161
|
+
"-v",
|
|
162
|
+
"--version",
|
|
163
|
+
help="Show the workflow version and exit.",
|
|
164
|
+
is_eager=True,
|
|
165
|
+
callback=self._print_pipline_version,
|
|
166
|
+
show_default=False,
|
|
167
|
+
),
|
|
168
|
+
path: Optional[bool] = typer.Option(
|
|
169
|
+
None,
|
|
170
|
+
"-p",
|
|
171
|
+
"--path",
|
|
172
|
+
help="Show the workflow path and exit.",
|
|
173
|
+
is_eager=True,
|
|
174
|
+
callback=self._print_pipline_path,
|
|
175
|
+
show_default=False,
|
|
176
|
+
),
|
|
177
|
+
):
|
|
178
|
+
if ctx.invoked_subcommand is None:
|
|
179
|
+
typer.echo(f"{ctx.get_help()}")
|
|
180
|
+
|
|
181
|
+
return callback
|
|
182
|
+
|
|
183
|
+
def _create_logo(
|
|
184
|
+
self, tagline="A Snakemake workflow CLI generated with snk", font="small"
|
|
185
|
+
):
|
|
186
|
+
"""
|
|
187
|
+
Create a logo for the CLI.
|
|
188
|
+
Args:
|
|
189
|
+
font (str): The font to use for the logo.
|
|
190
|
+
Returns:
|
|
191
|
+
str: The logo.
|
|
192
|
+
Examples:
|
|
193
|
+
>>> CLI._create_logo()
|
|
194
|
+
"""
|
|
195
|
+
if self.snk_config.art:
|
|
196
|
+
art = self.snk_config.art
|
|
197
|
+
else:
|
|
198
|
+
logo = self.snk_config.logo if self.snk_config.logo else self.name
|
|
199
|
+
art = text2art(logo, font=font)
|
|
200
|
+
doc = f"""\b{art}\b{tagline}"""
|
|
201
|
+
return doc
|
|
202
|
+
|
|
203
|
+
def _find_snakefile(self):
|
|
204
|
+
"""
|
|
205
|
+
Search possible snakefile locations.
|
|
206
|
+
Returns:
|
|
207
|
+
Path: The path to the snakefile.
|
|
208
|
+
Examples:
|
|
209
|
+
>>> CLI._find_snakefile()
|
|
210
|
+
"""
|
|
211
|
+
for path in SNAKEFILE_CHOICES:
|
|
212
|
+
if (self.workflow.path / path).exists():
|
|
213
|
+
return self.workflow.path / path
|
|
214
|
+
raise FileNotFoundError("Snakefile not found!")
|
|
215
|
+
|
|
216
|
+
def info(self):
|
|
217
|
+
"""
|
|
218
|
+
Display information about current workflow install.
|
|
219
|
+
Returns:
|
|
220
|
+
str: A JSON string containing information about the current workflow install.
|
|
221
|
+
Examples:
|
|
222
|
+
>>> CLI.info()
|
|
223
|
+
"""
|
|
224
|
+
import json
|
|
225
|
+
|
|
226
|
+
info_dict = {}
|
|
227
|
+
info_dict["name"] = self.workflow.path.name
|
|
228
|
+
info_dict["version"] = self.version
|
|
229
|
+
info_dict["workflow_dir_path"] = str(self.workflow.path)
|
|
230
|
+
typer.echo(json.dumps(info_dict, indent=2))
|
|
231
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .config import SnkConfig
|
snk_cli/config/config.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
import snakemake
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from .utils import get_version_from_config
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SnkConfigError(Exception):
|
|
10
|
+
"""Base class for all SNK config exceptions"""
|
|
11
|
+
|
|
12
|
+
class InvalidSnkConfigError(SnkConfigError, ValueError):
|
|
13
|
+
"""Thrown if the given SNK config appears to have an invalid format."""
|
|
14
|
+
|
|
15
|
+
class MissingSnkConfigError(SnkConfigError, FileNotFoundError):
|
|
16
|
+
"""Thrown if the given SNK config file cannot be found."""
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class SnkConfig:
|
|
20
|
+
"""
|
|
21
|
+
A dataclass for storing Snakemake workflow configuration.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
art: str = None
|
|
25
|
+
logo: str = None
|
|
26
|
+
tagline: str = "A Snakemake workflow CLI generated with Snk"
|
|
27
|
+
font: str = "small"
|
|
28
|
+
version: Optional[str] = None
|
|
29
|
+
conda: bool = True
|
|
30
|
+
resources: List[Path] = field(default_factory=list)
|
|
31
|
+
skip_missing: bool = False # skip any missing cli options (i.e. those not in the snk file)
|
|
32
|
+
additional_snakemake_args: List[str] = field(default_factory=list)
|
|
33
|
+
cli: dict = field(default_factory=dict)
|
|
34
|
+
symlink_resources: bool = False
|
|
35
|
+
_snk_config_path: Path = None
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_path(cls, snk_config_path: Path):
|
|
39
|
+
"""
|
|
40
|
+
Load and validate Snk config from file.
|
|
41
|
+
Args:
|
|
42
|
+
snk_config_path (Path): Path to the SNK config file.
|
|
43
|
+
Returns:
|
|
44
|
+
SnkConfig: A SnkConfig object.
|
|
45
|
+
Raises:
|
|
46
|
+
FileNotFoundError: If the SNK config file is not found.
|
|
47
|
+
Examples:
|
|
48
|
+
>>> SnkConfig.from_path(Path("snk.yaml"))
|
|
49
|
+
SnkConfig(art=None, logo=None, tagline='A Snakemake workflow CLI generated with Snk', font='small', resources=[], annotations={}, symlink_resources=False, _snk_config_path=PosixPath('snk.yaml'))
|
|
50
|
+
"""
|
|
51
|
+
if not snk_config_path.exists():
|
|
52
|
+
raise MissingSnkConfigError(
|
|
53
|
+
f"Could not find SNK config file: {snk_config_path}"
|
|
54
|
+
) from FileNotFoundError
|
|
55
|
+
# raise error if file is empty
|
|
56
|
+
if snk_config_path.stat().st_size == 0:
|
|
57
|
+
raise InvalidSnkConfigError(f"SNK config file is empty: {snk_config_path}") from ValueError
|
|
58
|
+
|
|
59
|
+
snk_config_dict = snakemake.load_configfile(snk_config_path)
|
|
60
|
+
snk_config_dict["version"] = get_version_from_config(snk_config_path, snk_config_dict)
|
|
61
|
+
if "annotations" in snk_config_dict:
|
|
62
|
+
# TODO: remove annotations in the future
|
|
63
|
+
snk_config_dict["cli"] = snk_config_dict["annotations"]
|
|
64
|
+
del snk_config_dict["annotations"]
|
|
65
|
+
if "conda_required" in snk_config_dict:
|
|
66
|
+
# TODO: remove conda_required in the future
|
|
67
|
+
snk_config_dict["conda"] = snk_config_dict["conda_required"]
|
|
68
|
+
del snk_config_dict["conda_required"]
|
|
69
|
+
snk_config = cls(**snk_config_dict)
|
|
70
|
+
snk_config.resources = [
|
|
71
|
+
snk_config_path.parent / resource for resource in snk_config.resources
|
|
72
|
+
]
|
|
73
|
+
snk_config.validate_resources(snk_config.resources)
|
|
74
|
+
snk_config._snk_config_path = snk_config_path
|
|
75
|
+
return snk_config
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_workflow_dir(
|
|
79
|
+
cls, workflow_dir_path: Path, create_if_not_exists: bool = False
|
|
80
|
+
):
|
|
81
|
+
"""
|
|
82
|
+
Load and validate SNK config from workflow directory.
|
|
83
|
+
Args:
|
|
84
|
+
workflow_dir_path (Path): Path to the workflow directory.
|
|
85
|
+
create_if_not_exists (bool): Whether to create a SNK config file if one does not exist.
|
|
86
|
+
Returns:
|
|
87
|
+
SnkConfig: A SnkConfig object.
|
|
88
|
+
Raises:
|
|
89
|
+
FileNotFoundError: If the SNK config file is not found.
|
|
90
|
+
Examples:
|
|
91
|
+
>>> SnkConfig.from_workflow_dir(Path("workflow"))
|
|
92
|
+
SnkConfig(art=None, logo=None, tagline='A Snakemake workflow CLI generated with Snk', font='small', resources=[], annotations={}, symlink_resources=False, _snk_config_path=PosixPath('workflow/snk.yaml'))
|
|
93
|
+
"""
|
|
94
|
+
if (workflow_dir_path / "snk.yaml").exists():
|
|
95
|
+
return cls.from_path(workflow_dir_path / "snk.yaml")
|
|
96
|
+
elif (workflow_dir_path / ".snk").exists():
|
|
97
|
+
import warnings
|
|
98
|
+
|
|
99
|
+
warnings.warn(
|
|
100
|
+
"Use of .snk will be deprecated in the future. Please use snk.yaml instead.",
|
|
101
|
+
DeprecationWarning,
|
|
102
|
+
)
|
|
103
|
+
return cls.from_path(workflow_dir_path / ".snk")
|
|
104
|
+
elif create_if_not_exists:
|
|
105
|
+
snk_config = cls(_snk_config_path=workflow_dir_path / "snk.yaml")
|
|
106
|
+
return snk_config
|
|
107
|
+
else:
|
|
108
|
+
raise FileNotFoundError(
|
|
109
|
+
f"Could not find SNK config file in workflow directory: {workflow_dir_path}"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def validate_resources(self, resources):
|
|
113
|
+
"""
|
|
114
|
+
Validate resources.
|
|
115
|
+
Args:
|
|
116
|
+
resources (List[Path]): List of resources to validate.
|
|
117
|
+
Raises:
|
|
118
|
+
FileNotFoundError: If a resource is not found.
|
|
119
|
+
Notes:
|
|
120
|
+
This function does not modify the resources list.
|
|
121
|
+
Examples:
|
|
122
|
+
>>> SnkConfig.validate_resources([Path("resource1.txt"), Path("resource2.txt")])
|
|
123
|
+
"""
|
|
124
|
+
for resource in resources:
|
|
125
|
+
if not resource.exists():
|
|
126
|
+
raise FileNotFoundError(f"Could not find resource: {resource}")
|
|
127
|
+
|
|
128
|
+
def add_resources(self, resources: List[Path], workflow_dir_path: Path = None):
|
|
129
|
+
"""
|
|
130
|
+
Add resources to the SNK config.
|
|
131
|
+
Args:
|
|
132
|
+
resources (List[Path]): List of resources to add.
|
|
133
|
+
workflow_dir_path (Path): Path to the workflow directory.
|
|
134
|
+
Returns:
|
|
135
|
+
None
|
|
136
|
+
Side Effects:
|
|
137
|
+
Adds the resources to the SNK config.
|
|
138
|
+
Examples:
|
|
139
|
+
>>> snk_config = SnkConfig()
|
|
140
|
+
>>> snk_config.add_resources([Path("resource1.txt"), Path("resource2.txt")], Path("workflow"))
|
|
141
|
+
"""
|
|
142
|
+
processed = []
|
|
143
|
+
for resource in resources:
|
|
144
|
+
if workflow_dir_path and not resource.is_absolute():
|
|
145
|
+
resource = workflow_dir_path / resource
|
|
146
|
+
processed.append(resource)
|
|
147
|
+
self.validate_resources(processed)
|
|
148
|
+
self.resources.extend(processed)
|
|
149
|
+
|
|
150
|
+
def to_yaml(self, path: Path) -> None:
|
|
151
|
+
"""
|
|
152
|
+
Write SNK config to YAML file.
|
|
153
|
+
Args:
|
|
154
|
+
path (Path): Path to write the YAML file to.
|
|
155
|
+
Returns:
|
|
156
|
+
None
|
|
157
|
+
Side Effects:
|
|
158
|
+
Writes the SNK config to the specified path.
|
|
159
|
+
Examples:
|
|
160
|
+
>>> snk_config = SnkConfig()
|
|
161
|
+
>>> snk_config.to_yaml(Path("snk.yaml"))
|
|
162
|
+
"""
|
|
163
|
+
config_dict = {k: v for k, v in vars(self).items() if not k.startswith("_")}
|
|
164
|
+
with open(path, "w") as f:
|
|
165
|
+
yaml.dump(config_dict, f)
|
|
166
|
+
|
|
167
|
+
def save(self) -> None:
|
|
168
|
+
"""
|
|
169
|
+
Save SNK config.
|
|
170
|
+
Args:
|
|
171
|
+
path (Path): Path to write the YAML file to.
|
|
172
|
+
Returns:
|
|
173
|
+
None
|
|
174
|
+
Side Effects:
|
|
175
|
+
Writes the SNK config to the path specified by _snk_config_path.
|
|
176
|
+
Examples:
|
|
177
|
+
>>> snk_config = SnkConfig()
|
|
178
|
+
>>> snk_config.save()
|
|
179
|
+
"""
|
|
180
|
+
self.to_yaml(self._snk_config_path)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_config_from_workflow_dir(workflow_dir_path: Path):
|
|
184
|
+
"""
|
|
185
|
+
Get the config file from a workflow directory.
|
|
186
|
+
Args:
|
|
187
|
+
workflow_dir_path (Path): Path to the workflow directory.
|
|
188
|
+
Returns:
|
|
189
|
+
Path: Path to the config file, or None if not found.
|
|
190
|
+
Examples:
|
|
191
|
+
>>> get_config_from_workflow_dir(Path("workflow"))
|
|
192
|
+
PosixPath('workflow/config.yaml')
|
|
193
|
+
"""
|
|
194
|
+
for path in [
|
|
195
|
+
Path("config") / "config.yaml",
|
|
196
|
+
Path("config") / "config.yml",
|
|
197
|
+
"config.yaml",
|
|
198
|
+
"config.yml",
|
|
199
|
+
]:
|
|
200
|
+
if (workflow_dir_path / path).exists():
|
|
201
|
+
return workflow_dir_path / path
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def load_workflow_snakemake_config(workflow_dir_path: Path):
|
|
206
|
+
"""
|
|
207
|
+
Load the Snakemake config from a workflow directory.
|
|
208
|
+
Args:
|
|
209
|
+
workflow_dir_path (Path): Path to the workflow directory.
|
|
210
|
+
Returns:
|
|
211
|
+
dict: The Snakemake config.
|
|
212
|
+
Examples:
|
|
213
|
+
>>> load_workflow_snakemake_config(Path("workflow"))
|
|
214
|
+
{'inputs': {'data': 'data.txt'}, 'outputs': {'results': 'results.txt'}}
|
|
215
|
+
"""
|
|
216
|
+
workflow_config_path = get_config_from_workflow_dir(workflow_dir_path)
|
|
217
|
+
if not workflow_config_path or not workflow_config_path.exists():
|
|
218
|
+
return {}
|
|
219
|
+
return snakemake.load_configfile(workflow_config_path)
|
snk_cli/config/utils.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from snakemake import load_configfile
|
|
3
|
+
|
|
4
|
+
def get_version_from_config(config_path: Path, config_dict: dict = None) -> str:
|
|
5
|
+
"""
|
|
6
|
+
Get the version from config. If dict not provided, load from file.
|
|
7
|
+
If the version is a path to a __about__ file, load the version from the file.
|
|
8
|
+
Path must be relative to the config file.
|
|
9
|
+
Args:
|
|
10
|
+
config_path (Path): Path to the config file.
|
|
11
|
+
config_dict (dict): Config dict.
|
|
12
|
+
Returns:
|
|
13
|
+
str: Version.
|
|
14
|
+
Examples:
|
|
15
|
+
>>> get_version_from_config_dict({"version": "0.1.0"})
|
|
16
|
+
'0.1.0'
|
|
17
|
+
"""
|
|
18
|
+
if not config_dict:
|
|
19
|
+
config_dict = load_configfile(config_path)
|
|
20
|
+
|
|
21
|
+
if "version" not in config_dict:
|
|
22
|
+
return None
|
|
23
|
+
if config_dict["version"] is None:
|
|
24
|
+
return None
|
|
25
|
+
version = str(config_dict["version"])
|
|
26
|
+
if "__about__.py" in version:
|
|
27
|
+
# load version from __about__.py
|
|
28
|
+
about_path = config_path.parent / version
|
|
29
|
+
if not about_path.exists():
|
|
30
|
+
raise FileNotFoundError(
|
|
31
|
+
f"Could not find version file: {about_path}"
|
|
32
|
+
)
|
|
33
|
+
about = {}
|
|
34
|
+
exec(about_path.read_text(), about)
|
|
35
|
+
try:
|
|
36
|
+
version = about["__version__"]
|
|
37
|
+
except KeyError as e:
|
|
38
|
+
raise KeyError(
|
|
39
|
+
f"Could not find __version__ in file: {about_path}"
|
|
40
|
+
) from e
|
|
41
|
+
return version
|