diff --git a/msvcrt.pyi b/msvcrt.pyi new file mode 100644 index 0000000..73c309a --- /dev/null +++ b/msvcrt.pyi @@ -0,0 +1,7 @@ +LK_LOCK: int +LK_NBLCK: int +LK_NBRLCK: int +LK_RLCK: int +LK_UNLCK: int + +def locking(file: int, mode: int, lock_length: int) -> int: ... diff --git a/portalocker/exceptions.py b/portalocker/exceptions.py index 0a815b9..0a8594d 100644 --- a/portalocker/exceptions.py +++ b/portalocker/exceptions.py @@ -1,10 +1,18 @@ +import typing + + class BaseLockException(Exception): # Error codes: LOCK_FAILED = 1 - def __init__(self, *args, fh=None, **kwargs): + def __init__( + self, + *args: typing.Any, + fh: typing.Optional[typing.IO] = None, + **kwargs: typing.Any, + ) -> None: self.fh = fh - Exception.__init__(self, *args, **kwargs) + Exception.__init__(self, *args) class LockException(BaseLockException): diff --git a/portalocker/redis.py b/portalocker/redis.py index 9d34b24..08dbd4a 100644 --- a/portalocker/redis.py +++ b/portalocker/redis.py @@ -18,7 +18,7 @@ DEFAULT_THREAD_SLEEP_TIME = 0.1 -class PubSubWorkerThread(client.PubSubWorkerThread): +class PubSubWorkerThread(client.PubSubWorkerThread): # type: ignore def run(self): try: diff --git a/portalocker/utils.py b/portalocker/utils.py index aefe299..b50de3e 100644 --- a/portalocker/utils.py +++ b/portalocker/utils.py @@ -28,7 +28,7 @@ Filename = typing.Union[str, pathlib.Path] -def coalesce(*args, test_value=None): +def coalesce(*args: typing.Any, test_value: typing.Any = None) -> typing.Any: '''Simple coalescing function that returns the first value that is not equal to the `test_value`. Or `None` if no value is valid. Usually this means that the last given value is the default value. @@ -56,7 +56,8 @@ def coalesce(*args, test_value=None): @contextlib.contextmanager -def open_atomic(filename: Filename, binary: bool = True): +def open_atomic(filename: Filename, binary: bool = True) \ + -> typing.Iterator[typing.IO]: '''Open a file for atomic writing. Instead of locking this method allows you to write the entire file and move it to the actual location. Note that this makes the assumption that a rename is atomic on your platform which @@ -129,21 +130,23 @@ def acquire( fail_when_locked: bool = None): return NotImplemented - def _timeout_generator(self, timeout, check_interval): - timeout = coalesce(timeout, self.timeout, 0.0) - check_interval = coalesce(check_interval, self.check_interval, 0.0) + def _timeout_generator(self, timeout: typing.Optional[float], + check_interval: typing.Optional[float]) \ + -> typing.Iterator[int]: + f_timeout = coalesce(timeout, self.timeout, 0.0) + f_check_interval = coalesce(check_interval, self.check_interval, 0.0) yield 0 i = 0 start_time = time.perf_counter() - while start_time + timeout > time.perf_counter(): + while start_time + f_timeout > time.perf_counter(): i += 1 yield i # Take low lock checks into account to stay within the interval since_start_time = time.perf_counter() - start_time - time.sleep(max(0.001, (i * check_interval) - since_start_time)) + time.sleep(max(0.001, (i * f_check_interval) - since_start_time)) @abc.abstractmethod def release(self): @@ -152,8 +155,13 @@ def release(self): def __enter__(self): return self.acquire() - def __exit__(self, type_, value, tb): + def __exit__(self, + exc_type: typing.Optional[typing.Type[BaseException]], + exc_value: typing.Optional[BaseException], + traceback: typing.Any, # Should be typing.TracebackType + ) -> typing.Optional[bool]: self.release() + return None def __delete__(self, instance): instance.release() diff --git a/setup.py b/setup.py index 055a458..8b532ac 100644 --- a/setup.py +++ b/setup.py @@ -128,7 +128,7 @@ def run(self): author_email=about['__email__'], url=about['__url__'], license='PSF', - package_data=dict(portalocker=['py.typed']), + package_data=dict(portalocker=['py.typed', 'msvcrt.pyi']), packages=setuptools.find_packages(exclude=[ 'examples', 'portalocker_tests']), # zip_safe=False, diff --git a/tox.ini b/tox.ini index 0274dfb..84454fa 100644 --- a/tox.ini +++ b/tox.ini @@ -14,6 +14,11 @@ basepython = deps = -e{toxinidir}[tests,redis] commands = python -m pytest {posargs} +[testenv:mypy] +basepython = python3 +deps = mypy +commands = mypy {toxinidir}/portalocker + [testenv:flake8] basepython = python3 deps = flake8