88import sys
99import tempfile
1010import time
11+ import typing
1112import urllib .error
1213import urllib .parse
1314import urllib .request
@@ -47,7 +48,7 @@ def set_verbosity(enable_verbosity: bool) -> None:
4748 # INFO: manually specified output and all higher log level output
4849 verbose_logging_level = logging .DEBUG
4950
50- if type (enable_verbosity ) is not bool :
51+ if not isinstance (enable_verbosity , bool ) :
5152 raise TypeError
5253 if enable_verbosity :
5354 logger .setLevel (level = verbose_logging_level )
@@ -523,6 +524,43 @@ def get_summary_value(self, show_emoji: bool, minimum, maximum) -> str:
523524
524525 return value
525526
527+ def get_previous_comment (self , url : str ) -> str | None :
528+ """Get a previous comment to update.
529+
530+ Arguments:
531+ url -- The URL used to traverse existing comments of a PR thread.
532+ To get the comment total, this str should be in the form:
533+ "{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/issues/{PR_NUMBER}"
534+ Returns:
535+ - A URL str to use for PATCHing the latest applicable comment.
536+ - None if no previous/applicable comments exist.
537+ """
538+ comment_url : str | None = None
539+
540+ pr_info = self .get_json_response (url )
541+ comment_count = typing .cast (typing .Dict [str , int ], pr_info ["json_data" ])["comments" ]
542+
543+ comments_api_url = url + "/comments"
544+ comments_response = self .get_json_response (url = comments_api_url )
545+ while comment_count :
546+ comments = typing .cast (
547+ typing .List [typing .Dict [str , typing .Any ]],
548+ comments_response ["json_data" ],
549+ )
550+ for comment in comments :
551+ if typing .cast (str , comment ["body" ]).startswith (self .report_key_beginning ):
552+ if comment_url is not None : # we have more than 1 old comment
553+ # delete old comment
554+ self .http_request (url = comment_url , method = "DELETE" )
555+
556+ # keep track of latest posted comment only
557+ comment_url = typing .cast (str , comment ["url" ])
558+ comment_count -= len (comments )
559+ next_page = comments_response ["page" ] + 1
560+ comments_response = self .get_json_response (url = f"{ comments_api_url } /?page={ next_page } " )
561+
562+ return comment_url
563+
526564 def comment_report (self , pr_number : int , report_markdown : str ) -> None :
527565 """Submit the report as a comment on the PR thread.
528566
@@ -532,9 +570,13 @@ def comment_report(self, pr_number: int, report_markdown: str) -> None:
532570 """
533571 print ("::debug::Adding deltas report comment to pull request" )
534572 report_data = json .dumps (obj = {"body" : report_markdown }).encode (encoding = "utf-8" )
535- url = "https://api.github.com/repos/" + self .repository_name + "/issues/" + str (pr_number ) + "/comments"
573+ url = f"https://api.github.com/repos/{ self .repository_name } /issues/{ pr_number } "
574+
575+ comment_url = self .get_previous_comment (url )
576+ url = comment_url or url + "/comments"
577+ method = "PATCH" if comment_url else None
536578
537- self .http_request (url = url , data = report_data )
579+ self .http_request (url = url , data = report_data , method = method )
538580
539581 def api_request (self , request : str , request_parameters : str = "" , page_number : int = 1 ):
540582 """Do a GitHub API request. Return a dictionary containing:
@@ -594,7 +636,7 @@ def get_json_response(self, url: str):
594636 except Exception as exception :
595637 raise exception
596638
597- def http_request (self , url : str , data : bytes | None = None ):
639+ def http_request (self , url : str , data : bytes | None = None , method : str | None = None ):
598640 """Make a request and return a dictionary:
599641 read -- the response
600642 info -- headers
@@ -604,28 +646,32 @@ def http_request(self, url: str, data: bytes | None = None):
604646 url -- the URL to load
605647 data -- data to pass with the request
606648 (default value: None)
649+ method -- the HTTP request method to use
650+ (default is None which means ``'GET' if data is None else 'POST'``).
607651 """
608- with self .raw_http_request (url = url , data = data ) as response_object :
652+ with self .raw_http_request (url = url , data = data , method = method ) as response_object :
609653 return {
610654 "body" : response_object .read ().decode (encoding = "utf-8" , errors = "ignore" ),
611655 "headers" : response_object .info (),
612656 "url" : response_object .geturl (),
613657 }
614658
615- def raw_http_request (self , url : str , data : bytes | None = None ):
659+ def raw_http_request (self , url : str , data : bytes | None = None , method : str | None = None ):
616660 """Make a request and return an object containing the response.
617661
618662 Keyword arguments:
619663 url -- the URL to load
620664 data -- data to pass with the request
621665 (default value: None)
666+ method -- the HTTP request method to use
667+ (default is None which means ``'GET' if data is None else 'POST'``).
622668 """
623669 # Maximum times to retry opening the URL before giving up
624670 maximum_urlopen_retries = 3
625671
626672 logger .info ("Opening URL: " + url )
627673
628- request = urllib .request .Request (url = url , data = data )
674+ request = urllib .request .Request (url = url , data = data , method = method )
629675 request .add_unredirected_header (key = "Accept" , val = "application/vnd.github+json" )
630676 request .add_unredirected_header (key = "Authorization" , val = "Bearer " + self .token )
631677 request .add_unredirected_header (key = "User-Agent" , val = self .repository_name .split ("/" )[0 ])
0 commit comments