Skip to content

Commit 7b4885f

Browse files
authored
Identify AI agents in API requests (#49)
1 parent 953ecd7 commit 7b4885f

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed

src/codeocean/client.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,32 @@
1313

1414
@dataclass
1515
class CodeOcean:
16+
"""
17+
Code Ocean API client.
18+
19+
This class provides a unified interface to access Code Ocean's API endpoints
20+
for managing capsules, pipelines, computations, and data assets.
21+
22+
Fields:
23+
domain: The Code Ocean domain URL (e.g., 'https://codeocean.acme.com')
24+
token: Code Ocean API access token
25+
retries: Optional retry configuration for failed HTTP requests. Can be an integer
26+
(number of retries) or a urllib3.util.Retry object for advanced
27+
retry configuration. Defaults to 0 (no retries)
28+
agent_id: Optional agent identifier for tracking AI agent API usage on behalf of users
29+
"""
1630

1731
domain: str
1832
token: str
1933
retries: Optional[Retry | int] = 0
34+
agent_id: Optional[str] = None
2035

2136
def __post_init__(self):
2237
self.session = BaseUrlSession(base_url=f"{self.domain}/api/v1/")
2338
self.session.auth = (self.token, "")
2439
self.session.headers.update({"Content-Type": "application/json"})
40+
if self.agent_id:
41+
self.session.headers.update({"Agent-Id": self.agent_id})
2542
self.session.hooks["response"] = [
2643
lambda response, *args, **kwargs: response.raise_for_status()
2744
]

tests/test_client.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import unittest
2+
from unittest.mock import patch
3+
from urllib3.util import Retry
4+
5+
from codeocean.client import CodeOcean
6+
7+
8+
class TestClient(unittest.TestCase):
9+
"""Test cases for the CodeOcean client class."""
10+
11+
def setUp(self):
12+
"""Set up test fixtures."""
13+
self.test_domain = "https://codeocean.acme.com"
14+
self.test_token = "test_token_123"
15+
self.test_agent_id = "test_agent_456"
16+
17+
def test_basic_init(self):
18+
"""Test a basic client initialization."""
19+
client = CodeOcean(
20+
domain=self.test_domain,
21+
token=self.test_token,
22+
)
23+
24+
# Verify base URL is set correctly
25+
self.assertEqual(client.session.base_url, f"{self.test_domain}/api/v1/")
26+
27+
# Verify auth is correctly set
28+
self.assertEqual(client.session.auth, (self.test_token, ""))
29+
30+
# Verify the session headers are correctly configured
31+
headers = client.session.headers
32+
self.assertIn("Content-Type", headers)
33+
self.assertEqual(headers["Content-Type"], "application/json")
34+
35+
@patch("codeocean.client.TCPKeepAliveAdapter")
36+
def test_retry_configuration_types(self, mock_adapter):
37+
"""Test that both integer and Retry object work for retries parameter."""
38+
# Test with integer
39+
CodeOcean(
40+
domain=self.test_domain,
41+
token=self.test_token,
42+
retries=5,
43+
)
44+
45+
# Test with Retry object
46+
retry_obj = Retry(total=3, backoff_factor=0.3)
47+
CodeOcean(
48+
domain=self.test_domain,
49+
token=self.test_token,
50+
retries=retry_obj,
51+
)
52+
53+
# Assert both configurations work
54+
self.assertEqual(mock_adapter.call_count, 2)
55+
mock_adapter.assert_any_call(max_retries=5)
56+
mock_adapter.assert_any_call(max_retries=retry_obj)
57+
58+
def test_agent_id_header_set_when_provided(self):
59+
"""Test that Agent-Id header is set when agent_id is provided."""
60+
client = CodeOcean(
61+
domain=self.test_domain,
62+
token=self.test_token,
63+
agent_id=self.test_agent_id,
64+
)
65+
66+
# Verify the session headers are correctly configured
67+
headers = client.session.headers
68+
self.assertIn("Agent-Id", headers)
69+
self.assertEqual(headers["Agent-Id"], self.test_agent_id)
70+
71+
def test_agent_id_header_not_set_when_none(self):
72+
"""Test that Agent-Id header is not set when agent_id is None."""
73+
client = CodeOcean(
74+
domain=self.test_domain,
75+
token=self.test_token,
76+
agent_id=None,
77+
)
78+
79+
# Verify the session headers are correctly configured
80+
headers = client.session.headers
81+
self.assertNotIn("Agent-Id", headers)

0 commit comments

Comments
 (0)