rowan-python 3.1.6__py3-none-any.whl → 3.1.7__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.
- rowan/protein.py +2 -4
- rowan/test_utils.py +54 -0
- rowan/utils.py +40 -0
- {rowan_python-3.1.6.dist-info → rowan_python-3.1.7.dist-info}/METADATA +21 -3
- {rowan_python-3.1.6.dist-info → rowan_python-3.1.7.dist-info}/RECORD +7 -6
- {rowan_python-3.1.6.dist-info → rowan_python-3.1.7.dist-info}/WHEEL +0 -0
- {rowan_python-3.1.6.dist-info → rowan_python-3.1.7.dist-info}/licenses/LICENSE +0 -0
rowan/protein.py
CHANGED
|
@@ -400,22 +400,20 @@ def upload_protein(
|
|
|
400
400
|
if isinstance(project_uuid, Project):
|
|
401
401
|
project_uuid = project_uuid.uuid
|
|
402
402
|
with api_client() as client:
|
|
403
|
-
# Step 1: Read the file and post it to the conversion endpoint.
|
|
404
403
|
conversion_payload = {"name": name, "text": file_path.read_text()}
|
|
405
404
|
conversion_response = client.post("/convert/pdb_file_to_protein", json=conversion_payload)
|
|
405
|
+
conversion_response.raise_for_status()
|
|
406
406
|
|
|
407
|
-
# Extract the JSON data from the conversion response.
|
|
408
407
|
protein_data = conversion_response.json()
|
|
409
408
|
|
|
410
|
-
# Step 2: Use the converted data to create the final protein object.
|
|
411
409
|
creation_payload = {
|
|
412
410
|
"name": name,
|
|
413
411
|
"protein_data": protein_data,
|
|
414
412
|
"project_uuid": project_uuid,
|
|
415
413
|
}
|
|
416
414
|
final_response = client.post("/protein", json=creation_payload)
|
|
415
|
+
final_response.raise_for_status()
|
|
417
416
|
|
|
418
|
-
# Deserialize the final JSON response into a Protein object and return it.
|
|
419
417
|
return Protein(**final_response.json())
|
|
420
418
|
|
|
421
419
|
|
rowan/test_utils.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Tests for Rowan request utilities."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from pytest import MonkeyPatch, raises
|
|
6
|
+
|
|
7
|
+
import rowan
|
|
8
|
+
from rowan.utils import api_credentials, get_api_key, get_project_uuid
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_api_credentials_override_global_configuration(monkeypatch: MonkeyPatch) -> None:
|
|
12
|
+
"""Prefer context-local credentials without changing global configuration."""
|
|
13
|
+
monkeypatch.setattr(rowan, "api_key", "global-key")
|
|
14
|
+
monkeypatch.setattr(rowan, "project_uuid", "global-project")
|
|
15
|
+
monkeypatch.setenv("ROWAN_API_KEY", "environment-key")
|
|
16
|
+
|
|
17
|
+
with api_credentials("context-key", project_uuid="context-project"):
|
|
18
|
+
assert get_api_key() == "context-key"
|
|
19
|
+
assert get_project_uuid() == "context-project"
|
|
20
|
+
|
|
21
|
+
assert get_api_key() == "global-key"
|
|
22
|
+
assert get_project_uuid() == "global-project"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_api_credentials_restore_nested_context() -> None:
|
|
26
|
+
"""Restore outer credentials after a nested context exits."""
|
|
27
|
+
with api_credentials("outer-key", project_uuid="outer-project"):
|
|
28
|
+
with api_credentials("inner-key"):
|
|
29
|
+
assert get_api_key() == "inner-key"
|
|
30
|
+
assert get_project_uuid() is None
|
|
31
|
+
|
|
32
|
+
assert get_api_key() == "outer-key"
|
|
33
|
+
assert get_project_uuid() == "outer-project"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_api_credentials_isolate_async_tasks() -> None:
|
|
37
|
+
"""Keep credentials isolated across concurrent asynchronous tasks."""
|
|
38
|
+
|
|
39
|
+
async def read_after_yield(api_key: str) -> str:
|
|
40
|
+
with api_credentials(api_key):
|
|
41
|
+
await asyncio.sleep(0)
|
|
42
|
+
return get_api_key()
|
|
43
|
+
|
|
44
|
+
async def run_workers() -> list[str]:
|
|
45
|
+
return list(await asyncio.gather(read_after_yield("first"), read_after_yield("second")))
|
|
46
|
+
|
|
47
|
+
assert asyncio.run(run_workers()) == ["first", "second"]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_api_credentials_reject_empty_key() -> None:
|
|
51
|
+
"""Reject empty context-local API keys."""
|
|
52
|
+
with raises(ValueError, match="cannot be empty"):
|
|
53
|
+
with api_credentials(""):
|
|
54
|
+
pass
|
rowan/utils.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from contextlib import contextmanager
|
|
3
|
+
from contextvars import ContextVar
|
|
4
|
+
from dataclasses import dataclass
|
|
3
5
|
from typing import Generator
|
|
4
6
|
|
|
5
7
|
import httpx
|
|
@@ -10,6 +12,40 @@ import rowan
|
|
|
10
12
|
from .constants import API_URL
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class _APIContext:
|
|
17
|
+
"""Store credentials isolated to the current execution context."""
|
|
18
|
+
|
|
19
|
+
api_key: str
|
|
20
|
+
project_uuid: str | None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_api_context: ContextVar[_APIContext | None] = ContextVar("rowan_api_context", default=None)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@contextmanager
|
|
27
|
+
def api_credentials(api_key: str, project_uuid: str | None = None) -> Generator[None, None, None]:
|
|
28
|
+
"""Temporarily use Rowan credentials in the current execution context.
|
|
29
|
+
|
|
30
|
+
Context-local credentials take precedence over module-level and environment configuration.
|
|
31
|
+
Nested contexts restore the previous credentials when they exit, and concurrent threads or
|
|
32
|
+
asynchronous tasks remain isolated from one another.
|
|
33
|
+
|
|
34
|
+
:param api_key: Rowan API key
|
|
35
|
+
:param project_uuid: active project UUID, if any
|
|
36
|
+
:yields: control while the credentials are active
|
|
37
|
+
:raises ValueError: API key is empty
|
|
38
|
+
"""
|
|
39
|
+
if not api_key:
|
|
40
|
+
raise ValueError("API key cannot be empty.")
|
|
41
|
+
|
|
42
|
+
token = _api_context.set(_APIContext(api_key=api_key, project_uuid=project_uuid))
|
|
43
|
+
try:
|
|
44
|
+
yield
|
|
45
|
+
finally:
|
|
46
|
+
_api_context.reset(token)
|
|
47
|
+
|
|
48
|
+
|
|
13
49
|
def get_api_key() -> str:
|
|
14
50
|
"""
|
|
15
51
|
Get the API key from the environment variable ROWAN_API_KEY or the module-level attribute
|
|
@@ -19,6 +55,8 @@ def get_api_key() -> str:
|
|
|
19
55
|
|
|
20
56
|
:returns: API key.
|
|
21
57
|
"""
|
|
58
|
+
if (context := _api_context.get()) is not None:
|
|
59
|
+
return context.api_key
|
|
22
60
|
if hasattr(rowan, "api_key") and rowan.api_key:
|
|
23
61
|
return rowan.api_key
|
|
24
62
|
elif (api_key := os.environ.get("ROWAN_API_KEY")) is not None:
|
|
@@ -36,6 +74,8 @@ def get_project_uuid() -> str | None:
|
|
|
36
74
|
|
|
37
75
|
:returns: Project UUID string, or None if not set.
|
|
38
76
|
"""
|
|
77
|
+
if (context := _api_context.get()) is not None:
|
|
78
|
+
return context.project_uuid
|
|
39
79
|
if hasattr(rowan, "project_uuid") and rowan.project_uuid:
|
|
40
80
|
return rowan.project_uuid
|
|
41
81
|
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rowan-python
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.7
|
|
4
4
|
Summary: Rowan Python Library
|
|
5
5
|
Project-URL: Homepage, https://github.com/rowansci/rowan-client
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/rowansci/rowan-client/issues
|
|
@@ -30,9 +30,27 @@ The documentation is available [here](https://docs.rowansci.com/python-api).
|
|
|
30
30
|
|
|
31
31
|
## Agent skill
|
|
32
32
|
|
|
33
|
-
Ships with a [skill](skills/computational-chemistry-and-biology/)
|
|
33
|
+
Ships with a [computational chemistry and biology skill](skills/computational-chemistry-and-biology/)
|
|
34
|
+
that helps coding agents choose and run Rowan workflows through either Rowan MCP tools or the
|
|
35
|
+
Rowan Python SDK.
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
### Claude Code
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
claude plugin marketplace add https://github.com/rowansci/rowan-python.git
|
|
41
|
+
claude plugin install computational-chemistry-and-biology@rowan
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Codex
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
codex plugin marketplace add rowansci/rowan-python --ref master
|
|
48
|
+
codex plugin add computational-chemistry-and-biology@rowan
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Start a new Claude Code or Codex session after installation. For manual installation, download the
|
|
52
|
+
[latest skill ZIP](https://github.com/rowansci/rowan-python/releases/download/skill-latest/computational-chemistry-and-biology-skill.zip)
|
|
53
|
+
and extract it into your agent's skills directory.
|
|
36
54
|
|
|
37
55
|
## Running examples
|
|
38
56
|
|
|
@@ -6,11 +6,12 @@ rowan/constants.py,sha256=emCH4m9OL2Hm5E-6mJGM_FgzrK_JrZT-FiKJ6pMNQ4Y,84
|
|
|
6
6
|
rowan/folder.py,sha256=RRg68dObluJ2RCTKX3rb7RxziOXOQUYxkfQSU2R5VMk,13708
|
|
7
7
|
rowan/molecule.py,sha256=1l-3YCN_A75Uz_Q-dMWuhkeF8rhD1z2JQM7AENyjKYM,15512
|
|
8
8
|
rowan/project.py,sha256=RtxYE9jv3Yz6fH5I56iDGRG5EbwlOHiSK-HF1uxc0d4,4582
|
|
9
|
-
rowan/protein.py,sha256=
|
|
9
|
+
rowan/protein.py,sha256=uFMF2BXgIvn6KOw3cdE-LDvZPPbjAxqqwgxakLd85XA,17763
|
|
10
10
|
rowan/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
rowan/test_utils.py,sha256=DTuZqftx6ycIatajAxSGWgDaGglLvTw2TYnVJHiiNdo,1936
|
|
11
12
|
rowan/types.py,sha256=MvcYvEYr6N9RXLal44MqsnMxoWfcBa-VLD_rGsW4HOc,591
|
|
12
13
|
rowan/user.py,sha256=rOCYVhSoRY71fCG_OCSD7cLAWWnAMANWGitF3ogfq30,6212
|
|
13
|
-
rowan/utils.py,sha256=
|
|
14
|
+
rowan/utils.py,sha256=J-3o9_ZlOrl4_ZuuC1yjEOPdD1IkKO0BOXBncV3Ij7c,5670
|
|
14
15
|
rowan/workflows/__init__.py,sha256=pkmAl8aPDznu15bSuRW0j8id-B7Xmzy_tq3LgZfQ7NQ,4786
|
|
15
16
|
rowan/workflows/admet.py,sha256=jDx1Kkgmp0XaeojPzaCFY_yUwvqtSM7Cezps-93cqPw,2865
|
|
16
17
|
rowan/workflows/analogue_docking.py,sha256=TshOw71RmK5XaaTIHz_Q1opiDU9x1L38tXwxoYUZaxY,9756
|
|
@@ -51,7 +52,7 @@ rowan/workflows/solvent_dependent_conformers.py,sha256=Z2xESmcM8WcTsR2TSTUa--Rrh
|
|
|
51
52
|
rowan/workflows/spin_states.py,sha256=rjkgie2-XVNIN7O6P93yn6EPv9Ogjy8iTj3ufZIsUgY,9331
|
|
52
53
|
rowan/workflows/strain.py,sha256=kCW_BlX__sdQG1JVbFZuB-57rkpnxa_JCw3h4uKdGDk,6425
|
|
53
54
|
rowan/workflows/tautomer_search.py,sha256=mbRl0ZJ7wibueRF8c8_idhXJ1rtwg7LBt--5QwQ_Cck,5767
|
|
54
|
-
rowan_python-3.1.
|
|
55
|
-
rowan_python-3.1.
|
|
56
|
-
rowan_python-3.1.
|
|
57
|
-
rowan_python-3.1.
|
|
55
|
+
rowan_python-3.1.7.dist-info/METADATA,sha256=mCUORIhOBiBBn69iu-_EXm3QUnUuv6wwDKKK84cio3o,2436
|
|
56
|
+
rowan_python-3.1.7.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
57
|
+
rowan_python-3.1.7.dist-info/licenses/LICENSE,sha256=i05z7xEhyrg6f8j0lR3XYjShnF-MJGFQ-DnpsZ8yiVI,1084
|
|
58
|
+
rowan_python-3.1.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|