Skip to content

Commit 1a79cd5

Browse files
committed
Core: Validate implementation classes loaded from properties
1 parent 8275a6f commit 1a79cd5

6 files changed

Lines changed: 27 additions & 0 deletions

File tree

‎pyiceberg/io/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,8 @@ def _import_file_io(io_impl: str, properties: Properties) -> FileIO | None:
340340
module_name, class_name = ".".join(path_parts[:-1]), path_parts[-1]
341341
module = importlib.import_module(module_name)
342342
class_ = getattr(module, class_name)
343+
if not isinstance(class_, type) or not issubclass(class_, FileIO):
344+
raise ValueError(f"py-io-impl should be a subclass of FileIO, got: {io_impl}")
343345
return class_(properties)
344346
except ModuleNotFoundError:
345347
logger.warning(f"Could not initialize FileIO: {io_impl}", exc_info=logger.isEnabledFor(logging.DEBUG))

‎pyiceberg/io/pyarrow.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ def _import_retry_strategy(impl: str) -> S3RetryStrategy | None:
232232
module_name, class_name = ".".join(path_parts[:-1]), path_parts[-1]
233233
module = importlib.import_module(module_name)
234234
class_ = getattr(module, class_name)
235+
if not isinstance(class_, type) or not issubclass(class_, S3RetryStrategy):
236+
raise ValueError(f"retry-strategy-impl should be a subclass of S3RetryStrategy, got: {impl}")
235237
return class_()
236238
except (ModuleNotFoundError, AttributeError):
237239
warnings.warn(f"Could not initialize S3 retry strategy: {impl}", stacklevel=2)

‎pyiceberg/table/locations.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ def _import_location_provider(
178178
module_name, class_name = ".".join(path_parts[:-1]), path_parts[-1]
179179
module = importlib.import_module(module_name)
180180
class_ = getattr(module, class_name)
181+
if not isinstance(class_, type) or not issubclass(class_, LocationProvider):
182+
raise ValueError(
183+
f"write.py-location-provider.impl should be a subclass of LocationProvider, got: {location_provider_impl}"
184+
)
181185
return class_(table_location, table_properties)
182186
except ModuleNotFoundError:
183187
logger.warning(

‎tests/io/test_io.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,11 @@ def test_import_file_io() -> None:
280280
assert isinstance(_import_file_io(ARROW_FILE_IO, {}), PyArrowFileIO)
281281

282282

283+
def test_import_file_io_wrong_type() -> None:
284+
with pytest.raises(ValueError, match="py-io-impl should be a subclass of FileIO"):
285+
_import_file_io("pyiceberg.table.locations.SimpleLocationProvider", {})
286+
287+
283288
def test_import_file_io_does_not_exist(caplog: Any) -> None:
284289
import logging
285290

‎tests/io/test_pyarrow.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3309,6 +3309,12 @@ def test_retry_strategy() -> None:
33093309
io.new_input("s3://bucket/path/to/file")
33103310

33113311

3312+
def test_retry_strategy_wrong_type() -> None:
3313+
io = PyArrowFileIO(properties={S3_RETRY_STRATEGY_IMPL: "pyiceberg.io.FileIO"})
3314+
with pytest.raises(ValueError, match="retry-strategy-impl should be a subclass of S3RetryStrategy"):
3315+
io.new_input("s3://bucket/path/to/file")
3316+
3317+
33123318
def test_retry_strategy_not_found() -> None:
33133319
io = PyArrowFileIO(properties={S3_RETRY_STRATEGY_IMPL: "pyiceberg.DoesNotExist"})
33143320
with pytest.warns(UserWarning, match="Could not initialize S3 retry strategy: pyiceberg.DoesNotExist"):

‎tests/table/test_locations.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ def test_custom_location_provider() -> None:
6060
assert provider.new_data_location("my_file") == "custom_location_provider/my_file"
6161

6262

63+
def test_custom_location_provider_wrong_type() -> None:
64+
with pytest.raises(ValueError, match="write.py-location-provider.impl should be a subclass of LocationProvider"):
65+
load_location_provider(
66+
table_location="table_location",
67+
table_properties={"write.py-location-provider.impl": "pyiceberg.io.FileIO"},
68+
)
69+
70+
6371
def test_custom_location_provider_single_path() -> None:
6472
with pytest.raises(ValueError, match=r"write\.py-location-provider\.impl should be full path"):
6573
load_location_provider(table_location="table_location", table_properties={"write.py-location-provider.impl": "not_found"})

0 commit comments

Comments
 (0)