ligandai 0.1.0__tar.gz
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.
- ligandai-0.1.0/.gitignore +58 -0
- ligandai-0.1.0/CHANGELOG.md +45 -0
- ligandai-0.1.0/LICENSE +21 -0
- ligandai-0.1.0/PKG-INFO +275 -0
- ligandai-0.1.0/README.md +231 -0
- ligandai-0.1.0/examples/01_quickstart.py +36 -0
- ligandai-0.1.0/examples/02_end_to_end.py +102 -0
- ligandai-0.1.0/examples/03_bivalent.py +60 -0
- ligandai-0.1.0/examples/04_async_parallel.py +55 -0
- ligandai-0.1.0/examples/05_custom_variant.py +54 -0
- ligandai-0.1.0/examples/06_streaming.py +52 -0
- ligandai-0.1.0/ligandai/__init__.py +78 -0
- ligandai-0.1.0/ligandai/_constants.py +105 -0
- ligandai-0.1.0/ligandai/_http.py +409 -0
- ligandai-0.1.0/ligandai/_version.py +7 -0
- ligandai-0.1.0/ligandai/client.py +331 -0
- ligandai-0.1.0/ligandai/errors.py +260 -0
- ligandai-0.1.0/ligandai/jobs.py +448 -0
- ligandai-0.1.0/ligandai/py.typed +0 -0
- ligandai-0.1.0/ligandai/receptordb.py +188 -0
- ligandai-0.1.0/ligandai/resources/__init__.py +9 -0
- ligandai-0.1.0/ligandai/resources/_base.py +36 -0
- ligandai-0.1.0/ligandai/resources/account.py +91 -0
- ligandai-0.1.0/ligandai/resources/bivalent.py +293 -0
- ligandai-0.1.0/ligandai/resources/charts.py +67 -0
- ligandai-0.1.0/ligandai/resources/discovery.py +333 -0
- ligandai-0.1.0/ligandai/resources/diseases.py +73 -0
- ligandai-0.1.0/ligandai/resources/jobs.py +104 -0
- ligandai-0.1.0/ligandai/resources/memory.py +118 -0
- ligandai-0.1.0/ligandai/resources/peptides.py +821 -0
- ligandai-0.1.0/ligandai/resources/programs.py +183 -0
- ligandai-0.1.0/ligandai/resources/proteins.py +248 -0
- ligandai-0.1.0/ligandai/resources/receptors.py +268 -0
- ligandai-0.1.0/ligandai/resources/reports.py +83 -0
- ligandai-0.1.0/ligandai/resources/structures.py +138 -0
- ligandai-0.1.0/ligandai/resources/synthesis.py +340 -0
- ligandai-0.1.0/ligandai/types.py +661 -0
- ligandai-0.1.0/pyproject.toml +172 -0
- ligandai-0.1.0/tests/__init__.py +1 -0
- ligandai-0.1.0/tests/conftest.py +49 -0
- ligandai-0.1.0/tests/integration/__init__.py +1 -0
- ligandai-0.1.0/tests/integration/test_live_api.py +60 -0
- ligandai-0.1.0/tests/unit/__init__.py +1 -0
- ligandai-0.1.0/tests/unit/test_client.py +132 -0
- ligandai-0.1.0/tests/unit/test_errors.py +145 -0
- ligandai-0.1.0/tests/unit/test_http.py +88 -0
- ligandai-0.1.0/tests/unit/test_jobs.py +153 -0
- ligandai-0.1.0/tests/unit/test_receptordb.py +56 -0
- ligandai-0.1.0/tests/unit/test_resources.py +200 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
*.egg
|
|
8
|
+
*.egg-info/
|
|
9
|
+
.eggs/
|
|
10
|
+
build/
|
|
11
|
+
dist/
|
|
12
|
+
wheels/
|
|
13
|
+
*.whl
|
|
14
|
+
|
|
15
|
+
# Virtual environments
|
|
16
|
+
.venv/
|
|
17
|
+
venv/
|
|
18
|
+
env/
|
|
19
|
+
ENV/
|
|
20
|
+
|
|
21
|
+
# Testing / coverage
|
|
22
|
+
.pytest_cache/
|
|
23
|
+
.coverage
|
|
24
|
+
.coverage.*
|
|
25
|
+
htmlcov/
|
|
26
|
+
.tox/
|
|
27
|
+
.nox/
|
|
28
|
+
coverage.xml
|
|
29
|
+
*.cover
|
|
30
|
+
.hypothesis/
|
|
31
|
+
|
|
32
|
+
# Type checking
|
|
33
|
+
.mypy_cache/
|
|
34
|
+
.pytype/
|
|
35
|
+
.ruff_cache/
|
|
36
|
+
|
|
37
|
+
# IDE
|
|
38
|
+
.vscode/
|
|
39
|
+
.idea/
|
|
40
|
+
*.swp
|
|
41
|
+
*.swo
|
|
42
|
+
|
|
43
|
+
# OS
|
|
44
|
+
.DS_Store
|
|
45
|
+
Thumbs.db
|
|
46
|
+
|
|
47
|
+
# Docs build
|
|
48
|
+
docs/_build/
|
|
49
|
+
docs/api/
|
|
50
|
+
|
|
51
|
+
# Local secrets / config
|
|
52
|
+
.env
|
|
53
|
+
.env.local
|
|
54
|
+
*.pem
|
|
55
|
+
secrets.json
|
|
56
|
+
|
|
57
|
+
# Build artifacts
|
|
58
|
+
*.log
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2025
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial public release.
|
|
13
|
+
- Sync (`LigandAI`) and async (`AsyncLigandAI`) clients.
|
|
14
|
+
- Tier detection from API key prefix (`lgai_free_*`, `lgai_edu_*`,
|
|
15
|
+
`lgai_pro_*`, `lgai_ent_*`, `lgai_sa_*`). No network call required.
|
|
16
|
+
- Twelve resource namespaces:
|
|
17
|
+
- `account`, `receptors`, `structures`, `proteins`, `discovery`,
|
|
18
|
+
`diseases`, `peptides`, `bivalent`, `synthesis`, `memory`,
|
|
19
|
+
`programs`, `charts`, `reports`, `jobs`.
|
|
20
|
+
- `Job` / `AsyncJob` polymorphic abstractions for long-running operations
|
|
21
|
+
with `.wait()`, `.poll()`, `.cancel()`, `.stream()` (SSE).
|
|
22
|
+
- Typed exception hierarchy: `LigandAIError`, `LigandAIAuthError`,
|
|
23
|
+
`LigandAITierError`, `LigandAIRateLimitError`, `LigandAICreditError`,
|
|
24
|
+
`LigandAINotFoundError`, `LigandAIServerError`, `LigandAIValidationError`,
|
|
25
|
+
`LigandAIJobError`, `LigandAITimeoutError`, `NotSupportedOnReceptorDB`.
|
|
26
|
+
- Pydantic v2 models for every documented request/response field.
|
|
27
|
+
- `httpx` transport with auto-retry on 429/5xx via `tenacity`,
|
|
28
|
+
exponential backoff, and `Retry-After` / `X-RateLimit-Reset` parsing.
|
|
29
|
+
- `ReceptorDBClient` and `AsyncReceptorDBClient` with the read-mostly
|
|
30
|
+
ReceptorDB subset; raises `NotSupportedOnReceptorDB` for endpoints
|
|
31
|
+
outside the subset.
|
|
32
|
+
- Tier feature-gating raised client-side (no round-trip) for known features.
|
|
33
|
+
- Six example scripts in `examples/`.
|
|
34
|
+
- Sphinx docs scaffold (`docs/`).
|
|
35
|
+
|
|
36
|
+
### Server endpoint mapping
|
|
37
|
+
|
|
38
|
+
The SDK targets `/api/*` routes (NOT `/v1/*`). Express's `isAuthenticated`
|
|
39
|
+
middleware accepts either a session cookie OR `Authorization: Bearer lgai_*`
|
|
40
|
+
on every `/api/*` route, so the SDK uses Bearer auth uniformly.
|
|
41
|
+
|
|
42
|
+
The `/v1/*` enterprise routes have feature gaps (no bivalent, no synthesis
|
|
43
|
+
cart, no charts) and use an in-memory `apiKeyStore` map rather than the
|
|
44
|
+
canonical Drizzle `apiKeys` table. See `wiki/synthesis/api_endpoints_complete_catalog.md:498`
|
|
45
|
+
for details.
|
ligandai-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ligandal, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ligandai-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ligandai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the LIGANDAI® platform — peptide design, structure prediction, scoring, and discovery.
|
|
5
|
+
Project-URL: Homepage, https://ligandai.com
|
|
6
|
+
Project-URL: Documentation, https://docs.ligandai.com
|
|
7
|
+
Project-URL: Repository, https://github.com/ligandal/ligandai-python-sdk
|
|
8
|
+
Project-URL: Issues, https://github.com/ligandal/ligandai-python-sdk/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/ligandal/ligandai-python-sdk/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Andre Watson <dre@ligandal.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: bioinformatics,boltz2,computational-biology,deltaforge,drug-discovery,ligandai,ligandforge,peptide-design,protein-folding
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
27
|
+
Requires-Dist: pydantic<3.0,>=2.0
|
|
28
|
+
Requires-Dist: tenacity<10.0,>=8.0
|
|
29
|
+
Requires-Dist: typing-extensions>=4.5
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
38
|
+
Provides-Extra: docs
|
|
39
|
+
Requires-Dist: myst-parser>=2.0; extra == 'docs'
|
|
40
|
+
Requires-Dist: sphinx-autoapi>=3.0; extra == 'docs'
|
|
41
|
+
Requires-Dist: sphinx-rtd-theme>=2.0; extra == 'docs'
|
|
42
|
+
Requires-Dist: sphinx>=7.0; extra == 'docs'
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
45
|
+
# LIGANDAI Python SDK
|
|
46
|
+
|
|
47
|
+
Official Python SDK for the [LIGANDAI](https://ligandai.com) platform — peptide
|
|
48
|
+
design, structure prediction, scoring, and discovery.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install ligandai
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from ligandai import LigandAI
|
|
56
|
+
|
|
57
|
+
client = LigandAI(api_key="lgai_pro_...")
|
|
58
|
+
print(f"Tier: {client.tier}, Credits: {client.credits}")
|
|
59
|
+
|
|
60
|
+
# Find tissue-specific surface markers
|
|
61
|
+
markers = client.discovery.tissue_markers(target_tissues=["Liver"], top_n=2000)
|
|
62
|
+
|
|
63
|
+
# Resolve a structure for the top marker
|
|
64
|
+
gene = markers.top[0].gene
|
|
65
|
+
structure = client.structures.get(gene)
|
|
66
|
+
analysis = client.structures.analyze(gene, analysis_depth="full")
|
|
67
|
+
|
|
68
|
+
# Generate peptides targeting the recommended pocket
|
|
69
|
+
job = client.peptides.generate(
|
|
70
|
+
gene=gene,
|
|
71
|
+
num_peptides=300,
|
|
72
|
+
target_residues=[analysis.recommended_pocket] if analysis.recommended_pocket else None,
|
|
73
|
+
targeting_strategy="pocket_targeted",
|
|
74
|
+
auto_fold=True,
|
|
75
|
+
top_n_fold=25,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Wait for completion (generation + auto-fold)
|
|
79
|
+
result = job.wait(timeout=1800)
|
|
80
|
+
print(f"Got {len(result.peptides)} peptides, top iPSAE: {result.peptides[0].ipsae}")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Authentication
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# 1. Pass explicitly
|
|
87
|
+
client = LigandAI(api_key="lgai_pro_...")
|
|
88
|
+
|
|
89
|
+
# 2. Read from env var (preferred for prod)
|
|
90
|
+
# $ export LIGANDAI_API_KEY=lgai_pro_...
|
|
91
|
+
client = LigandAI()
|
|
92
|
+
|
|
93
|
+
# 3. Custom base URL (dev / on-prem / enterprise)
|
|
94
|
+
client = LigandAI(api_key="...", base_url="http://localhost:5050")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
API keys carry a **tier prefix**:
|
|
98
|
+
|
|
99
|
+
| Prefix | Tier | What it can do |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| `lgai_free_*` | free | search, view structures, get job status |
|
|
102
|
+
| `lgai_edu_*` | academia | + generate, fold, score, glycosylation |
|
|
103
|
+
| `lgai_pro_*` | pro | + bivalent, transport vasculome (no batch ops) |
|
|
104
|
+
| `lgai_ent_*` | enterprise | everything + batch operations + priority queue |
|
|
105
|
+
| `lgai_sa_*` | superadmin | all features (internal) |
|
|
106
|
+
|
|
107
|
+
The client detects the tier from the prefix at construction — no network call.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
client.tier # "pro"
|
|
111
|
+
client.credits # int
|
|
112
|
+
client.feature_allowed("...") # bool
|
|
113
|
+
client.max_peptides_per_generation
|
|
114
|
+
client.rate_limit_per_minute
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
When a method requires a higher tier than the key carries, it raises
|
|
118
|
+
`LigandAITierError` **client-side**, before sending the request.
|
|
119
|
+
|
|
120
|
+
## Resource Namespaces
|
|
121
|
+
|
|
122
|
+
| Namespace | Endpoints | What it does |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `client.account` | `/api/auth/user`, `/api/user-credits`, ... | profile, credits, tier limits |
|
|
125
|
+
| `client.receptors` | `/api/receptordb/*` | search, browse, download PDBs |
|
|
126
|
+
| `client.structures` | `/api/structure/*`, `/api/gene-resolver/*` | gene → PDB / AlphaFold |
|
|
127
|
+
| `client.proteins` | `/api/protein-info/*`, `/api/protein-variants/*` | UniProt info, variants, custom PDBs |
|
|
128
|
+
| `client.discovery` | `/api/transcriptomics/*`, `/api/scrna/*`, `/api/geo-import/*` | tissue markers, scRNA, GEO import |
|
|
129
|
+
| `client.diseases` | `/api/disease-viewer/*` | disease search, mutations |
|
|
130
|
+
| `client.peptides` | `/api/ptf/parallel/*`, `/api/folding/*`, `/api/binder-scoring/*` | generate, fold, score |
|
|
131
|
+
| `client.bivalent` | `/api/ligandforge/bivalent/*` | bispecific design (pro+) |
|
|
132
|
+
| `client.synthesis` | `/api/synthesis-checkout/*`, `/api/adaptyv/*` | quote, cart, order |
|
|
133
|
+
| `client.memory` | `/api/episodic-memory/*` | memory search & save |
|
|
134
|
+
| `client.programs` | `/api/ptf/programs/*`, `/api/ptf/sessions/*` | programs, projects, sessions |
|
|
135
|
+
| `client.charts` | `/api/charts/*` | matplotlib chart generation |
|
|
136
|
+
| `client.reports` | `/api/reports/*` | PDF report generation |
|
|
137
|
+
| `client.jobs` | `/api/jobs/*` | list, cancel, stream |
|
|
138
|
+
|
|
139
|
+
## Long-Running Jobs
|
|
140
|
+
|
|
141
|
+
Generation, folding, and scoring submit GPU work and return a `Job`:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
job = client.peptides.generate(gene="EGFR", num_peptides=300)
|
|
145
|
+
job.id # str
|
|
146
|
+
job.status # "queued" | "running" | "complete" | "failed"
|
|
147
|
+
job.progress # 0-100 or None
|
|
148
|
+
job.estimated_credits
|
|
149
|
+
|
|
150
|
+
# Block until done
|
|
151
|
+
result = job.wait(timeout=1800, poll_interval=2.0)
|
|
152
|
+
|
|
153
|
+
# Or stream live progress events (SSE)
|
|
154
|
+
for event in job.stream():
|
|
155
|
+
print(f"{event.stage}: {event.message} ({event.progress})")
|
|
156
|
+
|
|
157
|
+
# Cancel
|
|
158
|
+
job.cancel()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Async equivalents:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
import asyncio
|
|
165
|
+
from ligandai import AsyncLigandAI
|
|
166
|
+
|
|
167
|
+
async def design_for_genes(genes):
|
|
168
|
+
async with AsyncLigandAI() as client:
|
|
169
|
+
jobs = await asyncio.gather(*[
|
|
170
|
+
client.peptides.generate(gene=g, num_peptides=300) for g in genes
|
|
171
|
+
])
|
|
172
|
+
results = await asyncio.gather(*[j.wait() for j in jobs])
|
|
173
|
+
return results
|
|
174
|
+
|
|
175
|
+
results = asyncio.run(design_for_genes(["EGFR", "HER2", "KIT"]))
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Errors
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from ligandai import (
|
|
182
|
+
LigandAIError, # base
|
|
183
|
+
LigandAIAuthError, # 401 — invalid/expired/revoked key
|
|
184
|
+
LigandAITierError, # 403 — feature requires higher tier
|
|
185
|
+
LigandAIRateLimitError, # 429 — rate limit
|
|
186
|
+
LigandAICreditError, # 402 — insufficient credits
|
|
187
|
+
LigandAINotFoundError, # 404
|
|
188
|
+
LigandAIServerError, # 5xx (auto-retried)
|
|
189
|
+
LigandAIValidationError, # 400/422
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
job = client.peptides.generate(gene="EGFR", num_peptides=10000)
|
|
194
|
+
except LigandAITierError as e:
|
|
195
|
+
print(f"Need {e.required_tier}, you have {e.current_tier}")
|
|
196
|
+
except LigandAICreditError as e:
|
|
197
|
+
print(f"Need {e.required} credits, have {e.available}")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Retry & Rate Limiting
|
|
201
|
+
|
|
202
|
+
The SDK automatically retries on `429`, `5xx`, and transient network errors
|
|
203
|
+
with exponential backoff (configurable via `max_retries=`). It also respects
|
|
204
|
+
`Retry-After` and `X-RateLimit-Reset` headers.
|
|
205
|
+
|
|
206
|
+
Per-tier rate limits:
|
|
207
|
+
|
|
208
|
+
| Tier | req/min |
|
|
209
|
+
|---|---|
|
|
210
|
+
| free | 10 |
|
|
211
|
+
| academia | 30 |
|
|
212
|
+
| pro | 60 |
|
|
213
|
+
| enterprise | 300 |
|
|
214
|
+
|
|
215
|
+
## ReceptorDB-restricted Client
|
|
216
|
+
|
|
217
|
+
For receptordb.com users, a thinner client exposes only browse / search /
|
|
218
|
+
download (no API key required for read endpoints):
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from ligandai import ReceptorDBClient
|
|
222
|
+
|
|
223
|
+
client = ReceptorDBClient()
|
|
224
|
+
hits = client.search("EGFR")
|
|
225
|
+
client.download_pdb(hits[0].complex_id, "egfr.pdb")
|
|
226
|
+
|
|
227
|
+
# With API key — fold/generate
|
|
228
|
+
client = ReceptorDBClient(api_key="lgai_basic_...")
|
|
229
|
+
job = client.fold(sequences=["MAEEPQSD..."], target_gene="EGFR")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Typed Models
|
|
233
|
+
|
|
234
|
+
All request/response shapes are pydantic models. IDE autocompletion works
|
|
235
|
+
out of the box, including for nested fields:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
from ligandai import BivalentTarget, LinkerConfig
|
|
239
|
+
|
|
240
|
+
session = client.bivalent.start(
|
|
241
|
+
target1=BivalentTarget(gene="PDCD1", chain="A"),
|
|
242
|
+
target2=BivalentTarget(gene="CD274", chain="A"),
|
|
243
|
+
linker=LinkerConfig(position="C", length_min=8, length_max=20),
|
|
244
|
+
binder_length_min=15,
|
|
245
|
+
binder_length_max=40,
|
|
246
|
+
num_designs=200,
|
|
247
|
+
)
|
|
248
|
+
print(session.id, session.status)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Examples
|
|
252
|
+
|
|
253
|
+
See `examples/` for complete worked demos:
|
|
254
|
+
|
|
255
|
+
- `examples/01_quickstart.py` — auth, tier check, simple search
|
|
256
|
+
- `examples/02_end_to_end.py` — discovery → structure → generate → fold → score → cart
|
|
257
|
+
- `examples/03_bivalent.py` — PD-1 / PD-L1 bispecific design
|
|
258
|
+
- `examples/04_async_parallel.py` — design for many genes concurrently
|
|
259
|
+
- `examples/05_custom_variant.py` — fold a mutation, save as variant, regenerate
|
|
260
|
+
- `examples/06_streaming.py` — live SSE progress
|
|
261
|
+
|
|
262
|
+
## Development
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
git clone https://github.com/ligandal/ligandai-python-sdk
|
|
266
|
+
cd ligandai-python-sdk
|
|
267
|
+
pip install -e ".[dev]"
|
|
268
|
+
pytest
|
|
269
|
+
mypy ligandai/
|
|
270
|
+
ruff check ligandai/
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
MIT — see [LICENSE](LICENSE).
|
ligandai-0.1.0/README.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# LIGANDAI Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [LIGANDAI](https://ligandai.com) platform — peptide
|
|
4
|
+
design, structure prediction, scoring, and discovery.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install ligandai
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
from ligandai import LigandAI
|
|
12
|
+
|
|
13
|
+
client = LigandAI(api_key="lgai_pro_...")
|
|
14
|
+
print(f"Tier: {client.tier}, Credits: {client.credits}")
|
|
15
|
+
|
|
16
|
+
# Find tissue-specific surface markers
|
|
17
|
+
markers = client.discovery.tissue_markers(target_tissues=["Liver"], top_n=2000)
|
|
18
|
+
|
|
19
|
+
# Resolve a structure for the top marker
|
|
20
|
+
gene = markers.top[0].gene
|
|
21
|
+
structure = client.structures.get(gene)
|
|
22
|
+
analysis = client.structures.analyze(gene, analysis_depth="full")
|
|
23
|
+
|
|
24
|
+
# Generate peptides targeting the recommended pocket
|
|
25
|
+
job = client.peptides.generate(
|
|
26
|
+
gene=gene,
|
|
27
|
+
num_peptides=300,
|
|
28
|
+
target_residues=[analysis.recommended_pocket] if analysis.recommended_pocket else None,
|
|
29
|
+
targeting_strategy="pocket_targeted",
|
|
30
|
+
auto_fold=True,
|
|
31
|
+
top_n_fold=25,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Wait for completion (generation + auto-fold)
|
|
35
|
+
result = job.wait(timeout=1800)
|
|
36
|
+
print(f"Got {len(result.peptides)} peptides, top iPSAE: {result.peptides[0].ipsae}")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Authentication
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# 1. Pass explicitly
|
|
43
|
+
client = LigandAI(api_key="lgai_pro_...")
|
|
44
|
+
|
|
45
|
+
# 2. Read from env var (preferred for prod)
|
|
46
|
+
# $ export LIGANDAI_API_KEY=lgai_pro_...
|
|
47
|
+
client = LigandAI()
|
|
48
|
+
|
|
49
|
+
# 3. Custom base URL (dev / on-prem / enterprise)
|
|
50
|
+
client = LigandAI(api_key="...", base_url="http://localhost:5050")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
API keys carry a **tier prefix**:
|
|
54
|
+
|
|
55
|
+
| Prefix | Tier | What it can do |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `lgai_free_*` | free | search, view structures, get job status |
|
|
58
|
+
| `lgai_edu_*` | academia | + generate, fold, score, glycosylation |
|
|
59
|
+
| `lgai_pro_*` | pro | + bivalent, transport vasculome (no batch ops) |
|
|
60
|
+
| `lgai_ent_*` | enterprise | everything + batch operations + priority queue |
|
|
61
|
+
| `lgai_sa_*` | superadmin | all features (internal) |
|
|
62
|
+
|
|
63
|
+
The client detects the tier from the prefix at construction — no network call.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
client.tier # "pro"
|
|
67
|
+
client.credits # int
|
|
68
|
+
client.feature_allowed("...") # bool
|
|
69
|
+
client.max_peptides_per_generation
|
|
70
|
+
client.rate_limit_per_minute
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
When a method requires a higher tier than the key carries, it raises
|
|
74
|
+
`LigandAITierError` **client-side**, before sending the request.
|
|
75
|
+
|
|
76
|
+
## Resource Namespaces
|
|
77
|
+
|
|
78
|
+
| Namespace | Endpoints | What it does |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `client.account` | `/api/auth/user`, `/api/user-credits`, ... | profile, credits, tier limits |
|
|
81
|
+
| `client.receptors` | `/api/receptordb/*` | search, browse, download PDBs |
|
|
82
|
+
| `client.structures` | `/api/structure/*`, `/api/gene-resolver/*` | gene → PDB / AlphaFold |
|
|
83
|
+
| `client.proteins` | `/api/protein-info/*`, `/api/protein-variants/*` | UniProt info, variants, custom PDBs |
|
|
84
|
+
| `client.discovery` | `/api/transcriptomics/*`, `/api/scrna/*`, `/api/geo-import/*` | tissue markers, scRNA, GEO import |
|
|
85
|
+
| `client.diseases` | `/api/disease-viewer/*` | disease search, mutations |
|
|
86
|
+
| `client.peptides` | `/api/ptf/parallel/*`, `/api/folding/*`, `/api/binder-scoring/*` | generate, fold, score |
|
|
87
|
+
| `client.bivalent` | `/api/ligandforge/bivalent/*` | bispecific design (pro+) |
|
|
88
|
+
| `client.synthesis` | `/api/synthesis-checkout/*`, `/api/adaptyv/*` | quote, cart, order |
|
|
89
|
+
| `client.memory` | `/api/episodic-memory/*` | memory search & save |
|
|
90
|
+
| `client.programs` | `/api/ptf/programs/*`, `/api/ptf/sessions/*` | programs, projects, sessions |
|
|
91
|
+
| `client.charts` | `/api/charts/*` | matplotlib chart generation |
|
|
92
|
+
| `client.reports` | `/api/reports/*` | PDF report generation |
|
|
93
|
+
| `client.jobs` | `/api/jobs/*` | list, cancel, stream |
|
|
94
|
+
|
|
95
|
+
## Long-Running Jobs
|
|
96
|
+
|
|
97
|
+
Generation, folding, and scoring submit GPU work and return a `Job`:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
job = client.peptides.generate(gene="EGFR", num_peptides=300)
|
|
101
|
+
job.id # str
|
|
102
|
+
job.status # "queued" | "running" | "complete" | "failed"
|
|
103
|
+
job.progress # 0-100 or None
|
|
104
|
+
job.estimated_credits
|
|
105
|
+
|
|
106
|
+
# Block until done
|
|
107
|
+
result = job.wait(timeout=1800, poll_interval=2.0)
|
|
108
|
+
|
|
109
|
+
# Or stream live progress events (SSE)
|
|
110
|
+
for event in job.stream():
|
|
111
|
+
print(f"{event.stage}: {event.message} ({event.progress})")
|
|
112
|
+
|
|
113
|
+
# Cancel
|
|
114
|
+
job.cancel()
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Async equivalents:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import asyncio
|
|
121
|
+
from ligandai import AsyncLigandAI
|
|
122
|
+
|
|
123
|
+
async def design_for_genes(genes):
|
|
124
|
+
async with AsyncLigandAI() as client:
|
|
125
|
+
jobs = await asyncio.gather(*[
|
|
126
|
+
client.peptides.generate(gene=g, num_peptides=300) for g in genes
|
|
127
|
+
])
|
|
128
|
+
results = await asyncio.gather(*[j.wait() for j in jobs])
|
|
129
|
+
return results
|
|
130
|
+
|
|
131
|
+
results = asyncio.run(design_for_genes(["EGFR", "HER2", "KIT"]))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Errors
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from ligandai import (
|
|
138
|
+
LigandAIError, # base
|
|
139
|
+
LigandAIAuthError, # 401 — invalid/expired/revoked key
|
|
140
|
+
LigandAITierError, # 403 — feature requires higher tier
|
|
141
|
+
LigandAIRateLimitError, # 429 — rate limit
|
|
142
|
+
LigandAICreditError, # 402 — insufficient credits
|
|
143
|
+
LigandAINotFoundError, # 404
|
|
144
|
+
LigandAIServerError, # 5xx (auto-retried)
|
|
145
|
+
LigandAIValidationError, # 400/422
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
job = client.peptides.generate(gene="EGFR", num_peptides=10000)
|
|
150
|
+
except LigandAITierError as e:
|
|
151
|
+
print(f"Need {e.required_tier}, you have {e.current_tier}")
|
|
152
|
+
except LigandAICreditError as e:
|
|
153
|
+
print(f"Need {e.required} credits, have {e.available}")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Retry & Rate Limiting
|
|
157
|
+
|
|
158
|
+
The SDK automatically retries on `429`, `5xx`, and transient network errors
|
|
159
|
+
with exponential backoff (configurable via `max_retries=`). It also respects
|
|
160
|
+
`Retry-After` and `X-RateLimit-Reset` headers.
|
|
161
|
+
|
|
162
|
+
Per-tier rate limits:
|
|
163
|
+
|
|
164
|
+
| Tier | req/min |
|
|
165
|
+
|---|---|
|
|
166
|
+
| free | 10 |
|
|
167
|
+
| academia | 30 |
|
|
168
|
+
| pro | 60 |
|
|
169
|
+
| enterprise | 300 |
|
|
170
|
+
|
|
171
|
+
## ReceptorDB-restricted Client
|
|
172
|
+
|
|
173
|
+
For receptordb.com users, a thinner client exposes only browse / search /
|
|
174
|
+
download (no API key required for read endpoints):
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from ligandai import ReceptorDBClient
|
|
178
|
+
|
|
179
|
+
client = ReceptorDBClient()
|
|
180
|
+
hits = client.search("EGFR")
|
|
181
|
+
client.download_pdb(hits[0].complex_id, "egfr.pdb")
|
|
182
|
+
|
|
183
|
+
# With API key — fold/generate
|
|
184
|
+
client = ReceptorDBClient(api_key="lgai_basic_...")
|
|
185
|
+
job = client.fold(sequences=["MAEEPQSD..."], target_gene="EGFR")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Typed Models
|
|
189
|
+
|
|
190
|
+
All request/response shapes are pydantic models. IDE autocompletion works
|
|
191
|
+
out of the box, including for nested fields:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from ligandai import BivalentTarget, LinkerConfig
|
|
195
|
+
|
|
196
|
+
session = client.bivalent.start(
|
|
197
|
+
target1=BivalentTarget(gene="PDCD1", chain="A"),
|
|
198
|
+
target2=BivalentTarget(gene="CD274", chain="A"),
|
|
199
|
+
linker=LinkerConfig(position="C", length_min=8, length_max=20),
|
|
200
|
+
binder_length_min=15,
|
|
201
|
+
binder_length_max=40,
|
|
202
|
+
num_designs=200,
|
|
203
|
+
)
|
|
204
|
+
print(session.id, session.status)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Examples
|
|
208
|
+
|
|
209
|
+
See `examples/` for complete worked demos:
|
|
210
|
+
|
|
211
|
+
- `examples/01_quickstart.py` — auth, tier check, simple search
|
|
212
|
+
- `examples/02_end_to_end.py` — discovery → structure → generate → fold → score → cart
|
|
213
|
+
- `examples/03_bivalent.py` — PD-1 / PD-L1 bispecific design
|
|
214
|
+
- `examples/04_async_parallel.py` — design for many genes concurrently
|
|
215
|
+
- `examples/05_custom_variant.py` — fold a mutation, save as variant, regenerate
|
|
216
|
+
- `examples/06_streaming.py` — live SSE progress
|
|
217
|
+
|
|
218
|
+
## Development
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
git clone https://github.com/ligandal/ligandai-python-sdk
|
|
222
|
+
cd ligandai-python-sdk
|
|
223
|
+
pip install -e ".[dev]"
|
|
224
|
+
pytest
|
|
225
|
+
mypy ligandai/
|
|
226
|
+
ruff check ligandai/
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Copyright © 2025 Ligandal, Inc. All rights reserved.
|
|
2
|
+
"""Quickstart — auth, tier check, simple search.
|
|
3
|
+
|
|
4
|
+
Run:
|
|
5
|
+
LIGANDAI_API_KEY=lgai_pro_... python examples/01_quickstart.py
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from ligandai import LigandAI
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> None:
|
|
14
|
+
client = LigandAI() # reads LIGANDAI_API_KEY env var
|
|
15
|
+
|
|
16
|
+
# No network call — tier comes from key prefix.
|
|
17
|
+
print(f"Tier: {client.tier}")
|
|
18
|
+
print(f"Rate limit: {client.rate_limit_per_minute} req/min")
|
|
19
|
+
print(f"Max peptides per generation: {client.max_peptides_per_generation}")
|
|
20
|
+
print()
|
|
21
|
+
|
|
22
|
+
# Network call: get user + credits
|
|
23
|
+
user = client.user
|
|
24
|
+
print(f"User: {user.email} ({user.first_name} {user.last_name})")
|
|
25
|
+
print(f"Credits: {client.credits}")
|
|
26
|
+
print()
|
|
27
|
+
|
|
28
|
+
# Search ReceptorDB
|
|
29
|
+
print("Searching for EGFR receptors...")
|
|
30
|
+
hits = client.receptors.search("EGFR", limit=5)
|
|
31
|
+
for h in hits:
|
|
32
|
+
print(f" {h.complex_name} — {h.oligomeric_state} ({h.organism})")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
main()
|