seqevi 0.3.0__tar.gz → 0.3.2__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.
- {seqevi-0.3.0 → seqevi-0.3.2}/PKG-INFO +3 -3
- {seqevi-0.3.0 → seqevi-0.3.2}/README.md +2 -2
- {seqevi-0.3.0 → seqevi-0.3.2}/pyproject.toml +2 -1
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/__init__.py +1 -1
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/adapters/interpro_pfam.py +173 -28
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/distribution/manifest.py +9 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/distribution/oci.py +1 -2
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/kits/dbcan-cazyme.toml +2 -1
- seqevi-0.3.2/tests/test_acceptance_containment.py +362 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_acceptance_harnesses.py +96 -13
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_cli.py +1 -1
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_dbcan_image_assets.py +18 -0
- seqevi-0.3.2/tests/test_dbcan_local_candidate_harness.py +158 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_interpro_pfam.py +332 -7
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_managed_setup.py +7 -1
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_oci.py +66 -3
- {seqevi-0.3.0 → seqevi-0.3.2}/LICENSE +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/__main__.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/adapters/__init__.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/adapters/base.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/adapters/dbcan_cazyme.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/adapters/eggnog.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/adapters/registry.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/annotate.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/api.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/cli.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/distribution/__init__.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/distribution/setup.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/errors.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/evidence.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/execution_profile.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/hashing.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/kits/__init__.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/resource_lock.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/result.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/runner.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/runtime_identity.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/sequence.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/service/__init__.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/service/app.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/service/config.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/service/persistence.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/__init__.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/_artifact_reader.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/_resolver.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/artifact.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/client.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/contract.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/factory.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/local.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/migration.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/migrations/__init__.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/migrations/env.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/migrations/versions/0001_initial_store.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/migrations/versions/0002_artifact_byte_size_bigint.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/migrations/versions/0003_evidence_claim_leases.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/migrations/versions/0004_claim_sessions.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/migrations/versions/__init__.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/schema.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/src/seqevi/store/transport.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/__init__.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/support.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_annotate.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_api.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_architecture_contracts.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_artifact_store.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_claim_leases.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_dbcan_cazyme.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_deployment_assets.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_eggnog.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_evidence.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_execution_profile.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_incremental_reuse.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_local_store.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_resource_lock.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_result_prototype.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_runner.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_runtime_identity.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_sequence.py +0 -0
- {seqevi-0.3.0 → seqevi-0.3.2}/tests/test_shared_store.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: seqevi
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Content-addressed, reusable protein sequence annotation evidence.
|
|
5
5
|
Author-Email: Fuqing Zhang <fu.qing.zhang.work@gmail.com>, FuqingZhang <103730099+FuqingZh@users.noreply.github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -45,11 +45,11 @@ single-file DuckDB result for the current FASTA.
|
|
|
45
45
|
## Status
|
|
46
46
|
|
|
47
47
|
The target architecture and v1.1 result contracts are approved. The SeqEvi
|
|
48
|
-
0.3.
|
|
48
|
+
The 0.3.2 candidate source tree uses strict protein sequence identity, the single-host
|
|
49
49
|
SQLite/POSIX Store, the external tool runner, exact cache-miss orchestration,
|
|
50
50
|
and atomic DuckDB result materialization.
|
|
51
51
|
|
|
52
|
-
SeqEvi 0.3.
|
|
52
|
+
SeqEvi 0.3.2 provides managed setup for dbCAN only. The `eggnog` and
|
|
53
53
|
`interpro-pfam` adapters remain supported through explicit runtimes and named
|
|
54
54
|
host profiles; managed setup for them is later feature work. The
|
|
55
55
|
[Slice D gate record](docs/benchmarks/20260806-v1.4-dbcan-public-release-gate.md)
|
|
@@ -11,11 +11,11 @@ single-file DuckDB result for the current FASTA.
|
|
|
11
11
|
## Status
|
|
12
12
|
|
|
13
13
|
The target architecture and v1.1 result contracts are approved. The SeqEvi
|
|
14
|
-
0.3.
|
|
14
|
+
The 0.3.2 candidate source tree uses strict protein sequence identity, the single-host
|
|
15
15
|
SQLite/POSIX Store, the external tool runner, exact cache-miss orchestration,
|
|
16
16
|
and atomic DuckDB result materialization.
|
|
17
17
|
|
|
18
|
-
SeqEvi 0.3.
|
|
18
|
+
SeqEvi 0.3.2 provides managed setup for dbCAN only. The `eggnog` and
|
|
19
19
|
`interpro-pfam` adapters remain supported through explicit runtimes and named
|
|
20
20
|
host profiles; managed setup for them is later feature work. The
|
|
21
21
|
[Slice D gate record](docs/benchmarks/20260806-v1.4-dbcan-public-release-gate.md)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "seqevi"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.2"
|
|
4
4
|
description = "Content-addressed, reusable protein sequence annotation evidence."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Fuqing Zhang", email = "fu.qing.zhang.work@gmail.com" },
|
|
@@ -115,6 +115,7 @@ testpaths = [
|
|
|
115
115
|
]
|
|
116
116
|
pythonpath = [
|
|
117
117
|
"src",
|
|
118
|
+
".",
|
|
118
119
|
]
|
|
119
120
|
addopts = [
|
|
120
121
|
"-ra",
|
|
@@ -28,7 +28,7 @@ from seqevi.sequence import SequenceIdentity
|
|
|
28
28
|
|
|
29
29
|
from .base import AdapterBatchResult, AdapterContract, AdapterSequenceResult
|
|
30
30
|
|
|
31
|
-
ADAPTER_CONTRACT_VERSION = "interpro-pfam/
|
|
31
|
+
ADAPTER_CONTRACT_VERSION = "interpro-pfam/2"
|
|
32
32
|
|
|
33
33
|
INTERPRO_PFAM_EVIDENCE_SCHEMA: Mapping[str, pl.DataType] = {
|
|
34
34
|
"SequenceID": pl.String(),
|
|
@@ -53,6 +53,7 @@ _INTERPRO_ACCESSION_PATTERN = re.compile(r"IPR\d{6}\Z")
|
|
|
53
53
|
_TSV_COLUMN_COUNT = 15
|
|
54
54
|
_PROBE_TIMEOUT_SECONDS = 120.0
|
|
55
55
|
_NORMALIZED_ROW_BATCH_SIZE = 1000
|
|
56
|
+
_JAVA_OPTION_VARIABLES = ("JDK_JAVA_OPTIONS", "JAVA_TOOL_OPTIONS", "_JAVA_OPTIONS")
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
@dataclass(frozen=True, slots=True)
|
|
@@ -77,7 +78,7 @@ class InterProPfamParameters:
|
|
|
77
78
|
)
|
|
78
79
|
if values != ("Pfam", "TSV", True, False, False, "protein"):
|
|
79
80
|
raise ValueError(
|
|
80
|
-
"interpro-pfam/
|
|
81
|
+
"interpro-pfam/2 uses one fixed direct-scan scientific contract"
|
|
81
82
|
)
|
|
82
83
|
|
|
83
84
|
def as_semantic_parameters(self) -> dict[str, object]:
|
|
@@ -102,6 +103,29 @@ class InterProPfamAdapter:
|
|
|
102
103
|
self.database = database.resolve()
|
|
103
104
|
self.parameters = parameters or InterProPfamParameters()
|
|
104
105
|
self.environment = dict(environment or {})
|
|
106
|
+
if "PATH" not in self.environment:
|
|
107
|
+
inherited_path = os.environ.get("PATH")
|
|
108
|
+
if inherited_path is None:
|
|
109
|
+
raise AdapterError(
|
|
110
|
+
"InterProScan runtime has no inherited or profile PATH"
|
|
111
|
+
)
|
|
112
|
+
self.environment["PATH"] = inherited_path
|
|
113
|
+
path_entries = self.environment["PATH"].split(os.pathsep)
|
|
114
|
+
if any(not entry or not Path(entry).is_absolute() for entry in path_entries):
|
|
115
|
+
raise AdapterError(
|
|
116
|
+
"InterProScan runtime PATH entries must be non-empty absolute paths"
|
|
117
|
+
)
|
|
118
|
+
for name in _JAVA_OPTION_VARIABLES:
|
|
119
|
+
effective_value = self.environment.get(name, os.environ.get(name, ""))
|
|
120
|
+
if effective_value:
|
|
121
|
+
raise AdapterError(
|
|
122
|
+
f"InterProScan runtime does not allow non-empty {name}"
|
|
123
|
+
)
|
|
124
|
+
self.environment[name] = ""
|
|
125
|
+
java_path, java_home = _resolve_selected_java(self.environment)
|
|
126
|
+
self.environment["PATH"] = os.pathsep.join(
|
|
127
|
+
(str(java_path.parent), self.environment["PATH"])
|
|
128
|
+
)
|
|
105
129
|
self.install_dir = self.executable.parent
|
|
106
130
|
self.properties_path = self.install_dir / "interproscan.properties"
|
|
107
131
|
self.jar_path = self.install_dir / "interproscan-5.jar"
|
|
@@ -122,7 +146,7 @@ class InterProPfamAdapter:
|
|
|
122
146
|
jar_path=self.jar_path,
|
|
123
147
|
properties=self.properties,
|
|
124
148
|
version=self.interproscan_version,
|
|
125
|
-
|
|
149
|
+
java_home=java_home,
|
|
126
150
|
)
|
|
127
151
|
resource_id = _calculate_resource_id(
|
|
128
152
|
database=self.database,
|
|
@@ -352,7 +376,7 @@ def _calculate_runtime_digest(
|
|
|
352
376
|
jar_path: Path,
|
|
353
377
|
properties: Mapping[str, str],
|
|
354
378
|
version: str,
|
|
355
|
-
|
|
379
|
+
java_home: Path,
|
|
356
380
|
) -> str:
|
|
357
381
|
bin_dir = _resolve_install_path(
|
|
358
382
|
install_dir,
|
|
@@ -370,12 +394,6 @@ def _calculate_runtime_digest(
|
|
|
370
394
|
if not hmmer_files:
|
|
371
395
|
raise AdapterError(f"InterProScan HMMER directory is empty: {hmmer_dir}")
|
|
372
396
|
|
|
373
|
-
java = shutil.which(
|
|
374
|
-
"java",
|
|
375
|
-
path=environment.get("PATH", os.environ.get("PATH")),
|
|
376
|
-
)
|
|
377
|
-
if java is None:
|
|
378
|
-
raise AdapterError("InterProScan runtime has no Java executable")
|
|
379
397
|
normalized_properties = {
|
|
380
398
|
**properties,
|
|
381
399
|
"data.directory": "${data.directory}",
|
|
@@ -388,25 +406,152 @@ def _calculate_runtime_digest(
|
|
|
388
406
|
separators=(",", ":"),
|
|
389
407
|
).encode("utf-8")
|
|
390
408
|
)
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
409
|
+
try:
|
|
410
|
+
jdk_components, jdk_snapshot = _jdk_runtime_components(java_home)
|
|
411
|
+
components = (
|
|
412
|
+
RuntimeComponent("launcher", executable),
|
|
413
|
+
RuntimeComponent("interproscan-5.jar", jar_path),
|
|
414
|
+
*jdk_components,
|
|
415
|
+
*(
|
|
416
|
+
RuntimeComponent(
|
|
417
|
+
f"hmmer/{path.relative_to(hmmer_dir).as_posix()}",
|
|
418
|
+
path,
|
|
419
|
+
)
|
|
420
|
+
for path in hmmer_files
|
|
421
|
+
),
|
|
422
|
+
)
|
|
423
|
+
digest = calculate_runtime_digest(
|
|
424
|
+
runtime_name="interproscan-pfam",
|
|
425
|
+
versions={
|
|
426
|
+
"interproscan": version,
|
|
427
|
+
"properties": f"sha256:{properties_digest}",
|
|
428
|
+
},
|
|
429
|
+
components=components,
|
|
430
|
+
)
|
|
431
|
+
_, after_snapshot = _jdk_runtime_components(java_home)
|
|
432
|
+
except (OSError, UnicodeError) as error:
|
|
433
|
+
raise AdapterError(
|
|
434
|
+
f"cannot read selected JAVA_HOME runtime: {error}"
|
|
435
|
+
) from error
|
|
436
|
+
if after_snapshot != jdk_snapshot:
|
|
437
|
+
raise AdapterError("selected JAVA_HOME changed while hashing")
|
|
438
|
+
return digest
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _resolve_selected_java(environment: Mapping[str, str]) -> tuple[Path, Path]:
|
|
442
|
+
"""Resolve the launcher's frozen PATH selection and its JDK layout."""
|
|
443
|
+
|
|
444
|
+
selected = shutil.which("java", path=environment["PATH"])
|
|
445
|
+
if selected is None:
|
|
446
|
+
raise AdapterError("InterProScan runtime has no Java executable")
|
|
447
|
+
java_path = Path(selected).resolve()
|
|
448
|
+
if java_path.name != "java" or java_path.parent.name != "bin":
|
|
449
|
+
raise AdapterError(
|
|
450
|
+
f"resolved Java executable must be JAVA_HOME/bin/java: {java_path}"
|
|
451
|
+
)
|
|
452
|
+
return java_path, java_path.parent.parent
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _jdk_runtime_components(
|
|
456
|
+
java_home: Path,
|
|
457
|
+
) -> tuple[tuple[RuntimeComponent, ...], tuple[tuple[object, ...], ...]]:
|
|
458
|
+
release_path = java_home / "release"
|
|
459
|
+
roots = (java_home / "bin", java_home / "conf", java_home / "lib")
|
|
460
|
+
entries: list[tuple[str, Path]] = [("jdk/release", release_path)]
|
|
461
|
+
snapshot: list[tuple[object, ...]] = []
|
|
462
|
+
|
|
463
|
+
def fail(error: OSError) -> None:
|
|
464
|
+
raise error
|
|
465
|
+
|
|
466
|
+
for root in roots:
|
|
467
|
+
if root.is_symlink() or not root.is_dir():
|
|
468
|
+
raise AdapterError(
|
|
469
|
+
"selected JAVA_HOME runtime directory is missing or a symbolic "
|
|
470
|
+
f"link: {root}"
|
|
399
471
|
)
|
|
400
|
-
|
|
401
|
-
|
|
472
|
+
root_file_count = 0
|
|
473
|
+
for directory, directory_names, file_names in os.walk(
|
|
474
|
+
root, followlinks=False, onerror=fail
|
|
475
|
+
):
|
|
476
|
+
directory_path = Path(directory)
|
|
477
|
+
relative_directory = directory_path.relative_to(java_home).as_posix()
|
|
478
|
+
directory_stat = directory_path.stat(follow_symlinks=False)
|
|
479
|
+
snapshot.append(
|
|
480
|
+
(
|
|
481
|
+
relative_directory,
|
|
482
|
+
"directory",
|
|
483
|
+
directory_stat.st_dev,
|
|
484
|
+
directory_stat.st_ino,
|
|
485
|
+
directory_stat.st_size,
|
|
486
|
+
directory_stat.st_mtime_ns,
|
|
487
|
+
directory_stat.st_ctime_ns,
|
|
488
|
+
)
|
|
489
|
+
)
|
|
490
|
+
for name in sorted((*directory_names, *file_names)):
|
|
491
|
+
path = directory_path / name
|
|
492
|
+
if path.is_symlink():
|
|
493
|
+
raise AdapterError(
|
|
494
|
+
f"selected JAVA_HOME runtime contains a symbolic link: {path}"
|
|
495
|
+
)
|
|
496
|
+
for name in sorted(file_names):
|
|
497
|
+
path = directory_path / name
|
|
498
|
+
relative = path.relative_to(java_home).as_posix()
|
|
499
|
+
file_stat = path.stat(follow_symlinks=False)
|
|
500
|
+
if not path.is_file():
|
|
501
|
+
raise AdapterError(
|
|
502
|
+
"selected JAVA_HOME runtime entry is not a regular file: "
|
|
503
|
+
f"{path}"
|
|
504
|
+
)
|
|
505
|
+
snapshot.append(
|
|
506
|
+
(
|
|
507
|
+
relative,
|
|
508
|
+
"file",
|
|
509
|
+
file_stat.st_dev,
|
|
510
|
+
file_stat.st_ino,
|
|
511
|
+
file_stat.st_size,
|
|
512
|
+
file_stat.st_mtime_ns,
|
|
513
|
+
file_stat.st_ctime_ns,
|
|
514
|
+
)
|
|
515
|
+
)
|
|
516
|
+
entries.append((f"jdk/{relative}", path))
|
|
517
|
+
root_file_count += 1
|
|
518
|
+
if root_file_count == 0:
|
|
519
|
+
raise AdapterError(
|
|
520
|
+
f"selected JAVA_HOME runtime directory has no regular files: {root}"
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
if not release_path.is_file() or release_path.is_symlink():
|
|
524
|
+
raise AdapterError(
|
|
525
|
+
f"selected JAVA_HOME release file is missing or invalid: {release_path}"
|
|
526
|
+
)
|
|
527
|
+
release_stat = release_path.stat(follow_symlinks=False)
|
|
528
|
+
release_text = release_path.read_text(encoding="utf-8")
|
|
529
|
+
version_match = re.search(r'^JAVA_VERSION="?(\d+)', release_text, re.MULTILINE)
|
|
530
|
+
if version_match is None:
|
|
531
|
+
raise AdapterError(
|
|
532
|
+
f"selected JAVA_HOME release file has no JAVA_VERSION: {release_path}"
|
|
533
|
+
)
|
|
534
|
+
if int(version_match.group(1)) >= 11:
|
|
535
|
+
modules_path = java_home / "lib" / "modules"
|
|
536
|
+
if not modules_path.is_file() or modules_path.is_symlink():
|
|
537
|
+
raise AdapterError(
|
|
538
|
+
"selected Java 11+ runtime has no regular lib/modules image: "
|
|
539
|
+
f"{modules_path}"
|
|
540
|
+
)
|
|
541
|
+
snapshot.append(
|
|
542
|
+
(
|
|
543
|
+
"release",
|
|
544
|
+
"file",
|
|
545
|
+
release_stat.st_dev,
|
|
546
|
+
release_stat.st_ino,
|
|
547
|
+
release_stat.st_size,
|
|
548
|
+
release_stat.st_mtime_ns,
|
|
549
|
+
release_stat.st_ctime_ns,
|
|
550
|
+
)
|
|
402
551
|
)
|
|
403
|
-
return
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
"interproscan": version,
|
|
407
|
-
"properties": f"sha256:{properties_digest}",
|
|
408
|
-
},
|
|
409
|
-
components=components,
|
|
552
|
+
return (
|
|
553
|
+
tuple(RuntimeComponent(name, path) for name, path in sorted(entries)),
|
|
554
|
+
tuple(sorted(snapshot)),
|
|
410
555
|
)
|
|
411
556
|
|
|
412
557
|
|
|
@@ -675,7 +820,7 @@ def _parse_tsv_row(
|
|
|
675
820
|
if go_annotations != "-" or pathway_annotations != "-":
|
|
676
821
|
raise AdapterError(
|
|
677
822
|
f"InterProScan TSV line {line_number} contains GO or pathway annotations "
|
|
678
|
-
"outside the interpro-pfam/
|
|
823
|
+
"outside the interpro-pfam/2 contract"
|
|
679
824
|
)
|
|
680
825
|
|
|
681
826
|
return {
|
|
@@ -14,6 +14,9 @@ from seqevi.errors import SetupError
|
|
|
14
14
|
|
|
15
15
|
_SCHEMA_VERSION = 1
|
|
16
16
|
_KIT_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]*\Z")
|
|
17
|
+
_SEQEVI_VERSION = re.compile(
|
|
18
|
+
r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\Z"
|
|
19
|
+
)
|
|
17
20
|
_SHA256 = re.compile(r"[0-9a-f]{64}\Z")
|
|
18
21
|
_OCI_IMAGE = re.compile(
|
|
19
22
|
r"[a-z0-9][a-z0-9./_-]*(?::[A-Za-z0-9._-]+)?@sha256:[0-9a-f]{64}\Z"
|
|
@@ -21,6 +24,7 @@ _OCI_IMAGE = re.compile(
|
|
|
21
24
|
_ROOT_KEYS = {
|
|
22
25
|
"schema_version",
|
|
23
26
|
"kit_id",
|
|
27
|
+
"seqevi_version",
|
|
24
28
|
"adapter",
|
|
25
29
|
"platform",
|
|
26
30
|
"dbcan_version",
|
|
@@ -54,6 +58,7 @@ class KitManifest:
|
|
|
54
58
|
|
|
55
59
|
schema_version: int
|
|
56
60
|
kit_id: str
|
|
61
|
+
seqevi_version: str
|
|
57
62
|
adapter: AdapterName
|
|
58
63
|
platform: str
|
|
59
64
|
dbcan_version: str
|
|
@@ -105,6 +110,9 @@ def parse_kit_manifest(
|
|
|
105
110
|
kit_id = _string(document["kit_id"], "kit_id")
|
|
106
111
|
if _KIT_ID.fullmatch(kit_id) is None:
|
|
107
112
|
raise SetupError("managed kit manifest kit_id contains unsafe characters")
|
|
113
|
+
seqevi_version = _string(document["seqevi_version"], "seqevi_version")
|
|
114
|
+
if _SEQEVI_VERSION.fullmatch(seqevi_version) is None:
|
|
115
|
+
raise SetupError("managed kit manifest seqevi_version must be X.Y.Z")
|
|
108
116
|
adapter_text = _string(document["adapter"], "adapter")
|
|
109
117
|
try:
|
|
110
118
|
adapter = AdapterName(adapter_text)
|
|
@@ -166,6 +174,7 @@ def parse_kit_manifest(
|
|
|
166
174
|
return KitManifest(
|
|
167
175
|
schema_version=schema_version,
|
|
168
176
|
kit_id=kit_id,
|
|
177
|
+
seqevi_version=seqevi_version,
|
|
169
178
|
adapter=adapter,
|
|
170
179
|
platform=platform,
|
|
171
180
|
dbcan_version=dbcan_version,
|
|
@@ -15,7 +15,6 @@ from urllib.parse import urlsplit
|
|
|
15
15
|
|
|
16
16
|
import duckdb
|
|
17
17
|
|
|
18
|
-
from seqevi import __version__
|
|
19
18
|
from seqevi.annotate import AnnotationMetrics, AnnotationSummary
|
|
20
19
|
from seqevi.errors import AnnotationError
|
|
21
20
|
from seqevi.evidence import sha256_digest
|
|
@@ -354,7 +353,7 @@ def _validate_result(
|
|
|
354
353
|
metadata = dict(zip(columns, row, strict=True))
|
|
355
354
|
checks = {
|
|
356
355
|
"ResultFormatVersion": RESULT_FORMAT_VERSION,
|
|
357
|
-
"SeqEviVersion":
|
|
356
|
+
"SeqEviVersion": manifest.seqevi_version,
|
|
358
357
|
"Adapter": profile.adapter.value,
|
|
359
358
|
"AdapterContractVersion": "dbcan-cazyme/1",
|
|
360
359
|
"UpstreamTool": "dbCAN",
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
schema_version = 1
|
|
2
2
|
kit_id = "dbcan-cazyme-5.2.9-db-2026.05.05"
|
|
3
|
+
seqevi_version = "0.3.1"
|
|
3
4
|
adapter = "dbcan-cazyme"
|
|
4
5
|
platform = "linux/amd64"
|
|
5
6
|
dbcan_version = "5.2.9"
|
|
6
7
|
diamond_version = "2.1.15"
|
|
7
|
-
image = "ghcr.io/fuqingzh/seqevi-dbcan@sha256:
|
|
8
|
+
image = "ghcr.io/fuqingzh/seqevi-dbcan@sha256:051cbffa57b1b39fa8a6f811832dcc3224671b4a378ef8df7000f7f4314db9b6"
|
|
8
9
|
|
|
9
10
|
[resource]
|
|
10
11
|
name = "dbcan"
|