This is an automated email from the ASF dual-hosted git repository.

yuqi4733 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 34938d52af [#10315]fix(client-python): Fix GCS credential handling for 
gcsfs 2026.2.0 compatibility (#10317)
34938d52af is described below

commit 34938d52aff8d2485015941724b01a8a0edfa314
Author: geyanggang <[email protected]>
AuthorDate: Tue Mar 10 14:34:20 2026 +0800

    [#10315]fix(client-python): Fix GCS credential handling for gcsfs 2026.2.0 
compatibility (#10317)
    
    ### What changes were proposed in this pull request?
    
    Wrap GCS OAuth2 access tokens in a google.auth.credentials.Credentials
    object before
    passing to gcsfs.GCSFileSystem. The new gcsfs version (2026.2.0)
    requires Credentials
    objects instead of raw token strings.
    
    Changes:
    
    Created StaticCredentials class implementing
    google.auth.credentials.Credentials interface
    Properly converts token string and expiry time to Credentials object
    Uses google.auth._helpers.utcnow() for consistent time comparison
    ### Why are the changes needed?
    
    The gcsfs library was upgraded from 2024.3.1 to 2026.2.0 in commit
    
https://github.com/datastrato/gravitino-internal/commit/79e37271216ebed689b1b4ea592adb12d6173170.
    The new
    version changed the token parameter handling and no longer accepts raw
    OAuth2 token strings.
    It now requires google.auth.credentials.Credentials objects.
    
    Without this fix, all GCS credential-based tests fail with "Provided
    token is either not
    valid, or expired" error, breaking the credential vending feature for
    GCS storage.
    
    Fix: #10315
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    The CI can pass.
---
 .../gravitino/filesystem/gvfs_storage_handler.py   | 51 +++++++++++++++++++++-
 1 file changed, 50 insertions(+), 1 deletion(-)

diff --git a/clients/client-python/gravitino/filesystem/gvfs_storage_handler.py 
b/clients/client-python/gravitino/filesystem/gvfs_storage_handler.py
index 63c215079a..22abc27aa9 100644
--- a/clients/client-python/gravitino/filesystem/gvfs_storage_handler.py
+++ b/clients/client-python/gravitino/filesystem/gvfs_storage_handler.py
@@ -19,6 +19,7 @@ import logging
 import sys
 import time
 from abc import ABC, abstractmethod
+from datetime import datetime, timezone
 from enum import Enum
 from typing import Optional, Tuple, List, Dict, Any
 
@@ -40,6 +41,14 @@ from gravitino.api.credential.s3_token_credential import 
S3TokenCredential
 from gravitino.exceptions.base import GravitinoRuntimeException
 from gravitino.filesystem.gvfs_config import GVFSConfig
 
+try:
+    from google.auth.credentials import Credentials
+    from google.auth.exceptions import RefreshError
+
+    GOOGLE_AUTH_AVAILABLE = True
+except ImportError:
+    GOOGLE_AUTH_AVAILABLE = False
+
 TIME_WITHOUT_EXPIRATION = sys.maxsize
 SLASH = "/"
 
@@ -403,10 +412,50 @@ class GCSStorageHandler(StorageHandler):
                     catalog_props,
                 )
                 if isinstance(credential, GCSTokenCredential):
+                    if not GOOGLE_AUTH_AVAILABLE:
+                        raise GravitinoRuntimeException(
+                            "Failed to import google.auth. "
+                            "Please ensure google-auth is installed."
+                        )
+
+                    # gcsfs expects a google.auth.credentials.Credentials 
object
+                    # We need to create a credentials object from the access 
token
+                    class StaticCredentials(Credentials):
+                        """A credentials object that uses a static access 
token."""
+
+                        def __init__(self, token_value, expiry_time_ms):
+                            super().__init__()
+                            self.token = token_value
+                            # Convert milliseconds to datetime
+                            self.expiry = datetime.fromtimestamp(
+                                expiry_time_ms / 1000.0, tz=timezone.utc
+                            )
+
+                        def refresh(self, request):
+                            # Static token cannot be refreshed
+                            raise RefreshError(
+                                "Cannot refresh static access token. "
+                                "Please request a new credential from 
Gravitino."
+                            )
+
+                        @property
+                        def expired(self):
+                            if not self.expiry:
+                                return False
+                            return datetime.now(timezone.utc) >= self.expiry
+
+                        @property
+                        def valid(self):
+                            return self.token is not None and not self.expired
+
+                    creds = StaticCredentials(
+                        credential.token(), credential.expire_time_in_ms()
+                    )
+
                     return (
                         expire_time,
                         self.get_filesystem(
-                            token=credential.token(),
+                            token=creds,
                             **kwargs,
                         ),
                     )

Reply via email to