feat: make numpy an optional dependency - #34
Conversation
Greptile SummaryThis PR makes numpy an optional dependency by introducing a
Confidence Score: 5/5Safe to merge. All numpy usages are either replaced with stdlib equivalents or guarded by the new lazy-import infrastructure, and CI still receives numpy via the updated The stdlib substitutions are semantically equivalent, the lazy-import machinery follows a well-established pattern (LazyLoader + stub), and No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["import dspy"] --> B["module-level: np = require('numpy')"]
B --> C{numpy installed?}
C -- Yes --> D["LazyLoader wraps real numpy\nadded to sys.modules"]
C -- No --> E["_MissingModule stub\n(stores call-site frame)"]
D --> F["First np.attr access\ntriggers real numpy import"]
E --> G["First np.attr access\nraises ImportError with\npip install dspy[numpy] hint"]
F --> H["Normal numpy operation"]
subgraph "stdlib replacements (no lazy import)"
I["math.log2 (was np.log2)"]
J["math.inf (was np.inf)"]
K["statistics.pstdev (was np.std)"]
L["sum/len (was np.average)"]
end
Reviews (14): Last reviewed commit: "docs(lazy_import): switch RST double bac..." | Re-trigger Greptile |
Move numpy from required dependencies to a new [numpy] optional extra. Add dspy/_numpy.require_numpy() helper so numpy-using features raise a clear ImportError pointing to 'pip install dspy[numpy]' when numpy is absent. Convert all module-level 'import numpy as np' statements to lazy imports inside the functions that actually need numpy, so 'import dspy' continues to work when the extra is not installed. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
CI installs individual extras (uv sync --dev --extra dev, then --extra test_extras) rather than --all-extras, so the new public [numpy] extra alone doesn't cover test collection. Several test files import numpy at module top, so numpy must be present in the extras CI actually installs. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
32c9363 to
787a37c
Compare
Replace dspy/utils/_numpy.require_numpy() with a generic dspy/utils/_optional.require_optional(module, extra=...) helper modeled on pandas.compat._optional.import_optional_dependency. dspy already exposes other capability-gated extras (anthropic, weaviate, mcp, langchain, optuna), so a generic helper avoids per-dep boilerplate as new optional integrations are added. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- mipro_optimizer_v2._set_num_trials_from_num_candidates: np.log2 -> math.log2.
- mipro_optimizer_v2._set_random_seeds: drop legacy np.random.seed call;
MIPROv2 itself doesn't read numpy's global RNG state.
- infer_rules.compile: -np.inf -> -math.inf.
Removes the require_optional("numpy") call from both files entirely;
neither code path needs numpy now.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
require_optional previously defaulted extra=module, so a caller passing
just require_optional("foo") would advertise `pip install dspy[foo]`
even if no such extra was declared in pyproject.toml. Make extra opt-in:
when omitted, the error message only suggests `pip install <module>`.
Update the existing numpy callers to pass extra="numpy" explicitly.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…xtra arg Modeled on transformers' BACKENDS_MAPPING. _DSPY_EXTRAS in dspy/utils/_optional.py maps each importable module name to its declared install extra; require_optional(module) consults it to add `pip install dspy[<extra>]` to the error message when an extra exists, or falls back to `pip install <module>` otherwise. Callers no longer write redundant extra="numpy". Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…pt/utils - Add dspy/utils/lazy_import.py with require(), optional(), is_available() and _INSTALL_HINTS registry (ported from dspy-core e8e3e19) - Replace all require_optional() call sites with require() - Remove dspy/utils/_optional.py - Replace numpy with sum()/len() in teleprompt/utils.py where stdlib suffices - Add tests for lazy_import helpers Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
|
In embedding.py and knn.py do you consider putting that at the bottom of the file instead: # mymodule.py
def __getattr__(name):
if name == 'np':
globals()['np'] = require("numpy")
return np
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")or that in the init of the class: # knn.py
import ...
np = None
class MyClass:
def __init__(self):
global np
if np is None:
np = require('numpy') |
|
|
||
| @functools.cache | ||
| def is_available(module: str) -> bool: | ||
| """Return True if ``module`` can be imported, without importing it. |
There was a problem hiding this comment.
single back-tick is preferred in our settings of mkdocs.
| ) from e | ||
|
|
||
|
|
||
| def optional(module: str, attr: str | None = None, default: Any = None) -> Any: |
There was a problem hiding this comment.
optional is not used, can we wait that it's needed before making this util?
…le stub
require() now returns a LazyLoader-wrapped module (installed) or a
_MissingModule stub (missing) that raises ImportError with install hint
on first attribute access. All numpy call sites use module-level
np = require('numpy'). Removes optional() helper.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
numpy is now an optional dependency. Added install hints to API docs for Embedder, Embeddings, KNN, KNNFewShot, SIMBA and to the RAG and tool_use tutorials. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…t to top of gepa.py Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
c1338ce to
330361d
Compare
- test_is_available_does_not_import_module: use a stdlib module dspy never imports (mailbox) instead of dspy.utils.lazy_import itself, and clean sys.modules via monkeypatch.delitem instead of mutating it directly. - test_install_hints_match_pyproject_extras: replace the ad-hoc regex TOML parser with tomllib (tomli fallback for 3.10), and use pytestconfig.rootpath to locate pyproject.toml. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…icks Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Summary
Move
numpyfrom a required runtime dependency to an optional extra.numpy-dependent features (KNN, Embeddings, SIMBA, MIPROv2, DummyVectorizer, Embedder post-processing, etc.) raise a clear
ImportErrorpointing atpip install dspy[numpy]only when actually invoked.Changes
New:
dspy/utils/lazy_import.pyCentralized lazy-import infrastructure (ported from dspy-core
e8e3e196):require(module, *, extra, feature)— import or raise a friendlyImportErrorwith install hintoptional(module, attr, default)— import-or-default for module-level sentinelsis_available(module)— cheapfind_spec-based check,lru_cached_INSTALL_HINTSregistry mapping import names to dspy extras (numpy, optuna, mcp, langchain, weaviate, anthropic)Replaced numpy with stdlib where possible
math.log2inmipro_optimizer_v2,gepamath.infininfer_rulesstatistics.pstdevincopro_optimizersum()/len()inteleprompt/utilsnp.random.seed()in mipro (optuna uses its own seed param)Lazy numpy via
require("numpy")Files that genuinely need numpy arrays (
knn.py,embeddings.py,embedding.py,dummies.py,simba.py) now callrequire("numpy")inside method bodies instead of top-levelimport numpy.pyproject.toml
dependenciesnumpy = ["numpy>=1.26.0"]optional extradevandtest_extrasso CI still gets itTests
tests/utils/test_lazy_import.py— 10 tests coveringrequire,optional,is_available, registry/fallback behavior, and pyproject extras consistency check (Python 3.10 compatible)Notes
import dspyworks without numpy installedpip install dspy[numpy]