Skip to content
Open
Show file tree
Hide file tree
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 Dec 19, 2025
00a2df4
Initial plan
Copilot Jan 12, 2026
caee90e
Add Variant helper and address review comments
Copilot Jan 12, 2026
a48740e
Fix code review comments
Copilot Jan 12, 2026
b61e53d
Apply cmake-format fixes
github-actions[bot] Jan 12, 2026
635f1fa
Apply Python linting fixes
github-actions[bot] Jan 12, 2026
62430b1
Initial plan
Copilot Jan 12, 2026
c1f5891
Fix ruff F722 and mypy errors in vectypes.py by using type aliases wi…
Copilot Jan 12, 2026
3cfdb11
Simplify metaclass implementation per code review feedback
Copilot Jan 12, 2026
1ff1a89
Fix CodeQL alert
greenc-FNAL Jan 12, 2026
fb31a36
Apply clang-format fixes
github-actions[bot] Jan 14, 2026
f870d15
Fix Python tests and enforce NumPy requirement
greenc-FNAL Jan 14, 2026
abd4bf5
Apply cmake-format fixes
github-actions[bot] Jan 14, 2026
7e3329b
More tests to fill gaps
greenc-FNAL Jan 14, 2026
22bb188
Apply cmake-format fixes
github-actions[bot] Jan 14, 2026
650f33b
Apply Python linting fixes
github-actions[bot] Jan 14, 2026
fb75668
Address remaining `ruff` issues
greenc-FNAL Jan 14, 2026
33e5c48
Per Gemini 3 Pro, get GIL when updating ref count
greenc-FNAL Jan 14, 2026
ef9c17a
Attempt to address CI hangs in `py:badbool` and `py:raise` tests
greenc-FNAL Jan 14, 2026
4615922
More coverage improvement
greenc-FNAL Jan 14, 2026
8d7eaf0
Apply cmake-format fixes
github-actions[bot] Jan 14, 2026
4d39398
Silence inapposite complaints; remove unused class
greenc-FNAL Jan 14, 2026
1275243
More hang protection
greenc-FNAL Jan 14, 2026
0d59220
Extra diagnostics to debug hangs during testing
greenc-FNAL Jan 14, 2026
f4f3c56
More debug logging
greenc-FNAL Jan 15, 2026
980ab44
Remove `failing_test_wrap.sh` as unnecessary
greenc-FNAL Jan 15, 2026
5dea7e2
Replace unsafe macro call with safe equivalent
greenc-FNAL Jan 15, 2026
ee90164
Remove all diagnostics to see if problems return
greenc-FNAL Jan 15, 2026
915b5b5
Remove diagnostic deadends and other unneeded code
greenc-FNAL Jan 15, 2026
bff0584
Apply clang-format fixes
github-actions[bot] Jan 15, 2026
cca9b58
Apply cmake-format fixes
github-actions[bot] Jan 15, 2026
7cf2e8d
Armor-plate `WILL_FAIL` tests against false pass
greenc-FNAL Jan 15, 2026
0e9b6ba
Remove possibly-problematic initialization check
greenc-FNAL Jan 15, 2026
f61c346
Apply cmake-format fixes
github-actions[bot] Jan 15, 2026
3a7e448
Further attempts to prevent stalls
greenc-FNAL Jan 15, 2026
659589b
Remove diagnostic invocations from coverage workflow
greenc-FNAL Jan 15, 2026
909b48b
Encourage `ctest --test-timeout` to limit impact of stalling tests
greenc-FNAL Jan 15, 2026
eb3e3a7
First pass at addressing review comments
greenc-FNAL Jan 15, 2026
737690c
Restore array-bounds warning deactivation for GCC 15
greenc-FNAL Jan 15, 2026
1877231
Improve Python argument ordering stability
greenc-FNAL Jan 15, 2026
3775d41
Apply clang-format fixes
github-actions[bot] Jan 15, 2026
eda7f4c
Make sure types agree with what's in vectypes.py (#10)
knoepfel Jan 15, 2026
14f6411
Apply cmake-format fixes
github-actions[bot] Jan 15, 2026
bb5fa1a
Revert unwanted change per review
greenc-FNAL Jan 15, 2026
432c76a
Have CMake report module check results
greenc-FNAL Jan 15, 2026
cb73ed3
Python AdjustAnnotations class improvements
greenc-FNAL Jan 15, 2026
06a9158
Apply cmake-format fixes
github-actions[bot] Jan 15, 2026
2c1dec6
Make sure non-test Python code is tested
greenc-FNAL Jan 16, 2026
aa60daf
Apply Python linting fixes
github-actions[bot] Jan 16, 2026
fa63ba9
Apply cmake-format fixes
github-actions[bot] Jan 16, 2026
8997a03
Address `ruff` issues
greenc-FNAL Jan 16, 2026
3d216af
Resolve issues with Python testing and coverage
greenc-FNAL Jan 16, 2026
9432c81
Enable FORM by default in presets
greenc-FNAL Jan 16, 2026
ecd7d46
Temporarily restore packaging workaround pending reconciliation
greenc-FNAL Jan 16, 2026
c5907d3
Rename AdjustAnnotations to Variant
google-labs-jules[bot] Jan 31, 2026
71b407f
Remove diagnostics
greenc-FNAL Jan 31, 2026
3c31c1a
Revert architectural churn while preserving test improvements and fixes
google-labs-jules[bot] Feb 3, 2026
b483e9a
Apply markdownlint fixes
github-actions[bot] Feb 3, 2026
7166c6b
Apply clang-format fixes
github-actions[bot] Feb 3, 2026
db8ac8a
Fix Jsonnet format issues
greenc-FNAL Feb 3, 2026
9b1d0ba
Fix NumPy array matching for PEP 604 union types in modulewrap.cpp
google-labs-jules[bot] Feb 3, 2026
3e1a9f7
Add diagnostics for test segfault
google-labs-jules[bot] Feb 3, 2026
d4ce833
Incorporate @wlav's use of `OrderedDict` into `Variant`
greenc-FNAL Feb 3, 2026
a06d2fa
Provide correct annotations for `example_func`
greenc-FNAL Feb 4, 2026
8870a9b
Fix Python test failures and improve annotation parsing robustness
google-labs-jules[bot] Feb 4, 2026
ff089ab
Fix Python test failures, improve annotation parsing, and resolve lin…
google-labs-jules[bot] Feb 4, 2026
95d2fbe
Fix Python test failures, improve annotation parsing, and add defensi…
google-labs-jules[bot] Feb 4, 2026
57fe76b
Address Python test failures and robustify Python-C++ bridge
google-labs-jules[bot] Feb 4, 2026
e3bf9db
Apply clang-format fixes
github-actions[bot] Feb 4, 2026
485e3e2
Fix Python test regressions and improve reference management
google-labs-jules[bot] Feb 4, 2026
e1de309
Address Python test failures, improve refcounting and fix SegFaults
google-labs-jules[bot] Feb 4, 2026
8d12e61
Address Wim's review comments.
Copilot Feb 9, 2026
40bff07
Address review comments: fix docstrings and restore test mismatch beh…
Copilot Feb 9, 2026
047bdd2
Restore positional annotation matching and clean up tests
google-labs-jules[bot] Feb 10, 2026
1c41cf5
Restore positional annotation matching and clean up Python tests
google-labs-jules[bot] Feb 10, 2026
510e762
Resolve Phlex Python test failures and robustify interop
google-labs-jules[bot] Feb 10, 2026
b0510b5
Apply clang-format fixes
github-actions[bot] Feb 10, 2026
e1d2b41
Address @wlav's latest review comments
greenc-FNAL Feb 10, 2026
81bff12
Apply clang-format fixes
github-actions[bot] Feb 10, 2026
20ac0fc
Use `PySequence_Fast()` for flexibility with sequence types
greenc-FNAL Feb 10, 2026
281a74f
Apply clang-format fixes
github-actions[bot] Feb 10, 2026
fb0df44
Revert to `PyObject_CallFunctionObjArgs()` per @wlav review
greenc-FNAL Feb 10, 2026
3c30056
Remove unwanted `lifeline_transform()` from `callv()`
greenc-FNAL Feb 11, 2026
72cb86c
Apply clang-format fixes
github-actions[bot] Feb 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,60 @@ All Markdown files must strictly follow these markdownlint rules:
- **MD034**: No bare URLs (for example, use a markdown link like `[text](destination)` instead of a plain URL)
- **MD036**: Use # headings, not **Bold:** for titles
- **MD040**: Always specify code block language (for example, use '```bash', '```python', '```text', etc.)

## Development & Testing Workflows

### Build and Test

- **Environment**: Always source `setup-env.sh` before building or testing. This applies to all environments (Dev Container, local machine, HPC).
- **Configuration**:
- **Presets**: Prefer `CMakePresets.json` workflows (e.g., `cmake --preset default`).
- **Generator**: Prefer `Ninja` over `Makefiles` when available (`-G Ninja`).
- **Build**:
- **Parallelism**: Always use multiple cores. Ninja does this by default. For `make`, use `cmake --build build -j $(nproc)`.
- **Test**:
- **Parallelism**: Run tests in parallel using `ctest -j $(nproc)` or `ctest --parallel <N>`.
- **Selection**: Run specific tests with `ctest -R "regex"` (e.g., `ctest -R "py:*"`).
- **Debugging**: Use `ctest --output-on-failure` to see logs for failed tests.
- **Guard against known or suspected stalling tests**: Use `ctest --test-timeout` to set the per-test time limit (e.g. `90`) for 90s, *vs* the default of 1500s.

### Python Integration

- **Naming**: Avoid naming Python test scripts `types.py` or other names that shadow standard library modules. This causes obscure import errors (e.g., `ModuleNotFoundError: No module named 'numpy'`).
- **PYTHONPATH**: Only include paths that contain user Python modules loaded by Phlex (for example, the source directory and any build output directory that houses generated modules). Do not append system/Spack/venv `site-packages`; `pymodule.cpp` handles CMAKE_PREFIX_PATH and virtual-environment path adjustments.
- **Test Structure**:
- **C++ Driver**: Provides data streams (e.g., `test/python/driver.cpp`).
- **Jsonnet Config**: Wires the graph (e.g., `test/python/pytypes.jsonnet`).
- **Python Script**: Implements algorithms (e.g., `test/python/test_types.py`).
- **Type Conversion**: `plugins/python/src/modulewrap.cpp` handles C++ ↔ Python conversion.
- **Mechanism**: Uses substring matching on type names (for example, `"float64]]"`). This is brittle.
- **Requirement**: Ensure converters exist for all types used in tests (e.g., `float`, `double`, `unsigned int`, and their vector equivalents).
- **Warning**: Exact type matches are required. `numpy.float32` != `float`.

### Coverage Analysis

- **Tooling**: The project uses LLVM source-based coverage.
- **Requirement**: The `phlex` binary must catch exceptions in `main` to ensure coverage data is flushed to disk even when tests fail/crash.
- **Generation**:
- **CMake Targets**: `coverage-xml`, `coverage-html` (if configured).
- **Manual**:
1. Run tests with `LLVM_PROFILE_FILE` set (e.g., `export LLVM_PROFILE_FILE="profraw/%m-%p.profraw"`).
2. Merge profiles: `llvm-profdata merge -sparse profraw/*.profraw -o coverage.profdata`.
3. Generate report: `llvm-cov show -instr-profile=coverage.profdata -format=html ...`

### Local GitHub Actions Testing (`act`)

- **Tool**: Use `act` to run GitHub Actions workflows locally.
- **Configuration**: Ensure `.actrc` exists in the workspace root with the following content to use a compatible runner image:

```text
-P ubuntu-latest=catthehacker/ubuntu:act-latest
```

- **Usage**:
- List jobs: `act -l`
- Run specific job: `act -j <job_name>` (e.g., `act -j python-check`)
- Run specific event: `act pull_request`
- **Troubleshooting**:
- **Docker Socket**: `act` requires access to the Docker socket. In dev containers, this may require specific mount configurations or permissions.
- **Artifacts**: `act` creates a `phlex-src` directory (or similar) for checkout. Ensure this is cleaned up or ignored by tools like `mypy`.
26 changes: 15 additions & 11 deletions .gitignore
Copy link
Member

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?

Copy link
Contributor Author

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.

Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
# Build directories
build/
build-cov/
_build/
*.dir/
phlex-src
phlex-build/
CMakeCache.txt
/phlex-src/
/phlex-build/
/CMakeCache.txt
CMakeFiles/
_deps/
/_deps/
_codeql_detected_source_root

# CMake user-specific presets (not generated by Spack)
CMakeUserPresets.json
/CMakeUserPresets.json

# Coverage reports
coverage.xml
coverage.info
coverage-html/
.coverage-generated/
.coverage-artifacts/
/coverage.profdata
/coverage_*.txt
/coverage.xml
/coverage.info
/coverage-html/
/profraw/
/.coverage-generated/
/.coverage-artifacts/
*.gcda
*.gcno
*.gcov
Expand Down Expand Up @@ -45,5 +49,5 @@ __pycache__/
.DS_Store
# act (local workflow testing)
.act-artifacts/
.secretsactionlint
.secrets
actionlint
25 changes: 16 additions & 9 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ project(phlex VERSION 0.1.0 LANGUAGES CXX)
cet_cmake_env()
# ##############################################################################

# Set CI/test timeouts to a conservative value to avoid long stalls in CI.
# Use cache variables so generated CTest/Dart files pick this up when configured.
set(DART_TESTING_TIMEOUT 90 CACHE STRING "Timeout (s) for Dart/CTest runs")
set(CTEST_TEST_TIMEOUT 90 CACHE STRING "Per-test timeout (s) for CTest")

# Make tools available
FetchContent_MakeAvailable(Catch2 GSL mimicpp)

Expand All @@ -70,13 +75,13 @@ add_compile_options(
)

if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.1"
AND CMAKE_COMPILER_VERSION VERSION_LESS "15"
)
# GCC 14.1 issues many false positives re. array-bounds and
# stringop-overflow
add_compile_options(-Wno-array-bounds -Wno-stringop-overflow)
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.1")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "15")
add_compile_options(-Wno-stringop-overflow)
endif()
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "16")
add_compile_options(-Wno-array-bounds)
endif()
endif()
endif()

Expand Down Expand Up @@ -108,7 +113,8 @@ if(ENABLE_TSAN)
-g
-O1
# Ensure no optimizations interfere with TSan
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fno-omit-frame-pointer -fno-optimize-sibling-calls>"
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fno-omit-frame-pointer>"
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fno-optimize-sibling-calls>"
)
add_link_options(-fsanitize=thread)
else()
Expand All @@ -130,7 +136,8 @@ if(ENABLE_ASAN)
-g
-O1
# Ensure no optimizations interfere with ASan
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fno-omit-frame-pointer -fno-optimize-sibling-calls>"
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fno-omit-frame-pointer>"
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fno-optimize-sibling-calls>"
)
add_link_options(-fsanitize=address)
else()
Expand Down
1 change: 1 addition & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"name": "default",
"hidden": false,
"cacheVariables": {
"PHLEX_USE_FORM": "ON",
"CMAKE_EXPORT_COMPILE_COMMANDS": "YES",
"CMAKE_CXX_STANDARD": "23",
"CMAKE_CXX_STANDARD_REQUIRED": "YES",
Expand Down
4 changes: 4 additions & 0 deletions phlex/core/edge_maker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
provider.full_name(),
node_name,
port.product_label.to_string());
if (port.port == nullptr) {
throw std::runtime_error("Unexpected null port while connecting provider " +
provider.full_name() + " to node " + node_name);

Check warning on line 33 in phlex/core/edge_maker.cpp

View check run for this annotation

Codecov / codecov/patch

phlex/core/edge_maker.cpp#L32-L33

Added lines #L32 - L33 were not covered by tests
}
make_edge(provider.sender(), *(port.port));
found_match = true;
break;
Expand Down
7 changes: 7 additions & 0 deletions phlex/core/edge_maker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@
continue;
}

if (producer->port == nullptr or receiver_port == nullptr) {
throw std::runtime_error("Unexpected null port while connecting " +
producer->node.full() + " to " + node_name);

Check warning on line 78 in phlex/core/edge_maker.hpp

View check run for this annotation

Codecov / codecov/patch

phlex/core/edge_maker.hpp#L77-L78

Added lines #L77 - L78 were not covered by tests
}
make_edge(*producer->port, *receiver_port);
}
}
Expand All @@ -93,6 +97,9 @@
for (auto const& [output_name, output_node] : outputs) {
make_edge(source, output_node->port());
for (auto const& named_port : producers_.values()) {
if (named_port.to_output == nullptr) {
throw std::runtime_error("Unexpected null output port for " + named_port.node.full());

Check warning on line 101 in phlex/core/edge_maker.hpp

View check run for this annotation

Codecov / codecov/patch

phlex/core/edge_maker.hpp#L101

Added line #L101 was not covered by tests
}
make_edge(*named_port.to_output, output_node->port());
}
}
Expand Down
5 changes: 5 additions & 0 deletions plugins/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ target_link_libraries(pymodule PRIVATE phlex::module Python::Python Python::NumP
target_compile_definitions(pymodule PRIVATE NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION)

install(TARGETS pymodule LIBRARY DESTINATION lib)

install(
DIRECTORY python/phlex
DESTINATION lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages
)
57 changes: 57 additions & 0 deletions plugins/python/README.md
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.
109 changes: 109 additions & 0 deletions plugins/python/python/phlex/__init__.py
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
2 changes: 1 addition & 1 deletion plugins/python/src/configwrap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ static PyObject* pcm_subscript(py_config_map* pycmap, PyObject* pykey)
}
}
} catch (std::runtime_error const&) {
PyErr_Format(PyExc_TypeError, "property \"%s\" does not exist", ckey.c_str());
PyErr_Format(PyExc_KeyError, "property \"%s\" does not exist", ckey.c_str());
}

// cache if found
Expand Down
5 changes: 5 additions & 0 deletions plugins/python/src/lifelinewrap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, these are correct.

}

// clang-format off
Expand Down
Loading
Loading