From 3934e4379e6445720fe991bd0263936a9542601b Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Thu, 8 Dec 2022 19:28:02 +0100 Subject: first try with mypy It would be nice to gradually improve the typing situation in Fietsboek. At least the parts that do not do heavy metaprogramming should have types. For most of the API, we already have types in the doc strings, so those could be removed then. --- .mypy.ini | 14 ++++++++++++++ fietsboek/config.py | 2 +- fietsboek/pages.py | 15 +++++++++------ fietsboek/updater/__init__.py | 8 +++++--- fietsboek/util.py | 2 +- fietsboek/views/browse.py | 5 +++-- fietsboek/views/tileproxy.py | 4 ++-- tox.ini | 14 +++++++++++++- 8 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 .mypy.ini diff --git a/.mypy.ini b/.mypy.ini new file mode 100644 index 0000000..79f0c44 --- /dev/null +++ b/.mypy.ini @@ -0,0 +1,14 @@ +[mypy] +follow_imports = silent +check_untyped_defs = True +allow_redefinition = True +exclude = fietsboek/updater/scripts/.+\.py + +[mypy-pyramid.*] +ignore_missing_imports = True + +[mypy-sqlalchemy.*] +ignore_missing_imports = True + +[mypy-zope.*] +ignore_missing_imports = True diff --git a/fietsboek/config.py b/fietsboek/config.py index 05fd4f6..74dc4d8 100644 --- a/fietsboek/config.py +++ b/fietsboek/config.py @@ -142,7 +142,7 @@ class Config(BaseModel): session_key: str """Session key.""" - available_locales: PyramidList = ["en", "de"] + available_locales: PyramidList = PyramidList(["en", "de"]) """Available locales.""" email_from: str = Field(alias="email.from") diff --git a/fietsboek/pages.py b/fietsboek/pages.py index e94a493..d8fd859 100644 --- a/fietsboek/pages.py +++ b/fietsboek/pages.py @@ -1,6 +1,7 @@ """Module containing logic to support "static" pages.""" import enum import re +from typing import List, Optional import markdown @@ -91,20 +92,21 @@ class Page: parser = markdown.Markdown(extensions=["meta"]) content = parser.convert(text) - title = parser.Meta.get('title', [''])[0] + title = parser.Meta.get('title', [''])[0] # type: ignore if not title: raise PageException("Missing `title`") - link_name = parser.Meta.get('link-name', [''])[0] + link_name = parser.Meta.get('link-name', [''])[0] # type: ignore if not link_name: raise PageException("Missing `link-name`") - slug = parser.Meta.get('slug', [''])[0] + slug = parser.Meta.get('slug', [''])[0] # type: ignore if not slug: raise PageException("Missing `slug`") + locale_filter: Optional[List[re.Pattern]] try: - locale_filter = list(map(re.compile, parser.Meta.get('locale', []))) + locale_filter = list(map(re.compile, parser.Meta.get('locale', []))) # type: ignore except re.error as exc: raise PageException("Invalid locale regex") from exc if not locale_filter: @@ -115,12 +117,13 @@ class Page: 'logged-out': UserFilter.LOGGED_OUT, 'everyone': UserFilter.EVERYONE, } - user_filter = filter_map.get(parser.Meta.get('show-to', ['everyone'])[0].lower()) + user_filter = filter_map.get( + parser.Meta.get('show-to', ['everyone'])[0].lower()) # type: ignore if user_filter is None: raise PageException("Invalid `show-to` filter") try: - menu_index = int(parser.Meta.get('index', ['0'])[0]) + menu_index = int(parser.Meta.get('index', ['0'])[0]) # type: ignore except ValueError as exc: raise PageException("Invalid value for `index`") from exc diff --git a/fietsboek/updater/__init__.py b/fietsboek/updater/__init__.py index a5bcf0e..d336daf 100644 --- a/fietsboek/updater/__init__.py +++ b/fietsboek/updater/__init__.py @@ -5,6 +5,7 @@ import random import string import importlib.util from pathlib import Path +from typing import List # Compat for Python < 3.9 import importlib_resources @@ -151,7 +152,7 @@ class Updater: def _make_schedule(self, wanted, dependencies): wanted = set(wanted) - queue = [] + queue: List[str] = [] while wanted: next_updates = { update @@ -233,7 +234,7 @@ class Updater: current_alembic = context.get_current_heads() LOGGER.debug("Found alembic versions: %s", current_alembic) assert len(current_alembic) == 1 - current_alembic = current_alembic[0] + current_alembic = current_alembic[0] # type: ignore loader = jinja2.DictLoader({"revision.py": TEMPLATE}) env = jinja2.Environment(loader=loader, autoescape=False) @@ -291,7 +292,8 @@ class UpdateScript: def __init__(self, source, name): self.name = name spec = importlib.util.spec_from_loader(f"{__name__}.{name}", None) - self.module = importlib.util.module_from_spec(spec) + self.module = importlib.util.module_from_spec(spec) # type: ignore + assert self.module exec(source, self.module.__dict__) # pylint: disable=exec-used def __repr__(self): diff --git a/fietsboek/util.py b/fietsboek/util.py index 71f5d16..a500d1e 100644 --- a/fietsboek/util.py +++ b/fietsboek/util.py @@ -58,7 +58,7 @@ def safe_markdown(md_source): :return: The safe HTML transformed version. :rtype: Markup """ - html = markdown.markdown(md_source, output_format='html5') + html = markdown.markdown(md_source, output_format='html') html = bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES) return Markup(html) diff --git a/fietsboek/views/browse.py b/fietsboek/views/browse.py index c01d4f6..986ae5e 100644 --- a/fietsboek/views/browse.py +++ b/fietsboek/views/browse.py @@ -1,6 +1,7 @@ """Views for browsing all tracks.""" import datetime from io import RawIOBase +from typing import List from zipfile import ZipFile, ZIP_DEFLATED from pyramid.view import view_config @@ -212,7 +213,7 @@ class FilterCollection(Filter): :rtype: FilterCollection """ # pylint: disable=singleton-comparison - filters = [] + filters: List[Filter] = [] if request.params.get('search-terms'): term = request.params.get('search-terms').strip() filters.append(SearchFilter([term])) @@ -341,7 +342,7 @@ def archive(request): def generate(): try: stream = Stream() - with ZipFile(stream, "w", ZIP_DEFLATED) as zipfile: + with ZipFile(stream, "w", ZIP_DEFLATED) as zipfile: # type: ignore for track in tracks: zipfile.writestr(f"track_{track.id}.gpx", track.gpx_data) yield stream.readall() diff --git a/fietsboek/views/tileproxy.py b/fietsboek/views/tileproxy.py index 3e2abc1..f484caf 100644 --- a/fietsboek/views/tileproxy.py +++ b/fietsboek/views/tileproxy.py @@ -9,7 +9,7 @@ Additionally, this protects the users' IP, as only fietsboek can see it. import datetime import random import logging -from typing import NamedTuple +from typing import NamedTuple, Optional from itertools import chain from pyramid.view import view_config @@ -33,7 +33,7 @@ class TileSource(NamedTuple): """URL with placeholders.""" layer_type: LayerType """Type of this layer.""" - zoom: int + zoom: Optional[int] """Max zoom of this layer.""" access: LayerAccess """Access restrictions to use this layer.""" diff --git a/tox.ini b/tox.ini index 66e9870..9f69f7e 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ per-file-ignores = fietsboek/models/__init__.py:F401 [tox] -envlist = python,pylint,pylint-tests,flake8 +envlist = python,pylint,pylint-tests,flake8,mypy isolated_build = true [testenv] @@ -44,3 +44,15 @@ allowlist_externals = make changedir={toxinidir}{/}doc commands = make html + +[testenv:mypy] +deps = + mypy + types-Markdown + types-bleach + types-babel + types-redis + types-requests +usedevelop = true +commands = + mypy fietsboek -- cgit v1.2.3