Python library: optionally support numpy types without depending on numpy
Question:
Context
We develop a Python library that contains a function expecting a numlike parameter. We specify this in our signature and make use of python type hints:
def cool(value: float | int | List[float | int])
Problem & Goal
During runtime, we noticed it’s fine to pass in numpy number types as well, e.g. np.float16(1.2345)
. So we thought: why not incorporate "numpy number types" into our signature as this would be beneficial for the community that will use our library.
However, we don’t want numpy
as dependency in our project. We’d like to only signify in the method signature that we can take a float
, int
, a list of them OR any "numpy number type". If the user hasn’t installed numpy
on their system, they should still be able to use our library and just ignore that they could possibly pass in a "numpy number type" as well.
We don’t want to depend on numpy
as we don’t use it in our library (except for allowing their types in our method signature). So why include it in our dependency graph? There’s no reason to do so. One dependency less is better.
Additional requirements/context
- We search for an answer that is compatible with all Python versions
>=3.8
.
- (The answer should work with
setuptools>=69.0
.)
- The answer should be such that we get proper IntelliSense (
Ctrl + Space
) when typing cool(
in an IDE, e.g. VSCode.
- This is what our
pyproject.toml
looks like.
Efforts
- We’ve noticed the option
[project.optional-dependencies]
for the pyproject.toml
file, see here. However, it remains unclear how this optional dependencies declaration helps us in providing optional numpy
datatypes in our method signatures.
numpy
provides the numpy.typing
type annotations. Is it somehow possible to only depend on this subpackage?
- We did search on search engines and found this SO question, however our question is more specific with regards to how we can only use types from another module. We also found this SO question, but despite having "optional" in its title, it’s not about optional numpy types.
Answers:
Defer evaluation of annotations, and only import numpy conditionally.
from __future__ import annotations
import typing as t
if t.TYPE_CHECKING:
import numpy as np
def cool(value: int | np.floating | etc ...):
...
Now the numpy dependency is only necessary when type-checking.
See PEP 563 – Postponed Evaluation of Annotations
Side-questions..
numpy
provides the numpy.typing
type annotations. Is it somehow possible to only depend on this subpackage?
No, this is not possible.
We’ve noticed the option [project.optional-dependencies]
for the pyproject.toml
file …
It doesn’t really help you much here. It could still be useful if you wanted "extra" dependencies which the user can opt-in for, e.g.:
pip install mypkg # install with required dependencies
pip install mypkg[typing] # install with extra dependencies such as numpy
Then you could use this to easily install the package along with the soft-deps in your CI, for example.
Context
We develop a Python library that contains a function expecting a numlike parameter. We specify this in our signature and make use of python type hints:
def cool(value: float | int | List[float | int])
Problem & Goal
During runtime, we noticed it’s fine to pass in numpy number types as well, e.g. np.float16(1.2345)
. So we thought: why not incorporate "numpy number types" into our signature as this would be beneficial for the community that will use our library.
However, we don’t want numpy
as dependency in our project. We’d like to only signify in the method signature that we can take a float
, int
, a list of them OR any "numpy number type". If the user hasn’t installed numpy
on their system, they should still be able to use our library and just ignore that they could possibly pass in a "numpy number type" as well.
We don’t want to depend on numpy
as we don’t use it in our library (except for allowing their types in our method signature). So why include it in our dependency graph? There’s no reason to do so. One dependency less is better.
Additional requirements/context
- We search for an answer that is compatible with all Python versions
>=3.8
. - (The answer should work with
setuptools>=69.0
.) - The answer should be such that we get proper IntelliSense (
Ctrl + Space
) when typingcool(
in an IDE, e.g. VSCode. - This is what our
pyproject.toml
looks like.
Efforts
- We’ve noticed the option
[project.optional-dependencies]
for thepyproject.toml
file, see here. However, it remains unclear how this optional dependencies declaration helps us in providing optionalnumpy
datatypes in our method signatures. numpy
provides thenumpy.typing
type annotations. Is it somehow possible to only depend on this subpackage?- We did search on search engines and found this SO question, however our question is more specific with regards to how we can only use types from another module. We also found this SO question, but despite having "optional" in its title, it’s not about optional numpy types.
Defer evaluation of annotations, and only import numpy conditionally.
from __future__ import annotations
import typing as t
if t.TYPE_CHECKING:
import numpy as np
def cool(value: int | np.floating | etc ...):
...
Now the numpy dependency is only necessary when type-checking.
See PEP 563 – Postponed Evaluation of Annotations
Side-questions..
numpy
provides thenumpy.typing
type annotations. Is it somehow possible to only depend on this subpackage?
No, this is not possible.
We’ve noticed the option
[project.optional-dependencies]
for thepyproject.toml
file …
It doesn’t really help you much here. It could still be useful if you wanted "extra" dependencies which the user can opt-in for, e.g.:
pip install mypkg # install with required dependencies
pip install mypkg[typing] # install with extra dependencies such as numpy
Then you could use this to easily install the package along with the soft-deps in your CI, for example.