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.
Files changed (49) hide show
  1. ligandai-0.1.0/.gitignore +58 -0
  2. ligandai-0.1.0/CHANGELOG.md +45 -0
  3. ligandai-0.1.0/LICENSE +21 -0
  4. ligandai-0.1.0/PKG-INFO +275 -0
  5. ligandai-0.1.0/README.md +231 -0
  6. ligandai-0.1.0/examples/01_quickstart.py +36 -0
  7. ligandai-0.1.0/examples/02_end_to_end.py +102 -0
  8. ligandai-0.1.0/examples/03_bivalent.py +60 -0
  9. ligandai-0.1.0/examples/04_async_parallel.py +55 -0
  10. ligandai-0.1.0/examples/05_custom_variant.py +54 -0
  11. ligandai-0.1.0/examples/06_streaming.py +52 -0
  12. ligandai-0.1.0/ligandai/__init__.py +78 -0
  13. ligandai-0.1.0/ligandai/_constants.py +105 -0
  14. ligandai-0.1.0/ligandai/_http.py +409 -0
  15. ligandai-0.1.0/ligandai/_version.py +7 -0
  16. ligandai-0.1.0/ligandai/client.py +331 -0
  17. ligandai-0.1.0/ligandai/errors.py +260 -0
  18. ligandai-0.1.0/ligandai/jobs.py +448 -0
  19. ligandai-0.1.0/ligandai/py.typed +0 -0
  20. ligandai-0.1.0/ligandai/receptordb.py +188 -0
  21. ligandai-0.1.0/ligandai/resources/__init__.py +9 -0
  22. ligandai-0.1.0/ligandai/resources/_base.py +36 -0
  23. ligandai-0.1.0/ligandai/resources/account.py +91 -0
  24. ligandai-0.1.0/ligandai/resources/bivalent.py +293 -0
  25. ligandai-0.1.0/ligandai/resources/charts.py +67 -0
  26. ligandai-0.1.0/ligandai/resources/discovery.py +333 -0
  27. ligandai-0.1.0/ligandai/resources/diseases.py +73 -0
  28. ligandai-0.1.0/ligandai/resources/jobs.py +104 -0
  29. ligandai-0.1.0/ligandai/resources/memory.py +118 -0
  30. ligandai-0.1.0/ligandai/resources/peptides.py +821 -0
  31. ligandai-0.1.0/ligandai/resources/programs.py +183 -0
  32. ligandai-0.1.0/ligandai/resources/proteins.py +248 -0
  33. ligandai-0.1.0/ligandai/resources/receptors.py +268 -0
  34. ligandai-0.1.0/ligandai/resources/reports.py +83 -0
  35. ligandai-0.1.0/ligandai/resources/structures.py +138 -0
  36. ligandai-0.1.0/ligandai/resources/synthesis.py +340 -0
  37. ligandai-0.1.0/ligandai/types.py +661 -0
  38. ligandai-0.1.0/pyproject.toml +172 -0
  39. ligandai-0.1.0/tests/__init__.py +1 -0
  40. ligandai-0.1.0/tests/conftest.py +49 -0
  41. ligandai-0.1.0/tests/integration/__init__.py +1 -0
  42. ligandai-0.1.0/tests/integration/test_live_api.py +60 -0
  43. ligandai-0.1.0/tests/unit/__init__.py +1 -0
  44. ligandai-0.1.0/tests/unit/test_client.py +132 -0
  45. ligandai-0.1.0/tests/unit/test_errors.py +145 -0
  46. ligandai-0.1.0/tests/unit/test_http.py +88 -0
  47. ligandai-0.1.0/tests/unit/test_jobs.py +153 -0
  48. ligandai-0.1.0/tests/unit/test_receptordb.py +56 -0
  49. 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.
@@ -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).
@@ -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()