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

Type Hints #1095

Closed
mdantonio opened this issue Dec 21, 2020 · 56 comments
Closed

Type Hints #1095

mdantonio opened this issue Dec 21, 2020 · 56 comments

Comments

@mdantonio
Copy link

Hello,

I was looking for any type hints for GitPython, but I found nothing neither in the code nor in independent stub repositories.

Is there any plan to introduce type hinting?

@Byron
Copy link
Member

Byron commented Dec 22, 2020

I am absolutely open to receiving PRs adding type hints, and GitPython would be happy to host them directly in source.

Any amount of types would help and is appreciated, thus there is no need to add type hints everywhere at once.

@Yobmod
Copy link
Contributor

Yobmod commented Feb 18, 2021

I looked at starting to contribute type hints, but wanted to ask would it be feasible to drop support for python 3.4 (EOL 2 years ago)?

With 3.5+ type hints can be added directly to the code, but for 3.4 they would need to be in .pyi files, doubling the number of files and requiring they are kept in sync (AFAIK, 3.4 will give errors rather than ignore most inline type hints even though annotation syntax is possible)

@Byron
Copy link
Member

Byron commented Feb 19, 2021

@Yobmod Thanks for bringing this to my attention, and I would be glad to see type hints in code. Please feel free to submit a draft PR that starts out by dropping PY3.4 support to allow adding the first type hints in code. That one wouldn't have to be complete at all, but could lay the foundation for more PRs adding types in future.

@Yobmod
Copy link
Contributor

Yobmod commented Feb 24, 2021

I've put in a PR to drop python 3.4.
Planning to use Monkeytype to add initial types to the most user-facing files and see how that goes.

@Yobmod
Copy link
Contributor

Yobmod commented Feb 27, 2021

A plan:

  • Drop 3.4 (add 3.9)
  • Final 3.4 release
  • Monkeytype using test suite
  • Add monkeytype.sqlite3 and mypy.ini to .gitignore
  • Apply initial types to Repo/base & Repo/fun
  • Add mypy.ini
  • Add py.typed
  • Appy initial types to git/*
  • First typed release
  • Appy initial types to index/*
  • Appy initial types to refs/*
  • Appy initial types to objects/*
  • Increase mypy strictness
  • Expand/Refine types (use overloads, Literals, TypedDicts etc)
  • Make IterableList Generic & update throughout
  • second typed release
  • Drop 3.5 (add 3.10)
  • Fix blocking issues caused by 3.5 compat

@mdantonio
Copy link
Author

Thank for all the effort towards type hints! I am sorry I have not been able to contribute so far, I hope to be able to put some effort after the great initial start of @Yobmod

@Yobmod
Copy link
Contributor

Yobmod commented Feb 28, 2021

Thank for all the effort towards type hints! I am sorry I have not been able to contribute so far, I hope to be able to put some effort after the great initial start of @Yobmod

Thanks, Any help is always good :)

Branch for adding types is here: https://github.com/Yobmod/GitPython/tree/addtypes

Mypy-stubgen just typed everythin as Any, and Monkeytype has not been as useful as i hoped. If any wants to run it, commands are:

  • monkeytype run test/tstrunner.py
  • monkeytype list > mtype_filelist.txt
  • monkeytype stub git.cmd > git/cmd.pyi

But i'm mostly doing them by hand.

@kasium
Copy link
Contributor

kasium commented Jun 30, 2021

Hey, it seems that most of the code is typed, right? Would it be possible to include a "py.typed" file into the repo so that mypy can pick the types up?

@Yobmod
Copy link
Contributor

Yobmod commented Jun 30, 2021

Hey, it seems that most of the code is typed, right? Would it be possible to include a "py.typed" file into the repo so that mypy can pick the types up?

Hi, there already is one. Are you on the newest version?
Annotations showing up for me.

@kasium
Copy link
Contributor

kasium commented Jun 30, 2021

Yes I use the latest version. The py.typed file is not present in the tar.gz nor in the wheel - maybe that's the issue.
Mypy (also latest version) complains that no stub could be found

@Yobmod
Copy link
Contributor

Yobmod commented Jun 30, 2021

I've read up on some setuptools options, it seems that the package_data and include_package_data arguments are mutually exclusive. So setup.py is picking up the package_data only from the MANIFEST.INI and not from within the setup.include_package_data list.

I'll add py.typed to the manifest and hopefully that fixes it.

@kasium
Copy link
Contributor

kasium commented Jun 30, 2021

Great. thanks a lot

@Yobmod
Copy link
Contributor

Yobmod commented Jul 9, 2021

I've installed from git and it puts the py.typed where it should be now.
If anyone wants the typehints before the next release, can do:

pip install git+https://github.com/gitpython-developers/GitPython.git -U

@kasium
Copy link
Contributor

kasium commented Jul 21, 2021

@Yobmod thanks for the work here. Are there any plans when 3.1.19 will be released?

@Byron
Copy link
Member

Byron commented Jul 21, 2021

@kasium Judging from the checklist further up all work seems to be done waiting for everything to be released. @Yobmod - do you think a release is in order?

@Yobmod
Copy link
Contributor

Yobmod commented Jul 21, 2021

Yep, we could do a release now.
I avoided any api changes, but lots of small runtime changes (None checks, isinstance(), etc), so would be good to find out if they trip up on any edge cases, which I think only a release can do.

The next step for typing is to check the runtime types to fix the remaining unknowns. Unfortunately I cant find a library to do it (Typeguard etc. will report if using the wrong type, but not if the type is too broad), so it will take a while.

@Byron Byron added this to the v3.1.19 - Bugfixes milestone Jul 23, 2021
@Byron
Copy link
Member

Byron commented Jul 23, 2021

And here is the new release.

Fingers crossed!

@adeandrade
Copy link

    import git
  File "/opt/python/lib/python3.9/site-packages/git/__init__.py", line 8, in <module>
    from git.exc import *                       # @NoMove @IgnorePep8
  File "/opt/python/lib/python3.9/site-packages/git/exc.py", line 10, in <module>
    from git.compat import safe_decode
  File "/opt/python/lib/python3.9/site-packages/git/compat.py", line 32, in <module>
    from git.types import TBD
  File "/opt/python/lib/python3.9/site-packages/git/types.py", line 18, in <module>
    from typing_extensions import TypeGuard  # noqa: F401
ImportError: cannot import name 'TypeGuard' from 'typing_extensions' (/opt/python/lib/python3.9/site-packages/typing_extensions.py)

@Byron Just got this with 3.1.19 and Python 3.9.6. Maybe we should yank it?

@Yobmod
Copy link
Contributor

Yobmod commented Jul 23, 2021 via email

@mdantonio
Copy link
Author

mdantonio commented Jul 23, 2021

Hello,
installed v3.1.19 (and upgraded typing_extensions to 3.10.0.0) but type hints are still ignored by mypy because py.typed seems to be yet not included in the release

Here the content of the installation folder:

$ ls /usr/local/lib/python3.9/dist-packages/git/
cmd.py  compat.py  config.py  db.py  diff.py  exc.py  index  __init__.py  objects  __pycache__  refs  remote.py  repo  types.py  util.py

$ grep "3.1.19" /usr/local/lib/python3.9/dist-packages/git/*
grep: /usr/local/lib/python3.9/dist-packages/git/index: Is a directory
/usr/local/lib/python3.9/dist-packages/git/__init__.py:__version__ = '3.1.19'

type hints started to work after a touch /usr/local/lib/python3.9/dist-packages/git/py.typed

@Byron
Copy link
Member

Byron commented Jul 23, 2021 via email

@Yobmod
Copy link
Contributor

Yobmod commented Jul 23, 2021

😭
I've looked at loads of example libraries and instructions, and they're all different.

Encode/starlette have py.typed listed in setup.py package_data, but not in manifest.in, and that works.
That what mypy instructions say but it didn't work last release.

Encode/httpx (issue linked above) had same issue, so added py.typed to manifest.in.
So we did that, and it worked for installs from github, but it seems not from pypi.

I've now added it to both setup.py and manifest.in (DRY is so 1980s) to see if that fixes it (based on https://stackoverflow.com/questions/3596979/manifest-in-ignored-on-python-setup-py-install-no-data-files-installed)

If that doesn't work, the only thing I can think of is something to do with the installed lib having a different name? (git vs gitpython).

Maybe should release a beta with the fixes I commited this morn? Then I can install from pypi. I can't think of how else to test this?
Or I'll release as a clone package and test that one.

@Yobmod
Copy link
Contributor

Yobmod commented Jul 23, 2021

Yeah, CI doesn't error because it only happens with pypi install into already existing environments, but CI is making fresh venvs with the newest available libs each time

@mdantonio
Copy link
Author

mdantonio commented Jul 23, 2021

mypy docs (see the very last paragraph in https://mypy.readthedocs.io/en/latest/installed_packages.html#creating-pep-561-compatible-packages) suggests:

global-include *.typed

in MANIFEST.in

But i think that include py.typed should work as well...

(However, none of the files in the MANIFEST.in are included in the package... it seems that the MANIFEST is ignored at all)

kajarenc added a commit to kajarenc/streamlit that referenced this issue Jul 23, 2021
This release includes the unfinished addition of types to the library.

For more information see: gitpython-developers/GitPython#1095
@Yobmod
Copy link
Contributor

Yobmod commented Jul 28, 2021

I think the more version specific guard is the correct fix, mypy just complaining because it can't cope with the same variable being defined 3 different ways for different pythons.

@Yobmod
Copy link
Contributor

Yobmod commented Jul 28, 2021

Type checked all my functions that are using gitpython (removed several Any and cast, I'm super happy!) and this is the only problematic case that I found:

I have a function that can return gitobj.active_branch.name, but mypy complains:

Returning Any from function declared to return "Optional[str]"

At runtime i got a str from active_branch.name so probably it is still un-annotated

What is the gitobj?
The only active_branch.name in the code base is an attribute of Repo, but that is typed (as Union[str, PathLike[str], which ive changed to just str).
Or if it was a SymbolicReference of some type, that has a name property that was missing a type, now added.

For some of the Git comand wrappers, they are too dynamic to add types (so default to Any), until/unless someone adds specific methods/overloads for each command.

@mdantonio
Copy link
Author

gitobj is an instance of Repo

Here a minimal test case to reproduce the issue:

from git import Repo

def get_active_branch(gitobj: Repo) -> str:
    return gitobj.active_branch.name

gitobj = Repo(".")
print(get_active_branch(gitobj))

$ mypy --warn-return-any active_branch_test.py 
active_branch_test.py:4: error: Returning Any from function declared to return "str"
Found 1 error in 1 file (checked 1 source file)

@Yobmod
Copy link
Contributor

Yobmod commented Jul 28, 2021

Ah, no error for me.

What does mypy show for reveal_type(gitobj.active_branch)?
For me it is 'Head' from your snippet
and reveal_type(gitobj.active_branch.name) is str.

So maybe adding type hint to symbolicReference.name has fixed it in main? (Head <- Reference <- SymbolicReference)

@mdantonio
Copy link
Author

$ mypy --warn-return-any active_branch_test.py 
active_branch_test.py:5: note: Revealed type is "git.repo.base.Repo"
active_branch_test.py:6: note: Revealed type is "git.refs.symbolic.SymbolicReference"
active_branch_test.py:7: note: Revealed type is "Any"
active_branch_test.py:8: error: Returning Any from function declared to return "str"
Found 1 error in 1 file (checked 1 source file)

@Yobmod
Copy link
Contributor

Yobmod commented Jul 28, 2021

Thx for checking. Then it is fixed for next release :)

@Byron
Copy link
Member

Byron commented Jul 29, 2021

Thanks everyone for bearing with us. Yesterday after the second issue was filed I yanked 3.1.20, so it looks like that the issues around older python versions and ordered dict is all there is for now.

@Yobmod What do you think about setting up CI to test patch releases as well, to have more probes when determining that the type related changes are actually working for everyone? Typing seems to be a minefield when trying to support as many python versions as we do, and I have a feeling we have to beef up our testing game in order to avoid all these hassles in the future.
My suggestion is to setup CI to test the oldest patch level on each minor python version. If it's not supported by GitHub workflows, we might use pyenv or something similar and implement that ourselves. Maybe there are python actions that do that already though.
Just some thoughts of mine, I am looking forward to learning about yours.

@kasium
Copy link
Contributor

kasium commented Jul 29, 2021

Hi all,

just a short idea: typing imports could be wrapped into if TYPE_CHECKING and then all types must be wrapped into quotes. With this you could avoid these kind of issues like with python 3.7.1.

In addition there is also a checker for flake8: https://github.com/asottile/flake8-typing-imports

foo.py

from typing import OrderedDict

Execute: flake8 --select TYP --min-python-version 3.7.0 foo.py

foo.py:1:1: TYP001 guard import by `if TYPE_CHECKING:`: OrderedDict (not in 3.7.0, 3.7.1)

EDIT: I'm happy to file a PR to add flake8-typing-imports if you think this makes sense

@gaborbernat
Copy link

When running in strict mode one gets failures for the repository not exporting explicitly its properties:

Module "git" does not explicitly export attribute "Git"; implicit reexport disabled

See python/mypy#10198 (comment) for more details.

@Yobmod
Copy link
Contributor

Yobmod commented Aug 2, 2021

Hi all,

just a short idea: typing imports could be wrapped into if TYPE_CHECKING and then all types must be wrapped into quotes. With this you could avoid these kind of issues like with python 3.7.1.

In addition there is also a checker for flake8: https://github.com/asottile/flake8-typing-imports

foo.py

from typing import OrderedDict

Execute: flake8 --select TYP --min-python-version 3.7.0 foo.py

foo.py:1:1: TYP001 guard import by `if TYPE_CHECKING:`: OrderedDict (not in 3.7.0, 3.7.1)

EDIT: I'm happy to file a PR to add flake8-typing-imports if you think this makes sense

We already have a similar library in requirements-dev.txt (flake8-type-checking), but it has too many false positives to use in CI.
So feel free to swap it for flake8-typing-imports and if it works (and once all errors are fixed/ignored) it can be added to test-requirements.txt.

The issue for Orderdict though is that we are subclassing it, so one import has to be runtime available. Maybe we can put the typed versions in the if TYPE_CHECKING block after the runtime import? It will still need py version guards for mypy though? it just wont break at runtime.

@Yobmod
Copy link
Contributor

Yobmod commented Aug 2, 2021

When running in strict mode one gets failures for the repository not exporting explicitly its properties:

Module "git" does not explicitly export attribute "Git"; implicit reexport disabled

See python/mypy#10198 (comment) for more details.

Yep, we have a lot of * imports that rely on implicit-reexport for mypy. For end-users this should not cause problems, but its a known mypy bug: python/mypy#8754.

If someone wants to change all the * imports to match __all__ for each module it will fix it, but otherwise have to wait for the bug to be fixed. Current workaround is if using --strict, also use --implicit-reexport.

@gaborbernat
Copy link

gaborbernat commented Aug 2, 2021

I personally think for reasons explained in python/mypy#10198 (comment) this is not a mypy bug 🤔 and I don't think due to that will be fixed 🤔 At best I'd imagine in py.typed you'd have some information about the package using implicit re-export but you cannot assume all packages will use that. Implicit re-export is a convenient but dangerous feature, and should be IMHO an opt-in not an always on thing.

@kasium
Copy link
Contributor

kasium commented Aug 2, 2021

The issue for Orderdict though is that we are subclassing it, so one import has to be runtime available. Maybe we can put the typed versions in the if TYPE_CHECKING block after the runtime import? It will still need py version guards for mypy though? it just wont break at runtime.

Yes, I guess it will only prevent runtime issues but during type checking time, the imports must work. However, normal users might get more stable releases

So feel free to swap it for flake8-typing-imports and if it works (and once all errors are fixed/ignored) it can be added to test-requirements.txt.

Let me check and open a PR if possible. Please note, that these libraries are doing different things. The current one is more style related, while flake8-typing-imports checks import from typing based on the current python version

@kasium
Copy link
Contributor

kasium commented Aug 2, 2021

So, I opened #1309.
Please note that gitpython says it's compatible with >= 3.6.0 but it's only compatible with > 3.6.1! See the PR for details.

@Yobmod
Copy link
Contributor

Yobmod commented Aug 2, 2021

Seems useful already!

So we have to decide:

  • Drop 3.6 now,

  • or i'll pull out NamedTuple defaults (there are only a couple of uses) for now.

(NoReturn and Deque are available in typing_extensions, so that can be easily fixed.)

I was planning to wait until 3.10 released to drop 3.6.
But I see it had it's final release this June and i have noticed big packages already dropping it (numpy and pandas did last year and therefore most of scientific packages).

@Byron
Copy link
Member

Byron commented Aug 2, 2021

Dropping py 3.6 seems like the path of least resistance.
I suggest to go with that.

@gaborbernat
Copy link

If someone wants to change all the * imports to match __all__ for each module it will fix it, but otherwise have to wait for the bug to be fixed. Current workaround is if using --strict, also use --implicit-reexport.

See python/mypy#8754 (comment), seems this is not a bug in mypy, but a feature and the issue lies in this package, not the type checkers (both mypy and pyright seem to assume that implicit re-export is off).

@Yobmod
Copy link
Contributor

Yobmod commented Aug 2, 2021

If someone wants to change all the * imports to match __all__ for each module it will fix it, but otherwise have to wait for the bug to be fixed. Current workaround is if using --strict, also use --implicit-reexport.

See python/mypy#8754 (comment), seems this is not a bug in mypy, but a feature and the issue lies in this package, not the type checkers (both mypy and pyright seem to assume that implicit re-export is off).

Its fixable here, but there are 156 implicit-reexports errors if its activated in the codebase atm, so it wont be done for the next few releases unless someone volunteers.
I've added implicit_reexport = true to our settings, but I don't know if that will be overriden downstream?

@kasium
Copy link
Contributor

kasium commented Aug 11, 2021

Is there any update how you want to proceed with this?

@Yobmod
Copy link
Contributor

Yobmod commented Sep 9, 2021

@Byron

I think i've done everything I planned for the next release.
3.6 is dropped, and all reported errors in typing fixed and tested against an expanded test matrix. (Github actions only goes as early as 3.7.5 though).

Testpypi uploads fine (using 3.7.3), so ready to go!

@Byron
Copy link
Member

Byron commented Sep 10, 2021

Thanks a lot! Thanks to a 'hickup' this was ultimately released as https://pypi.org/project/GitPython/3.1.23/ .

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

No branches or pull requests

6 participants