Skip to content

ENH: Add set of tests to type check the public APIs #45252

Open

Description

@Dr-Irv

From discussion on pandas typing meeting on January 7, 2022 (notes at https://docs.google.com/document/d/1tGbTiYORHiSPgVMXawiweGJlBw5dOkVJLY-licoBmBU/)

Follow up to discussion here: #28142 (comment)

Goal is to have a set of tests that make sure that users of the pandas API writing correct code work with whatever type stubs we publish.

Example of an approach that is suggested was developed within the pandas-stubs project based on various code snippets which are then tested with mypy : https://github.com/VirtusLab/pandas-stubs/tree/master/tests/snippets
@zkrolikowski-vl and @joannasendorek from that project have offered to help (nudge, nudge)

Ideally we would add this testing framework to the CI before starting to bring over the stubs.

Activity

  1. added
    Testingpandas testing functions or related to the test suite
    CIContinuous Integration
    Typingtype annotations, mypy/pyright type checking
    on Jan 7, 2022
  2. simonjayhawkins commented on Jan 8, 2022

    @simonjayhawkins
    Member

    Example of an approach that is suggested was developed within the pandas-stubs project based on various code snippets which are then tested with mypy : https://github.com/VirtusLab/pandas-stubs/tree/master/tests/snippets

    see #40202 for a less verbose but more primitive approach.

    Using pytest is perhaps more robust. How easy is is to parametrize testing of types? I assume that parametrization cannot be used for static checks.

    The method in #40202 has several issues. If anyone knows of other approaches to testing types, would much appreciate posting suggestions here.

  3. simonjayhawkins commented on Jan 8, 2022

    @simonjayhawkins
    Member

    from #40202 (comment)

    we must also remember that there is also the "do nothing" option and not explicitly test the types. IIRC this was suggested in the January dev meeting (Mostly relying on user feedback to report issues.)

    repeating here so that this option is not overlooked if the solution gets too complex.

  4. twoertwein commented on Jan 8, 2022

    @twoertwein
    Member

    One potentially easier to implement option is to have something like mypy_primer but for pandas. Typeshed uses mypy_primer to automatically comment on each PR whether it would change type annotations as inferred by mypy.

    "Pandas_primer" would help to 1) prevent involuntary changes to public type annotations and to 2) verify that intended changes actually have an effect.

  5. zkrolikowski-vl commented on Jan 10, 2022

    @zkrolikowski-vl
    Contributor

    In pandas-stubs, we've decided to go with the simplest method, which doesn't require any extra setup or testing constructs. Here are some of the other tools we've looked at (not including dynamic type-checkers):

    Coming back to our solution, there's a way to test negative scenarios in our approach but it's far from elegant:

        rdf: pd.Series = pd.concat({1: df, 2: df2})     # type: ignore
    

    This will cause mypy not to return errors when the line in-fact is incorrect,

    whereas using it incorrectly

        rdf: pd.DataFrame = pd.concat({1: df, 2: df2})     # type: ignore
    

    will result in an error

    tests/snippets/test_pandas.py:55: error: Unused "type: ignore" comment
    

    The issue that remains is that our whole approach is static and I don't see it being easily integrated with parametric list of use-cases.

    mypy_primer seems like an another interesting approach. Potentially difficult to integrated into pandas CI but might be worth the extra effort.

    Overall it seems like either pytest-mypy-testing/ @simonjayhawkins 's (with some work) or our (pandas-stubs) solution might be a semi-immediate fit for pandas use-case.

    @simonjayhawkins if I may ask: Why was your work discontinued? Was it just inconvenient at the time?

  6. Dr-Irv commented on Jan 10, 2022

    @Dr-Irv
    ContributorAuthor

    Here is why I like the approach taken by the pandas-stubs project: https://github.com/VirtusLab/pandas-stubs/tree/master/tests/snippets

    • We need a way to test the typing stubs we publish.
    • When they are published, it is like Goldilocks and the Three Bears. A particular stub could be too narrow, too wide, or just right.
    • The community will discover that certain stubs are too narrow or too wide.
    • As a stub is fixed, you can add tests for ones that were too narrow by adding test code that should pass a mypy test
    • As a stub is fixed, you can add tests for ones that were too wide by adding test code with #type: ignore comments and use the mypy warn_unused_ignores option to make sure that the ignore line is correct (and tested with future changes to the stubs)

    From my experience in submitting a bunch of PR's to the Microsoft python-type-stubs project, which has no testing for the pandas stubs, I can tell you that I am always concerned that a fix I submit for the stubs will break something else. If they had included something like what the pandas-stubs project did, it would make life a lot easier to do more test-driven development of the stubs. This is especially true when you make a change in pandas/_typing.py that changes one of the top-level types. It's hard to know if you have then made some downstream stub too narrow or too wide.

  7. joannasendorek commented on Jan 10, 2022

    @joannasendorek

    The approach we took in pandas-stubs was the most pragmatical for our usage and capacity we have to maintain the project. We can assume that any stubs will not be perfect and when people will first start using them, they will get some errors (I applied our stubs for one of the thousand lines project and made 10 fixes to pandas-stubs based on that only). So whenever this happens, we would first re-create the false negative case provided, modify the stubs and make sure the exact usage is corrected. From this moment, this particular usage is guarded so people using the project shouldn't notice regression and we can add only small improvements at once.

  8. simonjayhawkins commented on Jan 10, 2022

    @simonjayhawkins
    Member

    @simonjayhawkins if I may ask: Why was your work discontinued? Was it just inconvenient at the time?

    abandoned due to #40202 (comment). In pandas we tend not merge stuff if there are any objections.

    I assumed from that position and the notes from the meeting...

    Pandas-stubs can reorganize their test files to correspond better to current pandas testing

    that the pandas-stubs would be integrated into out pytest tests, but I guess I got that wrong from not attending the meeting.

    @Dr-Irv I assume from #45252 (comment) that your objections to previous attempt no longer apply?

  9. Dr-Irv commented on Jan 10, 2022

    @Dr-Irv
    ContributorAuthor

    that the pandas-stubs would be integrated into out pytest tests, but I guess I got that wrong from not attending the meeting.

    What we decided is that the testing approach from pandas-stubs would be integrated, but we would use the Microsoft stubs as a starting point, because they are more complete than the pandas-stubs .

    @Dr-Irv I assume from #45252 (comment) that your objections to previous attempt no longer apply?

    I'm not sure which objections you are referring to....

    What I said there is that if we could annotate all of our existing tests (which means adding #type: ignore statements in lots of places), that would be a way to test the stubs. But I think that would take a lot of effort.

    Since then, I've seen the pandas-stubs approach and I think it is easier to start from there, and expand the methodology in there to cover many use cases of any stubs we decide to publish.

  10. simonjayhawkins commented on Jan 10, 2022

    @simonjayhawkins
    Member

    I'm not sure which objections you are referring to....

    specifically from #40202 (comment)

    I think it will be easier for the community to fix our source code that has typed methods for the public API (and add an appropriate test in pandas/tests) as opposed to using the reveal_type method proposed in this PR, which would require us to enumerate all the possibilities in two places - once in our regular testing code and once in the "testing types" code.

    Since then, I've seen the pandas-stubs approach and I think it is easier to start from there, and expand the methodology in there to cover many use cases of any stubs we decide to publish.

    IIUC the panda-stubs approach, if not using pytest, is not so different from #40202. i.e. we need to enumerate all the possibilities and have a separate set of tests.

    also from #40202 (comment)

    In addition, it's not clear to me how the current proposal helps us test the variety of arguments that can be passed to methods. We know that our existing testing code already tries different argument types, so if we typed our testing code (and ran mypy on it), then we're getting the benefit of one set of tests that tests functionality as well as typing signatures.

    The pandas-stubs testing approach, if not integrated into the current testing, also does not satisfy this.

  11. Dr-Irv commented on Jan 10, 2022

    @Dr-Irv
    ContributorAuthor

    I think it will be easier for the community to fix our source code that has typed methods for the public API (and add an appropriate test in pandas/tests) as opposed to using the reveal_type method proposed in this PR, which would require us to enumerate all the possibilities in two places - once in our regular testing code and once in the "testing types" code.

    I was more referring to using the reveal_type() aspect of the testing.

    IIUC the panda-stubs approach, if not using pytest, is not so different from #40202. i.e. we need to enumerate all the possibilities and have a separate set of tests.

    I don't think we have to enumerate all the possibilities. We can start with a set of tests that represent "common" usage, and if the stubs are not covering some particular use case (by being too wide or too narrow), we can just add to those stubs.

    In addition, it's not clear to me how the current proposal helps us test the variety of arguments that can be passed to methods. We know that our existing testing code already tries different argument types, so if we typed our testing code (and ran mypy on it), then we're getting the benefit of one set of tests that tests functionality as well as typing signatures.

    The pandas-stubs testing approach, if not integrated into the current testing, also does not satisfy this.

    That's correct. But it's a first step forward. I would like it if we could put typing into our testing code, and just run mypy on that, but it's a much larger effort that just starting with the pandas-stubs testing approach.

  12. simonjayhawkins commented on Jan 10, 2022

    @simonjayhawkins
    Member

    just starting with the pandas-stubs testing approach.

    +1

    At the end of the day it's not so different than the previous attempt. We would at least have some testing and the tests in place (esp for the overloads).

    changing those tests at a later date from say

    i1: pd.Interval = pd.Interval(1, 2, closed='both')
    

    to

    reveal_type(pd.Interval(1, 2, closed='both')) # E: Interval
    

    would not be difficult and would improve the robustness of the checks.

  13. 23 remaining items

  14. twoertwein commented on Apr 30, 2022

    @twoertwein
    Member

    @Dr-Irv I think you mentioned that the MS stub started using a not-yet supported argument of reveal_type to assert the type. Mypy 0.950 now supports something similar: typing_extensions.assert_type python/mypy#12584 (and typing.assert_type in future python versions).

  15. Dr-Irv commented on Apr 30, 2022

    @Dr-Irv
    ContributorAuthor

    @twoertwein Thanks. It appears that assert_type() was also added to pyright as well. This will clean up various tests in the MS stubs and the pandas-stubs repo that I'm working on here: https://github.com/pandas-dev/pandas-stubs

  16. simonjayhawkins commented on May 2, 2022

    @simonjayhawkins
    Member

    It appears that assert_type() was also added to pyright as well. This will clean up various tests in the MS stubs and the pandas-stubs repo that I'm working on here:

    cool. this will overcome the deficiencies of the "assignment" method that I commented on in #45252 (comment) and now align the testing with the numpy method (that was done in #40202)?

  17. simonjayhawkins commented on May 2, 2022

    @simonjayhawkins
    Member

    @Dr-Irv can/should we move this issue (and maybe others) to https://github.com/pandas-dev/pandas-stubs or best to keep that to stub specific issues since I think we could also use the test framework to test the pandas code too. see my comment in mailing list thread.

  18. simonjayhawkins commented on May 2, 2022

    @simonjayhawkins
    Member

    I think we could also use the test framework to test the pandas code too.

    I guess there are two ways to do this.

    1. a script to copy over the tests from pandas-stubs, this could be a github workflow on a commit to pandas-stubs that touches the tests, scheduled, nightly, weekly etc or on a pandas-stubs release.
    2. ci on pandas stubs that tests against pandas main (and maybe the stable release)

    or both. if there is testing against the pandas codebase in pandas-stubs, 1. is also more likely to succeed if automated.

  19. Dr-Irv commented on May 2, 2022

    @Dr-Irv
    ContributorAuthor

    cool. this will overcome the deficiencies of the "assignment" method that I commented on in #45252 (comment) and now align the testing with the numpy method (that was done in #40202)?

    I believe that to be the case.

  20. Dr-Irv commented on May 2, 2022

    @Dr-Irv
    ContributorAuthor

    @Dr-Irv can/should we move this issue (and maybe others) to https://github.com/pandas-dev/pandas-stubs or best to keep that to stub specific issues since I think we could also use the test framework to test the pandas code too. see my comment in mailing list thread.

    I could see it going either way. From the pandas development standpoint, we would want to make sure that changes to pandas in a PR don't cause the tests in pandas-stubs to fail. So keeping this issue here makes sense. On the other hand, from the pandas-stubs viewpoint, we'd have to figure out how to manage testing the stubs that test against the released version of pandas, versus testing stubs that test against the main branch of pandas, so having an issue there makes sense as well.

    There are other issues here that are purely about public type stubs that could possibly be moved over (and maybe closed).

  21. twoertwein commented on May 2, 2022

    @twoertwein
    Member

    I really like the idea of testing pandas with its own annotations and pandas-stubs, but I'm honestly not sure whether pandas-stubs can be used to check pandas without having pandas-stubs-specific ignore comments in pandas (or some patch/sed magic).

    I don't think this is a particularly good example, but it might demonstrate the potential issue: __getitem__ will probably never return all possible types in pandas-stubs (#46616), I could imagine that (after __getitem__ is typed with unions in pandas), that mypy will complain about unnecessary comparisons when using pandas-stubs.

    Instead of checking pandas with pandas-stubs, it might be an option to somehow (don't ask me how) enforce that pandas-stubs only contains annotations where different annotations are intended (for example __getitem__) and functions that are not annotated in pandas. Functions that have suitable annotations in pandas could simply(?) be imported from pandas (if pandas is a dependency of pandas-stubs).

  22. Dr-Irv commented on May 2, 2022

    @Dr-Irv
    ContributorAuthor

    I really like the idea of testing pandas with its own annotations and pandas-stubs, but I'm honestly not sure whether pandas-stubs can be used to check pandas without having pandas-stubs-specific ignore comments in pandas (or some patch/sed magic).

    I think we are not talking about using the pandas-stubs to test pandas source. I think the idea is that we make sure that changes to pandas-stubs work with both released versions of pandas, and in-development versions of pandas, and, conversely, that changes to pandas in development don't break the stubs. The stubs are tested with mypy, pyright, but also pytest, so you could imagine a change in pandas breaking a pytest test.

    Instead of checking pandas with pandas-stubs, it might be an option to somehow (don't ask me how) enforce that pandas-stubs only contains annotations where different annotations are intended (for example __getitem__) and functions that are not annotated in pandas. Functions that have suitable annotations in pandas could simply(?) be imported from pandas (if pandas is a dependency of pandas-stubs).

    I don't think that you can have some stubs in a pandas-stubs package, and the rest in the source code. Not sure the type checkers will manage that.

  23. twoertwein commented on May 2, 2022

    @twoertwein
    Member

    I don't think that you can have some stubs in a pandas-stubs package, and the rest in the source code. Not sure the type checkers will manage that.

    If the stubs explicitly import them (and re-export them as public functions in __all__ or with as) it should work. But having pandas as a dependency of pandas-stubs might not be great.

  24. simonjayhawkins commented on May 3, 2022

    @simonjayhawkins
    Member

    Just to be clear, my comment #45252 (comment) regards the test framework only.

    In the discussion about the challenges of making pandas stubs public (mailing list, dev meeting and elsewhere), my support for having a separate stubs package was on the basis that we would have a test framework that could help keep the standalone stubs package and the pandas codebase more consistent.

    Other than the test framework, we should probably keep the standalone pandas-stubs and inline annotations in pandas decoupled. This will allow us to use all the latest typing features in pandas-stubs as they become available. It will also allow us to continue to add annotations to the pandas code without further constraints (it's quite a challenge already relative to the ease of creating stubs)

    I suspect that we may need to comment out failing tests (on the pandas side) with a TODO in some cases or just allow the test job to fail if we copy over the tests, but I hope that we wouldn't need to change code on the pandas side or stubs on the pandas-stubs side to share the tests.

  25. Dr-Irv commented on May 3, 2022

    @Dr-Irv
    ContributorAuthor

    Other than the test framework, we should probably keep the standalone pandas-stubs and inline annotations in pandas decoupled. This will allow us to use all the latest typing features in pandas-stubs as they become available. It will also allow us to continue to add annotations to the pandas code without further constraints (it's quite a challenge already relative to the ease of creating stubs)

    I think we are all in agreement on keeping the public stubs separate.

    I suspect that we may need to comment out failing tests (on the pandas side) with a TODO in some cases or just allow the test job to fail if we copy over the tests, but I hope that we wouldn't need to change code on the pandas side or stubs on the pandas-stubs side to share the tests.

    I'm not sure that the tests in pandas-stubs would be good to use as typing tests against the pandas code base for the following reason. I intend to add assert_type to those tests. For some methods/functions, the public stubs may return a more narrow type than the internal types declare. As an example, consider the discussion we had about whether pd.to_datetime() should indicate that it can return NaTType in all the overloads. For the public stubs, we dont' want to indicate that - it would make users do cast all the time. But internally, we may want to indicate the full wider type. So a test in pandas-stubs would do an assert_type on the narrower type, but if you use that test on the pandas code base, that test would fail.

  26. simonjayhawkins commented on May 3, 2022

    @simonjayhawkins
    Member

    I'm not sure that the tests in pandas-stubs would be good to use as typing tests against the pandas code base for the following reason. I intend to add assert_type to those tests. For some methods/functions, the public stubs may return a more narrow type than the internal types declare.

    I think that is exactly the reason to use the same tests. IMO we should make the annotations as consistent as possible and part of the original argument on why we did not have separate stubs before now.

    As an example, consider the discussion we had about whether pd.to_datetime() should indicate that it can return NaTType in all the overloads. For the public stubs, we dont' want to indicate that - it would make users do cast all the time.

    I still disagree with this approach, but am happy to await user feedback once the stubs are public on whether this is a significant issue.

    So a test in pandas-stubs would do an assert_type on the narrower type, but if you use that test on the pandas code base, that test would fail.

    I do expect that we would need to have some allowed failures at first, but fixing these would make the pandas-stubs and in-line type annotations more consistent.

  27. Dr-Irv commented on Jul 9, 2023

    @Dr-Irv
    ContributorAuthor

    @simonjayhawkins @twoertwein

    I'm thinking we can close this, as we are now testing the type checking of the published pandas-stubs API's as part of the CI in that project. Would like your opinion.

  28. twoertwein commented on Jul 9, 2023

    @twoertwein
    Member

    Could also leave this issue open if we hope to have consistency checks between pandas and pandas-stubs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CIContinuous IntegrationEnhancementTestingpandas testing functions or related to the test suiteTypingtype annotations, mypy/pyright type checking

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions