This is an automated email from the ASF dual-hosted git repository.
lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 579f7abe4c [python] Improve error response parsing in HTTP client
(#7276)
579f7abe4c is described below
commit 579f7abe4cf09a1910693ff1c75d1a684f4f0107
Author: Jiajia Li <[email protected]>
AuthorDate: Fri Feb 13 21:25:54 2026 +0800
[python] Improve error response parsing in HTTP client (#7276)
---
paimon-python/pypaimon/api/client.py | 26 ++++-----
paimon-python/pypaimon/tests/binary_row_test.py | 5 ++
.../pypaimon/tests/data_evolution_test.py | 5 ++
.../manifest/manifest_entry_identifier_test.py | 5 ++
paimon-python/pypaimon/tests/predicates_test.py | 5 ++
.../pypaimon/tests/py36/ao_predicate_test.py | 5 ++
.../pypaimon/tests/py36/data_evolution_test.py | 5 ++
paimon-python/pypaimon/tests/rest/client_test.py | 62 ++++++++++++++++++++++
.../pypaimon/tests/shard_table_updator_test.py | 5 ++
9 files changed, 110 insertions(+), 13 deletions(-)
diff --git a/paimon-python/pypaimon/api/client.py
b/paimon-python/pypaimon/api/client.py
index 56b6e44499..c2f38912f4 100644
--- a/paimon-python/pypaimon/api/client.py
+++ b/paimon-python/pypaimon/api/client.py
@@ -223,23 +223,23 @@ def _normalize_uri(uri: str) -> str:
def _parse_error_response(response_body: Optional[str], status_code: int) ->
ErrorResponse:
+ error = None
if response_body:
try:
- return JSON.from_json(response_body, ErrorResponse)
- except Exception:
- return ErrorResponse(
- resource_type=None,
- resource_name=None,
- message=response_body,
- code=status_code
- )
- else:
+ error = JSON.from_json(response_body, ErrorResponse)
+ except json.JSONDecodeError:
+ # ignore exception
+ pass
+
+ if error is None or not error.message:
return ErrorResponse(
- resource_type=None,
- resource_name=None,
- message="response body is null",
- code=status_code
+ resource_type=error.resource_type if error and error.resource_type
else "",
+ resource_name=error.resource_name if error and error.resource_name
else "",
+ message=response_body if response_body else "response body is
null",
+ code=error.code if error and error.code else status_code
)
+
+ return error
def _get_headers_with_params(path: str, query_params: Dict[str, str],
diff --git a/paimon-python/pypaimon/tests/binary_row_test.py
b/paimon-python/pypaimon/tests/binary_row_test.py
index 1f440a9969..a0f42913d9 100644
--- a/paimon-python/pypaimon/tests/binary_row_test.py
+++ b/paimon-python/pypaimon/tests/binary_row_test.py
@@ -17,6 +17,7 @@
################################################################################
import os
import random
+import shutil
import tempfile
import unittest
from typing import List
@@ -76,6 +77,10 @@ class BinaryRowTest(unittest.TestCase):
write.close()
commit.close()
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(cls.tempdir, ignore_errors=True)
+
def test_not_equal_append(self):
table = self.catalog.get_table('default.test_append')
self._overwrite_manifest_entry(table)
diff --git a/paimon-python/pypaimon/tests/data_evolution_test.py
b/paimon-python/pypaimon/tests/data_evolution_test.py
index 9f95672a19..b86e544982 100644
--- a/paimon-python/pypaimon/tests/data_evolution_test.py
+++ b/paimon-python/pypaimon/tests/data_evolution_test.py
@@ -16,6 +16,7 @@ See the License for the specific language governing
permissions and
limitations under the License.
"""
import os
+import shutil
import tempfile
import unittest
from types import SimpleNamespace
@@ -38,6 +39,10 @@ class DataEvolutionTest(unittest.TestCase):
})
cls.catalog.create_database('default', False)
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(cls.tempdir, ignore_errors=True)
+
def test_basic(self):
simple_pa_schema = pa.schema([
('f0', pa.int8()),
diff --git
a/paimon-python/pypaimon/tests/manifest/manifest_entry_identifier_test.py
b/paimon-python/pypaimon/tests/manifest/manifest_entry_identifier_test.py
index fb91b12eec..f78c86bce0 100644
--- a/paimon-python/pypaimon/tests/manifest/manifest_entry_identifier_test.py
+++ b/paimon-python/pypaimon/tests/manifest/manifest_entry_identifier_test.py
@@ -15,6 +15,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
+import shutil
import tempfile
import unittest
@@ -40,6 +41,10 @@ class ManifestEntryIdentifierTest(unittest.TestCase):
cls.catalog =
FileSystemCatalog(Options({CatalogOptions.WAREHOUSE.key(): cls.tempdir}))
cls.catalog.create_database('default', False)
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(cls.tempdir, ignore_errors=True)
+
def setUp(self):
try:
table_identifier = Identifier.from_string('default.test_table')
diff --git a/paimon-python/pypaimon/tests/predicates_test.py
b/paimon-python/pypaimon/tests/predicates_test.py
index bceae05c4c..6db0ce6e61 100644
--- a/paimon-python/pypaimon/tests/predicates_test.py
+++ b/paimon-python/pypaimon/tests/predicates_test.py
@@ -17,6 +17,7 @@
################################################################################
import os
import random
+import shutil
import tempfile
import unittest
@@ -83,6 +84,10 @@ class PredicateTest(unittest.TestCase):
cls.df = df
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(cls.tempdir, ignore_errors=True)
+
def test_wrong_field_name(self):
table = self.catalog.get_table('default.test_append')
predicate_builder = table.new_read_builder().new_predicate_builder()
diff --git a/paimon-python/pypaimon/tests/py36/ao_predicate_test.py
b/paimon-python/pypaimon/tests/py36/ao_predicate_test.py
index a3b3e0ec9f..7858eb87f7 100644
--- a/paimon-python/pypaimon/tests/py36/ao_predicate_test.py
+++ b/paimon-python/pypaimon/tests/py36/ao_predicate_test.py
@@ -16,6 +16,7 @@
# limitations under the License.
################################################################################
import os
+import shutil
import tempfile
import unittest
@@ -58,6 +59,10 @@ class AOPredicatePy36Test(unittest.TestCase):
cls.df = df
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(cls.tempdir, ignore_errors=True)
+
def test_wrong_field_name(self):
table = self.catalog.get_table('default.test_append')
predicate_builder = table.new_read_builder().new_predicate_builder()
diff --git a/paimon-python/pypaimon/tests/py36/data_evolution_test.py
b/paimon-python/pypaimon/tests/py36/data_evolution_test.py
index 90abd2f916..5c5fb30562 100644
--- a/paimon-python/pypaimon/tests/py36/data_evolution_test.py
+++ b/paimon-python/pypaimon/tests/py36/data_evolution_test.py
@@ -16,6 +16,7 @@ See the License for the specific language governing
permissions and
limitations under the License.
"""
import os
+import shutil
import tempfile
import unittest
@@ -33,6 +34,10 @@ class DataEvolutionTest(unittest.TestCase):
})
cls.catalog.create_database('default', False)
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(cls.tempdir, ignore_errors=True)
+
def test_basic(self):
simple_pa_schema = pa.schema([
('f0', pa.int8()),
diff --git a/paimon-python/pypaimon/tests/rest/client_test.py
b/paimon-python/pypaimon/tests/rest/client_test.py
new file mode 100644
index 0000000000..6f381214c7
--- /dev/null
+++ b/paimon-python/pypaimon/tests/rest/client_test.py
@@ -0,0 +1,62 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+from pypaimon.api.client import _parse_error_response
+
+
+class HttpClientTest(unittest.TestCase):
+ def test_parse_error_response_with_valid_json(self):
+ """Test parsing a valid error response JSON"""
+ response_body = (
+ '{"message": "Table not found", "code": 404, '
+ '"resourceType": "table", "resourceName": "my_table"}'
+ )
+ error = _parse_error_response(response_body, 404)
+
+ self.assertEqual(error.message, "Table not found")
+ self.assertEqual(error.code, 404)
+ self.assertEqual(error.resource_type, "table")
+ self.assertEqual(error.resource_name, "my_table")
+
+ def test_parse_error_response_with_unparsable_json(self):
+ # Test unparsable JSON with uppercase fields
+ response_body = '{"Message":"Your request is denied as lack of ssl
protect.","Code":"InvalidProtocol.NeedSsl"}'
+ error = _parse_error_response(response_body, 403)
+ self.assertEqual(error.message, response_body)
+ self.assertEqual(error.code, 403)
+ self.assertEqual(error.resource_type, '')
+ self.assertEqual(error.resource_name, '')
+
+ # Test plain text response
+ response_body = "Internal Server Error: Database connection failed"
+ error = _parse_error_response(response_body, 500)
+ self.assertEqual(error.message, response_body)
+ self.assertEqual(error.code, 500)
+ self.assertEqual(error.resource_type, '')
+ self.assertEqual(error.resource_name, '')
+
+ # Test null body
+ error = _parse_error_response(None, 500)
+ self.assertEqual(error.message, "response body is null")
+ self.assertEqual(error.code, 500)
+ self.assertEqual(error.resource_type, '')
+ self.assertEqual(error.resource_name, '')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/paimon-python/pypaimon/tests/shard_table_updator_test.py
b/paimon-python/pypaimon/tests/shard_table_updator_test.py
index 641f545b47..967dfbcd6e 100644
--- a/paimon-python/pypaimon/tests/shard_table_updator_test.py
+++ b/paimon-python/pypaimon/tests/shard_table_updator_test.py
@@ -16,6 +16,7 @@ See the License for the specific language governing
permissions and
limitations under the License.
"""
import os
+import shutil
import tempfile
import unittest
@@ -37,6 +38,10 @@ class ShardTableUpdatorTest(unittest.TestCase):
cls.catalog.create_database('default', False)
cls.table_count = 0
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(cls.tempdir, ignore_errors=True)
+
def _create_unique_table_name(self, prefix='test'):
ShardTableUpdatorTest.table_count += 1
return f'default.{prefix}_{ShardTableUpdatorTest.table_count}'