This is an automated email from the ASF dual-hosted git repository.
jerryshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 38de02edaf [#12324] fix(client-python): serialize property field in
RemoveCatalogPropertyRequest (#12325)
38de02edaf is described below
commit 38de02edafd0c0ab0aa777ba00dedad973115091
Author: Phước <[email protected]>
AuthorDate: Wed Aug 12 12:49:59 2026 +0700
[#12324] fix(client-python): serialize property field in
RemoveCatalogPropertyRequest (#12325)
### What changes were proposed in this pull request?
`RemoveCatalogPropertyRequest` in
`clients/client-python/gravitino/dto/requests/catalog_update_request.py`
was serialized without the `property` field, so
`GravitinoClient.alter_catalog(...)` with a
`CatalogChange.remove_property(...)` always failed server-side with:
```
IllegalArgumentException: "property" field is required and cannot be empty
```
The class had two compounding defects vs. its siblings: it was missing
the `@dataclass` decorator, and its field was declared as `property:
Optional[str] = None` while `__init__` set `self._property` (a different
attribute). It now matches the sibling pattern
(`RemoveTablePropertyRequest`, `RemoveSchemaPropertyRequest`):
```python
@dataclass
class RemoveCatalogPropertyRequest(CatalogUpdateRequestBase):
_property: Optional[str] = field(
default=None, metadata=config(field_name="property")
)
```
Tests added:
- `tests/unittests/dto/requests/test_catalog_update_request.py` —
serialize/validate coverage for all four catalog update request types
(catalog update requests previously had no unit-test coverage, unlike
table/tag/view/schema).
- `test_alter_catalog_remove_property` integration test in
`tests/integration/test_catalog.py` — sets then removes a catalog
property end-to-end.
### Why are the changes needed?
Any `alter_catalog` call that needs to remove a property is broken (e.g.
config-driven provisioners reconciling catalog properties).
Fix: #12324
### Does this PR introduce _any_ user-facing change?
No. It fixes existing broken behavior for removing catalog properties
via the Python SDK; no API or property-key change.
### How was this patch tested?
- New unit test `test_remove_catalog_property_request_serialize` was red
before the fix (`{"@type": "removeProperty"}`) and green after
(`{"@type": "removeProperty", "property": "prop1"}`).
- Full `tests/unittests/dto/requests/` suite: 91 passed.
- `ruff format --check` passes on changed files.
- Integration test `test_alter_catalog_remove_property` verifies
set+remove end-to-end (requires a running Gravitino server via
`GRAVITINO_HOME`).
Co-authored-by: phuocho <[email protected]>
---
.../dto/requests/catalog_update_request.py | 5 +-
.../tests/integration/test_catalog.py | 20 ++++
.../dto/requests/test_catalog_update_request.py | 108 +++++++++++++++++++++
3 files changed, 132 insertions(+), 1 deletion(-)
diff --git
a/clients/client-python/gravitino/dto/requests/catalog_update_request.py
b/clients/client-python/gravitino/dto/requests/catalog_update_request.py
index 1ea4d9953c..b4ba71c546 100644
--- a/clients/client-python/gravitino/dto/requests/catalog_update_request.py
+++ b/clients/client-python/gravitino/dto/requests/catalog_update_request.py
@@ -105,10 +105,13 @@ class CatalogUpdateRequest:
if not self._value:
raise ValueError('"value" field is required and cannot be
empty')
+ @dataclass
class RemoveCatalogPropertyRequest(CatalogUpdateRequestBase):
"""Request to remove a property from a catalog."""
- property: Optional[str] = None
+ _property: Optional[str] = field(
+ default=None, metadata=config(field_name="property")
+ )
"""The property to remove."""
def __init__(self, catalog_property: str):
diff --git a/clients/client-python/tests/integration/test_catalog.py
b/clients/client-python/tests/integration/test_catalog.py
index d99acb8e84..44464ec7fe 100644
--- a/clients/client-python/tests/integration/test_catalog.py
+++ b/clients/client-python/tests/integration/test_catalog.py
@@ -133,6 +133,26 @@ class TestCatalog(MetalakeTestMixin, IntegrationTestEnv):
)
self.catalog_name = self.catalog_name + "_new"
+ def test_alter_catalog_remove_property(self):
+ self.create_catalog(self.catalog_name)
+
+ property_key = "catalog_remove_property_key"
+ property_value = "catalog_remove_property_value"
+
+ # Set a custom property first so that there is something to remove.
+ catalog = self.gravitino_client.alter_catalog(
+ self.catalog_name,
+ CatalogChange.set_property(property_key, property_value),
+ )
+ self.assertEqual(property_value,
catalog.properties().get(property_key))
+
+ # Remove the property and assert it is gone from the catalog.
+ catalog = self.gravitino_client.alter_catalog(
+ self.catalog_name,
+ CatalogChange.remove_property(property_key),
+ )
+ self.assertNotIn(property_key, catalog.properties())
+
def test_drop_catalog(self):
self.create_catalog(self.catalog_name)
self.gravitino_client.disable_catalog(self.catalog_name)
diff --git
a/clients/client-python/tests/unittests/dto/requests/test_catalog_update_request.py
b/clients/client-python/tests/unittests/dto/requests/test_catalog_update_request.py
new file mode 100644
index 0000000000..afe16e5cc1
--- /dev/null
+++
b/clients/client-python/tests/unittests/dto/requests/test_catalog_update_request.py
@@ -0,0 +1,108 @@
+# 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 json as _json
+import unittest
+
+from gravitino.api.catalog_change import CatalogChange
+from gravitino.dto.requests.catalog_update_request import CatalogUpdateRequest
+
+
+class TestCatalogUpdateRequest(unittest.TestCase):
+ def test_rename_catalog_request_validate(self) -> None:
+ invalid_request = CatalogUpdateRequest.RenameCatalogRequest("")
+
+ with self.assertRaises(ValueError):
+ invalid_request.validate()
+
+ def test_rename_catalog_request_serialize(self) -> None:
+ request = CatalogUpdateRequest.RenameCatalogRequest("newCatalog")
+ json_str = _json.dumps(
+ {
+ "@type": "rename",
+ "newName": "newCatalog",
+ },
+ ensure_ascii=False,
+ )
+
+ self.assertEqual(json_str, request.to_json())
+
+ def test_update_catalog_comment_request_serialize(self) -> None:
+ request = CatalogUpdateRequest.UpdateCatalogCommentRequest("new
comment")
+ json_str = _json.dumps(
+ {
+ "@type": "updateComment",
+ "newComment": "new comment",
+ },
+ ensure_ascii=False,
+ )
+
+ self.assertEqual(json_str, request.to_json())
+
+ def test_set_catalog_property_request_validate(self) -> None:
+ invalid_request1 = CatalogUpdateRequest.SetCatalogPropertyRequest("",
"value")
+ invalid_request2 =
CatalogUpdateRequest.SetCatalogPropertyRequest("key", "")
+
+ with self.assertRaises(ValueError):
+ invalid_request1.validate()
+
+ with self.assertRaises(ValueError):
+ invalid_request2.validate()
+
+ def test_set_catalog_property_request_serialize(self) -> None:
+ request = CatalogUpdateRequest.SetCatalogPropertyRequest("key",
"value1")
+ json_str = _json.dumps(
+ {
+ "@type": "setProperty",
+ "property": "key",
+ "value": "value1",
+ },
+ ensure_ascii=False,
+ )
+
+ self.assertEqual(json_str, request.to_json())
+
+ def test_remove_catalog_property_request_validate(self) -> None:
+ invalid_request = CatalogUpdateRequest.RemoveCatalogPropertyRequest("")
+
+ with self.assertRaises(ValueError):
+ invalid_request.validate()
+
+ # A non-empty property must pass validation.
+ valid_request =
CatalogUpdateRequest.RemoveCatalogPropertyRequest("key")
+ valid_request.validate()
+
+ def test_remove_catalog_property_request_serialize(self) -> None:
+ request = CatalogUpdateRequest.RemoveCatalogPropertyRequest("prop1")
+ json_str = _json.dumps(
+ {
+ "@type": "removeProperty",
+ "property": "prop1",
+ },
+ ensure_ascii=False,
+ )
+
+ self.assertEqual(json_str, request.to_json())
+
+ def test_remove_catalog_property_request_catalog_change(self) -> None:
+ request = CatalogUpdateRequest.RemoveCatalogPropertyRequest("prop1")
+
+ self.assertIsInstance(request.catalog_change(),
CatalogChange.RemoveProperty)
+
+
+if __name__ == "__main__":
+ unittest.main()