dnakit 0.1.0.dev0__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.
Files changed (210) hide show
  1. dnakit/__init__.py +108 -0
  2. dnakit/_version.py +5 -0
  3. dnakit/alignment/__init__.py +6 -0
  4. dnakit/alignment/pairwise.py +512 -0
  5. dnakit/alignment/results.py +188 -0
  6. dnakit/backends/__init__.py +80 -0
  7. dnakit/backends/external.py +517 -0
  8. dnakit/backends/registry.py +207 -0
  9. dnakit/batch.py +452 -0
  10. dnakit/cache/__init__.py +5 -0
  11. dnakit/cache/store.py +424 -0
  12. dnakit/chunking.py +920 -0
  13. dnakit/cli/__init__.py +5 -0
  14. dnakit/cli/__main__.py +6 -0
  15. dnakit/cli/app.py +814 -0
  16. dnakit/cli/workflow.py +94 -0
  17. dnakit/config/__init__.py +5 -0
  18. dnakit/config/loader.py +381 -0
  19. dnakit/core/__init__.py +80 -0
  20. dnakit/core/_json.py +206 -0
  21. dnakit/core/backend_info.py +70 -0
  22. dnakit/core/collection.py +163 -0
  23. dnakit/core/coordinates.py +295 -0
  24. dnakit/core/enums.py +95 -0
  25. dnakit/core/facade.py +719 -0
  26. dnakit/core/feature.py +90 -0
  27. dnakit/core/gap.py +82 -0
  28. dnakit/core/issues.py +62 -0
  29. dnakit/core/provenance.py +305 -0
  30. dnakit/core/record.py +161 -0
  31. dnakit/core/results.py +134 -0
  32. dnakit/core/sequence.py +327 -0
  33. dnakit/datasets/__init__.py +100 -0
  34. dnakit/datasets/_advanced_shared.py +198 -0
  35. dnakit/datasets/_metadata.py +33 -0
  36. dnakit/datasets/advanced_deduplicate.py +183 -0
  37. dnakit/datasets/advanced_split.py +302 -0
  38. dnakit/datasets/clustering.py +286 -0
  39. dnakit/datasets/config.py +531 -0
  40. dnakit/datasets/deduplicate.py +303 -0
  41. dnakit/datasets/evaluation.py +244 -0
  42. dnakit/datasets/filter.py +135 -0
  43. dnakit/datasets/neural_clustering.py +221 -0
  44. dnakit/datasets/results.py +520 -0
  45. dnakit/datasets/split.py +450 -0
  46. dnakit/descriptors/__init__.py +102 -0
  47. dnakit/descriptors/_dinucleotide.py +345 -0
  48. dnakit/descriptors/_shared.py +180 -0
  49. dnakit/descriptors/all.py +917 -0
  50. dnakit/descriptors/basic.py +193 -0
  51. dnakit/descriptors/codon.py +195 -0
  52. dnakit/descriptors/complexity.py +201 -0
  53. dnakit/descriptors/entropy.py +101 -0
  54. dnakit/descriptors/homopolymer.py +105 -0
  55. dnakit/descriptors/kmer.py +88 -0
  56. dnakit/descriptors/results.py +286 -0
  57. dnakit/descriptors/schema.py +414 -0
  58. dnakit/descriptors/window.py +203 -0
  59. dnakit/download/__init__.py +54 -0
  60. dnakit/download/catalogs.py +166 -0
  61. dnakit/download/ena.py +131 -0
  62. dnakit/download/encode.py +103 -0
  63. dnakit/download/files.py +469 -0
  64. dnakit/download/indexes.py +233 -0
  65. dnakit/download/metadata.py +164 -0
  66. dnakit/download/models.py +227 -0
  67. dnakit/download/ncbi.py +333 -0
  68. dnakit/download/sequence.py +133 -0
  69. dnakit/download/ucsc.py +99 -0
  70. dnakit/evaluation/__init__.py +81 -0
  71. dnakit/evaluation/_shared.py +288 -0
  72. dnakit/evaluation/collection.py +270 -0
  73. dnakit/evaluation/config.py +591 -0
  74. dnakit/evaluation/distribution.py +287 -0
  75. dnakit/evaluation/frechet.py +223 -0
  76. dnakit/evaluation/generative.py +381 -0
  77. dnakit/evaluation/reference.py +409 -0
  78. dnakit/evaluation/results.py +137 -0
  79. dnakit/evaluation/scorecard.py +211 -0
  80. dnakit/evaluation/sequence.py +403 -0
  81. dnakit/evaluation/synthesis.py +249 -0
  82. dnakit/exceptions.py +189 -0
  83. dnakit/fingerprints/__init__.py +109 -0
  84. dnakit/fingerprints/_shared.py +116 -0
  85. dnakit/fingerprints/advanced.py +829 -0
  86. dnakit/fingerprints/bit.py +229 -0
  87. dnakit/fingerprints/encoding.py +351 -0
  88. dnakit/fingerprints/kmer.py +170 -0
  89. dnakit/fingerprints/results.py +531 -0
  90. dnakit/fingerprints/sketch.py +246 -0
  91. dnakit/io/__init__.py +112 -0
  92. dnakit/io/_advanced_common.py +105 -0
  93. dnakit/io/_formats.py +994 -0
  94. dnakit/io/annotations.py +1179 -0
  95. dnakit/io/api.py +759 -0
  96. dnakit/io/config.py +315 -0
  97. dnakit/io/genbank.py +523 -0
  98. dnakit/io/indexed.py +1146 -0
  99. dnakit/io/metadata.py +452 -0
  100. dnakit/io/parquet.py +56 -0
  101. dnakit/io/results.py +131 -0
  102. dnakit/io/source.py +138 -0
  103. dnakit/io/tables.py +1237 -0
  104. dnakit/molbio/__init__.py +101 -0
  105. dnakit/molbio/_shared.py +266 -0
  106. dnakit/molbio/assembly.py +279 -0
  107. dnakit/molbio/crispr.py +181 -0
  108. dnakit/molbio/optimization.py +897 -0
  109. dnakit/molbio/primer3_design.py +667 -0
  110. dnakit/molbio/primers.py +899 -0
  111. dnakit/molbio/restriction.py +573 -0
  112. dnakit/molbio/results.py +583 -0
  113. dnakit/ops/__init__.py +588 -0
  114. dnakit/ops/_common.py +201 -0
  115. dnakit/ops/circular.py +193 -0
  116. dnakit/ops/concat.py +204 -0
  117. dnakit/ops/direction.py +27 -0
  118. dnakit/ops/edit.py +443 -0
  119. dnakit/ops/evolution.py +991 -0
  120. dnakit/ops/mutation.py +151 -0
  121. dnakit/ops/records.py +1012 -0
  122. dnakit/ops/translation.py +257 -0
  123. dnakit/patterns/__init__.py +81 -0
  124. dnakit/patterns/_shared.py +282 -0
  125. dnakit/patterns/coding.py +360 -0
  126. dnakit/patterns/crispr.py +337 -0
  127. dnakit/patterns/motif.py +721 -0
  128. dnakit/patterns/regions.py +341 -0
  129. dnakit/patterns/repeats.py +480 -0
  130. dnakit/patterns/restriction.py +281 -0
  131. dnakit/patterns/results.py +411 -0
  132. dnakit/py.typed +1 -0
  133. dnakit/references/__init__.py +27 -0
  134. dnakit/references/models.py +155 -0
  135. dnakit/references/ncbi.py +785 -0
  136. dnakit/representations/__init__.py +29 -0
  137. dnakit/representations/backends.py +928 -0
  138. dnakit/representations/checkpoints.py +356 -0
  139. dnakit/representations/extraction.py +220 -0
  140. dnakit/representations/models.py +395 -0
  141. dnakit/representations/results.py +79 -0
  142. dnakit/search/__init__.py +132 -0
  143. dnakit/search/_http.py +271 -0
  144. dnakit/search/_shared.py +108 -0
  145. dnakit/search/blast.py +516 -0
  146. dnakit/search/ena.py +245 -0
  147. dnakit/search/encode.py +102 -0
  148. dnakit/search/ensembl.py +874 -0
  149. dnakit/search/models.py +159 -0
  150. dnakit/search/ncbi.py +756 -0
  151. dnakit/search/ucsc.py +388 -0
  152. dnakit/secondary_structure/__init__.py +38 -0
  153. dnakit/secondary_structure/dotbracket.py +410 -0
  154. dnakit/secondary_structure/nupack.py +589 -0
  155. dnakit/secondary_structure/results.py +142 -0
  156. dnakit/similarity/__init__.py +125 -0
  157. dnakit/similarity/_shared.py +152 -0
  158. dnakit/similarity/approximate.py +258 -0
  159. dnakit/similarity/compare.py +162 -0
  160. dnakit/similarity/dashing.py +936 -0
  161. dnakit/similarity/distance.py +279 -0
  162. dnakit/similarity/index.py +365 -0
  163. dnakit/similarity/matrix.py +254 -0
  164. dnakit/similarity/results.py +676 -0
  165. dnakit/similarity/search.py +286 -0
  166. dnakit/similarity/sketch.py +96 -0
  167. dnakit/similarity/vector.py +662 -0
  168. dnakit/standardize/__init__.py +77 -0
  169. dnakit/standardize/_shared.py +100 -0
  170. dnakit/standardize/config.py +411 -0
  171. dnakit/standardize/gaps.py +1060 -0
  172. dnakit/standardize/normalize.py +565 -0
  173. dnakit/standardize/results.py +247 -0
  174. dnakit/standardize/validate.py +487 -0
  175. dnakit/structure3d/__init__.py +47 -0
  176. dnakit/structure3d/_shared.py +47 -0
  177. dnakit/structure3d/geometry.py +773 -0
  178. dnakit/structure3d/pdb.py +253 -0
  179. dnakit/structure3d/results.py +233 -0
  180. dnakit/structure3d/threedna.py +174 -0
  181. dnakit/thermodynamics/__init__.py +112 -0
  182. dnakit/thermodynamics/_shared.py +92 -0
  183. dnakit/thermodynamics/backends.py +653 -0
  184. dnakit/thermodynamics/calculations.py +837 -0
  185. dnakit/thermodynamics/config.py +175 -0
  186. dnakit/thermodynamics/equilibrium.py +340 -0
  187. dnakit/thermodynamics/optics.py +385 -0
  188. dnakit/thermodynamics/parameters.py +79 -0
  189. dnakit/thermodynamics/results.py +445 -0
  190. dnakit/visualization/__init__.py +43 -0
  191. dnakit/visualization/_svg.py +185 -0
  192. dnakit/visualization/advanced.py +314 -0
  193. dnakit/visualization/config.py +255 -0
  194. dnakit/visualization/export.py +349 -0
  195. dnakit/visualization/matrix.py +279 -0
  196. dnakit/visualization/report.py +431 -0
  197. dnakit/visualization/results.py +236 -0
  198. dnakit/visualization/sequence.py +459 -0
  199. dnakit/workflows/__init__.py +45 -0
  200. dnakit/workflows/manifest.py +365 -0
  201. dnakit/workflows/runner.py +1548 -0
  202. dnakit/workflows/schema.py +898 -0
  203. dnakit-0.1.0.dev0.dist-info/METADATA +126 -0
  204. dnakit-0.1.0.dev0.dist-info/RECORD +210 -0
  205. dnakit-0.1.0.dev0.dist-info/WHEEL +5 -0
  206. dnakit-0.1.0.dev0.dist-info/entry_points.txt +2 -0
  207. dnakit-0.1.0.dev0.dist-info/licenses/DISCLAIMER.md +26 -0
  208. dnakit-0.1.0.dev0.dist-info/licenses/LICENSE +21 -0
  209. dnakit-0.1.0.dev0.dist-info/licenses/THIRD_PARTY_NOTICES.md +73 -0
  210. dnakit-0.1.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,188 @@
1
+ """Immutable pairwise-alignment result objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from dataclasses import dataclass, field
7
+ from typing import Any, Literal, cast
8
+
9
+ from dnakit.core import (
10
+ ExecutionMode,
11
+ ImplementationInfo,
12
+ ImplementationLabel,
13
+ Issue,
14
+ OriginClass,
15
+ Provenance,
16
+ )
17
+ from dnakit.core._json import FrozenDict, to_json_compatible
18
+ from dnakit.exceptions import ConfigurationError
19
+
20
+
21
+ def _alignment_provenance() -> Provenance:
22
+ return Provenance(
23
+ implementation=ImplementationInfo(
24
+ label=ImplementationLabel.REIMPLEMENTATION,
25
+ execution_mode=ExecutionMode.INTERNAL,
26
+ origin_class=OriginClass.PUBLISHED_ALGORITHM,
27
+ )
28
+ )
29
+
30
+
31
+ @dataclass(frozen=True, slots=True)
32
+ class AlignmentColumn:
33
+ """One alignment column and its optional source coordinates."""
34
+
35
+ query_symbol: str
36
+ target_symbol: str
37
+ query_position: int | None
38
+ target_position: int | None
39
+ operation: Literal["match", "mismatch", "insertion", "deletion"]
40
+
41
+ def __post_init__(self) -> None:
42
+ for name in ("query_symbol", "target_symbol"):
43
+ value = getattr(self, name)
44
+ if not isinstance(value, str) or len(value) != 1:
45
+ raise ConfigurationError(f"Alignment column {name} must be one character.")
46
+ for name in ("query_position", "target_position"):
47
+ value = getattr(self, name)
48
+ if value is not None and (
49
+ isinstance(value, bool) or not isinstance(value, int) or value < 0
50
+ ):
51
+ raise ConfigurationError(f"Alignment column {name} must be non-negative or None.")
52
+ if self.operation not in ("match", "mismatch", "insertion", "deletion"):
53
+ raise ConfigurationError("Alignment column operation is invalid.")
54
+ if self.operation == "insertion":
55
+ valid = (
56
+ self.query_symbol == "-"
57
+ and self.query_position is None
58
+ and self.target_symbol != "-"
59
+ and self.target_position is not None
60
+ )
61
+ elif self.operation == "deletion":
62
+ valid = (
63
+ self.target_symbol == "-"
64
+ and self.target_position is None
65
+ and self.query_symbol != "-"
66
+ and self.query_position is not None
67
+ )
68
+ else:
69
+ valid = (
70
+ self.query_symbol != "-"
71
+ and self.target_symbol != "-"
72
+ and self.query_position is not None
73
+ and self.target_position is not None
74
+ and ((self.query_symbol == self.target_symbol) == (self.operation == "match"))
75
+ )
76
+ if not valid:
77
+ raise ConfigurationError("Alignment column fields conflict with its operation.")
78
+
79
+
80
+ @dataclass(frozen=True, slots=True)
81
+ class AlignmentResult:
82
+ """Traceable global or local alignment using alignment-only '-' symbols."""
83
+
84
+ name: str
85
+ method: Literal["global", "local", "semi_global"]
86
+ algorithm_version: str
87
+ score: float
88
+ aligned_query: str
89
+ aligned_target: str
90
+ query_id: str | None
91
+ target_id: str | None
92
+ query_start: int
93
+ query_end: int
94
+ target_start: int
95
+ target_end: int
96
+ matches: int
97
+ mismatches: int
98
+ insertions: int
99
+ deletions: int
100
+ identity: float | None
101
+ query_coverage: float
102
+ target_coverage: float
103
+ columns: tuple[AlignmentColumn, ...]
104
+ parameters: FrozenDict
105
+ provenance: Provenance = field(default_factory=_alignment_provenance)
106
+ issues: tuple[Issue, ...] = ()
107
+
108
+ def __post_init__(self) -> None:
109
+ for name in ("name", "algorithm_version"):
110
+ value = getattr(self, name)
111
+ if not isinstance(value, str) or not value.strip():
112
+ raise ConfigurationError(f"Alignment {name} must be non-empty.")
113
+ if self.method not in ("global", "local", "semi_global"):
114
+ raise ConfigurationError("Alignment method must be global, local, or semi_global.")
115
+ for name in ("query_id", "target_id"):
116
+ value = getattr(self, name)
117
+ if value is not None and (not isinstance(value, str) or not value.strip()):
118
+ raise ConfigurationError(f"Alignment {name} must be non-empty or None.")
119
+ if (
120
+ isinstance(self.score, bool)
121
+ or not isinstance(self.score, (int, float))
122
+ or not math.isfinite(self.score)
123
+ ):
124
+ raise ConfigurationError("Alignment score must be finite.")
125
+ if not isinstance(self.aligned_query, str) or not isinstance(self.aligned_target, str):
126
+ raise ConfigurationError("Aligned values must be strings.")
127
+ if len(self.aligned_query) != len(self.aligned_target):
128
+ raise ConfigurationError("Aligned strings must have equal lengths.")
129
+ if not isinstance(self.columns, tuple) or any(
130
+ not isinstance(column, AlignmentColumn) for column in self.columns
131
+ ):
132
+ raise ConfigurationError("Alignment columns must be AlignmentColumn objects.")
133
+ if len(self.columns) != len(self.aligned_query) or any(
134
+ column.query_symbol != query_symbol or column.target_symbol != target_symbol
135
+ for column, query_symbol, target_symbol in zip(
136
+ self.columns, self.aligned_query, self.aligned_target, strict=True
137
+ )
138
+ ):
139
+ raise ConfigurationError("Alignment columns must match aligned-string length.")
140
+ for name in ("query_start", "query_end", "target_start", "target_end"):
141
+ value = getattr(self, name)
142
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
143
+ raise ConfigurationError(f"Alignment {name} must be non-negative.")
144
+ if self.query_end < self.query_start or self.target_end < self.target_start:
145
+ raise ConfigurationError("Alignment source coordinates must be ordered.")
146
+ for name in ("matches", "mismatches", "insertions", "deletions"):
147
+ value = getattr(self, name)
148
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
149
+ raise ConfigurationError(f"Alignment {name} must be non-negative.")
150
+ counts = {
151
+ "match": self.matches,
152
+ "mismatch": self.mismatches,
153
+ "insertion": self.insertions,
154
+ "deletion": self.deletions,
155
+ }
156
+ if any(
157
+ sum(column.operation == operation for column in self.columns) != count
158
+ for operation, count in counts.items()
159
+ ):
160
+ raise ConfigurationError("Alignment operation counts do not match its columns.")
161
+ if not 0.0 <= self.query_coverage <= 1.0 or not 0.0 <= self.target_coverage <= 1.0:
162
+ raise ConfigurationError("Alignment coverage must be between zero and one.")
163
+ if self.identity is not None and not 0.0 <= self.identity <= 1.0:
164
+ raise ConfigurationError("Alignment identity must be between zero and one.")
165
+ expected_identity = self.matches / len(self.columns) if self.columns else None
166
+ if self.identity is None:
167
+ if expected_identity is not None:
168
+ raise ConfigurationError("Non-empty alignment must report identity.")
169
+ elif expected_identity is None or not math.isclose(self.identity, expected_identity):
170
+ raise ConfigurationError("Alignment identity does not match its columns.")
171
+ if not isinstance(self.parameters, FrozenDict):
172
+ raise ConfigurationError("Alignment parameters must be FrozenDict.")
173
+ if not isinstance(self.provenance, Provenance):
174
+ raise ConfigurationError("Alignment provenance must be Provenance.")
175
+ if not isinstance(self.issues, tuple) or any(
176
+ not isinstance(issue, Issue) for issue in self.issues
177
+ ):
178
+ raise ConfigurationError("Alignment issues must contain Issue objects.")
179
+
180
+ @property
181
+ def alignment_length(self) -> int:
182
+ return len(self.columns)
183
+
184
+ def to_dict(self) -> dict[str, Any]:
185
+ return cast(dict[str, Any], to_json_compatible(self))
186
+
187
+
188
+ __all__ = ["AlignmentColumn", "AlignmentResult"]
@@ -0,0 +1,80 @@
1
+ """Lazy optional-backend registration without import-time probing."""
2
+
3
+ from dnakit.backends.external import (
4
+ EXTERNAL_CLI_ADAPTERS,
5
+ BoundedCommandResult,
6
+ ExternalCLIAdapter,
7
+ execute_bounded_command,
8
+ )
9
+ from dnakit.backends.registry import BackendRegistration, BackendRegistry, backend_registry
10
+ from dnakit.core import BackendInfo
11
+
12
+
13
+ def _probe_primer3_cli() -> BackendInfo:
14
+ from dnakit.thermodynamics import probe_primer3
15
+
16
+ return probe_primer3()
17
+
18
+
19
+ def _load_primer3_cli() -> object:
20
+ from dnakit.thermodynamics import Primer3CLIAdapter
21
+
22
+ return Primer3CLIAdapter()
23
+
24
+
25
+ def _probe_nupack() -> BackendInfo:
26
+ from dnakit.secondary_structure import probe_nupack
27
+
28
+ return probe_nupack()
29
+
30
+
31
+ def _load_nupack() -> object:
32
+ from dnakit.secondary_structure import NupackAdapter
33
+
34
+ return NupackAdapter()
35
+
36
+
37
+ def _external_registration(adapter: ExternalCLIAdapter) -> BackendRegistration:
38
+ def load() -> object:
39
+ return adapter
40
+
41
+ return BackendRegistration(
42
+ adapter.backend_id,
43
+ adapter.probe,
44
+ load,
45
+ source="builtin:external-cli",
46
+ )
47
+
48
+
49
+ def _register_builtin_backends() -> None:
50
+ existing = set(backend_registry)
51
+ for registration in (
52
+ BackendRegistration(
53
+ "primer3-cli",
54
+ _probe_primer3_cli,
55
+ _load_primer3_cli,
56
+ source="builtin:thermodynamics",
57
+ ),
58
+ BackendRegistration(
59
+ "nupack",
60
+ _probe_nupack,
61
+ _load_nupack,
62
+ source="builtin:secondary-structure",
63
+ ),
64
+ *(_external_registration(adapter) for adapter in EXTERNAL_CLI_ADAPTERS),
65
+ ):
66
+ if registration.backend_id not in existing:
67
+ backend_registry.register(registration)
68
+
69
+
70
+ _register_builtin_backends()
71
+
72
+ __all__ = [
73
+ "EXTERNAL_CLI_ADAPTERS",
74
+ "BackendRegistration",
75
+ "BackendRegistry",
76
+ "BoundedCommandResult",
77
+ "ExternalCLIAdapter",
78
+ "backend_registry",
79
+ "execute_bounded_command",
80
+ ]