encode-toolkit 0.3.0b1__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.
@@ -0,0 +1,332 @@
1
+ """Pydantic models for ENCODE API response objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class ExperimentSummary(BaseModel):
9
+ """Condensed experiment info returned from search results."""
10
+
11
+ accession: str
12
+ assay_title: str = ""
13
+ target: str = ""
14
+ biosample_summary: str = ""
15
+ organism: str = ""
16
+ organ: str = ""
17
+ biosample_type: str = ""
18
+ status: str = ""
19
+ date_released: str = ""
20
+ description: str = ""
21
+ lab: str = ""
22
+ file_count: int = 0
23
+ replication_type: str = ""
24
+ life_stage: str = ""
25
+ assembly: list[str] = Field(default_factory=list)
26
+ audit_error_count: int = 0
27
+ audit_warning_count: int = 0
28
+ dbxrefs: list[str] = Field(default_factory=list)
29
+ url: str = ""
30
+
31
+ @classmethod
32
+ def from_api(cls, data: dict) -> ExperimentSummary:
33
+ """Parse from ENCODE API experiment object (frame=object)."""
34
+ # Extract target label
35
+ target = ""
36
+ if data.get("target"):
37
+ t = data["target"]
38
+ if isinstance(t, str):
39
+ # Reference path like /targets/H3K27me3-human/
40
+ target = t.strip("/").split("/")[-1] if "/" in t else t
41
+ elif isinstance(t, dict):
42
+ target = t.get("label", "")
43
+
44
+ # Extract lab
45
+ lab = ""
46
+ if data.get("lab"):
47
+ lab_raw = data["lab"]
48
+ if isinstance(lab_raw, str):
49
+ lab = lab_raw.strip("/").split("/")[-1]
50
+ elif isinstance(lab_raw, dict):
51
+ lab = lab_raw.get("title", lab_raw.get("name", ""))
52
+
53
+ # Extract biosample type from ontology
54
+ biosample_type = ""
55
+ organ = ""
56
+ if data.get("biosample_ontology"):
57
+ ont = data["biosample_ontology"]
58
+ if isinstance(ont, dict):
59
+ biosample_type = ont.get("classification", "")
60
+ organ_slims = ont.get("organ_slims", [])
61
+ organ = ", ".join(organ_slims) if organ_slims else ""
62
+ elif isinstance(ont, str):
63
+ biosample_type = ont
64
+
65
+ # Extract assembly list from files
66
+ assemblies = sorted(
67
+ set(f.get("assembly", "") for f in data.get("files", []) if isinstance(f, dict) and f.get("assembly"))
68
+ )
69
+
70
+ # Extract audit counts
71
+ audit = data.get("audit", {})
72
+ audit_errors = len(audit.get("ERROR", [])) if isinstance(audit, dict) else 0
73
+ audit_warnings = len(audit.get("WARNING", [])) if isinstance(audit, dict) else 0
74
+
75
+ # Extract dbxrefs (GEO accessions, etc.)
76
+ dbxrefs = data.get("dbxrefs", []) or []
77
+
78
+ accession_val = data.get("accession", "")
79
+
80
+ return cls(
81
+ accession=accession_val,
82
+ assay_title=data.get("assay_title", ""),
83
+ target=target,
84
+ biosample_summary=data.get("biosample_summary", ""),
85
+ organism=data.get("organism", {}).get("scientific_name", "")
86
+ if isinstance(data.get("organism"), dict)
87
+ else "",
88
+ organ=organ,
89
+ biosample_type=biosample_type,
90
+ status=data.get("status", ""),
91
+ date_released=data.get("date_released", ""),
92
+ description=data.get("description", ""),
93
+ lab=lab,
94
+ file_count=len(data.get("files", [])),
95
+ replication_type=data.get("replication_type", ""),
96
+ life_stage=data.get("life_stage_age", ""),
97
+ assembly=assemblies,
98
+ audit_error_count=audit_errors,
99
+ audit_warning_count=audit_warnings,
100
+ dbxrefs=dbxrefs,
101
+ url=f"https://www.encodeproject.org/experiments/{accession_val}/" if accession_val else "",
102
+ )
103
+
104
+
105
+ class FileSummary(BaseModel):
106
+ """Condensed file info from search/listing results."""
107
+
108
+ accession: str
109
+ file_format: str = ""
110
+ file_type: str = ""
111
+ output_type: str = ""
112
+ output_category: str = ""
113
+ file_size: int = 0
114
+ file_size_human: str = ""
115
+ assembly: str = ""
116
+ biological_replicates: list[int] = Field(default_factory=list)
117
+ technical_replicates: list[str] = Field(default_factory=list)
118
+ status: str = ""
119
+ download_url: str = ""
120
+ s3_uri: str = ""
121
+ md5sum: str = ""
122
+ experiment_accession: str = ""
123
+ experiment_assay: str = ""
124
+ biosample_summary: str = ""
125
+ preferred_default: bool = False
126
+ date_created: str = ""
127
+
128
+ @classmethod
129
+ def from_api(cls, data: dict, base_url: str = "https://www.encodeproject.org") -> FileSummary:
130
+ """Parse from ENCODE API file object."""
131
+ href = data.get("href", "")
132
+ download_url = f"{base_url}{href}" if href and not href.startswith("http") else href
133
+
134
+ file_size = data.get("file_size", 0) or 0
135
+
136
+ # Extract experiment accession from dataset path
137
+ experiment_accession = ""
138
+ dataset = data.get("dataset", "")
139
+ if isinstance(dataset, str) and "/experiments/" in dataset:
140
+ experiment_accession = dataset.strip("/").split("/")[-1]
141
+ elif isinstance(dataset, dict):
142
+ experiment_accession = dataset.get("accession", "")
143
+
144
+ return cls(
145
+ accession=data.get("accession", ""),
146
+ file_format=data.get("file_format", ""),
147
+ file_type=data.get("file_type", ""),
148
+ output_type=data.get("output_type", ""),
149
+ output_category=data.get("output_category", ""),
150
+ file_size=file_size,
151
+ file_size_human=_human_size(file_size),
152
+ assembly=data.get("assembly", "") or "",
153
+ biological_replicates=data.get("biological_replicates", []),
154
+ technical_replicates=data.get("technical_replicates", []),
155
+ status=data.get("status", ""),
156
+ download_url=download_url,
157
+ s3_uri=data.get("s3_uri", "") or "",
158
+ md5sum=data.get("md5sum", "") or "",
159
+ experiment_accession=experiment_accession,
160
+ experiment_assay=data.get("assay_title", ""),
161
+ biosample_summary=data.get("biosample_summary", "") or "",
162
+ preferred_default=data.get("preferred_default", False) or False,
163
+ date_created=data.get("date_created", ""),
164
+ )
165
+
166
+
167
+ def _extract_organism(data: dict) -> str:
168
+ """Extract organism from nested ENCODE API data."""
169
+ # Direct organism field (frame=object)
170
+ org = data.get("organism")
171
+ if isinstance(org, dict):
172
+ return org.get("scientific_name", "")
173
+ if isinstance(org, str) and org:
174
+ return org.strip("/").split("/")[-1]
175
+ # Try replicates path (frame=embedded)
176
+ for rep in data.get("replicates", []):
177
+ if isinstance(rep, dict):
178
+ lib = rep.get("library", {})
179
+ if isinstance(lib, dict):
180
+ bs = lib.get("biosample", {})
181
+ if isinstance(bs, dict):
182
+ donor = bs.get("donor", {})
183
+ if isinstance(donor, dict):
184
+ org2 = donor.get("organism", {})
185
+ if isinstance(org2, dict):
186
+ name = org2.get("scientific_name", "")
187
+ if name:
188
+ return name
189
+ return ""
190
+
191
+
192
+ class ExperimentDetail(BaseModel):
193
+ """Full experiment details including files list."""
194
+
195
+ accession: str
196
+ assay_title: str = ""
197
+ assay_term_name: str = ""
198
+ target: str = ""
199
+ biosample_summary: str = ""
200
+ description: str = ""
201
+ status: str = ""
202
+ date_released: str = ""
203
+ lab: str = ""
204
+ award: str = ""
205
+ organism: str = ""
206
+ organ: str = ""
207
+ biosample_type: str = ""
208
+ life_stage: str = ""
209
+ replication_type: str = ""
210
+ bio_replicate_count: int = 0
211
+ tech_replicate_count: int = 0
212
+ possible_controls: list[str] = Field(default_factory=list)
213
+ related_series: list[str] = Field(default_factory=list)
214
+ documents: list[str] = Field(default_factory=list)
215
+ url: str = ""
216
+ files: list[FileSummary] = Field(default_factory=list)
217
+
218
+ # Quality/audit info
219
+ audit_error_count: int = 0
220
+ audit_warning_count: int = 0
221
+
222
+ @classmethod
223
+ def from_api(cls, data: dict, files: list[dict] | None = None) -> ExperimentDetail:
224
+ """Parse from ENCODE API experiment object (frame=embedded preferred)."""
225
+ # Extract target
226
+ target = ""
227
+ if data.get("target"):
228
+ t = data["target"]
229
+ if isinstance(t, str):
230
+ target = t.strip("/").split("/")[-1]
231
+ elif isinstance(t, dict):
232
+ target = t.get("label", "")
233
+
234
+ # Extract lab
235
+ lab = ""
236
+ if data.get("lab"):
237
+ lab_raw = data["lab"]
238
+ if isinstance(lab_raw, str):
239
+ lab = lab_raw.strip("/").split("/")[-1]
240
+ elif isinstance(lab_raw, dict):
241
+ lab = lab_raw.get("title", lab_raw.get("name", ""))
242
+
243
+ # Extract award/project
244
+ award = ""
245
+ if data.get("award"):
246
+ a = data["award"]
247
+ if isinstance(a, str):
248
+ award = a.strip("/").split("/")[-1]
249
+ elif isinstance(a, dict):
250
+ award = a.get("project", a.get("name", ""))
251
+
252
+ # Extract biosample details
253
+ biosample_type = ""
254
+ organ = ""
255
+ if data.get("biosample_ontology"):
256
+ ont = data["biosample_ontology"]
257
+ if isinstance(ont, dict):
258
+ biosample_type = ont.get("classification", "")
259
+ organ_slims = ont.get("organ_slims", [])
260
+ organ = ", ".join(organ_slims) if organ_slims else ""
261
+
262
+ # Controls
263
+ controls = []
264
+ for ctrl in data.get("possible_controls", []):
265
+ if isinstance(ctrl, str):
266
+ controls.append(ctrl.strip("/").split("/")[-1])
267
+ elif isinstance(ctrl, dict):
268
+ controls.append(ctrl.get("accession", ""))
269
+
270
+ # Audit counts
271
+ audit = data.get("audit", {})
272
+ error_count = len(audit.get("ERROR", [])) if isinstance(audit, dict) else 0
273
+ warning_count = len(audit.get("WARNING", [])) if isinstance(audit, dict) else 0
274
+
275
+ # Parse files
276
+ file_summaries = []
277
+ if files:
278
+ file_summaries = [FileSummary.from_api(f) for f in files]
279
+
280
+ return cls(
281
+ accession=data.get("accession", ""),
282
+ assay_title=data.get("assay_title", ""),
283
+ assay_term_name=data.get("assay_term_name", ""),
284
+ target=target,
285
+ biosample_summary=data.get("biosample_summary", ""),
286
+ description=data.get("description", ""),
287
+ status=data.get("status", ""),
288
+ date_released=data.get("date_released", ""),
289
+ lab=lab,
290
+ award=award,
291
+ organism=_extract_organism(data),
292
+ organ=organ,
293
+ biosample_type=biosample_type,
294
+ life_stage=data.get("life_stage_age", ""),
295
+ replication_type=data.get("replication_type", ""),
296
+ bio_replicate_count=data.get("bio_replicate_count", 0) or 0,
297
+ tech_replicate_count=data.get("tech_replicate_count", 0) or 0,
298
+ possible_controls=controls,
299
+ related_series=[
300
+ s if isinstance(s, str) else s.get("accession", "") for s in data.get("related_series", [])
301
+ ],
302
+ documents=[d if isinstance(d, str) else d.get("@id", "") for d in data.get("documents", [])],
303
+ url=f"https://www.encodeproject.org/experiments/{data.get('accession', '')}/",
304
+ files=file_summaries,
305
+ audit_error_count=error_count,
306
+ audit_warning_count=warning_count,
307
+ )
308
+
309
+
310
+ class DownloadResult(BaseModel):
311
+ """Result of a file download operation."""
312
+
313
+ accession: str
314
+ file_path: str = ""
315
+ file_size: int = 0
316
+ file_size_human: str = ""
317
+ success: bool = False
318
+ error: str = ""
319
+ md5_verified: bool = False
320
+
321
+
322
+ def _human_size(size_bytes: int) -> str:
323
+ """Convert bytes to human-readable string."""
324
+ if size_bytes == 0:
325
+ return "0 B"
326
+ units = ["B", "KB", "MB", "GB", "TB"]
327
+ i = 0
328
+ size = float(size_bytes)
329
+ while size >= 1024 and i < len(units) - 1:
330
+ size /= 1024
331
+ i += 1
332
+ return f"{size:.1f} {units[i]}"