blob: 2871c3b8850208a60c09b24cbcb49de610a6233f [file]
# 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.
"""Distributed HDF5 ingestion using the multimodal transform contract."""
from typing import Any, Dict, Mapping, Optional
def load_from_hdf5(
table_identifier: str,
paths,
catalog_options: Dict[str, str],
*,
transform,
source_options: Optional[Mapping[str, object]] = None,
concurrency: Optional[int] = None,
ray_remote_args: Optional[Dict[str, Any]] = None):
"""Transform complete HDF5 files on Ray and append them in one commit.
The transform has the same ``(h5py.File, Hdf5File)`` contract as
:meth:`MultimodalConnection.load_from_hdf5`. Discovery runs on the driver;
workers open and transform complete files, and the Paimon Ray sink commits
all worker messages once. The result reports logical output rows and the
exact committed snapshot; its batch count is ``None`` because Ray does not
expose that count without re-executing the lazy transform.
"""
if not callable(transform):
raise ValueError("transform must be callable.")
from pypaimon.catalog.catalog_factory import CatalogFactory
from pypaimon.common.options import Options
from pypaimon.multimodal.hdf5 import (
Hdf5LoadResult,
_Hdf5SourceFileIO,
_discover_hdf5_files,
_path_values,
_validate_source_kerberos,
_validated_source_options,
)
from pypaimon.multimodal.table import _target_schema
from pypaimon.ray.ray_paimon import _require_ray_data, write_paimon
ray_data = _require_ray_data()
validated_options = _validated_source_options(source_options)
path_values = _path_values(paths)
_validate_source_kerberos(path_values, validated_options)
source_file_io = _Hdf5SourceFileIO(Options(validated_options))
try:
files = _discover_hdf5_files(path_values, source_file_io)
finally:
source_file_io.close()
if not files:
return Hdf5LoadResult(
file_count=0,
batch_count=None,
row_count=0,
snapshot_id=None,
)
table = CatalogFactory.create(catalog_options).get_table(table_identifier)
target_schema = _target_schema(table)
inputs = ray_data.from_items(
[{"path": source.path} for source in files],
override_num_blocks=len(files),
)
transformed = inputs.map_batches(
_TransformHdf5File,
fn_constructor_kwargs={
"transform": transform,
"source_options": validated_options,
"target_schema": target_schema,
},
batch_format="pyarrow",
batch_size=1,
concurrency=concurrency,
**dict(ray_remote_args or {}),
)
write_result = write_paimon(
transformed,
table_identifier,
catalog_options,
concurrency=concurrency,
ray_remote_args=ray_remote_args,
)
return Hdf5LoadResult(
file_count=len(files),
batch_count=None,
row_count=0 if write_result is None else write_result.row_count,
snapshot_id=(
None if write_result is None else write_result.snapshot_id
),
)
class _TransformHdf5File:
def __init__(self, *, transform, source_options, target_schema):
self.transform = transform
self.source_options = source_options
self.target_schema = target_schema
def __call__(self, batch):
if batch.num_rows != 1:
raise ValueError("Ray HDF5 transform requires one source per batch.")
from pypaimon.common.options import Options
from pypaimon.multimodal.hdf5 import (
Hdf5File,
_Hdf5SourceFileIO,
_transform_hdf5_file,
)
try:
import h5py
except ImportError as error:
raise ImportError(
"load_from_hdf5 requires h5py; install 'pypaimon[ray,hdf5]'."
) from error
source = Hdf5File(path=batch["path"][0].as_py())
source_file_io = _Hdf5SourceFileIO(Options(self.source_options))
try:
for table in _transform_hdf5_file(
source,
self.transform,
source_file_io,
h5py,
self.target_schema):
if table.num_rows:
yield table
finally:
source_file_io.close()