Make optional Assimulo imports lazy#135
Conversation
bernalde
left a comment
There was a problem hiding this comment.
Maintainer review of the lazy-Assimulo-import change (Refs #134): a PharmaPy/_assimulo.py shim, top-level Assimulo imports removed from 9 solver-backed modules, 8 solver-free tests moved into the core lane, the fake sys.modules helper removed, and a new import-boundary regression test. I authored this PR, so it is posted as a COMMENT — GitHub does not accept an author APPROVE/REQUEST_CHANGES. No blocking findings.
What I verified
- File set matches
gh pr diff(20 files); merge-base diff confirmed. TerminateSimulationis behavior-preserving. Evaporators/SolidLiquidSep now import it fromPharmaPy.Commonsinstead ofassimulo.exception.Commons.py(unchanged) already defines it astry: from assimulo.exception import TerminateSimulation / except ImportError: class TerminateSimulation(Exception). So when Assimulo is installed it is the same exception object Assimulo's event loop raises/catches — event termination is unchanged — and when absent it is an importable fallback. This was the main regression risk and it is handled correctly.- The shim is a drop-in for construction only. Every use of
CVode/IDA/Explicit_Problem/Implicit_Problemacross the 9 modules is a constructor call (Explicit_Problem(...),CVode(problem)); noisinstance/subclassing/class-attribute use exists (grep-confirmed), so replacing the classes with factory functions forwarding*args/**kwargspreserves behavior. Instance methods/attrs (solver.make_consistent(...),.simulate()) run on the real returned Assimulo object. - Removed imports are genuinely dead.
LSODARappears only in a commented-out line (Reactors.py:1144);Radau5DAEhas no remaining references. - No stragglers. The only
from assimuloleft inPharmaPy/*.pyis the Commons fallback.MetaModelerstill writesfrom assimulo...lines into the standalone scripts it generates, but it does not import Assimulo itself and those generated scripts are meant to run in an Assimulo environment — out of scope and correct. - Regression test is genuine (ran locally). In an Assimulo-free Python 3.13 env:
tests/test_optional_assimulo_imports.py→ 2 passed at head. Overlaying basemaster'sPharmaPy/into an isolated worktree,test_model_modules_import_without_assimulofails atContainers.pyfrom assimulo.solvers import CVode— matching the PR body's red-on-base claim. The subprocessbuiltins.__import__blocker is a robust replacement for the old fake-sys.modulestree.
Linked issue (#134)
Correctly Refs #134. The change implements the optional-dependency boundary the issue asks for: solver-free constructors/balances/shortcuts import without Assimulo, and a solver-backed path raises an actionable ImportError at problem/solver construction.
Nonblocking
- The four public shim symbols look like classes but are factory functions — a one-line foot-gun for future contributors. See inline.
Merge-readiness
No blocking findings. Live state: state=OPEN, mergeStateStatus=CLEAN, reviewDecision empty. CI at head 1f9c4f0: Core tests SUCCESS, Assimulo integration tests SUCCESS. Posting this review adds one inline thread; since mergeStateStatus is CLEAN, required_conversation_resolution is not enforced, so it is not a merge gate. This author account cannot supply a formal APPROVE — GitHub currently reports no approval gate, but if branch protection requires one, another eligible maintainer must approve. Note the PR body's own hygiene flag: PRs #121 and #124 overlap these files, so whichever lands second must preserve both #121's shortcut changes and this lazy-import boundary.
| return constructor(*args, **kwargs) | ||
|
|
||
|
|
||
| def CVode(*args: Any, **kwargs: Any) -> Any: |
There was a problem hiding this comment.
Nonblocking: these four public symbols (CVode, IDA, Explicit_Problem, Implicit_Problem) are named like the Assimulo classes but are factory functions returning instances. That is deliberate and correct for the current call sites (all constructor calls), but a future contributor porting Assimulo code could write isinstance(solver, CVode) or subclass Explicit_Problem and it would fail silently/oddly since these are functions, not the classes. Consider a one-line module note (or comment on each) stating they are lazy constructors, not the classes, so nobody reaches for isinstance/subclassing. Not a blocker.
There was a problem hiding this comment.
Addressed in ccd161c. The module docstring now states that these exports are
factory functions rather than Assimulo class aliases and warns that they are
unsuitable for isinstance checks or subclassing.
|
Addressed all current review feedback in
No comments were declined. GitHub currently reports an empty review decision |
Summary
the backend boundary
Assimulo-free installation
fake
sys.modulesAssimulo package treeRadau5DAEimport and the commented-onlyLSODARimportThanks to Yirang for flagging the repeated test monkeypatching that exposed this
optional-dependency boundary.
Behavioral and scientific effect
Solver-free constructors, balances, shortcut calculations, and steady-state
helpers can now be imported without Assimulo. Calling a solver-backed path
without Assimulo raises an actionable installation error when the problem or
solver is constructed.
The real Assimulo objects still receive the existing arguments unchanged. No
model equations, state ordering, units, physical bases, numerical options, or
public model signatures changed.
Verification
origin/master:python -m pytest -q tests/test_optional_assimulo_imports.pyfailed atPharmaPy/Containers.pyimportingassimulo.solvers.python -m pytest tests/ -m "not assimulo"— 66 passed, 6 deselected.python -m pytest tests/ -v -m assimuloin the localpharmapy-assimulo-condaconda environment — 6 passed, 66 deselected.66 core tests and 6 Assimulo-marked integration tests.
ruff check PharmaPy/_assimulo.py tests/test_optional_assimulo_imports.pypassed.
Warnings reported by pytest are existing invalid-escape,
numpy.matlibdeprecation, and synthetic-fixture runtime warnings; no new warning category was
introduced.
Branch Hygiene
masterat18442e9.fix/issue-134-lazy-assimulo-imports.upstream/mastertip.test-helper files removed or simplified here. Whichever PR lands second will
need to preserve Fix distillation shortcut reflux calculations #121's shortcut-design changes while retaining the lazy
import boundary.
Drying_Model.pyandtests/test_drying_energy_rate_basis.py, but its current hunks arenon-adjacent to this change.
Closes #134.
Refs #10.