%global pypi_name openlifu_sdk %global dist_name openlifu-sdk Name: python-%{dist_name} Version: 2.0.16 Release: 4%{?dist} Summary: Openwater LIFU SDK - hardware I/O interface library # AGPL-3.0-only per PyPI metadata, consistent with the sibling python-openlifu # and the SlicerOpenLIFU extension. Free, so this belongs in the free COPR. License: AGPL-3.0-only URL: https://github.com/OpenwaterHealth/openlifu-sdk Source0: https://files.pythonhosted.org/packages/source/o/%{pypi_name}/%{pypi_name}-%{version}.tar.gz BuildArch: noarch BuildRequires: python3-devel %description Hardware I/O interface library for Openwater's low-intensity focused ultrasound (LIFU) devices: serial and USB transport, device discovery, and a simulated interface used for software testing without connected hardware. This package is required by the SlicerOpenLIFU extension, which uses openlifu_sdk.ui.simulated_interface to run without a physical device. %package -n python3-%{dist_name} Summary: %{summary} # Runtime dependencies belong HERE, on the subpackage that is actually built. # Placed in the preamble through -3, so none of these reached the rpm. # The `ui` extra. Needed only by base_connector/telemetry_thread/version_check, # and only outside Slicer -- inside Slicer the PythonQt `qt` module serves. # The `crypto` extra. Lazily imported, so a weak dependency. # Why this package exists: 3dslicer-openlifu's own # OpenLIFULib/dependency_utils.py refuses to consider its requirements met # unless find_spec locates openlifu_sdk AND openlifu_sdk.ui.simulated_interface. # When that check fails the extension offers to pip install into the user's # Slicer, which is the behaviour packaging exists to prevent. Nothing in Fedora # or this COPR provided the module, so the extension shipped installable and # unusable. Requires: python3-numpy Requires: python3-pandas Requires: python3-xarray Requires: python3-pyserial Requires: python3-pyusb Requires: python3-base58 Recommends: python3-cryptography Recommends: python3-pyqt6 %description -n python3-%{dist_name} %{summary}. %prep %autosetup -n %{pypi_name}-%{version} -p1 # Upstream pins its dependencies with == to an exact lockfile-style version -- # numpy==2.5.1, pandas==3.0.3, xarray==2026.7.0, cryptography==46.0.5 -- none of # which match Fedora (2.4.6, 2.3.3, 2026.2.0, 50.0.0). These are release-engineering # pins, not compatibility floors, and Fedora policy is to build against the # distro's versions. Relaxed to >= so the metadata describes what is actually # installed rather than an unsatisfiable set. sed -i -E 's/([A-Za-z0-9_.-]+)==[0-9][^",]*/\1/g' pyproject.toml %generate_buildrequires %pyproject_buildrequires %build %pyproject_wheel %install %pyproject_install %pyproject_save_files -l %{pypi_name} %check # The ui submodules base_connector, telemetry_thread and version_check import # PyQt6, which is upstream's OPTIONAL `ui` extra. We deliberately do not require # it: openlifu_sdk/ui/__init__.py documents that simulated_interface works with # either PyQt6 or the PythonQt `qt` module 3D Slicer ships, "so the simulated # hardware can be plugged into a Slicer-based application without pulling PyQt6 # alongside Slicer's bundled Qt". Requiring PyQt6 would put a second Qt binding # next to Slicer's own, which is the thing that design avoids. # So exclude the ui subtree from the blanket import check. (My first attempt had # this right and I removed it for the wrong reason -- the failure was the missing # PYTHONPATH on the line below, not the exclusion.) %pyproject_check_import -e '%{pypi_name}.ui.*' # Then assert the exact path 3dslicer-openlifu probes -- and assert it the same # WAY the extension does. dependency_utils.py uses importlib.util.find_spec, # which locates the module without executing it; importing it here would demand # a Qt binding the buildroot has, by design, neither of. PYTHONPATH=%{buildroot}%{python3_sitelib} %{python3} -c "import importlib.util, sys; sys.exit(0 if importlib.util.find_spec('%{pypi_name}.ui.simulated_interface') else 1)" %files -n python3-%{dist_name} -f %{pyproject_files} %changelog * Sat Sep 26 2026 Morgan Hough - 2.0.16-4 - Move all six Requires and both Recommends onto the python3-openlifu-sdk subpackage. Through -3 every one of them sat in the preamble, attached to the never-built source package, so the rpm carried none of numpy, pandas, xarray, pyserial, pyusb or base58. * Wed Sep 23 2026 Morgan Hough - 2.0.16-3 - Second %%check failure: ModuleNotFoundError: No module named 'PyQt6'. openlifu_sdk.ui.{base_connector,telemetry_thread,version_check} import PyQt6, upstream's optional `ui` extra. PyQt6 is NOT added as a Requires: upstream designed simulated_interface to work with either PyQt6 or the PythonQt `qt` module Slicer ships, precisely so a Slicer app need not carry a second Qt binding. It is a Recommends for standalone users of the PyQt6-only helpers. - The ui subtree is excluded from the blanket import check again, and the simulated_interface assertion now uses find_spec -- the same call 3dslicer-openlifu's dependency_utils.py makes -- rather than importing, which would need a Qt binding the buildroot deliberately lacks. * Tue Sep 22 2026 Morgan Hough - 2.0.16-2 - Fix the %%check, which failed the first build. `%%pyproject_check_import -e` EXCLUDES the pattern it matches, so the openlifu_sdk.ui path the Slicer extension probes was precisely the one skipped; and the follow-up bare python import ran without the buildroot on sys.path, failing with ModuleNotFoundError whatever the package contained. The package itself built in 16 seconds. * Tue Sep 22 2026 Morgan Hough - 2.0.16-1 - Initial package. Required by 3dslicer-openlifu, whose upstream dependency check demands openlifu_sdk and openlifu_sdk.ui.simulated_interface; without them the extension prompts to pip install into the user's Slicer. - Upstream's == pins relaxed to >=: numpy, pandas, xarray and cryptography are all pinned to versions Fedora does not carry, and they are lockfile pins rather than compatibility constraints. - %%check imports openlifu_sdk.ui.simulated_interface specifically, since that is the exact path the extension probes and a package that builds without it would look successful while leaving the extension broken.