# ADR-0002 — Split pyproject dependencies into headless base and `gui` optional extra | Field | Value | |---|---| | **Status** | Accepted | | **Date** | 2026-06-15 | | **Author** | ogunc | --- ## 1. Context `otko.core` is pure Pydantic v2 (no Qt, no OpenSeesPy imports — stated explicitly in `core/__init__.py`). `otko.services` adds NumPy, h5py, and OpenSeesPy for headless computation. Together, these two packages can be used from scripts, Jupyter notebooks, and web backends **without any Qt or 3D-rendering stack**. Before this ADR, every `pip install otko` pulled in PySide6, pyvista, pyvistaqt, vtk, pyqtgraph, and imageio — roughly 800 MB of GUI/visualization packages — even when only the headless computation layer was needed. Web backends and CI machines without a display had to work around this with `--no-deps`, which is fragile and skips genuine compute-layer deps (numpy, h5py) too. ## 2. Decision Split `[project.dependencies]` into two tiers in `pyproject.toml`: ### Headless base (`pip install -e .`) Packages imported by `core/` and the non-Qt parts of `services/`: | Package | Where used | |---------|-----------| | `pydantic>=2.5` | All `core/` modules, `material_tester.py` | | `numpy>=1.26` | `services/` computation modules (7 files) | | `h5py>=3.10` | `opensees_runner._run_transient()`, `TransientResults` accessors | | `openseespy==3.8.0.0` | Lazy import in `OpenSeesRunner.__init__` | | `openseespywin==3.8.0.0 ; sys_platform=='win32'` | Windows DLL companion | ### GUI extra (`pip install -e ".[gui]"`) Packages only needed by `views/`, `viewmodels/`, `commands/`, `qt_workers.py`, and `animation_export.py` (which drives a live PyVista plotter): `PySide6`, `pyvista`, `pyvistaqt`, `vtk`, `pyqtgraph`, `imageio[ffmpeg]`, `scipy` (forward-compat, currently a phantom dep), `pandas` (same). ## 3. Consequences - **Desktop developers** install with `pip install -e ".[gui,dev]"`. No change to what gets installed; only the install command changes from `.[dev]` → `.[gui,dev]`. - **Web backends / scripts / notebooks** install with `pip install -e .` (or `pip install otko`) and get a lean ~50 MB environment. - **otko-web** can drop the `--no-deps` workaround and install the package normally. The web backend's `requirements.txt` no longer needs to list pydantic/numpy/h5py separately — they come from the base install. - **scipy and pandas** are listed under `[gui]` as phantom deps (currently never imported anywhere in the codebase). They are kept to avoid surprise breakage if a future feature adds them; audited and flagged on 2026-06-15. ## 4. Verification After installing only the base set: ```python import sys from otko.core import Project from otko.services.opensees_runner import OpenSeesRunner from otko.services.material_tester import test_uniaxial_material # Run a modal analysis on the bundled two-storey shear frame example import json from pathlib import Path from otko.core import ModalCase data = json.loads(Path("examples/eigen_two_storey_shear_frame.osmodel").read_text()) project = Project.model_validate(data) modal_case = next(c for c in project.analyses if isinstance(c, ModalCase)) runner = OpenSeesRunner(project) runner.build() results = runner._run_modal(modal_case) assert len(results.eigenvalues) == 2 assert all(ev > 0 for ev in results.eigenvalues) gui_packages = {"PySide6", "pyvista", "pyvistaqt", "vtk", "pyqtgraph"} assert not gui_packages.intersection(sys.modules), \ f"GUI package imported: {gui_packages & sys.modules.keys()}" print("PASS — headless modal analysis complete, no GUI packages imported") ```