Import ENVI lazily so a broken jax stack cannot take COVET down - #91
Import ENVI lazily so a broken jax stack cannot take COVET down#91Marius1311 wants to merge 1 commit into
Conversation
|
Pushed a revision that removes a workaround in favour of fixing its cause. The lazy import previously had to force the class back into Rather than rebinding around it, the module is now Also added while a genuine failure inside an installed dependency surfaces as itself.
from scenvi._deps import ENVI_MODULES, error_on_missing_dependencies
from scenvi.utils import compute_covet # noqa: F401
__all__ = ["ENVI", "compute_covet"]
def __getattr__(name):
"""Resolve ``ENVI`` on first access so its heavy dependencies stay optional."""
if name == "ENVI":
error_on_missing_dependencies(*ENVI_MODULES)
from scenvi._envi import ENVI
return ENVI
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")Testing after the change: 11 passed / 2 skipped with the |
9549000 to
c002726
Compare
|
Hi, thanks for the PR. Good idea to seperate this make COVET more accessible by itself. Since the main use case for this project is to process data with We can consider refactoring to a layout like below, where Thoughts? |
COVET is ENVI's first step and is pure numpy/sklearn/scanpy: sklearn.neighbors
for the spatial kNN, np.matmul for the shifted covariance, np.linalg.eigh for
the matrix square root. Nothing in it touches jax, flax, optax, clu or
tensorflow_probability.
Importing it nevertheless imported all of them, in two places:
* utils.py defined the ENVI CVAE's flax/clu components alongside the COVET
functions, so importing compute_covet imported flax and clu;
* __init__.py eagerly imported the ENVI module, which additionally needs
tensorflow_probability.
That coupling is why a break anywhere in the deep-learning stack takes COVET
with it, even though COVET uses none of it. It is not hypothetical: the
tensorflow_probability pin (^0.22.0, so <0.23) fails to import against current
jax with
AttributeError: module 'jax.interpreters.xla' has no attribute
'pytype_aval_mappings'
which today makes `import scenvi` impossible on a fresh install, COVET
included. dpeerlab#9 was the same failure mode with an older scipy.
This commit decouples the two. It is internal only -- pyproject.toml, README.md
and the CI workflow are untouched, so `pip install scenvi` installs exactly what
it installed before, and the COVET and ENVI code itself is unmodified:
* moves FeedForward, CVAE, Metrics and TrainState verbatim into scenvi/_nn.py
and reduces utils.py's imports to what COVET actually uses;
* renames scenvi/ENVI.py to scenvi/_envi.py. The module and the class it
exports shared a name, which is harmless while the import is eager but not
once it is lazy: importing the submodule binds `scenvi.ENVI` to the module,
shadowing the class on every later lookup. Renaming removes the collision
rather than working around it. All documented usage is `scenvi.ENVI(...)`,
the class, which is unchanged; docs/source/ENVI.rst is updated;
* resolves ENVI lazily in __init__.py via PEP 562 __getattr__;
* adds tests/test_covet.py, including a subprocess test that runs COVET end to
end and asserts none of jax, flax, optax, clu or tensorflow_probability
entered sys.modules, so a new deep-learning import on the compute_covet path
fails loudly instead of quietly restoring the coupling.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
c002726 to
df9a45d
Compare
|
That's your call to make, and I think you're right on the substance — so I've
I also dropped the The reason to keep the lazy import even with the deps required is that it On the splitI agree, and I don't think an extra is a substitute for it. A single I've written up what the split would take in #94, including the part I'd Two smaller things I ran into and should mention:
|
Motivation
COVET is ENVI's first step, and the README advertises using it on its own:
Its maths is pure numpy/sklearn/scanpy —
sklearn.neighborsfor the spatial kNN,np.matmulfor the shifted covariance,np.linalg.eighfor the matrix squareroot. It touches nothing in jax, flax, optax, clu or tensorflow_probability. But
importing it imported all of them, for two reasons:
utils.pydefined the ENVI CVAE's flax/clu components alongside the COVETfunctions, so importing
compute_covetimported flax and clu;__init__.pyeagerly importedENVI.py, which additionally needstensorflow_probability.
That coupling means a break anywhere in the deep-learning stack is also a break in
COVET. It is not hypothetical:
tensorflow_probabilityis pinned to^0.22.0andtfp 0.22 cannot be imported against current jax, so
import scenvifails outrighttoday — COVET included, though COVET uses none of it. #9 was the same shape with an
older scipy. (The import failure itself is fixed separately in #93; this PR is about
the coupling that turns it into a COVET failure.)
What this does
Internal only.
pyproject.toml,README.mdand.github/workflows/test.yamlarebyte-for-byte identical to
main, sopip install scenviinstalls exactly what itinstalled before, and the COVET and ENVI code itself is unmodified.
FeedForward,CVAE,MetricsandTrainStateverbatim fromutils.pyinto a new
scenvi/_nn.py, and reducesutils.py's imports to what COVETactually uses.
scenvi/ENVI.pytoscenvi/_envi.py. The module and the class it exportsshared a name, which is harmless while the import is eager but not once it is
lazy: importing the submodule binds
scenvi.ENVIto the module, shadowing theclass on every later lookup. Renaming removes the collision rather than working
around it. All documented usage is
scenvi.ENVI(...), the class, which isunchanged;
docs/source/ENVI.rstis updated.ENVIlazily in__init__.pyvia PEP 562__getattr__.tests/test_covet.py, including a subprocess test that runs COVET end to endand asserts that none of jax, flax, optax, clu or tensorflow_probability entered
sys.modules— so a new deep-learning import on thecompute_covetpath failsloudly instead of quietly restoring the coupling.
Testing
import jaxtoutils.pyandconfirming it fails with
the COVET path imported jax.compute_covetoutput is unchanged, checked bit-for-bit against an independentreimplementation's differential test suite.