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 c2a2f043cb [python] Add Python 3.7 to CI (#8695)
c2a2f043cb is described below

commit c2a2f043cb0f802a6aa5dcdd3b9e58831795b28a
Author: XiaoHongbo <[email protected]>
AuthorDate: Sat Jul 18 12:06:07 2026 +0800

    [python] Add Python 3.7 to CI (#8695)
---
 .github/workflows/paimon-python-checks.yml         |  24 +++-
 paimon-python/dev/lint-python.sh                   |   9 +-
 paimon-python/dev/requirements.txt                 |   3 +-
 paimon-python/dev/run_mixed_tests.sh               | 149 +++++++++++++--------
 paimon-python/pypaimon/__init__.py                 |   2 +-
 .../pypaimon/filesystem/hdfs_native_file_io.py     |   5 +-
 paimon-python/pypaimon/filesystem/local_file_io.py |   7 +-
 .../pypaimon/filesystem/pyarrow_file_io.py         |  24 ++--
 paimon-python/pypaimon/manifest/__init__.py        |   2 +-
 .../pypaimon/manifest/fastavro_py36_compat.py      |  17 ++-
 .../pypaimon/tests/e2e/java_py_read_write_test.py  |   3 +-
 paimon-python/pypaimon/tests/file_io_test.py       |  19 +--
 paimon-python/setup.py                             |   4 +-
 13 files changed, 162 insertions(+), 106 deletions(-)

diff --git a/.github/workflows/paimon-python-checks.yml 
b/.github/workflows/paimon-python-checks.yml
index 8e92a9c1fb..af7c7883fa 100755
--- a/.github/workflows/paimon-python-checks.yml
+++ b/.github/workflows/paimon-python-checks.yml
@@ -43,14 +43,14 @@ concurrency:
   cancel-in-progress: true
 
 jobs:
-  # Lint + test on 3.6 and 3.10 only (not every Python version).
+  # Lint + test on selected versions only (3.6 / 3.7 low end, 3.10 / 3.11 
current), not every Python version.
   lint-python:
     runs-on: ubuntu-latest
     container: "python:${{ matrix.python-version }}-slim"
     strategy:
       fail-fast: false
       matrix:
-        python-version: [ '3.6.15', '3.10', '3.11' ]
+        python-version: [ '3.6.15', '3.7', '3.10', '3.11' ]
 
     steps:
       - name: Checkout code
@@ -86,13 +86,20 @@ jobs:
           mvn -version
 
       - name: Install Rust toolchain
+        if: matrix.python-version != '3.7'
         run: |
           curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- 
-y --default-toolchain stable --profile minimal
           echo "$HOME/.cargo/bin" >> $GITHUB_PATH
 
-      - name: Install paimon-full-text native dependency
+      - name: Install paimon-full-text dependency
+        shell: bash
         run: |
-          bash .github/workflows/install-paimon-full-text.sh native
+          # 3.7 runs no native full-text tests; the Java artifact is enough.
+          if [[ "${{ matrix.python-version }}" == "3.7" ]]; then
+            bash .github/workflows/install-paimon-full-text.sh java
+          else
+            bash .github/workflows/install-paimon-full-text.sh native
+          fi
 
       - name: Verify Python version
         run: python --version
@@ -125,6 +132,13 @@ jobs:
             python --version
             python -m pip install --no-cache-dir pyroaring 
readerwriterlock==1.0.9 'fsspec==2021.10.1' 'cachetools==4.2.4' 
'ossfs==2021.8.0' pyarrow==6.0.1 pandas==1.1.5 'polars==0.9.12' 
'fastavro==1.4.7' zstandard==0.19.0 dataclasses==0.8.0 flake8 pytest 
py4j==0.10.9.9 requests parameterized==0.8.1 2>&1 >/dev/null
             python -m pip install 'lumina-data>=${{ env.LUMINA_DATA_VERSION 
}}' -i https://pypi.org/simple/
+          elif [[ "${{ matrix.python-version }}" == "3.7" ]]; then
+            # 3.7 installs the version-pinned set declared for 3.7 in 
dev/requirements.txt.
+            # ray/lance/daft/torch have no 3.7 wheels; those tests are 
importorskip-ed (as on 3.6.15).
+            python -m pip install --upgrade pip
+            python -m pip install -r paimon-python/dev/requirements.txt
+            python -m pip install flake8==4.0.1 'pytest~=7.0' py4j==0.10.9.9 
parameterized==0.9.0
+            python -m pip install 'lumina-data>=${{ env.LUMINA_DATA_VERSION 
}}' -i https://pypi.org/simple/
           else
             python -m pip install --upgrade pip
             pip install torch --index-url https://download.pytorch.org/whl/cpu
@@ -138,7 +152,7 @@ jobs:
           df -h
 
       - name: Install paimon-full-text Python dependency
-        if: matrix.python-version != '3.6.15'
+        if: matrix.python-version != '3.6.15' && matrix.python-version != '3.7'
         shell: bash
         run: |
           python -m pip install "${PAIMON_FULL_TEXT_DIR}/python"
diff --git a/paimon-python/dev/lint-python.sh b/paimon-python/dev/lint-python.sh
index ffee46cbef..e9a54c365c 100755
--- a/paimon-python/dev/lint-python.sh
+++ b/paimon-python/dev/lint-python.sh
@@ -195,13 +195,14 @@ function pytest_check() {
     PYTHON_VERSION=$(python -c "import sys; 
print(f'{sys.version_info.major}.{sys.version_info.minor}')")
     echo "Detected Python version: $PYTHON_VERSION"
 
-    # Determine test directory based on Python version
-    if [ "$PYTHON_VERSION" = "3.6" ]; then
+    # 3.6/3.7 run a curated core subset (their dep ceiling rules out the
+    # vector/index/multimodal/blob suites); 3.10+ run the full suite.
+    if [ "$PYTHON_VERSION" = "3.6" ] || [ "$PYTHON_VERSION" = "3.7" ]; then
         TEST_DIR="pypaimon/tests/py36 pypaimon/tests/file_io_test.py"
-        echo "Running tests for Python 3.6: $TEST_DIR"
+        echo "Running core test subset for Python $PYTHON_VERSION: $TEST_DIR"
     else
         TEST_DIR="pypaimon/tests pypaimon/acceptance 
--ignore=pypaimon/tests/py36 --ignore=pypaimon/tests/e2e 
--ignore=pypaimon/tests/torch_read_test.py"
-        echo "Running tests for Python $PYTHON_VERSION (excluding py36): 
pypaimon/tests pypaimon/acceptance --ignore=pypaimon/tests/py36"
+        echo "Running tests for Python $PYTHON_VERSION (excluding py36): 
$TEST_DIR"
     fi
 
     # the return value of a pipeline is the status of the last command to exit
diff --git a/paimon-python/dev/requirements.txt 
b/paimon-python/dev/requirements.txt
index 3415427248..dc9b4b4e91 100644
--- a/paimon-python/dev/requirements.txt
+++ b/paimon-python/dev/requirements.txt
@@ -29,7 +29,8 @@ pandas>=1.5,<3; python_version >= "3.9"
 polars>=0.9,<1; python_version<"3.8"
 polars>=1,<2; python_version=="3.8"
 polars>=1.32,<2; python_version>="3.9"
-pyarrow>=6,<7; python_version < "3.8"
+pyarrow>=6,<7; python_version < "3.7"
+pyarrow>=7,<13; python_version >= "3.7" and python_version < "3.8"
 pyarrow>=16,!=19.0.0,<20; python_version >= "3.8"
 pyroaring<=0.3.3; python_version < "3.7"
 pyroaring<=0.4.5; python_version == "3.7"
diff --git a/paimon-python/dev/run_mixed_tests.sh 
b/paimon-python/dev/run_mixed_tests.sh
index 7d5767357d..6423b59e04 100755
--- a/paimon-python/dev/run_mixed_tests.sh
+++ b/paimon-python/dev/run_mixed_tests.sh
@@ -118,10 +118,13 @@ run_batched_java_write_tests() {
         result=1
     fi
 
-    local 
lance_tests="org.apache.paimon.JavaPyLanceE2ETest#testJavaWriteReadPkTableLance"
-    lance_tests="${lance_tests}+testDataEvolutionWriteLance"
-    if ! run_maven_test_batch "paimon-lance Java write tests" "paimon-lance" 
"$lance_tests" -q; then
-        result=1
+    # lance has no Python wheel on <3.8; its readers are all skipped there.
+    if [[ "$PYTHON_MINOR" -ge 8 ]]; then
+        local 
lance_tests="org.apache.paimon.JavaPyLanceE2ETest#testJavaWriteReadPkTableLance"
+        lance_tests="${lance_tests}+testDataEvolutionWriteLance"
+        if ! run_maven_test_batch "paimon-lance Java write tests" 
"paimon-lance" "$lance_tests" -q; then
+            result=1
+        fi
     fi
 
     if [[ "$PYTHON_MINOR" -ge 11 ]]; then
@@ -142,10 +145,13 @@ run_batched_java_write_tests() {
         fi
     fi
 
-    local 
lumina_tests="org.apache.paimon.lumina.index.JavaPyLuminaE2ETest#testLuminaVectorIndexWrite"
-    lumina_tests="${lumina_tests}+testLuminaVectorWithBTreeIndexWrite"
-    if ! run_maven_test_batch "paimon-lumina Java write tests" "paimon-lumina" 
"$lumina_tests" -q; then
-        result=1
+    # Lumina vector reads need BitMap64 (>=3.8); skip the write below it too.
+    if [[ "$PYTHON_MINOR" -ge 8 ]]; then
+        local 
lumina_tests="org.apache.paimon.lumina.index.JavaPyLuminaE2ETest#testLuminaVectorIndexWrite"
+        lumina_tests="${lumina_tests}+testLuminaVectorWithBTreeIndexWrite"
+        if ! run_maven_test_batch "paimon-lumina Java write tests" 
"paimon-lumina" "$lumina_tests" -q; then
+            result=1
+        fi
     fi
 
     if [[ "$PYTHON_MINOR" -ge 9 ]]; then
@@ -187,15 +193,17 @@ run_java_write_test() {
 
     echo ""
 
-    # Run the Java test method for lance format
-    echo "Running Maven test for 
JavaPyLanceE2ETest.testJavaWriteReadPkTableLance (Lance)..."
-    echo "Note: Maven may download dependencies on first run, this may take a 
while..."
+    # Run the Java test method for lance format (readers skipped on <3.8).
     local lance_result=0
-    if mvn test 
-Dtest=org.apache.paimon.JavaPyLanceE2ETest#testJavaWriteReadPkTableLance -pl 
paimon-lance -Drun.e2e.tests=true; then
-        echo -e "${GREEN}✓ Java write lance test completed successfully${NC}"
-    else
-        echo -e "${RED}✗ Java write lance test failed${NC}"
-        lance_result=1
+    if [[ "$PYTHON_MINOR" -ge 8 ]]; then
+        echo "Running Maven test for 
JavaPyLanceE2ETest.testJavaWriteReadPkTableLance (Lance)..."
+        echo "Note: Maven may download dependencies on first run, this may 
take a while..."
+        if mvn test 
-Dtest=org.apache.paimon.JavaPyLanceE2ETest#testJavaWriteReadPkTableLance -pl 
paimon-lance -Drun.e2e.tests=true; then
+            echo -e "${GREEN}✓ Java write lance test completed 
successfully${NC}"
+        else
+            echo -e "${RED}✗ Java write lance test failed${NC}"
+            lance_result=1
+        fi
     fi
 
     if [[ $parquet_result -eq 0 && $lance_result -eq 0 ]]; then
@@ -261,15 +269,19 @@ run_java_read_test() {
 
     echo ""
 
-    # Run Java test for lance format in paimon-lance
-    echo "Running Maven test for JavaPyLanceE2ETest.testReadPkTableLance (Java 
Read Lance)..."
-    echo "Note: Maven may download dependencies on first run, this may take a 
while..."
+    # Java read Lance reads a Python-written table; Python skips lance on <3.8.
     local lance_result=0
-    if mvn test 
-Dtest=org.apache.paimon.JavaPyLanceE2ETest#testReadPkTableLance -pl 
paimon-lance -Drun.e2e.tests=true -Dpython.version="$PYTHON_VERSION"; then
-        echo -e "${GREEN}✓ Java read lance test completed successfully${NC}"
+    if [[ "$PYTHON_MINOR" -ge 8 ]]; then
+        echo "Running Maven test for JavaPyLanceE2ETest.testReadPkTableLance 
(Java Read Lance)..."
+        echo "Note: Maven may download dependencies on first run, this may 
take a while..."
+        if mvn test 
-Dtest=org.apache.paimon.JavaPyLanceE2ETest#testReadPkTableLance -pl 
paimon-lance -Drun.e2e.tests=true -Dpython.version="$PYTHON_VERSION"; then
+            echo -e "${GREEN}✓ Java read lance test completed 
successfully${NC}"
+        else
+            echo -e "${RED}✗ Java read lance test failed${NC}"
+            lance_result=1
+        fi
     else
-        echo -e "${RED}✗ Java read lance test failed${NC}"
-        lance_result=1
+        echo -e "${YELLOW}⏭ Skipping Java read Lance (lance needs Python >= 
3.8, current: $PYTHON_VERSION)${NC}"
     fi
 
     if [[ $parquet_result -eq 0 && $lance_result -eq 0 ]]; then
@@ -800,13 +812,15 @@ run_data_evolution_test() {
             core_result=1
         fi
 
-        # Java write data evolution table (lance)
-        echo "Running Maven test for 
JavaPyLanceE2ETest.testDataEvolutionWriteLance..."
-        if mvn test 
-Dtest=org.apache.paimon.JavaPyLanceE2ETest#testDataEvolutionWriteLance -pl 
paimon-lance -q -Drun.e2e.tests=true; then
-            echo -e "${GREEN}✓ Java data evolution write (lance) completed 
successfully${NC}"
-        else
-            echo -e "${RED}✗ Java data evolution write (lance) failed${NC}"
-            lance_result=1
+        # Java write data evolution table (lance) -- only read back on >=3.8.
+        if [[ "$PYTHON_MINOR" -ge 8 ]]; then
+            echo "Running Maven test for 
JavaPyLanceE2ETest.testDataEvolutionWriteLance..."
+            if mvn test 
-Dtest=org.apache.paimon.JavaPyLanceE2ETest#testDataEvolutionWriteLance -pl 
paimon-lance -q -Drun.e2e.tests=true; then
+                echo -e "${GREEN}✓ Java data evolution write (lance) completed 
successfully${NC}"
+            else
+                echo -e "${RED}✗ Java data evolution write (lance) failed${NC}"
+                lance_result=1
+            fi
         fi
     fi
 
@@ -876,14 +890,18 @@ run_data_evolution_py_write_test() {
         core_result=1
     fi
 
-    # Java read data evolution table (lance)
-    echo "Running Maven test for 
JavaPyLanceE2ETest.testReadDataEvolutionTableLance..."
+    # Java read Lance reads a Python-written table; Python skips lance on <3.8.
     local lance_result=0
-    if mvn test 
-Dtest=org.apache.paimon.JavaPyLanceE2ETest#testReadDataEvolutionTableLance -pl 
paimon-lance -q -Drun.e2e.tests=true -Dpython.version="$PYTHON_VERSION"; then
-        echo -e "${GREEN}✓ Java data evolution read (lance) completed 
successfully${NC}"
+    if [[ "$PYTHON_MINOR" -ge 8 ]]; then
+        echo "Running Maven test for 
JavaPyLanceE2ETest.testReadDataEvolutionTableLance..."
+        if mvn test 
-Dtest=org.apache.paimon.JavaPyLanceE2ETest#testReadDataEvolutionTableLance -pl 
paimon-lance -q -Drun.e2e.tests=true -Dpython.version="$PYTHON_VERSION"; then
+            echo -e "${GREEN}✓ Java data evolution read (lance) completed 
successfully${NC}"
+        else
+            echo -e "${RED}✗ Java data evolution read (lance) failed${NC}"
+            lance_result=1
+        fi
     else
-        echo -e "${RED}✗ Java data evolution read (lance) failed${NC}"
-        lance_result=1
+        echo -e "${YELLOW}⏭ Skipping Java data evolution read Lance (lance 
needs Python >= 3.8, current: $PYTHON_VERSION)${NC}"
     fi
 
     if [[ $core_result -ne 0 || $lance_result -ne 0 ]]; then
@@ -1151,22 +1169,31 @@ main() {
 
     echo ""
 
-    # Run BTree raw fallback test (Java write indexed + unindexed rows, Python 
read)
-    if ! run_btree_raw_fallback_test; then
-        btree_raw_fallback_result=1
-    fi
+    # Raw fallback / bitmap / global index need pyroaring BitMap64 (>=1.0,
+    # Python >=3.8) on the Python read side; skip on older interpreters.
+    if [[ "$PYTHON_MINOR" -ge 8 ]]; then
+        # Run BTree raw fallback test (Java write indexed + unindexed rows, 
Python read)
+        if ! run_btree_raw_fallback_test; then
+            btree_raw_fallback_result=1
+        fi
 
-    echo ""
+        echo ""
 
-    # Run Bitmap index test (Java write, Python read)
-    if ! run_bitmap_index_test; then
-        bitmap_index_result=1
-    fi
+        # Run Bitmap index test (Java write, Python read)
+        if ! run_bitmap_index_test; then
+            bitmap_index_result=1
+        fi
 
-    echo ""
+        echo ""
 
-    if ! run_compressed_global_index_test; then
-        compressed_global_index_result=1
+        if ! run_compressed_global_index_test; then
+            compressed_global_index_result=1
+        fi
+    else
+        echo -e "${YELLOW}⏭ Skipping BTree raw fallback / bitmap / global 
index tests (need BitMap64, Python >= 3.8, current: $PYTHON_VERSION)${NC}"
+        btree_raw_fallback_result=0
+        bitmap_index_result=0
+        compressed_global_index_result=0
     fi
 
     echo ""
@@ -1233,16 +1260,23 @@ main() {
 
     echo ""
 
-    # Run Lumina vector index test (Java write, Python read)
-    if ! run_lumina_vector_test; then
-        lumina_vector_result=1
-    fi
+    # Lumina vector index needs BitMap64 (Python >= 3.8) on the read side.
+    if [[ "$PYTHON_MINOR" -ge 8 ]]; then
+        # Run Lumina vector index test (Java write, Python read)
+        if ! run_lumina_vector_test; then
+            lumina_vector_result=1
+        fi
 
-    echo ""
+        echo ""
 
-    # Run Lumina vector + BTree pre-filter test (Java write, Python read)
-    if ! run_lumina_vector_btree_test; then
-        lumina_vector_btree_result=1
+        # Run Lumina vector + BTree pre-filter test (Java write, Python read)
+        if ! run_lumina_vector_btree_test; then
+            lumina_vector_btree_result=1
+        fi
+    else
+        echo -e "${YELLOW}⏭ Skipping Lumina Vector Index tests (need BitMap64, 
Python >= 3.8, current: $PYTHON_VERSION)${NC}"
+        lumina_vector_result=0
+        lumina_vector_btree_result=0
     fi
 
     echo ""
@@ -1293,7 +1327,8 @@ main() {
 
     echo ""
 
-    # Run data evolution test (Java write, Python read)
+    # Run data evolution test (Java write, Python read). Lance variant skips
+    # itself on <3.8 (get_file_format_params + gated Java lance read).
     if ! run_data_evolution_test; then
         data_evolution_result=1
     fi
diff --git a/paimon-python/pypaimon/__init__.py 
b/paimon-python/pypaimon/__init__.py
index 8f44e62537..aa18f812e7 100644
--- a/paimon-python/pypaimon/__init__.py
+++ b/paimon-python/pypaimon/__init__.py
@@ -17,7 +17,7 @@
 
 import sys
 
-if sys.version_info[:2] == (3, 6):
+if sys.version_info[:2] < (3, 8):
     try:
         from pypaimon.manifest import fastavro_py36_compat  # noqa: F401
     except ImportError:
diff --git a/paimon-python/pypaimon/filesystem/hdfs_native_file_io.py 
b/paimon-python/pypaimon/filesystem/hdfs_native_file_io.py
index 3c3d45ad86..78cad2140e 100644
--- a/paimon-python/pypaimon/filesystem/hdfs_native_file_io.py
+++ b/paimon-python/pypaimon/filesystem/hdfs_native_file_io.py
@@ -562,13 +562,14 @@ class HdfsNativeFileIO(FileIO):
     def write_orc(self, path: str, data: pyarrow.Table,
                   compression: str = 'zstd', zstd_level: int = 1, **kwargs):
         try:
-            import sys
             import pyarrow.orc as orc
             data = self._cast_time_columns_for_orc(data)
             with self.new_output_stream(path) as raw_stream:
                 stream = pyarrow.PythonFile(raw_stream, mode='wb')
                 try:
-                    if sys.version_info[:2] == (3, 6):
+                    # ORC compression= was added in PyArrow 7.0; PyArrow 6 
lacks it.
+                    from pypaimon.filesystem.pyarrow_file_io import 
_pyarrow_lt_7
+                    if _pyarrow_lt_7():
                         orc.write_table(data, stream, **kwargs)
                     else:
                         orc.write_table(
diff --git a/paimon-python/pypaimon/filesystem/local_file_io.py 
b/paimon-python/pypaimon/filesystem/local_file_io.py
index c9848705aa..c35e34d19d 100644
--- a/paimon-python/pypaimon/filesystem/local_file_io.py
+++ b/paimon-python/pypaimon/filesystem/local_file_io.py
@@ -307,9 +307,8 @@ class LocalFileIO(FileIO):
     def write_orc(self, path: str, data: pyarrow.Table, compression: str = 
'zstd',
                   zstd_level: int = 1, **kwargs):
         try:
-            import sys
             import pyarrow.orc as orc
-            
+
             file_path = self._to_file(path)
             parent = file_path.parent
             if parent and not parent.exists():
@@ -318,7 +317,9 @@ class LocalFileIO(FileIO):
             data = self._cast_time_columns_for_orc(data)
             
             with open(file_path, 'wb') as f:
-                if sys.version_info[:2] == (3, 6):
+                # ORC compression= was added in PyArrow 7.0; PyArrow 6 lacks 
it.
+                from pypaimon.filesystem.pyarrow_file_io import _pyarrow_lt_7
+                if _pyarrow_lt_7():
                     orc.write_table(data, f, **kwargs)
                 else:
                     orc.write_table(data, f, compression=compression, **kwargs)
diff --git a/paimon-python/pypaimon/filesystem/pyarrow_file_io.py 
b/paimon-python/pypaimon/filesystem/pyarrow_file_io.py
index c9a3a6437c..8b0b4a7446 100644
--- a/paimon-python/pypaimon/filesystem/pyarrow_file_io.py
+++ b/paimon-python/pypaimon/filesystem/pyarrow_file_io.py
@@ -49,8 +49,12 @@ class PyArrowFileIO(FileIO):
     def __init__(self, path: str, catalog_options: Options):
         self.properties = catalog_options
         self.logger = logging.getLogger(__name__)
-        self._pyarrow_gte_7 = not _pyarrow_lt_7()
         self._pyarrow_gte_8 = parse(pyarrow.__version__) >= parse("8.0.0")
+        # force_virtual_addressing landed in PyArrow 16; below it the OSS 
bucket
+        # goes into endpoint_override, so keys must omit it (init + path share
+        # this flag so they can't drift).
+        self._pyarrow_gte_16 = parse(pyarrow.__version__) >= parse("16.0.0")
+        self._oss_bucket_in_endpoint = not self._pyarrow_gte_16
         scheme, netloc, _ = self.parse_location(path)
         self.uri_reader_factory = UriReaderFactory(catalog_options)
         self._is_oss = scheme in {"oss"}
@@ -185,7 +189,7 @@ class PyArrowFileIO(FileIO):
             "region": self.properties.get(OssOptions.OSS_REGION),
         }
 
-        if self._pyarrow_gte_7:
+        if not self._oss_bucket_in_endpoint:
             client_kwargs['force_virtual_addressing'] = True
             client_kwargs['endpoint_override'] = 
self.properties.get(OssOptions.OSS_ENDPOINT)
         else:
@@ -226,7 +230,7 @@ class PyArrowFileIO(FileIO):
             "session_token": session_token,
             "region": region,
         }
-        if self._pyarrow_gte_7:
+        if self._pyarrow_gte_16:
             path_style_access = (
                 self._get_s3_boolean_property("path-style-access") or
                 self._get_s3_boolean_property("path.style.access"))
@@ -346,8 +350,8 @@ class PyArrowFileIO(FileIO):
 
         if self._use_jindo:
             pass
-        elif self._is_oss and not self._pyarrow_gte_7:
-            # For PyArrow 6.x + OSS, path_str is already just the key part
+        elif self._is_oss and self._oss_bucket_in_endpoint:
+            # OSS with bucket baked into endpoint: path_str is already the key
             if '/' in path_str:
                 parent_dir = '/'.join(path_str.split('/')[:-1])
             else:
@@ -554,15 +558,13 @@ class PyArrowFileIO(FileIO):
             (which is 3, see 
https://github.com/facebook/zstd/blob/dev/programs/zstdcli.c)
             instead of the specified level.
             """
-            import sys
-
             import pyarrow.orc as orc
 
             data = self._cast_time_columns_for_orc(data)
 
             with self.new_output_stream(path) as output_stream:
-                # Check Python version - if 3.6, don't use compression 
parameter
-                if sys.version_info[:2] == (3, 6):
+                # ORC compression= was added in PyArrow 7.0; PyArrow 6 lacks 
it.
+                if _pyarrow_lt_7():
                     orc.write_table(data, output_stream, **kwargs)
                 else:
                     orc.write_table(
@@ -732,8 +734,8 @@ class PyArrowFileIO(FileIO):
             if parsed.scheme:
                 if parsed.netloc:
                     path_part = normalized_path.lstrip('/')
-                    # OSS+PyArrow<7: endpoint_override has bucket, pass key 
only.
-                    if self._is_oss and not self._pyarrow_gte_7:
+                    # OSS with bucket baked into endpoint: pass key only.
+                    if self._is_oss and self._oss_bucket_in_endpoint:
                         return path_part if path_part else '.'
                     result = f"{parsed.netloc}/{path_part}" if path_part else 
parsed.netloc
                     return result
diff --git a/paimon-python/pypaimon/manifest/__init__.py 
b/paimon-python/pypaimon/manifest/__init__.py
index 88064e3a67..8509db9163 100644
--- a/paimon-python/pypaimon/manifest/__init__.py
+++ b/paimon-python/pypaimon/manifest/__init__.py
@@ -18,7 +18,7 @@
 # Apply fastavro Python 3.6 compatibility patch early, before any other
 # manifest modules are imported that might use fastavro
 import sys
-if sys.version_info[:2] == (3, 6):
+if sys.version_info[:2] < (3, 8):
     try:
         from pypaimon.manifest import fastavro_py36_compat  # noqa: F401
     except ImportError:
diff --git a/paimon-python/pypaimon/manifest/fastavro_py36_compat.py 
b/paimon-python/pypaimon/manifest/fastavro_py36_compat.py
index 87b34b7d02..aba8c758a8 100644
--- a/paimon-python/pypaimon/manifest/fastavro_py36_compat.py
+++ b/paimon-python/pypaimon/manifest/fastavro_py36_compat.py
@@ -16,15 +16,14 @@
 # under the License.
 
 """
-Provides compatibility patches for fastavro on Python 3.6,
-specifically for handling zstd-compressed Avro files.
+Compatibility patch for fastavro on Python 3.6/3.7 (the python-zstandard
+fastavro), for reading zstd-compressed Avro (manifest) files.
 
-The main issue addressed is:
-- On Python 3.6, fastavro's zstd decompression may fail with:
+Those fastavro versions may fail on frames without a content-size header
+(e.g. written by Java or backports.zstd) with:
   "zstd.ZstdError: could not determine content size in frame header"
-  
-This module patches fastavro's zstd handling to use a more compatible
-decompression method that works on Python 3.6.
+This patches fastavro's zstd block reader to stream-decompress instead.
+Python 3.8+ uses backports.zstd and needs no patch.
 """
 
 import sys
@@ -34,7 +33,7 @@ _patch_applied = False
 
 def _apply_zstd_patch():
     global _patch_applied
-    if _patch_applied or sys.version_info[:2] != (3, 6):
+    if _patch_applied or sys.version_info[:2] >= (3, 8):
         return
 
     try:
@@ -69,7 +68,7 @@ def _apply_zstd_patch():
         _patch_applied = True
 
 
-if sys.version_info[:2] == (3, 6):
+if sys.version_info[:2] < (3, 8):
     try:
         _apply_zstd_patch()
     except ImportError:
diff --git a/paimon-python/pypaimon/tests/e2e/java_py_read_write_test.py 
b/paimon-python/pypaimon/tests/e2e/java_py_read_write_test.py
index db7ace5243..ac9f44cc22 100644
--- a/paimon-python/pypaimon/tests/e2e/java_py_read_write_test.py
+++ b/paimon-python/pypaimon/tests/e2e/java_py_read_write_test.py
@@ -39,7 +39,8 @@ else:
 
 
 def get_file_format_params():
-    if sys.version_info[:2] == (3, 6):
+    # lance has no wheel on Python < 3.8.
+    if sys.version_info[:2] < (3, 8):
         return [('parquet',), ('orc',), ('avro',)]
     else:
         return [('parquet',), ('orc',), ('avro',), ('lance',)]
diff --git a/paimon-python/pypaimon/tests/file_io_test.py 
b/paimon-python/pypaimon/tests/file_io_test.py
index a9489cfd98..fd5f00306c 100644
--- a/paimon-python/pypaimon/tests/file_io_test.py
+++ b/paimon-python/pypaimon/tests/file_io_test.py
@@ -65,25 +65,26 @@ class FileIOTest(unittest.TestCase):
         parent_str = str(Path(converted_path).parent)
         self.assertEqual(file_io.to_filesystem_path(parent_str), parent_str)
 
-        lt7 = _pyarrow_lt_7()
         oss_io = PyArrowFileIO("oss://test-bucket/warehouse", Options({
             OssOptions.OSS_ENDPOINT.key(): 'oss-cn-hangzhou.aliyuncs.com',
             OssOptions.OSS_ACCESS_KEY_ID.key(): 'test-key',
             OssOptions.OSS_ACCESS_KEY_SECRET.key(): 'test-secret',
             OssOptions.OSS_IMPL.key(): 'legacy',
         }))
+        # PyArrow <16 bakes the bucket into endpoint_override, so keys omit it.
+        bucket_stripped = oss_io._oss_bucket_in_endpoint
         got = oss_io.to_filesystem_path("oss://test-bucket/path/to/file.txt")
-        self.assertEqual(got, "path/to/file.txt" if lt7 else 
"test-bucket/path/to/file.txt")
-        if lt7:
+        self.assertEqual(got, "path/to/file.txt" if bucket_stripped else 
"test-bucket/path/to/file.txt")
+        if bucket_stripped:
             
self.assertEqual(oss_io.to_filesystem_path("db-xxx.db/tbl-xxx/data.parquet"),
                              "db-xxx.db/tbl-xxx/data.parquet")
             self.assertEqual(oss_io.to_filesystem_path("db-xxx.db/tbl-xxx"), 
"db-xxx.db/tbl-xxx")
             manifest_uri = 
"oss://test-bucket/warehouse/db.db/table/manifest/manifest-list-abc-0"
             manifest_key = oss_io.to_filesystem_path(manifest_uri)
             self.assertEqual(manifest_key, 
"warehouse/db.db/table/manifest/manifest-list-abc-0",
-                             "OSS+PyArrow6 must pass key only to PyArrow so 
manifest is written to correct bucket")
+                             "OSS+PyArrow<16 must pass key only so manifest 
lands in the right bucket")
             self.assertFalse(manifest_key.startswith("test-bucket/"),
-                             "path must not start with bucket name or PyArrow 
6 writes to wrong bucket")
+                             "path must not start with bucket or PyArrow <16 
writes to wrong bucket")
         nf = MagicMock(type=pafs.FileType.NotFound)
         get_file_info_calls = []
 
@@ -92,24 +93,24 @@ class FileIOTest(unittest.TestCase):
             return [MagicMock(type=pafs.FileType.NotFound) for _ in paths]
 
         mock_fs = MagicMock()
-        mock_fs.get_file_info.side_effect = record_get_file_info if lt7 else 
[[nf], [nf]]
+        mock_fs.get_file_info.side_effect = record_get_file_info if 
bucket_stripped else [[nf], [nf]]
         mock_fs.create_dir = MagicMock()
         mock_fs.open_output_stream.return_value = MagicMock()
         oss_io.filesystem = mock_fs
         oss_io.new_output_stream("oss://test-bucket/path/to/file.txt")
         mock_fs.create_dir.assert_called_once()
         path_str = 
oss_io.to_filesystem_path("oss://test-bucket/path/to/file.txt")
-        if lt7:
+        if bucket_stripped:
             expected_parent = '/'.join(path_str.split('/')[:-1]) if '/' in 
path_str else ''
         else:
             expected_parent = "/".join(path_str.split("/")[:-1]) if "/" in 
path_str else str(Path(path_str).parent)
         self.assertEqual(mock_fs.create_dir.call_args[0][0], expected_parent)
-        if lt7:
+        if bucket_stripped:
             for call_paths in get_file_info_calls:
                 for p in call_paths:
                     self.assertFalse(
                         p.startswith("test-bucket/"),
-                        "OSS+PyArrow<7 must pass key only to get_file_info, 
not bucket/key. Got: %r" % (p,)
+                        "OSS+PyArrow<16 must pass key only to get_file_info, 
not bucket/key. Got: %r" % (p,)
                     )
 
     def test_exists(self):
diff --git a/paimon-python/setup.py b/paimon-python/setup.py
index c4a543f8fb..f66717fb91 100644
--- a/paimon-python/setup.py
+++ b/paimon-python/setup.py
@@ -152,7 +152,7 @@ setup(
     },
     extras_require={
         'ray': [
-            'ray>=2.10,<3; python_version>="3.7"',
+            'ray>=2.10,<3; python_version>="3.8"',
         ],
         'torch': [
             'torch',
@@ -175,7 +175,7 @@ setup(
             'vortex-data==0.70.0; python_version>="3.11"',
         ],
         'mosaic': [
-            'paimon-mosaic>=0.1.0',
+            'paimon-mosaic>=0.1.0; python_version>="3.9"',
         ],
         'lumina': [
             'lumina-data>=0.1.0'

Reply via email to