karmendra8386 commented on code in PR #14623:
URL: https://github.com/apache/gravitino/pull/14623#discussion_r4467535194
##########
clients/client-python/gravitino/client/gravitino_metalake.py:
##########
@@ -846,6 +879,377 @@ def delete_tag(self, tag_name) -> bool:
return drop_response.dropped()
+ def list_policy_associations_for_tag(self, tag_name) ->
list[PolicyTagAssociation]:
+ """
+ List the policy associations of a tag under a metalake.
+
+ Args:
+ tag_name (str): The name of the tag.
+
+ Returns:
+ list[PolicyTagAssociation]: The policy associations of the tag.
+
+ Raises:
+ NoSuchTagException: If the tag does not exist.
+ """
+ url = self.API_METALAKES_TAG_POLICIES_PATH.format(
+ encode_string(self.name()), encode_string(tag_name)
+ )
+ response = self.rest_client.get(
+ url,
+ params={"details": "true"},
+ error_handler=TAG_ERROR_HANDLER,
+ )
+ resp = PolicyForTagAssociationListResponse.from_json(
+ response.body, infer_missing=True
+ )
+ resp.validate()
+
+ tag = self.get_tag(tag_name)
+ return [
+ GenericPolicyTagAssociation(
+ GenericPolicy(association.policy()),
+ tag,
+ association.selector().to_selector(),
+ )
+ for association in resp.associations()
+ ]
+
+ def add_policy_for_tag(
+ self,
+ tag_name,
+ policy_name,
+ selector: PolicyAssociationSelector = None,
+ ) -> PolicyTagAssociation:
+ """
+ Associate a policy with a tag under a metalake. When no selector is
+ given, the association applies to all values of the tag.
+
+ Args:
+ tag_name (str): The name of the tag.
+ policy_name (str): The name of the policy.
+ selector (PolicyAssociationSelector): The selector scoping the
+ association, or None for all tag values.
+
+ Returns:
+ PolicyTagAssociation: The created policy association.
+
+ Raises:
+ NoSuchTagException: If the tag does not exist.
+ NoSuchPolicyException: If the policy does not exist.
+ PolicyAlreadyAssociatedException: If the policy is already
+ associated with the tag.
+ """
+ if selector is None:
+ selector = AllValuesSelector.get()
+ add_request = PolicyTagAddRequest(
+ PolicyAssociationSelectorDTO.from_selector(selector)
+ )
+ add_request.validate()
+
+ url = self.API_METALAKES_TAG_POLICY_PATH.format(
+ encode_string(self.name()),
+ encode_string(tag_name),
+ encode_string(policy_name),
+ )
+ response = self.rest_client.post(
+ url,
+ json=add_request,
+ error_handler=TAG_ERROR_HANDLER,
Review Comment:
Fixed in 2e5060a05. `TAG_ERROR_HANDLER` now also maps
`NoSuchPolicyException` (404 / code 1003) and
`PolicyAlreadyAssociatedException` (409 / code 1004), keeping all of its tag
mappings. The map is keyed by the response `type`, which the tag endpoints
never return for these types, so the other tag calls are unaffected. Regression
tests run both error responses through the real `HTTPClient` for both
`add_policy_for_tag` and `remove_policy_from_tag` and assert the
policy-specific exceptions, and the handler mapping-table test covers the new
entries as well.
##########
clients/client-python/gravitino/client/metadata_object_policy_operations.py:
##########
@@ -0,0 +1,97 @@
+# 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.
+
+from __future__ import annotations
+
+from gravitino.api.metadata_object import MetadataObject
+from gravitino.api.policy.policy import Policy
+from gravitino.api.policy.policy_list import PolicyList
+from gravitino.api.policy.supports_policies import SupportsPolicies
+from gravitino.client.generic_policy import GenericPolicy
+from gravitino.dto.responses.policy_response import (
+ MetadataObjectPolicyListResponse,
+ PolicyNameListResponse,
+)
+from gravitino.exceptions.handlers.policy_error_handler import
POLICY_ERROR_HANDLER
+from gravitino.rest.rest_utils import encode_string
+from gravitino.utils.http_client import HTTPClient
+
+
+class MetadataObjectPolicyOperations(SupportsPolicies):
Review Comment:
Fixed in 8e5940a46. Following the tag pattern,
`MetadataObjectPolicyOperations` is now composed into `BaseSchemaCatalog`,
`GenericSchema`, `RelationalTable`, `GenericFileset`, `GenericModel` and
`GenericSemanticModel`: each loaded object is a `SupportsPolicies` delegating
to the helper, and `Catalog`/`Schema`/`Table`/`Fileset`/`Model`/`SemanticModel`
declare `supports_policies()` with an unsupported default, mirroring the Java
client's `supportsPolicies()` scope. So `catalog.as_schemas().load_schema("s")`
now returns a `GenericSchema` that is a `SupportsPolicies` with a working
`list_policies()`. The new tests list policies through a loaded schema, table
and catalog (mocked `HTTPClient.get` at the load level) instead of
instantiating the helper directly.
##########
clients/client-python/gravitino/client/gravitino_metalake.py:
##########
@@ -846,6 +879,377 @@ def delete_tag(self, tag_name) -> bool:
return drop_response.dropped()
+ def list_policy_associations_for_tag(self, tag_name) ->
list[PolicyTagAssociation]:
+ """
+ List the policy associations of a tag under a metalake.
+
+ Args:
+ tag_name (str): The name of the tag.
+
+ Returns:
+ list[PolicyTagAssociation]: The policy associations of the tag.
+
+ Raises:
+ NoSuchTagException: If the tag does not exist.
+ """
+ url = self.API_METALAKES_TAG_POLICIES_PATH.format(
+ encode_string(self.name()), encode_string(tag_name)
+ )
+ response = self.rest_client.get(
+ url,
+ params={"details": "true"},
+ error_handler=TAG_ERROR_HANDLER,
+ )
+ resp = PolicyForTagAssociationListResponse.from_json(
+ response.body, infer_missing=True
+ )
+ resp.validate()
+
+ tag = self.get_tag(tag_name)
+ return [
+ GenericPolicyTagAssociation(
+ GenericPolicy(association.policy()),
+ tag,
+ association.selector().to_selector(),
+ )
+ for association in resp.associations()
+ ]
+
+ def add_policy_for_tag(
+ self,
+ tag_name,
+ policy_name,
+ selector: PolicyAssociationSelector = None,
+ ) -> PolicyTagAssociation:
+ """
+ Associate a policy with a tag under a metalake. When no selector is
+ given, the association applies to all values of the tag.
+
+ Args:
+ tag_name (str): The name of the tag.
+ policy_name (str): The name of the policy.
+ selector (PolicyAssociationSelector): The selector scoping the
+ association, or None for all tag values.
+
+ Returns:
+ PolicyTagAssociation: The created policy association.
+
+ Raises:
+ NoSuchTagException: If the tag does not exist.
+ NoSuchPolicyException: If the policy does not exist.
+ PolicyAlreadyAssociatedException: If the policy is already
+ associated with the tag.
+ """
+ if selector is None:
+ selector = AllValuesSelector.get()
+ add_request = PolicyTagAddRequest(
+ PolicyAssociationSelectorDTO.from_selector(selector)
+ )
+ add_request.validate()
+
+ url = self.API_METALAKES_TAG_POLICY_PATH.format(
+ encode_string(self.name()),
+ encode_string(tag_name),
+ encode_string(policy_name),
+ )
+ response = self.rest_client.post(
+ url,
+ json=add_request,
+ error_handler=TAG_ERROR_HANDLER,
+ )
+ resp = PolicyTagAssociationResponse.from_json(response.body,
infer_missing=True)
+ resp.validate()
+
+ return GenericPolicyTagAssociation(
+ self.get_policy(resp.policy()),
+ self.get_tag(resp.tag()),
+ resp.selector().to_selector(),
+ )
+
+ def remove_policy_from_tag(self, tag_name, policy_name) -> None:
+ """
+ Remove the association between a policy and a tag under a metalake.
+
+ Args:
+ tag_name (str): The name of the tag.
+ policy_name (str): The name of the policy.
+
+ Raises:
+ NoSuchTagException: If the tag does not exist.
+ NoSuchPolicyException: If the policy does not exist.
+ """
+ url = self.API_METALAKES_TAG_POLICY_PATH.format(
+ encode_string(self.name()),
+ encode_string(tag_name),
+ encode_string(policy_name),
+ )
+ response = self.rest_client.delete(
+ url,
+ error_handler=TAG_ERROR_HANDLER,
+ )
+ resp = BaseResponse.from_json(response.body, infer_missing=True)
+ resp.validate()
Review Comment:
Fixed in 2e5060a05. `remove_policy_from_tag` no longer parses the response
body — the server returns HTTP 204 with no body on success, so parsing it
raised `JSONDecodeError` after the removal had already gone through. The test
(`test_gravitino_remove_policy_from_tag`) now runs a real 204 empty-body
response through the real `HTTPClient` instead of the mocked 200 JSON
`BaseResponse`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]