ladock 0.3.0__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.
- ladock/__init__.py +14 -0
- ladock/binaries.py +215 -0
- ladock/cli/__init__.py +5 -0
- ladock/cli/__main__.py +6 -0
- ladock/cli/agent.py +3707 -0
- ladock/desktop/__init__.py +6 -0
- ladock/desktop/__main__.py +6 -0
- ladock/desktop/app/__init__.py +0 -0
- ladock/desktop/app/about_dialog.py +264 -0
- ladock/desktop/app/documentation_dialog.py +332 -0
- ladock/desktop/app/license_dialog.py +312 -0
- ladock/desktop/app/main_window.py +490 -0
- ladock/desktop/app/project_manager.py +323 -0
- ladock/desktop/app/settings_dialog.py +319 -0
- ladock/desktop/config/__init__.py +0 -0
- ladock/desktop/config/configdock.txt +67 -0
- ladock/desktop/config/example.csv +9 -0
- ladock/desktop/config/example.mol +254 -0
- ladock/desktop/config/example.mol2 +229 -0
- ladock/desktop/config/example.pdb +42 -0
- ladock/desktop/config/example.smi +12 -0
- ladock/desktop/config/example.uri +4 -0
- ladock/desktop/config/example_1.sdf +4339 -0
- ladock/desktop/config/model_example/lig_example.pdb +42 -0
- ladock/desktop/config/model_example/rec_example.pdb +4729 -0
- ladock/desktop/core/__init__.py +1 -0
- ladock/desktop/core/docking_features.py +29 -0
- ladock/desktop/core/job_scheduler.py +43 -0
- ladock/desktop/core/license_manager.py +214 -0
- ladock/desktop/core/ligand_importer.py +1032 -0
- ladock/desktop/core/ligand_smiles.py +97 -0
- ladock/desktop/core/render_smiles_svg.py +187 -0
- ladock/desktop/core/resources.py +19 -0
- ladock/desktop/core/tool_paths.py +309 -0
- ladock/desktop/core/wsl_backend.py +119 -0
- ladock/desktop/data/__init__.py +2 -0
- ladock/desktop/data/project.py +126 -0
- ladock/desktop/data/result_parser.py +129 -0
- ladock/desktop/engine/__init__.py +5 -0
- ladock/desktop/engine/interaction_analyzer.py +549 -0
- ladock/desktop/engine/mol_prep.py +567 -0
- ladock/desktop/engine/native_prep.py +216 -0
- ladock/desktop/engine/tool_detector.py +175 -0
- ladock/desktop/gui/__init__.py +0 -0
- ladock/desktop/gui/assets/3Dmol-min.js +2 -0
- ladock/desktop/gui/assets/icon.png +0 -0
- ladock/desktop/gui/assets/ladock.ico +0 -0
- ladock/desktop/gui/assets/viewer.html +639 -0
- ladock/desktop/gui/panels/__init__.py +3 -0
- ladock/desktop/gui/panels/docking_prep_panel.py +1083 -0
- ladock/desktop/gui/panels/interaction_panel.py +1852 -0
- ladock/desktop/gui/panels/job_manager.py +340 -0
- ladock/desktop/gui/panels/ligand_test_panel.py +2627 -0
- ladock/desktop/gui/panels/native_redocking_panel.py +2598 -0
- ladock/desktop/gui/panels/result_explorer.py +1070 -0
- ladock/desktop/gui/theme.py +463 -0
- ladock/desktop/gui/viewer/__init__.py +1 -0
- ladock/desktop/gui/viewer/molecular_viewer.py +693 -0
- ladock/desktop/gui/widgets/__init__.py +0 -0
- ladock/desktop/gui/widgets/common.py +202 -0
- ladock/desktop/gui/widgets/tool_status_widget.py +159 -0
- ladock/desktop/main.py +88 -0
- ladock/paths.py +104 -0
- ladock-0.3.0.dist-info/METADATA +313 -0
- ladock-0.3.0.dist-info/RECORD +69 -0
- ladock-0.3.0.dist-info/WHEEL +5 -0
- ladock-0.3.0.dist-info/entry_points.txt +6 -0
- ladock-0.3.0.dist-info/licenses/LICENSE +98 -0
- ladock-0.3.0.dist-info/top_level.txt +1 -0
ladock/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""LADOCK — molecular docking workstation.
|
|
2
|
+
|
|
3
|
+
One distribution, two front-ends that share the same bundled engine binaries:
|
|
4
|
+
|
|
5
|
+
* ``ladock-cli`` — rule-based (non-LLM) docking agent, :mod:`ladock.cli`
|
|
6
|
+
* ``ladock-desktop`` — PySide6 GUI workstation, :mod:`ladock.desktop`
|
|
7
|
+
|
|
8
|
+
Importing this package is deliberately cheap: nothing here pulls in PySide6,
|
|
9
|
+
RDKit or Meeko, so ``ladock-cli`` starts without paying for the GUI stack.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "0.3.0"
|
|
13
|
+
|
|
14
|
+
__all__ = ["__version__"]
|
ladock/binaries.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fetch the pre-packaged engine binaries into ``ladock/bin/<platform>/``.
|
|
3
|
+
|
|
4
|
+
The docking engines (Vina, AutoDock4/AutoGrid4, AutoDock-GPU, MGLTools) are far
|
|
5
|
+
too large to ship inside the wheel, so ``pip install ladock`` installs the code
|
|
6
|
+
and this command pulls the binaries afterwards::
|
|
7
|
+
|
|
8
|
+
ladock-fetch-binaries # the platform you are running on
|
|
9
|
+
ladock-fetch-binaries linux mac # explicit
|
|
10
|
+
ladock-fetch-binaries all
|
|
11
|
+
ladock-fetch-binaries --fix-permissions # repair a tree that lost +x
|
|
12
|
+
|
|
13
|
+
Rather than rebuilding each engine from upstream (fragile — Scripps downtime,
|
|
14
|
+
changing apt package names, …), this downloads the exact ``bin/<platform>/`` set
|
|
15
|
+
the maintainer already validated, packaged as ONE archive per platform.
|
|
16
|
+
|
|
17
|
+
Expected layout on the host (each archive has a top-level ``<platform>/`` dir)::
|
|
18
|
+
|
|
19
|
+
<BASE>/bin-windows.tar.gz -> bin/windows/...
|
|
20
|
+
<BASE>/bin-linux.tar.gz -> bin/linux/... (with or without ADFRsuite;
|
|
21
|
+
<BASE>/bin-mac.tar.gz -> bin/mac/... build_release.py prunes
|
|
22
|
+
ADFRsuite at stage time)
|
|
23
|
+
|
|
24
|
+
Use **.tar.gz** for linux/mac so executable bits are preserved (zip does not
|
|
25
|
+
preserve Unix permissions reliably).
|
|
26
|
+
|
|
27
|
+
Configure via environment (defaults shown):
|
|
28
|
+
LADOCK_BIN_BASE_URL base URL (default: https://ladock.ladeep.id/bin)
|
|
29
|
+
LADOCK_BIN_TOKEN optional bearer token for private storage
|
|
30
|
+
LADOCK_BIN install binaries here instead of inside the package
|
|
31
|
+
"""
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import os
|
|
35
|
+
import shutil
|
|
36
|
+
import sys
|
|
37
|
+
import tarfile
|
|
38
|
+
import urllib.request
|
|
39
|
+
import zipfile
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
from ladock.paths import bin_root, platform_name
|
|
43
|
+
|
|
44
|
+
PLATFORMS = ("windows", "linux", "mac")
|
|
45
|
+
|
|
46
|
+
# Default to the GitHub release assets: they need no separate web host, no
|
|
47
|
+
# certificate to keep alive, and they are already public. `or default` so an
|
|
48
|
+
# empty env var (e.g. an unset CI secret) falls back cleanly.
|
|
49
|
+
_DEFAULT_BASE = "https://github.com/laode-aman-ung/LADOCK/releases/download/v0.3.0"
|
|
50
|
+
_BASE = (os.environ.get("LADOCK_BIN_BASE_URL") or _DEFAULT_BASE).rstrip("/")
|
|
51
|
+
_TOKEN = (os.environ.get("LADOCK_BIN_TOKEN") or "").strip()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _download(url: str, dest: Path) -> None:
|
|
55
|
+
print(f" downloading {url}")
|
|
56
|
+
req = urllib.request.Request(url)
|
|
57
|
+
if _TOKEN:
|
|
58
|
+
req.add_header("Authorization", f"Bearer {_TOKEN}")
|
|
59
|
+
with urllib.request.urlopen(req, timeout=120) as resp, open(dest, "wb") as fh:
|
|
60
|
+
shutil.copyfileobj(resp, fh)
|
|
61
|
+
print(f" -> {dest.stat().st_size / 1024 / 1024:.1f} MB")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _is_executable_payload(path: Path) -> bool:
|
|
65
|
+
"""True for ELF/Mach-O binaries and shebang scripts — things meant to run."""
|
|
66
|
+
try:
|
|
67
|
+
with open(path, "rb") as fh:
|
|
68
|
+
head = fh.read(4)
|
|
69
|
+
except OSError:
|
|
70
|
+
return False
|
|
71
|
+
return head[:4] == b"\x7fELF" or head[:2] == b"#!" or head[:4] in (
|
|
72
|
+
b"\xcf\xfa\xed\xfe", # Mach-O 64-bit little-endian
|
|
73
|
+
b"\xca\xfe\xba\xbe", # Mach-O universal
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def restore_exec_bits(root: Path) -> int:
|
|
78
|
+
"""Re-apply the executable bit to every runnable file under ``root``.
|
|
79
|
+
|
|
80
|
+
Zip archives do not carry Unix permissions, so a tree extracted from one
|
|
81
|
+
(or copied through a filesystem that drops the bit) leaves every engine
|
|
82
|
+
non-executable and docking fails with a bare "Permission denied".
|
|
83
|
+
"""
|
|
84
|
+
fixed = 0
|
|
85
|
+
for path in root.rglob("*"):
|
|
86
|
+
if not path.is_file() or os.access(path, os.X_OK):
|
|
87
|
+
continue
|
|
88
|
+
if not _is_executable_payload(path):
|
|
89
|
+
continue
|
|
90
|
+
try:
|
|
91
|
+
mode = path.stat().st_mode
|
|
92
|
+
path.chmod(mode | 0o111)
|
|
93
|
+
fixed += 1
|
|
94
|
+
except OSError:
|
|
95
|
+
pass
|
|
96
|
+
return fixed
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _extract(archive: Path, into: Path) -> None:
|
|
100
|
+
if archive.name.endswith(".zip"):
|
|
101
|
+
with zipfile.ZipFile(archive) as z:
|
|
102
|
+
z.extractall(into)
|
|
103
|
+
return
|
|
104
|
+
with tarfile.open(archive) as t:
|
|
105
|
+
# filter="data" (3.12+) refuses absolute/parent paths and other members
|
|
106
|
+
# that would write outside `into`.
|
|
107
|
+
if sys.version_info >= (3, 12):
|
|
108
|
+
t.extractall(into, filter="data")
|
|
109
|
+
else:
|
|
110
|
+
t.extractall(into)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def fetch_platform(platform: str, dest_root: Path | None = None) -> None:
|
|
114
|
+
root = dest_root or bin_root()
|
|
115
|
+
print(f"\n== {platform} ==")
|
|
116
|
+
dest = root / platform
|
|
117
|
+
if dest.is_dir() and any(dest.iterdir()):
|
|
118
|
+
print(f" [ok] {dest} already present - skipping")
|
|
119
|
+
return
|
|
120
|
+
try:
|
|
121
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
except OSError as exc:
|
|
123
|
+
raise SystemExit(
|
|
124
|
+
f"Cannot create {root}: {exc}\n"
|
|
125
|
+
f"Set LADOCK_BIN to a writable directory (e.g. ~/.local/share/ladock/bin) "
|
|
126
|
+
f"and re-run.") from exc
|
|
127
|
+
|
|
128
|
+
last_err: Exception | None = None
|
|
129
|
+
for ext in (".tar.gz", ".tar.xz", ".zip"):
|
|
130
|
+
url = f"{_BASE}/bin-{platform}{ext}"
|
|
131
|
+
archive = root / f"bin-{platform}{ext}"
|
|
132
|
+
try:
|
|
133
|
+
_download(url, archive)
|
|
134
|
+
except Exception as exc: # noqa: BLE001
|
|
135
|
+
last_err = exc
|
|
136
|
+
print(f" - not found ({exc})")
|
|
137
|
+
continue
|
|
138
|
+
print(f" extracting {archive.name}...")
|
|
139
|
+
_extract(archive, root)
|
|
140
|
+
archive.unlink(missing_ok=True)
|
|
141
|
+
if os.name != "nt":
|
|
142
|
+
fixed = restore_exec_bits(dest)
|
|
143
|
+
if fixed:
|
|
144
|
+
print(f" restored the executable bit on {fixed} file(s)")
|
|
145
|
+
if not (dest.is_dir() and any(dest.iterdir())):
|
|
146
|
+
raise SystemExit(
|
|
147
|
+
f"Archive extracted but {dest} is empty - the archive "
|
|
148
|
+
f"must contain a top-level '{platform}/' folder.")
|
|
149
|
+
print(f" [ok] {dest} ready")
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
raise SystemExit(
|
|
153
|
+
f"Could not fetch bin/{platform} from {_BASE} "
|
|
154
|
+
f"(tried .tar.gz/.tar.xz/.zip). Last error: {last_err}\n"
|
|
155
|
+
f"Host your validated binaries as <BASE>/bin-{platform}.tar.gz, or set "
|
|
156
|
+
f"LADOCK_BIN_BASE_URL / LADOCK_BIN_TOKEN.")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def binaries_present(platform: str | None = None) -> bool:
|
|
160
|
+
target = bin_root() / (platform or platform_name())
|
|
161
|
+
return target.is_dir() and any(target.iterdir())
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def ensure_platform_binaries(quiet: bool = False) -> bool:
|
|
165
|
+
"""Download this platform's engines if they are not there yet.
|
|
166
|
+
|
|
167
|
+
Called before docking so ``pip install ladock`` is followed straight by
|
|
168
|
+
``ladock-cli`` — no separate fetch step for the user to know about. It is a
|
|
169
|
+
no-op once the binaries exist, and a failure here is not fatal: the caller
|
|
170
|
+
still reports the missing engine in its own terms.
|
|
171
|
+
"""
|
|
172
|
+
if binaries_present():
|
|
173
|
+
return True
|
|
174
|
+
if not quiet:
|
|
175
|
+
print("Docking engines are not installed yet — downloading them once "
|
|
176
|
+
f"into {bin_root()} …", flush=True)
|
|
177
|
+
try:
|
|
178
|
+
fetch_platform(platform_name())
|
|
179
|
+
except SystemExit as exc:
|
|
180
|
+
if not quiet:
|
|
181
|
+
print(f" Could not download the engines: {exc}", flush=True)
|
|
182
|
+
return False
|
|
183
|
+
except Exception as exc: # noqa: BLE001 - never block docking on this
|
|
184
|
+
if not quiet:
|
|
185
|
+
print(f" Could not download the engines: {exc}", flush=True)
|
|
186
|
+
return False
|
|
187
|
+
return binaries_present()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def main(argv: list[str] | None = None) -> int:
|
|
191
|
+
args = list(argv) if argv is not None else sys.argv[1:]
|
|
192
|
+
if "--fix-permissions" in args:
|
|
193
|
+
# Repair an existing tree in place — for binaries that arrived by zip,
|
|
194
|
+
# USB stick, or a filesystem that dropped the executable bit.
|
|
195
|
+
root = bin_root()
|
|
196
|
+
if not root.is_dir():
|
|
197
|
+
raise SystemExit(f"No binaries at {root}. Run `ladock-fetch-binaries` first.")
|
|
198
|
+
print(f"Restored the executable bit on {restore_exec_bits(root)} file(s) in {root}")
|
|
199
|
+
return 0
|
|
200
|
+
# No argument = "make THIS machine work", which is what someone who just ran
|
|
201
|
+
# `pip install ladock` wants; downloading all three platforms is a build-time
|
|
202
|
+
# need, so it stays opt-in via `all`.
|
|
203
|
+
platforms = args or [platform_name()]
|
|
204
|
+
if "all" in platforms:
|
|
205
|
+
platforms = list(PLATFORMS)
|
|
206
|
+
for p in platforms:
|
|
207
|
+
if p not in PLATFORMS:
|
|
208
|
+
raise SystemExit(f"Unknown platform '{p}' ({'|'.join(PLATFORMS)}|all)")
|
|
209
|
+
fetch_platform(p)
|
|
210
|
+
print(f"\nDone. Binaries in {bin_root()}")
|
|
211
|
+
return 0
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
if __name__ == "__main__":
|
|
215
|
+
raise SystemExit(main())
|
ladock/cli/__init__.py
ADDED