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.
Files changed (69) hide show
  1. ladock/__init__.py +14 -0
  2. ladock/binaries.py +215 -0
  3. ladock/cli/__init__.py +5 -0
  4. ladock/cli/__main__.py +6 -0
  5. ladock/cli/agent.py +3707 -0
  6. ladock/desktop/__init__.py +6 -0
  7. ladock/desktop/__main__.py +6 -0
  8. ladock/desktop/app/__init__.py +0 -0
  9. ladock/desktop/app/about_dialog.py +264 -0
  10. ladock/desktop/app/documentation_dialog.py +332 -0
  11. ladock/desktop/app/license_dialog.py +312 -0
  12. ladock/desktop/app/main_window.py +490 -0
  13. ladock/desktop/app/project_manager.py +323 -0
  14. ladock/desktop/app/settings_dialog.py +319 -0
  15. ladock/desktop/config/__init__.py +0 -0
  16. ladock/desktop/config/configdock.txt +67 -0
  17. ladock/desktop/config/example.csv +9 -0
  18. ladock/desktop/config/example.mol +254 -0
  19. ladock/desktop/config/example.mol2 +229 -0
  20. ladock/desktop/config/example.pdb +42 -0
  21. ladock/desktop/config/example.smi +12 -0
  22. ladock/desktop/config/example.uri +4 -0
  23. ladock/desktop/config/example_1.sdf +4339 -0
  24. ladock/desktop/config/model_example/lig_example.pdb +42 -0
  25. ladock/desktop/config/model_example/rec_example.pdb +4729 -0
  26. ladock/desktop/core/__init__.py +1 -0
  27. ladock/desktop/core/docking_features.py +29 -0
  28. ladock/desktop/core/job_scheduler.py +43 -0
  29. ladock/desktop/core/license_manager.py +214 -0
  30. ladock/desktop/core/ligand_importer.py +1032 -0
  31. ladock/desktop/core/ligand_smiles.py +97 -0
  32. ladock/desktop/core/render_smiles_svg.py +187 -0
  33. ladock/desktop/core/resources.py +19 -0
  34. ladock/desktop/core/tool_paths.py +309 -0
  35. ladock/desktop/core/wsl_backend.py +119 -0
  36. ladock/desktop/data/__init__.py +2 -0
  37. ladock/desktop/data/project.py +126 -0
  38. ladock/desktop/data/result_parser.py +129 -0
  39. ladock/desktop/engine/__init__.py +5 -0
  40. ladock/desktop/engine/interaction_analyzer.py +549 -0
  41. ladock/desktop/engine/mol_prep.py +567 -0
  42. ladock/desktop/engine/native_prep.py +216 -0
  43. ladock/desktop/engine/tool_detector.py +175 -0
  44. ladock/desktop/gui/__init__.py +0 -0
  45. ladock/desktop/gui/assets/3Dmol-min.js +2 -0
  46. ladock/desktop/gui/assets/icon.png +0 -0
  47. ladock/desktop/gui/assets/ladock.ico +0 -0
  48. ladock/desktop/gui/assets/viewer.html +639 -0
  49. ladock/desktop/gui/panels/__init__.py +3 -0
  50. ladock/desktop/gui/panels/docking_prep_panel.py +1083 -0
  51. ladock/desktop/gui/panels/interaction_panel.py +1852 -0
  52. ladock/desktop/gui/panels/job_manager.py +340 -0
  53. ladock/desktop/gui/panels/ligand_test_panel.py +2627 -0
  54. ladock/desktop/gui/panels/native_redocking_panel.py +2598 -0
  55. ladock/desktop/gui/panels/result_explorer.py +1070 -0
  56. ladock/desktop/gui/theme.py +463 -0
  57. ladock/desktop/gui/viewer/__init__.py +1 -0
  58. ladock/desktop/gui/viewer/molecular_viewer.py +693 -0
  59. ladock/desktop/gui/widgets/__init__.py +0 -0
  60. ladock/desktop/gui/widgets/common.py +202 -0
  61. ladock/desktop/gui/widgets/tool_status_widget.py +159 -0
  62. ladock/desktop/main.py +88 -0
  63. ladock/paths.py +104 -0
  64. ladock-0.3.0.dist-info/METADATA +313 -0
  65. ladock-0.3.0.dist-info/RECORD +69 -0
  66. ladock-0.3.0.dist-info/WHEEL +5 -0
  67. ladock-0.3.0.dist-info/entry_points.txt +6 -0
  68. ladock-0.3.0.dist-info/licenses/LICENSE +98 -0
  69. 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
@@ -0,0 +1,5 @@
1
+ """LADOCK CLI — the rule-based (non-LLM) docking agent behind ``ladock-cli``."""
2
+
3
+ from ladock.cli.agent import main
4
+
5
+ __all__ = ["main"]
ladock/cli/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Allow ``python -m ladock.cli`` alongside the ``ladock-cli`` command."""
2
+
3
+ from ladock.cli.agent import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())