Repository: PyGithub/PyGithub

Issue:
Incomplete API documentation on stable PyGithub
I'm trying to update my code with the latest PyGithub classes however documentation is missing on the new authentication methods.

jwt – string deprecated, use auth=github.Auth.AppAuth(…) or auth=github.Auth.AppAuthToken(…) instead
app_auth – github.AppAuthentication deprecated, use auth=github.Auth.AppInstallationAuth(…) instead

There isn't any documentation on:
AppAuthToken
AppInstallationAuth


Discussion:
Right, docs should be here: https://pygithub.readthedocs.io/en/stable/github_objects/Auth.html

For the time being, the authentication examples might help: https://pygithub.readthedocs.io/en/stable/examples/Authentication.html

Resolving patch:
diff --git a/doc/conf.py b/doc/conf.py
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -25,11 +25,14 @@
 # along with PyGithub. If not, see <http://www.gnu.org/licenses/>.             #
 #                                                                              #
 ################################################################################
+from __future__ import annotations
 
 import datetime
 import glob
 import os
+import re
 import sys
+from typing import Iterable
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
@@ -268,24 +271,45 @@
 autodoc_member_order = "bysource"
 autoclass_content = "both"
 
-githubClasses = [
-    fileName[10:-3]
-    for fileName in sorted(glob.glob("../github/*.py"))
-    if fileName
-    not in [
-        "../github/GithubException.py",
-        "../github/GithubObject.py",
-        "../github/InputFileContent.py",
-        "../github/InputGitAuthor.py",
-        "../github/InputGitTreeElement.py",
-        "../github/Legacy.py",
-        "../github/MainClass.py",
-        "../github/PaginatedList.py",
-        "../github/Requester.py",
-        "../github/Consts.py",
-        "../github/__init__.py",
-    ]
-]
+githubObjectTypes = {
+    variation
+    for object_type in ["GithubObject", "CompletableGithubObject", "NonCompletableGithubObject"]
+    for variation in [object_type, "GithubObject." + object_type, "github.GithubObject." + object_type]
+}
+githubObjectClasses: dict[str, str] = {}
+
+
+def collect_classes(types: set[str]) -> Iterable[tuple[str, str]]:
+    def get_base_classes(class_definition: str) -> Iterable[str]:
+        if "(" in class_definition and ")" in class_definition:
+            for base in class_definition[class_definition.index("(") + 1 : class_definition.index(")")].split(","):
+                yield base.strip()
+        else:
+            return []
+
+    for filename in sorted(glob.glob("../github/*.py")):
+        module = f"github.{filename[10:-3]}"
+        with open(filename) as r:
+            for line in r.readlines():
+                if line.startswith("class ") and any([base in types for base in get_base_classes(line)]):
+                    class_name = re.match(r"class (\w+)[:(]", line).group(1)
+                    if class_name not in types:
+                        yield class_name, f"{module}"
+
+
+# get all classes derived from GithubObject classes directly
+classes = list(collect_classes(githubObjectTypes))
+while classes:
+    githubObjectClasses.update(classes)
+    # get all classes derived from detected classes
+    githubObjectTypes.update(
+        {
+            variation
+            for object_type in [cls for cls, _ in classes]
+            for variation in [object_type, "GithubObject." + object_type, "github.GithubObject." + object_type]
+        }
+    )
+    classes = list(collect_classes(set(githubObjectTypes)))
 
 with open("github_objects.rst", "w") as f:
     f.write("Github objects\n")
@@ -294,23 +318,21 @@
     f.write(".. autoclass:: github.GithubObject.GithubObject()\n")
     f.write("\n")
     f.write(".. toctree::\n")
-    for githubClass in githubClasses:
-        f.write("   github_objects/" + githubClass + "\n")
+    for githubObjectClass in sorted(githubObjectClasses.keys()):
+        f.write("   github_objects/" + githubObjectClass + "\n")
 
-for githubClass in githubClasses:
-    with open("github_objects/" + githubClass + ".rst", "w") as f:
-        f.write(githubClass + "\n")
-        f.write("=" * len(githubClass) + "\n")
+for githubObjectClass, module in githubObjectClasses.items():
+    with open("github_objects/" + githubObjectClass + ".rst", "w") as f:
+        f.write(githubObjectClass + "\n")
+        f.write("=" * len(githubObjectClass) + "\n")
         f.write("\n")
-        f.write(".. autoclass:: github." + githubClass + "." + githubClass + "()\n")
+        f.write(".. autoclass:: " + module + "." + githubObjectClass + "()\n")
 
 methods = dict()
-for githubClass in githubClasses + ["MainClass"]:
-    with open("../github/" + githubClass + ".py") as f:
-        if githubClass == "MainClass":
-            githubClass = "github.MainClass.Github"
-        else:
-            githubClass = "github." + githubClass + "." + githubClass
+githubObjectClasses.update([("MainClass", "github.MainClass")])
+githubObjectClasses.update([("GithubIntegration", "github.GithubIntegration")])
+for githubObjectClass, module in githubObjectClasses.items():
+    with open("../" + module.replace(".", "/") + ".py") as f:
         method = None
         isProperty = False
         for line in f:
@@ -345,7 +367,7 @@
                         methods[url] = dict()
                     if verb not in methods[url]:
                         methods[url][verb] = set()
-                    methods[url][verb].add(":meth:`" + githubClass + "." + method + "`")
+                    methods[url][verb].add(":meth:`" + module + "." + githubObjectClass + "." + method + "`")
                 method = None
 
 methods["/markdown/raw"] = dict()
diff --git a/github/Auth.py b/github/Auth.py
--- a/github/Auth.py
+++ b/github/Auth.py
@@ -25,7 +25,7 @@
 import time
 from abc import ABC
 from datetime import datetime, timedelta, timezone
-from typing import Dict, Optional, Union
+from typing import TYPE_CHECKING, Dict, Optional, Union
 
 import jwt
 from requests import utils
@@ -34,11 +34,15 @@
 from github.InstallationAuthorization import InstallationAuthorization
 from github.Requester import Requester, WithRequester
 
+if TYPE_CHECKING:
+    from github.GithubIntegration import GithubIntegration
+
 # For App authentication, time remaining before token expiration to request a new one
 ACCESS_TOKEN_REFRESH_THRESHOLD_SECONDS = 20
 TOKEN_REFRESH_THRESHOLD_TIMEDELTA = timedelta(seconds=ACCESS_TOKEN_REFRESH_THRESHOLD_SECONDS)
 
 
+# add new implementations of github.Auth.Auth to docs/utilities.rst
 class Auth(abc.ABC):
     """
     This class is the base class of all authentication methods for Requester.
@@ -83,7 +87,7 @@ def token(self) -> str:
 
 class Login(HTTPBasicAuth):
     """
-    This class is used to authenticate Requester with login and password.
+    This class is used to authenticate with login and password.
     """
 
     def __init__(self, login: str, password: str):
@@ -110,7 +114,7 @@ def password(self) -> str:
 
 class Token(Auth):
     """
-    This class is used to authenticate Requester with a single constant token.
+    This class is used to authenticate with a single constant token.
     """
 
     def __init__(self, token: str):
@@ -140,7 +144,7 @@ def token_type(self) -> str:
 
 class AppAuth(JWT):
     """
-    This class is used to authenticate Requester as a GitHub App.
+    This class is used to authenticate as a GitHub App.
     https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app
     """
 
@@ -219,7 +223,7 @@ def create_jwt(self, expiration: Optional[int] = None) -> str:
 
 class AppAuthToken(JWT):
     """
-    This class is used to authenticate Requester as a GitHub App with a single constant JWT.
+    This class is used to authenticate as a GitHub App with a single constant JWT.
     https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app
     """
 
@@ -235,15 +239,12 @@ def token(self) -> str:
 
 class AppInstallationAuth(Auth, WithRequester["AppInstallationAuth"]):
     """
-    This class is used to authenticate Requester as a GitHub App Installation.
+    This class is used to authenticate as a GitHub App Installation.
     https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation
     """
 
-    # imported here to avoid circular import, needed for typing only
-    from github.GithubIntegration import GithubIntegration
-
     # used to fetch live access token when calling self.token
-    __integration: Optional[GithubIntegration] = None
+    __integration: Optional["GithubIntegration"] = None
     __installation_authorization: Optional[InstallationAuthorization] = None
 
     def __init__(
@@ -269,6 +270,7 @@ def __init__(
     def withRequester(self, requester: Requester) -> "AppInstallationAuth":
         super().withRequester(requester.withAuth(self._app_auth))
 
+        # imported here to avoid circular import
         from github.GithubIntegration import GithubIntegration
 
         self.__integration = GithubIntegration(**self.requester.kwargs)
@@ -317,7 +319,7 @@ def _get_installation_authorization(self) -> InstallationAuthorization:
 
 class AppUserAuth(Auth, WithRequester["AppUserAuth"]):
     """
-    This class is used to authenticate Requester as a GitHub App on behalf of a user.
+    This class is used to authenticate as a GitHub App on behalf of a user.
     https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-with-a-github-app-on-behalf-of-a-user
     """
 
@@ -445,7 +447,7 @@ def refresh_expires_at(self) -> Optional[datetime]:
 
 class NetrcAuth(HTTPBasicAuth, WithRequester["NetrcAuth"]):
     """
-    This class is used to authenticate Requester via .netrc.
+    This class is used to authenticate via .netrc.
     """
 
     def __init__(self) -> None:
diff --git a/github/RepositoryAdvisory.py b/github/RepositoryAdvisory.py
--- a/github/RepositoryAdvisory.py
+++ b/github/RepositoryAdvisory.py
@@ -199,9 +199,9 @@ def offer_credit(
         credit_type: str,
     ) -> None:
         """
-        :calls: `PATCH /repos/{owner}/{repo}/security-advisories/:advisory_id <https://docs.github.com/en/rest/security-advisories/repository-advisories>`
         Offers credit to a user for a vulnerability in a repository.
         Unless you are giving credit to yourself, the user having credit offered will need to explicitly accept the credit.
+        :calls: `PATCH /repos/{owner}/{repo}/security-advisories/:advisory_id <https://docs.github.com/en/rest/security-advisories/repository-advisories>`
         """
         self.offer_credits([{"login": login_or_user, "type": credit_type}])
 
@@ -210,9 +210,9 @@ def offer_credits(
         credited: Iterable[Credit],
     ) -> None:
         """
-        :calls: `PATCH /repos/{owner}/{repo}/security-advisories/:advisory_id <https://docs.github.com/en/rest/security-advisories/repository-advisories>`
         Offers credit to a list of users for a vulnerability in a repository.
         Unless you are giving credit to yourself, the user having credit offered will need to explicitly accept the credit.
+        :calls: `PATCH /repos/{owner}/{repo}/security-advisories/:advisory_id <https://docs.github.com/en/rest/security-advisories/repository-advisories>`
         :param credited: iterable of dict with keys "login" and "type"
         """
         assert isinstance(credited, Iterable), credited
@@ -328,8 +328,8 @@ def edit(
 
     def accept_report(self) -> None:
         """
-        :calls: `PATCH /repos/{owner}/{repo}/security-advisories/:advisory_id <https://docs.github.com/en/rest/security-advisories/repository-advisories>`
         Accepts the advisory reported from an external reporter via private vulnerability reporting.
+        :calls: `PATCH /repos/{owner}/{repo}/security-advisories/:advisory_id <https://docs.github.com/en/rest/security-advisories/repository-advisories>`
         """
         patch_parameters = {"state": "draft"}
         headers, data = self._requester.requestJsonAndCheck(
@@ -341,8 +341,8 @@ def accept_report(self) -> None:
 
     def publish(self) -> None:
         """
-        :calls: `PATCH /repos/{owner}/{repo}/security-advisories/:advisory_id <https://docs.github.com/en/rest/security-advisories/repository-advisories>`
         Publishes the advisory.
+        :calls: `PATCH /repos/{owner}/{repo}/security-advisories/:advisory_id <https://docs.github.com/en/rest/security-advisories/repository-advisories>`
         """
         patch_parameters = {"state": "published"}
         headers, data = self._requester.requestJsonAndCheck(
@@ -354,8 +354,8 @@ def publish(self) -> None:
 
     def close(self) -> None:
         """
-        :calls: `PATCH /repos/{owner}/{repo}/security-advisories/:advisory_id <https://docs.github.com/en/rest/security-advisories/repository-advisories>`
         Closes the advisory.
+        :calls: `PATCH /repos/{owner}/{repo}/security-advisories/:advisory_id <https://docs.github.com/en/rest/security-advisories/repository-advisories>`
         """
         patch_parameters = {"state": "closed"}
         headers, data = self._requester.requestJsonAndCheck(


Test patch: