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 a7d221cd0a [#12181] feat(client-python): Support tags for views and
functions (#12182)
a7d221cd0a is described below
commit a7d221cd0a33fbce27931bb7889bee7ba32385c2
Author: Zhiguo Wu <[email protected]>
AuthorDate: Mon Jul 27 14:36:40 2026 +0800
[#12181] feat(client-python): Support tags for views and functions (#12182)
### What changes were proposed in this pull request?
This PR adds Python client support for tag operations on views and
functions.
Changes include:
- Add default `supports_tags()` methods to the `View` and `Function` API
classes.
- Extend `GenericView` and `GenericFunction` with tag operations backed
by `MetadataObjectTagOperations`.
- Return tag-capable wrappers from Python client view and function
operations.
- Add Python client unit and integration tests for view/function tag
support.
### Why are the changes needed?
#11844 enables server-side tag association for `VIEW` and `FUNCTION`
metadata objects. The Python client should expose the same capability so
users can manage tags for views and functions through the typed client
API, consistent with other tag-capable metadata objects.
Fix: #12181
Related: #11844
### Does this PR introduce _any_ user-facing change?
Yes.
Python client users can now call `supports_tags()` on `View` and
`Function` objects returned by the client, for example:
```python
view.supports_tags().associate_tags(...)
function.supports_tags().list_tags()
```
### How was this patch tested?
- `./gradlew :clients:client-python:black :clients:client-python:pylint
:clients:client-python:unitTests`
- `./gradlew :clients:client-python:integrationTest`
---
.../gravitino/api/function/function.py | 13 +++
clients/client-python/gravitino/api/rel/view.py | 13 +++
.../client/function_catalog_operations.py | 17 +++-
.../gravitino/client/generic_function.py | 56 ++++++++++-
.../client-python/gravitino/client/generic_view.py | 46 ++++++++-
.../client/metadata_object_tag_operations.py | 5 +-
.../gravitino/client/relational_catalog.py | 6 +-
.../tests/integration/test_supports_tags.py | 107 +++++++++++++++++++++
.../tests/unittests/api/function/__init__.py | 16 +++
.../tests/unittests/api/function/test_function.py | 35 +++++++
.../tests/unittests/api/rel/test_view.py | 7 ++
.../tests/unittests/test_generic_function.py | 79 +++++++++++++++
.../tests/unittests/test_generic_view.py | 24 +++++
13 files changed, 411 insertions(+), 13 deletions(-)
diff --git a/clients/client-python/gravitino/api/function/function.py
b/clients/client-python/gravitino/api/function/function.py
index 4aec6550f4..8eff5dc546 100644
--- a/clients/client-python/gravitino/api/function/function.py
+++ b/clients/client-python/gravitino/api/function/function.py
@@ -21,6 +21,8 @@ from typing import List, Optional
from gravitino.api.auditable import Auditable
from gravitino.api.function.function_definition import FunctionDefinition
from gravitino.api.function.function_type import FunctionType
+from gravitino.api.tag.supports_tags import SupportsTags
+from gravitino.exceptions.base import UnsupportedOperationException
class Function(Auditable):
@@ -61,3 +63,14 @@ class Function(Auditable):
def definitions(self) -> List[FunctionDefinition]:
"""Returns the definitions of the function."""
pass
+
+ def supports_tags(self) -> SupportsTags:
+ """Return tag operations if the function supports tags.
+
+ Raises:
+ UnsupportedOperationException: If the function does not support
tag operations.
+
+ Returns:
+ SupportsTags: The tag operations supported by the function.
+ """
+ raise UnsupportedOperationException("Function does not support tag
operations.")
diff --git a/clients/client-python/gravitino/api/rel/view.py
b/clients/client-python/gravitino/api/rel/view.py
index 7db72a6750..fd495f0342 100644
--- a/clients/client-python/gravitino/api/rel/view.py
+++ b/clients/client-python/gravitino/api/rel/view.py
@@ -22,6 +22,8 @@ from gravitino.api.auditable import Auditable
from gravitino.api.rel.column import Column
from gravitino.api.rel.representation import Representation
from gravitino.api.rel.sql_representation import SQLRepresentation
+from gravitino.api.tag.supports_tags import SupportsTags
+from gravitino.exceptions.base import UnsupportedOperationException
class View(Auditable):
@@ -66,3 +68,14 @@ class View(Auditable):
def properties(self) -> dict[str, str]:
"""Returns the view properties."""
return {}
+
+ def supports_tags(self) -> SupportsTags:
+ """Return tag operations if the view supports tags.
+
+ Raises:
+ UnsupportedOperationException: If the view does not support tag
operations.
+
+ Returns:
+ SupportsTags: The tag operations supported by the view.
+ """
+ raise UnsupportedOperationException("View does not support tag
operations.")
diff --git
a/clients/client-python/gravitino/client/function_catalog_operations.py
b/clients/client-python/gravitino/client/function_catalog_operations.py
index 46050c7812..1d2e533ff5 100644
--- a/clients/client-python/gravitino/client/function_catalog_operations.py
+++ b/clients/client-python/gravitino/client/function_catalog_operations.py
@@ -132,7 +132,10 @@ class FunctionCatalogOperations(FunctionCatalog):
function_list_response = FunctionListResponse.from_json(resp.body)
function_list_response.validate()
- return [GenericFunction(func) for func in
function_list_response.functions()]
+ return [
+ GenericFunction(func, self._rest_client, full_namespace)
+ for func in function_list_response.functions()
+ ]
def get_function(self, ident: NameIdentifier) -> Function:
"""Get a function by NameIdentifier from the catalog.
@@ -158,7 +161,9 @@ class FunctionCatalogOperations(FunctionCatalog):
function_response = FunctionResponse.from_json(resp.body)
function_response.validate()
- return GenericFunction(function_response.function())
+ return GenericFunction(
+ function_response.function(), self._rest_client, full_namespace
+ )
def register_function(
self,
@@ -211,7 +216,9 @@ class FunctionCatalogOperations(FunctionCatalog):
function_response = FunctionResponse.from_json(resp.body)
function_response.validate()
- return GenericFunction(function_response.function())
+ return GenericFunction(
+ function_response.function(), self._rest_client, full_namespace
+ )
def alter_function(
self, ident: NameIdentifier, *changes: FunctionChange
@@ -250,7 +257,9 @@ class FunctionCatalogOperations(FunctionCatalog):
function_response = FunctionResponse.from_json(resp.body)
function_response.validate()
- return GenericFunction(function_response.function())
+ return GenericFunction(
+ function_response.function(), self._rest_client, full_namespace
+ )
def drop_function(self, ident: NameIdentifier) -> bool:
"""Drop a function from the catalog.
diff --git a/clients/client-python/gravitino/client/generic_function.py
b/clients/client-python/gravitino/client/generic_function.py
index 634084b291..2ef6bdfd72 100644
--- a/clients/client-python/gravitino/client/generic_function.py
+++ b/clients/client-python/gravitino/client/generic_function.py
@@ -20,20 +20,47 @@ from typing import List, Optional
from gravitino.api.function.function import Function
from gravitino.api.function.function_definition import FunctionDefinition
from gravitino.api.function.function_type import FunctionType
+from gravitino.api.metadata_object import MetadataObject
+from gravitino.api.metadata_objects import MetadataObjects
+from gravitino.api.tag.supports_tags import SupportsTags
+from gravitino.api.tag.tag import Tag
+from gravitino.client.metadata_object_tag_operations import
MetadataObjectTagOperations
from gravitino.dto.audit_dto import AuditDTO
from gravitino.dto.function.function_dto import FunctionDTO
+from gravitino.namespace import Namespace
+from gravitino.utils.http_client import HTTPClient
-class GenericFunction(Function):
+class GenericFunction(Function, SupportsTags):
"""A generic implementation of the Function interface."""
- def __init__(self, function_dto: FunctionDTO):
+ def __init__(
+ self,
+ function_dto: FunctionDTO,
+ rest_client: HTTPClient,
+ function_namespace: Namespace,
+ ):
"""Create a GenericFunction from a FunctionDTO.
Args:
function_dto: The function DTO.
+ rest_client: The REST client for tag operations.
+ function_namespace: The full function namespace in
metalake.catalog.schema format.
"""
self._function_dto = function_dto
+ function_object: MetadataObject = MetadataObjects.of(
+ [
+ function_namespace.level(1),
+ function_namespace.level(2),
+ function_dto.name(),
+ ],
+ MetadataObject.Type.FUNCTION,
+ )
+ self._object_tag_operations = MetadataObjectTagOperations(
+ function_namespace.level(0),
+ function_object,
+ rest_client,
+ )
def name(self) -> str:
"""Returns the function name."""
@@ -58,3 +85,28 @@ class GenericFunction(Function):
def audit_info(self) -> Optional[AuditDTO]:
"""Returns the audit information."""
return self._function_dto.audit_info()
+
+ def list_tags(self) -> list[str]:
+ """List the tag names associated with the function."""
+ return self._object_tag_operations.list_tags()
+
+ def list_tags_info(self) -> list[Tag]:
+ """List the tags associated with the function."""
+ return self._object_tag_operations.list_tags_info()
+
+ def get_tag(self, name: str) -> Tag:
+ """Get an associated tag by name."""
+ return self._object_tag_operations.get_tag(name)
+
+ def associate_tags(
+ self, tags_to_add: list[str], tags_to_remove: list[str]
+ ) -> list[str]:
+ """Associate or disassociate tags with the function."""
+ return self._object_tag_operations.associate_tags(
+ tags_to_add,
+ tags_to_remove,
+ )
+
+ def supports_tags(self) -> SupportsTags:
+ """Return the function's tag operations."""
+ return self
diff --git a/clients/client-python/gravitino/client/generic_view.py
b/clients/client-python/gravitino/client/generic_view.py
index ce4e2a765c..991a2e4ce0 100644
--- a/clients/client-python/gravitino/client/generic_view.py
+++ b/clients/client-python/gravitino/client/generic_view.py
@@ -18,25 +18,49 @@
from typing import Optional
from gravitino.api.audit import Audit
+from gravitino.api.metadata_object import MetadataObject
+from gravitino.api.metadata_objects import MetadataObjects
from gravitino.api.rel.column import Column
from gravitino.api.rel.representation import Representation
from gravitino.api.rel.view import View
+from gravitino.api.tag.supports_tags import SupportsTags
+from gravitino.api.tag.tag import Tag
+from gravitino.client.metadata_object_tag_operations import
MetadataObjectTagOperations
from gravitino.dto.rel.view_dto import ViewDTO
+from gravitino.namespace import Namespace
+from gravitino.utils.http_client import HTTPClient
-class GenericView(View):
+class GenericView(View, SupportsTags):
"""A generic implementation of the View interface."""
def __init__(
self,
view_dto: ViewDTO,
+ rest_client: HTTPClient,
+ view_namespace: Namespace,
):
"""Create a GenericView from a ViewDTO.
Args:
view_dto: The view DTO.
+ rest_client: The REST client for tag operations.
+ view_namespace: The full view namespace in metalake.catalog.schema
format.
"""
self._view_dto = view_dto
+ view_object: MetadataObject = MetadataObjects.of(
+ [
+ view_namespace.level(1),
+ view_namespace.level(2),
+ view_dto.name(),
+ ],
+ MetadataObject.Type.VIEW,
+ )
+ self._object_tag_operations = MetadataObjectTagOperations(
+ view_namespace.level(0),
+ view_object,
+ rest_client,
+ )
def name(self) -> str:
return self._view_dto.name()
@@ -61,3 +85,23 @@ class GenericView(View):
def audit_info(self) -> Audit:
return self._view_dto.audit_info()
+
+ def list_tags(self) -> list[str]:
+ return self._object_tag_operations.list_tags()
+
+ def list_tags_info(self) -> list[Tag]:
+ return self._object_tag_operations.list_tags_info()
+
+ def get_tag(self, name: str) -> Tag:
+ return self._object_tag_operations.get_tag(name)
+
+ def associate_tags(
+ self, tags_to_add: list[str], tags_to_remove: list[str]
+ ) -> list[str]:
+ return self._object_tag_operations.associate_tags(
+ tags_to_add,
+ tags_to_remove,
+ )
+
+ def supports_tags(self) -> SupportsTags:
+ return self
diff --git
a/clients/client-python/gravitino/client/metadata_object_tag_operations.py
b/clients/client-python/gravitino/client/metadata_object_tag_operations.py
index 499486019c..8f46ed0c6d 100644
--- a/clients/client-python/gravitino/client/metadata_object_tag_operations.py
+++ b/clients/client-python/gravitino/client/metadata_object_tag_operations.py
@@ -36,9 +36,8 @@ from gravitino.utils.string_utils import StringUtils
class MetadataObjectTagOperations(SupportsTags):
"""
- The implementation of SupportsTags. This helper is composed into metadata
objects,
- including catalog, schema, table, column, fileset, and topic, to provide
tag
- operations for these objects.
+ The implementation of SupportsTags. This helper is composed into supported
metadata
+ objects to provide tag operations.
"""
TAG_REQUEST_PATH = "api/metalakes/{}/objects/{}/{}/tags"
diff --git a/clients/client-python/gravitino/client/relational_catalog.py
b/clients/client-python/gravitino/client/relational_catalog.py
index d42af35804..c8cffbe662 100644
--- a/clients/client-python/gravitino/client/relational_catalog.py
+++ b/clients/client-python/gravitino/client/relational_catalog.py
@@ -381,7 +381,7 @@ class RelationalCatalog(
)
view_resp = ViewResponse.from_json(resp.body, infer_missing=True)
view_resp.validate()
- return GenericView(view_resp.view())
+ return GenericView(view_resp.view(), self.rest_client, full_namespace)
def create_view(
self,
@@ -412,7 +412,7 @@ class RelationalCatalog(
)
view_resp = ViewResponse.from_json(resp.body, infer_missing=True)
view_resp.validate()
- return GenericView(view_resp.view())
+ return GenericView(view_resp.view(), self.rest_client, full_namespace)
def alter_view(self, identifier: NameIdentifier, *changes: ViewChange) ->
View:
self._check_view_name_identifier(identifier)
@@ -429,7 +429,7 @@ class RelationalCatalog(
)
view_resp = ViewResponse.from_json(resp.body, infer_missing=True)
view_resp.validate()
- return GenericView(view_resp.view())
+ return GenericView(view_resp.view(), self.rest_client, full_namespace)
def drop_view(self, identifier: NameIdentifier) -> bool:
self._check_view_name_identifier(identifier)
diff --git a/clients/client-python/tests/integration/test_supports_tags.py
b/clients/client-python/tests/integration/test_supports_tags.py
index 2e13aad2db..39bbe4e75d 100644
--- a/clients/client-python/tests/integration/test_supports_tags.py
+++ b/clients/client-python/tests/integration/test_supports_tags.py
@@ -19,10 +19,18 @@ from random import randint
from gravitino import Catalog, GravitinoAdminClient, GravitinoClient,
GravitinoMetalake
from gravitino.api.file.fileset import Fileset
+from gravitino.api.function.function import Function
+from gravitino.api.function.function_definition import FunctionDefinitions
+from gravitino.api.function.function_type import FunctionType
+from gravitino.api.function.sql_impl import SQLImpl
from gravitino.api.model.model import Model
+from gravitino.api.rel.column import Column
+from gravitino.api.rel.dialects import Dialects
+from gravitino.api.rel.sql_representation import SQLRepresentation
from gravitino.api.rel.table import Table
from gravitino.api.rel.table_catalog import TableCatalog
from gravitino.api.rel.types.types import Types
+from gravitino.api.rel.view import View
from gravitino.api.tag import Tag
from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.dto.rel.column_dto import ColumnDTO
@@ -60,6 +68,8 @@ class TestSupportsTags(IntegrationTestEnv):
_schema_name = "tag_it_schema" + str(randint(0, 1000))
# OTHER
_table_name: str = "tag_it_table" + str(randint(0, 1000))
+ _view_name: str = "tag_it_view" + str(randint(0, 1000))
+ _function_name: str = "tag_it_function" + str(randint(0, 1000))
_fileset_name: str = "tag_it_fileset" + str(randint(0, 1000))
_model_name: str = "tag_it_model" + str(randint(0, 1000))
@@ -81,6 +91,8 @@ class TestSupportsTags(IntegrationTestEnv):
_tag4: Tag
_table_ident: NameIdentifier
+ _view_ident: NameIdentifier
+ _function_ident: NameIdentifier
_fileset_ident: NameIdentifier
_model_ident: NameIdentifier
_hdfs_container: HDFSContainer
@@ -161,6 +173,14 @@ class TestSupportsTags(IntegrationTestEnv):
self._schema_name,
self._table_name,
)
+ self._view_ident: NameIdentifier = NameIdentifier.of(
+ self._schema_name,
+ self._view_name,
+ )
+ self._function_ident: NameIdentifier = NameIdentifier.of(
+ self._schema_name,
+ self._function_name,
+ )
self._fileset_ident: NameIdentifier = NameIdentifier.of(
self._schema_name,
self._fileset_name,
@@ -325,6 +345,93 @@ class TestSupportsTags(IntegrationTestEnv):
)
self._check_no_tag_associated(relational_table.supports_tags())
+ def test_view_tag_operations(self) -> None:
+ """Test tag operations (associate, list, get, dissociate) on view."""
+ self._relational_catalog.as_schemas().create_schema(
+ schema_name=self._schema_name,
+ comment="view it schema",
+ properties={},
+ )
+ table = self.create_test_table()
+ view: View = self._relational_catalog.as_view_catalog().create_view(
+ identifier=self._view_ident,
+ columns=[
+ Column.of("dt", Types.DateType.get()),
+ Column.of("country", Types.StringType.get()),
+ ],
+ representations=[
+ SQLRepresentation(
+ Dialects.HIVE,
+ f"SELECT dt, country FROM {table.name()}",
+ )
+ ],
+ comment="view for tag operations",
+ properties={},
+ )
+
+ view.supports_tags().associate_tags(
+ tags_to_add=[self._tag_name3, self._tag_name4],
+ tags_to_remove=[],
+ )
+ view.supports_tags().associate_tags(
+ tags_to_add=[self._tag_name1, self._tag_name2],
+ tags_to_remove=[self._tag_name3, self._tag_name4],
+ )
+ self._test_list_tags(view.supports_tags())
+ self._test_list_tags_info(view.supports_tags())
+ self._test_get_tag(view.supports_tags())
+
+ view.supports_tags().associate_tags(
+ tags_to_add=[],
+ tags_to_remove=[self._tag_name1, self._tag_name2],
+ )
+ self._check_no_tag_associated(view.supports_tags())
+
+ def test_function_tag_operations(self) -> None:
+ """Test tag operations (associate, list, get, dissociate) on
function."""
+ self._relational_catalog.as_schemas().create_schema(
+ schema_name=self._schema_name,
+ comment="function it schema",
+ properties={},
+ )
+ implementation = (
+ SQLImpl.builder()
+ .with_runtime_type(SQLImpl.RuntimeType.SPARK)
+ .with_sql("SELECT 1")
+ .build()
+ )
+ definition = FunctionDefinitions.of(
+ [],
+ Types.IntegerType.get(),
+ [implementation],
+ )
+ function_catalog = self._relational_catalog.as_function_catalog()
+ function: Function = function_catalog.register_function(
+ ident=self._function_ident,
+ comment="function for tag operations",
+ function_type=FunctionType.SCALAR,
+ deterministic=True,
+ definitions=[definition],
+ )
+
+ function.supports_tags().associate_tags(
+ tags_to_add=[self._tag_name3, self._tag_name4],
+ tags_to_remove=[],
+ )
+ function.supports_tags().associate_tags(
+ tags_to_add=[self._tag_name1, self._tag_name2],
+ tags_to_remove=[self._tag_name3, self._tag_name4],
+ )
+ self._test_list_tags(function.supports_tags())
+ self._test_list_tags_info(function.supports_tags())
+ self._test_get_tag(function.supports_tags())
+
+ function.supports_tags().associate_tags(
+ tags_to_add=[],
+ tags_to_remove=[self._tag_name1, self._tag_name2],
+ )
+ self._check_no_tag_associated(function.supports_tags())
+
def test_column_tag_operations(self) -> None:
"""Test tag operations (associate, list, get, dissociate) on column."""
self._relational_catalog.as_schemas().create_schema(
diff --git a/clients/client-python/tests/unittests/api/function/__init__.py
b/clients/client-python/tests/unittests/api/function/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/clients/client-python/tests/unittests/api/function/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git
a/clients/client-python/tests/unittests/api/function/test_function.py
b/clients/client-python/tests/unittests/api/function/test_function.py
new file mode 100644
index 0000000000..202d4a8dc0
--- /dev/null
+++ b/clients/client-python/tests/unittests/api/function/test_function.py
@@ -0,0 +1,35 @@
+# 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 unittest.mock import Mock
+
+from gravitino.api.function.function import Function
+from gravitino.exceptions.base import UnsupportedOperationException
+
+
+class TestFunction(unittest.TestCase):
+ def test_default_comment(self):
+ function = Mock(spec=Function)
+
+ self.assertIsNone(Function.comment(function))
+
+ def test_supports_tags_raises_exception(self):
+ function = Mock(spec=Function)
+
+ with self.assertRaises(UnsupportedOperationException):
+ Function.supports_tags(function)
diff --git a/clients/client-python/tests/unittests/api/rel/test_view.py
b/clients/client-python/tests/unittests/api/rel/test_view.py
index 19fa1769d7..54f30d3c9b 100644
--- a/clients/client-python/tests/unittests/api/rel/test_view.py
+++ b/clients/client-python/tests/unittests/api/rel/test_view.py
@@ -22,6 +22,7 @@ from gravitino.api.rel.dialects import Dialects
from gravitino.api.rel.representation import Representation
from gravitino.api.rel.sql_representation import SQLRepresentation
from gravitino.api.rel.view import View
+from gravitino.exceptions.base import UnsupportedOperationException
class TestView(unittest.TestCase):
@@ -67,3 +68,9 @@ class TestView(unittest.TestCase):
self.assertIsNone(View.default_catalog(view))
self.assertIsNone(View.default_schema(view))
self.assertEqual({}, View.properties(view))
+
+ def test_supports_tags_raises_exception(self):
+ view = Mock(spec=View)
+
+ with self.assertRaises(UnsupportedOperationException):
+ View.supports_tags(view)
diff --git a/clients/client-python/tests/unittests/test_generic_function.py
b/clients/client-python/tests/unittests/test_generic_function.py
new file mode 100644
index 0000000000..85ad00af1e
--- /dev/null
+++ b/clients/client-python/tests/unittests/test_generic_function.py
@@ -0,0 +1,79 @@
+# 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 gravitino.api.function.function_type import FunctionType
+from gravitino.api.rel.types.types import Types
+from gravitino.api.tag.supports_tags import SupportsTags
+from gravitino.client.generic_function import GenericFunction
+from gravitino.dto.audit_dto import AuditDTO
+from gravitino.dto.function.function_definition_dto import
FunctionDefinitionDTO
+from gravitino.dto.function.function_dto import FunctionDTO
+from gravitino.namespace import Namespace
+from gravitino.utils.http_client import HTTPClient
+
+
+class TestGenericFunction(unittest.TestCase):
+ _rest_client = HTTPClient("http://localhost:8080")
+ _function_namespace = Namespace.of(
+ "demo_metalake",
+ "demo_catalog",
+ "demo_schema",
+ )
+
+ def _generic_function(self) -> GenericFunction:
+ return GenericFunction(
+ FunctionDTO(
+ _name="demo_function",
+ _function_type=FunctionType.SCALAR,
+ _deterministic=True,
+ _definitions=[
+ FunctionDefinitionDTO(
+ _parameters=[],
+ _return_type=Types.IntegerType.get(),
+ _impls=[],
+ )
+ ],
+ _comment="comment",
+ _audit=AuditDTO("creator"),
+ ),
+ self._rest_client,
+ self._function_namespace,
+ )
+
+ def test_generic_function(self) -> None:
+ generic_function = self._generic_function()
+
+ self.assertEqual("demo_function", generic_function.name())
+ self.assertEqual(FunctionType.SCALAR, generic_function.function_type())
+ self.assertTrue(generic_function.deterministic())
+ self.assertEqual("comment", generic_function.comment())
+ self.assertEqual(1, len(generic_function.definitions()))
+ self.assertEqual("creator", generic_function.audit_info().creator())
+
+ def test_extends_supports_tags_class(self) -> None:
+ generic_function = self._generic_function()
+
+ self.assertTrue(issubclass(GenericFunction, SupportsTags))
+ expected_methods = ["list_tags", "list_tags_info", "get_tag",
"associate_tags"]
+ self.assertTrue(
+ all(
+ callable(getattr(generic_function, method, None))
+ for method in expected_methods
+ )
+ )
diff --git a/clients/client-python/tests/unittests/test_generic_view.py
b/clients/client-python/tests/unittests/test_generic_view.py
index a07c15be05..6eac593f5e 100644
--- a/clients/client-python/tests/unittests/test_generic_view.py
+++ b/clients/client-python/tests/unittests/test_generic_view.py
@@ -18,13 +18,23 @@
import unittest
from gravitino.api.rel.dialects import Dialects
+from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.client.generic_view import GenericView
from gravitino.dto.audit_dto import AuditDTO
from gravitino.dto.rel.sql_representation_dto import SQLRepresentationDTO
from gravitino.dto.rel.view_dto import ViewDTO
+from gravitino.namespace import Namespace
+from gravitino.utils.http_client import HTTPClient
class TestGenericView(unittest.TestCase):
+ _rest_client = HTTPClient("http://localhost:8080")
+ _view_namespace = Namespace.of(
+ "demo_metalake",
+ "demo_catalog",
+ "demo_schema",
+ )
+
def _generic_view(
self,
name: str = "demo_view",
@@ -45,6 +55,8 @@ class TestGenericView(unittest.TestCase):
_properties={"key": "value"},
_audit=AuditDTO("creator"),
),
+ self._rest_client,
+ self._view_namespace,
)
def test_generic_view(self) -> None:
@@ -59,3 +71,15 @@ class TestGenericView(unittest.TestCase):
self.assertEqual(1, len(generic_view.representations()))
self.assertEqual("SELECT 1",
generic_view.sql_for(Dialects.TRINO).sql())
self.assertEqual("creator", generic_view.audit_info().creator())
+
+ def test_extends_supports_tags_class(self) -> None:
+ generic_view = self._generic_view()
+
+ self.assertTrue(issubclass(GenericView, SupportsTags))
+ expected_methods = ["list_tags", "list_tags_info", "get_tag",
"associate_tags"]
+ self.assertTrue(
+ all(
+ callable(getattr(generic_view, method, None))
+ for method in expected_methods
+ )
+ )