snk-cli 0.1.4__tar.gz → 0.2.1__tar.gz

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.
Files changed (70) hide show
  1. {snk_cli-0.1.4 → snk_cli-0.2.1}/.github/workflows/tests.yml +1 -1
  2. {snk_cli-0.1.4 → snk_cli-0.2.1}/PKG-INFO +2 -2
  3. {snk_cli-0.1.4 → snk_cli-0.2.1}/pyproject.toml +5 -15
  4. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/__about__.py +1 -1
  5. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/cli.py +14 -2
  6. snk_cli-0.2.1/src/snk_cli/conda.py +106 -0
  7. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/config/config.py +3 -3
  8. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/config/utils.py +5 -1
  9. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/dynamic_typer.py +1 -1
  10. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/subcommands/env.py +12 -29
  11. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/subcommands/run.py +34 -16
  12. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/subcommands/script.py +3 -10
  13. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/test_cli/test_run.py +9 -0
  14. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/test_cli/test_subcommands.py +1 -0
  15. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/test_cli/test_workflow_cli.py +2 -3
  16. snk_cli-0.2.1/tests/test_conda_env.py +11 -0
  17. snk_cli-0.1.4/.github/workflows/mkdocs.yml +0 -23
  18. snk_cli-0.1.4/src/snk_cli/subcommands/utils.py +0 -175
  19. {snk_cli-0.1.4 → snk_cli-0.2.1}/.github/workflows/publish.yml +0 -0
  20. {snk_cli-0.1.4 → snk_cli-0.2.1}/.gitignore +0 -0
  21. {snk_cli-0.1.4 → snk_cli-0.2.1}/LICENSE.txt +0 -0
  22. {snk_cli-0.1.4 → snk_cli-0.2.1}/README.md +0 -0
  23. {snk_cli-0.1.4 → snk_cli-0.2.1}/docs/index.md +0 -0
  24. {snk_cli-0.1.4 → snk_cli-0.2.1}/docs/reference/cli.md +0 -0
  25. {snk_cli-0.1.4 → snk_cli-0.2.1}/docs/reference/config.md +0 -0
  26. {snk_cli-0.1.4 → snk_cli-0.2.1}/docs/reference/dynamic_typer.md +0 -0
  27. {snk_cli-0.1.4 → snk_cli-0.2.1}/docs/reference/options.md +0 -0
  28. {snk_cli-0.1.4 → snk_cli-0.2.1}/docs/reference/subcommands.md +0 -0
  29. {snk_cli-0.1.4 → snk_cli-0.2.1}/docs/reference/testing.md +0 -0
  30. {snk_cli-0.1.4 → snk_cli-0.2.1}/docs/reference/utils.md +0 -0
  31. {snk_cli-0.1.4 → snk_cli-0.2.1}/docs/reference/validate.md +0 -0
  32. {snk_cli-0.1.4 → snk_cli-0.2.1}/docs/reference/workflow.md +0 -0
  33. {snk_cli-0.1.4 → snk_cli-0.2.1}/mkdocs.yml +0 -0
  34. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/__init__.py +0 -0
  35. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/config/__init__.py +0 -0
  36. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/options/__init__.py +0 -0
  37. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/options/option.py +0 -0
  38. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/options/utils.py +0 -0
  39. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/subcommands/__init__.py +0 -0
  40. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/subcommands/config.py +0 -0
  41. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/subcommands/profile.py +0 -0
  42. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/testing.py +0 -0
  43. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/utils.py +0 -0
  44. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/validate.py +0 -0
  45. {snk_cli-0.1.4 → snk_cli-0.2.1}/src/snk_cli/workflow.py +0 -0
  46. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/__init__.py +0 -0
  47. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/conftest.py +0 -0
  48. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/artic_v4.1.bed +0 -0
  49. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/config.yaml +0 -0
  50. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/cov.fasta +0 -0
  51. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/print_config/Snakefile +0 -0
  52. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/print_config/cli.py +0 -0
  53. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/print_config/config.yaml +0 -0
  54. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/print_config/snk.yaml +0 -0
  55. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/workflow/cli.py +0 -0
  56. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/workflow/config.yaml +0 -0
  57. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/workflow/resources/data.txt +0 -0
  58. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/workflow/snk.yaml +0 -0
  59. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/workflow/things/__about__.py +0 -0
  60. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/workflow/workflow/Snakefile +0 -0
  61. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/workflow/workflow/envs/python.yml +0 -0
  62. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/workflow/workflow/profiles/base/config.yaml +0 -0
  63. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/workflow/workflow/profiles/slurm/config.yaml +0 -0
  64. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/data/workflow/workflow/scripts/hello.py +0 -0
  65. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/test_cli/__init__.py +0 -0
  66. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/test_cli/test_dynamic_options.py +0 -0
  67. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/test_cli/test_snk_config.py +0 -0
  68. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/test_cli/test_validate.py +0 -0
  69. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/test_dynamic_typer.py +0 -0
  70. {snk_cli-0.1.4 → snk_cli-0.2.1}/tests/utils.py +0 -0
@@ -31,7 +31,7 @@ jobs:
31
31
  - run: sudo apt-get -y install graphviz
32
32
  - name: Run tests
33
33
  shell: bash
34
- run: hatch run cov
34
+ run: hatch run snakemake:cov
35
35
  - name: Upload coverage reports to Codecov
36
36
  uses: codecov/codecov-action@v3
37
37
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: snk-cli
3
- Version: 0.1.4
3
+ Version: 0.2.1
4
4
  Project-URL: Documentation, https://github.com/unknown/snk-cli#readme
5
5
  Project-URL: Issues, https://github.com/unknown/snk-cli/issues
6
6
  Project-URL: Source, https://github.com/unknown/snk-cli
@@ -22,7 +22,7 @@ Requires-Dist: datrie>=0.8.2
22
22
  Requires-Dist: gitpython~=3.1
23
23
  Requires-Dist: makefun~=1.15
24
24
  Requires-Dist: pulp<2.8
25
- Requires-Dist: snakemake<8,>=7
25
+ Requires-Dist: snakemake>=7
26
26
  Requires-Dist: typer[all]~=0.9.0
27
27
  Description-Content-Type: text/markdown
28
28
 
@@ -25,7 +25,7 @@ classifiers = [
25
25
  "Programming Language :: Python :: Implementation :: PyPy",
26
26
  ]
27
27
  dependencies = [
28
- "snakemake>=7,<8",
28
+ "snakemake>=7",
29
29
  "typer[all]~=0.9.0",
30
30
  "GitPython~=3.1",
31
31
  "pulp<2.8", # Pin pulp <2.8 for snakemake: https://github.com/snakemake/snakemake/issues/2607
@@ -43,24 +43,14 @@ Source = "https://github.com/unknown/snk-cli"
43
43
  path = "src/snk_cli/__about__.py"
44
44
 
45
45
 
46
- [[tool.hatch.envs.test.matrix]]
47
- python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
48
- snakemake = ["7.32.4"]
49
-
50
- # [[tool.hatch.envs.test.matrix]]
51
- # python = ["3.11", "3.12"]
52
- # snakemake = ["8.5.5"]
53
-
54
- [tool.hatch.envs.test]
55
- dependencies = [
56
- "coverage[toml]>=6.5",
57
- "pytest",
58
- "snakemake=={matrix:snakemake}"]
46
+ [[tool.hatch.envs.snakemake.matrix]]
47
+ snakemake = ["7.32.4", "8.10.8"]
59
48
 
60
49
  [tool.hatch.envs.default]
61
50
  dependencies = [
62
51
  "coverage[toml]>=6.5",
63
52
  "pytest",
53
+ "snakemake=={matrix:snakemake:7.32.4}",
64
54
  ]
65
55
  [tool.hatch.envs.default.scripts]
66
56
  test = "pytest {args:tests}"
@@ -75,7 +65,7 @@ cov = [
75
65
  ]
76
66
 
77
67
  [[tool.hatch.envs.all.matrix]]
78
- python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
68
+ python = ["3.9", "3.10", "3.11", "3.12"]
79
69
 
80
70
  [tool.hatch.envs.types]
81
71
  dependencies = [
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2024-present Wytamma Wirth <wytamma.wirth@me.com>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.1.4"
4
+ __version__ = "0.2.1"
@@ -6,8 +6,6 @@ from pathlib import Path
6
6
  from typing import Optional
7
7
  import os
8
8
 
9
-
10
- from snakemake import SNAKEFILE_CHOICES
11
9
  from art import text2art
12
10
 
13
11
  from snk_cli.dynamic_typer import DynamicTyper
@@ -224,6 +222,17 @@ class CLI(DynamicTyper):
224
222
  Examples:
225
223
  >>> CLI._find_snakefile()
226
224
  """
225
+ SNAKEFILE_CHOICES = list(
226
+ map(
227
+ Path,
228
+ (
229
+ "Snakefile",
230
+ "snakefile",
231
+ "workflow/Snakefile",
232
+ "workflow/snakefile",
233
+ ),
234
+ )
235
+ )
227
236
  for path in SNAKEFILE_CHOICES:
228
237
  if (self.workflow.path / path).exists():
229
238
  return self.workflow.path / path
@@ -244,6 +253,9 @@ class CLI(DynamicTyper):
244
253
  info_dict = {}
245
254
  info_dict["name"] = self.workflow.path.name
246
255
  info_dict["version"] = self.version
256
+ info_dict["snakefile"] = str(self.snakefile)
257
+ info_dict["conda_prefix_dir"] = str(self.conda_prefix_dir)
258
+ info_dict["singularity_prefix_dir"] = str(self.singularity_prefix_dir)
247
259
  info_dict["workflow_dir_path"] = str(self.workflow.path)
248
260
  typer.echo(json.dumps(info_dict, indent=2))
249
261
 
@@ -0,0 +1,106 @@
1
+ # This file contains functions to create and manage conda environments for snakemake workflows
2
+ # it needs to work with v 7 and 8 of snakemake
3
+ #
4
+ from pathlib import Path
5
+ from packaging import version
6
+ from dataclasses import dataclass
7
+ import os
8
+
9
+ from snk_cli.utils import check_command_available
10
+ from snakemake.deployment.conda import Env
11
+ from snakemake.persistence import Persistence
12
+ import snakemake
13
+
14
+ snakemake_version = version.parse(snakemake.__version__)
15
+ is_snakemake_version_8_or_above = snakemake_version >= version.parse('8')
16
+
17
+ @dataclass
18
+ class PersistenceMock(Persistence):
19
+ """
20
+ Mock for workflow.persistence
21
+ """
22
+ conda_env_path: Path = None
23
+ _metadata_path: Path = None
24
+ _incomplete_path: Path = None
25
+ shadow_path: Path = None
26
+ conda_env_archive_path: Path = None
27
+ container_img_path: Path = None
28
+ aux_path: Path = None
29
+
30
+
31
+ def get_frontend():
32
+ if check_command_available("mamba"):
33
+ conda_frontend = "mamba"
34
+ else:
35
+ conda_frontend = "conda"
36
+ return conda_frontend
37
+
38
+ def create_workflow_v7(conda_prefix):
39
+ from snakemake.workflow import Workflow
40
+
41
+ conda_frontend = get_frontend()
42
+ workflow = Workflow(
43
+ snakefile=Path(),
44
+ overwrite_config=dict(),
45
+ overwrite_workdir=None,
46
+ overwrite_configfiles=[],
47
+ overwrite_clusterconfig=dict(),
48
+ conda_frontend=conda_frontend,
49
+ use_conda=True,
50
+ )
51
+
52
+ persistence = PersistenceMock(
53
+ conda_env_path=Path(conda_prefix).resolve() if conda_prefix else None,
54
+ conda_env_archive_path=os.path.join(Path(".snakemake"), "conda-archive"),
55
+ )
56
+ if hasattr(workflow, "_persistence"):
57
+ workflow._persistence = persistence
58
+ else:
59
+ workflow.persistence = persistence
60
+ return workflow
61
+
62
+ def create_workflow_v8(
63
+ conda_prefix
64
+ ):
65
+ from snakemake.api import Workflow
66
+ from snakemake.settings import (
67
+ ConfigSettings,
68
+ DeploymentSettings,
69
+ ResourceSettings,
70
+ WorkflowSettings,
71
+ StorageSettings,
72
+ )
73
+ conda_frontend = get_frontend()
74
+ workflow = Workflow(
75
+ config_settings=ConfigSettings(),
76
+ resource_settings=ResourceSettings(),
77
+ workflow_settings=WorkflowSettings(),
78
+ storage_settings=StorageSettings(),
79
+ deployment_settings=DeploymentSettings(
80
+ conda_frontend=conda_frontend,
81
+ conda_prefix=conda_prefix
82
+ ),
83
+ )
84
+ persistence = PersistenceMock(
85
+ conda_env_path=Path(conda_prefix).resolve() if conda_prefix else None,
86
+ conda_env_archive_path=os.path.join(Path(".snakemake"), "conda-archive"),
87
+ )
88
+ if hasattr(workflow, "_persistence"):
89
+ workflow._persistence = persistence
90
+ else:
91
+ workflow.persistence = persistence
92
+ return workflow
93
+
94
+ def conda_environment_factory(env_file_path: Path, conda_prefix_dir_path: Path) -> Env:
95
+ """
96
+ Create a snakemake environment object from a given environment file and conda prefix directory
97
+ """
98
+ if is_snakemake_version_8_or_above:
99
+ snakemake_workflow = create_workflow_v8(
100
+ conda_prefix_dir_path
101
+ )
102
+ else:
103
+ snakemake_workflow = create_workflow_v7(conda_prefix_dir_path)
104
+ env_file_path = Path(env_file_path).resolve()
105
+ env = Env(snakemake_workflow, env_file=env_file_path)
106
+ return env
@@ -2,7 +2,7 @@ from pathlib import Path
2
2
  from typing import List, Optional
3
3
  import snakemake
4
4
  from dataclasses import dataclass, field
5
- from .utils import get_version_from_config
5
+ from .utils import get_version_from_config, load_configfile
6
6
  import yaml
7
7
 
8
8
 
@@ -97,7 +97,7 @@ class SnkConfig:
97
97
  if snk_config_path.stat().st_size == 0:
98
98
  raise InvalidSnkConfigError(f"SNK config file is empty: {snk_config_path}") from ValueError
99
99
 
100
- snk_config_dict = snakemake.load_configfile(snk_config_path)
100
+ snk_config_dict = load_configfile(snk_config_path)
101
101
  snk_config_dict["version"] = get_version_from_config(snk_config_path, snk_config_dict)
102
102
  if "annotations" in snk_config_dict:
103
103
  # TODO: remove annotations in the future
@@ -257,4 +257,4 @@ def load_workflow_snakemake_config(workflow_dir_path: Path):
257
257
  workflow_config_path = get_config_from_workflow_dir(workflow_dir_path)
258
258
  if not workflow_config_path or not workflow_config_path.exists():
259
259
  return {}
260
- return snakemake.load_configfile(workflow_config_path)
260
+ return load_configfile(workflow_config_path)
@@ -1,5 +1,9 @@
1
1
  from pathlib import Path
2
- from snakemake import load_configfile
2
+ try:
3
+ # snakemake < 8.0.0
4
+ from snakemake import load_configfile
5
+ except ImportError:
6
+ from snakemake.common.configfile import _load_configfile as load_configfile # noqa: F401
3
7
 
4
8
  def get_version_from_config(config_path: Path, config_dict: dict = None) -> str:
5
9
  """
@@ -217,8 +217,8 @@ class DynamicTyper:
217
217
  flat_config = None
218
218
 
219
219
  if kwargs.get("configfile"):
220
- from snakemake import load_configfile
221
220
  from .utils import flatten
221
+ from snk_cli.config.utils import load_configfile
222
222
 
223
223
  snakemake_config = load_configfile(kwargs["configfile"])
224
224
  flat_config = flatten(snakemake_config)
@@ -7,7 +7,7 @@ from typing import List, Optional
7
7
  import typer
8
8
 
9
9
  from snk_cli.dynamic_typer import DynamicTyper
10
- from .utils import create_snakemake_workflow
10
+ from snk_cli.conda import conda_environment_factory
11
11
  from ..workflow import Workflow
12
12
  from rich.console import Console
13
13
  from rich.syntax import Syntax
@@ -22,22 +22,15 @@ def get_num_cores(default=4):
22
22
  except:
23
23
  return default
24
24
 
25
- def create_conda_environment(args):
26
- # unpack args
27
- env_path_str, snakefile, snakemake_config, configfiles, conda_prefix_dir_str = args
28
- # Reconstruct the snakemake_workflow configuration
25
+ def create_conda_environment_wrapper(args):
26
+ """
27
+ This wrapper is designed to be submitted to a ProcessPoolExecutor
28
+ """
29
+ env_path_str, conda_prefix_dir_str = args
29
30
  conda_prefix_dir = Path(conda_prefix_dir_str).resolve()
30
- snakemake_workflow = create_snakemake_workflow(
31
- snakefile,
32
- config=snakemake_config,
33
- configfiles=configfiles,
34
- use_conda=True,
35
- conda_prefix=conda_prefix_dir,
36
- )
37
31
  env_path = Path(env_path_str).resolve()
38
- env = Env(snakemake_workflow, env_file=env_path)
39
32
  try:
40
- env.create()
33
+ conda_environment_factory(env_path, conda_prefix_dir).create()
41
34
  except CreateCondaEnvironmentException as e:
42
35
  typer.secho(str(e), fg="red", err=True)
43
36
  return 1
@@ -57,13 +50,6 @@ class EnvApp(DynamicTyper):
57
50
  self.snakemake_config = snakemake_config
58
51
  self.snakefile = snakefile
59
52
  self.configfile = get_config_from_workflow_dir(self.workflow.path)
60
- self.snakemake_workflow = create_snakemake_workflow(
61
- self.snakefile,
62
- config=self.snakemake_config,
63
- configfiles=[self.configfile] if self.configfile else None,
64
- use_conda=True,
65
- conda_prefix=self.conda_prefix_dir.resolve(),
66
- )
67
53
  self.register_command(self.list, help="List the environments in the workflow.")
68
54
  self.register_command(self.show, help="Show the contents of an environment.")
69
55
  self.register_command(
@@ -86,7 +72,7 @@ class EnvApp(DynamicTyper):
86
72
 
87
73
  table = Table("Name", "CMD", "Env", show_header=True, show_lines=True)
88
74
  for env in self.workflow.environments:
89
- conda = Env(self.snakemake_workflow, env_file=env.resolve())
75
+ conda = conda_environment_factory(env, self.conda_prefix_dir)
90
76
  # address relative to cwd
91
77
  address = Path(conda.address)
92
78
  if address.exists():
@@ -137,7 +123,7 @@ class EnvApp(DynamicTyper):
137
123
  cmd: List[str] = typer.Argument(..., help="The command to run in environment."),
138
124
  ):
139
125
  env_path = self._get_conda_env_path(name)
140
- env = Env(self.snakemake_workflow, env_file=env_path.resolve())
126
+ env = conda_environment_factory(env_path, self.conda_prefix_dir)
141
127
  env.create()
142
128
  cmd = self._shellcmd(env.address, " ".join(cmd))
143
129
  if verbose:
@@ -154,7 +140,7 @@ class EnvApp(DynamicTyper):
154
140
  ):
155
141
  if name:
156
142
  env_path = self._get_conda_env_path(name)
157
- env = Env(self.snakemake_workflow, env_file=env_path.resolve())
143
+ env = conda_environment_factory(env_path, self.conda_prefix_dir)
158
144
  path = Path(env.address)
159
145
  if not path.exists():
160
146
  self.error(f"Environment {name} not created!")
@@ -176,15 +162,12 @@ class EnvApp(DynamicTyper):
176
162
  env_args = [
177
163
  (
178
164
  path,
179
- self.snakefile,
180
- self.snakemake_config,
181
- [self.configfile] if self.configfile else None,
182
165
  self.conda_prefix_dir.resolve(),
183
166
  )
184
167
  for path in env_paths
185
168
  ]
186
169
  with ProcessPoolExecutor(max_workers=max_workers) as executor:
187
- status_codes = executor.map(create_conda_environment, env_args)
170
+ status_codes = executor.map(create_conda_environment_wrapper, env_args)
188
171
  if any(status_codes):
189
172
  self.error("Failed to create all conda environments!")
190
173
  if names:
@@ -201,7 +184,7 @@ class EnvApp(DynamicTyper):
201
184
  ):
202
185
  env_path = self._get_conda_env_path(name)
203
186
  self.log(f"Activating {name} environment... (type 'exit' to deactivate)")
204
- env = Env(self.snakemake_workflow, env_file=env_path.resolve())
187
+ env = conda_environment_factory(env_path, self.conda_prefix_dir)
205
188
  env.create()
206
189
  user_shell = os.environ.get("SHELL", "/bin/bash")
207
190
  activate_cmd = self._shellcmd(env.address, user_shell)
@@ -5,6 +5,7 @@ from contextlib import contextmanager
5
5
 
6
6
  from snk_cli.dynamic_typer import DynamicTyper
7
7
  from snk_cli.options.option import Option
8
+ from snk_cli.conda import is_snakemake_version_8_or_above
8
9
  from ..workflow import Workflow
9
10
  from snk_cli.utils import (
10
11
  parse_config_args,
@@ -64,9 +65,7 @@ class RunApp(DynamicTyper):
64
65
  >>> RunApp._print_snakemake_help(True)
65
66
  """
66
67
  if value:
67
- import snakemake
68
-
69
- snakemake.main("-h")
68
+ execute_snakemake("-h")
70
69
 
71
70
  def run(
72
71
  self,
@@ -167,7 +166,6 @@ class RunApp(DynamicTyper):
167
166
  Examples:
168
167
  >>> RunApp.run(target='my_target', configfile=Path('/path/to/config.yaml'), resource=[Path('/path/to/resource')], verbose=True)
169
168
  """
170
- import snakemake
171
169
  import shutil
172
170
  import sys
173
171
 
@@ -274,8 +272,7 @@ class RunApp(DynamicTyper):
274
272
  if dag:
275
273
  return self._save_dag(snakemake_args=args, filename=dag)
276
274
  try:
277
- snakemake.parse_config = parse_config_monkeypatch
278
- snakemake.main(args)
275
+ execute_snakemake(args)
279
276
  except SystemExit as e:
280
277
  status = int(str(e))
281
278
  if status:
@@ -286,7 +283,6 @@ class RunApp(DynamicTyper):
286
283
 
287
284
  def _save_dag(self, snakemake_args: List[str], filename: Path):
288
285
  from contextlib import redirect_stdout
289
- import snakemake
290
286
  import subprocess
291
287
  import io
292
288
 
@@ -300,8 +296,7 @@ class RunApp(DynamicTyper):
300
296
  with redirect_stdout(snakemake_output):
301
297
  # Capture the output of snakemake.main(args) using a try-except block
302
298
  try:
303
- snakemake.parse_config = parse_config_monkeypatch
304
- snakemake.main(snakemake_args)
299
+ execute_snakemake(snakemake_args)
305
300
  except SystemExit: # Catch SystemExit exception to prevent termination
306
301
  pass
307
302
  try:
@@ -415,6 +410,24 @@ class RunApp(DynamicTyper):
415
410
  )
416
411
  remove_resource(copied_resource)
417
412
 
413
+ def execute_snakemake(args):
414
+ """
415
+ Execute snakemake with the given arguments.
416
+
417
+ Args:
418
+ args: The arguments to pass to snakemake.
419
+
420
+ Side Effects:
421
+ Executes snakemake with the given arguments.
422
+ """
423
+ import snakemake
424
+ if is_snakemake_version_8_or_above:
425
+ from snakemake import cli
426
+ cli.parse_config = parse_config_monkeypatch
427
+ cli.main(args)
428
+ else:
429
+ snakemake.parse_config = parse_config_monkeypatch
430
+ snakemake.main(args)
418
431
 
419
432
  def parse_config_monkeypatch(args):
420
433
  """
@@ -427,8 +440,13 @@ def parse_config_monkeypatch(args):
427
440
  dict: The parsed config.
428
441
  """
429
442
  import yaml
430
- import snakemake
431
443
  import re
444
+ if is_snakemake_version_8_or_above:
445
+ from snakemake.cli import parse_key_value_arg, update_config, _bool_parser
446
+ entries = args
447
+ else:
448
+ from snakemake import parse_key_value_arg, update_config, _bool_parser
449
+ entries = args.config
432
450
 
433
451
  class NoDatesSafeLoader(yaml.SafeLoader):
434
452
  @classmethod
@@ -465,12 +483,12 @@ def parse_config_monkeypatch(args):
465
483
  s = s.replace(": None", ": null")
466
484
  return yaml.load(s, Loader=NoDatesSafeLoader)
467
485
 
468
- parsers = [int, float, snakemake._bool_parser, _yaml_safe_load, str]
486
+ parsers = [int, float, _bool_parser, _yaml_safe_load, str]
469
487
  config = dict()
470
- if args.config is not None:
488
+ if entries is not None:
471
489
  valid = re.compile(r"[a-zA-Z_]\w*$")
472
- for entry in args.config:
473
- key, val = snakemake.parse_key_value_arg(
490
+ for entry in entries:
491
+ key, val = parse_key_value_arg(
474
492
  entry,
475
493
  errmsg="Invalid config definition: Config entries have to be defined as name=value pairs.",
476
494
  )
@@ -480,7 +498,7 @@ def parse_config_monkeypatch(args):
480
498
  )
481
499
  v = None
482
500
  if val == "" or val == "None":
483
- snakemake.update_config(config, {key: v})
501
+ update_config(config, {key: v})
484
502
  continue
485
503
  for parser in parsers:
486
504
  try:
@@ -491,5 +509,5 @@ def parse_config_monkeypatch(args):
491
509
  except:
492
510
  pass
493
511
  assert v is not None
494
- snakemake.update_config(config, {key: v})
512
+ update_config(config, {key: v})
495
513
  return config
@@ -6,11 +6,11 @@ from typing import List
6
6
  import typer
7
7
 
8
8
  from ..dynamic_typer import DynamicTyper
9
- from .utils import create_snakemake_workflow
9
+ from snk_cli.conda import conda_environment_factory
10
10
  from ..workflow import Workflow
11
11
  from rich.console import Console
12
12
  from rich.syntax import Syntax
13
- from snakemake.deployment.conda import Conda, Env
13
+ from snakemake.deployment.conda import Conda
14
14
  from snk_cli.config.config import get_config_from_workflow_dir
15
15
 
16
16
 
@@ -116,14 +116,7 @@ class ScriptApp(DynamicTyper):
116
116
  cmd = [executor, f'"{script_path}"'] + args
117
117
  if env:
118
118
  env_path = self._get_conda_env_path(env)
119
- workflow = create_snakemake_workflow(
120
- self.snakefile,
121
- config=self.snakemake_config,
122
- configfiles=[self.configfile] if self.configfile else None,
123
- use_conda=True,
124
- conda_prefix=self.conda_prefix_dir.resolve(),
125
- )
126
- env = Env(workflow, env_file=env_path.resolve())
119
+ env = conda_environment_factory(env_path, self.conda_prefix_dir)
127
120
  env.create()
128
121
  cmd = self._shellcmd(env.address, " ".join(cmd))
129
122
  else:
@@ -39,3 +39,12 @@ def test_run_with_config(tmp_path):
39
39
  assert res.exit_code == 0, res.stderr
40
40
  assert "cli" in res.stdout, res.stderr
41
41
 
42
+ def test_snakemake_help(local_runner: SnkCliRunner):
43
+ res = local_runner(["run", "-hs"])
44
+ assert res.exit_code == 0, res.stderr
45
+ assert "snakemake" in res.stdout
46
+
47
+ def test_snakemake_version(local_runner: SnkCliRunner):
48
+ res = local_runner(["run", "--snake-v"])
49
+ assert res.exit_code == 0, res.stderr
50
+ assert res.stdout in ["7.32.4\n", "8.10.8\n"]
@@ -13,6 +13,7 @@ import pytest
13
13
  (["env", "run", "python", "which python"], ["bin/python"], []),
14
14
  (["env", "activate", "python"], [], ["Activating python environment...", "Exiting python environment..."]),
15
15
  (["env", "remove", "-f"], ["Deleted"], []),
16
+ (["info"], ["name", "version", "snakefile", "conda_prefix_dir", "singularity_prefix_dir", "workflow_dir_path"], []),
16
17
  ])
17
18
  def test_snk_cli_command(capfd, local_runner, cmd, expected_in_stdout, expected_in_stderr):
18
19
  res = local_runner(cmd)
@@ -1,12 +1,11 @@
1
1
  from pathlib import Path
2
2
  from snk_cli.utils import flatten, convert_key_to_snakemake_format
3
- import snakemake
4
3
  import pytest
5
4
  from ..utils import SnkCliRunner
6
-
5
+ from snk_cli.config.utils import load_configfile
7
6
 
8
7
  def test_flatten(example_config: Path):
9
- config = snakemake.load_configfile(example_config)
8
+ config = load_configfile(example_config)
10
9
  flat_config = flatten(config)
11
10
  assert flat_config["diffexp:contrasts:A-vs-B"] == ["A", "B"]
12
11
 
@@ -0,0 +1,11 @@
1
+ from snk_cli.conda import conda_environment_factory
2
+ from snakemake.deployment.conda import Env
3
+ from pathlib import Path
4
+
5
+
6
+ def test_conda_env(tmp_path):
7
+ env = conda_environment_factory("tests/data/workflow/workflow/envs/python.yml", tmp_path)
8
+ assert isinstance(env, Env)
9
+ assert not Path(env.address).exists()
10
+ env.create()
11
+ assert Path(env.address).exists()
@@ -1,23 +0,0 @@
1
-
2
- name: mkdocs
3
- on:
4
- push:
5
- branches:
6
- - master
7
- - main
8
- permissions:
9
- contents: write
10
- jobs:
11
- deploy:
12
- runs-on: ubuntu-latest
13
- steps:
14
- - uses: actions/checkout@v3
15
- - uses: actions/setup-python@v4
16
- with:
17
- python-version: 3.x
18
- - uses: actions/cache@v2
19
- with:
20
- key: ${{ github.ref }}
21
- path: .cache
22
- - run: pip install "mkdocstrings==0.22.0" "mkdocstrings-python==1.3.*" "mkdocs-material"
23
- - run: mkdocs gh-deploy --force
@@ -1,175 +0,0 @@
1
- from dataclasses import dataclass
2
- from pathlib import Path
3
- from snakemake import (
4
- Workflow,
5
- update_config,
6
- load_configfile,
7
- dict_to_key_value_args,
8
- common,
9
- )
10
- from snakemake.persistence import Persistence
11
- from ..utils import check_command_available
12
- import os
13
-
14
- @dataclass
15
- class PersistenceMock(Persistence):
16
- """
17
- Mock for workflow.persistence
18
- """
19
-
20
- conda_env_path: Path = None
21
- _metadata_path: Path = None
22
- _incomplete_path: Path = None
23
- shadow_path: Path = None
24
- conda_env_archive_path: Path = None
25
- container_img_path: Path = None
26
- aux_path: Path = None
27
-
28
- def create_snakemake_workflow(
29
- snakefile,
30
- cache=None,
31
- lint=None,
32
- cores=1,
33
- nodes=None,
34
- max_threads=None,
35
- resources=dict(),
36
- overwrite_threads=None,
37
- overwrite_scatter=None,
38
- overwrite_resource_scopes=None,
39
- default_resources=None,
40
- overwrite_resources=None,
41
- config=dict(),
42
- configfiles=None,
43
- config_args=None,
44
- workdir=None,
45
- printshellcmds=False,
46
- rerun_triggers=["mtime", "params", "input", "software-env", "code"],
47
- conda_cleanup_envs=False,
48
- latency_wait=3,
49
- print_compilation=False,
50
- debug=False,
51
- all_temp=False,
52
- jobscript=None,
53
- overwrite_shellcmd=None,
54
- restart_times=0,
55
- attempt=1,
56
- verbose=False,
57
- use_conda=False,
58
- use_singularity=False,
59
- use_env_modules=False,
60
- singularity_args="",
61
- conda_frontend="conda",
62
- conda_prefix=None,
63
- conda_cleanup_pkgs=None,
64
- list_conda_envs=False,
65
- singularity_prefix=None,
66
- shadow_prefix=None,
67
- scheduler="ilp",
68
- scheduler_ilp_solver=None,
69
- mode=common.Mode.default,
70
- wrapper_prefix=None,
71
- default_remote_prefix="",
72
- assume_shared_fs=True,
73
- keep_metadata=True,
74
- edit_notebook=None,
75
- envvars=None,
76
- overwrite_groups=None,
77
- group_components=None,
78
- max_inventory_wait_time=20,
79
- execute_subworkflows=True,
80
- conda_not_block_search_path_envvars=False,
81
- scheduler_solver_path=None,
82
- conda_base_path=None,
83
- local_groupid="local",
84
- ):
85
- overwrite_config = dict()
86
- if configfiles is None:
87
- configfiles = []
88
- for f in configfiles:
89
- # get values to override. Later configfiles override earlier ones.
90
- update_config(overwrite_config, load_configfile(f))
91
- # convert provided paths to absolute paths
92
- configfiles = list(map(os.path.abspath, configfiles))
93
-
94
- # directly specified elements override any configfiles
95
- if config:
96
- update_config(overwrite_config, config)
97
- if config_args is None:
98
- config_args = dict_to_key_value_args(config)
99
-
100
- if workdir:
101
- if not os.path.exists(workdir):
102
- os.makedirs(workdir)
103
- workdir = os.path.abspath(workdir)
104
- os.chdir(workdir)
105
-
106
- if check_command_available("mamba"):
107
- conda_frontend = "mamba"
108
-
109
- workflow = Workflow(
110
- snakefile=snakefile,
111
- rerun_triggers=rerun_triggers,
112
- jobscript=jobscript,
113
- overwrite_shellcmd=overwrite_shellcmd,
114
- overwrite_config=overwrite_config,
115
- overwrite_workdir=workdir,
116
- overwrite_configfiles=configfiles,
117
- overwrite_clusterconfig=dict(),
118
- overwrite_threads=overwrite_threads,
119
- max_threads=max_threads,
120
- overwrite_scatter=overwrite_scatter,
121
- overwrite_groups=overwrite_groups,
122
- overwrite_resources=overwrite_resources,
123
- overwrite_resource_scopes=overwrite_resource_scopes,
124
- group_components=group_components,
125
- config_args=config_args,
126
- debug=debug,
127
- verbose=verbose,
128
- use_conda=use_conda or list_conda_envs or conda_cleanup_envs,
129
- use_singularity=use_singularity,
130
- use_env_modules=use_env_modules,
131
- conda_frontend=conda_frontend,
132
- conda_prefix=conda_prefix,
133
- conda_cleanup_pkgs=conda_cleanup_pkgs,
134
- singularity_prefix=singularity_prefix,
135
- shadow_prefix=shadow_prefix,
136
- singularity_args=singularity_args,
137
- scheduler_type=scheduler,
138
- scheduler_ilp_solver=scheduler_ilp_solver,
139
- mode=mode,
140
- wrapper_prefix=wrapper_prefix,
141
- printshellcmds=printshellcmds,
142
- restart_times=restart_times,
143
- attempt=attempt,
144
- default_remote_provider=None,
145
- default_remote_prefix=default_remote_prefix,
146
- run_local=True,
147
- assume_shared_fs=assume_shared_fs,
148
- default_resources=default_resources,
149
- cache=cache,
150
- cores=cores,
151
- nodes=nodes,
152
- resources=resources,
153
- edit_notebook=edit_notebook,
154
- envvars=envvars,
155
- max_inventory_wait_time=max_inventory_wait_time,
156
- conda_not_block_search_path_envvars=conda_not_block_search_path_envvars,
157
- execute_subworkflows=execute_subworkflows,
158
- scheduler_solver_path=scheduler_solver_path,
159
- conda_base_path=conda_base_path,
160
- check_envvars=not lint, # for linting, we do not need to check whether requested envvars exist
161
- all_temp=all_temp,
162
- local_groupid=local_groupid,
163
- keep_metadata=keep_metadata,
164
- latency_wait=latency_wait,
165
- )
166
-
167
- persistence = PersistenceMock(
168
- conda_env_path=Path(conda_prefix).resolve() if conda_prefix else None,
169
- conda_env_archive_path=os.path.join(Path(".snakemake"), "conda-archive"),
170
- )
171
- if hasattr(workflow, "_persistence"):
172
- workflow._persistence = persistence
173
- else:
174
- workflow.persistence = persistence
175
- return workflow
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes