bindsight 0.3.5__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 (106) hide show
  1. bindsight/README.md +44 -0
  2. bindsight/__init__.py +15 -0
  3. bindsight/benchmark/__init__.py +71 -0
  4. bindsight/benchmark/core.py +497 -0
  5. bindsight/benchmark/defaults.py +28 -0
  6. bindsight/benchmark/designer_bench.py +899 -0
  7. bindsight/benchmark/outcomes.py +508 -0
  8. bindsight/benchmark/panel.py +621 -0
  9. bindsight/benchmark/rediscovery.py +1153 -0
  10. bindsight/benchmark/statistics.py +622 -0
  11. bindsight/benchmark/study.py +1153 -0
  12. bindsight/benchmark/study_figures.py +164 -0
  13. bindsight/benchmark/study_report.py +607 -0
  14. bindsight/cli.py +2429 -0
  15. bindsight/config.py +451 -0
  16. bindsight/cost.py +258 -0
  17. bindsight/deg/__init__.py +7 -0
  18. bindsight/deg/pydeseq2_runner.py +285 -0
  19. bindsight/design/__init__.py +23 -0
  20. bindsight/design/_common.py +501 -0
  21. bindsight/design/bindcraft.py +78 -0
  22. bindsight/design/boltzgen.py +79 -0
  23. bindsight/design/developability.py +140 -0
  24. bindsight/design/embeddings.py +165 -0
  25. bindsight/design/prescreen.py +169 -0
  26. bindsight/design/protocol.py +111 -0
  27. bindsight/design/rfdiff_mpnn.py +93 -0
  28. bindsight/epitopes/__init__.py +7 -0
  29. bindsight/epitopes/surface_bind.py +134 -0
  30. bindsight/export/__init__.py +7 -0
  31. bindsight/export/ro_crate.py +595 -0
  32. bindsight/io/__init__.py +19 -0
  33. bindsight/io/cbioportal.py +118 -0
  34. bindsight/io/gdc.py +343 -0
  35. bindsight/io/paths.py +124 -0
  36. bindsight/pipelines/__init__.py +9 -0
  37. bindsight/pipelines/caveats.py +60 -0
  38. bindsight/pipelines/discover.py +1443 -0
  39. bindsight/pipelines/full_run.py +410 -0
  40. bindsight/plugins.py +335 -0
  41. bindsight/provenance/__init__.py +34 -0
  42. bindsight/provenance/append.py +199 -0
  43. bindsight/provenance/fragments.py +216 -0
  44. bindsight/provenance/manifest.py +506 -0
  45. bindsight/rank/__init__.py +7 -0
  46. bindsight/rank/scoring.py +355 -0
  47. bindsight/report/__init__.py +39 -0
  48. bindsight/report/format.py +100 -0
  49. bindsight/report/html.py +645 -0
  50. bindsight/report/showcase.py +653 -0
  51. bindsight/report/templates/report.css +67 -0
  52. bindsight/report/templates/report.html.j2 +420 -0
  53. bindsight/report/theme.py +231 -0
  54. bindsight/report/web/__init__.py +35 -0
  55. bindsight/report/web/app.py +624 -0
  56. bindsight/report/web/static/binder_viewer.js +245 -0
  57. bindsight/report/web/static/bindsight.css +455 -0
  58. bindsight/report/web/static/charts.js +425 -0
  59. bindsight/report/web/static/vendor/3Dmol-min.js +2 -0
  60. bindsight/report/web/static/vendor/3Dmol.LICENSE +90 -0
  61. bindsight/report/web/static/your_data_check.js +370 -0
  62. bindsight/report/web/templates/base.html.j2 +48 -0
  63. bindsight/report/web/templates/evidence.html.j2 +341 -0
  64. bindsight/report/web/templates/overview.html.j2 +140 -0
  65. bindsight/report/web/templates/runs.html.j2 +125 -0
  66. bindsight/report/web/templates/try.html.j2 +155 -0
  67. bindsight/report/web/templates/your_data.html.j2 +46 -0
  68. bindsight/runners/__init__.py +7 -0
  69. bindsight/runners/colab.py +158 -0
  70. bindsight/runners/job_exec.py +1110 -0
  71. bindsight/runners/kaggle.py +425 -0
  72. bindsight/runners/kaggle_kernel.py +489 -0
  73. bindsight/runners/local_docker.py +154 -0
  74. bindsight/runners/mock.py +167 -0
  75. bindsight/runners/modal_runner.py +199 -0
  76. bindsight/runners/notebook.py +116 -0
  77. bindsight/runners/notebook_content.py +217 -0
  78. bindsight/runners/protocol.py +81 -0
  79. bindsight/runners/source_wheel.py +104 -0
  80. bindsight/runners/tools.py +997 -0
  81. bindsight/structures/__init__.py +7 -0
  82. bindsight/structures/alphafolddb.py +93 -0
  83. bindsight/structures/plddt.py +92 -0
  84. bindsight/structures/topology.py +227 -0
  85. bindsight/surfaceome/__init__.py +42 -0
  86. bindsight/surfaceome/data/surfaceome_extended.tsv +4814 -0
  87. bindsight/surfaceome/data/surfy_offline.txt +17 -0
  88. bindsight/surfaceome/data/surfy_v1.ensembl.tsv +3380 -0
  89. bindsight/surfaceome/data/surfy_v1.uniprot.txt +2901 -0
  90. bindsight/surfaceome/surfy.py +441 -0
  91. bindsight/targets/__init__.py +7 -0
  92. bindsight/targets/ensembl_uniprot.py +56 -0
  93. bindsight/targets/gtex.py +250 -0
  94. bindsight/targets/open_targets.py +239 -0
  95. bindsight/validate/__init__.py +7 -0
  96. bindsight/validate/af2_ig.py +49 -0
  97. bindsight/validate/boltz2.py +256 -0
  98. bindsight/validate/chai1r.py +42 -0
  99. bindsight/validate/protocol.py +188 -0
  100. bindsight-0.3.5.data/data/bindsight_demo/config.yaml +54 -0
  101. bindsight-0.3.5.dist-info/METADATA +495 -0
  102. bindsight-0.3.5.dist-info/RECORD +106 -0
  103. bindsight-0.3.5.dist-info/WHEEL +4 -0
  104. bindsight-0.3.5.dist-info/entry_points.txt +20 -0
  105. bindsight-0.3.5.dist-info/licenses/COPYRIGHT +24 -0
  106. bindsight-0.3.5.dist-info/licenses/LICENSE +681 -0
@@ -0,0 +1,216 @@
1
+ # SPDX-FileCopyrightText: 2026 Mikhaeel Atef Rizk Wahba
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+ """Per-stage provenance fragments, shared by the Snakemake front-end.
4
+
5
+ The Snakemake rules run in separate processes, so they cannot append to a live
6
+ :class:`~bindsight.provenance.Manifest` the way the Click CLI does. Each rule
7
+ writes a fragment instead, and ``scripts/assemble_manifest.py`` stitches them
8
+ into the run manifest.
9
+
10
+ Those fragments used to carry only a stage name, a status and a metrics blob.
11
+ Every Snakemake manifest therefore had empty ``inputs``, empty ``outputs``, no
12
+ sha256 anywhere, no per-stage params, no real timings, and the same placeholder
13
+ tool for all six stages — while ``ARCHITECTURE.md`` claimed the CLI and
14
+ Snakemake front-ends produce identical artifacts, and
15
+ ``assemble_manifest.py``'s own docstring claimed it emitted "the same populated
16
+ provenance artifact the Click CLI does". It did not.
17
+
18
+ This module closes that gap by making both front-ends build the *same*
19
+ :class:`~bindsight.provenance.StageRecord` objects out of the same helpers, so
20
+ a Snakemake run is as auditable as a CLI run.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ import logging
27
+ from pathlib import Path
28
+ from typing import Any, get_args
29
+
30
+ from bindsight.provenance.manifest import (
31
+ InputRef,
32
+ OutputRef,
33
+ StageRecord,
34
+ ToolRef,
35
+ _now_iso,
36
+ sha256_file,
37
+ )
38
+
39
+ #: Media types for the artifact kinds the pipeline emits.
40
+ _MEDIA_TYPES = {
41
+ ".parquet": "application/x-parquet",
42
+ ".tsv": "text/tab-separated-values",
43
+ ".gz": "application/gzip",
44
+ ".jsonl": "application/x-ndjson",
45
+ ".jsonld": "application/ld+json",
46
+ ".json": "application/json",
47
+ ".html": "text/html",
48
+ ".cif": "chemical/x-mmcif",
49
+ ".pdb": "chemical/x-pdb",
50
+ ".yaml": "application/yaml",
51
+ }
52
+
53
+
54
+ LOG = logging.getLogger(__name__)
55
+
56
+
57
+ #: The statuses :class:`~bindsight.provenance.manifest.StageRecord` accepts, read
58
+ #: from the model so the two cannot disagree.
59
+ _STATUS_VALUES: frozenset[str] = frozenset(get_args(StageRecord.model_fields["status"].annotation))
60
+
61
+
62
+ def media_type_for(path: Path | str) -> str | None:
63
+ """Best-effort IANA media type for a pipeline artifact."""
64
+ return _MEDIA_TYPES.get(Path(path).suffix.lower())
65
+
66
+
67
+ def artifact_ref(
68
+ path: Path | str,
69
+ *,
70
+ role: str,
71
+ run_root: Path | str | None = None,
72
+ ) -> dict[str, Any] | None:
73
+ """Describe an artifact the way ``InputRef``/``OutputRef`` require.
74
+
75
+ Args:
76
+ path: The artifact on disk.
77
+ role: Its logical role within the stage (e.g. ``"counts"``).
78
+ run_root: When given, the recorded path is made relative to it, so the
79
+ manifest describes the run rather than the machine.
80
+
81
+ Returns:
82
+ A dict carrying role, path, sha256, bytes and media type, or ``None``
83
+ when the file does not exist.
84
+ """
85
+ p = Path(path)
86
+ if not p.is_file():
87
+ return None
88
+ recorded = p
89
+ if run_root is not None:
90
+ try:
91
+ recorded = p.resolve().relative_to(Path(run_root).resolve())
92
+ except ValueError:
93
+ # Outside the run (e.g. an input cohort); record it as given.
94
+ recorded = p
95
+ return {
96
+ "role": role,
97
+ "path": recorded.as_posix(),
98
+ "sha256": sha256_file(p),
99
+ "bytes": p.stat().st_size,
100
+ "media_type": media_type_for(p),
101
+ }
102
+
103
+
104
+ def write_fragment(
105
+ path: Path | str,
106
+ *,
107
+ stage: str,
108
+ status: str = "completed",
109
+ tool: dict[str, Any] | None = None,
110
+ inputs: list[dict[str, Any] | None] | None = None,
111
+ outputs: list[dict[str, Any] | None] | None = None,
112
+ params: dict[str, Any] | None = None,
113
+ notes: str | None = None,
114
+ started_at: str | None = None,
115
+ error: str | None = None,
116
+ ) -> Path:
117
+ """Write one stage's provenance fragment.
118
+
119
+ ``None`` entries in ``inputs``/``outputs`` are dropped, so callers can pass
120
+ :func:`artifact_ref` results directly without guarding each one.
121
+
122
+ Args:
123
+ path: Fragment destination.
124
+ stage: Stage name, matching the CLI's naming.
125
+ status: Terminal status for the stage.
126
+ tool: Serialised :class:`ToolRef`; defaults to bindsight itself.
127
+ inputs: Artifact refs consumed.
128
+ outputs: Artifact refs produced.
129
+ params: The stage's resolved parameters.
130
+ notes: Human-readable summary.
131
+ started_at: ISO timestamp for the stage start; defaults to now.
132
+ error: Failure detail, when ``status`` is ``"failed"``.
133
+
134
+ Returns:
135
+ The fragment path.
136
+ """
137
+ out = Path(path)
138
+ out.parent.mkdir(parents=True, exist_ok=True)
139
+ payload = {
140
+ "stage": stage,
141
+ "status": status,
142
+ "tool": tool or default_tool(),
143
+ "inputs": [r for r in (inputs or []) if r],
144
+ "outputs": [r for r in (outputs or []) if r],
145
+ "params": params or {},
146
+ "notes": notes,
147
+ "started_at": started_at or _now_iso(),
148
+ "ended_at": _now_iso(),
149
+ "error": error,
150
+ }
151
+ out.write_text(json.dumps(payload, indent=2, default=str), encoding="utf-8", newline="\n")
152
+ return out
153
+
154
+
155
+ def default_tool(**overrides: Any) -> dict[str, Any]:
156
+ """Serialised :class:`ToolRef` for bindsight itself."""
157
+ from bindsight import __version__
158
+
159
+ ref = {
160
+ "name": "bindsight",
161
+ "version": __version__,
162
+ "license": "AGPL-3.0-or-later",
163
+ "repo_url": "https://github.com/mikhaeelatefrizk/bindsight",
164
+ }
165
+ ref.update(overrides)
166
+ return ref
167
+
168
+
169
+ def stage_record_from_fragment(payload: dict[str, Any]) -> StageRecord:
170
+ """Rebuild a full :class:`StageRecord` from a fragment.
171
+
172
+ Tolerates the older, thinner fragment shape (stage/status/metrics only) so
173
+ a part-finished run from a previous version still assembles.
174
+
175
+ Args:
176
+ payload: Parsed fragment JSON.
177
+
178
+ Returns:
179
+ The reconstructed stage record.
180
+ """
181
+ name = str(payload.get("stage") or "unknown")
182
+ status = str(payload.get("status") or "completed")
183
+ # Derived from StageRecord's own Literal rather than restated here. The
184
+ # hand-copied set could fall behind the model, and an unrecognised status
185
+ # being rewritten to "completed" is the worst possible direction for that
186
+ # drift: a stage that failed in a way this file does not know about would be
187
+ # recorded as having succeeded.
188
+ if status not in _STATUS_VALUES:
189
+ LOG.warning(
190
+ "fragment for stage %r carries unrecognised status %r; recording it as "
191
+ "failed rather than completed, because an unknown state is not success",
192
+ name,
193
+ status,
194
+ )
195
+ status = "failed"
196
+
197
+ tool_payload = payload.get("tool") or default_tool()
198
+ notes = payload.get("notes")
199
+ if notes is None and payload.get("metrics") is not None:
200
+ # Legacy fragments carried their only real content here.
201
+ notes = json.dumps({"metrics": payload["metrics"]})
202
+
203
+ record = StageRecord(
204
+ name=name,
205
+ status=status,
206
+ tool=ToolRef(**tool_payload),
207
+ inputs=[InputRef(**r) for r in payload.get("inputs") or []],
208
+ outputs=[OutputRef(**r) for r in payload.get("outputs") or []],
209
+ params=payload.get("params") or {},
210
+ notes=notes,
211
+ error=payload.get("error"),
212
+ )
213
+ if payload.get("started_at"):
214
+ record.started_at = str(payload["started_at"])
215
+ record.ended_at = str(payload["ended_at"]) if payload.get("ended_at") else _now_iso()
216
+ return record