diff --git a/src/underworld3/materials.py b/src/underworld3/materials.py index b9a9be69f..c2f0ca817 100644 --- a/src/underworld3/materials.py +++ b/src/underworld3/materials.py @@ -297,7 +297,7 @@ def add_callback(self, callback: Callable): Parameters ---------- callback : callable - Function called as callback(event_type, \*args) + Function called as ``callback(event_type, *args)`` """ self._callbacks.append(callback) diff --git a/src/underworld3/model.py b/src/underworld3/model.py index 7f9795fb8..2a398e57d 100644 --- a/src/underworld3/model.py +++ b/src/underworld3/model.py @@ -124,7 +124,7 @@ def __init__(self, name: Optional[str] = None, **kwargs): ---------- name : str, optional Human-readable name for this model instance - \*\*kwargs : dict + **kwargs : dict Additional arguments for Pydantic BaseModel """ # Handle name generation before calling super().__init__ @@ -540,7 +540,7 @@ def define_parameter(self, name: str, ptype=None, **kwargs): Parameter path (e.g., 'material.viscosity', 'solver.tolerance') ptype : ParameterType, optional Parameter type for validation (not used yet) - \*\*kwargs : dict + **kwargs : dict Additional arguments """ # TODO: Implement when parameter system is ready @@ -592,7 +592,7 @@ def set_reference_quantities(self, verbose=False, nondimensional_scaling=True, * [0-1] space while user-facing values remain in physical units. Set to False for expert mode (dimensional units only, no scaling). Disabling this may cause numerical conditioning issues. - \*\*quantities : dict + **quantities : dict Named reference quantities using Pint units or UWQuantity objects, e.g. ``domain_depth=uw.quantity(2900, "km")``. diff --git a/tests/test_0800_optional_modules.py b/tests/test_0800_optional_modules.py index 13d5bfdcc..15ac4f262 100644 --- a/tests/test_0800_optional_modules.py +++ b/tests/test_0800_optional_modules.py @@ -49,6 +49,15 @@ def check_module_available(module_name: str) -> bool: HAS_GDAL = check_module_available("osgeo.gdal") HAS_GEOPANDAS = check_module_available("geopandas") HAS_PYVISTA = check_module_available("pyvista") + +# Check if a display server is available for rendering. +# On headless CI (no DISPLAY, no WAYLAND), pyvista.Plotter() calls +# VTK's OpenGL probe which aborts the entire process — not catchable +# with try/except. We must skip rendering tests before they run. +import os +HAS_DISPLAY = bool(os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY") + or os.environ.get("PYVISTA_OFF_SCREEN")) +HAS_PYVISTA_RENDERING = HAS_PYVISTA and HAS_DISPLAY HAS_TRAME = check_module_available("trame") # Composite feature flags @@ -98,6 +107,11 @@ def check_petsc_has_pragmatic() -> bool: reason="Requires pyvista. Install with: pixi install -e runtime" ) +requires_pyvista_rendering = pytest.mark.skipif( + not HAS_PYVISTA_RENDERING, + reason="Requires pyvista and a display server (DISPLAY or PYVISTA_OFF_SCREEN)" +) + requires_amr = pytest.mark.skipif( not HAS_AMR, reason="Requires AMR-enabled PETSc. Install with: pixi install -e amr && pixi run -e amr petsc-build" @@ -233,12 +247,12 @@ def test_pyvista_mesh_conversion(self): sphere = pv.Sphere() assert sphere is not None - @requires_pyvista + @requires_pyvista_rendering def test_pyvista_plotter_available(self): """Test pyvista plotter when available.""" import pyvista as pv - # Just verify we can create a plotter (off-screen) + pv.OFF_SCREEN = True plotter = pv.Plotter(off_screen=True) assert plotter is not None plotter.close()