Skip to content

API Documentation

github_bot_api

Event dataclass

Represents a GitHub webhook event.

Source code in github_bot_api/event.py
@dataclass
class Event:
    """
    Represents a GitHub webhook event.
    """

    name: str
    """ The name of the event. Could be `pull_request`, for example."""

    delivery_id: str
    """The delivery ID of the event."""

    signature: t.Optional[str]
    """The signature of the event. Will only be set if a Webhook-secret is configured on the
    client side (e.g. in [`Webhook.secret`][github_bot_api.webhook.Webhook] / if the *webhook_secret* parameter is
    passed to [`accept_event()`][github_bot_api.event.accept_event])."""

    user_agent: str
    """The user agent invoking the webhook."""

    payload: t.Dict[str, t.Any]
    """The event payload."""
delivery_id instance-attribute
delivery_id: str

The delivery ID of the event.

name instance-attribute
name: str

The name of the event. Could be pull_request, for example.

payload instance-attribute
payload: Dict[str, Any]

The event payload.

signature instance-attribute
signature: Optional[str]

The signature of the event. Will only be set if a Webhook-secret is configured on the client side (e.g. in Webhook.secret / if the webhook_secret parameter is passed to accept_event()).

user_agent instance-attribute
user_agent: str

The user agent invoking the webhook.

GithubApp dataclass

Represents a GitHub application and all the required details.

Source code in github_bot_api/app.py
@dataclasses.dataclass
class GithubApp:
    """
    Represents a GitHub application and all the required details.
    """

    PUBLIC_GITHUB_V3_API_URL = "https://api.github.com"

    user_agent: str
    """User agent of the application. This will be respected in #get_user_agent()."""

    app_id: int
    """GitHub Application ID."""

    private_key: str = dataclasses.field(repr=False)
    """RSA private key to sign the JWT with."""

    client_id: t.Optional[str] = None
    """The GitHub App's OAuth client ID. This is required for OAuth2 authorization URL generation. Can be omitted
    if the app does not use OAuth2."""

    client_secret: t.Optional[str] = dataclasses.field(default=None, repr=False)
    """The GitHub App's OAuth client secret. This is required for OAuth2 authorization URL generation. Can be omitted
    if the app does not use OAuth2. Note that this must be specified if #client_id is specified."""

    redirect_uri: t.Optional[str] = None
    """The GitHub App's OAuth redirect URI. This is required for OAuth2 authorization URL generation. Can be omitted
    if the app does not use OAuth2. This field is optional, but required for the web authorization flow with
    #oauth2_web_application_flow_url()."""

    v3_api_url: str = PUBLIC_GITHUB_V3_API_URL
    """GitHub API base URL. Defaults to the public GitHub API."""

    def __post_init__(self):
        self._jwt_supplier = JwtSupplier(self.app_id, self.private_key)
        self._lock = threading.Lock()
        self._installation_tokens: t.Dict[int, InstallationTokenSupplier] = {}

        if self.client_id is not None:
            if self.client_secret is None:
                raise ValueError("client_secret must be specified if client_id is specified.")
        if self.redirect_uri is not None:
            if self.client_id is None:
                raise ValueError("redirect_uri does not make sense without client_id.")

    def _get_base_github_client_settings(self) -> GithubClientSettings:
        return GithubClientSettings(self.v3_api_url, self.get_user_agent())

    def get_user_agent(self, installation_id: t.Optional[int] = None) -> str:
        """
        Create a user agent string for the PyGithub client, including the installation if specified.
        """

        user_agent = f"{self.user_agent} PyGithub/python (app_id={self.app_id}"
        if installation_id:
            user_agent += f", installation_id={installation_id})"
        return user_agent

    @property
    def jwt(self) -> TokenInfo:
        """
        Returns the JWT for your GitHub application. The JWT is the token to use with GitHub application APIs.
        """

        return self._jwt_supplier()

    @property
    def jwt_supplier(self) -> JwtSupplier:
        """
        Returns a new #JwtSupplier that is used for generating JWT tokens for your GitHub application.
        """

        return JwtSupplier(self.app_id, self.private_key)

    def app_client(self, settings: t.Union[GithubClientSettings, t.Dict[str, t.Any], None] = None) -> "github.Github":
        """
        Returns a PyGithub client for your GitHub application.

        Note that the client's token will expire after 10 minutes and you will have to create a new client or update the
        client's token with the value returned by #jwt. It is recommended that you create a new client for each atomic
        operation you perform.

        This requires you to install `PyGithub>=1.58`.
        """

        if isinstance(settings, dict):
            settings = GithubClientSettings(**settings)
        elif settings is None:
            settings = GithubClientSettings()

        settings = self._get_base_github_client_settings().update(settings)
        return settings.make_client(jwt=self.jwt.value)

    def __requestor(self, auth_header: str, installation_id: int) -> t.Dict[str, str]:
        return requests.post(
            self.v3_api_url.rstrip("/") + f"/app/installations/{installation_id}/access_tokens",
            headers={"Authorization": auth_header, "User-Agent": user_agent},
        ).json()

    @deprecated.deprecated(reason="Use .installation_token_supplier() instead.", version="0.8.0")
    def get_installation_token_supplier(self, installation_id: int) -> InstallationTokenSupplier:
        return self.installation_token_supplier(installation_id)

    def installation_token_supplier(self, installation_id: int) -> InstallationTokenSupplier:
        """
        Create an #InstallationTokenSupplier for your GitHub application to act within the scope of the given
        *installation_id*.
        """

        with self._lock:
            return self._installation_tokens.setdefault(
                installation_id,
                InstallationTokenSupplier(
                    self._jwt_supplier,
                    installation_id,
                    self.__requestor,
                ),
            )

    def installation_token(self, installation_id: int) -> TokenInfo:
        """
        A short-hand to retrieve a new installation token for the given *installation_id*.
        """

        return self.get_installation_token_supplier(installation_id)()

    def installation_client(
        self,
        installation_id: int,
        settings: t.Union[GithubClientSettings, t.Dict[str, t.Any], None] = None,
    ) -> "github.Github":
        """
        Returns a PyGithub client for your GitHub application to act in the scope of the given *installation_id*.

        Note that the client's token will expire after 10 minutes and you will have to create a new client or update the
        client's token with the value returned by #jwt. It is recommended that you create a new client for each atomic
        operation you perform.

        This requires you to install `PyGithub>=1.58`.
        """

        if isinstance(settings, dict):
            settings = GithubClientSettings(**settings)
        elif settings is None:
            settings = GithubClientSettings()

        token = self.installation_token(installation_id).value
        settings = self._get_base_github_client_settings().update(settings)
        return settings.make_client(login_or_token=token)

    def oauth2_web_application_flow_url(self, state: t.Optional[str] = None) -> str:
        """
        Returns the URL for a user to begin the OAuth2 web authorization flow.

        Preconditions:
        - You must have provided a `client_id` when constructing the #GithubApp instance.

        Documentation: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-web-application-flow-to-generate-a-user-access-token
        """

        if self.client_id is None:
            raise ValueError("client_id must be specified to generate OAuth2 authorization URL.")
        if self.client_secret is None:
            raise ValueError("client_secret must be specified to generate OAuth2 authorization URL.")
        if not self.redirect_uri:
            raise ValueError("redirect_uri must be specified to generate OAuth2 authorization URL.")

        params = {
            "client_id": self.client_id,
            "redirect_uri": self.redirect_uri,
        }
        if state is not None:
            params["state"] = state

        url = self.v3_api_url.replace("api.", "").replace("/api/v3", "").rstrip("/")
        return f"{url}/login/oauth/authorize?" + urlencode(params)

    def oauth2_device_flow(self) -> "OAuth2DeviceCodeFlow":
        """
        Makes a request to GitHub to request a device code for the OAuth2 device flow.

        Prerequisites:

        - "Enable Device Flow" must be checked in your GitHub app's settings.
        - You must have provided a `client_id` when constructing the #GithubApp instance.

        Documentation: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-device-flow-to-generate-a-user-access-token
        """

        params = {"client_id": self.client_id}
        url = self.v3_api_url.replace("api.", "").replace("/api/v3", "").rstrip("/")
        response = requests.post(f"{url}/login/device/code", params=params)
        response.raise_for_status()
        payload = {k: v[0] for k, v in parse_qs(response.text).items()}
        return OAuth2DeviceCodeFlow(
            device_code=payload["device_code"],
            user_code=payload["user_code"],
            verification_uri=payload["verification_uri"],
            expires_in=int(payload["expires_in"]),
            interval=int(payload["interval"]),
            app=self,
        )

    def oauth2_access_token(
        self, *, code: t.Optional[str] = None, device_code: t.Optional[str] = None
    ) -> t.Optional["OAuth2TokenInfo"]:
        """
        Makes a request to GitHub to exchange an OAuth2 code for an access token.

        Important: You must provide the correct `code` or `device_code` parameter, but not both.

        Documentation: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#generating-a-user-access-token-when-a-user-installs-your-app

        Returns None if the token is not yet available (in the case of the device flow). Otherwise, returns a
        #TokenInfo object. If an error occurs, an #AccessTokenError is raised.
        """

        params = {"client_id": self.client_id, "client_secret": self.client_secret}
        if code is not None:
            params["code"] = code
        elif device_code is not None:
            params["device_code"] = device_code
            params["grant_type"] = "urn:ietf:params:oauth:grant-type:device_code"
        else:
            raise ValueError("You must provide either a code or a device_code.")

        url = self.v3_api_url.replace("api.", "").replace("/api/v3", "").rstrip("/")
        response = requests.post(f"{url}/login/oauth/access_token", params=params)
        payload = {k: v[0] for k, v in parse_qs(response.text).items()}

        if (error := payload.get("error")) == "authorization_pending":
            return None
        elif error is not None:
            return OAuth2TokenInfo(
                access_token=payload["access_token"],
                expires_in=int(payload["expires_in"]),
                scope=payload.get("scope"),
                token_type=payload["token_type"],
                refresh_token=payload.get("refresh_token"),
                refresh_token_expires_in=int(payload["refresh_token_expires_in"])
                if "refresh_token_expires_in" in payload
                else None,
            )
        else:
            raise AccessTokenError(error)  # type: ignore[arg-type]
app_id instance-attribute
app_id: int

GitHub Application ID.

client_id class-attribute instance-attribute
client_id: Optional[str] = None

The GitHub App's OAuth client ID. This is required for OAuth2 authorization URL generation. Can be omitted if the app does not use OAuth2.

client_secret class-attribute instance-attribute
client_secret: Optional[str] = field(
    default=None, repr=False
)

The GitHub App's OAuth client secret. This is required for OAuth2 authorization URL generation. Can be omitted if the app does not use OAuth2. Note that this must be specified if #client_id is specified.

jwt property
jwt: TokenInfo

Returns the JWT for your GitHub application. The JWT is the token to use with GitHub application APIs.

jwt_supplier property
jwt_supplier: JwtSupplier

Returns a new #JwtSupplier that is used for generating JWT tokens for your GitHub application.

private_key class-attribute instance-attribute
private_key: str = field(repr=False)

RSA private key to sign the JWT with.

redirect_uri class-attribute instance-attribute
redirect_uri: Optional[str] = None

The GitHub App's OAuth redirect URI. This is required for OAuth2 authorization URL generation. Can be omitted if the app does not use OAuth2. This field is optional, but required for the web authorization flow with

oauth2_web_application_flow_url().
user_agent instance-attribute
user_agent: str

User agent of the application. This will be respected in #get_user_agent().

v3_api_url class-attribute instance-attribute
v3_api_url: str = PUBLIC_GITHUB_V3_API_URL

GitHub API base URL. Defaults to the public GitHub API.

app_client
app_client(
    settings: Union[
        GithubClientSettings, Dict[str, Any], None
    ] = None
) -> Github

Returns a PyGithub client for your GitHub application.

Note that the client's token will expire after 10 minutes and you will have to create a new client or update the client's token with the value returned by #jwt. It is recommended that you create a new client for each atomic operation you perform.

This requires you to install PyGithub>=1.58.

Source code in github_bot_api/app.py
def app_client(self, settings: t.Union[GithubClientSettings, t.Dict[str, t.Any], None] = None) -> "github.Github":
    """
    Returns a PyGithub client for your GitHub application.

    Note that the client's token will expire after 10 minutes and you will have to create a new client or update the
    client's token with the value returned by #jwt. It is recommended that you create a new client for each atomic
    operation you perform.

    This requires you to install `PyGithub>=1.58`.
    """

    if isinstance(settings, dict):
        settings = GithubClientSettings(**settings)
    elif settings is None:
        settings = GithubClientSettings()

    settings = self._get_base_github_client_settings().update(settings)
    return settings.make_client(jwt=self.jwt.value)
get_user_agent
get_user_agent(
    installation_id: Optional[int] = None,
) -> str

Create a user agent string for the PyGithub client, including the installation if specified.

Source code in github_bot_api/app.py
def get_user_agent(self, installation_id: t.Optional[int] = None) -> str:
    """
    Create a user agent string for the PyGithub client, including the installation if specified.
    """

    user_agent = f"{self.user_agent} PyGithub/python (app_id={self.app_id}"
    if installation_id:
        user_agent += f", installation_id={installation_id})"
    return user_agent
installation_client
installation_client(
    installation_id: int,
    settings: Union[
        GithubClientSettings, Dict[str, Any], None
    ] = None,
) -> Github

Returns a PyGithub client for your GitHub application to act in the scope of the given installation_id.

Note that the client's token will expire after 10 minutes and you will have to create a new client or update the client's token with the value returned by #jwt. It is recommended that you create a new client for each atomic operation you perform.

This requires you to install PyGithub>=1.58.

Source code in github_bot_api/app.py
def installation_client(
    self,
    installation_id: int,
    settings: t.Union[GithubClientSettings, t.Dict[str, t.Any], None] = None,
) -> "github.Github":
    """
    Returns a PyGithub client for your GitHub application to act in the scope of the given *installation_id*.

    Note that the client's token will expire after 10 minutes and you will have to create a new client or update the
    client's token with the value returned by #jwt. It is recommended that you create a new client for each atomic
    operation you perform.

    This requires you to install `PyGithub>=1.58`.
    """

    if isinstance(settings, dict):
        settings = GithubClientSettings(**settings)
    elif settings is None:
        settings = GithubClientSettings()

    token = self.installation_token(installation_id).value
    settings = self._get_base_github_client_settings().update(settings)
    return settings.make_client(login_or_token=token)
installation_token
installation_token(installation_id: int) -> TokenInfo

A short-hand to retrieve a new installation token for the given installation_id.

Source code in github_bot_api/app.py
def installation_token(self, installation_id: int) -> TokenInfo:
    """
    A short-hand to retrieve a new installation token for the given *installation_id*.
    """

    return self.get_installation_token_supplier(installation_id)()
installation_token_supplier
installation_token_supplier(
    installation_id: int,
) -> InstallationTokenSupplier

Create an #InstallationTokenSupplier for your GitHub application to act within the scope of the given installation_id.

Source code in github_bot_api/app.py
def installation_token_supplier(self, installation_id: int) -> InstallationTokenSupplier:
    """
    Create an #InstallationTokenSupplier for your GitHub application to act within the scope of the given
    *installation_id*.
    """

    with self._lock:
        return self._installation_tokens.setdefault(
            installation_id,
            InstallationTokenSupplier(
                self._jwt_supplier,
                installation_id,
                self.__requestor,
            ),
        )
oauth2_access_token
oauth2_access_token(
    *,
    code: Optional[str] = None,
    device_code: Optional[str] = None
) -> Optional[OAuth2TokenInfo]

Makes a request to GitHub to exchange an OAuth2 code for an access token.

Important: You must provide the correct code or device_code parameter, but not both.

Documentation: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#generating-a-user-access-token-when-a-user-installs-your-app

Returns None if the token is not yet available (in the case of the device flow). Otherwise, returns a

TokenInfo object. If an error occurs, an #AccessTokenError is raised.
Source code in github_bot_api/app.py
def oauth2_access_token(
    self, *, code: t.Optional[str] = None, device_code: t.Optional[str] = None
) -> t.Optional["OAuth2TokenInfo"]:
    """
    Makes a request to GitHub to exchange an OAuth2 code for an access token.

    Important: You must provide the correct `code` or `device_code` parameter, but not both.

    Documentation: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#generating-a-user-access-token-when-a-user-installs-your-app

    Returns None if the token is not yet available (in the case of the device flow). Otherwise, returns a
    #TokenInfo object. If an error occurs, an #AccessTokenError is raised.
    """

    params = {"client_id": self.client_id, "client_secret": self.client_secret}
    if code is not None:
        params["code"] = code
    elif device_code is not None:
        params["device_code"] = device_code
        params["grant_type"] = "urn:ietf:params:oauth:grant-type:device_code"
    else:
        raise ValueError("You must provide either a code or a device_code.")

    url = self.v3_api_url.replace("api.", "").replace("/api/v3", "").rstrip("/")
    response = requests.post(f"{url}/login/oauth/access_token", params=params)
    payload = {k: v[0] for k, v in parse_qs(response.text).items()}

    if (error := payload.get("error")) == "authorization_pending":
        return None
    elif error is not None:
        return OAuth2TokenInfo(
            access_token=payload["access_token"],
            expires_in=int(payload["expires_in"]),
            scope=payload.get("scope"),
            token_type=payload["token_type"],
            refresh_token=payload.get("refresh_token"),
            refresh_token_expires_in=int(payload["refresh_token_expires_in"])
            if "refresh_token_expires_in" in payload
            else None,
        )
    else:
        raise AccessTokenError(error)  # type: ignore[arg-type]
oauth2_device_flow
oauth2_device_flow() -> OAuth2DeviceCodeFlow

Makes a request to GitHub to request a device code for the OAuth2 device flow.

Prerequisites:

  • "Enable Device Flow" must be checked in your GitHub app's settings.
  • You must have provided a client_id when constructing the #GithubApp instance.

Documentation: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-device-flow-to-generate-a-user-access-token

Source code in github_bot_api/app.py
def oauth2_device_flow(self) -> "OAuth2DeviceCodeFlow":
    """
    Makes a request to GitHub to request a device code for the OAuth2 device flow.

    Prerequisites:

    - "Enable Device Flow" must be checked in your GitHub app's settings.
    - You must have provided a `client_id` when constructing the #GithubApp instance.

    Documentation: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-device-flow-to-generate-a-user-access-token
    """

    params = {"client_id": self.client_id}
    url = self.v3_api_url.replace("api.", "").replace("/api/v3", "").rstrip("/")
    response = requests.post(f"{url}/login/device/code", params=params)
    response.raise_for_status()
    payload = {k: v[0] for k, v in parse_qs(response.text).items()}
    return OAuth2DeviceCodeFlow(
        device_code=payload["device_code"],
        user_code=payload["user_code"],
        verification_uri=payload["verification_uri"],
        expires_in=int(payload["expires_in"]),
        interval=int(payload["interval"]),
        app=self,
    )
oauth2_web_application_flow_url
oauth2_web_application_flow_url(
    state: Optional[str] = None,
) -> str

Returns the URL for a user to begin the OAuth2 web authorization flow.

Preconditions: - You must have provided a client_id when constructing the #GithubApp instance.

Documentation: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-web-application-flow-to-generate-a-user-access-token

Source code in github_bot_api/app.py
def oauth2_web_application_flow_url(self, state: t.Optional[str] = None) -> str:
    """
    Returns the URL for a user to begin the OAuth2 web authorization flow.

    Preconditions:
    - You must have provided a `client_id` when constructing the #GithubApp instance.

    Documentation: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-web-application-flow-to-generate-a-user-access-token
    """

    if self.client_id is None:
        raise ValueError("client_id must be specified to generate OAuth2 authorization URL.")
    if self.client_secret is None:
        raise ValueError("client_secret must be specified to generate OAuth2 authorization URL.")
    if not self.redirect_uri:
        raise ValueError("redirect_uri must be specified to generate OAuth2 authorization URL.")

    params = {
        "client_id": self.client_id,
        "redirect_uri": self.redirect_uri,
    }
    if state is not None:
        params["state"] = state

    url = self.v3_api_url.replace("api.", "").replace("/api/v3", "").rstrip("/")
    return f"{url}/login/oauth/authorize?" + urlencode(params)

Webhook dataclass

Represents a GitHub webhook that listens on an HTTP endpoint for events. Event handlers can be registered using the #@on() decorator or #register() method.

Source code in github_bot_api/webhook.py
@dataclass
class Webhook:
    """
    Represents a GitHub webhook that listens on an HTTP endpoint for events. Event handlers can be
    registered using the #@on() decorator or #register() method.
    """

    #: The webhook secret, if also configured on GitHub. When specified, the payload signature
    #: is checked before an event is accepted by the underlying HTTP framework.
    secret: t.Optional[str]

    handlers: t.List[EventHandler] = field(default_factory=list)

    @t.overload
    def listen(self, event: str) -> t.Callable[[T], T]:
        """
        Decorator to register an event handler function for the specified *event*. The event name
        can be an fnmatch pattern.
        """

    @t.overload
    def listen(self, event: str, func: t.Callable[[Event], bool]) -> None:
        """
        Directly register an event handler function.
        """

    def listen(self, event, func=None):
        if func is None:

            def wrapper(func):
                assert func is not None
                self.listen(event, func)
                return func

            return wrapper
        else:
            self.handlers.append(EventHandler(event, func))

    def dispatch(self, event: Event) -> bool:
        """
        Dispatch an event on the first handler that matches it.

        Returns #True only if the event was handled by a handler.
        """

        matched = False

        for handler in self.handlers:
            if fnmatch.fnmatch(event.name, handler.event):
                matched = True
                if handler.func(event):
                    return True
        logger.info(f'Event %r (id: %r) goes {"unhandled" if matched else "unmatched"}.', event.name, event.delivery_id)

        return matched
dispatch
dispatch(event: Event) -> bool

Dispatch an event on the first handler that matches it.

Returns #True only if the event was handled by a handler.

Source code in github_bot_api/webhook.py
def dispatch(self, event: Event) -> bool:
    """
    Dispatch an event on the first handler that matches it.

    Returns #True only if the event was handled by a handler.
    """

    matched = False

    for handler in self.handlers:
        if fnmatch.fnmatch(event.name, handler.event):
            matched = True
            if handler.func(event):
                return True
    logger.info(f'Event %r (id: %r) goes {"unhandled" if matched else "unmatched"}.', event.name, event.delivery_id)

    return matched

accept_event

accept_event(
    headers: Mapping[str, str],
    raw_body: bytes,
    webhook_secret: Optional[str] = None,
) -> Event

Converts thee HTTP headers and the raw_body to an #Event object.

Parameters:

Name Type Description Default
headers Mapping[str, str]

The HTTP headers. Must have X-Github-Event, X-Github-Delivery, User-Agent, Content-Type. May have X-Hub-Signature or X-Hub-Signature-256.

required
raw_body bytes

The raw request body for the event. This is converted into a JSON payload.

required
webhook_secret Optional[str]

If specified, the X-Hub-Signature or X-Hub-Signature-256 headers are used to verify the signature of the payload. If not specified, the client does not validate the signature.

None
Source code in github_bot_api/event.py
def accept_event(
    headers: t.Mapping[str, str],
    raw_body: bytes,
    webhook_secret: t.Optional[str] = None,
) -> Event:
    """
    Converts thee HTTP *headers* and the *raw_body* to an #Event object.

    Args:
        headers: The HTTP headers. Must have `X-Github-Event`, `X-Github-Delivery`, `User-Agent`, `Content-Type`.
                 May have `X-Hub-Signature` or `X-Hub-Signature-256`.
        raw_body: The raw request body for the event. This is converted into a JSON payload.
        webhook_secret: If specified, the `X-Hub-Signature` or `X-Hub-Signature-256` headers are used to verify
                        the signature of the payload. If not specified, the client does not validate the signature.
    """

    event_name = headers.get("X-GitHub-Event")
    delivery_id = headers.get("X-GitHub-Delivery")
    signature_1 = headers.get("X-Hub-Signature")
    signature_256 = headers.get("X-Hub-Signature-256")
    user_agent = headers.get("User-Agent")
    content_type = headers.get("Content-Type")

    if not event_name or not delivery_id or not user_agent or not content_type:
        raise InvalidRequest("missing required headers")
    if webhook_secret is not None and not signature_1 and not signature_256:
        raise InvalidRequest("webhook secret is configured but no signature header was received")

    mime_type, parameters = get_mime_components(content_type)
    if mime_type != "application/json":
        raise InvalidRequest(f"expected Content-Type: application/json, got {content_type}")
    encoding = dict(parameters).get("encoding", "UTF-8")

    if webhook_secret is not None:
        if signature_256:
            check_signature(signature_256, raw_body, webhook_secret.encode("ascii"), algo="sha256")
        elif signature_1:
            check_signature(signature_1, raw_body, webhook_secret.encode("ascii"), algo="sha1")
        else:
            raise RuntimeError

    return Event(
        event_name,
        delivery_id,
        signature_256 or signature_1,
        user_agent,
        json.loads(raw_body.decode(encoding)),
    )

github_bot_api.flask

Flask binding for handling GitHub webhook events.

Note that you need to install the flask module separately.

Example

from github_bot_api import Event, Webhook
from github_bot_api.flask import create_flask_app

def on_any_event(event: Event) -> bool:
  print(event)
  return True

webhook = Webhook(secret=None)
webhook.listen('*', on_any_event)

import os; os.environ['FLASK_ENV'] = 'development'
flask_app = create_flask_app(__name__, webhook)
flask_app.run()

create_event_handler

create_event_handler(
    webhook: Webhook,
) -> Callable[[], Tuple[Text, int, Dict[str, str]]]

Creates an event handler flask view that interprets the received HTTP request as a GitHub application event and dispatches it via #webhook.dispatch().

Source code in github_bot_api/flask.py
def create_event_handler(webhook: Webhook) -> t.Callable[[], t.Tuple[t.Text, int, t.Dict[str, str]]]:
    """
    Creates an event handler flask view that interprets the received HTTP request as a GitHub application
    event and dispatches it via #webhook.dispatch().
    """

    def event_handler():
        event = accept_event(
            t.cast(t.Mapping[str, str], flask.request.headers), flask.request.get_data(), webhook.secret
        )
        webhook.dispatch(event)
        return "", 202, {}

    return event_handler

create_flask_app

create_flask_app(
    name: str,
    webhook: Webhook,
    path: str = "/event-handler",
) -> Flask

Creates a new #flask.Flask application with a POST event handler under the given path (defaulting to /event-handler). This is a useful shorthand to attach your #Webhook to an HTTP server.

Source code in github_bot_api/flask.py
def create_flask_app(
    name: str,
    webhook: Webhook,
    path: str = "/event-handler",
) -> flask.Flask:
    """
    Creates a new #flask.Flask application with a `POST` event handler under the given *path* (defaulting
    to `/event-handler`). This is a useful shorthand to attach your #Webhook to an HTTP server.
    """

    flask_app = flask.Flask(name)
    flask_app.route(path, methods=["POST"])(create_event_handler(webhook))
    return flask_app