Skip to content
This repository was archived by the owner on Jun 10, 2020. It is now read-only.

Ensure stubs are valid for Python 2 and fix running of tests #19

Merged
merged 2 commits into from
May 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions numpy-stubs/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
import builtins
import sys

from numpy.core._internal import _ctypes
from typing import (
Any, Dict, Iterable, List, Optional, Mapping, Sequence, Sized,
SupportsInt, SupportsFloat, SupportsComplex, SupportsBytes, SupportsAbs,
Text, Tuple, Type, TypeVar, Union,
Any,
Container,
Dict,
Iterable,
List,
Mapping,
Optional,
Sequence,
Sized,
SupportsAbs,
SupportsComplex,
SupportsFloat,
SupportsInt,
Text,
Tuple,
Type,
TypeVar,
Union,
)

import sys

from numpy.core._internal import _ctypes
if sys.version_info[0] < 3:
class SupportsBytes: ...
else:
from typing import SupportsBytes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to see this change be in a separate commit to the reformatting (but I do agree with the reformatting in general)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I am OK with minor reformatting inline with other changes. There are relatively few cases where I've wanted to look at changes with more granularity than that provided by pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to pull this out to a separate commit if you'd like :) In my day job we only do squash-and-merge, so I'm not used to thinking about having granular commits within a PR haha.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been doing squash-and-merge for most of the PRs to numpy-stubs, so I think it's fine to keep this as is.


_Shape = Tuple[int, ...]

Expand Down Expand Up @@ -325,7 +343,7 @@ class _ArrayOrScalarCommon(SupportsInt, SupportsFloat, SupportsComplex,
def __getattr__(self, name) -> Any: ...


class ndarray(_ArrayOrScalarCommon, Iterable, Sized):
class ndarray(_ArrayOrScalarCommon, Iterable, Sized, Container):
real: ndarray
imag: ndarray

Expand Down
28 changes: 26 additions & 2 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,22 @@ reveal_type(x) # E: <type name>
Right now, the error messages and types are must be **contained within
corresponding mypy message**.

Test files that end in `_py3.py` will only be type checked against Python 3.
All other test files must be valid in both Python 2 and Python 3.

## Running the tests

To setup your test environment, cd into the root of the repo and run:


```
pip install -r test-requirements.txt
pip install .
```

Note that due to how mypy reads type information in PEP 561 packages, you'll
need to re-run the `pip install .` command each time you change the stubs.

We use `py.test` to orchestrate our tests. You can just run:

```
Expand All @@ -34,6 +48,16 @@ to run the entire test suite. To run `mypy` on a specific file (which
can be useful for debugging), you can also run:

```
$ cd tests
$ MYPYPATH=.. mypy <file_path>
mypy <file_path>
```

Note that it is assumed that all of these commands target the same
underlying Python interpreter. To ensure you're using the intended version of
Python you can use `python -m` versions of these commands instead:

```
python -m pip install -r test-requirements.txt
python -m pip install .
python -m pytest
python -m mypy <file_path>
```
10 changes: 5 additions & 5 deletions tests/pass/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
import operator

import numpy as np
from typing import Iterable
from typing import Iterable # noqa: F401

# Basic checks
array = np.array([1, 2])
def ndarray_func(x: np.ndarray) -> np.ndarray:
def ndarray_func(x):
# type: (np.ndarray) -> np.ndarray
return x
ndarray_func(np.array([1, 2]))
array == 1
Expand All @@ -28,7 +29,8 @@ def ndarray_func(x: np.ndarray) -> np.ndarray:
np.dtype((np.int32, (np.int8, 4)))

# Iteration and indexing
def iterable_func(x: Iterable) -> Iterable:
def iterable_func(x):
# type: (Iterable) -> Iterable
return x
iterable_func(array)
[element for element in array]
Expand Down Expand Up @@ -122,8 +124,6 @@ def iterable_func(x: Iterable) -> Iterable:
1 | array
array |= 1

array @ array

# unary arithmetic
-array
+array
Expand Down
6 changes: 6 additions & 0 deletions tests/pass/simple_py3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import numpy as np

array = np.array([1, 2])

# The @ operator is not in python 2
array @ array
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some people on 3.4 may want to run the test suite, so perhaps this shouldn't be used? Mypy defaults to using the python version of the executable used to run it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think enough people are still running Python 3.4 that we need to worry about this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could try to come up with a more generalized way of specifying what versions of python a given file can run on, or extend the mechanism so we have something like _py<min_version>.py files, where min_version could be 3, but could also be something like 3.5. If you think that's worth it I'm happy to do that :)

55 changes: 33 additions & 22 deletions tests/test_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,48 @@
import pytest
from mypy import api

ROOT_DIR = os.path.dirname(os.path.dirname(__file__))
PASS_DIR = os.path.join(os.path.dirname(__file__), "pass")
FAIL_DIR = os.path.join(os.path.dirname(__file__), "fail")
REVEAL_DIR = os.path.join(os.path.dirname(__file__), "reveal")

os.environ['MYPYPATH'] = ROOT_DIR
TESTS_DIR = os.path.dirname(__file__)
PASS_DIR = os.path.join(TESTS_DIR, "pass")
FAIL_DIR = os.path.join(TESTS_DIR, "fail")
REVEAL_DIR = os.path.join(TESTS_DIR, "reveal")


def get_test_cases(directory):
for root, __, files in os.walk(directory):
for fname in files:
if os.path.splitext(fname)[-1] == ".py":
# yield relative path for nice py.test name
yield os.path.relpath(
os.path.join(root, fname), start=directory)


@pytest.mark.parametrize("path", get_test_cases(PASS_DIR))
def test_success(path):
stdout, stderr, exitcode = api.run([os.path.join(PASS_DIR, path)])
fullpath = os.path.join(root, fname)
# Use relative path for nice py.test name
relpath = os.path.relpath(fullpath, start=directory)
skip_py2 = fname.endswith("_py3.py")

for py_version_number in (2, 3):
if py_version_number == 2 and skip_py2:
continue
py2_arg = ['--py2'] if py_version_number == 2 else []

yield pytest.param(
fullpath,
py2_arg,
# Manually specify a name for the test
id="{} - python{}".format(relpath, py_version_number),
)


@pytest.mark.parametrize("path,py2_arg", get_test_cases(PASS_DIR))
def test_success(path, py2_arg):
stdout, stderr, exitcode = api.run([path] + py2_arg)
assert stdout == ''
assert exitcode == 0


@pytest.mark.parametrize("path", get_test_cases(FAIL_DIR))
def test_fail(path):
stdout, stderr, exitcode = api.run([os.path.join(FAIL_DIR, path)])
@pytest.mark.parametrize("path,py2_arg", get_test_cases(FAIL_DIR))
def test_fail(path, py2_arg):
stdout, stderr, exitcode = api.run([path] + py2_arg)

assert exitcode != 0

with open(os.path.join(FAIL_DIR, path)) as fin:
with open(path) as fin:
lines = fin.readlines()

errors = {}
Expand All @@ -59,11 +70,11 @@ def test_fail(path):
pytest.fail(f'Error {repr(errors[lineno])} not found')


@pytest.mark.parametrize("path", get_test_cases(REVEAL_DIR))
def test_reveal(path):
stdout, stderr, exitcode = api.run([os.path.join(REVEAL_DIR, path)])
@pytest.mark.parametrize("path,py2_arg", get_test_cases(REVEAL_DIR))
def test_reveal(path, py2_arg):
stdout, stderr, exitcode = api.run([path] + py2_arg)

with open(os.path.join(REVEAL_DIR, path)) as fin:
with open(path) as fin:
lines = fin.readlines()

for error_line in stdout.split("\n"):
Expand Down