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 81a3f27162 [python] Fix pyjindo advanced config setting in pypaimon 
(#9206)
81a3f27162 is described below

commit 81a3f2716275c0b8d805a42280eee8227b27cd01
Author: timmyyao <[email protected]>
AuthorDate: Thu Aug 13 21:51:08 2026 +0800

    [python] Fix pyjindo advanced config setting in pypaimon (#9206)
---
 .../filesystem/jindo_file_system_handler.py        |  64 +++++-----
 .../pypaimon/tests/jindo_file_system_test.py       | 136 +++++++++++++++++++++
 2 files changed, 170 insertions(+), 30 deletions(-)

diff --git a/paimon-python/pypaimon/filesystem/jindo_file_system_handler.py 
b/paimon-python/pypaimon/filesystem/jindo_file_system_handler.py
index efbfe7f189..73ad09077f 100644
--- a/paimon-python/pypaimon/filesystem/jindo_file_system_handler.py
+++ b/paimon-python/pypaimon/filesystem/jindo_file_system_handler.py
@@ -22,11 +22,7 @@ from pyarrow import PythonFile
 from pyarrow._fs import FileSystemHandler
 from pyarrow.fs import FileInfo, FileSelector, FileType
 
-# `JindoFileSystemHandler` (the PyArrow FileIO path) only needs `pyjindo.fs`
-# and `pyjindo.util`. The PVFS jindo backend (`create_jindo_oss_filesystem`)
-# additionally needs `pyjindo.ossfs`. Track the two surfaces independently so
-# that a pyjindosdk build without `pyjindo.ossfs` does not silently disable
-# the previously-working PyArrow path.
+# The PyArrow and PVFS paths use separate pyjindo modules.
 try:
     import pyjindo.fs as jfs
     import pyjindo.util as jutil
@@ -47,18 +43,42 @@ from pypaimon.common.options import Options
 from pypaimon.common.options.config import OssOptions
 
 
-def build_jindo_config(catalog_options: Options):
-    """Build a pyjindo ``Config`` from OSS catalog options.
+_JINDO_CONFIG_PREFIXES = ("fs.", "logger.")
+_PYPAIMON_ONLY_JINDO_CONFIG_KEYS = {OssOptions.OSS_IMPL.key()}
+_CASE_SENSITIVE_JINDO_CONFIG_KEYS = {
+    OssOptions.OSS_ACCESS_KEY_ID.key().lower(): 
OssOptions.OSS_ACCESS_KEY_ID.key(),
+    OssOptions.OSS_ACCESS_KEY_SECRET.key().lower(): 
OssOptions.OSS_ACCESS_KEY_SECRET.key(),
+    OssOptions.OSS_SECURITY_TOKEN.key().lower(): 
OssOptions.OSS_SECURITY_TOKEN.key(),
+}
+
+
+def _jindo_config_value(value) -> str:
+    if isinstance(value, bool):
+        return str(value).lower()
+    return str(value)
 
-    Shared by ``JindoFileSystemHandler`` (the PyArrow FileIO path) and
-    ``create_jindo_oss_filesystem`` (the PVFS fsspec path) so both jindo entry
-    points consume exactly the same credential / endpoint options.
-    """
+
+def build_jindo_config(catalog_options: Options):
+    """Build a pyjindo ``Config`` from catalog options."""
     if not JINDO_AVAILABLE:
         raise ImportError("Module pyjindo is not available. Please install 
pyjindosdk.")
 
+    # Use catalog options as the complete configuration source.
     config = jutil.Config()
 
+    # Forward supported filesystem and logger options.
+    for raw_key, value in catalog_options.to_map().items():
+        supported_prefix = isinstance(raw_key, str)
+        supported_prefix = supported_prefix and raw_key.startswith(
+            _JINDO_CONFIG_PREFIXES)
+        if not supported_prefix or value is None:
+            continue
+        key = _CASE_SENSITIVE_JINDO_CONFIG_KEYS.get(raw_key.lower(), raw_key)
+        if key in _PYPAIMON_ONLY_JINDO_CONFIG_KEYS:
+            # This option is handled by PyPaimon.
+            continue
+        config.set(key, _jindo_config_value(value))
+
     access_key_id = catalog_options.get(OssOptions.OSS_ACCESS_KEY_ID)
     access_key_secret = catalog_options.get(OssOptions.OSS_ACCESS_KEY_SECRET)
     security_token = catalog_options.get(OssOptions.OSS_SECURITY_TOKEN)
@@ -81,18 +101,7 @@ def build_jindo_config(catalog_options: Options):
 
 
 def create_jindo_oss_filesystem(root_uri: str, catalog_options: Options):
-    """Create an fsspec-compatible ``JindoOssFileSystem`` for an OSS bucket.
-
-    ``PaimonVirtualFileSystem`` uses this to back OSS reads/writes with the
-    native JindoSDK instead of ``ossfs``. JindoSDK writes objects via
-    PutObject / multipart upload, so it never issues OSS ``AppendObject`` --
-    the call that fails with ``PositionNotEqualToLength`` (409) on the OSS
-    data-acceleration endpoint when ``ossfs`` flushes a multi-chunk write.
-
-    ``root_uri`` is the bucket root, e.g. ``oss://my-bucket/``; it must carry
-    the bucket so ``JindoOssFileSystem`` can re-attach the ``oss://`` scheme to
-    the bucket-relative paths that ``PaimonVirtualFileSystem`` passes in.
-    """
+    """Create a Jindo OSS filesystem for ``PaimonVirtualFileSystem``."""
     if not (JINDO_AVAILABLE and JINDO_OSSFS_AVAILABLE):
         raise ImportError(
             "pyjindo.ossfs is not available. Please install 
pyjindosdk>=6.10.4."
@@ -101,14 +110,9 @@ def create_jindo_oss_filesystem(root_uri: str, 
catalog_options: Options):
     return jossfs.JindoOssFileSystem(
         uri=root_uri,
         config=build_jindo_config(catalog_options),
-        # PaimonVirtualFileSystem owns directory semantics for the virtual FS;
-        # the backing object-store fs must not auto-create dir-marker objects.
+        # PaimonVirtualFileSystem manages directory semantics.
         auto_mkdir=False,
-        # Bypass fsspec's _Cached metaclass instance cache, so the only
-        # reference to this filesystem -- and to its underlying native jindo
-        # connection -- is the PaimonRealStorage cache in PVFS. On token
-        # refresh PVFS replaces that entry and the native resources can be
-        # released, instead of being pinned forever by fsspec's global cache.
+        # PaimonVirtualFileSystem manages filesystem instances.
         skip_instance_cache=True,
     )
 
diff --git a/paimon-python/pypaimon/tests/jindo_file_system_test.py 
b/paimon-python/pypaimon/tests/jindo_file_system_test.py
index 9bd45aedda..b828fd8b83 100644
--- a/paimon-python/pypaimon/tests/jindo_file_system_test.py
+++ b/paimon-python/pypaimon/tests/jindo_file_system_test.py
@@ -16,17 +16,153 @@
 # under the License.
 
 import os
+import types
 import unittest
 import uuid
+from unittest import mock
 
 import pyarrow.fs as pafs
 
 from pyarrow.fs import PyFileSystem
 from pypaimon.common.options import Options
 from pypaimon.common.options.config import OssOptions
+from pypaimon.filesystem import jindo_file_system_handler as jindo_module
 from pypaimon.filesystem.jindo_file_system_handler import 
JindoFileSystemHandler, JINDO_AVAILABLE
 
 
+class _RecordingConfig:
+    def __init__(self, values=None):
+        self.values = dict(values or {})
+
+    def set(self, key, value):
+        self.values[key] = value
+
+
+class JindoConfigTest(unittest.TestCase):
+
+    def test_forwards_native_options_to_connect(self):
+        created_config = _RecordingConfig()
+        config_factory = mock.Mock(return_value=created_config)
+        read_config = mock.Mock()
+        fake_jutil = types.SimpleNamespace(
+            Config=config_factory,
+            read_config=read_config,
+        )
+        options = Options({
+            "fs.oss.accesskeyid": "ak",
+            "fs.oss.accesskeysecret": "sk",
+            "fs.oss.securitytoken": "token",
+            OssOptions.OSS_ENDPOINT.key(): "https://cache-seed:80";,
+            OssOptions.OSS_IMPL.key(): "jindo",
+            "fs.oss.dlf-cache.consistent-hash.enabled": True,
+            "fs.oss.dlf-cache.server.address": "http://cache-server:18101";,
+            "fs.oss.https.enable": False,
+            "fs.oss.second.level.domain.enable": "true",
+            "fs.jindocache.client.metrics.enable": True,
+            "logger.dir": "/tmp/jindo-log",
+            "logger.verbose": 3,
+            "logger.console.log.enable": False,
+            "fs.oss.unset.option": None,
+            "metastore": "rest",
+        })
+        connect = mock.Mock(return_value=mock.sentinel.jindo_fs)
+        fake_jfs = types.SimpleNamespace(connect=connect)
+
+        with mock.patch.object(jindo_module, "JINDO_AVAILABLE", True), \
+             mock.patch.object(jindo_module, "jfs", fake_jfs), \
+             mock.patch.object(jindo_module, "jutil", fake_jutil):
+            handler = JindoFileSystemHandler("oss://bucket/", options)
+
+        config_factory.assert_called_once_with()
+        read_config.assert_not_called()
+        connect.assert_called_once_with("oss://bucket/", "root", mock.ANY)
+        self.assertIs(handler._jindo_fs, mock.sentinel.jindo_fs)
+        config = connect.call_args[0][2]
+        self.assertIs(config, created_config)
+        self.assertEqual(config.values["fs.oss.accessKeyId"], "ak")
+        self.assertEqual(config.values["fs.oss.accessKeySecret"], "sk")
+        self.assertEqual(config.values["fs.oss.securityToken"], "token")
+        self.assertEqual(config.values["fs.oss.endpoint"], "cache-seed:80")
+        self.assertEqual(
+            config.values["fs.oss.dlf-cache.consistent-hash.enabled"], "true")
+        self.assertEqual(
+            config.values["fs.oss.dlf-cache.server.address"],
+            "http://cache-server:18101";)
+        self.assertEqual(config.values["fs.oss.https.enable"], "false")
+        self.assertEqual(
+            config.values["fs.oss.second.level.domain.enable"], "true")
+        self.assertEqual(
+            config.values["fs.jindocache.client.metrics.enable"], "true")
+        self.assertEqual(config.values["logger.dir"], "/tmp/jindo-log")
+        self.assertEqual(config.values["logger.verbose"], "3")
+        self.assertEqual(config.values["logger.console.log.enable"], "false")
+        self.assertEqual(config.values["fs.oss.user.agent.features"], 
"pypaimon")
+        self.assertNotIn(OssOptions.OSS_IMPL.key(), config.values)
+        self.assertNotIn("fs.oss.unset.option", config.values)
+        self.assertNotIn("metastore", config.values)
+
+    def test_does_not_load_external_config(self):
+        created_config = _RecordingConfig()
+        config_factory = mock.Mock(return_value=created_config)
+        read_config = mock.Mock(
+            side_effect=AssertionError("external config must not be loaded"))
+        fake_jutil = types.SimpleNamespace(
+            Config=config_factory,
+            read_config=read_config,
+        )
+        options = Options({
+            OssOptions.OSS_ENDPOINT.key(): "http://127.0.0.1:80";,
+            "logger.verbose": "3",
+        })
+
+        with mock.patch.object(jindo_module, "JINDO_AVAILABLE", True), \
+             mock.patch.object(jindo_module, "jutil", fake_jutil):
+            config = jindo_module.build_jindo_config(options)
+
+        config_factory.assert_called_once_with()
+        read_config.assert_not_called()
+        self.assertIs(config, created_config)
+        self.assertEqual(config.values["fs.oss.endpoint"], "127.0.0.1:80")
+        self.assertEqual(config.values["logger.verbose"], "3")
+        self.assertNotIn("fs.oss.provider.endpoint", config.values)
+        self.assertNotIn("fs.oss.provider.format", config.values)
+
+    def test_forwards_native_options_to_jindo_oss_filesystem(self):
+        created_config = _RecordingConfig()
+        config_factory = mock.Mock(return_value=created_config)
+        fake_jutil = types.SimpleNamespace(Config=config_factory)
+        jindo_oss_filesystem = 
mock.Mock(return_value=mock.sentinel.jindo_oss_fs)
+        fake_jossfs = 
types.SimpleNamespace(JindoOssFileSystem=jindo_oss_filesystem)
+        options = Options({
+            "fs.oss.dlf-cache.consistent-hash.enabled": "true",
+            "fs.oss.dlf-cache.server.address": "http://cache-server:18101";,
+            "logger.verbose": 3,
+        })
+
+        with mock.patch.object(jindo_module, "JINDO_AVAILABLE", True), \
+             mock.patch.object(jindo_module, "JINDO_OSSFS_AVAILABLE", True), \
+             mock.patch.object(jindo_module, "jutil", fake_jutil), \
+             mock.patch.object(jindo_module, "jossfs", fake_jossfs):
+            filesystem = jindo_module.create_jindo_oss_filesystem(
+                "oss://bucket/", options)
+
+        self.assertIs(filesystem, mock.sentinel.jindo_oss_fs)
+        config_factory.assert_called_once_with()
+        jindo_oss_filesystem.assert_called_once_with(
+            uri="oss://bucket/",
+            config=created_config,
+            auto_mkdir=False,
+            skip_instance_cache=True,
+        )
+        self.assertEqual(
+            created_config.values["fs.oss.dlf-cache.consistent-hash.enabled"],
+            "true")
+        self.assertEqual(
+            created_config.values["fs.oss.dlf-cache.server.address"],
+            "http://cache-server:18101";)
+        self.assertEqual(created_config.values["logger.verbose"], "3")
+
+
 class JindoFileSystemTest(unittest.TestCase):
     """Test cases for JindoFileSystem."""
 

Reply via email to