-
-
Notifications
You must be signed in to change notification settings - Fork 7
disallowed Series | AnyArrayLike as Series is already array-like #28
disallowed Series | AnyArrayLike as Series is already array-like #28
Conversation
Thanks @Pydare for doing this! Looks like CI is red (insufficient coverage), could you fix it up please? |
any idea on what I could do? |
check that all the branches in the code you've added are covered by the tests - e.g. put breakpoints in them and check that the tests reach them |
I've been able to see what branches are not well covered, am I to write tests for each of these individual branches? Kind of unsure of what to do from here |
Hmm looks like for the file you've added, it's already at 100% - the issue's probably somewhere else then, I'll take a closer look tomorrow, it's probably unrelated to this PR Well done anyway, this generally looks really good! |
Looks like coverage for pandas_dev_flaker/_ast_helpers.py is less than 100% - I'd suggest, check that all the code you've added in that file is covered by at least one test |
$ coverage report --show-missing
Name Stmts Miss Branch BrPart Cover Missing
-------------------------------------------------------------------------------
pandas_dev_flaker/_ast_helpers.py 34 4 22 4 86% 63, 69, 74, 76
-------------------------------------------------------------------------------
TOTAL 758 4 220 4 99%
58 files skipped due to complete coverage.
Coverage failure: total of 99 is less than fail-under=100 |
I suspected this was the problem. I tried looking for the file that contained tests for _ast_helpers.py but couldn't find any. |
There's no file that directly tests So, you need to make sure that the test cases in pandas_dev_flaker/_plugins_tree/disallow_argument_types.py are comprehensive enough that they cover all the code you've added to |
Looks like there's still a few missing branches:
To see this locally, you can run
|
If there's a branch which you think can't be covered, then you can raise an |
Codecov Report
@@ Coverage Diff @@
## main #28 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 53 55 +2
Lines 711 776 +65
Branches 89 104 +15
=========================================
+ Hits 711 776 +65
Continue to review full report at Codecov.
|
pandas_dev_flaker/_ast_helpers.py
Outdated
if node.left.id == any_array_like: # pragma: no cover | ||
is_array_like = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why can't this branch be covered?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on line 62, if the if
condition is true, we're to break out of the while loop. but before breaking out we're also making checks for series and any-array-like annotations, these 2 inner conditions were affecting the test coverage.
the coverage warning: line 65 didn't jump to line 67 because the condition for line 65 was never false
i wrote tests that covered for when line 65 was false. i'm sincerely not really sure why it fails.
both conditions have to be checked because either of them could be the first annotation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, thanks for explaining - I'll take a look later then
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't a test case like
def dot(foo, other: Series | AnyArrayLike): pass
get here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't satisfy the coverage warning.
pandas_dev_flaker/_ast_helpers.py
Outdated
or ( | ||
sys.version_info >= (3, 8) | ||
and isinstance(node.func.value, ast.Constant) | ||
and isinstance(node.func.value.value, str) | ||
) | ||
) | ||
|
||
|
||
def parse_annotation(node: ast.BinOp) -> bool: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this function only used in pandas_dev_flaker/_plugins_tree/disallow_argument_types.py
? If so, it can go in that file, this one is meant for shared functions
pandas_dev_flaker/_ast_helpers.py
Outdated
if node.left.id == any_array_like: # pragma: no cover | ||
is_array_like = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't a test case like
def dot(foo, other: Series | AnyArrayLike): pass
get here?
pandas_dev_flaker/_ast_helpers.py
Outdated
else: | ||
raise AssertionError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't a test case like
pytest.param(
"def p(foo, other: Series | Union[int, str] ): pass",
id="Function argument with two annotations",
),
hit this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this test worked. i was expecting the test i wrote on line 31 in disallow_argument_types.py
to have covered such a case
else: | ||
raise AssertionError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try running this on pandas
, this'll be reached
$ flake8 ../pandas-marco/pandas --select PDF026 -j 1
Traceback (most recent call last):
File "/home/marco/pandas-style-guide/venv/bin/flake8", line 8, in <module>
sys.exit(main())
File "/home/marco/pandas-style-guide/venv/lib/python3.8/site-packages/flake8/main/cli.py", line 22, in main
app.run(argv)
File "/home/marco/pandas-style-guide/venv/lib/python3.8/site-packages/flake8/main/application.py", line 363, in run
self._run(argv)
File "/home/marco/pandas-style-guide/venv/lib/python3.8/site-packages/flake8/main/application.py", line 351, in _run
self.run_checks()
File "/home/marco/pandas-style-guide/venv/lib/python3.8/site-packages/flake8/main/application.py", line 264, in run_checks
self.file_checker_manager.run()
File "/home/marco/pandas-style-guide/venv/lib/python3.8/site-packages/flake8/checker.py", line 323, in run
self.run_serial()
File "/home/marco/pandas-style-guide/venv/lib/python3.8/site-packages/flake8/checker.py", line 307, in run_serial
checker.run_checks()
File "/home/marco/pandas-style-guide/venv/lib/python3.8/site-packages/flake8/checker.py", line 589, in run_checks
self.run_ast_checks()
File "/home/marco/pandas-style-guide/venv/lib/python3.8/site-packages/flake8/checker.py", line 494, in run_ast_checks
for (line_number, offset, text, _) in runner:
File "/home/marco/pandas-style-guide/pandas_dev_flaker/__main__.py", line 22, in run
for line, col, msg in callbacks_tree:
File "/home/marco/pandas-style-guide/pandas_dev_flaker/_data_tree.py", line 74, in visit_tree
yield from ast_func(state, node, parent)
File "/home/marco/pandas-style-guide/pandas_dev_flaker/_plugins_tree/disallow_argument_types.py", line 17, in visit_arg
if isinstance(arg.annotation, ast.BinOp) and parse_annotation(
File "/home/marco/pandas-style-guide/pandas_dev_flaker/_plugins_tree/disallow_argument_types.py", line 45, in parse_annotation
raise AssertionError
AssertionError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the example that breaks this:
def maybe_iterable_to_list(obj: Iterable[T] | T) -> Collection[T] | T:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wow, wow, wow. i didn't think of this edge case. i'd try to fix it
elif isinstance(node.left, ast.Name) or isinstance( | ||
node.left, | ||
ast.Subscript, | ||
): | ||
break | ||
else: | ||
raise AssertionError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is too specific, and can break with
def __array__(self, dtype: npt.DTypeLike | None = None) -> np.ndarray:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this could work? there are no coverage warnings also
if not isinstance(node.left, ast.BinOp): break else: node = node.left
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might be fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well done 💪 ! I think we've got the logic in parse_annotation
down
Last thing (I promise 😄 ) to address is the return type, e.g.
def foo(arg) -> Series | AnyArrayLike
currently isn't caught
There's a couple of possible solutions - either change visit_arg
to also check the return type, or by registering two functions, one for ast.Attribute
and the other for ast.Name
, see
for an example
The second solution would be better, because it would also catch annotations outside of function definitions
is_series, is_array_like = False, False | ||
|
||
while True: | ||
if isinstance(node.right, ast.Name): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we probably also want a branch like:
if isinstance(node.right, ast.Str):
if node.right.value == "Series"
to catch
arg: AnyArrayLike | "Series"
(and viceversa)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i wrote some tests with the already existing logic for cases of "Series"
and it handles it. i don't think we need extra logic to cover that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, comment on naming
anyway, you've done a lot here, if you want me to finish it let me know
yield arg.lineno, arg.col_offset, MSG | ||
|
||
|
||
def parse_annotation(node: ast.BinOp) -> bool: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, this probably needs a better name, e.g. constains_series_and_arraylike
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you think i should change the function name from visit_args
to visit_FunctionDef
? seems more appropriate and consistent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah sounds good
I'm kinda unsure why we should register From my understanding according to docs, Just trying to understand how I can link them to catch the annotations outside functions. The only edge case I can think about for now are variable definitions ( |
I think a single file is fine, as until now I've been keeping it to 1 file for each check |
In general, my way of checking what to use is to make some test file, like var: int
def foo(bar: str) -> None: ... then running
and checking from the output what the objects are |
Nice! I'll take a closer a look at the weekend, but this might be it! |
18d81ef
to
e4572dc
Compare
e4572dc
to
185fd23
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good to me too. we can merge :) |
Issue #42359 in the pandas-dev repo