diff --git a/.travis.yml b/.travis.yml index f57f755..85941e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,8 @@ matrix: env: TOXENV=pypy3 CC=clang - python: 3.8 env: TOXENV=pep8 + - python: 3.8 + env: TOXENV=mypy - env: TOXENV=packaging - python: 3.8 arch: arm64 diff --git a/MANIFEST.in b/MANIFEST.in index 5a6604a..a4ec24c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,6 +8,6 @@ include src/build_bcrypt.py recursive-include src/_csrc * recursive-include tests *.py -exclude requirements.txt release.py .travis.yml +exclude requirements.txt release.py .travis.yml mypy.ini prune .travis diff --git a/README.rst b/README.rst index 2fa6044..29f1e2d 100644 --- a/README.rst +++ b/README.rst @@ -56,6 +56,8 @@ Changelog Unreleased ---------- +* Add typehints for library functions + * Dropped support for Python versions less than 3.6 (2.7, 3.4, 3.5). 3.1.7 diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..10317dd --- /dev/null +++ b/mypy.ini @@ -0,0 +1,32 @@ +[mypy] +show_column_numbers=True +pretty=True + +disallow_any_unimported=True +# _bcrypt usage will result in any exprs +disallow_any_expr=False +disallow_any_decorated=True +disallow_any_explicit=True +disallow_any_generics=True +disallow_subclassing_any=True + +disallow_untyped_calls=True +disallow_untyped_defs=True +disallow_incomplete_defs=True +check_untyped_defs=True +disallow_untyped_decorators=True + +no_implicit_optional=True +strict_optional=True + +warn_redundant_casts=True +warn_unused_ignores=True +warn_no_return=True +# _bcrypt is untyped so all calls involving it will be Any +warn_return_any=False +# keep backwards compatibility for users not using static type checking +warn_unreachable=False + +strict_equality=True + +ignore_missing_imports=False diff --git a/setup.py b/setup.py index 7cefac2..7c2588e 100644 --- a/setup.py +++ b/setup.py @@ -208,10 +208,11 @@ setup( author_email=__about__["__email__"], python_requires=">=3.6", install_requires=[CFFI_DEPENDENCY, SIX_DEPENDENCY], - extras_require={"tests": ["pytest>=3.2.1,!=3.3.0"]}, + extras_require={"tests": ["pytest>=3.2.1,!=3.3.0"], "typecheck": ["mypy"]}, tests_require=["pytest>=3.2.1,!=3.3.0"], package_dir={"": "src"}, packages=["bcrypt"], + package_data={"bcrypt": ["py.typed"]}, zip_safe=False, classifiers=[ "Development Status :: 5 - Production/Stable", diff --git a/src/bcrypt/__init__.py b/src/bcrypt/__init__.py index 9679a42..0a8cc05 100644 --- a/src/bcrypt/__init__.py +++ b/src/bcrypt/__init__.py @@ -22,7 +22,7 @@ import warnings import six -from . import _bcrypt +from . import _bcrypt # type: ignore from .__about__ import ( __author__, __copyright__, @@ -54,7 +54,7 @@ __all__ = [ _normalize_re = re.compile(br"^\$2y\$") -def gensalt(rounds=12, prefix=b"2b"): +def gensalt(rounds: int = 12, prefix: bytes = b"2b") -> bytes: if prefix not in (b"2a", b"2b"): raise ValueError("Supported prefixes are b'2a' or b'2b'") @@ -75,7 +75,7 @@ def gensalt(rounds=12, prefix=b"2b"): ) -def hashpw(password, salt): +def hashpw(password: bytes, salt: bytes) -> bytes: if isinstance(password, six.text_type) or isinstance(salt, six.text_type): raise TypeError("Unicode-objects must be encoded before hashing") @@ -113,7 +113,7 @@ def hashpw(password, salt): return original_salt[:4] + _bcrypt.ffi.string(hashed)[4:] -def checkpw(password, hashed_password): +def checkpw(password: bytes, hashed_password: bytes) -> bool: if isinstance(password, six.text_type) or isinstance( hashed_password, six.text_type ): @@ -132,7 +132,13 @@ def checkpw(password, hashed_password): return _bcrypt.lib.timingsafe_bcmp(ret, hashed_password, len(ret)) == 0 -def kdf(password, salt, desired_key_bytes, rounds, ignore_few_rounds=False): +def kdf( + password: bytes, + salt: bytes, + desired_key_bytes: int, + rounds: int, + ignore_few_rounds: bool = False, +) -> bytes: if isinstance(password, six.text_type) or isinstance(salt, six.text_type): raise TypeError("Unicode-objects must be encoded before hashing") @@ -167,6 +173,6 @@ def kdf(password, salt, desired_key_bytes, rounds, ignore_few_rounds=False): return _bcrypt.ffi.buffer(key, desired_key_bytes)[:] -def _bcrypt_assert(ok): +def _bcrypt_assert(ok: bool) -> None: if not ok: raise SystemError("bcrypt assertion failed") diff --git a/src/bcrypt/py.typed b/src/bcrypt/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tox.ini b/tox.ini index 3438053..717c8a0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = pypy3,py36,py37,py38,pep8,packaging +envlist = pypy3,py36,py37,py38,pep8,packaging,mypy isolated_build = True [testenv] @@ -21,6 +21,12 @@ commands = flake8 . black --check . +[testenv:mypy] +deps = + mypy +commands = + mypy src/bcrypt + [testenv:packaging] deps = check-manifest