add type annotations

This commit is contained in:
Thomas Grainger 2021-03-21 13:40:34 +00:00
parent 79e00d6b6a
commit 5b33095b00
No known key found for this signature in database
GPG Key ID: E452A1247BAC1A88
6 changed files with 261 additions and 152 deletions

8
mypy.ini Normal file
View File

@ -0,0 +1,8 @@
[mypy]
strict=true
warn_unused_configs=true
show_error_codes=true
[mypy-incremental.tests.*]
allow_untyped_defs=true
check_untyped_defs=false

View File

@ -19,6 +19,8 @@ classifiers =
license = MIT
description = "A small library that versions your Python projects."
long_description = file: README.rst
install_requires =
typing >= 3.7.4.3; python_version < '3.5'
[options]
packages = find:
@ -37,6 +39,9 @@ distutils.setup_keywords =
scripts =
click>=6.0
twisted>=16.4.0
mypy =
%(scripts)s
mypy==0.812
[bdist_wheel]
universal = 1

View File

@ -11,91 +11,43 @@ from __future__ import division, absolute_import
import sys
import warnings
from typing import TYPE_CHECKING, Any, TypeVar, Generic, Union, Optional, Dict
#
# Compat functions
#
if sys.version_info < (3, 0):
_PY3 = False
_T = TypeVar("_T", contravariant=True)
if TYPE_CHECKING:
from typing_extensions import Literal
from distutils.dist import Distribution as _Distribution
def _cmp(a, b): # type: (Any, Any) -> int
return 0
else:
_PY3 = True
_Distribution = object
try:
_cmp = cmp
except NameError:
try:
_cmp = cmp
except NameError:
def _cmp(a, b):
"""
Compare two objects.
def _cmp(a, b): # type: (Any, Any) -> int
"""
Compare two objects.
Returns a negative number if C{a < b}, zero if they are equal, and a
positive number if C{a > b}.
"""
if a < b:
return -1
elif a == b:
return 0
else:
return 1
def _comparable(klass):
"""
Class decorator that ensures support for the special C{__cmp__} method.
On Python 2 this does nothing.
On Python 3, C{__eq__}, C{__lt__}, etc. methods are added to the class,
relying on C{__cmp__} to implement their comparisons.
"""
# On Python 2, __cmp__ will just work, so no need to add extra methods:
if not _PY3:
return klass
def __eq__(self, other):
c = self.__cmp__(other)
if c is NotImplemented:
return c
return c == 0
def __ne__(self, other):
c = self.__cmp__(other)
if c is NotImplemented:
return c
return c != 0
def __lt__(self, other):
c = self.__cmp__(other)
if c is NotImplemented:
return c
return c < 0
def __le__(self, other):
c = self.__cmp__(other)
if c is NotImplemented:
return c
return c <= 0
def __gt__(self, other):
c = self.__cmp__(other)
if c is NotImplemented:
return c
return c > 0
def __ge__(self, other):
c = self.__cmp__(other)
if c is NotImplemented:
return c
return c >= 0
klass.__lt__ = __lt__
klass.__gt__ = __gt__
klass.__le__ = __le__
klass.__ge__ = __ge__
klass.__eq__ = __eq__
klass.__ne__ = __ne__
return klass
Returns a negative number if C{a < b}, zero if they are equal, and a
positive number if C{a > b}.
"""
if a < b:
return -1
elif a == b:
return 0
else:
return 1
#
@ -103,13 +55,12 @@ def _comparable(klass):
#
@_comparable
class _inf(object):
class _Inf(object):
"""
An object that is bigger than all other objects.
"""
def __cmp__(self, other):
def __cmp__(self, other): # type: (object) -> int
"""
@param other: Another object.
@type other: any
@ -121,8 +72,46 @@ class _inf(object):
return 0
return 1
if sys.version_info >= (3, 0):
_inf = _inf()
def __eq__(self, other): # type: (object) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c == 0
def __ne__(self, other): # type: (object) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c != 0
def __lt__(self, other): # type: (object) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c < 0
def __le__(self, other): # type: (object) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c <= 0
def __gt__(self, other): # type: (object) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c > 0
def __ge__(self, other): # type: (object) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c >= 0
_inf = _Inf()
class IncomparableVersions(TypeError):
@ -131,7 +120,6 @@ class IncomparableVersions(TypeError):
"""
@_comparable
class Version(object):
"""
An encapsulation of a version for a project, with support for outputting
@ -143,14 +131,14 @@ class Version(object):
def __init__(
self,
package,
major,
minor,
micro,
release_candidate=None,
prerelease=None,
post=None,
dev=None,
package, # type: str
major, # type: Union[Literal["NEXT"], int]
minor, # type: int
micro, # type: int
release_candidate=None, # type: Optional[int]
prerelease=None, # type: Optional[int]
post=None, # type: Optional[int]
dev=None, # type: Optional[int]
):
"""
@param package: Name of the package that this is a version of.
@ -199,7 +187,7 @@ class Version(object):
self.dev = dev
@property
def prerelease(self):
def prerelease(self): # type: () -> Optional[int]
warnings.warn(
(
"Accessing incremental.Version.prerelease was "
@ -211,7 +199,7 @@ class Version(object):
),
return self.release_candidate
def public(self):
def public(self): # type: () -> str
"""
Return a PEP440-compatible "public" representation of this L{Version}.
@ -246,7 +234,7 @@ class Version(object):
short = public
local = public
def __repr__(self):
def __repr__(self): # type: () -> str
if self.release_candidate is None:
release_candidate = ""
@ -274,10 +262,10 @@ class Version(object):
dev,
)
def __str__(self):
def __str__(self): # type: () -> str
return "[%s, version %s]" % (self.package, self.short())
def __cmp__(self, other):
def __cmp__(self, other): # type: (Version) -> int
"""
Compare two versions, considering major versions, minor versions, micro
versions, then release candidates, then postreleases, then dev
@ -306,12 +294,12 @@ class Version(object):
raise IncomparableVersions("%r != %r" % (self.package, other.package))
if self.major == "NEXT":
major = _inf
major = _inf # type: Union[int, _Inf]
else:
major = self.major
if self.release_candidate is None:
release_candidate = _inf
release_candidate = _inf # type: Union[int, _Inf]
else:
release_candidate = self.release_candidate
@ -321,17 +309,17 @@ class Version(object):
post = self.post
if self.dev is None:
dev = _inf
dev = _inf # type: Union[int, _Inf]
else:
dev = self.dev
if other.major == "NEXT":
othermajor = _inf
othermajor = _inf # type: Union[int, _Inf]
else:
othermajor = other.major
if other.release_candidate is None:
otherrc = _inf
otherrc = _inf # type: Union[int, _Inf]
else:
otherrc = other.release_candidate
@ -341,7 +329,7 @@ class Version(object):
otherpost = other.post
if other.dev is None:
otherdev = _inf
otherdev = _inf # type: Union[int, _Inf]
else:
otherdev = other.dev
@ -351,8 +339,46 @@ class Version(object):
)
return x
if sys.version_info >= (3, 0):
def getVersionString(version):
def __eq__(self, other): # type: (Any) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c == 0
def __ne__(self, other): # type: (Any) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c != 0
def __lt__(self, other): # type: (Version) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c < 0
def __le__(self, other): # type: (Version) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c <= 0
def __gt__(self, other): # type: (Version) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c > 0
def __ge__(self, other): # type: (Version) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c >= 0
def getVersionString(version): # type: (Version) -> str
"""
Get a friendly string for the given version object.
@ -363,7 +389,7 @@ def getVersionString(version):
return result
def _get_version(dist, keyword, value):
def _get_version(dist, keyword, value): # type: (_Distribution, object, object) -> None
"""
Get the version from the package listed in the Distribution.
"""
@ -375,12 +401,12 @@ def _get_version(dist, keyword, value):
sp_command = build_py.build_py(dist)
sp_command.finalize_options()
for item in sp_command.find_all_modules():
for item in sp_command.find_all_modules(): # type: ignore[attr-defined]
if item[1] == "_version":
version_file = {}
version_file = {} # type: Dict[str, Version]
with open(item[2]) as f:
exec(f.read(), version_file)
exec (f.read(), version_file)
dist.metadata.version = version_file["__version__"].public()
return None
@ -391,7 +417,7 @@ def _get_version(dist, keyword, value):
from ._version import __version__ # noqa
def _setuptools_version():
def _setuptools_version(): # type: () -> str
return __version__.public()

0
src/incremental/py.typed Normal file
View File

View File

@ -6,9 +6,51 @@ from __future__ import absolute_import, division, print_function
import click
import os
import datetime
from typing import TYPE_CHECKING, Dict, Optional, Callable, Union, Iterable
from incremental import Version
from twisted.python.filepath import FilePath
if TYPE_CHECKING:
from typing_extensions import Protocol
class _ReadableWritable(Protocol):
def read(self): # type: () -> bytes
pass
def write(self, v): # type: (bytes) -> object
pass
def __enter__(self): # type: () -> _ReadableWritable
pass
def __exit__(self, *args, **kwargs): # type: (object, object) -> Optional[bool]
pass
class FilePath(object):
def __init__(self, path): # type: (str) -> None
self.path = path
def child(self, v): # type: (str) -> FilePath
pass
def isdir(self): # type: () -> bool
pass
def isfile(self): # type: () -> bool
pass
def getContent(self): # type: () -> bytes
pass
def open(self, mode): # type: (str) -> _ReadableWritable
pass
def walk(self): # type: () -> Iterable[FilePath]
pass
else:
from twisted.python.filepath import FilePath
_VERSIONPY_TEMPLATE = '''"""
Provides {package} version information.
@ -26,7 +68,7 @@ __all__ = ["__version__"]
_YEAR_START = 2000
def _findPath(path, package):
def _findPath(path, package): # type: (str, str) -> FilePath
cwd = FilePath(path)
@ -48,28 +90,28 @@ def _findPath(path, package):
)
def _existing_version(path):
version_info = {}
def _existing_version(path): # type: (FilePath) -> Version
version_info = {} # type: Dict[str, Version]
with path.child("_version.py").open("r") as f:
exec(f.read(), version_info)
exec (f.read(), version_info)
return version_info["__version__"]
def _run(
package,
path,
newversion,
patch,
rc,
post,
dev,
create,
_date=None,
_getcwd=None,
_print=print,
):
package, # type: str
path, # type: Optional[str]
newversion, # type: Optional[str]
patch, # type: bool
rc, # type: bool
post, # type: bool
dev, # type: bool
create, # type: bool
_date=None, # type: Optional[datetime.date]
_getcwd=None, # type: Optional[Callable[[], str]]
_print=print, # type: Callable[[object], object]
): # type: (...) -> None
if not _getcwd:
_getcwd = os.getcwd
@ -77,13 +119,10 @@ def _run(
if not _date:
_date = datetime.date.today()
if type(package) != str:
if not TYPE_CHECKING and type(package) != str:
package = package.encode("utf8")
if not path:
path = _findPath(_getcwd(), package)
else:
path = FilePath(path)
_path = FilePath(path) if path else _findPath(_getcwd(), package)
if (
newversion
@ -117,22 +156,31 @@ def _run(
if newversion:
from pkg_resources import parse_version
existing = _existing_version(path)
st_version = parse_version(newversion)._version
existing = _existing_version(_path)
st_version = parse_version(newversion)._version # type: ignore[attr-defined]
release = list(st_version.release)
minor = 0
micro = 0
if len(release) == 1:
release.append(0)
if len(release) == 2:
release.append(0)
(major,) = release
elif len(release) == 2:
(
major,
minor,
) = release
else:
major, minor, micro = release
v = Version(
package,
*release,
major,
minor,
micro,
release_candidate=st_version.pre[1] if st_version.pre else None,
post=st_version.post[1] if st_version.post else None,
dev=st_version.dev[1] if st_version.dev else None
dev=st_version.dev[1] if st_version.dev else None,
)
elif create:
@ -140,7 +188,7 @@ def _run(
existing = v
elif rc and not patch:
existing = _existing_version(path)
existing = _existing_version(_path)
if existing.release_candidate:
v = Version(
@ -154,16 +202,17 @@ def _run(
v = Version(package, _date.year - _YEAR_START, _date.month, 0, 1)
elif patch:
if rc:
rc = 1
else:
rc = None
existing = _existing_version(path)
v = Version(package, existing.major, existing.minor, existing.micro + 1, rc)
existing = _existing_version(_path)
v = Version(
package,
existing.major,
existing.minor,
existing.micro + 1,
1 if rc else None,
)
elif post:
existing = _existing_version(path)
existing = _existing_version(_path)
if existing.post is None:
_post = 0
@ -173,7 +222,7 @@ def _run(
v = Version(package, existing.major, existing.minor, existing.micro, post=_post)
elif dev:
existing = _existing_version(path)
existing = _existing_version(_path)
if existing.dev is None:
_dev = 0
@ -190,7 +239,7 @@ def _run(
)
else:
existing = _existing_version(path)
existing = _existing_version(_path)
if existing.release_candidate:
v = Version(package, existing.major, existing.minor, existing.micro)
@ -208,7 +257,7 @@ def _run(
_print("Updating codebase to %s" % (v.public()))
for x in path.walk():
for x in _path.walk():
if not x.isfile():
continue
@ -241,8 +290,8 @@ def _run(
with x.open("w") as f:
f.write(content)
_print("Updating %s/_version.py" % (path.path))
with path.child("_version.py").open("w") as f:
_print("Updating %s/_version.py" % (_path.path))
with _path.child("_version.py").open("w") as f:
f.write(
(
_VERSIONPY_TEMPLATE.format(package=package, version_repr=version_repr)
@ -259,8 +308,26 @@ def _run(
@click.option("--post", is_flag=True)
@click.option("--dev", is_flag=True)
@click.option("--create", is_flag=True)
def run(*args, **kwargs):
return _run(*args, **kwargs)
def run(
package, # type: str
path, # type: Optional[str]
newversion, # type: Optional[str]
patch, # type: bool
rc, # type: bool
post, # type: bool
dev, # type: bool
create, # type: bool
): # type: (...) -> None
return _run(
package=package,
path=path,
newversion=newversion,
patch=patch,
rc=rc,
post=post,
dev=dev,
create=create,
)
if __name__ == "__main__": # pragma: no cover

View File

@ -20,6 +20,9 @@ deps =
check-manifest: check-manifest
black: black
black-reformat: black
extras =
mypy: mypy
tests: scripts
setenv =
black: BLACK_LINT_ARGS=--check
@ -33,7 +36,6 @@ commands =
check-manifest: check-manifest -v
tests: pip install incremental[scripts]
tests: coverage --version
tests: {envbindir}/trial --version
tests: coverage erase
@ -43,6 +45,7 @@ commands =
tests: coverage combine
tests: coverage report
tests: coverage html
mypy: mypy src
[testenv:black-reformat]
setenv =