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

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new f60438742f [python] Remove ExpirationAt from DLF token JSON (#10186)
f60438742f is described below

commit f60438742fd4e8bf1e4013e2e7210192cfd44775
Author: XiaoHongbo <[email protected]>
AuthorDate: Mon Sep 28 21:35:29 2026 +0800

    [python] Remove ExpirationAt from DLF token JSON (#10186)
---
 paimon-python/pypaimon/api/token_loader.py            |  4 ++--
 paimon-python/pypaimon/common/json_util.py            | 15 +++++++++++++++
 .../pypaimon/tests/rest/token_loader_test.py          | 19 +++++++++++++++++++
 3 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/paimon-python/pypaimon/api/token_loader.py 
b/paimon-python/pypaimon/api/token_loader.py
index f76eca4306..15e8035b0d 100644
--- a/paimon-python/pypaimon/api/token_loader.py
+++ b/paimon-python/pypaimon/api/token_loader.py
@@ -29,7 +29,7 @@ from requests.exceptions import RequestException
 from pypaimon.api.client import ExponentialRetry
 from pypaimon.common.options import Options
 from pypaimon.common.options.config import CatalogOptions
-from pypaimon.common.json_util import JSON, json_field
+from pypaimon.common.json_util import JSON, json_field, json_ignore_field
 
 
 @dataclass
@@ -40,7 +40,7 @@ class DLFToken:
     access_key_secret: str = json_field('AccessKeySecret')
     security_token: Optional[str] = json_field('SecurityToken')
     expiration: Optional[str] = json_field('Expiration')
-    expiration_at_millis: Optional[int] = json_field('ExpirationAt', 
default=None)
+    expiration_at_millis: Optional[int] = json_ignore_field(default=None)
 
     @staticmethod
     def parse_expiration_to_millis(expiration: str) -> int:
diff --git a/paimon-python/pypaimon/common/json_util.py 
b/paimon-python/pypaimon/common/json_util.py
index 0ae8c232f9..df9c95427c 100644
--- a/paimon-python/pypaimon/common/json_util.py
+++ b/paimon-python/pypaimon/common/json_util.py
@@ -28,6 +28,12 @@ def json_field(json_name: str, **kwargs):
     return field(metadata={"json_name": json_name, **metadata}, **kwargs)
 
 
+def json_ignore_field(**kwargs):
+    """Create a field excluded from JSON serialization and deserialization."""
+    metadata = kwargs.pop("metadata", {})
+    return field(metadata={"json_ignore": True, **metadata}, **kwargs)
+
+
 def optional_json_field(json_name: str, json_include: str):
     """Create a field with custom JSON name"""
     return field(metadata={"json_name": json_name, "json_include": 
json_include}, default=None)
@@ -76,6 +82,9 @@ class JSON:
         # Otherwise, use dataclass field-by-field serialization
         result = {}
         for field_info in fields(obj):
+            if field_info.metadata.get("json_ignore", False):
+                continue
+
             field_value = getattr(obj, field_info.name)
 
             # Get custom JSON name from metadata
@@ -122,6 +131,9 @@ class JSON:
         type_mapping = {}
         decoder_mapping = {}
         for field_info in fields(target_class):
+            if field_info.metadata.get("json_ignore", False):
+                continue
+
             json_name = field_info.metadata.get("json_name", field_info.name)
             field_mapping[json_name] = field_info.name
             decoder = field_info.metadata.get("decoder")
@@ -168,6 +180,9 @@ class JSON:
                     kwargs[field_name] = value
 
         for field_info in fields(target_class):
+            if field_info.metadata.get("json_ignore", False):
+                continue
+
             json_name = field_info.metadata.get("json_name", field_info.name)
             if (
                 json_name not in data
diff --git a/paimon-python/pypaimon/tests/rest/token_loader_test.py 
b/paimon-python/pypaimon/tests/rest/token_loader_test.py
index 1fb1d29f61..23a9d18874 100644
--- a/paimon-python/pypaimon/tests/rest/token_loader_test.py
+++ b/paimon-python/pypaimon/tests/rest/token_loader_test.py
@@ -15,6 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import json
 import tempfile
 import unittest
 from pathlib import Path
@@ -32,6 +33,24 @@ from pypaimon.common.options.config import CatalogOptions
 
 class DLFLocalFileTokenLoaderTest(unittest.TestCase):
 
+    def test_expiration_at_millis_is_internal_only(self):
+        token = DLFToken(
+            access_key_id="access-key-id",
+            access_key_secret="access-key-secret",
+            security_token="security-token",
+            expiration="2099-12-01T12:00:00Z",
+        )
+
+        token_json = JSON.to_json(token)
+        self.assertNotIn("ExpirationAt", json.loads(token_json))
+
+        loaded_token = JSON.from_json(
+            '{"AccessKeyId":"ak","AccessKeySecret":"sk",'
+            '"SecurityToken":"sts","ExpirationAt":123}',
+            DLFToken,
+        )
+        self.assertIsNone(loaded_token.expiration_at_millis)
+
     def test_load_token_from_configured_path(self):
         token = DLFToken(
             access_key_id="access-key-id",

Reply via email to