This is an automated email from the ASF dual-hosted git repository. jerryshao pushed a commit to branch branch-1.3 in repository https://gitbox.apache.org/repos/asf/gravitino.git
commit 50261f1383f596021c9b026c6b7e5fbc032c4379 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` (cherry picked from commit a7d221cd0a33fbce27931bb7889bee7ba32385c2) --- .../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 + ) + )
