8
8
import sys
9
9
import tempfile
10
10
import time
11
+ import typing
11
12
import urllib .error
12
13
import urllib .parse
13
14
import urllib .request
@@ -47,7 +48,7 @@ def set_verbosity(enable_verbosity: bool) -> None:
47
48
# INFO: manually specified output and all higher log level output
48
49
verbose_logging_level = logging .DEBUG
49
50
50
- if type (enable_verbosity ) is not bool :
51
+ if not isinstance (enable_verbosity , bool ) :
51
52
raise TypeError
52
53
if enable_verbosity :
53
54
logger .setLevel (level = verbose_logging_level )
@@ -523,6 +524,43 @@ def get_summary_value(self, show_emoji: bool, minimum, maximum) -> str:
523
524
524
525
return value
525
526
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
+
526
564
def comment_report (self , pr_number : int , report_markdown : str ) -> None :
527
565
"""Submit the report as a comment on the PR thread.
528
566
@@ -532,9 +570,13 @@ def comment_report(self, pr_number: int, report_markdown: str) -> None:
532
570
"""
533
571
print ("::debug::Adding deltas report comment to pull request" )
534
572
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
536
578
537
- self .http_request (url = url , data = report_data )
579
+ self .http_request (url = url , data = report_data , method = method )
538
580
539
581
def api_request (self , request : str , request_parameters : str = "" , page_number : int = 1 ):
540
582
"""Do a GitHub API request. Return a dictionary containing:
@@ -594,7 +636,7 @@ def get_json_response(self, url: str):
594
636
except Exception as exception :
595
637
raise exception
596
638
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 ):
598
640
"""Make a request and return a dictionary:
599
641
read -- the response
600
642
info -- headers
@@ -604,28 +646,32 @@ def http_request(self, url: str, data: bytes | None = None):
604
646
url -- the URL to load
605
647
data -- data to pass with the request
606
648
(default value: None)
649
+ method -- the HTTP request method to use
650
+ (default is None which means ``'GET' if data is None else 'POST'``).
607
651
"""
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 :
609
653
return {
610
654
"body" : response_object .read ().decode (encoding = "utf-8" , errors = "ignore" ),
611
655
"headers" : response_object .info (),
612
656
"url" : response_object .geturl (),
613
657
}
614
658
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 ):
616
660
"""Make a request and return an object containing the response.
617
661
618
662
Keyword arguments:
619
663
url -- the URL to load
620
664
data -- data to pass with the request
621
665
(default value: None)
666
+ method -- the HTTP request method to use
667
+ (default is None which means ``'GET' if data is None else 'POST'``).
622
668
"""
623
669
# Maximum times to retry opening the URL before giving up
624
670
maximum_urlopen_retries = 3
625
671
626
672
logger .info ("Opening URL: " + url )
627
673
628
- request = urllib .request .Request (url = url , data = data )
674
+ request = urllib .request .Request (url = url , data = data , method = method )
629
675
request .add_unredirected_header (key = "Accept" , val = "application/vnd.github+json" )
630
676
request .add_unredirected_header (key = "Authorization" , val = "Bearer " + self .token )
631
677
request .add_unredirected_header (key = "User-Agent" , val = self .repository_name .split ("/" )[0 ])
0 commit comments