diff --git a/assets/src/bundles/origin/visits-reporting.js b/assets/src/bundles/origin/visits-reporting.js --- a/assets/src/bundles/origin/visits-reporting.js +++ b/assets/src/bundles/origin/visits-reporting.js @@ -112,8 +112,8 @@ // process input visits let firstFullVisit; allVisits.forEach((v, i) => { - // Turn Unix epoch into Javascript Date object - v.date = new Date(Math.floor(v.date * 1000)); + // Turn Python ISO8601 string date into Javascript UTC Date object + v.date = new Date(v.date.slice(0, -6)); const visitLink = '' + v.formatted_date + ''; if (v.status === 'full') { if (!firstFullVisit) { diff --git a/swh/web/browse/snapshot_context.py b/swh/web/browse/snapshot_context.py --- a/swh/web/browse/snapshot_context.py +++ b/swh/web/browse/snapshot_context.py @@ -8,6 +8,7 @@ from collections import defaultdict from typing import Any, Dict, List, Optional, Tuple +from django.http import HttpRequest, HttpResponse from django.shortcuts import render from django.utils.html import escape @@ -48,7 +49,9 @@ _empty_snapshot_id = Snapshot(branches={}).id.hex() -def _get_branch(branches, branch_name, snapshot_id): +def _get_branch( + branches: List[SnapshotBranchInfo], branch_name: str, snapshot_id: str +) -> Optional[SnapshotBranchInfo]: """ Utility function to get a specific branch from a snapshot. Returns None if the branch cannot be found. @@ -71,9 +74,12 @@ if snp_branch and snp_branch[0]["name"] == branch_name: branches.append(snp_branch[0]) return snp_branch[0] + return None -def _get_release(releases, release_name, snapshot_id): +def _get_release( + releases: List[SnapshotReleaseInfo], release_name: Optional[str], snapshot_id: str +) -> Optional[SnapshotReleaseInfo]: """ Utility function to get a specific release from a snapshot. Returns None if the release cannot be found. @@ -81,7 +87,7 @@ filtered_releases = [r for r in releases if r["name"] == release_name] if filtered_releases: return filtered_releases[0] - else: + elif release_name: # case where a large branches list has been truncated try: # git origins have specific branches for releases @@ -102,11 +108,18 @@ if snp_release and snp_release[0]["name"] == release_name: releases.append(snp_release[0]) return snp_release[0] + return None def _branch_not_found( - branch_type, branch, snapshot_id, snapshot_sizes, origin_info, timestamp, visit_id -): + branch_type: str, + branch: str, + snapshot_id: str, + snapshot_sizes: Dict[str, int], + origin_info: Optional[OriginInfo], + timestamp: Optional[str], + visit_id: Optional[int], +) -> None: """ Utility function to raise an exception when a specified branch/release can not be found. @@ -131,25 +144,25 @@ branch, snapshot_id, ) - elif visit_id and snapshot_sizes[target_type] == 0: + elif visit_id and snapshot_sizes[target_type] == 0 and origin_info: msg = ( "Origin with url %s" " for visit with id %s has an empty list" " of %s!" % (origin_info["url"], visit_id, branch_type_plural) ) - elif visit_id: + elif visit_id and origin_info: msg = ( "%s %s associated to visit with" " id %s for origin with url %s" " not found!" % (branch_type, branch, visit_id, origin_info["url"]) ) - elif snapshot_sizes[target_type] == 0: + elif snapshot_sizes[target_type] == 0 and origin_info and timestamp: msg = ( "Origin with url %s" " for visit with timestamp %s has an empty list" " of %s!" % (origin_info["url"], timestamp, branch_type_plural) ) - else: + elif origin_info and timestamp: msg = ( "%s %s associated to visit with" " timestamp %s for origin with " @@ -569,6 +582,7 @@ # HEAD alias targets a release release_name = archive.lookup_release(head["target"])["name"] head_rel = _get_release(releases, release_name, snapshot_id) + assert head_rel is not None if head_rel["target_type"] == "revision": revision = archive.lookup_revision(head_rel["target"]) root_directory = revision["directory"] @@ -663,7 +677,9 @@ return snapshot_context -def _build_breadcrumbs(snapshot_context: SnapshotContext, path: str): +def _build_breadcrumbs( + snapshot_context: SnapshotContext, path: Optional[str] +) -> List[Dict[str, str]]: origin_info = snapshot_context["origin_info"] url_args = snapshot_context["url_args"] query_params = dict(snapshot_context["query_params"]) @@ -700,14 +716,18 @@ return breadcrumbs -def _check_origin_url(snapshot_id, origin_url): +def _check_origin_url(snapshot_id: Optional[str], origin_url: Optional[str]) -> None: if snapshot_id is None and origin_url is None: raise BadInputExc("An origin URL must be provided as query parameter.") def browse_snapshot_directory( - request, snapshot_id=None, origin_url=None, timestamp=None, path=None -): + request: HttpRequest, + snapshot_id: Optional[str] = None, + origin_url: Optional[str] = None, + timestamp: Optional[str] = None, + path: Optional[str] = None, +) -> HttpResponse: """ Django view implementation for browsing a directory in a snapshot context. """ @@ -728,7 +748,7 @@ root_directory = snapshot_context["root_directory"] sha1_git = root_directory - error_info = { + error_info: Dict[str, Any] = { "status_code": 200, "description": None, } @@ -813,7 +833,7 @@ dir_path = "/" + path swh_objects = [] - vault_cooking = { + vault_cooking: Dict[str, Any] = { "directory_context": False, "directory_swhid": None, "revision_context": False, @@ -932,7 +952,12 @@ PER_PAGE = 100 -def browse_snapshot_log(request, snapshot_id=None, origin_url=None, timestamp=None): +def browse_snapshot_log( + request: HttpRequest, + snapshot_id: Optional[str] = None, + origin_url: Optional[str] = None, + timestamp: Optional[str] = None, +) -> HttpResponse: """ Django view implementation for browsing a revision history in a snapshot context. @@ -991,9 +1016,10 @@ query_params = snapshot_context["query_params"] snapshot_id = snapshot_context["snapshot_id"] - query_params["per_page"] = per_page + query_params["per_page"] = str(per_page) revs_ordering = request.GET.get("revs_ordering", "") - query_params["revs_ordering"] = revs_ordering or None + if revs_ordering: + query_params["revs_ordering"] = revs_ordering if origin_info: browse_view_name = "browse-origin-log" @@ -1002,14 +1028,14 @@ prev_log_url = None if len(rev_log) > offset + per_page: - query_params["offset"] = offset + per_page + query_params["offset"] = str(offset + per_page) prev_log_url = reverse( browse_view_name, url_args=url_args, query_params=query_params ) next_log_url = None if offset != 0: - query_params["offset"] = offset - per_page + query_params["offset"] = str(offset - per_page) next_log_url = reverse( browse_view_name, url_args=url_args, query_params=query_params ) @@ -1029,7 +1055,7 @@ "snapshot": snapshot_id, } - if origin_info: + if origin_info and visit_info: revision_metadata["origin url"] = origin_info["url"] revision_metadata["origin visit date"] = format_utc_iso_date(visit_info["date"]) revision_metadata["origin visit type"] = visit_info["type"] @@ -1077,8 +1103,12 @@ def browse_snapshot_branches( - request, snapshot_id=None, origin_url=None, timestamp=None, branch_name_include=None -): + request: HttpRequest, + snapshot_id: Optional[str] = None, + origin_url: Optional[str] = None, + timestamp: Optional[str] = None, + branch_name_include: Optional[str] = None, +) -> HttpResponse: """ Django view implementation for browsing a list of branches in a snapshot context. @@ -1093,8 +1123,8 @@ visit_id=visit_id or None, ) - branches_bc = request.GET.get("branches_breadcrumbs", "") - branches_bc = branches_bc.split(",") if branches_bc else [] + branches_bc_str = request.GET.get("branches_breadcrumbs", "") + branches_bc = branches_bc_str.split(",") if branches_bc_str else [] branches_from = branches_bc[-1] if branches_bc else "" origin_info = snapshot_context["origin_info"] @@ -1113,9 +1143,10 @@ target_types=["revision", "alias"], branch_name_include_substring=branch_name_include, ) - displayed_branches = [] + displayed_branches: List[Dict[str, Any]] = [] if snapshot: - displayed_branches, _, _ = process_snapshot_branches(snapshot) + branches, _, _ = process_snapshot_branches(snapshot) + displayed_branches = [dict(branch) for branch in branches] for branch in displayed_branches: rev_query_params = {} @@ -1190,11 +1221,11 @@ def browse_snapshot_releases( - request, - snapshot_id=None, - origin_url=None, - timestamp=None, - release_name_include=None, + request: HttpRequest, + snapshot_id: Optional[str] = None, + origin_url: Optional[str] = None, + timestamp: Optional[str] = None, + release_name_include: Optional[str] = None, ): """ Django view implementation for browsing a list of releases in a snapshot @@ -1210,8 +1241,8 @@ visit_id=visit_id or None, ) - rel_bc = request.GET.get("releases_breadcrumbs", "") - rel_bc = rel_bc.split(",") if rel_bc else [] + rel_bc_str = request.GET.get("releases_breadcrumbs", "") + rel_bc = rel_bc_str.split(",") if rel_bc_str else [] rel_from = rel_bc[-1] if rel_bc else "" origin_info = snapshot_context["origin_info"] @@ -1225,9 +1256,10 @@ target_types=["release", "alias"], branch_name_include_substring=release_name_include, ) - displayed_releases = [] + displayed_releases: List[Dict[str, Any]] = [] if snapshot: - _, displayed_releases, _ = process_snapshot_branches(snapshot) + _, releases, _ = process_snapshot_branches(snapshot) + displayed_releases = [dict(release) for release in releases] for release in displayed_releases: query_params_tgt = {"snapshot": snapshot_id, "release": release["name"]} diff --git a/swh/web/browse/views/content.py b/swh/web/browse/views/content.py --- a/swh/web/browse/views/content.py +++ b/swh/web/browse/views/content.py @@ -5,8 +5,9 @@ import difflib from distutils.util import strtobool +from typing import Any, Dict, Optional -from django.http import HttpResponse, JsonResponse +from django.http import HttpRequest, HttpResponse, JsonResponse from django.shortcuts import redirect, render from swh.model.hashutil import hash_to_hex @@ -32,11 +33,11 @@ @browse_route( - r"content/(?P[0-9a-z_:]*[0-9a-f]+.)/raw/", + r"content/(?P[0-9a-z_:]*[0-9a-f]+)/raw/", view_name="browse-content-raw", checksum_args=["query_string"], ) -def content_raw(request, query_string): +def content_raw(request: HttpRequest, query_string: str) -> HttpResponse: """Django view that produces a raw display of a content identified by its hash value. @@ -70,10 +71,12 @@ @browse_route( - r"content/(?P.*)/diff/(?P.*)/", + r"content/(?P.+)/diff/(?P.+)/", view_name="diff-contents", ) -def _contents_diff(request, from_query_string, to_query_string): +def _contents_diff( + request: HttpRequest, from_query_string: str, to_query_string: str +) -> HttpResponse: """ Browse endpoint used to compute unified diffs between two contents. @@ -102,11 +105,11 @@ content_to_size = 0 content_from_lines = [] content_to_lines = [] - force = request.GET.get("force", "false") + force_str = request.GET.get("force", "false") path = request.GET.get("path", None) language = "plaintext" - force = bool(strtobool(force)) + force = bool(strtobool(force_str)) if from_query_string == to_query_string: diff_str = "File renamed without changes" @@ -173,7 +176,7 @@ return JsonResponse(diff_data) -def _get_content_from_request(request): +def _get_content_from_request(request: HttpRequest) -> Dict[str, Any]: path = request.GET.get("path") if path is None: raise BadInputExc("The path query parameter must be provided.") @@ -196,16 +199,19 @@ browse_context="content", ) root_directory = snapshot_context["root_directory"] + assert root_directory is not None # to keep mypy happy return archive.lookup_directory_with_path(root_directory, path) @browse_route( - r"content/(?P[0-9a-z_:]*[0-9a-f]+.)/", + r"content/(?P[0-9a-z_:]*[0-9a-f]+)/", r"content/", view_name="browse-content", checksum_args=["query_string"], ) -def content_display(request, query_string=None): +def content_display( + request: HttpRequest, query_string: Optional[str] = None +) -> HttpResponse: """Django view that produces an HTML display of a content identified by its hash value. @@ -215,11 +221,11 @@ """ if query_string is None: # this case happens when redirected from origin/content or snapshot/content - content = _get_content_from_request(request) + content_data = _get_content_from_request(request) return redirect( reverse( "browse-content", - url_args={"query_string": f"sha1_git:{content['target']}"}, + url_args={"query_string": f"sha1_git:{content_data['target']}"}, query_params=request.GET, ), ) @@ -233,7 +239,7 @@ snapshot_id = request.GET.get("snapshot") or request.GET.get("snapshot_id") path = request.GET.get("path") content_data = {} - error_info = {"status_code": 200, "description": None} + error_info: Dict[str, Any] = {"status_code": 200, "description": None} try: content_data = request_content(query_string) except NotFoundExc as e: @@ -327,9 +333,9 @@ query_params=query_params, ) breadcrumbs.append({"name": pi["name"], "url": dir_url}) - breadcrumbs.append({"name": filename, "url": None}) + breadcrumbs.append({"name": filename, "url": ""}) - if path and root_dir != path: + if path and root_dir is not None and root_dir != path: dir_info = archive.lookup_directory_with_path(root_dir, path) directory_id = dir_info["target"] elif root_dir != path: @@ -360,10 +366,10 @@ sha256=content_checksums.get("sha256"), blake2s256=content_checksums.get("blake2s256"), content_url=content_url, - mimetype=content_data.get("mimetype"), - encoding=content_data.get("encoding"), + mimetype=content_data.get("mimetype", ""), + encoding=content_data.get("encoding", ""), size=content_data.get("length", 0), - language=content_data.get("language"), + language=content_data.get("language", ""), root_directory=root_dir, path=f"/{path}" if path else None, filename=filename or "", @@ -418,7 +424,7 @@ heading = "Content - %s" % content_checksums.get("sha1_git") if breadcrumbs: - content_path = "/".join([bc["name"] for bc in breadcrumbs]) + content_path = "/".join(bc["name"] for bc in breadcrumbs) heading += " - %s" % content_path return render( diff --git a/swh/web/browse/views/directory.py b/swh/web/browse/views/directory.py --- a/swh/web/browse/views/directory.py +++ b/swh/web/browse/views/directory.py @@ -4,8 +4,9 @@ # See top-level LICENSE file for more information import os +from typing import Any, Dict, Optional -from django.http import HttpResponse +from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect, render from swh.model.swhids import ObjectType @@ -23,21 +24,24 @@ from swh.web.common.utils import gen_path_info, reverse, swh_object_icons -def _directory_browse(request, sha1_git, path=None): +def _directory_browse( + request: HttpRequest, sha1_git: str, path: Optional[str] = None +) -> HttpResponse: root_sha1_git = sha1_git - error_info = {"status_code": 200, "description": None} + dir_sha1_git: Optional[str] = sha1_git + error_info: Dict[str, Any] = {"status_code": 200, "description": None} if path: try: dir_info = archive.lookup_directory_with_path(sha1_git, path) - sha1_git = dir_info["target"] + dir_sha1_git = dir_info["target"] except NotFoundExc as e: error_info["status_code"] = 404 error_info["description"] = f"NotFoundExc: {str(e)}" - sha1_git = None + dir_sha1_git = None dirs, files = [], [] - if sha1_git is not None: - dirs, files = get_directory_entries(sha1_git) + if dir_sha1_git is not None: + dirs, files = get_directory_entries(dir_sha1_git) origin_url = request.GET.get("origin_url") if not origin_url: origin_url = request.GET.get("origin") @@ -56,7 +60,7 @@ except NotFoundExc as e: if str(e).startswith("Origin"): raw_dir_url = reverse( - "browse-directory", url_args={"sha1_git": sha1_git} + "browse-directory", url_args={"sha1_git": dir_sha1_git} ) error_message = ( "The Software Heritage archive has a directory " @@ -144,7 +148,7 @@ dir_metadata = DirectoryMetadata( object_type=ObjectType.DIRECTORY, - object_id=sha1_git, + object_id=dir_sha1_git, directory=root_sha1_git, nb_files=len(files), nb_dirs=len(dirs), @@ -159,12 +163,14 @@ vault_cooking = { "directory_context": True, - "directory_swhid": f"swh:1:dir:{sha1_git}", + "directory_swhid": f"swh:1:dir:{dir_sha1_git}", "revision_context": False, "revision_swhid": None, } - swh_objects = [SWHObjectInfo(object_type=ObjectType.DIRECTORY, object_id=sha1_git)] + swh_objects = [ + SWHObjectInfo(object_type=ObjectType.DIRECTORY, object_id=dir_sha1_git) + ] if snapshot_context: if snapshot_context["revision_id"]: @@ -190,7 +196,7 @@ swhids_info = get_swhids_info(swh_objects, snapshot_context, dir_metadata) - heading = "Directory - %s" % sha1_git + heading = "Directory - %s" % dir_sha1_git if breadcrumbs: dir_path = "/".join([bc["name"] for bc in breadcrumbs]) + "/" heading += " - %s" % dir_path @@ -244,7 +250,7 @@ view_name="browse-directory", checksum_args=["sha1_git"], ) -def directory_browse(request, sha1_git): +def directory_browse(request: HttpRequest, sha1_git: str) -> HttpResponse: """Django view for browsing the content of a directory identified by its sha1_git value. @@ -259,7 +265,9 @@ view_name="browse-directory-legacy", checksum_args=["sha1_git"], ) -def directory_browse_legacy(request, sha1_git, path): +def directory_browse_legacy( + request: HttpRequest, sha1_git: str, path: str +) -> HttpResponse: """Django view for browsing the content of a directory identified by its sha1_git value. @@ -274,13 +282,15 @@ view_name="browse-directory-resolve-content-path", checksum_args=["sha1_git"], ) -def _directory_resolve_content_path(request, sha1_git): +def _directory_resolve_content_path( + request: HttpRequest, sha1_git: str +) -> HttpResponse: """ Internal endpoint redirecting to data url for a specific file path relative to a root directory. """ try: - path = os.path.normpath(request.GET.get("path")) + path = os.path.normpath(request.GET.get("path", "")) if not path.startswith("../"): dir_info = archive.lookup_directory_with_path(sha1_git, path) if dir_info["type"] == "file": diff --git a/swh/web/browse/views/origin.py b/swh/web/browse/views/origin.py --- a/swh/web/browse/views/origin.py +++ b/swh/web/browse/views/origin.py @@ -1,8 +1,11 @@ -# Copyright (C) 2021 The Software Heritage developers +# Copyright (C) 2021-2022 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information +from typing import Optional + +from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect, render from swh.web.browse.browseurls import browse_route @@ -25,7 +28,7 @@ r"origin/directory/", view_name="browse-origin-directory", ) -def origin_directory_browse(request): +def origin_directory_browse(request: HttpRequest) -> HttpResponse: """Django view for browsing the content of a directory associated to an origin for a given visit. @@ -47,7 +50,12 @@ r"origin/(?P.+)/directory/", view_name="browse-origin-directory-legacy", ) -def origin_directory_browse_legacy(request, origin_url, timestamp=None, path=None): +def origin_directory_browse_legacy( + request: HttpRequest, + origin_url: str, + timestamp: Optional[str] = None, + path: Optional[str] = None, +) -> HttpResponse: """Django view for browsing the content of a directory associated to an origin for a given visit. @@ -68,7 +76,7 @@ r"origin/content/", view_name="browse-origin-content", ) -def origin_content_browse(request): +def origin_content_browse(request: HttpRequest) -> HttpResponse: """ This route is deprecated; use http:get:`/browse/content` instead @@ -87,7 +95,12 @@ r"origin/(?P.+)/content/", view_name="browse-origin-content-legacy", ) -def origin_content_browse_legacy(request, origin_url, path=None, timestamp=None): +def origin_content_browse_legacy( + request: HttpRequest, + origin_url: str, + path: Optional[str] = None, + timestamp: Optional[str] = None, +) -> HttpResponse: """ This route is deprecated; use http:get:`/browse/content` instead @@ -106,7 +119,7 @@ r"origin/log/", view_name="browse-origin-log", ) -def origin_log_browse(request): +def origin_log_browse(request: HttpRequest) -> HttpResponse: """ This route is deprecated; use http:get:`/browse/snapshot/log` instead @@ -123,7 +136,9 @@ r"origin/(?P.+)/log/", view_name="browse-origin-log-legacy", ) -def origin_log_browse_legacy(request, origin_url, timestamp=None): +def origin_log_browse_legacy( + request: HttpRequest, origin_url: str, timestamp: Optional[str] = None +) -> HttpResponse: """ This route is deprecated; use http:get:`/browse/snapshot/log` instead @@ -145,7 +160,7 @@ r"origin/branches/", view_name="browse-origin-branches", ) -def origin_branches_browse(request): +def origin_branches_browse(request: HttpRequest) -> HttpResponse: """ This route is deprecated; use http:get:`/browse/snapshot/branches` instead @@ -163,7 +178,9 @@ r"origin/(?P.+)/branches/", view_name="browse-origin-branches-legacy", ) -def origin_branches_browse_legacy(request, origin_url, timestamp=None): +def origin_branches_browse_legacy( + request: HttpRequest, origin_url: str, timestamp: Optional[str] = None +) -> HttpResponse: """ This route is deprecated; use http:get:`/browse/snapshot/branches` instead @@ -182,7 +199,7 @@ r"origin/releases/", view_name="browse-origin-releases", ) -def origin_releases_browse(request): +def origin_releases_browse(request: HttpRequest) -> HttpResponse: """ This route is deprecated; use http:get:`/browse/snapshot/releases` instead @@ -200,7 +217,9 @@ r"origin/(?P.+)/releases/", view_name="browse-origin-releases-legacy", ) -def origin_releases_browse_legacy(request, origin_url, timestamp=None): +def origin_releases_browse_legacy( + request: HttpRequest, origin_url: str, timestamp: Optional[str] = None +) -> HttpResponse: """ This route is deprecated; use http:get:`/browse/snapshot/releases` instead @@ -215,12 +234,15 @@ return redirect_to_new_route(request, "browse-snapshot-releases") -def _origin_visits_browse(request, origin_url): +def _origin_visits_browse( + request: HttpRequest, origin_url: Optional[str] +) -> HttpResponse: if origin_url is None: raise BadInputExc("An origin URL must be provided as query parameter.") origin_info = archive.lookup_origin({"url": origin_url}) origin_visits = get_origin_visits(origin_info) + snapshot_context = get_snapshot_context(origin_url=origin_url) for i, visit in enumerate(origin_visits): @@ -242,7 +264,7 @@ ) if not snapshot: visit["snapshot"] = "" - visit["date"] = parse_iso8601_date_to_utc(visit["date"]).timestamp() + visit["date"] = parse_iso8601_date_to_utc(visit["date"]).isoformat() heading = "Origin visits - %s" % origin_url @@ -263,7 +285,7 @@ @browse_route(r"origin/visits/", view_name="browse-origin-visits") -def origin_visits_browse(request): +def origin_visits_browse(request: HttpRequest) -> HttpResponse: """Django view that produces an HTML display of visits reporting for a given origin. @@ -276,7 +298,7 @@ @browse_route( r"origin/(?P.+)/visits/", view_name="browse-origin-visits-legacy" ) -def origin_visits_browse_legacy(request, origin_url): +def origin_visits_browse_legacy(request: HttpRequest, origin_url: str) -> HttpResponse: """Django view that produces an HTML display of visits reporting for a given origin. @@ -287,7 +309,7 @@ @browse_route(r"origin/", view_name="browse-origin") -def origin_browse(request): +def origin_browse(request: HttpRequest) -> HttpResponse: """Django view that redirects to the display of the latest archived snapshot for a given software origin. """ @@ -299,7 +321,7 @@ @browse_route(r"origin/(?P.+)/", view_name="browse-origin-legacy") -def origin_browse_legacy(request, origin_url): +def origin_browse_legacy(request: HttpRequest, origin_url: str) -> HttpResponse: """Django view that redirects to the display of the latest archived snapshot for a given software origin. """ diff --git a/swh/web/browse/views/release.py b/swh/web/browse/views/release.py --- a/swh/web/browse/views/release.py +++ b/swh/web/browse/views/release.py @@ -3,6 +3,9 @@ # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information +from typing import Optional + +from django.http import HttpRequest, HttpResponse from django.shortcuts import render from swh.model.swhids import ObjectType @@ -19,7 +22,7 @@ from swh.web.common import archive from swh.web.common.exc import NotFoundExc, sentry_capture_exception from swh.web.common.identifiers import get_swhids_info -from swh.web.common.typing import ReleaseMetadata, SWHObjectInfo +from swh.web.common.typing import ReleaseMetadata, SnapshotContext, SWHObjectInfo from swh.web.common.utils import format_utc_iso_date, reverse @@ -28,7 +31,7 @@ view_name="browse-release", checksum_args=["sha1_git"], ) -def release_browse(request, sha1_git): +def release_browse(request: HttpRequest, sha1_git: str) -> HttpResponse: """ Django view that produces an HTML display of a release identified by its id. @@ -36,7 +39,7 @@ The url that points to it is :http:get:`/browse/release/(sha1_git)/`. """ release = archive.lookup_release(sha1_git) - snapshot_context = {} + snapshot_context: Optional[SnapshotContext] = None origin_info = None snapshot_id = request.GET.get("snapshot_id") if not snapshot_id: @@ -76,7 +79,8 @@ snapshot_id, release_name=release["name"] ) - snapshot_id = snapshot_context.get("snapshot_id", None) + if snapshot_context is not None: + snapshot_id = snapshot_context.get("snapshot_id", None) release_metadata = ReleaseMetadata( object_type=ObjectType.RELEASE, diff --git a/swh/web/browse/views/revision.py b/swh/web/browse/views/revision.py --- a/swh/web/browse/views/revision.py +++ b/swh/web/browse/views/revision.py @@ -6,8 +6,9 @@ import hashlib import json import textwrap +from typing import Any, Dict, List, Optional -from django.http import JsonResponse +from django.http import HttpRequest, HttpResponse, JsonResponse from django.shortcuts import render from django.utils.safestring import mark_safe @@ -30,7 +31,7 @@ from swh.web.common import archive from swh.web.common.exc import NotFoundExc, http_status_code_message from swh.web.common.identifiers import get_swhids_info -from swh.web.common.typing import RevisionMetadata, SWHObjectInfo +from swh.web.common.typing import RevisionMetadata, SnapshotContext, SWHObjectInfo from swh.web.common.utils import ( format_utc_iso_date, gen_path_info, @@ -39,7 +40,12 @@ ) -def _gen_content_url(revision, query_string, path, snapshot_context): +def _gen_content_url( + revision: Dict[str, Any], + query_string: str, + path: str, + snapshot_context: Optional[SnapshotContext], +) -> str: if snapshot_context: query_params = snapshot_context["query_params"] query_params["path"] = path @@ -55,7 +61,7 @@ return content_url -def _gen_diff_link(idx, diff_anchor, link_text): +def _gen_diff_link(idx: int, diff_anchor: str, link_text: str) -> str: if idx < _max_displayed_file_diffs: return gen_link(diff_anchor, link_text) else: @@ -66,7 +72,11 @@ _max_displayed_file_diffs = 1000 -def _gen_revision_changes_list(revision, changes, snapshot_context): +def _gen_revision_changes_list( + revision: Dict[str, Any], + changes: List[Dict[str, Any]], + snapshot_context: Optional[SnapshotContext], +) -> str: """ Returns a HTML string describing the file changes introduced in a revision. @@ -151,7 +161,7 @@ view_name="diff-revision", checksum_args=["sha1_git"], ) -def _revision_diff(request, sha1_git): +def _revision_diff(request: HttpRequest, sha1_git: str) -> HttpResponse: """ Browse internal endpoint to compute revision diff """ @@ -161,7 +171,8 @@ if not origin_url: origin_url = request.GET.get("origin", None) timestamp = request.GET.get("timestamp", None) - visit_id = request.GET.get("visit_id", None) + visit_id_str = request.GET.get("visit_id", None) + visit_id = int(visit_id_str) if visit_id_str is not None else None if origin_url: snapshot_context = get_snapshot_context( origin_url=origin_url, timestamp=timestamp, visit_id=visit_id @@ -186,7 +197,7 @@ view_name="browse-revision-log", checksum_args=["sha1_git"], ) -def revision_log_browse(request, sha1_git): +def revision_log_browse(request: HttpRequest, sha1_git: str) -> HttpResponse: """ Django view that produces an HTML display of the history log for a revision identified by its id. @@ -296,7 +307,7 @@ view_name="browse-revision", checksum_args=["sha1_git"], ) -def revision_browse(request, sha1_git): +def revision_browse(request: HttpRequest, sha1_git: str) -> HttpResponse: """ Django view that produces an HTML display of a revision identified by its id. @@ -350,7 +361,7 @@ elif snapshot_id: snapshot_context = get_snapshot_context(snapshot_id) - error_info = {"status_code": 200, "description": None} + error_info: Dict[str, Any] = {"status_code": 200, "description": None} if path: try: diff --git a/swh/web/browse/views/snapshot.py b/swh/web/browse/views/snapshot.py --- a/swh/web/browse/views/snapshot.py +++ b/swh/web/browse/views/snapshot.py @@ -1,9 +1,11 @@ -# Copyright (C) 2018-2019 The Software Heritage developers +# Copyright (C) 2018-2022 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information +from typing import Optional +from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect from swh.web.browse.browseurls import browse_route @@ -18,7 +20,7 @@ from swh.web.common.utils import redirect_to_new_route, reverse -def get_snapshot_from_request(request): +def get_snapshot_from_request(request: HttpRequest) -> str: snapshot = request.GET.get("snapshot") if snapshot: return snapshot @@ -34,7 +36,7 @@ view_name="browse-snapshot", checksum_args=["snapshot_id"], ) -def snapshot_browse(request, snapshot_id): +def snapshot_browse(request: HttpRequest, snapshot_id: str) -> HttpResponse: """Django view for browsing the content of a snapshot. The url that points to it is :http:get:`/browse/snapshot/(snapshot_id)/` @@ -52,7 +54,7 @@ view_name="browse-snapshot-directory", checksum_args=["snapshot_id"], ) -def snapshot_directory_browse(request, snapshot_id): +def snapshot_directory_browse(request: HttpRequest, snapshot_id: str) -> HttpResponse: """Django view for browsing the content of a directory collected in a snapshot. @@ -71,7 +73,9 @@ view_name="browse-snapshot-directory-legacy", checksum_args=["snapshot_id"], ) -def snapshot_directory_browse_legacy(request, snapshot_id, path=None): +def snapshot_directory_browse_legacy( + request: HttpRequest, snapshot_id: str, path: Optional[str] = None +) -> HttpResponse: """Django view for browsing the content of a directory collected in a snapshot. @@ -91,7 +95,7 @@ view_name="browse-snapshot-content", checksum_args=["snapshot_id"], ) -def snapshot_content_browse(request, snapshot_id): +def snapshot_content_browse(request: HttpRequest, snapshot_id: str) -> HttpResponse: """ This route is deprecated; use http:get:`/browse/content` instead @@ -109,7 +113,9 @@ view_name="browse-snapshot-content-legacy", checksum_args=["snapshot_id"], ) -def snapshot_content_browse_legacy(request, snapshot_id, path): +def snapshot_content_browse_legacy( + request: HttpRequest, snapshot_id: str, path: str +) -> HttpResponse: """ This route is deprecated; use http:get:`/browse/content` instead @@ -128,7 +134,9 @@ view_name="browse-snapshot-log", checksum_args=["snapshot_id"], ) -def snapshot_log_browse(request, snapshot_id=None): +def snapshot_log_browse( + request: HttpRequest, snapshot_id: Optional[str] = None +) -> HttpResponse: """Django view that produces an HTML display of revisions history (aka the commit log) collected in a snapshot. @@ -161,7 +169,9 @@ view_name="browse-snapshot-branches", checksum_args=["snapshot_id"], ) -def snapshot_branches_browse(request, snapshot_id=None): +def snapshot_branches_browse( + request: HttpRequest, snapshot_id: Optional[str] = None +) -> HttpResponse: """Django view that produces an HTML display of the list of branches collected in a snapshot. @@ -196,7 +206,9 @@ view_name="browse-snapshot-releases", checksum_args=["snapshot_id"], ) -def snapshot_releases_browse(request, snapshot_id=None): +def snapshot_releases_browse( + request: HttpRequest, snapshot_id: Optional[str] = None +) -> HttpResponse: """Django view that produces an HTML display of the list of releases collected in a snapshot. diff --git a/swh/web/common/identifiers.py b/swh/web/common/identifiers.py --- a/swh/web/common/identifiers.py +++ b/swh/web/common/identifiers.py @@ -1,9 +1,9 @@ -# Copyright (C) 2020-2021 The Software Heritage developers +# Copyright (C) 2020-2022 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information -from typing import Any, Dict, Iterable, List, Optional +from typing import Any, Dict, Iterable, List, Mapping, Optional from urllib.parse import quote, unquote from typing_extensions import TypedDict @@ -274,7 +274,7 @@ def get_swhids_info( swh_objects: Iterable[SWHObjectInfo], snapshot_context: Optional[SnapshotContext] = None, - extra_context: Optional[Dict[str, Any]] = None, + extra_context: Optional[Mapping[str, Any]] = None, ) -> List[SWHIDInfo]: """ Returns a list of dict containing info related to SWHIDs of objects. diff --git a/swh/web/common/typing.py b/swh/web/common/typing.py --- a/swh/web/common/typing.py +++ b/swh/web/common/typing.py @@ -137,7 +137,7 @@ class SWHObjectInfo(TypedDict): object_type: ObjectType - object_id: str + object_id: Optional[str] class SWHIDContext(TypedDict, total=False): @@ -182,12 +182,12 @@ class DirectoryMetadata(SWHObjectInfo, SWHObjectInfoMetadata): - directory: str - nb_files: int - nb_dirs: int - sum_file_sizes: int + directory: Optional[str] + nb_files: Optional[int] + nb_dirs: Optional[int] + sum_file_sizes: Optional[int] root_directory: Optional[str] - path: str + path: Optional[str] revision: Optional[str] revision_found: Optional[bool] release: Optional[str]