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 rudimentary mypy annotations #971

Closed
wants to merge 3 commits into from
Closed

Conversation

atugushev
Copy link
Member

@atugushev atugushev commented Oct 26, 2019

What's done

  • updated setup.cfg
  • added mypy hook to pre-commit
  • added typings for the cache module

Typings added using pytest-annotate.

Refs: #972.

@atugushev atugushev added the maintenance Related to maintenance processes label Oct 26, 2019
@atugushev atugushev mentioned this pull request Oct 26, 2019
16 tasks
@codecov
Copy link

codecov bot commented Oct 26, 2019

Codecov Report

Merging #971 into master will decrease coverage by 0.33%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #971      +/-   ##
==========================================
- Coverage   99.31%   98.97%   -0.34%     
==========================================
  Files          35       35              
  Lines        2329     2338       +9     
  Branches      301      303       +2     
==========================================
+ Hits         2313     2314       +1     
- Misses          8       15       +7     
- Partials        8        9       +1
Impacted Files Coverage Δ
piptools/cache.py 100% <100%> (ø) ⬆️
piptools/__init__.py 66.66% <0%> (-33.34%) ⬇️
tests/utils.py 66.66% <0%> (-33.34%) ⬇️
tests/test_repository_pypi.py 97.82% <0%> (-2.18%) ⬇️
piptools/repositories/pypi.py 94.41% <0%> (-1%) ⬇️
piptools/resolver.py 100% <0%> (ø) ⬆️

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 02fb529...1a9b4be. Read the comment docs.

hooks:
- id: mypy
language_version: python3
# To prevent "Duplicate module named 'setup'" error
Copy link
Member

Choose a reason for hiding this comment

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

I was arguing about this with Anthony a while back. Still now sure about the best way to prevent this: https://github.com/pre-commit/mirrors-mypy/issues/5.

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Maybe. But then, I think, I've had problems with that too. It's up to you atm.

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 think there's nothing wrong with this exception since packages are raw test fixtures. I'd prefer to leave it as is and move on. It's unlikely that type checking in packages could be any useful.

@@ -49,7 +57,10 @@ class DependencyCache(object):
Where X.Y indicates the Python version.
"""

_cache = None # type: Dict[str, dict]
Copy link
Member

Choose a reason for hiding this comment

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

Why did you move it from instance to class?

Copy link
Member Author

Choose a reason for hiding this comment

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

It fixes the following complainings:

piptools/cache.py:82: error: Incompatible return value type (got "None", expected "Dict[str, Dict[Any, Any]]")
piptools/cache.py:109: error: Incompatible types in assignment (expression has type "Dict[str, Dict[Any, Any]]", variable has type "None")
piptools/cache.py:111: error: Incompatible types in assignment (expression has type "Dict[<nothing>, <nothing>]", variable has type "None")
piptools/cache.py:122: error: Incompatible types in assignment (expression has type "Dict[<nothing>, <nothing>]", variable has type "None")

Copy link
Member

Choose a reason for hiding this comment

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

But you completely changed the semantics with this move, which may cause unwanted side-effects. Why didn't you just add an annotation in the initializer?

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've tried at first:

self._cache = None  # type: Dict[str, dict]

but

piptools/cache.py:71: error: Incompatible types in assignment (expression has type "None", variable has type "Dict[str, Dict[Any, Any]]")

then

self._cache = None  # type: Optional[Dict[str, dict]]

but

piptools/cache.py:82: error: Incompatible return value type (got "Optional[Dict[str, Dict[Any, Any]]]", expected "Dict[str, Dict[Any, Any]]")

I've found the solution and now can't find a reference since I made changes a month ago. I should have fixed it in a separate well-described commit with the reference 😒

Copy link
Member Author

Choose a reason for hiding this comment

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

which may cause unwanted side-effects.

Yeah, now _cache is in DependencyCache.__dict__.

Copy link
Member Author

Choose a reason for hiding this comment

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

There might a design problem also.

Copy link
Member Author

Choose a reason for hiding this comment

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

from typings import Optional, Dict

class Cache:
    def __init__(self):
        # type: () -> None
        self._cache = None  # type: Optional[Dict[str, dict]]

    @property
    def cache(self):
        # type: () -> Dict[str, dict]
        if self._cache is None:
            self.read_cache()
        return self._cache

    def read_cache(self):
        # type: () -> None
        self._cache = {"cache": {}}
$ mypy t.py
Success: no issues found in 1 source file

🤷‍♂

Copy link
Member Author

@atugushev atugushev Oct 28, 2019

Choose a reason for hiding this comment

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

I've refactored property(cache) and read_cache to assign explicitly self._cache and it works:

diff --git piptools/cache.py piptools/cache.py
index 5c5fb34..24327f1 100644
--- piptools/cache.py
+++ piptools/cache.py
@@ -57,7 +57,6 @@ class DependencyCache(object):
     Where X.Y indicates the Python version.
     """

-    _cache = None  # type: Dict[str, dict]

     def __init__(self, cache_dir=None):
         # type: (Optional[str]) -> None
@@ -69,6 +68,7 @@ class DependencyCache(object):
         cache_filename = "depcache-py{}.json".format(py_version)

         self._cache_file = os.path.join(cache_dir, cache_filename)
+        self._cache = None  # type: Optional[Dict[str, dict]]

     @property
     def cache(self):
@@ -78,7 +78,7 @@ class DependencyCache(object):
         lazily loads the cache from disk.
         """
         if self._cache is None:
-            self.read_cache()
+            self._cache = self.read_cache()
         return self._cache

     def as_cache_key(self, ireq):
@@ -103,12 +103,12 @@ class DependencyCache(object):
         return name, "{}{}".format(version, extras_string)

     def read_cache(self):
-        # type: () -> None
+        # type: () -> Dict[str, dict]
         """Reads the cached contents into memory."""
         if os.path.exists(self._cache_file):
-            self._cache = read_cache_file(self._cache_file)
+            return read_cache_file(self._cache_file)
         else:
-            self._cache = {}
+            return {}

     def write_cache(self):
         # type: () -> None
$ mypy .
Success: no issues found in 44 source files

Copy link
Member Author

Choose a reason for hiding this comment

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

Is this approach okay?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, looks good.

@aleksihakli aleksihakli removed their request for review October 28, 2019 11:35
@codingjoe
Copy link
Member

codingjoe commented Oct 29, 2019

@atugushev general question, why don't we use proper PEP484 type hints? They have been implemented in Python 3.5. We don't need to support Python 3.4 since it reached EoL in March this year.

If we really want to use comments for type annotations, I would recommend going with Google style doc strings.

@atugushev
Copy link
Member Author

atugushev commented Oct 29, 2019

@codingjoe type annotations were presented in Python 3, and they have not been backported to Python 2. The general solution is to use type comments for projects which support legacy Python. When we finally drop Python 2 support, we can convert type comments to the proper Python 3 style type annotations using pytype (or similar).

@codingjoe
Copy link
Member

codingjoe commented Oct 29, 2019

@codingjoe type annotations were presented in Python 3, and they have not been backported to Python 2. The general solution is to use type comments for projects which support legacy Python. When we finally drop Python 2 support, we can convert type comments to the proper Python 3 style type annotations using pytype (or similar).

Why don't we just drop Python 2 support? There's only two months left until EoL. Should we find a security vulnerability we can backport that patch on an older major version of pip-tools.

It's probably going to take 2 months until we removed all Python 2 specific code anyways, so by the time we release a new major version, Python 2 is finally going to be a thing of the past.

I mean, if Django, which has over 100x more users, dropped Python 2 main stream support years ago, why are we having such a hard time? Just saying ;)

@atugushev
Copy link
Member Author

atugushev commented Oct 29, 2019

@codingjoe

Why don't we just drop Python 2 support?

Perhaps, it makes sense to drop support Python 2 when pip does. Discussion about a final date is still on, though.

image

@atugushev
Copy link
Member Author

@codingjoe but, personally, I'm in favor of dropping Python 2 support after 2020-01-01 and add pinned pip to the latest minor version as a dependency.

@codingjoe
Copy link
Member

codingjoe commented Oct 30, 2019

@atugushev is that statistic for the latest version? Also, I believe a lot of this will be package mirrors. Then again, it doesn't matter. You can still use an old version, and we can patch an old version until end of year. All I want to achieve is to save you time and effort to implement something, that we could really do a lot better in Python 3 only.

@atugushev
Copy link
Member Author

@codingjoe

@atugushev is that statistic for the latest version? Also, I believe a lot of this will be package mirrors.

There are all the pip-tools versions.

Then again, it doesn't matter. You can still use an old version, and we can patch an old version until end of year. All I want to achieve is to save you time and effort to implement something, that we could really do a lot better in Python 3 only.

That's why I've added the alternative solution to the #972 😉

@codingjoe codingjoe removed their request for review April 21, 2020 11:05
@atugushev
Copy link
Member Author

Close this due to #972 (comment).

@atugushev atugushev closed this Jun 28, 2020
@atugushev atugushev deleted the mypy branch June 28, 2020 06:31
@atugushev atugushev mentioned this pull request Dec 16, 2020
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintenance Related to maintenance processes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants