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

Add typing to signac.core.json. #313

Merged
merged 16 commits into from
Apr 2, 2020
Merged

Conversation

bdice
Copy link
Member

@bdice bdice commented Mar 21, 2020

Description

This is an experimental PR adding types to signac.core.json. This was created using MonkeyType and modified slightly. Adding type validation seems like a simple process.

To generate this, I installed monkeytype and then ran the following commands from the repository root:

# Collect type annotations based on runtime types
monkeytype run -m pytest -v
# List the modules with suggested annotations
monkeytype list-modules
# Show the type annotations it's suggesting
monkeytype stub signac.core.json
# Apply the suggested annotations
monkeytype apply signac.core.json
# Manually edit the suggested annotations to be more general
vim signac/core/json.py

We'd need to validate the results with mypy, I think.

Motivation and Context

Just wanted to try it out.

Types of Changes

  • New feature

1The change breaks (or has the potential to break) existing functionality.

Checklist:

If necessary:

  • I have updated the API documentation as part of the package doc-strings.
  • I have created a separate pull request to update the framework documentation on signac-docs and linked it here.
  • I have updated the changelog and added all related issue and pull request numbers for future reference (if applicable). See example below.

@bdice bdice added the refactor Code refactoring label Mar 21, 2020
@bdice bdice self-assigned this Mar 21, 2020
@bdice
Copy link
Member Author

bdice commented Mar 21, 2020

Adding mypy checking is probably a good idea. For example, it caught this bug:

print("Did you mean {}?".format(CAST_MAPPING_WARNING[x], file=sys.stderr))

Note that the parentheses are in the wrong places.

@csadorf
Copy link
Contributor

csadorf commented Mar 23, 2020

@bdice Cool! Did you have a specific reason to start with that module?

@bdice
Copy link
Member Author

bdice commented Mar 24, 2020

@csadorf No specific reason to start there. @glotzerlab/signac-committers Can I get a couple opinions on this? Should we apply types? I think it is a good idea, and it is an automated safety check that we can use to ensure code quality going forward. It's a little bit like unit tests in that it can't/won't catch every potential problem but definitely reduces the "surface area" for potential problems.

@csadorf
Copy link
Contributor

csadorf commented Mar 24, 2020

I am all in favor of slowly introducing typing to the code base. Is this already checked as part of the CI on this branch?

@vyasr
Copy link
Contributor

vyasr commented Mar 25, 2020

We discussed adding types as part of glotzerlab/signac-flow#193. In that PR, I laid out what i think are the necessary preconditions for documenting parameter types. On that issue I pointed out that applying type hints could proceed independently of docs, and the basic CI setup for doc linting is already in place in this repo, so adding CI for types would be reasonable and not lead to any conflicting dev IMO.

@codecov
Copy link

codecov bot commented Mar 28, 2020

Codecov Report

Merging #313 into master will decrease coverage by <.01%.
The diff coverage is 90.9%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #313      +/-   ##
==========================================
- Coverage   76.18%   76.18%   -0.01%     
==========================================
  Files          43       43              
  Lines        7076     7075       -1     
==========================================
- Hits         5391     5390       -1     
  Misses       1685     1685
Impacted Files Coverage Δ
signac/contrib/project.py 91.58% <ø> (ø) ⬆️
signac/common/errors.py 75% <ø> (-3.58%) ⬇️
signac/contrib/filterparse.py 83.87% <0%> (ø) ⬆️
signac/common/host.py 46.42% <100%> (ø) ⬆️
signac/contrib/indexing.py 75.59% <100%> (ø) ⬆️
signac/syncutil.py 82.8% <100%> (ø) ⬆️
signac/core/json.py 83.33% <100%> (+0.72%) ⬆️
signac/core/h5store.py 91% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update deb56fa...5f406e2. Read the comment docs.

@mikemhenry
Copy link
Collaborator

I love this!!!! I'm not sure what any downside would be. It can help us catch bugs + make our code easier to maintain.

@bdice bdice marked this pull request as ready for review March 28, 2020 03:10
@bdice bdice requested review from a team as code owners March 28, 2020 03:10
@bdice bdice requested review from csadorf and cbkerr and removed request for a team March 28, 2020 03:10
Copy link
Member Author

@bdice bdice left a comment

Choose a reason for hiding this comment

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

I have annotated my PR with explanations for the benefit of the reader, since none of these changes are especially obvious without experience with type annotations / mypy.

@@ -22,9 +22,5 @@ class ExportError(Error, RuntimeError):
pass


class FileNotFoundError(Error, FileNotFoundError):
Copy link
Member Author

Choose a reason for hiding this comment

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

This seemed like an unused compatibility layer for Python 2 (which did not have FileNotFoundError). I removed it.

Comment on lines +510 to +511
cls = H5Store # type: ignore
suffix = '.h5' # type: ignore
Copy link
Member Author

Choose a reason for hiding this comment

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

Explanation for the ignore: These are overriding a default of None in the DictManager parent class, meaning that the type is changing. That raises an error.

@@ -11,14 +11,14 @@

logger = logging.getLogger('sync')
logging.addLevelName(LEVEL_MORE, 'MORE')
logging.MORE = LEVEL_MORE
logging.MORE = LEVEL_MORE # type: ignore
Copy link
Member Author

Choose a reason for hiding this comment

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

Explanation for the ignore: Assigning logging.MORE (and logger.more below) is setting a property that doesn't exist in the original module / class. This kind of monkey-patch raises an error:

signac/syncutil.py:14: error: Module has no attribute "MORE"
signac/syncutil.py:21: error: "Logger" has no attribute "more"

Comment on lines 56 to 57
# The following line must be ignored: https://github.com/python/mypy/issues/708
methodmap['same_files'] = methodmap['diff_files'] = phase3 # type: ignore
Copy link
Member Author

Choose a reason for hiding this comment

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

Explanation: This ignore is due to a long-standing issue in mypy. This is the "third case" described by Guido van Rossum in that issue.

signac/syncutil.py:57: error: Incompatible types in assignment (expression has type "Callable[[dircmp_deep], Any]", target has type "Callable[[], None]")

@@ -159,7 +159,7 @@ class RegexFileCrawler(BaseCrawler):
MyCrawler.define('.*\/a_(?P<a>\d+)\.txt', 'TextFile')
"""
"Mapping of compiled regex objects and associated formats."
definitions = dict()
definitions = dict() # type: ignore
Copy link
Member Author

Choose a reason for hiding this comment

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

Explanation: This requests a type annotation for the dict. I added ignores to all deprecated modules where necessary, without questioning it.

@@ -56,7 +56,7 @@ def _cast(x):
"Attempt to interpret x with the correct type."
try:
if x in CAST_MAPPING_WARNING:
print("Did you mean {}?".format(CAST_MAPPING_WARNING[x], file=sys.stderr))
print("Did you mean {}?".format(CAST_MAPPING_WARNING[x]), file=sys.stderr)
Copy link
Member Author

Choose a reason for hiding this comment

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

mypy caught a bug in the print statement! Fixed here.

@@ -17,7 +17,7 @@


SESSION_PASSWORD_HASH_CACHE = SimpleKeyring()
SESSION_USERNAME_CACHE = dict()
SESSION_USERNAME_CACHE = dict() # type: ignore
Copy link
Member Author

Choose a reason for hiding this comment

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

Explanation: This requests a type annotation for the dict. I added ignores to all deprecated modules where necessary, without questioning it.

@@ -18,6 +18,9 @@ exclude = configobj,passlib,cite.py,conf.py
match = jsondict.py
ignore = D105, D107, D203, D213

[mypy]
ignore_missing_imports = True
Copy link
Member Author

Choose a reason for hiding this comment

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

ignore_missing_imports is enabled so that mypy doesn't get mad about missing type annotations in dependencies like deprecation or numpy.

@bdice
Copy link
Member Author

bdice commented Mar 28, 2020

I am all in favor of slowly introducing typing to the code base. Is this already checked as part of the CI on this branch?

@csadorf Now it is in CI. This PR is ready for review. After this is approved/merged, I will add type annotations for other files.

Copy link
Contributor

@csadorf csadorf left a comment

Choose a reason for hiding this comment

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

Two suggestions, otherwise LGTM!

Thank you very much @bdice !

@@ -31,6 +31,11 @@ jobs:
command: |
pydocstyle

- run:
name: style-check-type-hints
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this really a style check?

Suggested change
name: style-check-type-hints
name: check-type-hints

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree -- I was just matching the existing pattern. I renamed the CI job to "pre-checks" so it's all-encompassing, and renamed the individual checks according to their purpose.

signac/syncutil.py Outdated Show resolved Hide resolved
signac/syncutil.py Outdated Show resolved Hide resolved
@cbkerr
Copy link
Member

cbkerr commented Apr 2, 2020

Looks good, except should we leave the corrections in a168649 to another branch/PR since it is unrelated to typing, or is it not worth it?

@cbkerr cbkerr closed this Apr 2, 2020
@cbkerr
Copy link
Member

cbkerr commented Apr 2, 2020

My mistake.

@cbkerr cbkerr reopened this Apr 2, 2020
@bdice
Copy link
Member Author

bdice commented Apr 2, 2020

Looks good, except should we leave the corrections in a168649 to another branch/PR since it is unrelated to typing, or is it not worth it?

I decided it wasn't worth putting in its own PR. That's a choice that could be debated but I think it's fine to merge in as-is in the interest of expediency.

Copy link
Member

@cbkerr cbkerr left a comment

Choose a reason for hiding this comment

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

I decided it wasn't worth putting in its own PR. That's a choice that could be debated but I think it's fine to merge in as-is in the interest of expediency.

Sounds good!

@bdice bdice merged commit 43a5519 into master Apr 2, 2020
@bdice bdice deleted the feature/typing-signac.core.json branch April 2, 2020 16:02
@bdice bdice added this to the v1.5.0 milestone Apr 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
refactor Code refactoring
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants