Skip to content

Commit

Permalink
Release/2.1.3 (#82)
Browse files Browse the repository at this point in the history
* Added history.

* Added helper for writing test Python files.

* Changed the Invoke tasks to also add sources alongside the wheels

* package_info test should only run locally.

* Fixed incorrect error message.

* Fix for Pyright that didn't like Shape and Structure.

* Version bump.

* Added the sources to the wheel test.

* Writing some history.

Co-authored-by: Ramon <p8u7wAPC5Pg9HYkkCkzA>
  • Loading branch information
ramonhagenaars committed Jun 19, 2022
1 parent 14a6bba commit 0e37975
Show file tree
Hide file tree
Showing 14 changed files with 135 additions and 53 deletions.
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# History

## 2.1.3 (2022-06-19)

- Fixed typing issue with Pyright/Pylance that caused the message: "Literal" is not a class
- Fixed wrong error message when an invalid `Structure` was provided to `NDArray`.

## 2.1.2 (2022-06-08)

- Fixed bug that caused MyPy to fail with the message: Value of type variable "_DType_co" of "ndarray" cannot be "floating[Any]"
Expand Down
4 changes: 4 additions & 0 deletions constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ mypy-extensions==0.4.3
# via
# black
# mypy
nodeenv==1.6.0
# via pyright
numpy==1.22.4 ; python_version >= "3.8"
# via -r ./dependencies\requirements.txt
pathspec==0.9.0
Expand All @@ -68,6 +70,8 @@ pyflakes==2.4.0
# via autoflake
pylint==2.14.1
# via -r ./dependencies\qa-requirements.txt
pyright==1.1.254
# via -r ./dependencies\qa-requirements.txt
requests==2.27.1
# via codecov
sgmllib3k==1.0.0
Expand Down
9 changes: 5 additions & 4 deletions dependencies/qa-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
autoflake
beartype<0.10.0; python_version<'3.10'
beartype>=0.10.0; python_version>='3.10'
black
coverage
codecov>=2.1.0
feedparser
isort
mypy
pylint
pyright
setuptools
wheel
beartype<0.10.0; python_version<'3.10'
beartype>=0.10.0; python_version>='3.10'
typeguard
feedparser
wheel
2 changes: 1 addition & 1 deletion nptyping/ndarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def _get_dtype(cls, dtype_candidate: Any) -> DType:
raise InvalidArgumentsError(
f"Unexpected argument '{dtype_candidate}', expecting"
" Structure[<StructureExpression>]"
" or Literal[<ShapeExpression>]"
" or Literal[<StructureExpression>]"
" or a dtype"
" or typing.Any."
)
Expand Down
2 changes: 1 addition & 1 deletion nptyping/package_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
SOFTWARE.
"""
__title__ = "nptyping"
__version__ = "2.1.2"
__version__ = "2.1.3"
__author__ = "Ramon Hagenaars"
__author_email__ = "[email protected]"
__description__ = "Type hints for NumPy."
Expand Down
7 changes: 6 additions & 1 deletion nptyping/shape.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ try:
except ImportError:
from typing_extensions import Literal # type: ignore[attr-defined,misc]

from typing import cast
from typing import Any, cast

# For MyPy:
Shape = cast(Literal, Shape) # type: ignore[has-type,misc]

# For PyRight:
class Shape: # type: ignore[no-redef]
def __class_getitem__(cls, item: Any) -> Any: ...
9 changes: 8 additions & 1 deletion nptyping/structure.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ try:
except ImportError:
from typing_extensions import Literal # type: ignore[attr-defined,misc]

from typing import cast
from typing import Any, cast

import numpy as np

# For MyPy:
Structure = cast(Literal, Structure) # type: ignore[has-type,misc]

# For PyRight:
class Structure(np.dtype[Any]): # type: ignore[no-redef]
def __class_getitem__(cls, item: Any) -> Any: ...
1 change: 1 addition & 0 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ def init(context, py=None):
def wheel(context, py=None):
"""Build a wheel."""
print(f"Installing dependencies into: {_DEFAULT_VENV}")
context.run(f"{get_py(py)} setup.py sdist")
context.run(f"{get_py(py)} setup.py bdist_wheel")


Expand Down
Empty file added tests/test_helpers/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions tests/test_helpers/temp_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import contextlib
import os
from pathlib import Path
from tempfile import TemporaryDirectory
from textwrap import dedent


@contextlib.contextmanager
def temp_file(python_code: str, file_name: str = "test_file.py"):
file_content = dedent(python_code).strip() + os.linesep
with TemporaryDirectory() as directory_name:
path_to_file = Path(directory_name) / file_name
with open(path_to_file, "w") as file:
file.write(file_content)
yield path_to_file
67 changes: 31 additions & 36 deletions tests/test_mypy.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
import os
from pathlib import Path
from tempfile import TemporaryDirectory
from textwrap import dedent
from typing import Tuple
from unittest import TestCase

from mypy import api

from tests.test_helpers.temp_file import temp_file

def _check_mypy_on_code(python_code: str) -> str:
file_content = dedent(python_code).strip() + os.linesep
with TemporaryDirectory() as directory_name:
path_to_file = Path(directory_name) / "mypy_test.py"
with open(path_to_file, "w") as file:
file.write(file_content)
mypy_findings, _, _ = api.run([str(path_to_file)])
return mypy_findings

def _check_mypy_on_code(python_code: str) -> Tuple[int, str, str]:
with temp_file(python_code) as path_to_file:
stdout, stderr, exit_code = api.run([str(path_to_file)])
return exit_code, stdout, stderr


class MyPyTest(TestCase):
def test_mypy_accepts_ndarray_with_any(self):
mypy_findings = _check_mypy_on_code(
exit_code, stdout, stderr = _check_mypy_on_code(
"""
from typing import Any
from nptyping import NDArray
Expand All @@ -28,10 +23,10 @@ def test_mypy_accepts_ndarray_with_any(self):
NDArray[Any, Any]
"""
)
self.assertIn("Success", mypy_findings)
self.assertEqual(0, exit_code, stdout)

def test_mypy_accepts_ndarray_with_shape(self):
mypy_findings = _check_mypy_on_code(
exit_code, stdout, stderr = _check_mypy_on_code(
"""
from typing import Any
from nptyping import NDArray, Shape
Expand All @@ -41,10 +36,10 @@ def test_mypy_accepts_ndarray_with_shape(self):
"""
)

self.assertIn("Success", mypy_findings)
self.assertEqual(0, exit_code, stdout)

def test_mypy_accepts_ndarray_with_structure(self):
mypy_findings = _check_mypy_on_code(
exit_code, stdout, stderr = _check_mypy_on_code(
"""
from typing import Any
from nptyping import NDArray, RecArray, Structure
Expand All @@ -54,10 +49,10 @@ def test_mypy_accepts_ndarray_with_structure(self):
"""
)

self.assertIn("Success", mypy_findings)
self.assertEqual(0, exit_code, stdout)

def test_mypy_disapproves_ndarray_with_wrong_function_arguments(self):
mypy_findings = _check_mypy_on_code(
exit_code, stdout, stderr = _check_mypy_on_code(
"""
from typing import Any
import numpy as np
Expand All @@ -72,12 +67,12 @@ def func(_: NDArray[Shape["2, 2"], Any]) -> None:
"""
)

self.assertIn('Argument 1 to "func" has incompatible type "str"', mypy_findings)
self.assertIn('expected "ndarray[Any, Any]"', mypy_findings)
self.assertIn("Found 1 error in 1 file", mypy_findings)
self.assertIn('Argument 1 to "func" has incompatible type "str"', stdout)
self.assertIn('expected "ndarray[Any, Any]"', stdout)
self.assertIn("Found 1 error in 1 file", stdout)

def test_mypy_accepts_ndarrays_as_function_arguments(self):
mypy_findings = _check_mypy_on_code(
exit_code, stdout, stderr = _check_mypy_on_code(
"""
from typing import Any
import numpy as np
Expand All @@ -92,10 +87,10 @@ def func(_: NDArray[Shape["2, 2"], Any]) -> None:
"""
)

self.assertIn("Success", mypy_findings)
self.assertEqual(0, exit_code, stdout)

def test_mypy_accepts_ndarrays_as_variable_hints(self):
mypy_findings = _check_mypy_on_code(
exit_code, stdout, stderr = _check_mypy_on_code(
"""
from typing import Any
import numpy as np
Expand All @@ -106,10 +101,10 @@ def test_mypy_accepts_ndarrays_as_variable_hints(self):
"""
)

self.assertIn("Success", mypy_findings)
self.assertEqual(0, exit_code, stdout)

def test_mypy_accepts_recarray_with_structure(self):
mypy_findings = _check_mypy_on_code(
exit_code, stdout, stderr = _check_mypy_on_code(
"""
from typing import Any
from nptyping import RecArray, Structure
Expand All @@ -119,10 +114,10 @@ def test_mypy_accepts_recarray_with_structure(self):
"""
)

self.assertIn("Success", mypy_findings)
self.assertEqual(0, exit_code, stdout)

def test_mypy_accepts_numpy_types(self):
mypy_findings = _check_mypy_on_code(
exit_code, stdout, stderr = _check_mypy_on_code(
"""
from typing import Any
from nptyping import NDArray
Expand All @@ -136,10 +131,10 @@ def test_mypy_accepts_numpy_types(self):
"""
)

self.assertIn("Success", mypy_findings)
self.assertEqual(0, exit_code, stdout)

def test_mypy_wont_accept_numpy_types_without_dtype(self):
mypy_findings = _check_mypy_on_code(
exit_code, stdout, stderr = _check_mypy_on_code(
"""
from nptyping import NDArray
from typing import Any
Expand All @@ -152,13 +147,13 @@ def test_mypy_wont_accept_numpy_types_without_dtype(self):

self.assertIn(
'Value of type variable "_DType_co" of "ndarray" cannot be "signedinteger[Any]"',
mypy_findings,
stdout,
)

def test_mypy_knows_of_ndarray_methods(self):
# If MyPy knows of some arbitrary ndarray methods, we can assume that
# code completion works.
mypy_findings = _check_mypy_on_code(
exit_code, stdout, stderr = _check_mypy_on_code(
"""
from typing import Any
from nptyping import NDArray
Expand All @@ -173,10 +168,10 @@ def test_mypy_knows_of_ndarray_methods(self):
"""
)

self.assertIn("Success", mypy_findings)
self.assertEqual(0, exit_code, stdout)

def test_mypy_accepts_nptyping_types(self):
mypy_findings = _check_mypy_on_code(
exit_code, stdout, stderr = _check_mypy_on_code(
"""
from typing import Any
import numpy as np
Expand Down Expand Up @@ -314,4 +309,4 @@ def test_mypy_accepts_nptyping_types(self):
"""
)

self.assertIn("Success", mypy_findings)
self.assertEqual(0, exit_code, stdout)
3 changes: 3 additions & 0 deletions tests/test_package_info.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import os
from unittest import TestCase
from unittest.case import skipIf

import feedparser

from nptyping import __version__


class PackageInfoTest(TestCase):
@skipIf(os.environ.get("CI"), reason="Only run locally")
def test_version_bump(self):
releases = feedparser.parse(
"https://pypi.org/rss/project/nptyping/releases.xml"
Expand Down
48 changes: 48 additions & 0 deletions tests/test_pyright.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from functools import partial
from subprocess import PIPE, run
from typing import Tuple
from unittest import TestCase

import pyright

from tests.test_helpers.temp_file import temp_file


def _check_pyright_on_code(python_code: str) -> Tuple[int, str, str]:
pyright.node.subprocess.run = partial(run, stdout=PIPE, stderr=PIPE)
try:
with temp_file(python_code) as path_to_file:
result = pyright.run(str(path_to_file))
return (
result.returncode,
bytes.decode(result.stdout),
bytes.decode(result.stderr),
)
finally:
pyright.node.subprocess.run = run


class PyrightTest(TestCase):
def test_pyright_accepts_array_with_shape(self):
exit_code, stdout, sterr = _check_pyright_on_code(
"""
from typing import Any
from nptyping import NDArray, Shape
NDArray[Shape["*, ..."], Any]
"""
)
self.assertEqual(0, exit_code, stdout)

def test_pyright_accepts_array_with_structure(self):
exit_code, stdout, sterr = _check_pyright_on_code(
"""
from typing import Any
from nptyping import NDArray, Structure
NDArray[Any, Structure["x: Int, y: Float"]]
"""
)
self.assertEqual(0, exit_code, stdout)
16 changes: 7 additions & 9 deletions tests/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,8 @@


def determine_order(_: Any, x: str, __: str) -> int:
if x == "test_wheel_is_built_correctly":
return -1
if x == "test_wheel_can_be_installed":
return -1
return 1
prio_tests = ("test_wheel_is_built_correctly", "test_wheel_can_be_installed")
return -1 if x in prio_tests else 1


TestLoader.sortTestMethodsUsing = determine_order
Expand Down Expand Up @@ -83,11 +80,12 @@ def tearDownClass(cls) -> None:

def test_wheel_is_built_correctly(self):
with working_dir(_ROOT):
subprocess.check_output(
f"{sys.executable} setup.py bdist_wheel", shell=True
)
wheel_files = glob(f"dist/*{__version__}*")
subprocess.check_output(f"{sys.executable} -m invoke wheel", shell=True)
wheel_files = glob(f"dist/*{__version__}*.whl")
src_files = glob(f"dist/*{__version__}*.tar.gz")

self.assertEqual(1, len(wheel_files))
self.assertEqual(1, len(src_files))

with ZipFile(_ROOT / Path(wheel_files[0]), "r") as zip_:
files_in_wheel = set(
Expand Down

0 comments on commit 0e37975

Please sign in to comment.