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.
- encode_connector/__init__.py +4 -0
- encode_connector/__main__.py +5 -0
- encode_connector/client/__init__.py +6 -0
- encode_connector/client/auth.py +262 -0
- encode_connector/client/constants.py +348 -0
- encode_connector/client/downloader.py +305 -0
- encode_connector/client/encode_client.py +585 -0
- encode_connector/client/models.py +332 -0
- encode_connector/client/tracker.py +1129 -0
- encode_connector/client/validation.py +188 -0
- encode_connector/server/__init__.py +1 -0
- encode_connector/server/__main__.py +5 -0
- encode_connector/server/main.py +1495 -0
- encode_toolkit-0.3.0b1.dist-info/METADATA +810 -0
- encode_toolkit-0.3.0b1.dist-info/RECORD +18 -0
- encode_toolkit-0.3.0b1.dist-info/WHEEL +4 -0
- encode_toolkit-0.3.0b1.dist-info/entry_points.txt +2 -0
- encode_toolkit-0.3.0b1.dist-info/licenses/LICENSE +144 -0
|
@@ -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]}"
|