From 26fa4708f3380a85d8dd9bc5b04de0b84d26ec0c Mon Sep 17 00:00:00 2001 From: Mark Nottingham Date: Wed, 13 Dec 2023 21:53:09 +1100 Subject: [PATCH] Strict optional type checking --- pyproject.toml | 2 +- redbot/cache_file.py | 3 +- redbot/daemon.py | 5 ++- redbot/formatter/__init__.py | 10 ++--- redbot/formatter/har.py | 10 ++++- redbot/formatter/html.py | 4 +- redbot/formatter/html_base.py | 17 ++++---- redbot/formatter/slack.py | 13 +++--- redbot/formatter/text.py | 6 +-- redbot/resource/__init__.py | 12 +++--- redbot/resource/active_check/base.py | 4 +- redbot/resource/active_check/conneg.py | 4 +- redbot/resource/active_check/etag_validate.py | 2 +- redbot/resource/active_check/lm_validate.py | 2 +- redbot/resource/active_check/range.py | 8 ++-- redbot/resource/fetch.py | 31 ++++++++++---- redbot/resource/link_parse.py | 21 ++++++---- redbot/type.py | 3 +- redbot/webui/__init__.py | 41 ++++++++++--------- redbot/webui/ratelimit.py | 2 +- redbot/webui/saved_tests.py | 6 ++- 21 files changed, 116 insertions(+), 90 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c211250c..11006353 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ warn_redundant_casts = true warn_unused_ignores = true warn_return_any = true warn_unreachable = true -strict_optional = false +strict_optional = true show_error_codes = true [tool.pylint.basic] diff --git a/redbot/cache_file.py b/redbot/cache_file.py index 3d02667c..afcf84e9 100644 --- a/redbot/cache_file.py +++ b/redbot/cache_file.py @@ -2,6 +2,7 @@ import os from os import path import time +from typing import Optional import zlib @@ -14,7 +15,7 @@ class CacheFile: def __init__(self, my_path: str) -> None: self.path = my_path - def read(self) -> bytes: + def read(self) -> Optional[bytes]: """ Read the file, returning its contents. If it does not exist or cannot be read, returns None. diff --git a/redbot/daemon.py b/redbot/daemon.py index d78d0bdf..03ba59ad 100755 --- a/redbot/daemon.py +++ b/redbot/daemon.py @@ -131,6 +131,9 @@ def __init__( self.uri = b"" self.req_hdrs: RawHeaderListType = [] self.req_body = b"" + if not exchange.http_conn.tcp_conn: + return + self.client_ip = exchange.http_conn.tcp_conn.socket.getpeername()[0] exchange.on("request_start", self.request_start) exchange.on("request_body", self.request_body) exchange.on("request_done", self.request_done) @@ -148,7 +151,7 @@ def request_body(self, chunk: bytes) -> None: def request_done(self, trailers: RawHeaderListType) -> None: p_uri = urlsplit(self.uri) if p_uri.path == b"/": - client_ip = self.exchange.http_conn.tcp_conn.socket.getpeername()[0] + client_ip = self.client_ip try: RedWebUi( self.server.config, diff --git a/redbot/formatter/__init__.py b/redbot/formatter/__init__.py index 9d75e06c..f4c286b5 100644 --- a/redbot/formatter/__init__.py +++ b/redbot/formatter/__init__.py @@ -9,7 +9,7 @@ import locale import sys import time -from typing import Any, Callable, List, Dict, Type, TYPE_CHECKING +from typing import Optional, Any, Callable, List, Dict, Type, TYPE_CHECKING import unittest from markdown import Markdown @@ -75,8 +75,8 @@ class Formatter(EventEmitter): Is available to UIs based upon the 'name' attribute. """ - media_type: str = None # the media type of the format. - name: str = None # the name of the format. + media_type: str # the media type of the format. + name: str = "base class" # the name of the format. can_multiple = False # formatter can represent multiple responses. def __init__( @@ -184,7 +184,7 @@ def f_num(i: int, by1024: bool = False) -> str: return locale.format_string("%d", i, grouping=True) -def relative_time(utime: float, now: float = None, show_sign: int = 1) -> str: +def relative_time(utime: float, now: Optional[float] = None, show_sign: int = 1) -> str: """ Given two times, return a string that explains how far apart they are. show_sign can be: @@ -199,8 +199,6 @@ def relative_time(utime: float, now: float = None, show_sign: int = 1) -> str: 2: ("none", "behind", "ahead"), } - if utime is None: - return None if now is None: now = time.time() age = round(now - utime) diff --git a/redbot/formatter/har.py b/redbot/formatter/har.py index 08ce738c..499c42eb 100644 --- a/redbot/formatter/har.py +++ b/redbot/formatter/har.py @@ -5,7 +5,7 @@ import datetime import json -from typing import Any, Dict, List +from typing import Optional, Any, Dict, List from typing_extensions import TypedDict from redbot import __version__ @@ -71,7 +71,12 @@ def finish_output(self) -> None: def error_output(self, message: str) -> None: self.output(message) - def add_entry(self, resource: HttpResource, page_ref: int = None) -> None: + def add_entry(self, resource: HttpResource, page_ref: Optional[int] = None) -> None: + assert resource.request.start_time, "request.start_time not set in add_entry" + assert resource.response.start_time, "response.start_time not set in add_entry" + assert ( + resource.response.finish_time + ), "response.finish_time not set in add_entry" entry = { "startedDateTime": isoformat(resource.request.start_time), "time": int( @@ -135,6 +140,7 @@ def add_entry(self, resource: HttpResource, page_ref: int = None) -> None: self.har["log"]["entries"].append(entry) def add_page(self, resource: HttpResource) -> int: + assert resource.request.start_time, "request.start_time not set in add_page" page_id = self.last_id + 1 page = { "startedDateTime": isoformat(resource.request.start_time), diff --git a/redbot/formatter/html.py b/redbot/formatter/html.py index a65d0dcd..3e245ad6 100644 --- a/redbot/formatter/html.py +++ b/redbot/formatter/html.py @@ -141,7 +141,9 @@ def format_body_sample(self, resource: HttpResource) -> Markup: """show the stored body sample""" sample = b"".join(resource.response_decoded_sample) try: - uni_sample = sample.decode(resource.response.character_encoding, "ignore") + uni_sample = sample.decode( + resource.response.character_encoding or "utf-8", "ignore" + ) except (TypeError, LookupError): uni_sample = sample.decode("utf-8", "replace") safe_sample = escape(uni_sample) diff --git a/redbot/formatter/html_base.py b/redbot/formatter/html_base.py index dd2d6bec..2fadd6e0 100644 --- a/redbot/formatter/html_base.py +++ b/redbot/formatter/html_base.py @@ -3,7 +3,7 @@ import json import os import time -from typing import Any, List, Tuple +from typing import Optional, Any, List, Tuple from urllib.parse import urljoin, urlencode, quote as urlquote import httplint @@ -94,12 +94,8 @@ def feed(self, sample: bytes) -> None: pass def start_output(self) -> None: - if self.resource is None: - uri = "" - req_headers = [] - else: - uri = self.resource.request.uri or "" - req_headers = self.resource.request.headers.text + uri = self.resource.request.uri or "" + req_headers = self.resource.request.headers.text extra_title = " " if self.kw.get("is_saved", None): extra_title += " saved " @@ -229,9 +225,9 @@ def format_extra(self, etype: str = ".html") -> Markup: def redbot_link( self, link_value: str, - link: str = None, - check_name: str = None, - res_format: str = None, + link: Optional[str] = None, + check_name: Optional[str] = None, + res_format: Optional[str] = None, use_stored: bool = True, descend: bool = False, referer: bool = False, @@ -261,6 +257,7 @@ def redbot_link( Request headers are copied over from the current context. """ + assert self.resource.request.uri, "resource.request.uri not set in redbot_link" uri = self.resource.request.uri args: List[Tuple[str, str]] = [] if check_name: diff --git a/redbot/formatter/slack.py b/redbot/formatter/slack.py index 60d6cbb1..3dbf95fb 100644 --- a/redbot/formatter/slack.py +++ b/redbot/formatter/slack.py @@ -3,7 +3,7 @@ """ import json -from typing import Any, List, Dict, Union +from typing import Optional, Any, List, Dict, Union from httplint import HttpResponseLinter from httplint.note import categories, levels @@ -59,12 +59,7 @@ def finish_output(self) -> None: + self.link_saved() ) else: - if self.resource.fetch_error is None: - notification = "No response error." - else: - notification = ( - f"Sorry, I can't do that; {self.resource.fetch_error.desc}" - ) + notification = f"Sorry, I can't do that; {self.resource.fetch_error.desc}" blocks = [self.markdown_block(f"_{notification}_")] self.send_slack_message(blocks, notification) @@ -74,7 +69,9 @@ def error_output(self, message: str) -> None: def timeout(self) -> None: self.send_slack_message([self.markdown_block("_Timed out._")], "Timed out.") - def send_slack_message(self, blocks: List[Dict], notification: str = None) -> None: + def send_slack_message( + self, blocks: List[Dict], notification: Optional[str] = None + ) -> None: data: Dict[str, Any] = {"blocks": blocks} if notification: data["text"] = notification diff --git a/redbot/formatter/text.py b/redbot/formatter/text.py index f718d297..83b2740d 100644 --- a/redbot/formatter/text.py +++ b/redbot/formatter/text.py @@ -6,7 +6,7 @@ import operator import re import textwrap -from typing import Any, List +from typing import Any, List, Optional from httplint import HttpResponseLinter from httplint.note import Note, levels, categories @@ -117,7 +117,7 @@ def format_recommendation( def format_text(note: Note) -> List[str]: return textwrap.wrap(strip_tags(re.sub(r"(?m)\s\s+", " ", note.detail))) - def colorize(self, level: levels, instr: str) -> str: + def colorize(self, level: Optional[levels], instr: str) -> str: if self.kw.get("tty_out", False): # info color_start = "\033[0;32m" @@ -188,7 +188,7 @@ def finish_output(self) -> None: self.output(self.format_recommendations(subresource) + NL + NL) def format_uri(self, resource: HttpResource) -> str: - return self.colorize(None, resource.request.uri) + return self.colorize(None, resource.request.uri or "") class VerboseTextListFormatter(TextListFormatter): diff --git a/redbot/resource/__init__.py b/redbot/resource/__init__.py index 6c979a1d..3f446248 100644 --- a/redbot/resource/__init__.py +++ b/redbot/resource/__init__.py @@ -11,7 +11,7 @@ from configparser import SectionProxy import sys -from typing import List, Dict, Set, Tuple, Union +from typing import Optional, List, Dict, Set, Tuple, Union from urllib.parse import urljoin import thor @@ -43,10 +43,10 @@ def __init__(self, config: SectionProxy, descend: bool = False) -> None: RedFetcher.__init__(self, config) self.descend: bool = descend self.check_done: bool = False - self.partial_support: bool = None - self.inm_support: bool = None - self.ims_support: bool = None - self.gzip_support: bool = None + self.partial_support: bool = False + self.inm_support: bool = False + self.ims_support: bool = False + self.gzip_support: bool = False self.gzip_savings: int = 0 self._task_map: Set[RedFetcher] = set([]) self.subreqs = {ac.check_name: ac(config, self) for ac in active_checks} @@ -91,7 +91,7 @@ def check_done() -> None: # pylint: enable=cell-var-from-loop - def finish_check(self, resource: RedFetcher = None) -> None: + def finish_check(self, resource: Optional[RedFetcher] = None) -> None: "A check is done. Was that the last one?" if resource: try: diff --git a/redbot/resource/active_check/base.py b/redbot/resource/active_check/base.py index c4d5fc08..ca406919 100644 --- a/redbot/resource/active_check/base.py +++ b/redbot/resource/active_check/base.py @@ -53,8 +53,8 @@ def check(self) -> None: ) RedFetcher.set_request( self, - self.base.request.uri, - self.base.request.method, + self.base.request.uri or "", + self.base.request.method or "", modified_headers, self.base.request_content, ) diff --git a/redbot/resource/active_check/conneg.py b/redbot/resource/active_check/conneg.py index 8efa85bf..845d648e 100644 --- a/redbot/resource/active_check/conneg.py +++ b/redbot/resource/active_check/conneg.py @@ -63,8 +63,8 @@ def done(self) -> None: self.add_base_note( "status", VARY_STATUS_MISMATCH, - neg_status=negotiated.status_code, - noneg_status=bare.status_code, + neg_status=negotiated.status_code or 0, + noneg_status=bare.status_code or 0, ) return # Can't be sure what's going on... diff --git a/redbot/resource/active_check/etag_validate.py b/redbot/resource/active_check/etag_validate.py index 9974472a..4a65aab0 100644 --- a/redbot/resource/active_check/etag_validate.py +++ b/redbot/resource/active_check/etag_validate.py @@ -78,7 +78,7 @@ def done(self) -> None: self.add_base_note( "header-etag", INM_STATUS, - inm_status=self.response.status_code, + inm_status=self.response.status_code or 0, enc_inm_status=self.response.status_code or "(unknown)", ) diff --git a/redbot/resource/active_check/lm_validate.py b/redbot/resource/active_check/lm_validate.py index 09860805..db2c2ab5 100644 --- a/redbot/resource/active_check/lm_validate.py +++ b/redbot/resource/active_check/lm_validate.py @@ -94,7 +94,7 @@ def done(self) -> None: self.add_base_note( "header-last-modified", IMS_STATUS, - ims_status=self.response.status_code, + ims_status=self.response.status_code or 0, enc_ims_status=self.response.status_code or "(unknown)", ) diff --git a/redbot/resource/active_check/range.py b/redbot/resource/active_check/range.py index acfdcd7a..fbdd2b11 100644 --- a/redbot/resource/active_check/range.py +++ b/redbot/resource/active_check/range.py @@ -23,9 +23,9 @@ class RangeRequest(SubRequest): response_phrase = "The partial response" def __init__(self, config: SectionProxy, resource: "HttpResource") -> None: - self.range_start: int = None - self.range_end: int = None - self.range_target: bytes = None + self.range_start: int + self.range_end: int + self.range_target: bytes self.max_sample_size = 0 # unlimited SubRequest.__init__(self, config, resource) @@ -119,7 +119,7 @@ def done(self) -> None: self.add_base_note( "header-accept-ranges", RANGE_STATUS, - range_status=self.response.status_code, + range_status=self.response.status_code or 0, enc_range_status=self.response.status_code or "(unknown)", ) diff --git a/redbot/resource/fetch.py b/redbot/resource/fetch.py index c67a12d6..7dedb562 100644 --- a/redbot/resource/fetch.py +++ b/redbot/resource/fetch.py @@ -8,7 +8,7 @@ from configparser import SectionProxy import time -from typing import Any, Dict, List, Tuple, Callable +from typing import Optional, Any, Dict, List, Tuple, Callable from httplint import HttpRequestLinter, HttpResponseLinter from httplint.note import Note, categories, levels @@ -27,7 +27,7 @@ class RedHttpClient(thor.http.HttpClient): "Thor HttpClient for RedFetcher" - def __init__(self, loop: thor.loop.LoopBase = None) -> None: + def __init__(self, loop: Optional[thor.loop.LoopBase] = None) -> None: thor.http.HttpClient.__init__(self, loop) self.connect_timeout = 10 self.read_timeout = 15 @@ -69,7 +69,7 @@ def __init__(self, config: SectionProxy) -> None: self.nonfinal_responses: List[HttpResponseLinter] = [] self.response = HttpResponseLinter(message_ref=self.response_phrase) self.response.decoded.processors.append(self.sample_decoded) - self.exchange: HttpClientExchange = None + self.exchange: HttpClientExchange self.fetch_started = False self.fetch_error: httperr.HttpError self.fetch_done = False @@ -77,7 +77,10 @@ def __init__(self, config: SectionProxy) -> None: def __getstate__(self) -> Dict[str, Any]: state: Dict[str, Any] = thor.events.EventEmitter.__getstate__(self) - del state["exchange"] + try: + del state["exchange"] + except KeyError: + pass del state["response_content_processors"] return state @@ -123,7 +126,7 @@ def set_request( self, iri: str, method: str = "GET", - headers: StrHeaderListType = None, + headers: Optional[StrHeaderListType] = None, content: bytes = b"", ) -> None: """ @@ -135,7 +138,8 @@ def set_request( self.request.set_uri(iri) except httperr.UrlError as why: self.fetch_error = why - self.response.base_uri = self.request.uri + if self.request.uri: + self.response.base_uri = self.request.uri if headers: bheaders = [(n.encode("utf-8"), v.encode("utf-8")) for (n, v) in headers] self.request.process_headers(bheaders) @@ -155,6 +159,8 @@ def check(self) -> None: return self.fetch_started = True + assert self.request.method, "method not set in check" + assert self.request.uri, "uri not set in check" if "user-agent" not in [i[0].lower() for i in self.request.headers.text]: self.request.headers.process([(b"User-Agent", UA_STRING)]) @@ -190,6 +196,9 @@ def _response_nonfinal( nfres = HttpResponseLinter( message_ref="A non-final response", start_time=time.time() ) + assert ( + self.exchange.res_version + ), "exchange.res_version not set in _response_nonfinal" nfres.process_response_topline(self.exchange.res_version, status, phrase) nfres.process_headers(res_headers) nfres.finish_content(True) @@ -200,6 +209,9 @@ def _response_start( ) -> None: "Process the response start-line and headers." self.response.start_time = time.time() + assert ( + self.exchange.res_version + ), "exchange.res_version not set in _response_start" self.response.process_response_topline( self.exchange.res_version, status, phrase ) @@ -245,6 +257,7 @@ def _response_error(self, error: httperr.HttpError) -> None: "debug", f"fetch error {self.request.uri} ({self.check_name}) - {error.desc}", ) + assert error.detail, "detail not set in _response_error" err_sample = error.detail[:40] or "" if isinstance(error, httperr.ExtraDataError): if self.response.status_code == 304: @@ -256,6 +269,7 @@ def _response_error(self, error: httperr.HttpError) -> None: "header-transfer-encoding", BAD_CHUNK, chunk_sample=err_sample ) elif isinstance(error, httperr.HeaderSpaceError): + assert error.detail, "error.detail not set in _response_error" subject = f"header-{error.detail.lower().strip()}" self.response.notes.add( subject, HEADER_NAME_SPACE, header_name=error.detail @@ -268,7 +282,10 @@ def _fetch_done(self) -> None: self.response.finish_time = time.time() if not self.fetch_done: self.fetch_done = True - self.exchange = None + try: + delattr(self, "exchange") + except AttributeError: + pass self.emit("fetch_done") diff --git a/redbot/resource/link_parse.py b/redbot/resource/link_parse.py index ab2ab770..c317802f 100755 --- a/redbot/resource/link_parse.py +++ b/redbot/resource/link_parse.py @@ -7,7 +7,7 @@ import codecs from html.parser import HTMLParser -from typing import Any, Callable, Dict, List, Tuple +from typing import Optional, Any, Callable, Dict, List, Tuple from httplint.field.utils import split_string, unquote_string from httplint.message import HttpMessageLinter @@ -42,12 +42,12 @@ def __init__( self, message: HttpMessageLinter, link_procs: List[Callable[[str, str, str, str], None]], - err: Callable[[str], int] = None, + err: Optional[Callable[[str], int]] = None, ) -> None: self.message = message self.link_procs = link_procs self.err = err - self.link_types: Dict[str, Tuple[str, List[str]]] = { + self.link_types: Dict[str, Tuple[str, Optional[List[str]]]] = { "link": ("href", ["stylesheet"]), "a": ("href", None), "img": ("src", None), @@ -56,7 +56,7 @@ def __init__( "iframe": ("src", None), } self.errors = 0 - self.last_err_pos: int = None + self.last_err_pos: int = 0 self.ok = True HTMLParser.__init__(self) @@ -89,9 +89,9 @@ def feed(self, data: str) -> None: else: self.ok = False - def handle_starttag(self, tag: str, attrs: List[Tuple[str, str]]) -> None: + def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None: attr_d = dict(attrs) - title = attr_d.get("title", "").strip() + title = (attr_d.get("title", "") or "").strip() if tag in self.link_types: url_attr, rels = self.link_types[tag] if not rels or attr_d.get("rel", None) in rels: @@ -102,8 +102,11 @@ def handle_starttag(self, tag: str, attrs: List[Tuple[str, str]]) -> None: for proc in self.link_procs: proc(self.message.base_uri, target, tag, title) elif tag == "base": - self.message.base_uri = attr_d.get("href", self.message.base_uri) - elif tag == "meta" and attr_d.get("http-equiv", "").lower() == "content-type": + self.message.base_uri = attr_d.get("href", self.message.base_uri) or "" + elif ( + tag == "meta" + and (attr_d.get("http-equiv", "") or "").lower() == "content-type" + ): ct = attr_d.get("content", None) if ct: try: @@ -111,7 +114,7 @@ def handle_starttag(self, tag: str, attrs: List[Tuple[str, str]]) -> None: except ValueError: media_type, params = ct, "" media_type = media_type.lower() - param_dict = {} + param_dict: Dict[str, Optional[str]] = {} for param in split_string(params, rfc7231.parameter, r"\s*;\s*"): try: attr, val = param.split("=", 1) diff --git a/redbot/type.py b/redbot/type.py index 892c9c6b..3e8ea573 100644 --- a/redbot/type.py +++ b/redbot/type.py @@ -1,10 +1,9 @@ -from typing import Any, Callable, Dict, List, Tuple +from typing import Callable, Dict, List, Tuple from typing_extensions import Protocol StrHeaderListType = List[Tuple[str, str]] RawHeaderListType = List[Tuple[bytes, bytes]] -HeaderDictType = Dict[str, Any] ParamDictType = Dict[str, str] AddNoteMethodType = Callable[..., None] diff --git a/redbot/webui/__init__.py b/redbot/webui/__init__.py index 5ed58ba9..ff186ee0 100644 --- a/redbot/webui/__init__.py +++ b/redbot/webui/__init__.py @@ -11,7 +11,7 @@ import string import sys import time -from typing import Any, Callable, Dict, List, Tuple, Union, cast +from typing import Optional, Callable, Dict, List, Tuple, Union, cast from urllib.parse import parse_qs, urlsplit, urlencode import thor @@ -56,7 +56,7 @@ def __init__( req_body: bytes, exchange: HttpResponseExchange, client_ip: str, - console: Callable[[str], int] = sys.stderr.write, + console: Callable[[str], Optional[int]] = sys.stderr.write, ) -> None: self.config: SectionProxy = config self.charset = self.config["charset"] @@ -84,12 +84,12 @@ def __init__( ] self.format = self.query_string.get("format", ["html"])[0] self.descend = "descend" in self.query_string - self.check_name: str = None + self.check_name: Optional[str] = None if not self.descend: self.check_name = self.query_string.get("check_name", [None])[0] - self.save_path: str = None - self.timeout: Any = None + self.save_path: str + self.timeout: Optional[thor.loop.ScheduledEvent] = None self.nonce: str = standard_b64encode( getrandbits(128).to_bytes(16, "big") @@ -125,7 +125,10 @@ def __init__( else: self.error_response( find_formatter("html")( - self.config, None, self.output, nonce=self.nonce + self.config, + HttpResource(self.config), + self.output, + nonce=self.nonce, ), b"405", b"Method Not Allowed", @@ -204,7 +207,7 @@ def continue_test( self, top_resource: HttpResource, formatter: Formatter, - extra_headers: RawHeaderListType = None, + extra_headers: Optional[RawHeaderListType] = None, ) -> None: "Preliminary checks are done; actually run the test." @@ -271,23 +274,21 @@ def dump_client_error(self) -> None: def show_default(self) -> None: """Show the default page.""" + resource = HttpResource(self.config, descend=self.descend) + if self.test_uri: + resource.set_request(self.test_uri, headers=self.req_hdrs) formatter = html.BaseHtmlFormatter( self.config, - None, + resource, self.output, is_blank=self.test_uri == "", nonce=self.nonce, ) - if self.test_uri: - top_resource = HttpResource(self.config, descend=self.descend) - top_resource.set_request(self.test_uri, headers=self.req_hdrs) - if self.check_name: - formatter.resource = cast( - HttpResource, - top_resource.subreqs.get(self.check_name, top_resource), - ) - else: - formatter.resource = top_resource + if self.check_name: + formatter.resource = cast( + HttpResource, + resource.subreqs.get(self.check_name, resource), + ) self.exchange.response_start( b"200", b"OK", @@ -310,7 +311,7 @@ def error_response( status_code: bytes, status_phrase: bytes, message: str, - log_message: str = None, + log_message: Optional[str] = None, ) -> None: """Send an error response.""" if self.timeout: @@ -341,7 +342,7 @@ def error_log(self, message: str) -> None: self.console(f"{self.get_client_ip()}: {message}") def timeout_error( - self, formatter: Formatter, detail: Callable[[], str] = None + self, formatter: Formatter, detail: Optional[Callable[[], str]] = None ) -> None: """Max runtime reached.""" details = "" diff --git a/redbot/webui/ratelimit.py b/redbot/webui/ratelimit.py index 2b6c960a..dac9bea9 100644 --- a/redbot/webui/ratelimit.py +++ b/redbot/webui/ratelimit.py @@ -168,7 +168,7 @@ def url_to_origin(url: str) -> Union[str, None]: p_url = urlsplit(url) origin = ( f"{p_url.scheme.lower()}://" - f"{p_url.hostname.lower()}:" + f"{(p_url.hostname or '').lower()}:" f"{p_url.port or default_port.get(p_url.scheme, 0)}" ) except (AttributeError, ValueError): diff --git a/redbot/webui/saved_tests.py b/redbot/webui/saved_tests.py index 48fb53df..8b953fec 100644 --- a/redbot/webui/saved_tests.py +++ b/redbot/webui/saved_tests.py @@ -4,7 +4,7 @@ import pickle import tempfile import time -from typing import TYPE_CHECKING, cast, IO, Tuple +from typing import TYPE_CHECKING, cast, IO, Tuple, Optional import zlib import thor @@ -16,7 +16,7 @@ from redbot.webui import RedWebUi # pylint: disable=cyclic-import,unused-import -def init_save_file(webui: "RedWebUi") -> str: +def init_save_file(webui: "RedWebUi") -> Optional[str]: if webui.config.get("save_dir", "") and os.path.exists(webui.config["save_dir"]): try: fd, webui.save_path = tempfile.mkstemp( @@ -42,6 +42,7 @@ def save_test(webui: "RedWebUi", top_resource: HttpResource) -> None: def extend_saved_test(webui: "RedWebUi") -> None: """Extend the expiry time of a previously run test_id.""" + assert webui.test_id, "test_id not set in extend_saved_test" try: # touch the save file so it isn't deleted. now = time.time() @@ -99,6 +100,7 @@ def clean_saved_tests(config: SectionProxy) -> Tuple[int, int, int]: def load_saved_test(webui: "RedWebUi") -> None: """Load a saved test by test_id.""" + assert webui.test_id, "test_id not set in load_saved_test" try: with cast( IO[bytes],