diff --git a/swh/web/browse/browseurls.py b/swh/web/browse/browseurls.py --- a/swh/web/browse/browseurls.py +++ b/swh/web/browse/browseurls.py @@ -1,8 +1,10 @@ -# Copyright (C) 2017-2019 The Software Heritage developers +# Copyright (C) 2017-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 List, Optional + from swh.web.common.urlsindex import UrlsIndex @@ -14,7 +16,11 @@ scope = "browse" -def browse_route(*url_patterns, view_name=None, checksum_args=None): +def browse_route( + *url_patterns: str, + view_name: Optional[str] = None, + checksum_args: Optional[List[str]] = None, +): """ Decorator to ease the registration of a swh-web browse endpoint @@ -24,7 +30,7 @@ view_name: the name of the Django view associated to the routes used to reverse the url """ - url_patterns = ["^" + url_pattern + "$" for url_pattern in url_patterns] + url_patterns = tuple("^" + url_pattern + "$" for url_pattern in url_patterns) view_name = view_name def decorator(f): diff --git a/swh/web/browse/identifiers.py b/swh/web/browse/identifiers.py --- a/swh/web/browse/identifiers.py +++ b/swh/web/browse/identifiers.py @@ -1,19 +1,20 @@ -# Copyright (C) 2017-2020 The Software Heritage developers +# Copyright (C) 2017-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 django.http import HttpRequest, HttpResponse from django.shortcuts import redirect from swh.web.common.identifiers import resolve_swhid -def swhid_browse(request, swhid): +def swhid_browse(request: HttpRequest, swhid: str) -> HttpResponse: """ Django view enabling to browse the archive using :ref:`persistent-identifiers`. The url that points to it is :http:get:`/(swhid)/`. """ swhid_resolved = resolve_swhid(swhid, query_params=request.GET) - + assert swhid_resolved["browse_url"] return redirect(swhid_resolved["browse_url"]) 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,13 +582,24 @@ # HEAD alias targets a release release_name = archive.lookup_release(head["target"])["name"] head_rel = _get_release(releases, release_name, snapshot_id) - if head_rel["target_type"] == "revision": + if head_rel is None: + _branch_not_found( + "release", + str(release_name), + snapshot_id, + snapshot_sizes, + origin_info, + timestamp, + visit_id, + ) + elif head_rel["target_type"] == "revision": revision = archive.lookup_revision(head_rel["target"]) root_directory = revision["directory"] revision_id = head_rel["target"] elif head_rel["target_type"] == "directory": root_directory = head_rel["target"] - release_id = head_rel["id"] + if head_rel is not None: + release_id = head_rel["id"] elif branches: # fallback to browse first branch otherwise branch = branches[0] @@ -658,12 +682,16 @@ ) if revision_info: - revision_info["revision_url"] = gen_revision_url(revision_id, snapshot_context) + revision_info["revision_url"] = gen_revision_url( + revision_info["id"], snapshot_context + ) 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 +728,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 +760,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 +845,7 @@ dir_path = "/" + path swh_objects = [] - vault_cooking = { + vault_cooking: Dict[str, Any] = { "directory_context": False, "directory_swhid": None, "revision_context": False, @@ -932,7 +964,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 +1028,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 +1040,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 +1067,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 +1115,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 +1135,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 +1155,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 +1233,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 +1253,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 +1268,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/urls.py b/swh/web/browse/urls.py --- a/swh/web/browse/urls.py +++ b/swh/web/browse/urls.py @@ -1,9 +1,10 @@ -# Copyright (C) 2017-2021 The Software Heritage developers +# Copyright (C) 2017-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 django.conf.urls import url +from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect, render from swh.web.browse.browseurls import BrowseUrls @@ -17,13 +18,13 @@ from swh.web.common.utils import origin_visit_types, reverse -def _browse_help_view(request): +def _browse_help_view(request: HttpRequest) -> HttpResponse: return render( request, "browse/help.html", {"heading": "How to browse the archive ?"} ) -def _browse_search_view(request): +def _browse_search_view(request: HttpRequest) -> HttpResponse: return render( request, "browse/search.html", @@ -34,7 +35,7 @@ ) -def _browse_vault_view(request): +def _browse_vault_view(request: HttpRequest) -> HttpResponse: return render( request, "browse/vault-ui.html", @@ -42,7 +43,7 @@ ) -def _browse_origin_save_view(request): +def _browse_origin_save_view(request: HttpRequest) -> HttpResponse: return redirect(reverse("origin-save")) diff --git a/swh/web/browse/utils.py b/swh/web/browse/utils.py --- a/swh/web/browse/utils.py +++ b/swh/web/browse/utils.py @@ -6,7 +6,7 @@ import base64 import stat import textwrap -from typing import Tuple +from typing import Any, Dict, Iterator, List, Optional, Tuple, Union import chardet import magic @@ -16,6 +16,7 @@ from swh.web.common import archive, highlightjs from swh.web.common.exc import NotFoundExc, sentry_capture_exception +from swh.web.common.typing import SnapshotContext from swh.web.common.utils import ( browsers_supported_image_mimes, django_cache, @@ -27,7 +28,9 @@ @django_cache() -def get_directory_entries(sha1_git): +def get_directory_entries( + sha1_git: str, +) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: """Function that retrieves the content of a directory from the archive. @@ -44,7 +47,7 @@ Raises: NotFoundExc if the directory is not found """ - entries = list(archive.lookup_directory(sha1_git)) + entries: List[Dict[str, Any]] = list(archive.lookup_directory(sha1_git)) for e in entries: e["perms"] = stat.filemode(e["perms"]) if e["type"] == "rev": @@ -61,12 +64,12 @@ return dirs, files -def get_mimetype_and_encoding_for_content(content): +def get_mimetype_and_encoding_for_content(content: bytes) -> Tuple[str, str]: """Function that returns the mime type and the encoding associated to a content buffer using the magic module under the hood. Args: - content (bytes): a content buffer + content: a content buffer Returns: A tuple (mimetype, encoding), for instance ('text/plain', 'us-ascii'), @@ -131,10 +134,10 @@ def request_content( - query_string, - max_size=content_display_max_size, - re_encode=True, -): + query_string: str, + max_size: Optional[int] = content_display_max_size, + re_encode: bool = True, +) -> Dict[str, Any]: """Function that retrieves a content from the archive. Raw bytes content is first retrieved, then the content mime type. @@ -150,8 +153,7 @@ no size limit if None) Returns: - A tuple whose first member corresponds to the content raw bytes - and second member the content mime type + A dict filled with content info. Raises: NotFoundExc if the content is not found @@ -209,7 +211,9 @@ return content_data -def prepare_content_for_display(content_data, mime_type, path): +def prepare_content_for_display( + content_data: bytes, mime_type: str, path: Optional[str] +) -> Dict[str, Any]: """Function that prepares a content for HTML display. The function tries to associate a programming language to a @@ -221,9 +225,9 @@ for displaying the image. Args: - content_data (bytes): raw bytes of the content - mime_type (string): mime type of the content - path (string): path of the content including filename + content_data: raw bytes of the content + mime_type: mime type of the content + path: path of the content including filename Returns: A dict containing the content bytes (possibly different from the one @@ -242,29 +246,39 @@ if language is None: language = "plaintext" + processed_content: Union[bytes, str] = content_data + if mime_type.startswith("image/"): if mime_type in browsers_supported_image_mimes: - content_data = base64.b64encode(content_data).decode("ascii") + processed_content = base64.b64encode(content_data).decode("ascii") if mime_type.startswith("image/svg"): mime_type = "image/svg+xml" if mime_type.startswith("text/") or mime_type.startswith("application/"): - content_data = content_data.decode("utf-8", errors="replace") + processed_content = content_data.decode("utf-8", errors="replace") - return {"content_data": content_data, "language": language, "mimetype": mime_type} + return { + "content_data": processed_content, + "language": language, + "mimetype": mime_type, + } -def gen_link(url, link_text=None, link_attrs=None): +def gen_link( + url: str, + link_text: Optional[str] = None, + link_attrs: Optional[Dict[str, str]] = None, +) -> str: """ Utility function for generating an HTML link to insert in Django templates. Args: - url (str): an url - link_text (str): optional text for the produced link, + url: an url + link_text: optional text for the produced link, if not provided the url will be used - link_attrs (dict): optional attributes (e.g. class) + link_attrs: optional attributes (e.g. class) to add to the link Returns: @@ -281,8 +295,10 @@ return mark_safe(link) -def _snapshot_context_query_params(snapshot_context): - query_params = {} +def _snapshot_context_query_params( + snapshot_context: Optional[SnapshotContext], +) -> Dict[str, str]: + query_params: Dict[str, str] = {} if not snapshot_context: return query_params if snapshot_context and snapshot_context["origin_info"]: @@ -290,11 +306,11 @@ snp_query_params = snapshot_context["query_params"] query_params = {"origin_url": origin_info["url"]} if "timestamp" in snp_query_params: - query_params["timestamp"] = snp_query_params["timestamp"] + query_params["timestamp"] = str(snp_query_params["timestamp"]) if "visit_id" in snp_query_params: - query_params["visit_id"] = snp_query_params["visit_id"] + query_params["visit_id"] = str(snp_query_params["visit_id"]) if "snapshot" in snp_query_params and "visit_id" not in query_params: - query_params["snapshot"] = snp_query_params["snapshot"] + query_params["snapshot"] = str(snp_query_params["snapshot"]) elif snapshot_context: query_params = {"snapshot": snapshot_context["snapshot_id"]} @@ -310,13 +326,15 @@ return query_params -def gen_revision_url(revision_id, snapshot_context=None): +def gen_revision_url( + revision_id: str, snapshot_context: Optional[SnapshotContext] = None +) -> str: """ Utility function for generating an url to a revision. Args: - revision_id (str): a revision id - snapshot_context (dict): if provided, generate snapshot-dependent + revision_id: a revision id + snapshot_context: if provided, generate snapshot-dependent browsing url Returns: @@ -334,25 +352,28 @@ def gen_revision_link( - revision_id, - shorten_id=False, - snapshot_context=None, - link_text="Browse", - link_attrs={"class": "btn btn-default btn-sm", "role": "button"}, -): + revision_id: str, + shorten_id: bool = False, + snapshot_context: Optional[SnapshotContext] = None, + link_text: Optional[str] = "Browse", + link_attrs: Optional[Dict[str, str]] = { + "class": "btn btn-default btn-sm", + "role": "button", + }, +) -> Optional[str]: """ Utility function for generating a link to a revision HTML view to insert in Django templates. Args: - revision_id (str): a revision id - shorten_id (boolean): whether to shorten the revision id to 7 + revision_id: a revision id + shorten_id: whether to shorten the revision id to 7 characters for the link text - snapshot_context (dict): if provided, generate snapshot-dependent + snapshot_context: if provided, generate snapshot-dependent browsing link - link_text (str): optional text for the generated link + link_text: optional text for the generated link (the revision id will be used by default) - link_attrs (dict): optional attributes (e.g. class) + link_attrs: optional attributes (e.g. class) to add to the link Returns: @@ -373,20 +394,23 @@ def gen_directory_link( - sha1_git, - snapshot_context=None, - link_text="Browse", - link_attrs={"class": "btn btn-default btn-sm", "role": "button"}, -): + sha1_git: str, + snapshot_context: Optional[SnapshotContext] = None, + link_text: Optional[str] = "Browse", + link_attrs: Optional[Dict[str, str]] = { + "class": "btn btn-default btn-sm", + "role": "button", + }, +) -> Optional[str]: """ Utility function for generating a link to a directory HTML view to insert in Django templates. Args: - sha1_git (str): directory identifier - link_text (str): optional text for the generated link + sha1_git: directory identifier + link_text: optional text for the generated link (the directory id will be used by default) - link_attrs (dict): optional attributes (e.g. class) + link_attrs: optional attributes (e.g. class) to add to the link Returns: @@ -408,20 +432,23 @@ def gen_snapshot_link( - snapshot_id, - snapshot_context=None, - link_text="Browse", - link_attrs={"class": "btn btn-default btn-sm", "role": "button"}, -): + snapshot_id: str, + snapshot_context: Optional[SnapshotContext] = None, + link_text: Optional[str] = "Browse", + link_attrs: Optional[Dict[str, str]] = { + "class": "btn btn-default btn-sm", + "role": "button", + }, +) -> str: """ Utility function for generating a link to a snapshot HTML view to insert in Django templates. Args: - snapshot_id (str): snapshot identifier - link_text (str): optional text for the generated link + snapshot_id: snapshot identifier + link_text: optional text for the generated link (the snapshot id will be used by default) - link_attrs (dict): optional attributes (e.g. class) + link_attrs: optional attributes (e.g. class) to add to the link Returns: @@ -442,20 +469,23 @@ def gen_content_link( - sha1_git, - snapshot_context=None, - link_text="Browse", - link_attrs={"class": "btn btn-default btn-sm", "role": "button"}, -): + sha1_git: str, + snapshot_context: Optional[SnapshotContext] = None, + link_text: Optional[str] = "Browse", + link_attrs: Optional[Dict[str, str]] = { + "class": "btn btn-default btn-sm", + "role": "button", + }, +) -> Optional[str]: """ Utility function for generating a link to a content HTML view to insert in Django templates. Args: - sha1_git (str): content identifier - link_text (str): optional text for the generated link + sha1_git: content identifier + link_text: optional text for the generated link (the content sha1_git will be used by default) - link_attrs (dict): optional attributes (e.g. class) + link_attrs: optional attributes (e.g. class) to add to the link Returns: @@ -477,14 +507,16 @@ return gen_link(content_url, link_text, link_attrs) -def get_revision_log_url(revision_id, snapshot_context=None): +def get_revision_log_url( + revision_id: str, snapshot_context: Optional[SnapshotContext] = None +) -> str: """ Utility function for getting the URL for a revision log HTML view (possibly in the context of an origin). Args: - revision_id (str): revision identifier the history heads to - snapshot_context (dict): if provided, generate snapshot-dependent + revision_id: revision identifier the history heads to + snapshot_context: if provided, generate snapshot-dependent browsing link Returns: The revision log view URL @@ -510,22 +542,25 @@ def gen_revision_log_link( - revision_id, - snapshot_context=None, - link_text="Browse", - link_attrs={"class": "btn btn-default btn-sm", "role": "button"}, -): + revision_id: str, + snapshot_context: Optional[SnapshotContext] = None, + link_text: Optional[str] = "Browse", + link_attrs: Optional[Dict[str, str]] = { + "class": "btn btn-default btn-sm", + "role": "button", + }, +) -> Optional[str]: """ Utility function for generating a link to a revision log HTML view (possibly in the context of an origin) to insert in Django templates. Args: - revision_id (str): revision identifier the history heads to - snapshot_context (dict): if provided, generate snapshot-dependent + revision_id: revision identifier the history heads to + snapshot_context: if provided, generate snapshot-dependent browsing link - link_text (str): optional text to use for the generated link + link_text: optional text to use for the generated link (the revision id will be used by default) - link_attrs (dict): optional attributes (e.g. class) + link_attrs: optional attributes (e.g. class) to add to the link Returns: @@ -542,15 +577,17 @@ return gen_link(revision_log_url, link_text, link_attrs) -def gen_person_mail_link(person, link_text=None): +def gen_person_mail_link( + person: Dict[str, Any], link_text: Optional[str] = None +) -> str: """ Utility function for generating a mail link to a person to insert in Django templates. Args: - person (dict): dictionary containing person data + person: dictionary containing person data (*name*, *email*, *fullname*) - link_text (str): optional text to use for the generated mail link + link_text: optional text to use for the generated mail link (the person name will be used by default) Returns: @@ -570,20 +607,23 @@ def gen_release_link( - sha1_git, - snapshot_context=None, - link_text="Browse", - link_attrs={"class": "btn btn-default btn-sm", "role": "button"}, -): + sha1_git: str, + snapshot_context: Optional[SnapshotContext] = None, + link_text: Optional[str] = "Browse", + link_attrs: Optional[Dict[str, str]] = { + "class": "btn btn-default btn-sm", + "role": "button", + }, +) -> str: """ Utility function for generating a link to a release HTML view to insert in Django templates. Args: - sha1_git (str): release identifier - link_text (str): optional text for the generated link + sha1_git: release identifier + link_text: optional text for the generated link (the release id will be used by default) - link_attrs (dict): optional attributes (e.g. class) + link_attrs: optional attributes (e.g. class) to add to the link Returns: @@ -601,7 +641,11 @@ return gen_link(release_url, link_text, link_attrs) -def format_log_entries(revision_log, per_page, snapshot_context=None): +def format_log_entries( + revision_log: Iterator[Optional[Dict[str, Any]]], + per_page: int, + snapshot_context: Optional[SnapshotContext] = None, +) -> List[Dict[str, str]]: """ Utility functions that process raw revision log data for HTML display. Its purpose is to: @@ -611,15 +655,17 @@ * truncate the message log Args: - revision_log (list): raw revision log as returned by the swh-web api - per_page (int): number of log entries per page - snapshot_context (dict): if provided, generate snapshot-dependent + revision_log: raw revision log as returned by the swh-web api + per_page: number of log entries per page + snapshot_context: if provided, generate snapshot-dependent browsing link """ revision_log_data = [] for i, rev in enumerate(revision_log): + if rev is None: + continue if i == per_page: break author_name = "None" @@ -666,17 +712,19 @@ ] -def get_readme_to_display(readmes): +def get_readme_to_display( + readmes: Dict[str, str] +) -> Tuple[Optional[str], Optional[str], Optional[str]]: """ Process a list of readme files found in a directory in order to find the adequate one to display. Args: - readmes: a list of dict where keys are readme file names and values + readmes: a dict where keys are readme file names and values are readme sha1s Returns: - A tuple (readme_name, readme_sha1) + A tuple (readme_name, readme_url, readme_html) """ readme_name = None readme_url = None 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: @@ -256,7 +262,7 @@ browse_context="content", ) except NotFoundExc as e: - if str(e).startswith("Origin"): + if str(e).startswith("Origin") and origin_url is not None: raw_cnt_url = reverse( "browse-content", url_args={"query_string": query_string} ) @@ -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") @@ -54,9 +58,9 @@ path=path, ) except NotFoundExc as e: - if str(e).startswith("Origin"): + if str(e).startswith("Origin") and origin_url is not None: 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 Any, Dict, List, Optional, cast + +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) + origin_visits = cast(List[Dict[str, Any]], get_origin_visits(origin_info)) + snapshot_context = get_snapshot_context(origin_url=origin_url) for i, visit in enumerate(origin_visits): @@ -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]