Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide type annotations for the public API #131

Merged
merged 2 commits into from
Dec 23, 2023

Conversation

mjpieters
Copy link
Contributor

@mjpieters mjpieters commented Dec 7, 2023

The package now includes a PEP 561 py.typed flag file to let type
checkers know that this package provides type annotations.

The annotations provide full coverage for all public functions and
classes in the aocd package, with the library reaching 100% coverage as
per pyright --ignoreexternal --verifytypes.

A few notes on this PR:

  • I tried to follow the current best practices for type annotations as much as possible. This includes using from __future__ import annotations so the newer but much more readable A | B union syntax can be used and have the project still work on Python version 3.9. The exception are the type aliases in aocd.types; Python 3.9 has no typing.TypeAlias and you can't use | syntax in a type alias assignment in 3.9 (it's not an annotation). The first issue could be solved by adding a dependency on the typing_extensions library, but that seems overkill for one missing typing feature.

  • The PR is limited to the public API members only. All private names are left untouched. The goal was to provide consumers of aocd with type annotations, not to validate the types used within aocd itself. Contrast this with Add type annotations #123 which tries to provide full typing coverage of the whole codebase.

  • I re-introduced the __all__ list in aocd/__init__.py because that's what type checkers use to determine what is public more than anything else. Without this list, none of the imports in __init__.py would be seen as exported. I made sure to only export what was exported before.

  • A new aocd.types module holds public type aliases and TypedDict objects for types that I think consumers of aocd might find useful; e.g. a consumer might want to store a returned PuzzleStats value, and a 3rd-party library might want to expose a method that passes through a PuzzlePart and AnswerValue argument to aocd.submit(). Plus, these provide inline documentation in IDEs that make use of type information (e.g. PyCharm or Visual Studio Code with the Pylance extension).

  • The aocd.examples.Page dataclass and aocd.examples.Example named tuple already used type annotations for their fields, but these did not properly annotate the optional fields (those that can be None as well as their intended type). Changing these to ... | None means their API has now changed. I don't think this is a big deal, it was really a bug and so I doubt this needs a deprecation warning.

  • A second commit extends the Github workflow to validate that the public API keeps the 100% coverage. I recommend adding a pre-commitconfiguration that checks for this locally on every commit. See https://github.com/RobertCraigie/pyright-python#pre-commit for what that looks like.

Resolves #78

Copy link

codecov bot commented Dec 7, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (34d73e3) 91.56% compared to head (589325a) 91.72%.
Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #131      +/-   ##
==========================================
+ Coverage   91.56%   91.72%   +0.15%     
==========================================
  Files          24       25       +1     
  Lines        2656     2706      +50     
  Branches      357      358       +1     
==========================================
+ Hits         2432     2482      +50     
  Misses        159      159              
  Partials       65       65              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@mjpieters mjpieters force-pushed the type_annotations branch 2 times, most recently from 85f1652 to cd5dce5 Compare December 7, 2023 16:10
@mjpieters
Copy link
Contributor Author

I managed to completely miss the other PR here, #123. I'll take a look to see what is different between mine and that one.

@mjpieters
Copy link
Contributor Author

The difference between this PR and #123 is that this PR is much more limited in scope. It only aims to provide type annotations for consumers of the library, not for the library itself.

See it as a multi-step process; type hinting for internal use could be added separately. This makes this PR a lot smaller.

@mjpieters mjpieters force-pushed the type_annotations branch 6 times, most recently from af4900f to 87eeac9 Compare December 7, 2023 18:06
@mjpieters mjpieters mentioned this pull request Dec 7, 2023
Comment on lines -19 to +22
log = logging.getLogger(__name__)
log: logging.Logger = logging.getLogger(__name__)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The log objects in each module should probably be private, so _log. The only reason I annotated them is because they are part of the public API.

Comment on lines +1 to +2
from __future__ import annotations

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be honest, I suspect that the whole utils module should really be named _utils, and not be part of the public API at all.

But that's up to the maintainer of the project, really.

Copy link
Owner

@wimglenn wimglenn left a comment

Choose a reason for hiding this comment

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

Looks good, just a few questions/comments

.github/workflows/tests.yml Outdated Show resolved Hide resolved
aocd/utils.py Outdated Show resolved Hide resolved

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
Copy link
Owner

Choose a reason for hiding this comment

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

What Python version is this going to use by default?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point; it should really pick a version. It'll use the value from .python-version if that file is present, but if not it'll default to the 'default Python installed on the runner'.

# https://microsoft.github.io/pyright/#/import-resolution?id=editable-installs
pip install --editable . --config-settings editable_mode=strict

- run: echo "$PWD/.venv/bin" >> $GITHUB_PATH
Copy link
Owner

Choose a reason for hiding this comment

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

What does this do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This puts the venv on the PATH for all following steps. It lets pyright find the venv as it looks for the current python executable.

aocd/__init__.py Outdated Show resolved Hide resolved
aocd/__init__.py Outdated Show resolved Hide resolved

@property
def answers(self):
def answers(self) -> tuple[str | None, str | None]:
Copy link
Owner

Choose a reason for hiding this comment

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

What is the code-path where these can be None? IIRC we raise exception for unavailable answers, and day 25 part b answer is always the empty string.

Copy link
Contributor Author

@mjpieters mjpieters Dec 21, 2023

Choose a reason for hiding this comment

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

For the Example named tuple, the default values for answer_a and answer_b is None. Your code creates a missing = Example("") instance, and missing.answers is (None, None).

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 think you were thinking of Puzzle.answers here, which can only return tuple[str, str] and will raise an exception if that's not possible. But anyone can create an Example instance with one or other answer ommitted.

aocd/get.py Outdated Show resolved Hide resolved
@wimglenn
Copy link
Owner

@mjpieters I will likely merge this PR first and then rebase the other one on your changes. Could you address/resolve these review comments first, though?

@mjpieters mjpieters force-pushed the type_annotations branch 2 times, most recently from abed4c6 to 8981fc6 Compare December 21, 2023 09:44
The package now includes a PEP 561 `py.typed` flag file to let type
checkers know that this package provides type annotations.

The annotations provide full coverage for all public functions and
classes in the aocd package, with the library reaching 100% coverage as
per `pyright --ignoreexternal --verifytypes`.
@wimglenn wimglenn merged commit 40d1ef7 into wimglenn:main Dec 23, 2023
10 checks passed
@mjpieters mjpieters deleted the type_annotations branch December 23, 2023 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Include type information
2 participants