Compare commits

...
7 Commits
11 changed files with 117 additions and 22 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
run-tests: run-tests:
strategy: strategy:
matrix: matrix:
python-version: [ 3.7, 3.8, 3.9 ] python-version: [ 3.8, 3.9 ]
os: [ ubuntu-latest ] os: [ ubuntu-latest ]
fail-fast: false fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
+2 -2
View File
@@ -23,13 +23,13 @@ repos:
hooks: hooks:
- id: black - id: black
- repo: https://github.com/nbQA-dev/nbQA - repo: https://github.com/nbQA-dev/nbQA
rev: 1.2.3 rev: 1.6.3
hooks: hooks:
- id: nbqa-black - id: nbqa-black
- id: nbqa-isort - id: nbqa-isort
- id: nbqa-flake8 - id: nbqa-flake8
- repo: https://github.com/PyCQA/isort - repo: https://github.com/PyCQA/isort
rev: 5.10.1 rev: 5.12.0
hooks: hooks:
- id: isort - id: isort
- repo: https://github.com/pycqa/flake8 - repo: https://github.com/pycqa/flake8
+2 -3
View File
@@ -2,9 +2,10 @@
Type annotations **and runtime checking** for: Type annotations **and runtime checking** for:
1. shape and dtype of [JAX](https://github.com/google/jax) arrays; 1. shape and dtype of [JAX](https://github.com/google/jax) arrays; *(Now also supports PyTorch, NumPy, and TensorFlow!)*
2. [PyTrees](https://jax.readthedocs.io/en/latest/pytrees.html). 2. [PyTrees](https://jax.readthedocs.io/en/latest/pytrees.html).
**For example:** **For example:**
```python ```python
from jaxtyping import Array, Float, PyTree from jaxtyping import Array, Float, PyTree
@@ -28,8 +29,6 @@ def accepts_pytree_of_arrays(x: PyTree[Float[Array, "batch c1 c2"]]):
pip install jaxtyping pip install jaxtyping
``` ```
Requires JAX 0.3.4+.
Also install your favourite runtime type-checking package. The two most popular are [typeguard](https://github.com/agronholm/typeguard) (which exhaustively checks every argument) and [beartype](https://github.com/beartype/beartype) (which checks random pieces of arguments). Also install your favourite runtime type-checking package. The two most popular are [typeguard](https://github.com/agronholm/typeguard) (which exhaustively checks every argument) and [beartype](https://github.com/beartype/beartype) (which checks random pieces of arguments).
## Documentation ## Documentation
+19 -6
View File
@@ -79,14 +79,27 @@ from .import_hook import install_import_hook as install_import_hook
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
_T = typing.TypeVar("_T") # Set up to deliberately confuse a static type checker.
PyTree = getattr(typing, "foo" + "bar")
class PyTree(typing_extensions.Protocol[_T]): # What's going on with this madness?
pass #
# At static-type-checking-time, we want `PyTree` to be a type for which both
# `PyTree` and `PyTree[Foo]` are equivalent to `Any`.
# (The intention is that `PyTree` be a runtime-only type; there's no real way to
# do more with static type checkers.)
#
# Unfortunately, this isn't possible: `Any` isn't subscriptable. And there's no
# equivalent way we can fake this using typing annotations. (In some sense the
# closest thing would be a `Protocol[T]` with no methods, but that's actually the
# opposite of what we want: that ends up allowing nothing at all.)
#
# The good news for us is that static type checkers have an internal escape hatch.
# If they can't figure out what a type is, then they just give up and allow
# anything. (I believe this is sometimes called `Unknown`.) Thus, this odd-looking
# annotation, which static type checkers aren't smart enough to resolve.
elif has_jax: elif has_jax:
from .pytree_type import PyTree from .pytree_type import PyTree
del has_jax del has_jax
__version__ = "0.2.9" __version__ = "0.2.12"
+5 -3
View File
@@ -66,9 +66,11 @@ def _call_with_frames_removed(f, *args, **kwargs):
def _optimized_cache_from_source(path, debug_override=None): def _optimized_cache_from_source(path, debug_override=None):
# Version 2: change the position of the `@jaxtyped` decorator, so need a # Version 2: change the position of the `@jaxtyped` decorator, so need a
# different name to avoid hitting old __pycache__ # different name to avoid hitting old __pycache__.
# Version 3: now also annotating classes. # Version 3: now also annotating classes.
return cache_from_source(path, debug_override, optimization="jaxtyping3") # Version 4: I'm honestly not sure, but bumping this fixed some kind of odd error.
# Maybe I changed something with hte classes part way through version 3?
return cache_from_source(path, debug_override, optimization="jaxtyping4")
def _dot_lookup(*elements): def _dot_lookup(*elements):
@@ -111,7 +113,7 @@ class _JaxtypingTransformer(ast.NodeVisitor):
args = [ast.Constant(None)] args = [ast.Constant(None)]
else: else:
args = [_dot_lookup(*self._typechecker)] args = [_dot_lookup(*self._typechecker)]
node.decorator_list.append(ast.Call(func, args, keywords=[])) node.decorator_list.insert(0, ast.Call(func, args, keywords=[]))
self._parents.append(node) self._parents.append(node)
self.generic_visit(node) self.generic_visit(node)
self._parents.pop() self._parents.pop()
+9 -1
View File
@@ -59,6 +59,14 @@ class _MetaPyTree(type):
return out return out
try:
# new typeguard
_TypeCheckError = (TypeError, typeguard.TypeCheckError)
except AttributeError:
# old typeguard
_TypeCheckError = TypeError
class _MetaSubscriptPyTree(type): class _MetaSubscriptPyTree(type):
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
raise RuntimeError("PyTree cannot be instantiated") raise RuntimeError("PyTree cannot be instantiated")
@@ -77,7 +85,7 @@ class _MetaSubscriptPyTree(type):
def is_leaftype(x): def is_leaftype(x):
try: try:
accepts_leaftype(x) accepts_leaftype(x)
except TypeError: except _TypeCheckError:
return False return False
else: else:
return True return True
+19 -4
View File
@@ -18,16 +18,31 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import equinox as eqx import equinox as eqx
import typeguard
ParamError = []
ReturnError = []
ParamError.append(TypeError) # old typeguard
ReturnError.append(TypeError) # old typeguard
try:
# new typeguard
ParamError.append(typeguard.TypeCheckError)
ReturnError.append(typeguard.TypeCheckError)
except AttributeError:
pass
try: try:
import beartype import beartype
except ImportError: except ImportError:
ParamError = TypeError pass
ReturnError = TypeError
else: else:
ParamError = (TypeError, beartype.roar.BeartypeCallHintParamViolation) ParamError.append(beartype.roar.BeartypeCallHintParamViolation)
ReturnError = (TypeError, beartype.roar.BeartypeCallHintReturnViolation) ReturnError.append(beartype.roar.BeartypeCallHintReturnViolation)
ParamError = tuple(ParamError)
ReturnError = tuple(ReturnError)
@eqx.filter_jit @eqx.filter_jit
+15
View File
@@ -17,6 +17,8 @@
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import dataclasses
import equinox as eqx import equinox as eqx
import jax.numpy as jnp import jax.numpy as jnp
import pytest import pytest
@@ -45,3 +47,16 @@ with pytest.raises(ParamError):
M(1.0, jnp.array([1.0])) M(1.0, jnp.array([1.0]))
with pytest.raises(ParamError): with pytest.raises(ParamError):
M(1, jnp.array(1.0)) M(1, jnp.array(1.0))
@dataclasses.dataclass
class D:
foo: int
bar: Float32[jnp.ndarray, " a"]
D(1, jnp.array([1.0]))
with pytest.raises(ParamError):
D(1.0, jnp.array([1.0]))
with pytest.raises(ParamError):
D(1, jnp.array(1.0))
+15
View File
@@ -17,6 +17,8 @@
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import dataclasses
import equinox as eqx import equinox as eqx
import jax.numpy as jnp import jax.numpy as jnp
import pytest import pytest
@@ -45,3 +47,16 @@ with pytest.raises(ParamError):
M(1.0, jnp.array([1.0])) M(1.0, jnp.array([1.0]))
with pytest.raises(ParamError): with pytest.raises(ParamError):
M(1, jnp.array(1.0)) M(1, jnp.array(1.0))
@dataclasses.dataclass
class D:
foo: int
bar: Float32[jnp.ndarray, " a"]
D(1, jnp.array([1.0]))
with pytest.raises(ParamError):
D(1.0, jnp.array([1.0]))
with pytest.raises(ParamError):
D(1, jnp.array(1.0))
+15
View File
@@ -17,6 +17,8 @@
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import dataclasses
import equinox as eqx import equinox as eqx
import jax.numpy as jnp import jax.numpy as jnp
import pytest import pytest
@@ -45,3 +47,16 @@ with pytest.raises(ParamError):
M(1.0, jnp.array([1.0])) M(1.0, jnp.array([1.0]))
with pytest.raises(ParamError): with pytest.raises(ParamError):
M(1, jnp.array(1.0)) M(1, jnp.array(1.0))
@dataclasses.dataclass
class D:
foo: int
bar: Float32[jnp.ndarray, " a"]
D(1, jnp.array([1.0]))
with pytest.raises(ParamError):
D(1.0, jnp.array([1.0]))
with pytest.raises(ParamError):
D(1, jnp.array(1.0))
+15 -2
View File
@@ -1,13 +1,26 @@
import abc
from jaxtyping import jaxtyped from jaxtyping import jaxtyped
class M: class M(metaclass=abc.ABCMeta):
@jaxtyped @jaxtyped
@classmethod @classmethod
def f(cls): def f(cls):
return 3 return 3
@jaxtyped
@abc.abstractmethod
def g(self):
...
# Check that the @jaxtyped decorator doesn't blat the __get__ of @classmethod # Check that the @jaxtyped decorator doesn't blat the __get__ of @classmethod
def test_decorator(): def test_classmethod():
assert M.f() == 3 assert M.f() == 3
# Check that the @jaxtyped decorator doesn't blat the __isabstractmethod__ of
# @abstractmethod
def test_abstractmethod():
assert M.g.__isabstractmethod__