|
28 | 28 | from enum import EnumMeta as EnumType
|
29 | 29 |
|
30 | 30 |
|
31 |
| -def missing_exports(internal_obj, wrapped_obj) -> None: |
32 |
| - # Special case enums - just make sure they exist since dir() |
33 |
| - # and other functions get overridden. |
| 31 | +def missing_exports(internal_obj, wrapped_obj) -> None: # noqa: C901 |
| 32 | + """ |
| 33 | + Identify if any of the rust exposted structs or functions do not have wrappers. |
| 34 | +
|
| 35 | + Special handling for: |
| 36 | + - Raw* classes: Internal implementation details that shouldn't be exposed |
| 37 | + - _global_ctx: Internal implementation detail |
| 38 | + - __self__, __class__: Python special attributes |
| 39 | + """ |
| 40 | + # Special case enums - EnumType overrides a some of the internal functions, |
| 41 | + # so check all of the values exist and move on |
34 | 42 | if isinstance(wrapped_obj, EnumType):
|
| 43 | + expected_values = [v for v in dir(internal_obj) if not v.startswith("__")] |
| 44 | + for value in expected_values: |
| 45 | + assert value in dir(wrapped_obj) |
35 | 46 | return
|
36 | 47 |
|
37 |
| - for attr in dir(internal_obj): |
38 |
| - if attr in ["_global_ctx"]: |
39 |
| - continue |
40 |
| - assert attr in dir(wrapped_obj) |
| 48 | + for internal_attr_name in dir(internal_obj): |
| 49 | + wrapped_attr_name = internal_attr_name.removeprefix("Raw") |
| 50 | + assert wrapped_attr_name in dir(wrapped_obj) |
41 | 51 |
|
42 |
| - internal_attr = getattr(internal_obj, attr) |
43 |
| - wrapped_attr = getattr(wrapped_obj, attr) |
| 52 | + internal_attr = getattr(internal_obj, internal_attr_name) |
| 53 | + wrapped_attr = getattr(wrapped_obj, wrapped_attr_name) |
44 | 54 |
|
45 |
| - if internal_attr is not None and wrapped_attr is None: |
46 |
| - pytest.fail(f"Missing attribute: {attr}") |
| 55 | + # There are some auto generated attributes that can be None, such as |
| 56 | + # __kwdefaults__ and __doc__. As long as these are None on the internal |
| 57 | + # object, it's okay to skip them. However if they do exist on the internal |
| 58 | + # object they must also exist on the wrapped object. |
| 59 | + if internal_attr is not None: |
| 60 | + if wrapped_attr is None: |
| 61 | + pytest.fail(f"Missing attribute: {internal_attr_name}") |
47 | 62 |
|
48 |
| - if attr in ["__self__", "__class__"]: |
| 63 | + if internal_attr_name in ["__self__", "__class__"]: |
49 | 64 | continue
|
| 65 | + |
50 | 66 | if isinstance(internal_attr, list):
|
51 | 67 | assert isinstance(wrapped_attr, list)
|
| 68 | + |
| 69 | + # We have cases like __all__ that are a list and we want to be certain that |
| 70 | + # every value in the list in the internal object is also in the wrapper list |
52 | 71 | for val in internal_attr:
|
53 |
| - assert val in wrapped_attr |
| 72 | + if isinstance(val, str) and val.startswith("Raw"): |
| 73 | + assert val[3:] in wrapped_attr |
| 74 | + else: |
| 75 | + assert val in wrapped_attr |
54 | 76 | elif hasattr(internal_attr, "__dict__"):
|
| 77 | + # Check all submodules recursively |
55 | 78 | missing_exports(internal_attr, wrapped_attr)
|
56 | 79 |
|
57 | 80 |
|
58 | 81 | def test_datafusion_missing_exports() -> None:
|
59 | 82 | """Check for any missing python exports.
|
60 | 83 |
|
61 |
| - This test verifies that every exposed class, attribute, and function in |
62 |
| - the internal (pyo3) module is also exposed in our python wrappers. |
| 84 | + This test verifies that every exposed class, attribute, |
| 85 | + and function in the internal (pyo3) module - datafusion._internal |
| 86 | + is also exposed in our python wrappers - datafusion - |
| 87 | + i.e., the ones exposed to the public. |
63 | 88 | """
|
64 | 89 | missing_exports(datafusion._internal, datafusion)
|
0 commit comments