Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/barakkatzir/types-scipy-sparse
Type hints for scipy sparse module
https://github.com/barakkatzir/types-scipy-sparse
mypy-stubs python python3 scipy scipy-sparse type-annotation type-annotations type-package type-stubs
Last synced: about 1 month ago
JSON representation
Type hints for scipy sparse module
- Host: GitHub
- URL: https://github.com/barakkatzir/types-scipy-sparse
- Owner: BarakKatzir
- License: bsd-3-clause
- Created: 2024-08-04T14:31:53.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2024-08-12T11:00:02.000Z (3 months ago)
- Last Synced: 2024-10-14T01:20:55.267Z (about 1 month ago)
- Topics: mypy-stubs, python, python3, scipy, scipy-sparse, type-annotation, type-annotations, type-package, type-stubs
- Language: Python
- Homepage:
- Size: 181 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# Type annotations types-package for `scipy.sparse`
This is a [PEP-561](https://peps.python.org/pep-0561/) compilant type information package for the `sparse` module of the [`SciPy`](https://scipy.org/) package.
Installing this package will allow [`mypy`](https://mypy.readthedocs.io/en/latest/installed_packages.html) and possibly other static type checkers (e.g., pyright) to recognize type annotations for `scipy.sparse` classes and functions.> [!CAUTION]
> This package is a work in progress, and while it is tests the test coverage is still lacking (see this issue https://github.com/BarakKatzir/types-scipy-sparse/issues/6 ).
>
> Currently the `csgraph` and `linalg` submodules are not type annotated (see issue https://github.com/BarakKatzir/types-scipy-sparse/issues/5).
>
> This package only supports `"numpy <2.0.0"` for now (see issue https://github.com/BarakKatzir/types-scipy-sparse/issues/7).## Installation
In your python environment run
```bash
pip install types-scipy-sparse
```## Annotations
The basic annotations work fine. For example, if we have the file `important_func.py`
```python
# content of important_func.py
from scipy.sparse import coo_arraydef make_sparse(
x: list[int], y: list[int], vals: list[float]
) -> csr_array:
return coo_array((vals, (x, y))).tocsr()```
it will pass a mypy check
```console
$ mypy important_func.py
Success: no issues found in 1 source file
```### `numpy`-flavored generics
The different sparse array and sparse matrix classes are type annotated similarly to `numpy.ndarray`s, with two `TypeVar`s: `[ShapeType, DType]`, for example `csc_array[Any, numpy.float32]`. As for `numpy.ndarray`, the `DType` is bound by `numpy.dtype[Any]` and the `ShapeType` can be anything.
For example,
```python
# content of important_script.py
import numpy
from scipy.sparse import csr_array, lil_arrayx = csr_array([[0, 1], [2, 0]], dtype=numpy.float64)
y = lil_array(numpy.array([[0, 1], [2, 0]], dtype=numpy.float32))
reveal_locals()```
passes mypy with the revealed types of the `x` and `y` arrays
```console
$ mypy important_script.py
important_script.py:6: note: Revealed local types are:
important_script.py:6: note: x: scipy.sparse._csr.csr_array[Any, numpy.dtype[numpy.floating[numpy._typing._64Bit]]]
important_script.py:6: note: y: scipy.sparse._lil.lil_array[Any, numpy.dtype[numpy.floating[numpy._typing._32Bit]]]
Success: no issues found in 1 source file
```As in numpy, the first type variable, `ShapeType`, is left for user customization.
Note that for compatibility with any other type packages, The `ShapeType` and `DType` type variables default to `Any`, using the [PEP-696](https://peps.python.org/pep-0696/) feature. This means that type annotatin `x: dok_array` will implicitly insert these `Any`s:
```console
$ mypy -c "import scipy.sparse as sp; x: sp.dok_array; reveal_type(x)"
:1: note: Revealed type is "scipy.sparse._dok.dok_array[Any, numpy.dtype[Any]]"
Success: no issues found in 1 source file
```> [!NOTE]
> Since these generics are only introduced in the type stubs, they will raise an error at runtime. Thus, `x: coo_array[Any, numpy.dtype[numpy.uint8]]` will raise an error.
> If you desire to use this feature when annotating `.py` files then there are two easy solutions:
>
> * Use implicit forward references by adding `from __future__ import annotations` at the top of your script
>
> * Use explicit forward references by putting troublesome annotations in quotation marks, e.g., `x: "coo_array[Any, numpy.dtype[numpy.uint8]]"`### Type narrowing functions
There are several convenience type-narrowing functions in the `sparse` module, e.g., `issparse`, `isspmatrix` and `isdense`. These are annotated with the `TypeIs` introduced in [PEP-742](https://peps.python.org/pep-0742/). This conveniently narrows the type (and not casts to it, like `typing.TypeGuard`) and allows type narrowing in both `if` and `else` branches of a conditional.
For example the python file
```python
# content of my_script.py
from typing import Anyimport numpy
from scipy.sparse import csr_array, issparsex: numpy.ndarray[Any, Any] | csr_array
if issparse(x):
reveal_type(x)
# attribute of csr_array
x.indptr
elif isinstance(x, numpy.ndarray):
# attribute of ndarray
reveal_type(x)
x.strides
else:
# This branch is inferred to be unreachable
# so mypy ignores the following
reveal_type(x)
x.mystery_attribute```
will pass mypy with the correct revealed types
```console
$ mypy my_script.py
my_script.py:8: note: Revealed type is "scipy.sparse._csr.csr_array[Any, numpy.dtype[Any]]"
my_script.py:12: note: Revealed type is "numpy.ndarray[Any, Any]"
Success: no issues found in 1 source file
```### The `sparray` and `spmatrix` namespace classes
The classes `sparray` and `spmatrix` have empty bodies and are used for mainly for namespacing and `isinstance` checks, while the many sparse array/matrix class' methods are implemented via private mixin classes. While the private classes are not type annotated by this types package, the sparray and spmatrix are conveniantly type annotated as `Protocol[ShapeType, DType]`, this way it is conveyed to the type-checker that any methods they carry (such as `sum` and `asformat`) is not implemented, while still stating what method theri subclasses do implement.
### New `scipy.sparse.typing`
This type package introduces a new `scipy.sparse.typing` module for convenience. This new module is used internally for common type aliases and it exposes the `SparseArray` type alias which can specify a sparse array or sparse matrix by its scalar type similarly to `numpy`'s `numpy.typing.NDArray` (a scalar type is the subtype of the `numpy.generic`, e.g., `numpy.int8` and `numpy.complex128`),
```python
_SCT_co = TypeVar("_SCT_co", covariant=True, bound=numpy.generic)
SparseArray: TypeAlias = (
sparray[Any, numpy.dtype[_SCT_co]] | spmatrix[Any, numpy.dtype[_SCT_co]]
)
```For example, the following passes mypy
```python
from __future__ import annotationsfrom typing import TYPE_CHECKING
import numpyif TYPE_CHECKING:
from scipy.sparse.typing import SparseArraydef foo(x: SparseArray[numpy.float64]) -> None:
print(f"doing serious calculations with {x}")```
> [!NOTE]
> `SparseArray` cannot be used in runtime so it's best to use it in forward references (see above explanation for more)### What is and what isn't annotated?
I aim to annotate all the public object of `scipy.sparse`, see https://github.com/BarakKatzir/types-scipy-sparse/issues/5 for track of missing types. However, most of the private objects are left untyped (the private functions/classes/methods are those whose name begin with `'_'` and do not end with `'_'`, or that they reside in a private module and are not re-exported in a public module). There are a few exceptions to this that I keep track of here.
Currently, there are only two expections to the above rule: the function `_todata` and function `_ravel_coords` that seems useful and well docstringed.
Another thing to point out is that the whole *private* module `scipy.sparse._sparsetools` is not exposed in `sparse`'s `__init__.py`, but some of its functions are re-exposed by some deprecated modules such as `bsr.py`, `compressed.py`, `construct.py` and some others. As such, the `_sparsetools` module is typed in this package, but it would be prudent to not rely on these stubs.
## Development
### Development environment setup
The current development environment uses the experimental `uv` package.
Here are a few steps for anyone to set up the development environment quickly:
1) clone that package
2) [install uv](https://docs.astral.sh/uv/installation/) (consider fixing the version of `uv` to that of this repo)
In workspace root, create virtual environment
```bash
uv venv -p 3.10 --python-preference managed
```and activate the venv (e.g., run `source .venv/bin/activate` if in a linux terminal).
3) To install dependencies run
```bash
uv sync --frozen
```4) to make sure the type package is correctly installed, install in **non-editable** mode
```bash
uv pip install . --no-deps
```> [!NOTE]
> If you plan to change the version of `uv`, know that it is fixed separately in different places as the `uv` command is run separately in different environments: the dev venv, tox venvs, when building wheels, and in github workflows### Type stub generation
A big portion of the stub files are generated by code from common templates, since the scipy sparse module has a lot of similar classes with common functionality and common inheritance.
The repository includes the stub generating CLI tool named `make-scipy-sparse-stubs`. The tool can be installed as a package in editable mode (with the `uv sync` command or with `uv pip install -e make-scipy-sparse-stubs@tools/make-scipy-sparse-stubs`). It contains the python module `make_scipy_sparse_stubs` which can be run to either generate the type stubs or to check that the present / installed type stubs are in sync.To check that the stubs are in sync with the generated stubs run
```console
$ python -m make_scipy_sparse_stubs --check
Finished without any changes 🎉
```or to overwrite the current stubs by specifying their location in the workspace
```console
$ python -m make_scipy_sparse_stubs -sp src/scipy-stubs
Finished without any changes 🎉
```For more on the tool, see it's help:
```bash
python -m make_scipy_sparse_stubs --help
```> [!WARNING]
> The tool's functionality and interface can change between different revisions of the repository.### Tests
Pull requests to `main` are automatically run through tests, so you can develop locally and push to main and see if it passes. You can run the tests locally by running `tox` before pushing
```bash
uv run tox
```or if you activated the venv, then simply run `tox`.
The stubs are tested for:
* type stubs match the generated `make_scipy_sparse_stubs` tool
* `ruff` formatters and checkers
* `mypy` and mypy's `stubtest`
* `pytest` examines mypy failure / pass / reveal on example code (the template for these tests was adapted from the now archived `numpy-stubs` repo (see https://github.com/numpy/numpy-stubs)