diff --git a/pydocteur/github_api.py b/pydocteur/github_api.py index b202436..09bb40e 100644 --- a/pydocteur/github_api.py +++ b/pydocteur/github_api.py @@ -2,7 +2,6 @@ import requests from requests.auth import HTTPBasicAuth -from github import GithubException from github import Github from pydocteur.settings import GH_TOKEN @@ -10,7 +9,7 @@ from pydocteur.settings import REPOSITORY_NAME logger = logging.getLogger("pydocteur") -gh = Github(GH_TOKEN) +gh = Github(GH_TOKEN if GH_TOKEN else None) def get_rest_api(url: str) -> requests.Response: @@ -24,37 +23,31 @@ def get_graphql_api(query: str) -> requests.Response: return resp +def get_pull_request_from_checks(commit_sha): + prs_for_commit = gh.search_issues(f"type:pr repo:{REPOSITORY_NAME} sha:{commit_sha}") + if prs_for_commit.totalCount != 1: + logger.error("Should be exactly one PR for this sha: %s, found %s", commit_sha, prs_for_commit.totalCount) + return prs_for_commit[0] + + def get_pull_request(payload): - logger.debug("Getting repository") gh_repo = gh.get_repo(REPOSITORY_NAME) - logger.info("Trying to find PR number from payload") + logger.info("Trying to find PR from %s", payload.get("action", "A payload with: " + ", ".join(payload.keys()))) - is_run = payload.get("check_run", False) - is_suite = payload.get("check_suite", False) + head_sha = payload.get("check_suite", {}).get("head_sha") + if head_sha: + return get_pull_request_from_checks(head_sha) - if is_run or is_suite: - logger.info("Payload is from checks, ignoring") - return None + pr_number = payload.get("pull_request", {}).get("number") + if pr_number: + return gh_repo.get_pull(pr_number) - try: - try: - pr_number = payload["pull_request"]["number"] - logger.debug(f"Found PR {pr_number} first try") - except KeyError: - issue_number = payload["issue"]["number"] - logger.debug(f"Found issue {issue_number} from payload") - try: - repo = gh_repo.get_pull(issue_number) - logger.info(f"Found PR #{repo.number}") - return repo - except GithubException: - logger.debug(f"Found issue {issue_number}, returning None") - return None - except Exception: # noqa - logger.warning("Unknown payload, returning None") - logger.debug(payload) - return None - return gh_repo.get_pull(pr_number) + issue_number = payload.get("issue", {}).get("number") + if issue_number: + return gh_repo.get_pull(issue_number) + + logger.warning("Unknown payload, (action: %s)", payload.get("action", "")) + return None def get_trad_team_members() -> set: diff --git a/pydocteur/pr_status.py b/pydocteur/pr_status.py index b218150..7777239 100644 --- a/pydocteur/pr_status.py +++ b/pydocteur/pr_status.py @@ -48,12 +48,14 @@ def sort_reviews_key(review): return review.user.login, review.submitted_at last_reviews = [] - for author, reviews in groupby(sorted(pr_reviews, key=sort_reviews_key), key=lambda review: review.user.login): + for _, reviews in groupby(sorted(pr_reviews, key=sort_reviews_key), key=lambda review: review.user.login): last_reviews.append(list(reviews)[-1]) is_approved = all(review.state == "APPROVED" for review in last_reviews) logger.info( - f"is_approved for PR #{pr.number} is {is_approved}: " - + ", ".join(f"{review.user.login} has {review.state}" for review in last_reviews) + "is_pr_approved(%s): %s (%s)", + pr.number, + is_approved, + ", ".join(f"{review.user.login} has {review.state}" for review in last_reviews), ) return is_approved diff --git a/tests/cassettes/test_get_pr_from_check_run.yaml b/tests/cassettes/test_get_pr_from_check_run.yaml new file mode 100644 index 0000000..43e45f2 --- /dev/null +++ b/tests/cassettes/test_get_pr_from_check_run.yaml @@ -0,0 +1,266 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - PyGithub/Python + method: GET + uri: https://api.github.com/repos/python/python-docs-fr + response: + body: + string: !!binary | + H4sIAAAAAAAAA+1Y32/iOBD+Vyper2AIzbZEqvYqbbe6k1rudru6VV+Qkxji4sSR7cBB1P/9xnZ+ + Qe+g1A/30pcWjL/Pnyczk5kpezTuBRNvOLn0x/55L+Mxmeml3v2X2/WU/c6iu8kW//y2irKlf/98 + 4z9sf2wf/ry+7sFmnBLYmW9UwrN+zCPZnwtYnxeMzXZ+RK/25IKusAL4HDNJznt8nRHRC8oe4wua + NazAptWMfM+fXI329G2my8nmyfta4J95Et+xVfh8O7p//uFPv9xqfRhOwGJWCAZ8iVK5DBCyi3I8 + WFCVFGEhiYh4pkimBhFPUYGqsz6vri+AYyEqFmMUWNhjy2lFZNHAJqvLwt5EpWzveHuq2Wxtou3F + GeNrQO5L/W9y1GAaPM0WJ+MBUyKuEgJWAukv+sJUqlOEmP0l0v/AcTSDBKMLEp8gpkKAFO0FLyUS + JOeGqghlJGiuKM9OEbWDAx4uFjijW3wqD+AkwLWcU443+wFHVuBWpwAtoEQmOqKNNoEgEaErMOfJ + ZHtI4FKbXAfstGMNbWSqyAzHqQ47E40vb/Pc10Edk+ZpwTFfBcmi5EwJnElmTH/G52fgbGd/mIxx + BhmjSMFC5rcBKJlzsWwywsFAMxauBLzWoXmOmP0AAcQWwEHOkmwcWDS6RPC3CosIohyHXGDFj0X6 + IXE7NCXqftXuoghOHUQbONAknLtY0MCBhkpZkDd57qErGxaJ6uDIijS0ueotIXGI2OJBJ5aSLjJC + HCzXUJSoTqUheH6UuJDWDCWyn8wTxgsHmRoNJCHjoQMLvMWQoSiRTLB9baiZmzLNqRl2KAWZO8rU + DA2lEk7P2EjUFA0hvLkUPG4HjTUDKitLMpwtCrxw4Wwo4Enrd+sCb49WF4fipOUAQl0tCRoWrmms + ZdEq7Yse4trFlC1JS2lqh8O1yMGrdyoQc/k0pcde6Yf4KoIdJ3cm1X65T6y/H68+jknVDCVqM65N + 6BX3+61aZfRaY/eEqhh3cIOaAZW/5FglOjvBQTkW5P2CKwJUhhjqo8FgUCYEm2o3JcIpVi0eiLCI + Eij03q+xrBmgfkmxMmX0XEuMoaxmHMcONm0ogM4+vPfrtPjuM8+hYXQQZ+BdvpQyIhXPXHJoy9Fl + zriicxq9pZE4FFo7NOVnSbOInGPGzsFLFY0o+C20ZvrZQclIXGxj8XAF6NVtO8EIuLCDtQWxDCWy + 7V5McsY3jrmmQ6LDVRCYDcQzrKCN8Iajy/7Q73veozcMLoaBf/UEe4o87u7xhv2R1x96jyM/GF0G + F5d6T17IpKVpt3wKhsPAMzSQOitPhk8wHnjVn+91F7rbB5iUSQv7tQUFtqn/d1DEwCX34uat5632 + 32HHgCAy4SnJoZLoTD/0kGZgNQ6guUVzgfRt6BY2jf1PV+OdgiHiRQaPYOzB8horqGXh9dxdrAsN + OOEeL8kc4k6fi+XMBnkvUKKA6Y5eyQV/JpGS3bU2rXQ2rumS7gB1MdS0hba3q0R4Y5gJpVQIXo15 + MkgFTSqFiU01XYqpxCEj7QLPSVZprC/kAxWjEckk2KLUzR/cysxG4ErVNGtafZV5/Lcdkz1Mb75/ + v/32+Nv0QTulHjVZDd1JWvTp8Y49P/3lb58eb6570F/bFjMw+jtaeoFWUZu6snxM5rhgamZbABA1 + HkzgLEXSfGZdSvElgebdHtyddXwM1HZnfx8DtY+B2v8zUMuIWsNYqU42JvC7TU+dVUcv/wAuj38g + kxcAAA== + headers: + Accept-Ranges: + - bytes + Content-Length: + - '1315' + X-GitHub-Request-Id: + - 998E:DAEA:42E9D86:4D53DD3:5FC7BDEC + X-Ratelimit-Limit: + - '60' + X-Ratelimit-Remaining: + - '57' + X-Ratelimit-Reset: + - '1606929236' + X-Ratelimit-Used: + - '3' + access-control-allow-origin: + - '*' + access-control-expose-headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, + X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset + cache-control: + - public, max-age=60, s-maxage=60 + content-encoding: + - gzip + content-security-policy: + - default-src 'none' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 02 Dec 2020 16:16:44 GMT + etag: + - W/"24d977b9e4582f04b3f9ae01a0d39a62359d71ace777aa7e07d8cd5eaeaedef4" + last-modified: + - Wed, 02 Dec 2020 15:17:47 GMT + referrer-policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + server: + - GitHub.com + status: + - 200 OK + strict-transport-security: + - max-age=31536000; includeSubdomains; preload + vary: + - Accept, Accept-Encoding, Accept, X-Requested-With, Accept-Encoding + x-content-type-options: + - nosniff + x-frame-options: + - deny + x-github-media-type: + - github.v3; format=json + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - PyGithub/Python + method: GET + uri: https://api.github.com/search/issues?q=type%3Apr+repo%3Apython%2Fpython-docs-fr+sha%3A390a392633a4692ee96ebd8d122cfdcd602224e0&per_page=1 + response: + body: + string: !!binary | + H4sIAAAAAAAAA61V227jNhD9FYELv8UWJd8SAUWRvWSxD3barNKmWSwEWhrJTChSS1JOHMO/sa99 + 6Qf2EzqUL3WMhbdx90k2OefMcDg8Z0GsskwkqaqlJVFwQrhMVVkJsJBoMLWwhkQ5EwZwy0KJ/z4t + SK0FicjU2spEvs8q3im4ndaTDmJ9DZUyfjW3UyXXn3amUtPOtc+NqcH4QW8QkBPSRHKr9Dw5lhFZ + BJuAMEcz7Nbkr7gWvmQlLJEbz1OCtD+IfcOGxDD7cbQrLiSd2lLs9WHnXr59I1UtxOY+eEaiYdjv + DU77Yf+ESJVB4tbI6O27x0vxOpi8f/x4e3MR3N6M6Tg+D8d3993R0/0T5pZ1OQGNI4RXe0IstwIQ + GGuW1anlSnoZeDn74hcgQTPRqRSCauMgCyJUwaXLw2XGQQjccnkHQUj7/dPweSm/Dn67GYv07o/H + 0d15MI6LOYazGbNM7w9Bs2i66+l02VIlLXa+GdTa3yT4efZTD0kKvaZpDu3qOzTnjs74OyUfbv9O + YK6EUA+I3q/3+UN6nsDforCw1W8uiyMYELXwlZ0CtguP4Ma84OZ7M75XTINY+O6DI+I4DLZaQ/ai + gtYYLOcBp2K5aLSjIasnJtW8cpPzsi49QyKT0gWT/Im9nAmRBgkakXrRqRoEIv/LG99r6wqy8CvN + Zyydu1ZoSIHPsLFH0O1hkc3OK/cur93DwzajpCcsK93ja0R+uZHTRubdMwj73dMgHJ7RPUG4HsTv + L/jt7w/zcTzqjeIP3cv43In6wTdzyBtW2uu3Lmjr7KJ13mudDVohZbVVJegCkNqpMhb/959/ffV2 + 11MlFOoIoXQwGfYxMIOcoXVtnSuD7TQ5UZqC98uV98CF8CbQMHlNiowsPzeDbF0aVYFELqHSe9zZ + mCAzhhcSMECicqLwrP87Y0RwyQUYq+R2f6v50RDtRANyZwnD0khIQ9oOaDukcRBEtBt1+7euf1W2 + HxO2aRgHg4jSKBy6mFQos6ZZV1Gj2+oEi1Epb2YdE7y5HMdXH15fx5dXiGEowzNI3HHQ2ZlxIStw + pln+b6+cIWDAF/RpXPwfXu+ItlZ/WBu/b00Zz/Ojra3j0NiCitl0ejxLAyf4RCYqm7v+ulsw3iu0 + POo5etC50iVezIyzZOW8CauqTZ9NqjTORdChy8/LfwBesmWBegkAAA== + headers: + Accept-Ranges: + - bytes + Transfer-Encoding: + - chunked + X-GitHub-Request-Id: + - 998E:DAEA:42E9DD5:4D53E2C:5FC7BDEC + X-Ratelimit-Limit: + - '10' + X-Ratelimit-Remaining: + - '8' + X-Ratelimit-Reset: + - '1606925835' + X-Ratelimit-Used: + - '2' + access-control-allow-origin: + - '*' + access-control-expose-headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, + X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset + cache-control: + - no-cache + content-encoding: + - gzip + content-security-policy: + - default-src 'none' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 02 Dec 2020 16:16:44 GMT + referrer-policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + server: + - GitHub.com + status: + - 200 OK + strict-transport-security: + - max-age=31536000; includeSubdomains; preload + vary: + - Accept, Accept-Encoding, Accept, X-Requested-With, Accept-Encoding + x-content-type-options: + - nosniff + x-frame-options: + - deny + x-github-media-type: + - github.v3; format=json + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - PyGithub/Python + method: GET + uri: https://api.github.com/search/issues?q=type%3Apr+repo%3Apython%2Fpython-docs-fr+sha%3A390a392633a4692ee96ebd8d122cfdcd602224e0 + response: + body: + string: !!binary | + H4sIAAAAAAAAA61V227jNhD9FYELv8UWJd8SAUWRvWSxD3barNKmWSwEWhrJTChSS1JOHMO/sa99 + 6Qf2EzqUL3WMhbdx90k2OefMcDg8Z0GsskwkqaqlJVFwQrhMVVkJsJBoMLWwhkQ5EwZwy0KJ/z4t + SK0FicjU2spEvs8q3im4ndaTDmJ9DZUyfjW3UyXXn3amUtPOtc+NqcH4QW8QkBPSRHKr9Dw5lhFZ + BJuAMEcz7Nbkr7gWvmQlLJEbz1OCtD+IfcOGxDD7cbQrLiSd2lLs9WHnXr59I1UtxOY+eEaiYdjv + DU77Yf+ESJVB4tbI6O27x0vxOpi8f/x4e3MR3N6M6Tg+D8d3993R0/0T5pZ1OQGNI4RXe0IstwIQ + GGuW1anlSnoZeDn74hcgQTPRqRSCauMgCyJUwaXLw2XGQQjccnkHQUj7/dPweSm/Dn67GYv07o/H + 0d15MI6LOYazGbNM7w9Bs2i66+l02VIlLXa+GdTa3yT4efZTD0kKvaZpDu3qOzTnjs74OyUfbv9O + YK6EUA+I3q/3+UN6nsDforCw1W8uiyMYELXwlZ0CtguP4Ma84OZ7M75XTINY+O6DI+I4DLZaQ/ai + gtYYLOcBp2K5aLSjIasnJtW8cpPzsi49QyKT0gWT/Im9nAmRBgkakXrRqRoEIv/LG99r6wqy8CvN + Zyydu1ZoSIHPsLFH0O1hkc3OK/cur93DwzajpCcsK93ja0R+uZHTRubdMwj73dMgHJ7RPUG4HsTv + L/jt7w/zcTzqjeIP3cv43In6wTdzyBtW2uu3Lmjr7KJ13mudDVohZbVVJegCkNqpMhb/959/ffV2 + 11MlFOoIoXQwGfYxMIOcoXVtnSuD7TQ5UZqC98uV98CF8CbQMHlNiowsPzeDbF0aVYFELqHSe9zZ + mCAzhhcSMECicqLwrP87Y0RwyQUYq+R2f6v50RDtRANyZwnD0khIQ9oOaDukcRBEtBt1+7euf1W2 + HxO2aRgHg4jSKBy6mFQos6ZZV1Gj2+oEi1Epb2YdE7y5HMdXH15fx5dXiGEowzNI3HHQ2ZlxIStw + pln+b6+cIWDAF/RpXPwfXu+ItlZ/WBu/b00Zz/Ojra3j0NiCitl0ejxLAyf4RCYqm7v+ulsw3iu0 + POo5etC50iVezIyzZOW8CauqTZ9NqjTORdChy8/LfwBesmWBegkAAA== + headers: + Accept-Ranges: + - bytes + Transfer-Encoding: + - chunked + X-GitHub-Request-Id: + - 998E:DAEA:42E9E4A:4D53EB6:5FC7BDEC + X-Ratelimit-Limit: + - '10' + X-Ratelimit-Remaining: + - '7' + X-Ratelimit-Reset: + - '1606925835' + X-Ratelimit-Used: + - '3' + access-control-allow-origin: + - '*' + access-control-expose-headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, + X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset + cache-control: + - no-cache + content-encoding: + - gzip + content-security-policy: + - default-src 'none' + content-type: + - application/json; charset=utf-8 + date: + - Wed, 02 Dec 2020 16:16:45 GMT + referrer-policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + server: + - GitHub.com + status: + - 200 OK + strict-transport-security: + - max-age=31536000; includeSubdomains; preload + vary: + - Accept, Accept-Encoding, Accept, X-Requested-With, Accept-Encoding + x-content-type-options: + - nosniff + x-frame-options: + - deny + x-github-media-type: + - github.v3; format=json + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/tests/conftest.py b/tests/conftest.py index d5edf87..dcd3d09 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,5 @@ import os -os.environ["GH_TOKEN"] = "dummy" -os.environ["REPOSITORY_NAME"] = "dummy" -os.environ["GH_USERNAME"] = "dummy" +os.environ["GH_TOKEN"] = "" +os.environ["REPOSITORY_NAME"] = "python/python-docs-fr" +os.environ["GH_USERNAME"] = "PyDocTeur" diff --git a/tests/test_find_pr.py b/tests/test_find_pr.py new file mode 100644 index 0000000..be10907 --- /dev/null +++ b/tests/test_find_pr.py @@ -0,0 +1,27 @@ +import pytest +from pydocteur.github_api import get_pull_request + + +@pytest.mark.vcr() +def test_get_pr_from_check_suite(): + payload = { + "action": "completed", + "check_suite": { + "id": 1487327954, + "node_id": "MDg6Q2hlY2tSdW4xNDg3MzI3OTU0", + "head_sha": "390a392633a4692ee96ebd8d122cfdcd602224e0", + "external_id": "d133e080-687a-5f71-4d25-419e05f66c84", + "url": "https://api.github.com/repos/python/python-docs-fr/check-runs/1487327954", + "html_url": "https://github.com/python/python-docs-fr/runs/1487327954", + "details_url": "https://github.com/python/python-docs-fr/runs/1487327954", + "status": "completed", + "conclusion": "success", + "started_at": "2020-12-02T16:00:51Z", + "completed_at": "2020-12-02T16:05:22Z", + "output": { + "annotations_count": 0, + }, + }, + } + pr = get_pull_request(payload) + assert pr.number == 1461