Skip to content

Make optional Assimulo imports lazy#135

Open
bernalde wants to merge 2 commits into
masterfrom
fix/issue-134-lazy-assimulo-imports
Open

Make optional Assimulo imports lazy#135
bernalde wants to merge 2 commits into
masterfrom
fix/issue-134-lazy-assimulo-imports

Conversation

@bernalde

Copy link
Copy Markdown

Summary

  • load Assimulo problem and solver constructors only when a solve path reaches
    the backend boundary
  • keep all nine solver-backed model modules importable in the default,
    Assimulo-free installation
  • move eight solver-free test cases into the core lane and remove the temporary
    fake sys.modules Assimulo package tree
  • remove the unused Radau5DAE import and the commented-only LSODAR import

Thanks 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

  • Red regression on unchanged origin/master:
    python -m pytest -q tests/test_optional_assimulo_imports.py failed at
    PharmaPy/Containers.py importing assimulo.solvers.
  • Minimal environment, Python 3.13.5 without Assimulo:
    python -m pytest tests/ -m "not assimulo" — 66 passed, 6 deselected.
  • Assimulo environment, Python 3.11.15 with Assimulo 3.4.3:
    python -m pytest tests/ -v -m assimulo in the local
    pharmapy-assimulo-conda conda environment — 6 passed, 66 deselected.
  • Collection boundaries:
    66 core tests and 6 Assimulo-marked integration tests.
  • ruff check PharmaPy/_assimulo.py tests/test_optional_assimulo_imports.py
    passed.
  • Black checks for both new files passed.
  • CRLF-aware staged whitespace check passed.

Warnings reported by pytest are existing invalid-escape, numpy.matlib
deprecation, and synthetic-fixture runtime warnings; no new warning category was
introduced.

Branch Hygiene

Closes #134.
Refs #10.

@bernalde
bernalde marked this pull request as ready for review July 23, 2026 18:56

@bernalde bernalde left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.
  • TerminateSimulation is behavior-preserving. Evaporators/SolidLiquidSep now import it from PharmaPy.Commons instead of assimulo.exception. Commons.py (unchanged) already defines it as try: 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_Problem across the 9 modules is a constructor call (Explicit_Problem(...), CVode(problem)); no isinstance/subclassing/class-attribute use exists (grep-confirmed), so replacing the classes with factory functions forwarding *args/**kwargs preserves behavior. Instance methods/attrs (solver.make_consistent(...), .simulate()) run on the real returned Assimulo object.
  • Removed imports are genuinely dead. LSODAR appears only in a commented-out line (Reactors.py:1144); Radau5DAE has no remaining references.
  • No stragglers. The only from assimulo left in PharmaPy/*.py is the Commons fallback. MetaModeler still writes from 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 base master's PharmaPy/ into an isolated worktree, test_model_modules_import_without_assimulo fails at Containers.py from assimulo.solvers import CVode — matching the PR body's red-on-base claim. The subprocess builtins.__import__ blocker is a robust replacement for the old fake-sys.modules tree.

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.

Comment thread PharmaPy/_assimulo.py
return constructor(*args, **kwargs)


def CVode(*args: Any, **kwargs: Any) -> Any:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

@bernalde

Copy link
Copy Markdown
Author

Addressed all current review feedback in ccd161c.

  • Clarified in PharmaPy/_assimulo.py that CVode, IDA,
    Explicit_Problem, and Implicit_Problem are lazy factory functions rather
    than Assimulo class aliases, and must not be used for isinstance checks or
    subclassing. This covers the shim-symbols-are-factories finding.
  • Focused import-boundary test: 2 passed.
  • Core lane: 66 passed, 6 deselected.
  • Real-Assimulo lane: 6 passed, 66 deselected.
  • Ruff, Black, and whitespace checks passed.
  • Current-head CI: Core tests and Assimulo integration tests passed.

No comments were declined. GitHub currently reports an empty review decision
and mergeStateStatus=CLEAN; the PR author cannot provide a formal
self-approval. The existing overlap risk with PRs #121 and #124 remains as
documented in the PR body.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow solver-free model imports without Assimulo

1 participant