molbuilder-client 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.
@@ -0,0 +1,46 @@
1
+ # Build artifacts
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *.pyo
10
+ *.pyd
11
+ .Python
12
+
13
+ # Virtual environments
14
+ .venv/
15
+ venv/
16
+ ENV/
17
+
18
+ # Testing
19
+ .pytest_cache/
20
+ .coverage
21
+ htmlcov/
22
+ .tox/
23
+
24
+ # IDE
25
+ .vscode/
26
+ .idea/
27
+ *.swp
28
+ *.swo
29
+ *~
30
+
31
+ # OS
32
+ .DS_Store
33
+ Thumbs.db
34
+ Desktop.ini
35
+
36
+ # SQLite databases
37
+ *.db
38
+ *.db-wal
39
+ *.db-shm
40
+
41
+ # Environment
42
+ .env
43
+
44
+ # Distribution
45
+ *.tar.gz
46
+ *.whl
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wedge Dev Co
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,160 @@
1
+ Metadata-Version: 2.4
2
+ Name: molbuilder-client
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the MolBuilder REST API
5
+ Project-URL: Homepage, https://www.molbuilder.io
6
+ Project-URL: Documentation, https://www.molbuilder.io/docs
7
+ Project-URL: Repository, https://github.com/Taylor-C-Powell/Molecule_Builder
8
+ Author-email: Wedge Dev Co <taylorp661@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: api-client,chemistry,molecule,retrosynthesis,smiles
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: httpx>=0.27
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
25
+ Requires-Dist: pytest>=8; extra == 'dev'
26
+ Requires-Dist: respx>=0.21; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # molbuilder-client
30
+
31
+ Python SDK for the [MolBuilder](https://www.molbuilder.io) REST API.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install molbuilder-client
37
+ ```
38
+
39
+ Requires Python 3.11+. The only runtime dependency is [httpx](https://www.python-httpx.org/).
40
+
41
+ ## Quickstart
42
+
43
+ ```python
44
+ from molbuilder_client import MolBuilder
45
+
46
+ client = MolBuilder(api_key="mb_...")
47
+
48
+ # Parse a SMILES string
49
+ mol = client.from_smiles("CCO", name="ethanol")
50
+ print(mol.id, mol.num_atoms) # mol_xxxx 9
51
+
52
+ # Get molecular properties
53
+ props = client.get_properties(mol.id)
54
+ print(props.formula, props.molecular_weight) # C2H6O 46.07
55
+
56
+ # Get 3D coordinates
57
+ structure = client.get_3d(mol.id)
58
+ for atom in structure.atoms:
59
+ print(atom.symbol, atom.position)
60
+
61
+ # Look up an element
62
+ fe = client.get_element("Fe")
63
+ print(fe.name, fe.atomic_weight) # Iron 55.845
64
+
65
+ # Retrosynthesis
66
+ plan = client.retrosynthesis("c1ccccc1O") # phenol
67
+ print(plan.routes_found, plan.best_route.total_steps)
68
+
69
+ # Process evaluation
70
+ proc = client.process_evaluate("CCO", scale_kg=100.0)
71
+ print(proc.cost.total_usd, proc.cost.per_kg_usd)
72
+ ```
73
+
74
+ ## Async usage
75
+
76
+ ```python
77
+ import asyncio
78
+ from molbuilder_client import AsyncMolBuilder
79
+
80
+ async def main():
81
+ async with AsyncMolBuilder(api_key="mb_...") as client:
82
+ mol = await client.from_smiles("CCO")
83
+ props = await client.get_properties(mol.id)
84
+ print(props.formula)
85
+
86
+ asyncio.run(main())
87
+ ```
88
+
89
+ ## Error handling
90
+
91
+ All API errors raise typed exceptions that inherit from `MolBuilderError`:
92
+
93
+ ```python
94
+ from molbuilder_client import MolBuilder, ValidationError, RateLimitError
95
+
96
+ client = MolBuilder(api_key="mb_...")
97
+
98
+ try:
99
+ client.from_smiles("not_valid")
100
+ except ValidationError as e:
101
+ print(f"Bad SMILES: {e.message}")
102
+ except RateLimitError as e:
103
+ print(f"Slow down! Retry after {e.retry_after}s")
104
+ ```
105
+
106
+ | HTTP Status | Exception |
107
+ |-------------|----------------------------|
108
+ | 401 | `AuthenticationError` |
109
+ | 403 | `ForbiddenError` |
110
+ | 404 | `NotFoundError` |
111
+ | 422 | `ValidationError` |
112
+ | 429 | `RateLimitError` |
113
+ | 500 | `ServerError` |
114
+ | 501/503 | `ServiceUnavailableError` |
115
+
116
+ ## API reference
117
+
118
+ ### Auth
119
+
120
+ | Method | Returns | Description |
121
+ |---------------------------|----------------|-------------------------------------|
122
+ | `register(email)` | `APIKeyInfo` | Register a new free-tier API key |
123
+ | `get_token()` | `Token` | Exchange API key for a JWT |
124
+
125
+ ### Molecule
126
+
127
+ | Method | Returns | Description |
128
+ |-------------------------------|----------------------|------------------------------|
129
+ | `from_smiles(smiles, name="")` | `MoleculeInfo` | Parse SMILES into a molecule |
130
+ | `get_properties(mol_id)` | `MoleculeProperties` | Computed molecular properties |
131
+ | `get_3d(mol_id)` | `Molecule3D` | 3D coordinates and bonds |
132
+
133
+ ### Elements
134
+
135
+ | Method | Returns | Description |
136
+ |-----------------------|-----------|------------------------------|
137
+ | `get_element(symbol)` | `Element` | Look up element by symbol |
138
+
139
+ ### Retrosynthesis
140
+
141
+ | Method | Returns | Description |
142
+ |-----------------------------------------------------|----------------------|--------------------------|
143
+ | `retrosynthesis(smiles, max_depth=5, beam_width=5)` | `RetrosynthesisPlan` | Plan retrosynthetic routes |
144
+
145
+ ### Process
146
+
147
+ | Method | Returns | Description |
148
+ |-------------------------------------------------------------------|---------------------|--------------------------------|
149
+ | `process_evaluate(smiles, scale_kg=1.0, max_depth=5, beam_width=5)` | `ProcessEvaluation` | Full process engineering report |
150
+
151
+ ### Billing
152
+
153
+ | Method | Returns | Description |
154
+ |-------------------------|-------------------|------------------------------------|
155
+ | `create_checkout(plan)` | `CheckoutSession` | Create Stripe checkout session |
156
+ | `billing_status()` | `BillingStatus` | Current subscription status |
157
+
158
+ ## License
159
+
160
+ MIT
@@ -0,0 +1,132 @@
1
+ # molbuilder-client
2
+
3
+ Python SDK for the [MolBuilder](https://www.molbuilder.io) REST API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install molbuilder-client
9
+ ```
10
+
11
+ Requires Python 3.11+. The only runtime dependency is [httpx](https://www.python-httpx.org/).
12
+
13
+ ## Quickstart
14
+
15
+ ```python
16
+ from molbuilder_client import MolBuilder
17
+
18
+ client = MolBuilder(api_key="mb_...")
19
+
20
+ # Parse a SMILES string
21
+ mol = client.from_smiles("CCO", name="ethanol")
22
+ print(mol.id, mol.num_atoms) # mol_xxxx 9
23
+
24
+ # Get molecular properties
25
+ props = client.get_properties(mol.id)
26
+ print(props.formula, props.molecular_weight) # C2H6O 46.07
27
+
28
+ # Get 3D coordinates
29
+ structure = client.get_3d(mol.id)
30
+ for atom in structure.atoms:
31
+ print(atom.symbol, atom.position)
32
+
33
+ # Look up an element
34
+ fe = client.get_element("Fe")
35
+ print(fe.name, fe.atomic_weight) # Iron 55.845
36
+
37
+ # Retrosynthesis
38
+ plan = client.retrosynthesis("c1ccccc1O") # phenol
39
+ print(plan.routes_found, plan.best_route.total_steps)
40
+
41
+ # Process evaluation
42
+ proc = client.process_evaluate("CCO", scale_kg=100.0)
43
+ print(proc.cost.total_usd, proc.cost.per_kg_usd)
44
+ ```
45
+
46
+ ## Async usage
47
+
48
+ ```python
49
+ import asyncio
50
+ from molbuilder_client import AsyncMolBuilder
51
+
52
+ async def main():
53
+ async with AsyncMolBuilder(api_key="mb_...") as client:
54
+ mol = await client.from_smiles("CCO")
55
+ props = await client.get_properties(mol.id)
56
+ print(props.formula)
57
+
58
+ asyncio.run(main())
59
+ ```
60
+
61
+ ## Error handling
62
+
63
+ All API errors raise typed exceptions that inherit from `MolBuilderError`:
64
+
65
+ ```python
66
+ from molbuilder_client import MolBuilder, ValidationError, RateLimitError
67
+
68
+ client = MolBuilder(api_key="mb_...")
69
+
70
+ try:
71
+ client.from_smiles("not_valid")
72
+ except ValidationError as e:
73
+ print(f"Bad SMILES: {e.message}")
74
+ except RateLimitError as e:
75
+ print(f"Slow down! Retry after {e.retry_after}s")
76
+ ```
77
+
78
+ | HTTP Status | Exception |
79
+ |-------------|----------------------------|
80
+ | 401 | `AuthenticationError` |
81
+ | 403 | `ForbiddenError` |
82
+ | 404 | `NotFoundError` |
83
+ | 422 | `ValidationError` |
84
+ | 429 | `RateLimitError` |
85
+ | 500 | `ServerError` |
86
+ | 501/503 | `ServiceUnavailableError` |
87
+
88
+ ## API reference
89
+
90
+ ### Auth
91
+
92
+ | Method | Returns | Description |
93
+ |---------------------------|----------------|-------------------------------------|
94
+ | `register(email)` | `APIKeyInfo` | Register a new free-tier API key |
95
+ | `get_token()` | `Token` | Exchange API key for a JWT |
96
+
97
+ ### Molecule
98
+
99
+ | Method | Returns | Description |
100
+ |-------------------------------|----------------------|------------------------------|
101
+ | `from_smiles(smiles, name="")` | `MoleculeInfo` | Parse SMILES into a molecule |
102
+ | `get_properties(mol_id)` | `MoleculeProperties` | Computed molecular properties |
103
+ | `get_3d(mol_id)` | `Molecule3D` | 3D coordinates and bonds |
104
+
105
+ ### Elements
106
+
107
+ | Method | Returns | Description |
108
+ |-----------------------|-----------|------------------------------|
109
+ | `get_element(symbol)` | `Element` | Look up element by symbol |
110
+
111
+ ### Retrosynthesis
112
+
113
+ | Method | Returns | Description |
114
+ |-----------------------------------------------------|----------------------|--------------------------|
115
+ | `retrosynthesis(smiles, max_depth=5, beam_width=5)` | `RetrosynthesisPlan` | Plan retrosynthetic routes |
116
+
117
+ ### Process
118
+
119
+ | Method | Returns | Description |
120
+ |-------------------------------------------------------------------|---------------------|--------------------------------|
121
+ | `process_evaluate(smiles, scale_kg=1.0, max_depth=5, beam_width=5)` | `ProcessEvaluation` | Full process engineering report |
122
+
123
+ ### Billing
124
+
125
+ | Method | Returns | Description |
126
+ |-------------------------|-------------------|------------------------------------|
127
+ | `create_checkout(plan)` | `CheckoutSession` | Create Stripe checkout session |
128
+ | `billing_status()` | `BillingStatus` | Current subscription status |
129
+
130
+ ## License
131
+
132
+ MIT
@@ -0,0 +1,95 @@
1
+ """MolBuilder Python SDK -- typed client for the MolBuilder REST API.
2
+
3
+ Usage::
4
+
5
+ from molbuilder_client import MolBuilder
6
+
7
+ client = MolBuilder(api_key="mb_...")
8
+ mol = client.from_smiles("CCO")
9
+ print(mol.id, mol.num_atoms)
10
+ """
11
+
12
+ from molbuilder_client._async_client import AsyncMolBuilder
13
+ from molbuilder_client._client import MolBuilder
14
+ from molbuilder_client._exceptions import (
15
+ AuthenticationError,
16
+ ForbiddenError,
17
+ MolBuilderError,
18
+ NotFoundError,
19
+ RateLimitError,
20
+ ServerError,
21
+ ServiceUnavailableError,
22
+ ValidationError,
23
+ )
24
+ from molbuilder_client._models import (
25
+ APIKeyInfo,
26
+ Atom3D,
27
+ BestRoute,
28
+ BillingStatus,
29
+ Bond3D,
30
+ CheckoutSession,
31
+ Conditions,
32
+ CostBreakdown,
33
+ CostEstimate,
34
+ Disconnection,
35
+ Element,
36
+ Hazard,
37
+ Molecule3D,
38
+ MoleculeInfo,
39
+ MoleculeProperties,
40
+ Precursor,
41
+ ProcessEvaluation,
42
+ PurificationMethod,
43
+ Reactor,
44
+ RetroNode,
45
+ RetrosynthesisPlan,
46
+ RouteStep,
47
+ SafetyAssessment,
48
+ ScaleUp,
49
+ StepDetail,
50
+ Token,
51
+ )
52
+
53
+ __all__ = [
54
+ # Clients
55
+ "MolBuilder",
56
+ "AsyncMolBuilder",
57
+ # Exceptions
58
+ "MolBuilderError",
59
+ "AuthenticationError",
60
+ "ForbiddenError",
61
+ "NotFoundError",
62
+ "ValidationError",
63
+ "RateLimitError",
64
+ "ServerError",
65
+ "ServiceUnavailableError",
66
+ # Models
67
+ "APIKeyInfo",
68
+ "Token",
69
+ "MoleculeInfo",
70
+ "MoleculeProperties",
71
+ "Atom3D",
72
+ "Bond3D",
73
+ "Molecule3D",
74
+ "Element",
75
+ "Precursor",
76
+ "Disconnection",
77
+ "RetroNode",
78
+ "RouteStep",
79
+ "BestRoute",
80
+ "RetrosynthesisPlan",
81
+ "Reactor",
82
+ "Conditions",
83
+ "PurificationMethod",
84
+ "StepDetail",
85
+ "Hazard",
86
+ "SafetyAssessment",
87
+ "CostBreakdown",
88
+ "CostEstimate",
89
+ "ScaleUp",
90
+ "ProcessEvaluation",
91
+ "CheckoutSession",
92
+ "BillingStatus",
93
+ ]
94
+
95
+ __version__ = "0.1.0"
@@ -0,0 +1,175 @@
1
+ """Asynchronous MolBuilder API client backed by ``httpx.AsyncClient``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import httpx
8
+
9
+ from molbuilder_client._base import (
10
+ DEFAULT_BASE_URL,
11
+ build_headers,
12
+ build_url,
13
+ from_dict,
14
+ raise_for_status,
15
+ )
16
+ from molbuilder_client._models import (
17
+ APIKeyInfo,
18
+ BillingStatus,
19
+ CheckoutSession,
20
+ Element,
21
+ Molecule3D,
22
+ MoleculeInfo,
23
+ MoleculeProperties,
24
+ ProcessEvaluation,
25
+ RetrosynthesisPlan,
26
+ Token,
27
+ )
28
+
29
+
30
+ class AsyncMolBuilder:
31
+ """Asynchronous client for the MolBuilder REST API.
32
+
33
+ Parameters
34
+ ----------
35
+ api_key:
36
+ Your MolBuilder API key (starts with ``mb_``).
37
+ base_url:
38
+ Override the default production URL (useful for local dev).
39
+ timeout:
40
+ Request timeout in seconds. Defaults to 30.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ api_key: str,
46
+ *,
47
+ base_url: str = DEFAULT_BASE_URL,
48
+ timeout: float = 30.0,
49
+ ) -> None:
50
+ self._api_key = api_key
51
+ self._base_url = base_url
52
+ self._client = httpx.AsyncClient(
53
+ headers=build_headers(api_key),
54
+ timeout=timeout,
55
+ )
56
+
57
+ # -- Async context manager ------------------------------------------------
58
+
59
+ async def __aenter__(self) -> AsyncMolBuilder:
60
+ return self
61
+
62
+ async def __aexit__(self, *exc: Any) -> None:
63
+ await self.close()
64
+
65
+ async def close(self) -> None:
66
+ """Release the underlying connection pool."""
67
+ await self._client.aclose()
68
+
69
+ # -- Private helpers ------------------------------------------------------
70
+
71
+ def _url(self, path: str) -> str:
72
+ return build_url(self._base_url, path)
73
+
74
+ async def _get(self, path: str, *, params: dict[str, Any] | None = None) -> dict[str, Any]:
75
+ resp = await self._client.get(self._url(path), params=params)
76
+ raise_for_status(resp)
77
+ return resp.json()
78
+
79
+ async def _post(self, path: str, *, json: dict[str, Any] | None = None) -> dict[str, Any]:
80
+ resp = await self._client.post(self._url(path), json=json)
81
+ raise_for_status(resp)
82
+ return resp.json()
83
+
84
+ # -- Auth -----------------------------------------------------------------
85
+
86
+ async def register(self, email: str) -> APIKeyInfo:
87
+ """Register a new free-tier API key for *email*."""
88
+ data = await self._post("auth/register", json={"email": email})
89
+ return from_dict(APIKeyInfo, data)
90
+
91
+ async def get_token(self) -> Token:
92
+ """Exchange the current API key for a short-lived JWT."""
93
+ data = await self._post("auth/token", json={"api_key": self._api_key})
94
+ return from_dict(Token, data)
95
+
96
+ # -- Molecule -------------------------------------------------------------
97
+
98
+ async def from_smiles(self, smiles: str, *, name: str = "") -> MoleculeInfo:
99
+ """Parse a SMILES string and return basic molecule info."""
100
+ payload: dict[str, Any] = {"smiles": smiles}
101
+ if name:
102
+ payload["name"] = name
103
+ data = await self._post("molecule/from-smiles", json=payload)
104
+ return from_dict(MoleculeInfo, data)
105
+
106
+ async def get_properties(self, mol_id: str) -> MoleculeProperties:
107
+ """Retrieve computed molecular properties for *mol_id*."""
108
+ data = await self._get(f"molecule/{mol_id}/properties")
109
+ return from_dict(MoleculeProperties, data)
110
+
111
+ async def get_3d(self, mol_id: str) -> Molecule3D:
112
+ """Retrieve 3D atomic coordinates and bonds for *mol_id*."""
113
+ data = await self._get(f"molecule/{mol_id}/3d")
114
+ return from_dict(Molecule3D, data)
115
+
116
+ # -- Elements -------------------------------------------------------------
117
+
118
+ async def get_element(self, symbol: str) -> Element:
119
+ """Look up an element by its chemical symbol (e.g. ``"Fe"``)."""
120
+ data = await self._get(f"elements/{symbol}")
121
+ return from_dict(Element, data)
122
+
123
+ # -- Retrosynthesis -------------------------------------------------------
124
+
125
+ async def retrosynthesis(
126
+ self,
127
+ smiles: str,
128
+ *,
129
+ max_depth: int = 5,
130
+ beam_width: int = 5,
131
+ ) -> RetrosynthesisPlan:
132
+ """Plan retrosynthetic routes for a target SMILES."""
133
+ data = await self._post(
134
+ "retrosynthesis/plan",
135
+ json={
136
+ "smiles": smiles,
137
+ "max_depth": max_depth,
138
+ "beam_width": beam_width,
139
+ },
140
+ )
141
+ return from_dict(RetrosynthesisPlan, data)
142
+
143
+ # -- Process Evaluation ---------------------------------------------------
144
+
145
+ async def process_evaluate(
146
+ self,
147
+ smiles: str,
148
+ *,
149
+ scale_kg: float = 1.0,
150
+ max_depth: int = 5,
151
+ beam_width: int = 5,
152
+ ) -> ProcessEvaluation:
153
+ """Evaluate process engineering for a synthesis route."""
154
+ data = await self._post(
155
+ "process/evaluate",
156
+ json={
157
+ "smiles": smiles,
158
+ "scale_kg": scale_kg,
159
+ "max_depth": max_depth,
160
+ "beam_width": beam_width,
161
+ },
162
+ )
163
+ return from_dict(ProcessEvaluation, data)
164
+
165
+ # -- Billing --------------------------------------------------------------
166
+
167
+ async def create_checkout(self, plan: str) -> CheckoutSession:
168
+ """Create a Stripe Checkout session for *plan* (e.g. ``"pro_monthly"``)."""
169
+ data = await self._post("billing/checkout", json={"plan": plan})
170
+ return from_dict(CheckoutSession, data)
171
+
172
+ async def billing_status(self) -> BillingStatus:
173
+ """Return the current billing / subscription status."""
174
+ data = await self._get("billing/status")
175
+ return from_dict(BillingStatus, data)