-
Notifications
You must be signed in to change notification settings - Fork 10
Improve test coverage for Python support #213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
greenc-FNAL
wants to merge
84
commits into
Framework-R-D:main
Choose a base branch
from
greenc-FNAL:maintenance/improve-test-coverage
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
84 commits
Select commit
Hold shift + click to select a range
c09acae
Improve test coverage of Python/C++ interface code
greenc-FNAL 00a2df4
Initial plan
Copilot caee90e
Add Variant helper and address review comments
Copilot a48740e
Fix code review comments
Copilot b61e53d
Apply cmake-format fixes
github-actions[bot] 635f1fa
Apply Python linting fixes
github-actions[bot] 62430b1
Initial plan
Copilot c1f5891
Fix ruff F722 and mypy errors in vectypes.py by using type aliases wi…
Copilot 3cfdb11
Simplify metaclass implementation per code review feedback
Copilot 1ff1a89
Fix CodeQL alert
greenc-FNAL fb31a36
Apply clang-format fixes
github-actions[bot] f870d15
Fix Python tests and enforce NumPy requirement
greenc-FNAL abd4bf5
Apply cmake-format fixes
github-actions[bot] 7e3329b
More tests to fill gaps
greenc-FNAL 22bb188
Apply cmake-format fixes
github-actions[bot] 650f33b
Apply Python linting fixes
github-actions[bot] fb75668
Address remaining `ruff` issues
greenc-FNAL 33e5c48
Per Gemini 3 Pro, get GIL when updating ref count
greenc-FNAL ef9c17a
Attempt to address CI hangs in `py:badbool` and `py:raise` tests
greenc-FNAL 4615922
More coverage improvement
greenc-FNAL 8d7eaf0
Apply cmake-format fixes
github-actions[bot] 4d39398
Silence inapposite complaints; remove unused class
greenc-FNAL 1275243
More hang protection
greenc-FNAL 0d59220
Extra diagnostics to debug hangs during testing
greenc-FNAL f4f3c56
More debug logging
greenc-FNAL 980ab44
Remove `failing_test_wrap.sh` as unnecessary
greenc-FNAL 5dea7e2
Replace unsafe macro call with safe equivalent
greenc-FNAL ee90164
Remove all diagnostics to see if problems return
greenc-FNAL 915b5b5
Remove diagnostic deadends and other unneeded code
greenc-FNAL bff0584
Apply clang-format fixes
github-actions[bot] cca9b58
Apply cmake-format fixes
github-actions[bot] 7cf2e8d
Armor-plate `WILL_FAIL` tests against false pass
greenc-FNAL 0e9b6ba
Remove possibly-problematic initialization check
greenc-FNAL f61c346
Apply cmake-format fixes
github-actions[bot] 3a7e448
Further attempts to prevent stalls
greenc-FNAL 659589b
Remove diagnostic invocations from coverage workflow
greenc-FNAL 909b48b
Encourage `ctest --test-timeout` to limit impact of stalling tests
greenc-FNAL eb3e3a7
First pass at addressing review comments
greenc-FNAL 737690c
Restore array-bounds warning deactivation for GCC 15
greenc-FNAL 1877231
Improve Python argument ordering stability
greenc-FNAL 3775d41
Apply clang-format fixes
github-actions[bot] eda7f4c
Make sure types agree with what's in vectypes.py (#10)
knoepfel 14f6411
Apply cmake-format fixes
github-actions[bot] bb5fa1a
Revert unwanted change per review
greenc-FNAL 432c76a
Have CMake report module check results
greenc-FNAL cb73ed3
Python AdjustAnnotations class improvements
greenc-FNAL 06a9158
Apply cmake-format fixes
github-actions[bot] 2c1dec6
Make sure non-test Python code is tested
greenc-FNAL aa60daf
Apply Python linting fixes
github-actions[bot] fa63ba9
Apply cmake-format fixes
github-actions[bot] 8997a03
Address `ruff` issues
greenc-FNAL 3d216af
Resolve issues with Python testing and coverage
greenc-FNAL 9432c81
Enable FORM by default in presets
greenc-FNAL ecd7d46
Temporarily restore packaging workaround pending reconciliation
greenc-FNAL c5907d3
Rename AdjustAnnotations to Variant
google-labs-jules[bot] 71b407f
Remove diagnostics
greenc-FNAL 3c31c1a
Revert architectural churn while preserving test improvements and fixes
google-labs-jules[bot] b483e9a
Apply markdownlint fixes
github-actions[bot] 7166c6b
Apply clang-format fixes
github-actions[bot] db8ac8a
Fix Jsonnet format issues
greenc-FNAL 9b1d0ba
Fix NumPy array matching for PEP 604 union types in modulewrap.cpp
google-labs-jules[bot] 3e1a9f7
Add diagnostics for test segfault
google-labs-jules[bot] d4ce833
Incorporate @wlav's use of `OrderedDict` into `Variant`
greenc-FNAL a06d2fa
Provide correct annotations for `example_func`
greenc-FNAL 8870a9b
Fix Python test failures and improve annotation parsing robustness
google-labs-jules[bot] ff089ab
Fix Python test failures, improve annotation parsing, and resolve lin…
google-labs-jules[bot] 95d2fbe
Fix Python test failures, improve annotation parsing, and add defensi…
google-labs-jules[bot] 57fe76b
Address Python test failures and robustify Python-C++ bridge
google-labs-jules[bot] e3bf9db
Apply clang-format fixes
github-actions[bot] 485e3e2
Fix Python test regressions and improve reference management
google-labs-jules[bot] e1de309
Address Python test failures, improve refcounting and fix SegFaults
google-labs-jules[bot] 8d12e61
Address Wim's review comments.
Copilot 40bff07
Address review comments: fix docstrings and restore test mismatch beh…
Copilot 047bdd2
Restore positional annotation matching and clean up tests
google-labs-jules[bot] 1c41cf5
Restore positional annotation matching and clean up Python tests
google-labs-jules[bot] 510e762
Resolve Phlex Python test failures and robustify interop
google-labs-jules[bot] b0510b5
Apply clang-format fixes
github-actions[bot] e1d2b41
Address @wlav's latest review comments
greenc-FNAL 81bff12
Apply clang-format fixes
github-actions[bot] 20ac0fc
Use `PySequence_Fast()` for flexibility with sequence types
greenc-FNAL 281a74f
Apply clang-format fixes
github-actions[bot] fb0df44
Revert to `PyObject_CallFunctionObjArgs()` per @wlav review
greenc-FNAL 3c30056
Remove unwanted `lifeline_transform()` from `callv()`
greenc-FNAL 72cb86c
Apply clang-format fixes
github-actions[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # Phlex Python Plugin Architecture | ||
|
|
||
| This directory contains the C++ source code for the Phlex Python plugin, which enables Phlex to execute Python code as part of its computation graph. | ||
|
|
||
| ## Architecture Overview | ||
|
|
||
| The integration is built on the **Python C API** (not `pybind11`) to maintain strict control over the interpreter lifecycle and memory management. | ||
|
|
||
| ### 1. The "Type Bridge" (`modulewrap.cpp`) | ||
|
|
||
| The core of the integration is the type conversion layer in `src/modulewrap.cpp`. This layer is responsible for: | ||
|
|
||
| - Converting Phlex `Product` objects (C++) into Python objects (e.g., `PyObject*`, `numpy.ndarray`). | ||
| - Converting Python return values back into Phlex `Product` objects. | ||
|
|
||
| **Critical Implementation Detail:** | ||
| The type mapping relies on **string comparison** of type names. | ||
|
|
||
| - **Mechanism**: The C++ code checks whether `type_name()` contains `"float64]]"` to identify a 2D array of doubles. | ||
| - **Brittleness**: This is a fragile contract. If the type name changes (e.g., `numpy` changes its string representation) or if a user provides a slightly different type (e.g., `float` vs `np.float32`), the bridge may fail. | ||
| - **Extension**: When adding support for new types, you must explicitly add converters in `modulewrap.cpp` for both scalar and vector/array versions. | ||
|
|
||
| ### 2. Hybrid Configuration | ||
|
|
||
| Phlex uses a hybrid configuration model involving three languages: | ||
|
|
||
| 1. **Jsonnet** (`*.jsonnet`): Defines the computation graph structure. It specifies: | ||
| - The nodes in the graph. | ||
| - The Python module/class to load for specific nodes. | ||
| - Configuration parameters passed to the Python object. | ||
| 2. **C++ Driver**: The executable that: | ||
| - Parses the Jsonnet configuration. | ||
| - Initializes the Phlex core. | ||
| - Loads the Python interpreter and the specified plugin. | ||
| 3. **Python Code** (`*.py`): Implements the algorithmic logic. | ||
|
|
||
| ### 3. Environment & Testing | ||
|
|
||
| Because the Python interpreter is embedded within the C++ application, the runtime environment is critical. | ||
|
|
||
| - **PYTHONPATH**: Must be set correctly to include: | ||
| - The build directory (for generated modules). | ||
| - The source directory (for user scripts). | ||
| - Do not append system/Spack `site-packages`; `pymodule.cpp` adjusts `sys.path` based on `CMAKE_PREFIX_PATH` and active virtual environments. | ||
| - **Naming Collisions**: | ||
| - **Warning**: Do not name test files `types.py`, `test.py`, `code.py`, or other names that shadow standard library modules. | ||
| - **Consequence**: Shadowing can cause obscure failures in internal libraries (e.g., `numpy` failing to import because it tries to import `types` from the standard library but gets your local file instead). | ||
|
|
||
| ## Development Guidelines | ||
|
|
||
| 1. **Adding New Types**: | ||
| - Update `src/modulewrap.cpp` to handle the new C++ type. | ||
| - Add a corresponding test case in `test/python/` to verify the round-trip conversion. | ||
| 2. **Testing**: | ||
| - Use `ctest` to run tests. | ||
| - Tests are integration tests: they run the full C++ application which loads the Python script. | ||
| - Debugging: Use `ctest --output-on-failure` to see Python exceptions. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| """Annotation helper for C++ typing variants. | ||
|
|
||
| Python algorithms are generic, like C++ templates, but the Phlex registration | ||
| process requires a single unique signature. These helpers generate annotated | ||
| functions for registration with the proper C++ types. | ||
| """ | ||
|
|
||
| import collections | ||
| import copy | ||
| import inspect | ||
| from typing import Any, Callable | ||
|
|
||
|
|
||
| class MissingAnnotation(Exception): | ||
| """Exception noting the missing of an argument in the provied annotations.""" | ||
|
|
||
| def __init__(self, arg: str): | ||
| """Construct exception from the name of the argument without annotation.""" | ||
| self.arg = arg | ||
|
|
||
| def __str__(self): | ||
| """Report the argument that is missing an annotation.""" | ||
| return "argument '%s' is not annotated" % self.arg | ||
|
|
||
|
|
||
| class Variant: | ||
| """Wrapper to associate custom annotations with a callable. | ||
|
|
||
| This class wraps a callable and provides custom ``__annotations__`` and | ||
| ``__name__`` attributes, allowing the same underlying function or callable | ||
| object to be registered multiple times with different type annotations. | ||
|
|
||
| By default, the provided callable is kept by reference, but can be cloned | ||
| (e.g. for callable instances) if requested. | ||
|
|
||
| Phlex will recognize the "phlex_callable" data member, allowing an unwrap | ||
| and thus saving an indirection. To detect performance degradation, the | ||
| wrapper is not callable by default. | ||
|
|
||
| Attributes: | ||
| phlex_callable (Callable): The underlying callable (public). | ||
| __annotations__ (dict): Type information of arguments and return product. | ||
| __name__ (str): The name associated with this variant. | ||
|
|
||
| Examples: | ||
| >>> def add(i: Number, j: Number) -> Number: | ||
| ... return i + j | ||
| ... | ||
| >>> int_adder = Variant(add, {"i": int, "j": int, "return": int}, "iadd") | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| f: Callable, | ||
| annotations: dict[str, str | type | Any], | ||
| name: str, | ||
| clone: bool | str = False, | ||
| allow_call: bool = False, | ||
| ): | ||
| """Annotate the callable F. | ||
|
|
||
| Args: | ||
| f (Callable): Annotable function. | ||
| annotations (dict): Type information of arguments and return product. | ||
| name (str): Name to assign to this variant. | ||
| clone (bool|str): If True (or "deep"), creates a shallow (deep) copy | ||
| of the callable. | ||
| allow_call (bool): Allow this wrapper to forward to the callable. | ||
| """ | ||
| if clone == "deep": | ||
| self.phlex_callable = copy.deepcopy(f) | ||
| elif clone: | ||
| self.phlex_callable = copy.copy(f) | ||
| else: | ||
| self.phlex_callable = f | ||
|
|
||
| # annotions are expected as an ordinary dict and should be ordered, but | ||
| # we do not require it, so re-order based on the function's co_varnames | ||
| self.__annotations__ = collections.OrderedDict() | ||
|
|
||
| sig = inspect.signature(self.phlex_callable) | ||
| for k, v in sig.parameters.items(): | ||
| try: | ||
| self.__annotations__[k] = annotations[k] | ||
| except KeyError as e: | ||
| if v.default is inspect.Parameter.empty: | ||
| raise MissingAnnotation(k) from e | ||
|
|
||
| self.__annotations__["return"] = annotations.get("return", None) | ||
|
|
||
| self.__name__ = name | ||
| self.__code__ = getattr(self.phlex_callable, "__code__", None) | ||
| self.__defaults__ = getattr(self.phlex_callable, "__defaults__", None) | ||
| self._allow_call = allow_call | ||
|
|
||
| def __call__(self, *args, **kwargs): | ||
| """Raises an error if called directly. | ||
|
|
||
| Variant instances should not be called directly. The framework should | ||
| extract ``phlex_callable`` instead and call that. | ||
|
|
||
| Raises: | ||
| AssertionError: To indicate incorrect usage, unless overridden. | ||
| """ | ||
| assert self._allow_call, ( | ||
| f"Variant '{self.__name__}' was called directly. " | ||
| f"The framework should extract phlex_callable instead." | ||
| ) | ||
| return self.phlex_callable(*args, **kwargs) # type: ignore |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,9 +31,14 @@ static int ll_clear(py_lifeline_t* pyobj) | |
|
|
||
| static void ll_dealloc(py_lifeline_t* pyobj) | ||
| { | ||
| // This type participates in GC; untrack before clearing references so the | ||
| // collector does not traverse a partially torn-down object during dealloc. | ||
| PyObject_GC_UnTrack(pyobj); | ||
| Py_CLEAR(pyobj->m_view); | ||
| typedef std::shared_ptr<void> generic_shared_t; | ||
| pyobj->m_source.~generic_shared_t(); | ||
| // Use tp_free to pair with tp_alloc for GC-tracked Python objects. | ||
| Py_TYPE(pyobj)->tp_free((PyObject*)pyobj); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks scary. I don't claim that it's wrong. But do we understand why these changes are required?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, these are correct. |
||
| } | ||
|
|
||
| // clang-format off | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't some of these changes suggest the build directory may be at
/? Is this ever the case?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It pegs the ignore expression to the top-level directory, otherwise it matches in subdirectories also.