From 73e42f81011ccb8aac6a4f73ab5ad2a83098492e Mon Sep 17 00:00:00 2001 From: Unrud Date: Sun, 4 Oct 2020 15:13:01 +0200 Subject: [PATCH] Enable static type checking --- .gitignore | 1 + radicale/app/__init__.py | 2 +- radicale/config.py | 3 ++- radicale/pathutils.py | 5 ++++- radicale/server.py | 8 ++++++-- radicale/tests/test_base.py | 4 +++- radicale/tests/test_server.py | 9 ++++++++- setup.cfg | 5 +++++ setup.py | 6 ++++++ 9 files changed, 36 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index d61615a..5a0363a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ coverage.xml .coverage .coverage.* .eggs +.mypy_cache .project .pydevproject .settings diff --git a/radicale/app/__init__.py b/radicale/app/__init__.py index 21dcd11..4bf1e9a 100644 --- a/radicale/app/__init__.py +++ b/radicale/app/__init__.py @@ -58,7 +58,7 @@ from radicale.log import logger # WORKAROUND: https://github.com/tiran/defusedxml/issues/54 import defusedxml.ElementTree as DefusedET # isort: skip -sys.modules["xml.etree"].ElementTree = ET +sys.modules["xml.etree"].ElementTree = ET # type: ignore[attr-defined] VERSION = pkg_resources.get_distribution("radicale").version diff --git a/radicale/config.py b/radicale/config.py index abb3309..880f2a5 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -31,6 +31,7 @@ import os import string from collections import OrderedDict from configparser import RawConfigParser +from typing import Any, ClassVar from radicale import auth, rights, storage, web @@ -285,7 +286,7 @@ def load(paths=()): class Configuration: - SOURCE_MISSING = {} + SOURCE_MISSING: ClassVar[Any] = {} def __init__(self, schema): """Initialize configuration. diff --git a/radicale/pathutils.py b/radicale/pathutils.py index 400a604..7a2e675 100644 --- a/radicale/pathutils.py +++ b/radicale/pathutils.py @@ -27,6 +27,7 @@ import posixpath import sys import threading from tempfile import TemporaryDirectory +from typing import Type, Union if os.name == "nt": import ctypes @@ -34,6 +35,7 @@ if os.name == "nt": import msvcrt LOCKFILE_EXCLUSIVE_LOCK = 2 + ULONG_PTR: Union[Type[ctypes.c_uint32], Type[ctypes.c_uint64]] if ctypes.sizeof(ctypes.c_void_p) == 4: ULONG_PTR = ctypes.c_uint32 else: @@ -47,7 +49,8 @@ if os.name == "nt": ("offset_high", ctypes.wintypes.DWORD), ("h_event", ctypes.wintypes.HANDLE)] - kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32 = ctypes.WinDLL( # type: ignore[attr-defined] + "kernel32", use_last_error=True) lock_file_ex = kernel32.LockFileEx lock_file_ex.argtypes = [ ctypes.wintypes.HANDLE, diff --git a/radicale/server.py b/radicale/server.py index 97f6afe..ceeda83 100644 --- a/radicale/server.py +++ b/radicale/server.py @@ -30,21 +30,25 @@ import socketserver import ssl import sys import wsgiref.simple_server +from typing import MutableMapping from urllib.parse import unquote from radicale import Application, config from radicale.log import logger +COMPAT_EAI_ADDRFAMILY: int if hasattr(socket, "EAI_ADDRFAMILY"): - COMPAT_EAI_ADDRFAMILY = socket.EAI_ADDRFAMILY + COMPAT_EAI_ADDRFAMILY = socket.EAI_ADDRFAMILY # type: ignore[attr-defined] elif hasattr(socket, "EAI_NONAME"): # Windows and BSD don't have a special error code for this COMPAT_EAI_ADDRFAMILY = socket.EAI_NONAME +COMPAT_EAI_NODATA: int if hasattr(socket, "EAI_NODATA"): COMPAT_EAI_NODATA = socket.EAI_NODATA elif hasattr(socket, "EAI_NONAME"): # Windows and BSD don't have a special error code for this COMPAT_EAI_NODATA = socket.EAI_NONAME +COMPAT_IPPROTO_IPV6: int if hasattr(socket, "IPPROTO_IPV6"): COMPAT_IPPROTO_IPV6 = socket.IPPROTO_IPV6 elif os.name == "nt": @@ -155,7 +159,7 @@ class ParallelHTTPSServer(ParallelHTTPServer): class ServerHandler(wsgiref.simple_server.ServerHandler): # Don't pollute WSGI environ with OS environment - os_environ = {} + os_environ: MutableMapping[str, str] = {} def log_exception(self, exc_info): logger.error("An exception occurred during request: %s", diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py index 5aef849..e8ad347 100644 --- a/radicale/tests/test_base.py +++ b/radicale/tests/test_base.py @@ -25,6 +25,7 @@ import posixpath import shutil import sys import tempfile +from typing import Any, ClassVar import defusedxml.ElementTree as DefusedET import pytest @@ -1549,7 +1550,8 @@ class BaseRequestsMixIn: class BaseFileSystemTest(BaseTest): """Base class for filesystem backend tests.""" - storage_type = None + + storage_type: ClassVar[Any] def setup(self): self.configuration = config.load() diff --git a/radicale/tests/test_server.py b/radicale/tests/test_server.py index 90beefd..72ff52b 100644 --- a/radicale/tests/test_server.py +++ b/radicale/tests/test_server.py @@ -41,10 +41,17 @@ from radicale.tests.helpers import configuration_to_dict, get_file_path class DisabledRedirectHandler(request.HTTPRedirectHandler): + def http_error_301(self, req, fp, code, msg, headers): + raise HTTPError(req.full_url, code, msg, headers, fp) + def http_error_302(self, req, fp, code, msg, headers): raise HTTPError(req.full_url, code, msg, headers, fp) - http_error_301 = http_error_303 = http_error_307 = http_error_302 + def http_error_303(self, req, fp, code, msg, headers): + raise HTTPError(req.full_url, code, msg, headers, fp) + + def http_error_307(self, req, fp, code, msg, headers): + raise HTTPError(req.full_url, code, msg, headers, fp) class TestBaseServerRequests(BaseTest): diff --git a/setup.cfg b/setup.cfg index 7c2b51d..ae23eb4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,6 +5,7 @@ test = pytest python-tag = py3 [tool:pytest] +# More options are set in `setup.py` via environment variable `PYTEST_ADDOPTS` addopts = --flake8 --isort --cov --cov-report=term --cov-report=xml -r s norecursedirs = dist .cache .git build Radicale.egg-info .eggs venv @@ -15,6 +16,10 @@ known_third_party = defusedxml,passlib,pkg_resources,pytest,vobject [flake8] extend-ignore = H +[mypy] +ignore_missing_imports = True +show_error_codes = True + [coverage:run] branch = True source = radicale diff --git a/setup.py b/setup.py index b6786e9..55a61c0 100755 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ For further information, please visit the `Radicale Website """ +import os import sys from setuptools import find_packages, setup @@ -52,6 +53,11 @@ needs_pytest = {"pytest", "test", "ptr"}.intersection(sys.argv) pytest_runner = ["pytest-runner"] if needs_pytest else [] tests_require = ["pytest-runner", "pytest", "pytest-cov", "pytest-flake8", "pytest-isort", "waitress"] +os.environ["PYTEST_ADDOPTS"] = os.environ.get("PYTEST_ADDOPTS", "") +# Mypy only supports CPython +if sys.implementation.name == "cpython": + tests_require.extend(["pytest-mypy", "types-setuptools"]) + os.environ["PYTEST_ADDOPTS"] += " --mypy" setup( name="Radicale",