aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Schadt <kingdread@gmx.de>2022-12-08 20:41:12 +0100
committerDaniel Schadt <kingdread@gmx.de>2022-12-08 20:41:12 +0100
commitc7d80bf42c4a43b504a2ce80ae0f4501007b748f (patch)
tree9e2e1e2a8f7c67ce763002b321768d7d6021ea74
parent45bbf639ac180af3270edeac97901aa5382f0939 (diff)
downloadfietsboek-c7d80bf42c4a43b504a2ce80ae0f4501007b748f.tar.gz
fietsboek-c7d80bf42c4a43b504a2ce80ae0f4501007b748f.tar.bz2
fietsboek-c7d80bf42c4a43b504a2ce80ae0f4501007b748f.zip
type hints for fietsboek.util
-rw-r--r--.mypy.ini3
-rw-r--r--fietsboek/util.py66
-rw-r--r--fietsboek/views/upload.py3
3 files changed, 29 insertions, 43 deletions
diff --git a/.mypy.ini b/.mypy.ini
index 79f0c44..ed220e3 100644
--- a/.mypy.ini
+++ b/.mypy.ini
@@ -10,5 +10,8 @@ ignore_missing_imports = True
[mypy-sqlalchemy.*]
ignore_missing_imports = True
+[mypy-webob.*]
+ignore_missing_imports = True
+
[mypy-zope.*]
ignore_missing_imports = True
diff --git a/fietsboek/util.py b/fietsboek/util.py
index a500d1e..e4a66cf 100644
--- a/fietsboek/util.py
+++ b/fietsboek/util.py
@@ -4,6 +4,7 @@ import re
import os
import unicodedata
import secrets
+from typing import Optional
# Compat for Python < 3.9
import importlib_resources
@@ -11,9 +12,12 @@ import babel
import markdown
import bleach
import gpxpy
+import webob
+import sqlalchemy
from pyramid.i18n import TranslationString as _
from pyramid.httpexceptions import HTTPBadRequest
+from pyramid.request import Request
from markupsafe import Markup
from sqlalchemy import select
@@ -47,23 +51,21 @@ _windows_device_files = (
)
-def safe_markdown(md_source):
+def safe_markdown(md_source: str) -> Markup:
"""Transform a markdown document into a safe HTML document.
This uses ``markdown`` to first parse the markdown source into HTML, and
then ``bleach`` to strip any disallowed HTML tags.
:param md_source: The markdown source.
- :type md_source: str
:return: The safe HTML transformed version.
- :rtype: Markup
"""
html = markdown.markdown(md_source, output_format='html')
html = bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES)
return Markup(html)
-def fix_iso_timestamp(timestamp):
+def fix_iso_timestamp(timestamp: str) -> str:
"""Fixes an ISO timestamp to make it parseable by
:meth:`datetime.datetime.fromisoformat`.
@@ -71,24 +73,21 @@ def fix_iso_timestamp(timestamp):
it with '+00:00'.
:param timestamp: The timestamp to fix.
- :type timestamp: str
:return: The fixed timestamp.
- :rtype: str
"""
if timestamp.endswith('Z'):
return timestamp[:-1] + '+00:00'
return timestamp
-def round_timedelta_to_multiple(value, multiples):
+def round_timedelta_to_multiple(
+ value: datetime.timedelta, multiples: datetime.timedelta
+) -> datetime.timedelta:
"""Round the timedelta `value` to be a multiple of `multiples`.
:param value: The value to be rounded.
- :type value: datetime.timedelta
:param multiples: The size of each multiple.
- :type multiples: datetime.timedelta
:return: The rounded value.
- :rtype: datetime.timedelta
"""
lower = value.total_seconds() // multiples.total_seconds() * multiples.total_seconds()
second_offset = value.total_seconds() - lower
@@ -99,16 +98,14 @@ def round_timedelta_to_multiple(value, multiples):
return datetime.timedelta(seconds=lower) + multiples
-def guess_gpx_timezone(gpx):
+def guess_gpx_timezone(gpx: gpxpy.gpx.GPX) -> datetime.tzinfo:
"""Guess which timezone the GPX file was recorded in.
This looks at a few timestamps to see if they have timezone information
attached, including some known GPX extensions.
:param gpx: The parsed GPX file to analyse.
- :type gpx: gpxpy.GPX
:return: The timezone information.
- :rtype: datetime.timezone
"""
time_bounds = gpx.get_time_bounds()
times = [
@@ -152,7 +149,7 @@ def guess_gpx_timezone(gpx):
return datetime.timezone.utc
-def tour_metadata(gpx_data):
+def tour_metadata(gpx_data: str) -> dict:
"""Calculate the metadata of the tour.
Returns a dict with ``length``, ``uphill``, ``downhill``, ``moving_time``,
@@ -160,9 +157,7 @@ def tour_metadata(gpx_data):
``end_time``.
:param gpx_data: The GPX data of the tour.
- :type gpx_data: str
:return: A dictionary with the computed values.
- :rtype: dict
"""
gpx = gpxpy.parse(gpx_data)
timezone = guess_gpx_timezone(gpx)
@@ -186,46 +181,44 @@ def tour_metadata(gpx_data):
}
-def mps_to_kph(mps):
+def mps_to_kph(mps: float) -> float:
"""Converts meters/second to kilometers/hour.
:param mps: Input meters/second.
- :type mps: float
:return: The converted km/h value.
- :rtype: float
"""
return mps / 1000 * 60 * 60
-def month_name(request, month):
+def month_name(request: Request, month: int) -> str:
"""Returns the localized name for the month with the given number.
:param request: The pyramid request.
- :type request: pyramid.request.Request
:param month: Number of the month, 1 = January.
- :type month: int
:return: The localized month name.
- :rtype: str
"""
assert 1 <= month <= 12
locale = babel.Locale.parse(request.localizer.locale_name)
return locale.months["stand-alone"]["wide"][month]
-def random_link_secret(nbytes=20):
+def random_link_secret(nbytes: int = 20) -> str:
"""Safely generates a secret suitable for the link share.
The returned string consists of characters that are safe to use in a URL.
:param nbytes: Number of random bytes to use.
- :type nbytes: int
:return: A randomly drawn string.
- :rtype: str
"""
return secrets.token_urlsafe(nbytes)
-def retrieve_multiple(dbsession, model, params, name):
+def retrieve_multiple(
+ dbsession: "sqlalchemy.orm.session.Session",
+ model: type,
+ params: "webob.multidict.NestedMultiDict",
+ name: str,
+) -> list:
"""Parses a reply to retrieve multiple database objects.
This is usable for arrays sent by HTML forms, for example to retrieve all
@@ -237,15 +230,10 @@ def retrieve_multiple(dbsession, model, params, name):
:raises pyramid.httpexceptions.HTTPBadRequest: If an object could not be
found.
:param dbsession: The database session.
- :type dbsession: sqlalchemy.orm.session.Session
:param model: The model class to retrieve.
- :type model: class
:param params: The form parameters.
- :type params: webob.multidict.NestedMultiDict
:param name: Name of the parameter to look for.
- :type name: str
:return: A list of elements found.
- :rtype: list[model]
"""
objects = []
for obj_id in params.getall(name):
@@ -259,7 +247,7 @@ def retrieve_multiple(dbsession, model, params, name):
return objects
-def check_password_constraints(password, repeat_password=None):
+def check_password_constraints(password: str, repeat_password: Optional[str] = None):
"""Verifies that the password constraints match for the given password.
This is usually also verified client-side, but for people that bypass the
@@ -273,9 +261,7 @@ def check_password_constraints(password, repeat_password=None):
:class:`~pyramid.i18n.TranslationString` with the message of why the
verification failed.
:param password: The password which to verify.
- :type password: str
:param repeat_password: The password repeat.
- :type repeat_password: str
"""
if repeat_password is not None:
if repeat_password != password:
@@ -284,7 +270,7 @@ def check_password_constraints(password, repeat_password=None):
raise ValueError(_("password_constraint.length"))
-def read_localized_resource(locale_name, path, raise_on_error=False):
+def read_localized_resource(locale_name: str, path: str, raise_on_error: bool = False) -> str:
"""Reads a localized resource.
Localized resources are located in the ``fietsboek/locale/**`` directory.
@@ -293,13 +279,11 @@ def read_localized_resource(locale_name, path, raise_on_error=False):
If the resource could not be found, a placeholder string is returned instead.
:param locale_name: Name of the locale.
- :type locale_name: str
+ :param path: Path of the resource.
:param raise_on_error: Raise an error instead of returning a placeholder.
- :type raise_on_error: bool
:raises FileNotFoundError: If the path could not be found and
``raise_on_error`` is ``True``.
:return: The text content of the resource.
- :rtype: str
"""
locales = [locale_name]
# Second chance: If the locale is a specific form of a more general
@@ -319,7 +303,7 @@ def read_localized_resource(locale_name, path, raise_on_error=False):
return f"{locale_name}:{path}"
-def secure_filename(filename):
+def secure_filename(filename: str) -> str:
r"""Pass it a filename and it will return a secure version of it. This
filename can then safely be stored on a regular file system and passed
to :func:`os.path.join`. The filename returned is an ASCII only string
@@ -339,9 +323,7 @@ def secure_filename(filename):
generate a random filename if the function returned an empty one.
:param filename: the filename to secure
- :type filename: str
:return: The secure filename.
- :rtype: str
"""
# Taken from
# https://github.com/pallets/werkzeug/blob/main/src/werkzeug/utils.py
diff --git a/fietsboek/views/upload.py b/fietsboek/views/upload.py
index f63f45d..1fb30e2 100644
--- a/fietsboek/views/upload.py
+++ b/fietsboek/views/upload.py
@@ -108,6 +108,7 @@ def finish_upload(request):
date = gpx.time or gpx.get_time_bounds().start_time or datetime.datetime.now()
date = date.astimezone(timezone)
tz_offset = timezone.utcoffset(date)
+ tz_offset = 0 if tz_offset is None else tz_offset.total_seconds()
track_name = ""
for track in gpx.tracks:
if track.name:
@@ -118,7 +119,7 @@ def finish_upload(request):
'preview_id': upload.id,
'upload_title': gpx.name or track_name,
'upload_date': date,
- 'upload_date_tz': int(tz_offset.total_seconds() // 60),
+ 'upload_date_tz': int(tz_offset // 60),
'upload_visibility': Visibility.PRIVATE,
'upload_type': TrackType.ORGANIC,
'upload_description': gpx.description,