gseek 0.2.0__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.
- gseek/__init__.py +198 -0
- gseek/__main__.py +8 -0
- gseek/adapter.py +292 -0
- gseek/adapters/__init__.py +17 -0
- gseek/adapters/base.py +214 -0
- gseek/adapters/blast.py +395 -0
- gseek/adapters/bwa.py +396 -0
- gseek/adapters/kma.py +284 -0
- gseek/adapters/mash.py +326 -0
- gseek/adapters/minimap2.py +346 -0
- gseek/adapters/mummer.py +241 -0
- gseek/adapters/skani.py +243 -0
- gseek/adapters/sourmash.py +446 -0
- gseek/api.py +261 -0
- gseek/capabilities.py +132 -0
- gseek/cli.py +345 -0
- gseek/errors.py +245 -0
- gseek/execution.py +792 -0
- gseek/index.py +443 -0
- gseek/io.py +427 -0
- gseek/model/__init__.py +32 -0
- gseek/model/alignment.py +612 -0
- gseek/model/common.py +148 -0
- gseek/model/distance.py +202 -0
- gseek/model/run.py +220 -0
- gseek/provenance.py +373 -0
- gseek/py.typed +0 -0
- gseek/registry.py +206 -0
- gseek/replay.py +161 -0
- gseek/runner.py +627 -0
- gseek/types.py +205 -0
- gseek/version.py +7 -0
- gseek/views.py +129 -0
- gseek-0.2.0.dist-info/METADATA +252 -0
- gseek-0.2.0.dist-info/RECORD +38 -0
- gseek-0.2.0.dist-info/WHEEL +4 -0
- gseek-0.2.0.dist-info/entry_points.txt +2 -0
- gseek-0.2.0.dist-info/licenses/LICENSE +21 -0
gseek/__init__.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""gseek: unified, reproducible DNA alignment infrastructure."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from gseek.adapter import (
|
|
6
|
+
AlignmentAdapter,
|
|
7
|
+
AlignPlan,
|
|
8
|
+
AlignRequest,
|
|
9
|
+
DistanceAdapter,
|
|
10
|
+
DistanceRequest,
|
|
11
|
+
IndexPlan,
|
|
12
|
+
IndexRequest,
|
|
13
|
+
ParseContext,
|
|
14
|
+
PlanContext,
|
|
15
|
+
)
|
|
16
|
+
from gseek.api import (
|
|
17
|
+
align,
|
|
18
|
+
backends,
|
|
19
|
+
distance,
|
|
20
|
+
find,
|
|
21
|
+
find_backends,
|
|
22
|
+
get_backend,
|
|
23
|
+
register_adapter,
|
|
24
|
+
replay,
|
|
25
|
+
verify,
|
|
26
|
+
)
|
|
27
|
+
from gseek.capabilities import (
|
|
28
|
+
BackendSpec,
|
|
29
|
+
Capabilities,
|
|
30
|
+
ToolchainProbe,
|
|
31
|
+
ToolRequirement,
|
|
32
|
+
)
|
|
33
|
+
from gseek.errors import (
|
|
34
|
+
CacheError,
|
|
35
|
+
ConfigError,
|
|
36
|
+
DuplicateIdError,
|
|
37
|
+
ExecutionError,
|
|
38
|
+
GseekError,
|
|
39
|
+
IndexBuildError,
|
|
40
|
+
IndexLockError,
|
|
41
|
+
InputError,
|
|
42
|
+
ManifestError,
|
|
43
|
+
ParseError,
|
|
44
|
+
ReplayMismatchError,
|
|
45
|
+
ToolNotFoundError,
|
|
46
|
+
ToolVersionError,
|
|
47
|
+
UnsupportedFeatureError,
|
|
48
|
+
UsageError,
|
|
49
|
+
render_error,
|
|
50
|
+
)
|
|
51
|
+
from gseek.execution import (
|
|
52
|
+
CommandPlan,
|
|
53
|
+
Executor,
|
|
54
|
+
ProcessResult,
|
|
55
|
+
RunWorkspace,
|
|
56
|
+
ToolProbe,
|
|
57
|
+
ToolResolver,
|
|
58
|
+
create_workspace,
|
|
59
|
+
)
|
|
60
|
+
from gseek.index import IndexPolicy, IndexStore, compute_index_key, default_cache_root
|
|
61
|
+
from gseek.model import (
|
|
62
|
+
AlignmentRecord,
|
|
63
|
+
AlignmentResult,
|
|
64
|
+
AlignmentStats,
|
|
65
|
+
CommandRecord,
|
|
66
|
+
DistanceRecord,
|
|
67
|
+
DistanceResult,
|
|
68
|
+
DistanceStats,
|
|
69
|
+
FileRef,
|
|
70
|
+
IndexRef,
|
|
71
|
+
InputRef,
|
|
72
|
+
Issue,
|
|
73
|
+
QuerySummary,
|
|
74
|
+
RunManifest,
|
|
75
|
+
TargetSummary,
|
|
76
|
+
ToolRef,
|
|
77
|
+
build_alignment_stats,
|
|
78
|
+
)
|
|
79
|
+
from gseek.provenance import (
|
|
80
|
+
alignment_result_hash,
|
|
81
|
+
canonical_json,
|
|
82
|
+
distance_result_hash,
|
|
83
|
+
gseek_version,
|
|
84
|
+
read_manifest,
|
|
85
|
+
recipe_hash,
|
|
86
|
+
result_hash,
|
|
87
|
+
sha256_file,
|
|
88
|
+
verify_manifest,
|
|
89
|
+
write_manifest,
|
|
90
|
+
)
|
|
91
|
+
from gseek.replay import ReplayResult
|
|
92
|
+
from gseek.types import (
|
|
93
|
+
ALIGNMENT_SCHEMA,
|
|
94
|
+
DISTANCE_SCHEMA,
|
|
95
|
+
INDEX_SCHEMA,
|
|
96
|
+
RUN_SCHEMA,
|
|
97
|
+
IndexScope,
|
|
98
|
+
InputKind,
|
|
99
|
+
Interval,
|
|
100
|
+
JsonValue,
|
|
101
|
+
Molecule,
|
|
102
|
+
OutputKind,
|
|
103
|
+
RunStatus,
|
|
104
|
+
Strand,
|
|
105
|
+
)
|
|
106
|
+
from gseek.version import __version__
|
|
107
|
+
|
|
108
|
+
__all__ = [
|
|
109
|
+
"ALIGNMENT_SCHEMA",
|
|
110
|
+
"DISTANCE_SCHEMA",
|
|
111
|
+
"INDEX_SCHEMA",
|
|
112
|
+
"RUN_SCHEMA",
|
|
113
|
+
"AlignPlan",
|
|
114
|
+
"AlignRequest",
|
|
115
|
+
"AlignmentAdapter",
|
|
116
|
+
"AlignmentRecord",
|
|
117
|
+
"AlignmentResult",
|
|
118
|
+
"AlignmentStats",
|
|
119
|
+
"BackendSpec",
|
|
120
|
+
"CacheError",
|
|
121
|
+
"Capabilities",
|
|
122
|
+
"CommandPlan",
|
|
123
|
+
"CommandRecord",
|
|
124
|
+
"ConfigError",
|
|
125
|
+
"DistanceAdapter",
|
|
126
|
+
"DistanceRecord",
|
|
127
|
+
"DistanceRequest",
|
|
128
|
+
"DistanceResult",
|
|
129
|
+
"DistanceStats",
|
|
130
|
+
"DuplicateIdError",
|
|
131
|
+
"ExecutionError",
|
|
132
|
+
"Executor",
|
|
133
|
+
"FileRef",
|
|
134
|
+
"GseekError",
|
|
135
|
+
"IndexBuildError",
|
|
136
|
+
"IndexLockError",
|
|
137
|
+
"IndexPlan",
|
|
138
|
+
"IndexPolicy",
|
|
139
|
+
"IndexRef",
|
|
140
|
+
"IndexRequest",
|
|
141
|
+
"IndexScope",
|
|
142
|
+
"IndexStore",
|
|
143
|
+
"InputError",
|
|
144
|
+
"InputKind",
|
|
145
|
+
"InputRef",
|
|
146
|
+
"Interval",
|
|
147
|
+
"Issue",
|
|
148
|
+
"JsonValue",
|
|
149
|
+
"ManifestError",
|
|
150
|
+
"Molecule",
|
|
151
|
+
"OutputKind",
|
|
152
|
+
"ParseContext",
|
|
153
|
+
"ParseError",
|
|
154
|
+
"PlanContext",
|
|
155
|
+
"ProcessResult",
|
|
156
|
+
"QuerySummary",
|
|
157
|
+
"ReplayMismatchError",
|
|
158
|
+
"ReplayResult",
|
|
159
|
+
"RunManifest",
|
|
160
|
+
"RunStatus",
|
|
161
|
+
"RunWorkspace",
|
|
162
|
+
"Strand",
|
|
163
|
+
"TargetSummary",
|
|
164
|
+
"ToolNotFoundError",
|
|
165
|
+
"ToolProbe",
|
|
166
|
+
"ToolRef",
|
|
167
|
+
"ToolRequirement",
|
|
168
|
+
"ToolResolver",
|
|
169
|
+
"ToolVersionError",
|
|
170
|
+
"ToolchainProbe",
|
|
171
|
+
"UnsupportedFeatureError",
|
|
172
|
+
"UsageError",
|
|
173
|
+
"__version__",
|
|
174
|
+
"align",
|
|
175
|
+
"alignment_result_hash",
|
|
176
|
+
"backends",
|
|
177
|
+
"build_alignment_stats",
|
|
178
|
+
"canonical_json",
|
|
179
|
+
"compute_index_key",
|
|
180
|
+
"create_workspace",
|
|
181
|
+
"default_cache_root",
|
|
182
|
+
"distance",
|
|
183
|
+
"distance_result_hash",
|
|
184
|
+
"find",
|
|
185
|
+
"find_backends",
|
|
186
|
+
"get_backend",
|
|
187
|
+
"gseek_version",
|
|
188
|
+
"read_manifest",
|
|
189
|
+
"recipe_hash",
|
|
190
|
+
"register_adapter",
|
|
191
|
+
"render_error",
|
|
192
|
+
"replay",
|
|
193
|
+
"result_hash",
|
|
194
|
+
"sha256_file",
|
|
195
|
+
"verify",
|
|
196
|
+
"verify_manifest",
|
|
197
|
+
"write_manifest",
|
|
198
|
+
]
|
gseek/__main__.py
ADDED
gseek/adapter.py
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Adapter protocol: pure planning and parsing, no subprocesses.
|
|
2
|
+
|
|
3
|
+
An adapter turns a declarative request into commands plus a parser. The runner
|
|
4
|
+
owns execution, index publication, provenance, and result construction. This
|
|
5
|
+
split is what makes adapters testable without installing external tools.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Iterable, Iterator, Mapping
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Protocol, runtime_checkable
|
|
14
|
+
|
|
15
|
+
from gseek.capabilities import BackendSpec, ToolchainProbe, ToolRequirement, supports
|
|
16
|
+
from gseek.errors import ToolNotFoundError, ToolVersionError, UnsupportedFeatureError
|
|
17
|
+
from gseek.execution import CommandPlan, ToolProbe, ToolResolver
|
|
18
|
+
from gseek.index import IndexPolicy
|
|
19
|
+
from gseek.io import detect_input_kind
|
|
20
|
+
from gseek.model import AlignmentRecord, IndexRef, InputRef, ToolRef
|
|
21
|
+
from gseek.provenance import semantic_params
|
|
22
|
+
from gseek.types import InputKind, JsonValue, Molecule, OutputKind
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class AlignRequest:
|
|
27
|
+
"""One query (or read pair) against one target set."""
|
|
28
|
+
|
|
29
|
+
query: tuple[Path, ...]
|
|
30
|
+
target: tuple[Path, ...]
|
|
31
|
+
query_kind: InputKind | None = None
|
|
32
|
+
paired: bool = False
|
|
33
|
+
params: Mapping[str, JsonValue] = field(default_factory=dict)
|
|
34
|
+
threads: int = 1
|
|
35
|
+
timeout_s: float | None = None
|
|
36
|
+
index_policy: IndexPolicy = IndexPolicy.AUTO
|
|
37
|
+
target_filter: frozenset[str] | None = None
|
|
38
|
+
work_dir: Path | None = None
|
|
39
|
+
keep_work: bool = False
|
|
40
|
+
|
|
41
|
+
def __post_init__(self) -> None:
|
|
42
|
+
if not self.query:
|
|
43
|
+
raise UnsupportedFeatureError("at least one query file is required")
|
|
44
|
+
if not self.target:
|
|
45
|
+
raise UnsupportedFeatureError("at least one target file is required")
|
|
46
|
+
if self.paired and len(self.query) != 2:
|
|
47
|
+
raise UnsupportedFeatureError("paired mode requires exactly two query files")
|
|
48
|
+
if not self.paired and len(self.query) != 1:
|
|
49
|
+
raise UnsupportedFeatureError("single-end mode requires exactly one query file")
|
|
50
|
+
if self.threads < 1:
|
|
51
|
+
raise UnsupportedFeatureError(f"threads must be >= 1, got {self.threads}")
|
|
52
|
+
if self.timeout_s is not None and self.timeout_s <= 0:
|
|
53
|
+
raise UnsupportedFeatureError(f"timeout_s must be positive, got {self.timeout_s}")
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def resolved_query_kind(self) -> InputKind:
|
|
57
|
+
return self.query_kind or detect_input_kind(self.query[0])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True, slots=True)
|
|
61
|
+
class DistanceRequest:
|
|
62
|
+
"""One or more queries against a sketch/distance reference set."""
|
|
63
|
+
|
|
64
|
+
query: tuple[Path, ...]
|
|
65
|
+
reference: tuple[Path, ...]
|
|
66
|
+
query_kind: InputKind | None = None
|
|
67
|
+
params: Mapping[str, JsonValue] = field(default_factory=dict)
|
|
68
|
+
threads: int = 1
|
|
69
|
+
timeout_s: float | None = None
|
|
70
|
+
index_policy: IndexPolicy = IndexPolicy.AUTO
|
|
71
|
+
external_reference: bool = False
|
|
72
|
+
work_dir: Path | None = None
|
|
73
|
+
keep_work: bool = False
|
|
74
|
+
|
|
75
|
+
def __post_init__(self) -> None:
|
|
76
|
+
if not self.query:
|
|
77
|
+
raise UnsupportedFeatureError("at least one query file is required")
|
|
78
|
+
if not self.reference:
|
|
79
|
+
raise UnsupportedFeatureError("at least one reference file is required")
|
|
80
|
+
if self.threads < 1:
|
|
81
|
+
raise UnsupportedFeatureError(f"threads must be >= 1, got {self.threads}")
|
|
82
|
+
if self.external_reference and len(self.reference) != 1:
|
|
83
|
+
raise UnsupportedFeatureError(
|
|
84
|
+
"external_reference requires exactly one pre-built reference file"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(frozen=True, slots=True)
|
|
89
|
+
class IndexRequest:
|
|
90
|
+
target: tuple[Path, ...]
|
|
91
|
+
params: Mapping[str, JsonValue] = field(default_factory=dict)
|
|
92
|
+
threads: int = 1
|
|
93
|
+
force: bool = False
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True, slots=True)
|
|
97
|
+
class PlanContext:
|
|
98
|
+
"""Paths and immutable context available while planning commands."""
|
|
99
|
+
|
|
100
|
+
workspace: Path
|
|
101
|
+
index_dir: Path | None = None
|
|
102
|
+
threads: int = 1
|
|
103
|
+
query_refs: tuple[InputRef, ...] = ()
|
|
104
|
+
target_refs: tuple[InputRef, ...] = ()
|
|
105
|
+
|
|
106
|
+
def path(self, *parts: str) -> Path:
|
|
107
|
+
target = self.workspace.joinpath(*parts)
|
|
108
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
return target
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass(frozen=True, slots=True)
|
|
113
|
+
class IndexPlan:
|
|
114
|
+
"""Commands that build an index plus the expected entry/artifacts."""
|
|
115
|
+
|
|
116
|
+
commands: tuple[CommandPlan, ...]
|
|
117
|
+
entry: str
|
|
118
|
+
artifacts: tuple[str, ...] = ()
|
|
119
|
+
params: Mapping[str, JsonValue] = field(default_factory=dict)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass(frozen=True, slots=True)
|
|
123
|
+
class AlignPlan:
|
|
124
|
+
"""Commands that perform one alignment plus how to parse their output."""
|
|
125
|
+
|
|
126
|
+
commands: tuple[CommandPlan, ...]
|
|
127
|
+
stream: bool = False
|
|
128
|
+
result_files: tuple[Path, ...] = ()
|
|
129
|
+
extra: Mapping[str, JsonValue] = field(default_factory=dict)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass(frozen=True, slots=True)
|
|
133
|
+
class ParseContext:
|
|
134
|
+
"""Everything a parser needs to normalize one result set."""
|
|
135
|
+
|
|
136
|
+
request: AlignRequest
|
|
137
|
+
plan: AlignPlan
|
|
138
|
+
workspace: Path
|
|
139
|
+
index: IndexRef | None = None
|
|
140
|
+
tools: tuple[ToolRef, ...] = ()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@runtime_checkable
|
|
144
|
+
class AlignmentAdapter(Protocol):
|
|
145
|
+
"""A backend that produces :class:`AlignmentRecord` rows."""
|
|
146
|
+
|
|
147
|
+
spec: BackendSpec
|
|
148
|
+
|
|
149
|
+
def probe(self, resolver: ToolResolver, *, strict: bool = False) -> ToolchainProbe: ...
|
|
150
|
+
|
|
151
|
+
def semantic_params(self, params: Mapping[str, JsonValue]) -> dict[str, JsonValue]: ...
|
|
152
|
+
|
|
153
|
+
def plan_index(self, request: IndexRequest, ctx: PlanContext) -> IndexPlan | None: ...
|
|
154
|
+
|
|
155
|
+
def plan_align(
|
|
156
|
+
self,
|
|
157
|
+
request: AlignRequest,
|
|
158
|
+
index: IndexRef | None,
|
|
159
|
+
ctx: PlanContext,
|
|
160
|
+
) -> AlignPlan: ...
|
|
161
|
+
|
|
162
|
+
def parse_align(
|
|
163
|
+
self,
|
|
164
|
+
ctx: ParseContext,
|
|
165
|
+
stream: Iterable[str] | None = None,
|
|
166
|
+
) -> Iterator[AlignmentRecord]: ...
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@runtime_checkable
|
|
170
|
+
class DistanceAdapter(Protocol):
|
|
171
|
+
"""A backend that produces distance/containment rows."""
|
|
172
|
+
|
|
173
|
+
spec: BackendSpec
|
|
174
|
+
|
|
175
|
+
def probe(self, resolver: ToolResolver, *, strict: bool = False) -> ToolchainProbe: ...
|
|
176
|
+
|
|
177
|
+
def semantic_params(self, params: Mapping[str, JsonValue]) -> dict[str, JsonValue]: ...
|
|
178
|
+
|
|
179
|
+
def plan_index(self, request: IndexRequest, ctx: PlanContext) -> IndexPlan | None: ...
|
|
180
|
+
|
|
181
|
+
def plan_distance(
|
|
182
|
+
self,
|
|
183
|
+
request: DistanceRequest,
|
|
184
|
+
index: IndexRef | None,
|
|
185
|
+
ctx: PlanContext,
|
|
186
|
+
) -> AlignPlan: ...
|
|
187
|
+
|
|
188
|
+
def parse_distance(
|
|
189
|
+
self,
|
|
190
|
+
ctx: ParseContext,
|
|
191
|
+
stream: Iterable[str] | None = None,
|
|
192
|
+
) -> Iterator[object]: ...
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class BaseAdapter:
|
|
196
|
+
"""Shared validation/probing helpers for concrete adapters."""
|
|
197
|
+
|
|
198
|
+
spec: BackendSpec
|
|
199
|
+
|
|
200
|
+
def probe(self, resolver: ToolResolver, *, strict: bool = False) -> ToolchainProbe:
|
|
201
|
+
"""Resolve and version-check every declared tool."""
|
|
202
|
+
probes = []
|
|
203
|
+
failed: list[tuple[ToolRequirement, ToolProbe]] = []
|
|
204
|
+
for requirement in self.spec.tools:
|
|
205
|
+
probe = resolver.probe(requirement, strict=False)
|
|
206
|
+
probes.append(probe)
|
|
207
|
+
if not probe.ok and not requirement.optional:
|
|
208
|
+
failed.append((requirement, probe))
|
|
209
|
+
failures = [f"{probe.name}: {probe.error or 'unavailable'}" for _, probe in failed]
|
|
210
|
+
ok = not failures
|
|
211
|
+
result = ToolchainProbe(
|
|
212
|
+
backend=self.spec.name,
|
|
213
|
+
tools=tuple(probes),
|
|
214
|
+
ok=ok,
|
|
215
|
+
error="; ".join(failures),
|
|
216
|
+
)
|
|
217
|
+
if strict and failed:
|
|
218
|
+
missing = any(probe.path is None for _, probe in failed)
|
|
219
|
+
error_type = ToolNotFoundError if missing else ToolVersionError
|
|
220
|
+
hint = "; ".join(requirement.hint for requirement, _ in failed if requirement.hint)
|
|
221
|
+
raise error_type(
|
|
222
|
+
f"backend {self.spec.name!r} is unavailable: {result.error}",
|
|
223
|
+
hint=hint,
|
|
224
|
+
context={"backend": self.spec.name},
|
|
225
|
+
)
|
|
226
|
+
return result
|
|
227
|
+
|
|
228
|
+
def semantic_params(self, params: Mapping[str, JsonValue]) -> dict[str, JsonValue]:
|
|
229
|
+
return semantic_params(params)
|
|
230
|
+
|
|
231
|
+
def plan_index(self, request: IndexRequest, ctx: PlanContext) -> IndexPlan | None:
|
|
232
|
+
if self.spec.capabilities.index_scope.value != "none":
|
|
233
|
+
raise NotImplementedError(f"{self.spec.name} declares an index but does not plan one")
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
def check_align_request(self, request: AlignRequest) -> InputKind:
|
|
237
|
+
"""Validate input kind/paired/preset against the capability declaration."""
|
|
238
|
+
query_kind = request.resolved_query_kind
|
|
239
|
+
if not supports(
|
|
240
|
+
self.spec,
|
|
241
|
+
input_kind=query_kind,
|
|
242
|
+
output_kind=OutputKind.ALIGNMENT,
|
|
243
|
+
paired=request.paired,
|
|
244
|
+
):
|
|
245
|
+
raise UnsupportedFeatureError(
|
|
246
|
+
f"backend {self.spec.name!r} does not support "
|
|
247
|
+
f"input={query_kind.value}, paired={request.paired}",
|
|
248
|
+
context={"backend": self.spec.name, "input": query_kind.value},
|
|
249
|
+
)
|
|
250
|
+
preset = request.params.get("preset")
|
|
251
|
+
if (
|
|
252
|
+
preset is not None
|
|
253
|
+
and self.spec.capabilities.presets
|
|
254
|
+
and str(preset) not in self.spec.capabilities.presets
|
|
255
|
+
):
|
|
256
|
+
raise UnsupportedFeatureError(
|
|
257
|
+
f"unknown preset {preset!r} for backend {self.spec.name!r}; "
|
|
258
|
+
f"expected one of {self.spec.capabilities.presets}",
|
|
259
|
+
context={"backend": self.spec.name, "preset": str(preset)},
|
|
260
|
+
)
|
|
261
|
+
return query_kind
|
|
262
|
+
|
|
263
|
+
def describe(self) -> dict[str, JsonValue]:
|
|
264
|
+
from gseek.provenance import to_jsonable
|
|
265
|
+
|
|
266
|
+
return to_jsonable(self.spec)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def request_molecule(params: Mapping[str, JsonValue]) -> Molecule:
|
|
270
|
+
raw = str(params.get("molecule", Molecule.DNA.value)).lower()
|
|
271
|
+
try:
|
|
272
|
+
return Molecule(raw)
|
|
273
|
+
except ValueError as exc:
|
|
274
|
+
raise UnsupportedFeatureError(
|
|
275
|
+
f"unknown molecule {raw!r}",
|
|
276
|
+
context={"molecule": raw},
|
|
277
|
+
) from exc
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
__all__ = [
|
|
281
|
+
"AlignPlan",
|
|
282
|
+
"AlignRequest",
|
|
283
|
+
"AlignmentAdapter",
|
|
284
|
+
"BaseAdapter",
|
|
285
|
+
"DistanceAdapter",
|
|
286
|
+
"DistanceRequest",
|
|
287
|
+
"IndexPlan",
|
|
288
|
+
"IndexRequest",
|
|
289
|
+
"ParseContext",
|
|
290
|
+
"PlanContext",
|
|
291
|
+
"request_molecule",
|
|
292
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Built-in adapters.
|
|
2
|
+
|
|
3
|
+
Concrete modules land per the milestone plan:
|
|
4
|
+
|
|
5
|
+
- M1: :mod:`gseek.adapters.blast`, :mod:`gseek.adapters.minimap2`
|
|
6
|
+
- M2: :mod:`gseek.adapters.kma`, :mod:`gseek.adapters.mummer`
|
|
7
|
+
- M3: :mod:`gseek.adapters.bwa`, :mod:`gseek.adapters.mash`,
|
|
8
|
+
:mod:`gseek.adapters.sourmash`
|
|
9
|
+
- M4: protein BLAST modes, sourmash gather, :mod:`gseek.adapters.skani`
|
|
10
|
+
|
|
11
|
+
Each module exposes a class named ``ADAPTER``; add its dotted module path to
|
|
12
|
+
:data:`gseek.registry.BUILTIN_MODULES` when it is ready.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
__all__: list[str] = []
|