codeocean 0.8.0__tar.gz → 0.9.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 (24) hide show
  1. {codeocean-0.8.0 → codeocean-0.9.0}/CHANGELOG.md +4 -0
  2. {codeocean-0.8.0 → codeocean-0.9.0}/PKG-INFO +1 -1
  3. {codeocean-0.8.0 → codeocean-0.9.0}/pyproject.toml +1 -1
  4. {codeocean-0.8.0 → codeocean-0.9.0}/src/codeocean/__init__.py +1 -0
  5. {codeocean-0.8.0 → codeocean-0.9.0}/src/codeocean/client.py +9 -3
  6. codeocean-0.9.0/src/codeocean/error.py +41 -0
  7. codeocean-0.9.0/tests/test_error.py +140 -0
  8. {codeocean-0.8.0 → codeocean-0.9.0}/.flake8 +0 -0
  9. {codeocean-0.8.0 → codeocean-0.9.0}/.gitignore +0 -0
  10. {codeocean-0.8.0 → codeocean-0.9.0}/LICENSE +0 -0
  11. {codeocean-0.8.0 → codeocean-0.9.0}/README.md +0 -0
  12. {codeocean-0.8.0 → codeocean-0.9.0}/RELEASE.md +0 -0
  13. {codeocean-0.8.0 → codeocean-0.9.0}/examples/create_data_asset.py +0 -0
  14. {codeocean-0.8.0 → codeocean-0.9.0}/examples/run_capsule.py +0 -0
  15. {codeocean-0.8.0 → codeocean-0.9.0}/examples/run_pipeline.py +0 -0
  16. {codeocean-0.8.0 → codeocean-0.9.0}/src/codeocean/capsule.py +0 -0
  17. {codeocean-0.8.0 → codeocean-0.9.0}/src/codeocean/components.py +0 -0
  18. {codeocean-0.8.0 → codeocean-0.9.0}/src/codeocean/computation.py +0 -0
  19. {codeocean-0.8.0 → codeocean-0.9.0}/src/codeocean/data_asset.py +0 -0
  20. {codeocean-0.8.0 → codeocean-0.9.0}/src/codeocean/enum.py +0 -0
  21. {codeocean-0.8.0 → codeocean-0.9.0}/src/codeocean/folder.py +0 -0
  22. {codeocean-0.8.0 → codeocean-0.9.0}/tests/__init__.py +0 -0
  23. {codeocean-0.8.0 → codeocean-0.9.0}/tests/test_client.py +0 -0
  24. {codeocean-0.8.0 → codeocean-0.9.0}/tests/test_package.py +0 -0
@@ -1,6 +1,10 @@
1
1
  CHANGELOG
2
2
  =========
3
3
 
4
+ ## 0.9.0 (2025-08-05)
5
+ - [#47](https://github.com/codeocean/codeocean-sdk-python/pull/47) feat: add error handling
6
+
7
+
4
8
  ## 0.8.0 (2025-07-11)
5
9
  - [#50](https://github.com/codeocean/codeocean-sdk-python/pull/50) feat: Add Min-Server-Version header support
6
10
  - [#49](https://github.com/codeocean/codeocean-sdk-python/pull/49) feat: Identify AI agents in API requests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeocean
3
- Version: 0.8.0
3
+ Version: 0.9.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.8.0"
7
+ version = "0.9.0"
8
8
  authors = [
9
9
  { name="Code Ocean", email="dev@codeocean.com" },
10
10
  ]
@@ -1 +1,2 @@
1
1
  from codeocean.client import CodeOcean # noqa: F401
2
+ from codeocean.error import Error # noqa: F401
@@ -5,10 +5,12 @@ from requests_toolbelt.adapters.socket_options import TCPKeepAliveAdapter
5
5
  from requests_toolbelt.sessions import BaseUrlSession
6
6
  from typing import Optional
7
7
  from urllib3.util import Retry
8
+ import requests
8
9
 
9
10
  from codeocean.capsule import Capsules
10
11
  from codeocean.computation import Computations
11
12
  from codeocean.data_asset import DataAssets
13
+ from codeocean.error import Error
12
14
 
13
15
 
14
16
  @dataclass
@@ -45,11 +47,15 @@ class CodeOcean:
45
47
  })
46
48
  if self.agent_id:
47
49
  self.session.headers.update({"Agent-Id": self.agent_id})
48
- self.session.hooks["response"] = [
49
- lambda response, *args, **kwargs: response.raise_for_status()
50
- ]
50
+ self.session.hooks["response"] = [self._error_handler]
51
51
  self.session.mount(self.domain, TCPKeepAliveAdapter(max_retries=self.retries))
52
52
 
53
53
  self.capsules = Capsules(client=self.session)
54
54
  self.computations = Computations(client=self.session)
55
55
  self.data_assets = DataAssets(client=self.session)
56
+
57
+ def _error_handler(self, response, *args, **kwargs):
58
+ try:
59
+ response.raise_for_status()
60
+ except requests.HTTPError as err:
61
+ raise Error(err) from err
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import requests
5
+
6
+
7
+ class Error(Exception):
8
+ """
9
+ Represents an HTTP error with additional context extracted from the response.
10
+
11
+ Attributes:
12
+ http_err (requests.HTTPError): The HTTP error object.
13
+ status_code (int): The HTTP status code of the error response.
14
+ message (str): A message describing the error, extracted from the response body.
15
+ data (Any): If the response body is json, this attribute contains the json object; otherwise, it is None.
16
+
17
+ Args:
18
+ err (requests.HTTPError): The HTTP error object.
19
+ """
20
+ def __init__(self, err: requests.HTTPError):
21
+ self.http_err = err
22
+ self.status_code = err.response.status_code
23
+ self.message = "An error occurred."
24
+ self.data = None
25
+
26
+ try:
27
+ self.data = err.response.json()
28
+ if isinstance(self.data, dict):
29
+ self.message = self.data.get("message", self.message)
30
+ except Exception:
31
+ # response wasn't JSON – fall back to text
32
+ self.message = err.response.text
33
+
34
+ super().__init__(self.message)
35
+
36
+ def __str__(self) -> str:
37
+ msg = str(self.http_err)
38
+ msg += f"\n\nMessage: {self.message}"
39
+ if self.data:
40
+ msg += "\n\nData:\n" + json.dumps(self.data, indent=2)
41
+ return msg
@@ -0,0 +1,140 @@
1
+ import unittest
2
+ from unittest.mock import Mock
3
+ import requests
4
+
5
+ from codeocean.error import Error
6
+
7
+
8
+ class TestError(unittest.TestCase):
9
+ """Test cases for the Error exception class."""
10
+
11
+ def test_error_is_exception_subclass(self):
12
+ """Test that Error is a subclass of Exception."""
13
+ self.assertTrue(issubclass(Error, Exception))
14
+
15
+ def test_error_with_json_dict(self):
16
+ """Test Error creation with JSON dict response containing message."""
17
+ # Create mock HTTPError and response
18
+ mock_response = Mock()
19
+ mock_response.status_code = 400
20
+ mock_response.json.return_value = {"message": "Custom error message", "datasets": [{"id": "123", "name": "tv"}]}
21
+
22
+ mock_http_error = Mock(spec=requests.HTTPError)
23
+ mock_http_error.response = mock_response
24
+
25
+ # Create Error instance
26
+ error = Error(mock_http_error)
27
+
28
+ # Verify attributes
29
+ self.assertEqual(error.status_code, 400)
30
+ self.assertEqual(error.message, "Custom error message")
31
+ self.assertEqual(error.data, {"message": "Custom error message", "datasets": [{"id": "123", "name": "tv"}]})
32
+
33
+ def test_error_with_json_dict_no_message(self):
34
+ """Test Error creation with JSON dict response without message field."""
35
+ # Create mock HTTPError and response
36
+ mock_response = Mock()
37
+ mock_response.status_code = 500
38
+ mock_response.json.return_value = {"error": "some other field"}
39
+
40
+ mock_http_error = Mock(spec=requests.HTTPError)
41
+ mock_http_error.response = mock_response
42
+
43
+ # Create Error instance
44
+ error = Error(mock_http_error)
45
+
46
+ # Verify attributes
47
+ self.assertEqual(error.status_code, 500)
48
+ self.assertEqual(error.message, "An error occurred.")
49
+ self.assertEqual(error.data, {"error": "some other field"})
50
+
51
+ def test_error_with_json_list(self):
52
+ """Test Error creation with JSON list response."""
53
+ # Create mock HTTPError and response
54
+ mock_response = Mock()
55
+ mock_response.status_code = 403
56
+ mock_response.json.return_value = [{"field": "error1"}, {"field": "error2"}]
57
+
58
+ mock_http_error = Mock(spec=requests.HTTPError)
59
+ mock_http_error.response = mock_response
60
+
61
+ # Create Error instance
62
+ error = Error(mock_http_error)
63
+
64
+ # Verify attributes
65
+ self.assertEqual(error.status_code, 403)
66
+ self.assertEqual(error.message, "An error occurred.")
67
+ self.assertEqual(error.data, [{"field": "error1"}, {"field": "error2"}])
68
+
69
+ def test_error_with_non_json_response(self):
70
+ """Test Error creation when response is not JSON."""
71
+ # Create mock HTTPError and response
72
+ mock_response = Mock()
73
+ mock_response.status_code = 404
74
+ mock_response.json.side_effect = Exception("Not JSON")
75
+ mock_response.text = "Page not found"
76
+
77
+ mock_http_error = Mock(spec=requests.HTTPError)
78
+ mock_http_error.response = mock_response
79
+
80
+ # Create Error instance
81
+ error = Error(mock_http_error)
82
+
83
+ # Verify attributes
84
+ self.assertEqual(error.status_code, 404)
85
+ self.assertEqual(error.message, "Page not found")
86
+ self.assertIsNone(error.data)
87
+
88
+ def test_error_str_method_with_data(self):
89
+ """Test Error __str__ method when data is present."""
90
+ # Create mock HTTPError and response
91
+ mock_response = Mock()
92
+ mock_response.status_code = 400
93
+ mock_response.json.return_value = {"message": "Validation failed", "errors": ["field1", "field2"]}
94
+
95
+ mock_http_error = Mock(spec=requests.HTTPError)
96
+ mock_http_error.response = mock_response
97
+ mock_http_error.__str__ = Mock(return_value="400 Client Error: Bad Request for url: http://example.com")
98
+
99
+ # Create Error instance
100
+ error = Error(mock_http_error)
101
+
102
+ # Test __str__ method
103
+ error_str = str(error)
104
+
105
+ # Verify the string contains expected components
106
+ self.assertEqual(error_str, """400 Client Error: Bad Request for url: http://example.com
107
+
108
+ Message: Validation failed
109
+
110
+ Data:
111
+ {
112
+ "message": "Validation failed",
113
+ "errors": [
114
+ "field1",
115
+ "field2"
116
+ ]
117
+ }""")
118
+
119
+ def test_error_str_method_without_data(self):
120
+ """Test Error __str__ method when data is None."""
121
+ # Create mock HTTPError and response
122
+ mock_response = Mock()
123
+ mock_response.status_code = 404
124
+ mock_response.json.side_effect = Exception("Not JSON")
125
+ mock_response.text = "Page not found"
126
+
127
+ mock_http_error = Mock(spec=requests.HTTPError)
128
+ mock_http_error.response = mock_response
129
+ mock_http_error.__str__ = Mock(return_value="404 Client Error: Not Found for url: http://example.com")
130
+
131
+ # Create Error instance
132
+ error = Error(mock_http_error)
133
+
134
+ # Test __str__ method
135
+ error_str = str(error)
136
+
137
+ # Verify the string contains expected components
138
+ self.assertEqual(error_str, """404 Client Error: Not Found for url: http://example.com
139
+
140
+ Message: Page not found""")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes