codeocean 0.7.0__tar.gz → 0.8.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.
@@ -1,6 +1,10 @@
1
1
  CHANGELOG
2
2
  =========
3
3
 
4
+ ## 0.8.0 (2025-07-11)
5
+ - [#50](https://github.com/codeocean/codeocean-sdk-python/pull/50) feat: Add Min-Server-Version header support
6
+ - [#49](https://github.com/codeocean/codeocean-sdk-python/pull/49) feat: Identify AI agents in API requests
7
+
4
8
  ## 0.7.0 (2025-06-24)
5
9
  - [#46](https://github.com/codeocean/codeocean-sdk-python/pull/46) feat: Add API documentation
6
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeocean
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: Code Ocean Python SDK
5
5
  Project-URL: Homepage, https://github.com/codeocean/codeocean-sdk-python
6
6
  Project-URL: Issues, https://github.com/codeocean/codeocean-sdk-python/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "codeocean"
7
- version = "0.7.0"
7
+ version = "0.8.0"
8
8
  authors = [
9
9
  { name="Code Ocean", email="dev@codeocean.com" },
10
10
  ]
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from requests_toolbelt.adapters.socket_options import TCPKeepAliveAdapter
5
+ from requests_toolbelt.sessions import BaseUrlSession
6
+ from typing import Optional
7
+ from urllib3.util import Retry
8
+
9
+ from codeocean.capsule import Capsules
10
+ from codeocean.computation import Computations
11
+ from codeocean.data_asset import DataAssets
12
+
13
+
14
+ @dataclass
15
+ 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
+ """
30
+
31
+ domain: str
32
+ token: str
33
+ retries: Optional[Retry | int] = 0
34
+ agent_id: Optional[str] = None
35
+
36
+ # Minimum server version required by this SDK
37
+ MIN_SERVER_VERSION = "3.6.0"
38
+
39
+ def __post_init__(self):
40
+ self.session = BaseUrlSession(base_url=f"{self.domain}/api/v1/")
41
+ self.session.auth = (self.token, "")
42
+ self.session.headers.update({
43
+ "Content-Type": "application/json",
44
+ "Min-Server-Version": CodeOcean.MIN_SERVER_VERSION,
45
+ })
46
+ if self.agent_id:
47
+ self.session.headers.update({"Agent-Id": self.agent_id})
48
+ self.session.hooks["response"] = [
49
+ lambda response, *args, **kwargs: response.raise_for_status()
50
+ ]
51
+ self.session.mount(self.domain, TCPKeepAliveAdapter(max_retries=self.retries))
52
+
53
+ self.capsules = Capsules(client=self.session)
54
+ self.computations = Computations(client=self.session)
55
+ self.data_assets = DataAssets(client=self.session)
@@ -0,0 +1,83 @@
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
+ self.assertIn("Min-Server-Version", headers)
35
+ self.assertEqual(headers["Min-Server-Version"], CodeOcean.MIN_SERVER_VERSION)
36
+
37
+ @patch("codeocean.client.TCPKeepAliveAdapter")
38
+ def test_retry_configuration_types(self, mock_adapter):
39
+ """Test that both integer and Retry object work for retries parameter."""
40
+ # Test with integer
41
+ CodeOcean(
42
+ domain=self.test_domain,
43
+ token=self.test_token,
44
+ retries=5,
45
+ )
46
+
47
+ # Test with Retry object
48
+ retry_obj = Retry(total=3, backoff_factor=0.3)
49
+ CodeOcean(
50
+ domain=self.test_domain,
51
+ token=self.test_token,
52
+ retries=retry_obj,
53
+ )
54
+
55
+ # Assert both configurations work
56
+ self.assertEqual(mock_adapter.call_count, 2)
57
+ mock_adapter.assert_any_call(max_retries=5)
58
+ mock_adapter.assert_any_call(max_retries=retry_obj)
59
+
60
+ def test_agent_id_header_set_when_provided(self):
61
+ """Test that Agent-Id header is set when agent_id is provided."""
62
+ client = CodeOcean(
63
+ domain=self.test_domain,
64
+ token=self.test_token,
65
+ agent_id=self.test_agent_id,
66
+ )
67
+
68
+ # Verify the session headers are correctly configured
69
+ headers = client.session.headers
70
+ self.assertIn("Agent-Id", headers)
71
+ self.assertEqual(headers["Agent-Id"], self.test_agent_id)
72
+
73
+ def test_agent_id_header_not_set_when_none(self):
74
+ """Test that Agent-Id header is not set when agent_id is None."""
75
+ client = CodeOcean(
76
+ domain=self.test_domain,
77
+ token=self.test_token,
78
+ agent_id=None,
79
+ )
80
+
81
+ # Verify the session headers are correctly configured
82
+ headers = client.session.headers
83
+ self.assertNotIn("Agent-Id", headers)
@@ -1,32 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from requests_toolbelt.adapters.socket_options import TCPKeepAliveAdapter
5
- from requests_toolbelt.sessions import BaseUrlSession
6
- from typing import Optional
7
- from urllib3.util import Retry
8
-
9
- from codeocean.capsule import Capsules
10
- from codeocean.computation import Computations
11
- from codeocean.data_asset import DataAssets
12
-
13
-
14
- @dataclass
15
- class CodeOcean:
16
-
17
- domain: str
18
- token: str
19
- retries: Optional[Retry | int] = 0
20
-
21
- def __post_init__(self):
22
- self.session = BaseUrlSession(base_url=f"{self.domain}/api/v1/")
23
- self.session.auth = (self.token, "")
24
- self.session.headers.update({"Content-Type": "application/json"})
25
- self.session.hooks["response"] = [
26
- lambda response, *args, **kwargs: response.raise_for_status()
27
- ]
28
- self.session.mount(self.domain, TCPKeepAliveAdapter(max_retries=self.retries))
29
-
30
- self.capsules = Capsules(client=self.session)
31
- self.computations = Computations(client=self.session)
32
- self.data_assets = DataAssets(client=self.session)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes