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
dnakit/io/config.py ADDED
@@ -0,0 +1,315 @@
1
+ """Configuration value objects for DNA record I/O."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import codecs
6
+ import sys
7
+ from dataclasses import dataclass
8
+ from typing import Literal
9
+
10
+ from dnakit.core.enums import DNAAlphabet, Strandedness, Topology
11
+ from dnakit.exceptions import ConfigurationError
12
+
13
+ CompressionMode = Literal["auto", "none", "gzip"]
14
+ AnonymousIDPolicy = Literal["generate", "error"]
15
+ FeatureWritePolicy = Literal["error", "drop"]
16
+
17
+
18
+ def _non_empty(value: str, field_name: str) -> None:
19
+ if not isinstance(value, str) or not value.strip():
20
+ raise ConfigurationError(
21
+ f"{field_name} must be a non-empty string.",
22
+ code="INVALID_IO_CONFIG",
23
+ context={"field": field_name},
24
+ )
25
+
26
+
27
+ def _validate_encoding(value: str) -> None:
28
+ _non_empty(value, "encoding")
29
+ try:
30
+ codecs.lookup(value)
31
+ except LookupError as exc:
32
+ raise ConfigurationError(
33
+ "encoding must name a registered Python text codec.",
34
+ code="INVALID_IO_ENCODING",
35
+ context={"encoding": value},
36
+ ) from exc
37
+
38
+
39
+ @dataclass(frozen=True, slots=True)
40
+ class ReadConfig:
41
+ """Control decoding without silently relaxing sequence validation.
42
+
43
+ ``close_source=None`` applies ownership-aware behavior: paths are owned and
44
+ closed by :class:`~dnakit.io.RecordSource`, while caller-provided streams
45
+ are borrowed and left open.
46
+ """
47
+
48
+ encoding: str = "utf-8"
49
+ alphabet: DNAAlphabet = DNAAlphabet.IUPAC
50
+ topology: Topology = Topology.LINEAR
51
+ strandedness: Strandedness = Strandedness.SINGLE
52
+ uppercase: bool = True
53
+ phred_offset: int = 33
54
+ compression: CompressionMode = "auto"
55
+ close_source: bool | None = None
56
+ delimiter: str | None = None
57
+ id_column: str = "id"
58
+ sequence_column: str = "sequence"
59
+ description_column: str = "description"
60
+ metadata_column: str = "metadata"
61
+ letter_annotations_column: str = "letter_annotations"
62
+ features_column: str = "features"
63
+ parts_column: str = "parts"
64
+ max_field_size: int = 10_000_000
65
+ max_records: int = 1_000_000
66
+ max_record_lines: int = 1_000_000
67
+ max_sequence_symbols: int = 100_000_000
68
+ max_input_bytes: int = 1_000_000_000
69
+ max_json_depth: int = 100
70
+ max_json_nodes: int = 1_000_000
71
+ anonymous_id_prefix: str = "sequence_"
72
+
73
+ def __post_init__(self) -> None:
74
+ try:
75
+ alphabet = (
76
+ self.alphabet
77
+ if isinstance(self.alphabet, DNAAlphabet)
78
+ else DNAAlphabet(self.alphabet)
79
+ )
80
+ topology = (
81
+ self.topology if isinstance(self.topology, Topology) else Topology(self.topology)
82
+ )
83
+ strandedness = (
84
+ self.strandedness
85
+ if isinstance(self.strandedness, Strandedness)
86
+ else Strandedness(self.strandedness)
87
+ )
88
+ except (TypeError, ValueError) as exc:
89
+ raise ConfigurationError(
90
+ "Unknown DNA alphabet, topology, or strandedness for input.",
91
+ code="INVALID_IO_SEQUENCE_TYPE",
92
+ context={
93
+ "alphabet": self.alphabet,
94
+ "topology": self.topology,
95
+ "strandedness": self.strandedness,
96
+ },
97
+ ) from exc
98
+ if self.compression not in {"auto", "none", "gzip"}:
99
+ raise ConfigurationError(
100
+ "compression must be 'auto', 'none', or 'gzip'.",
101
+ code="INVALID_IO_COMPRESSION",
102
+ )
103
+ if (
104
+ isinstance(self.phred_offset, bool)
105
+ or not isinstance(self.phred_offset, int)
106
+ or not 33 <= self.phred_offset <= 64
107
+ ):
108
+ raise ConfigurationError(
109
+ "phred_offset must be an integer between 33 and 64.",
110
+ code="INVALID_PHRED_OFFSET",
111
+ )
112
+ if self.delimiter is not None and (
113
+ not isinstance(self.delimiter, str)
114
+ or len(self.delimiter) != 1
115
+ or self.delimiter in {"\x00", "\r", "\n", '"'}
116
+ ):
117
+ raise ConfigurationError(
118
+ "delimiter must be None or one character distinct from "
119
+ "NUL, line breaks, and the quote character.",
120
+ code="INVALID_TABLE_DELIMITER",
121
+ )
122
+ if not isinstance(self.uppercase, bool):
123
+ raise ConfigurationError(
124
+ "uppercase must be a boolean.",
125
+ code="INVALID_IO_CONFIG",
126
+ context={"field": "uppercase"},
127
+ )
128
+ if self.close_source is not None and not isinstance(self.close_source, bool):
129
+ raise ConfigurationError(
130
+ "close_source must be a boolean or None.",
131
+ code="INVALID_IO_CONFIG",
132
+ context={"field": "close_source"},
133
+ )
134
+ for field_name in (
135
+ "encoding",
136
+ "id_column",
137
+ "sequence_column",
138
+ "description_column",
139
+ "metadata_column",
140
+ "letter_annotations_column",
141
+ "features_column",
142
+ "parts_column",
143
+ "anonymous_id_prefix",
144
+ ):
145
+ _non_empty(getattr(self, field_name), field_name)
146
+ columns = (
147
+ self.id_column,
148
+ self.sequence_column,
149
+ self.description_column,
150
+ self.metadata_column,
151
+ self.letter_annotations_column,
152
+ self.features_column,
153
+ self.parts_column,
154
+ )
155
+ if len(set(columns)) != len(columns):
156
+ raise ConfigurationError(
157
+ "Configured table column names must be unique.",
158
+ code="DUPLICATE_TABLE_COLUMN",
159
+ )
160
+ _validate_encoding(self.encoding)
161
+ if any(symbol.isspace() for symbol in self.anonymous_id_prefix):
162
+ raise ConfigurationError(
163
+ "anonymous_id_prefix must not contain whitespace.",
164
+ code="INVALID_ANONYMOUS_ID_PREFIX",
165
+ )
166
+ if (
167
+ isinstance(self.max_field_size, bool)
168
+ or not isinstance(self.max_field_size, int)
169
+ or not 1 <= self.max_field_size <= sys.maxsize
170
+ ):
171
+ raise ConfigurationError(
172
+ "max_field_size must be an integer between 1 and sys.maxsize.",
173
+ code="INVALID_CSV_FIELD_SIZE_LIMIT",
174
+ )
175
+ for field_name in (
176
+ "max_records",
177
+ "max_record_lines",
178
+ "max_sequence_symbols",
179
+ "max_input_bytes",
180
+ "max_json_depth",
181
+ "max_json_nodes",
182
+ ):
183
+ value = getattr(self, field_name)
184
+ if isinstance(value, bool) or not isinstance(value, int) or value < 1:
185
+ raise ConfigurationError(
186
+ f"{field_name} must be a positive integer.",
187
+ code="INVALID_IO_RESOURCE_LIMIT",
188
+ context={"field": field_name, "value": value},
189
+ )
190
+ object.__setattr__(self, "alphabet", alphabet)
191
+ object.__setattr__(self, "topology", topology)
192
+ object.__setattr__(self, "strandedness", strandedness)
193
+
194
+
195
+ @dataclass(frozen=True, slots=True)
196
+ class WriteConfig:
197
+ """Control deterministic record serialization and destination ownership."""
198
+
199
+ encoding: str = "utf-8"
200
+ overwrite: bool = False
201
+ create_parents: bool = False
202
+ compression: CompressionMode = "auto"
203
+ compression_level: int = 6
204
+ close_target: bool | None = None
205
+ line_width: int = 80
206
+ phred_offset: int = 33
207
+ delimiter: str | None = None
208
+ anonymous_id_policy: AnonymousIDPolicy = "generate"
209
+ anonymous_id_prefix: str = "sequence_"
210
+ feature_policy: FeatureWritePolicy = "error"
211
+ json_indent: int | None = None
212
+ max_output_bytes: int | None = None
213
+
214
+ def __post_init__(self) -> None:
215
+ if self.compression not in {"auto", "none", "gzip"}:
216
+ raise ConfigurationError(
217
+ "compression must be 'auto', 'none', or 'gzip'.",
218
+ code="INVALID_IO_COMPRESSION",
219
+ )
220
+ if self.anonymous_id_policy not in {"generate", "error"}:
221
+ raise ConfigurationError(
222
+ "anonymous_id_policy must be 'generate' or 'error'.",
223
+ code="INVALID_ANONYMOUS_ID_POLICY",
224
+ )
225
+ if self.feature_policy not in {"error", "drop"}:
226
+ raise ConfigurationError(
227
+ "feature_policy must be 'error' or 'drop'.",
228
+ code="INVALID_FEATURE_WRITE_POLICY",
229
+ )
230
+ if (
231
+ isinstance(self.compression_level, bool)
232
+ or not isinstance(self.compression_level, int)
233
+ or not 0 <= self.compression_level <= 9
234
+ ):
235
+ raise ConfigurationError(
236
+ "compression_level must be an integer between 0 and 9.",
237
+ code="INVALID_COMPRESSION_LEVEL",
238
+ )
239
+ if (
240
+ isinstance(self.line_width, bool)
241
+ or not isinstance(self.line_width, int)
242
+ or self.line_width < 1
243
+ ):
244
+ raise ConfigurationError(
245
+ "line_width must be a positive integer.",
246
+ code="INVALID_FASTA_LINE_WIDTH",
247
+ )
248
+ if (
249
+ isinstance(self.phred_offset, bool)
250
+ or not isinstance(self.phred_offset, int)
251
+ or not 33 <= self.phred_offset <= 64
252
+ ):
253
+ raise ConfigurationError(
254
+ "phred_offset must be an integer between 33 and 64.",
255
+ code="INVALID_PHRED_OFFSET",
256
+ )
257
+ if self.delimiter is not None and (
258
+ not isinstance(self.delimiter, str)
259
+ or len(self.delimiter) != 1
260
+ or self.delimiter in {"\x00", "\r", "\n", '"'}
261
+ ):
262
+ raise ConfigurationError(
263
+ "delimiter must be None or one character distinct from "
264
+ "NUL, line breaks, and the quote character.",
265
+ code="INVALID_TABLE_DELIMITER",
266
+ )
267
+ for field_name in ("overwrite", "create_parents"):
268
+ value = getattr(self, field_name)
269
+ if not isinstance(value, bool):
270
+ raise ConfigurationError(
271
+ f"{field_name} must be a boolean.",
272
+ code="INVALID_IO_CONFIG",
273
+ context={"field": field_name},
274
+ )
275
+ if self.close_target is not None and not isinstance(self.close_target, bool):
276
+ raise ConfigurationError(
277
+ "close_target must be a boolean or None.",
278
+ code="INVALID_IO_CONFIG",
279
+ context={"field": "close_target"},
280
+ )
281
+ if self.json_indent is not None and (
282
+ isinstance(self.json_indent, bool)
283
+ or not isinstance(self.json_indent, int)
284
+ or self.json_indent < 0
285
+ ):
286
+ raise ConfigurationError(
287
+ "json_indent must be a non-negative integer or None.",
288
+ code="INVALID_JSON_INDENT",
289
+ )
290
+ if self.max_output_bytes is not None and (
291
+ isinstance(self.max_output_bytes, bool)
292
+ or not isinstance(self.max_output_bytes, int)
293
+ or self.max_output_bytes < 1
294
+ ):
295
+ raise ConfigurationError(
296
+ "max_output_bytes must be a positive integer or None.",
297
+ code="INVALID_IO_RESOURCE_LIMIT",
298
+ context={"field": "max_output_bytes", "value": self.max_output_bytes},
299
+ )
300
+ _validate_encoding(self.encoding)
301
+ _non_empty(self.anonymous_id_prefix, "anonymous_id_prefix")
302
+ if any(symbol.isspace() for symbol in self.anonymous_id_prefix):
303
+ raise ConfigurationError(
304
+ "anonymous_id_prefix must not contain whitespace.",
305
+ code="INVALID_ANONYMOUS_ID_PREFIX",
306
+ )
307
+
308
+
309
+ __all__ = [
310
+ "AnonymousIDPolicy",
311
+ "CompressionMode",
312
+ "FeatureWritePolicy",
313
+ "ReadConfig",
314
+ "WriteConfig",
315
+ ]