Skip to content

Commit

Permalink
update: add binary operators in type hints (new in 3.10) (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
krneta authored Oct 18, 2023
1 parent 0df6136 commit 70b7622
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 6 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@
"Natural Language :: English",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
],
)
19 changes: 14 additions & 5 deletions src/braket/flake8_plugins/braket_checkstyle_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ def _check_arguments(self, name: str, args: ast.arguments) -> None:
if argument.annotation is None:
if argument.arg not in self.RESERVED_ARGS:
self.add_problem(node=argument, code="BCS001", arguments=argument.arg)
for argument in args.kwonlyargs:
if argument.annotation is None:
self.add_problem(node=argument, code="BCS001", arguments=argument.arg)

def _check_return(self, node: ast.FunctionDef) -> None:
if not node.returns and not node.name.startswith("__") and node.name != "_":
Expand Down Expand Up @@ -302,10 +305,10 @@ def _annotation_to_doc_str(self, annotation) -> str:
if isinstance(annotation.slice, ast.Index):
slice_name = self._annotation_to_doc_str(annotation.slice.value)
else:
# This is done to be backward compatible. May not be able to hit it with
# usual test coverage.
# This is done to be backward compatible.
slice_name = self._annotation_to_doc_str(annotation.slice)
return annotation.value.id + f"[{slice_name}]"
raise NotImplementedError("Currently not handling annotation values that are not Names")
elif isinstance(annotation, (ast.List, ast.Tuple)):
values = []
for elt in annotation.elts:
Expand All @@ -319,6 +322,12 @@ def _annotation_to_doc_str(self, annotation) -> str:
return result
elif isinstance(annotation, ast.Ellipsis):
return "..."
elif isinstance(annotation, ast.BinOp):
if isinstance(annotation.op, ast.BitOr):
return f"{self._annotation_to_doc_str(annotation.left)}|{self._annotation_to_doc_str(annotation.right)}"
if isinstance(annotation.op, ast.BitAnd):
return f"{self._annotation_to_doc_str(annotation.left)}&{self._annotation_to_doc_str(annotation.right)}"
raise NotImplementedError("Currently not handling annotation XOR binary operations")
return ""

def _check_return_info(
Expand Down Expand Up @@ -361,6 +370,8 @@ def _verify_args(self, context: DocContext, node: ast.FunctionDef) -> None:
not self._function_has_arguments_to_document(node)
and node.args.kwarg is None
and node.args.vararg is None
and (node.args.kwonlyargs is None or node.args.kwonlyargs == [])
and (node.args.posonlyargs is None or node.args.posonlyargs == [])
):
self.add_problem(node=node, code="BCS019", arguments=node.name)
return
Expand Down Expand Up @@ -448,9 +459,7 @@ def _get_argument_with_name(


def _function_requires_documentation(node: ast.FunctionDef) -> bool:
if node.name.startswith("_"):
return False
if node.body is None or len(node.body) == 0:
if node.name.startswith("_") or node.body is None or len(node.body) == 0:
return False
for body_node in node.body:
if not isinstance(body_node, (ast.Expr, ast.Return, ast.Raise)):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,17 @@ def function_0(arg0: str, *, arg1: int = 0, arg2: int = 1, arg3: int = 1) -> int
Returns:
int: value of the function.
"""
pass


def function_1(arg0: str, *, arg1 = 0, arg2: int = 1, arg3: int = 1) -> int:
"""This is a description.
Args:
arg0 (str): This is a parameter
arg1 (bool): This is missing a type hint
arg2 (int): This is another parameter
arg3 (int): This is yet another parameter
Returns:
int: value of the function.
"""
pass
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,27 @@ def function_15(*args, a0: int) -> int:
pass


def function_16(a0: int | list, a1: bool & float) -> int:
"""This is a description.
Args:
a0 (int | list): This is a parameter
a1 (bool & float): This is a parameter also
Returns:
int: value of the function
"""
pass


def function_17(*, a0: int) -> int:
"""This is a description.
Args:
a0 (int): This is a parameter
Returns:
int: value of the function
"""
pass


class MyClass:
def __init__(self, a0:int):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def test_no_error_functions() -> None:
(
"keyword_functions.py",
{
"13:29 BCS001 - Argument 'arg1' is missing a type hint.",
"1:0 BCS004 - Argument 'arg3' documentation is missing the type hint.",
"1:0 BCS005 - Argument 'arg2' type hint doesn't match documentation. expected: 'int', documented as: 'bool'.", # noqa
"1:0 BCS011 - Argument 'arg1' is missing type hint documentation.",
Expand Down
61 changes: 61 additions & 0 deletions test/unit_tests/braket/flake8_plugins/test_completion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import ast
from unittest.mock import Mock

import pytest

from braket.flake8_plugins.braket_checkstyle_plugin import (
_return_type_requires_documentation,
_Visitor,
)


def test_check_annotation_invalid_annotation_type():
visitor = _Visitor()
visitor._check_annotation("test", "test", 0, None, Mock())
assert visitor.problems == []


def test_annotation_to_doc_str_subscript_annotation_index():
annotation = Mock(spec=ast.Subscript)
annotation.value = Mock(spec=ast.Name)
annotation.value.id = "Annotation ID"
annotation.slice = Mock(spec=ast.Index)
annotation.slice.value = Mock(spec=ast.Name)
annotation.slice.value.id = "Slice ID"
visitor = _Visitor()
result = visitor._annotation_to_doc_str(annotation)
assert result == "Annotation ID[Slice ID]"


def test_annotation_to_doc_str_subscript_backward_compatibility():
annotation = Mock(spec=ast.Subscript)
annotation.value = Mock(spec=ast.Name)
annotation.value.id = "Annotation ID"
annotation.slice = Mock(spec=ast.Name)
annotation.slice.id = "Slice ID"
visitor = _Visitor()
result = visitor._annotation_to_doc_str(annotation)
assert result == "Annotation ID[Slice ID]"


def test_annotation_to_doc_str_subscript_unhandled():
annotation = Mock(spec=ast.Subscript)
annotation.value = Mock(spec=ast.Attribute)
visitor = _Visitor()
with pytest.raises(NotImplementedError):
visitor._annotation_to_doc_str(annotation)


def test_annotation_to_doc_str_binop_unhandled():
annotation = Mock(spec=ast.BinOp)
annotation.op = Mock(spec=ast.BitXor)
visitor = _Visitor()
with pytest.raises(NotImplementedError):
visitor._annotation_to_doc_str(annotation)


def test_return_type_requires_documentation():
return_type = Mock()
return_type.returns = None
result = _return_type_requires_documentation(return_type)
assert not result

0 comments on commit 70b7622

Please sign in to comment.