You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by gi...@apache.org on 2026/09/14 16:49:42 UTC

(iceberg-python) branch main updated: refactor(encryption): [3/N] Move lazy import to utils/ (#3961)

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

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-python.git


The following commit(s) were added to refs/heads/main by this push:
     new a2cbb8252 refactor(encryption): [3/N] Move lazy import to utils/ (#3961)
a2cbb8252 is described below

commit a2cbb8252df913318b217bcf906c56898b9f4b55
Author: Xander <za...@googlemail.com>
AuthorDate: Mon Sep 14 15:35:44 2026 +0000

    refactor(encryption): [3/N] Move lazy import to utils/ (#3961)
    
    refactor(encryption): Move lazy import to utils/
---
 pyiceberg/transforms.py         | 33 ++++++++++-----------------------
 pyiceberg/utils/lazy_import.py  | 41 +++++++++++++++++++++++++++++++++++++++++
 tests/utils/test_lazy_import.py | 36 ++++++++++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+), 23 deletions(-)

diff --git a/pyiceberg/transforms.py b/pyiceberg/transforms.py
index 5e0027a82..54c01d9be 100644
--- a/pyiceberg/transforms.py
+++ b/pyiceberg/transforms.py
@@ -17,9 +17,7 @@
 
 import base64
 import datetime as py_datetime
-import importlib
 import struct
-import types
 from abc import ABC, abstractmethod
 from collections.abc import Callable
 from enum import IntEnum
@@ -31,7 +29,6 @@ from uuid import UUID
 import mmh3
 from pydantic import Field, PositiveInt, PrivateAttr
 
-from pyiceberg.exceptions import NotInstalledError
 from pyiceberg.expressions import (
     BoundEqualTo,
     BoundGreaterThan,
@@ -88,6 +85,7 @@ from pyiceberg.types import (
 )
 from pyiceberg.utils import datetime
 from pyiceberg.utils.decimal import decimal_to_bytes, truncate_decimal
+from pyiceberg.utils.lazy_import import try_import
 from pyiceberg.utils.parsing import ParseNumberFromBrackets
 from pyiceberg.utils.singleton import Singleton
 
@@ -112,17 +110,6 @@ BUCKET_PARSER = ParseNumberFromBrackets(BUCKET)
 TRUNCATE_PARSER = ParseNumberFromBrackets(TRUNCATE)
 
 
-def _try_import(module_name: str, extras_name: str | None = None) -> types.ModuleType:
-    try:
-        return importlib.import_module(module_name)
-    except ImportError:
-        if extras_name:
-            msg = f'{module_name} needs to be installed. pip install "pyiceberg[{extras_name}]"'
-        else:
-            msg = f"{module_name} needs to be installed."
-        raise NotInstalledError(msg) from None
-
-
 def _transform_literal(func: Callable[[Any], Any], lit: Literal[L]) -> Literal[L]:
     """Small helper to upwrap the value from the literal, and wrap it again."""
     return literal(func(lit.value))
@@ -395,7 +382,7 @@ class BucketTransform(Transform[S, int]):
         return f"BucketTransform(num_buckets={self._num_buckets})"
 
     def pyarrow_transform(self, source: IcebergType) -> "Callable[[pa.Array], pa.Array]":
-        pyiceberg_core_transform = _try_import("pyiceberg_core", extras_name="pyiceberg-core").transform
+        pyiceberg_core_transform = try_import("pyiceberg_core", extras_name="pyiceberg-core").transform
         return _pyiceberg_transform_wrapper(pyiceberg_core_transform.bucket, self._num_buckets)
 
 
@@ -509,8 +496,8 @@ class YearTransform(TimeTransform[S]):
         return "YearTransform()"
 
     def pyarrow_transform(self, source: IcebergType) -> "Callable[[pa.Array], pa.Array]":
-        pa = _try_import("pyarrow")
-        pyiceberg_core_transform = _try_import("pyiceberg_core", extras_name="pyiceberg-core").transform
+        pa = try_import("pyarrow")
+        pyiceberg_core_transform = try_import("pyiceberg_core", extras_name="pyiceberg-core").transform
         return _pyiceberg_transform_wrapper(pyiceberg_core_transform.year, expected_type=pa.int32())
 
 
@@ -569,8 +556,8 @@ class MonthTransform(TimeTransform[S]):
         return "MonthTransform()"
 
     def pyarrow_transform(self, source: IcebergType) -> "Callable[[pa.Array], pa.Array]":
-        pa = _try_import("pyarrow")
-        pyiceberg_core_transform = _try_import("pyiceberg_core", extras_name="pyiceberg-core").transform
+        pa = try_import("pyarrow")
+        pyiceberg_core_transform = try_import("pyiceberg_core", extras_name="pyiceberg-core").transform
 
         return _pyiceberg_transform_wrapper(pyiceberg_core_transform.month, expected_type=pa.int32())
 
@@ -638,8 +625,8 @@ class DayTransform(TimeTransform[S]):
         return "DayTransform()"
 
     def pyarrow_transform(self, source: IcebergType) -> "Callable[[pa.Array], pa.Array]":
-        pa = _try_import("pyarrow", extras_name="pyarrow")
-        pyiceberg_core_transform = _try_import("pyiceberg_core", extras_name="pyiceberg-core").transform
+        pa = try_import("pyarrow", extras_name="pyarrow")
+        pyiceberg_core_transform = try_import("pyiceberg_core", extras_name="pyiceberg-core").transform
 
         return _pyiceberg_transform_wrapper(pyiceberg_core_transform.day, expected_type=pa.int32())
 
@@ -691,7 +678,7 @@ class HourTransform(TimeTransform[S]):
         return "HourTransform()"
 
     def pyarrow_transform(self, source: IcebergType) -> "Callable[[pa.Array], pa.Array]":
-        pyiceberg_core_transform = _try_import("pyiceberg_core", extras_name="pyiceberg-core").transform
+        pyiceberg_core_transform = try_import("pyiceberg_core", extras_name="pyiceberg-core").transform
 
         return _pyiceberg_transform_wrapper(pyiceberg_core_transform.hour)
 
@@ -918,7 +905,7 @@ class TruncateTransform(Transform[S, S]):
         return f"TruncateTransform(width={self._width})"
 
     def pyarrow_transform(self, source: IcebergType) -> "Callable[[pa.Array], pa.Array]":
-        pyiceberg_core_transform = _try_import("pyiceberg_core", extras_name="pyiceberg-core").transform
+        pyiceberg_core_transform = try_import("pyiceberg_core", extras_name="pyiceberg-core").transform
 
         return _pyiceberg_transform_wrapper(pyiceberg_core_transform.truncate, self._width)
 
diff --git a/pyiceberg/utils/lazy_import.py b/pyiceberg/utils/lazy_import.py
new file mode 100644
index 000000000..8f6d7496c
--- /dev/null
+++ b/pyiceberg/utils/lazy_import.py
@@ -0,0 +1,41 @@
+# 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.
+"""Helpers for importing modules that ship in an optional extra."""
+
+from __future__ import annotations
+
+import importlib
+import types
+
+from pyiceberg.exceptions import NotInstalledError
+
+
+def try_import(module_name: str, extras_name: str | None = None) -> types.ModuleType:
+    """Import `module_name`, raising `NotInstalledError` with an install hint when it is missing.
+
+    Args:
+        module_name (str): The module to import.
+        extras_name (str | None): The pyiceberg extra that provides it, if any.
+    """
+    try:
+        return importlib.import_module(module_name)
+    except ImportError:
+        if extras_name:
+            msg = f'{module_name} needs to be installed. pip install "pyiceberg[{extras_name}]"'
+        else:
+            msg = f"{module_name} needs to be installed."
+        raise NotInstalledError(msg) from None
diff --git a/tests/utils/test_lazy_import.py b/tests/utils/test_lazy_import.py
new file mode 100644
index 000000000..d68da5423
--- /dev/null
+++ b/tests/utils/test_lazy_import.py
@@ -0,0 +1,36 @@
+# 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.
+import importlib
+
+import pytest
+
+from pyiceberg.exceptions import NotInstalledError
+from pyiceberg.utils.lazy_import import try_import
+
+
+def test_try_import_returns_the_module() -> None:
+    assert try_import("importlib") is importlib
+
+
+def test_try_import_missing_module_with_extras() -> None:
+    with pytest.raises(NotInstalledError, match=r'nonexistent needs to be installed. pip install "pyiceberg\[some-extra\]"'):
+        try_import("nonexistent", extras_name="some-extra")
+
+
+def test_try_import_missing_module_without_extras() -> None:
+    with pytest.raises(NotInstalledError, match="nonexistent needs to be installed."):
+        try_import("nonexistent")