blob: f484cb282422c339d93ef27e679a1c23224a7286 [file] [log] [blame]
# 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 datafusion
import datafusion.functions
import datafusion.object_store
import datafusion.substrait
import pytest
# EnumType introduced in 3.11. 3.10 and prior it was called EnumMeta.
try:
from enum import EnumType
except ImportError:
from enum import EnumMeta as EnumType
def missing_exports(internal_obj, wrapped_obj) -> None: # noqa: C901
"""
Identify if any of the rust exposted structs or functions do not have wrappers.
Special handling for:
- Raw* classes: Internal implementation details that shouldn't be exposed
- _global_ctx: Internal implementation detail
- __self__, __class__, __repr__: Python special attributes
"""
# Special case enums - EnumType overrides a some of the internal functions,
# so check all of the values exist and move on
if isinstance(wrapped_obj, EnumType):
expected_values = [v for v in dir(internal_obj) if not v.startswith("__")]
for value in expected_values:
assert value in dir(wrapped_obj)
return
if "__repr__" in internal_obj.__dict__ and "__repr__" not in wrapped_obj.__dict__:
pytest.fail(f"Missing __repr__: {internal_obj.__name__}")
for internal_attr_name in dir(internal_obj):
wrapped_attr_name = internal_attr_name.removeprefix("Raw")
assert wrapped_attr_name in dir(wrapped_obj)
internal_attr = getattr(internal_obj, internal_attr_name)
wrapped_attr = getattr(wrapped_obj, wrapped_attr_name)
# There are some auto generated attributes that can be None, such as
# __kwdefaults__ and __doc__. As long as these are None on the internal
# object, it's okay to skip them. However if they do exist on the internal
# object they must also exist on the wrapped object.
if internal_attr is not None and wrapped_attr is None:
pytest.fail(f"Missing attribute: {internal_attr_name}")
if internal_attr_name in ["__self__", "__class__"]:
continue
if isinstance(internal_attr, list):
assert isinstance(wrapped_attr, list)
# We have cases like __all__ that are a list and we want to be certain that
# every value in the list in the internal object is also in the wrapper list
for val in internal_attr:
if isinstance(val, str) and val.startswith("Raw"):
assert val[3:] in wrapped_attr
else:
assert val in wrapped_attr
elif hasattr(internal_attr, "__dict__"):
# Check all submodules recursively
missing_exports(internal_attr, wrapped_attr)
def test_datafusion_missing_exports() -> None:
"""Check for any missing python exports.
This test verifies that every exposed class, attribute,
and function in the internal (pyo3) module - datafusion._internal
is also exposed in our python wrappers - datafusion -
i.e., the ones exposed to the public.
"""
missing_exports(datafusion._internal, datafusion)