snakemake-interface-software-deployment-plugins 0.2.3__tar.gz → 0.4.0__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.
- {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.4.0}/PKG-INFO +1 -1
- {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.4.0}/pyproject.toml +2 -1
- {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.4.0}/snakemake_interface_software_deployment_plugins/__init__.py +95 -27
- {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.4.0}/snakemake_interface_software_deployment_plugins/registry/__init__.py +18 -1
- {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.4.0}/snakemake_interface_software_deployment_plugins/registry/plugin.py +2 -0
- snakemake_interface_software_deployment_plugins-0.4.0/snakemake_interface_software_deployment_plugins/settings.py +46 -0
- {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.4.0}/snakemake_interface_software_deployment_plugins/tests.py +25 -2
- snakemake_interface_software_deployment_plugins-0.2.3/snakemake_interface_software_deployment_plugins/settings.py +0 -17
- {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.4.0}/LICENSE +0 -0
- {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.4.0}/README.md +0 -0
- {snakemake_interface_software_deployment_plugins-0.2.3 → snakemake_interface_software_deployment_plugins-0.4.0}/snakemake_interface_software_deployment_plugins/_common.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: snakemake-interface-software-deployment-plugins
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: This package provides a stable interface for interactions between Snakemake and its software deployment plugins.
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Johannes Köster
|
|
@@ -5,7 +5,7 @@ license = "MIT"
|
|
|
5
5
|
name = "snakemake-interface-software-deployment-plugins"
|
|
6
6
|
packages = [{include = "snakemake_interface_software_deployment_plugins"}]
|
|
7
7
|
readme = "README.md"
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.4.0"
|
|
9
9
|
|
|
10
10
|
[tool.poetry.dependencies]
|
|
11
11
|
argparse-dataclass = "^2.0.0"
|
|
@@ -17,6 +17,7 @@ coverage = {extras = ["toml"], version = "^6.3.1"}
|
|
|
17
17
|
flake8-bugbear = "^22.1.11"
|
|
18
18
|
pytest = "^7.0"
|
|
19
19
|
ruff = "^0.9.9"
|
|
20
|
+
snakemake-software-deployment-plugin-envmodules = "^0.1.2"
|
|
20
21
|
|
|
21
22
|
[tool.coverage.run]
|
|
22
23
|
omit = [".*", "*/site-packages/*"]
|
|
@@ -5,11 +5,10 @@ __license__ = "MIT"
|
|
|
5
5
|
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
7
|
from copy import copy
|
|
8
|
-
from dataclasses import dataclass, field
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
9
|
import hashlib
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
import
|
|
12
|
-
from typing import Any, ClassVar, Dict, Optional, Tuple, Type
|
|
11
|
+
from typing import Any, ClassVar, Dict, Iterable, Optional, Self, Tuple, Type
|
|
13
12
|
import subprocess as sp
|
|
14
13
|
|
|
15
14
|
from snakemake_interface_software_deployment_plugins.settings import (
|
|
@@ -17,28 +16,91 @@ from snakemake_interface_software_deployment_plugins.settings import (
|
|
|
17
16
|
)
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
19
|
+
@dataclass
|
|
20
|
+
class SoftwareReport:
|
|
21
|
+
name: str
|
|
22
|
+
version: Optional[str] = None
|
|
23
|
+
is_secondary: bool = False
|
|
26
24
|
|
|
27
25
|
|
|
28
|
-
@dataclass
|
|
29
26
|
class EnvSpecBase(ABC):
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.within: Optional["EnvSpecBase"] = None
|
|
29
|
+
self.fallback: Optional["EnvSpecBase"] = None
|
|
30
|
+
self.kind: str = self.__class__.__module__.common_settings.provides
|
|
31
|
+
self._obj_hash: Optional[int] = None
|
|
32
32
|
|
|
33
33
|
@classmethod
|
|
34
34
|
def env_cls(cls):
|
|
35
|
-
return
|
|
35
|
+
return cls.__module__.EnvBase
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def identity_attributes(self) -> Iterable[str]:
|
|
39
|
+
"""Yield the attributes of the subclass that uniquely identify the
|
|
40
|
+
environment spec. These are used for hashing and equality comparison.
|
|
41
|
+
"""
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def source_path_attributes(self) -> Iterable[str]:
|
|
46
|
+
"""Return iterable of attributes of the subclass that represent paths that are
|
|
47
|
+
supposed to be interpreted as being relative to the defining rule.
|
|
48
|
+
|
|
49
|
+
For example, this would be attributes pointing to conda environment files.
|
|
50
|
+
"""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
def has_source_paths(self) -> bool:
|
|
54
|
+
if any(self.source_path_attributes()):
|
|
55
|
+
return True
|
|
56
|
+
if self.within is not None and self.within.has_source_paths():
|
|
57
|
+
return True
|
|
58
|
+
if self.fallback is not None and self.fallback.has_source_paths():
|
|
59
|
+
return True
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
def modify_source_paths(self, modify_func) -> Self:
|
|
63
|
+
if self.has_source_paths():
|
|
64
|
+
self_or_copied = copy(self)
|
|
65
|
+
else:
|
|
66
|
+
return self
|
|
67
|
+
for attr in self_or_copied.source_path_attributes():
|
|
68
|
+
setattr(self_or_copied, attr, modify_func(getattr(self_or_copied, attr)))
|
|
69
|
+
|
|
70
|
+
if self_or_copied.within is not None:
|
|
71
|
+
self_or_copied.within = self_or_copied.within.modify_source_paths(
|
|
72
|
+
modify_func
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if self_or_copied.fallback is not None:
|
|
76
|
+
self_or_copied.fallback = self_or_copied.fallback.modify_source_paths(
|
|
77
|
+
modify_func
|
|
78
|
+
)
|
|
79
|
+
return self_or_copied
|
|
36
80
|
|
|
37
81
|
def __or__(self, other: "EnvSpecBase") -> "EnvSpecBase":
|
|
38
82
|
copied = copy(self)
|
|
39
83
|
copied.fallback = other
|
|
84
|
+
copied._obj_hash = None
|
|
40
85
|
return copied
|
|
41
86
|
|
|
87
|
+
def managed_identity_attributes(self) -> Iterable[str]:
|
|
88
|
+
yield from self.identity_attributes()
|
|
89
|
+
yield "kind"
|
|
90
|
+
yield "within"
|
|
91
|
+
yield "fallback"
|
|
92
|
+
|
|
93
|
+
def __hash__(self) -> int:
|
|
94
|
+
return hash(
|
|
95
|
+
tuple(getattr(self, attr) for attr in self.managed_identity_attributes())
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def __eq__(self, other) -> bool:
|
|
99
|
+
return self.__class__ == other.__class__ and all(
|
|
100
|
+
getattr(self, attr) == getattr(other, attr)
|
|
101
|
+
for attr in self.managed_identity_attributes()
|
|
102
|
+
)
|
|
103
|
+
|
|
42
104
|
|
|
43
105
|
@dataclass
|
|
44
106
|
class EnvBase:
|
|
@@ -80,9 +142,21 @@ class EnvBase:
|
|
|
80
142
|
|
|
81
143
|
@abstractmethod
|
|
82
144
|
def record_hash(self, hash_object) -> None:
|
|
83
|
-
"""Update given hash object such that it changes
|
|
84
|
-
specified via self.spec could potentially contain a
|
|
85
|
-
software (in terms of versions or packages).
|
|
145
|
+
"""Update given hash object (using hash_object.update()) such that it changes
|
|
146
|
+
whenever the environment specified via self.spec could potentially contain a
|
|
147
|
+
different set of software (in terms of versions or packages).
|
|
148
|
+
"""
|
|
149
|
+
...
|
|
150
|
+
|
|
151
|
+
@abstractmethod
|
|
152
|
+
def report_software(self) -> Iterable[SoftwareReport]:
|
|
153
|
+
"""Report the software contained in the environment. This should be a list of
|
|
154
|
+
snakemake_interface_software_deployment_plugins.SoftwareReport data class.
|
|
155
|
+
Use SoftwareReport.is_secondary = True if the software is just some
|
|
156
|
+
less important technical dependency. This allows Snakemake's report to
|
|
157
|
+
hide those for clarity. In case of containers, it is also valid to
|
|
158
|
+
return the container URI as a "software".
|
|
159
|
+
Return an empty tuple () if no software can be reported.
|
|
86
160
|
"""
|
|
87
161
|
...
|
|
88
162
|
|
|
@@ -119,20 +193,14 @@ class EnvBase:
|
|
|
119
193
|
def __hash__(self) -> int:
|
|
120
194
|
# take the hash of all fields by settings, _managed_hash_store and _managed_deployment_hash_store
|
|
121
195
|
if self._obj_hash is None:
|
|
122
|
-
self._obj_hash = hash(
|
|
123
|
-
tuple(
|
|
124
|
-
getattr(self, field.name)
|
|
125
|
-
for field in fields(self)
|
|
126
|
-
if field.name not in _MANAGED_FIELDS
|
|
127
|
-
)
|
|
128
|
-
)
|
|
196
|
+
self._obj_hash = hash(self.hash())
|
|
129
197
|
return self._obj_hash
|
|
130
198
|
|
|
131
199
|
def __eq__(self, other) -> bool:
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
200
|
+
return (
|
|
201
|
+
self.__class__ == other.__class__
|
|
202
|
+
and self.spec == other.spec
|
|
203
|
+
and self.hash() == other.hash()
|
|
136
204
|
)
|
|
137
205
|
|
|
138
206
|
|
|
@@ -6,6 +6,7 @@ __license__ = "MIT"
|
|
|
6
6
|
import types
|
|
7
7
|
from typing import Mapping
|
|
8
8
|
from snakemake_interface_software_deployment_plugins.settings import (
|
|
9
|
+
CommonSettings,
|
|
9
10
|
SoftwareDeploymentSettingsBase,
|
|
10
11
|
)
|
|
11
12
|
|
|
@@ -16,7 +17,11 @@ from snakemake_interface_common.plugin_registry.attribute_types import (
|
|
|
16
17
|
)
|
|
17
18
|
from snakemake_interface_software_deployment_plugins.registry.plugin import Plugin
|
|
18
19
|
from snakemake_interface_common.plugin_registry import PluginRegistryBase
|
|
19
|
-
from snakemake_interface_software_deployment_plugins import
|
|
20
|
+
from snakemake_interface_software_deployment_plugins import (
|
|
21
|
+
EnvBase,
|
|
22
|
+
EnvSpecBase,
|
|
23
|
+
_common as common,
|
|
24
|
+
)
|
|
20
25
|
|
|
21
26
|
|
|
22
27
|
class SoftwareDeploymentPluginRegistry(PluginRegistryBase):
|
|
@@ -30,14 +35,21 @@ class SoftwareDeploymentPluginRegistry(PluginRegistryBase):
|
|
|
30
35
|
"""Load a plugin by name."""
|
|
31
36
|
return Plugin(
|
|
32
37
|
_name=name,
|
|
38
|
+
common_settings=module.common_settings,
|
|
33
39
|
_software_deployment_settings_cls=getattr(
|
|
34
40
|
module, "SoftwareDeploymentSettings", None
|
|
35
41
|
),
|
|
36
42
|
_env_cls=module.EnvBase,
|
|
43
|
+
_env_spec_cls=module.EnvSpecBase,
|
|
37
44
|
)
|
|
38
45
|
|
|
39
46
|
def expected_attributes(self) -> Mapping[str, AttributeType]:
|
|
40
47
|
return {
|
|
48
|
+
"common_settings": AttributeType(
|
|
49
|
+
cls=CommonSettings,
|
|
50
|
+
mode=AttributeMode.REQUIRED,
|
|
51
|
+
kind=AttributeKind.OBJECT,
|
|
52
|
+
),
|
|
41
53
|
"SoftwareDeploymentSettings": AttributeType(
|
|
42
54
|
cls=SoftwareDeploymentSettingsBase,
|
|
43
55
|
mode=AttributeMode.OPTIONAL,
|
|
@@ -48,4 +60,9 @@ class SoftwareDeploymentPluginRegistry(PluginRegistryBase):
|
|
|
48
60
|
mode=AttributeMode.REQUIRED,
|
|
49
61
|
kind=AttributeKind.CLASS,
|
|
50
62
|
),
|
|
63
|
+
"EnvSpec": AttributeType(
|
|
64
|
+
cls=EnvSpecBase,
|
|
65
|
+
mode=AttributeMode.REQUIRED,
|
|
66
|
+
kind=AttributeKind.CLASS,
|
|
67
|
+
),
|
|
51
68
|
}
|
|
@@ -7,6 +7,7 @@ from dataclasses import dataclass
|
|
|
7
7
|
from typing import Optional, Type
|
|
8
8
|
from snakemake_interface_software_deployment_plugins import EnvBase, EnvSpecBase
|
|
9
9
|
from snakemake_interface_software_deployment_plugins.settings import (
|
|
10
|
+
CommonSettings,
|
|
10
11
|
SoftwareDeploymentSettingsBase,
|
|
11
12
|
)
|
|
12
13
|
import snakemake_interface_software_deployment_plugins._common as common
|
|
@@ -16,6 +17,7 @@ from snakemake_interface_common.plugin_registry.plugin import PluginBase
|
|
|
16
17
|
|
|
17
18
|
@dataclass
|
|
18
19
|
class Plugin(PluginBase):
|
|
20
|
+
common_settings: CommonSettings
|
|
19
21
|
_software_deployment_settings_cls: Optional[Type[SoftwareDeploymentSettingsBase]]
|
|
20
22
|
_env_cls: Type[EnvBase]
|
|
21
23
|
_env_spec_cls: Type[EnvSpecBase]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import snakemake_interface_common.plugin_registry.plugin
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class SoftwareDeploymentSettingsBase(
|
|
9
|
+
snakemake_interface_common.plugin_registry.plugin.SettingsBase
|
|
10
|
+
):
|
|
11
|
+
"""Base class for software deployment settings.
|
|
12
|
+
|
|
13
|
+
Software deployment plugins can define a subclass of this class,
|
|
14
|
+
named 'SoftwareDeploymentProviderSettings'.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class CommonSettings:
|
|
22
|
+
"""Common settings for software deployment plugins.
|
|
23
|
+
|
|
24
|
+
This class is used to define common settings for software deployment plugins.
|
|
25
|
+
|
|
26
|
+
Attributes
|
|
27
|
+
----------
|
|
28
|
+
provides : str
|
|
29
|
+
The kind of the software environment provided (e.g. conda, container).
|
|
30
|
+
This should not return something describing the tool to provide the software
|
|
31
|
+
environment but the resulting environment itself. For example,
|
|
32
|
+
it should return "conda" instead of mamba, rattler, pixi etc., or
|
|
33
|
+
"container" instead of docker, singularity, podman, or
|
|
34
|
+
"envmodules" instead of lmod, environment-modules, etc.
|
|
35
|
+
Snakemake will ensure that the user only activates one plugin per provided
|
|
36
|
+
kind.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
provides: str
|
|
40
|
+
|
|
41
|
+
def __post_init__(self):
|
|
42
|
+
if not self.provides.isidentifier():
|
|
43
|
+
raise ValueError(
|
|
44
|
+
"CommonSettings.provides must be a valid Python identifier, but "
|
|
45
|
+
f"is {self.provides}."
|
|
46
|
+
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
from typing import Optional, Type
|
|
3
4
|
import subprocess as sp
|
|
4
5
|
|
|
@@ -9,6 +10,7 @@ from snakemake_interface_software_deployment_plugins import (
|
|
|
9
10
|
DeployableEnvBase,
|
|
10
11
|
EnvBase,
|
|
11
12
|
EnvSpecBase,
|
|
13
|
+
SoftwareReport,
|
|
12
14
|
)
|
|
13
15
|
from snakemake_interface_software_deployment_plugins.settings import (
|
|
14
16
|
SoftwareDeploymentSettingsBase,
|
|
@@ -46,10 +48,10 @@ class TestSoftwareDeploymentBase(ABC):
|
|
|
46
48
|
"""
|
|
47
49
|
...
|
|
48
50
|
|
|
51
|
+
@abstractmethod
|
|
49
52
|
def get_software_deployment_provider_settings(
|
|
50
53
|
self,
|
|
51
|
-
) -> Optional[SoftwareDeploymentSettingsBase]:
|
|
52
|
-
return None
|
|
54
|
+
) -> Optional[SoftwareDeploymentSettingsBase]: ...
|
|
53
55
|
|
|
54
56
|
def test_shellcmd(self, tmp_path):
|
|
55
57
|
env = self._get_env(tmp_path)
|
|
@@ -83,6 +85,27 @@ class TestSoftwareDeploymentBase(ABC):
|
|
|
83
85
|
env.archive()
|
|
84
86
|
assert any((tmp_path / "{_TEST_SDM_NAME}-archive").iterdir())
|
|
85
87
|
|
|
88
|
+
def test_report_software(self, tmp_path):
|
|
89
|
+
env = self._get_env(tmp_path)
|
|
90
|
+
rep = env.report_software()
|
|
91
|
+
assert all(isinstance(s, SoftwareReport) for s in rep)
|
|
92
|
+
|
|
93
|
+
def test_identity_attributes(self):
|
|
94
|
+
spec = self.get_env_spec()
|
|
95
|
+
assert all(
|
|
96
|
+
isinstance(attr, str) and hasattr(spec, attr)
|
|
97
|
+
for attr in spec.identity_attributes()
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def test_source_path_attributes(self):
|
|
101
|
+
spec = self.get_env_spec()
|
|
102
|
+
assert all(
|
|
103
|
+
isinstance(attr, str)
|
|
104
|
+
and hasattr(spec, attr)
|
|
105
|
+
and isinstance(getattr(spec, attr), (Path, str))
|
|
106
|
+
for attr in spec.source_path_attributes()
|
|
107
|
+
)
|
|
108
|
+
|
|
86
109
|
def _get_env(self, tmp_path) -> EnvBase:
|
|
87
110
|
env_cls = self.get_env_cls()
|
|
88
111
|
spec = self.get_env_spec()
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import snakemake_interface_common.plugin_registry.plugin
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@dataclass
|
|
8
|
-
class SoftwareDeploymentSettingsBase(
|
|
9
|
-
snakemake_interface_common.plugin_registry.plugin.SettingsBase
|
|
10
|
-
):
|
|
11
|
-
"""Base class for software deployment settings.
|
|
12
|
-
|
|
13
|
-
Software deployment plugins can define a subclass of this class,
|
|
14
|
-
named 'SoftwareDeploymentProviderSettings'.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
pass
|
|
File without changes
|
|
File without changes
|