Creating clients with GitHubAppClientFactory

Gidgethub is the recommended library for building GitHub integrations with Safir applications. To create a Gidgethub client for a GitHub App, Safir provides the safir.github.GitHubAppClientFactory class.

Note

GitHub provides multiple way of building integrations, each with slightly different capabilities and authentication flows. The GitHubAppClientFactory is designed to work with GitHub Apps, which are the recommended way of building integrations. GitHub Apps are specifically installed into an organization or user account, and can be granted access to specific repositories.

Another common way of building an integration is as an OAuth app (particularly before the introduction of GitHub Apps). OAuth app integrations are still useful if your app needs to access a user’s GitHub account (such as to provide a GitHub login for your application). However, it is not generally recommended since it requires users to grant your app access to their account.

A third way of building a GitHub integration is to authenticate as a user with a personal access token (usually for a bot account). GitHub Apps generally replace this use case for all but the simplest integrations. Note in particular that a GitHub App can be authenticated on behalf of a user.

For a comparison of the different ways of building integrations, see GitHub’s documentation.

Setting up the factory

The GitHubAppClientFactory requires information about the GitHub App, which you will normally include in your application’s configuration, and an instance of httpx.AsyncClient, which your application will typically create with the http_client_dependency dependency (see Using the HTTPX client). For information about creating a GitHub App, retrieving its App ID and generating a private key, see GitHub’s documentation.

from pydantic import Field, SecretStr
from pydantic_settings import BaseSettings

from safir.github import GitHubAppClientFactory


class Config(BaseSettings):
    """Application configuration showing common configurations needed for
    GitHub App functionality.
    """

    github_app_id: str = Field(validation_alias="GITHUB_APP_ID")

    github_webhook_secret: SecretStr = Field(
        validation_alias="GITHUB_WEBHOOK_SECRET"
    )

    github_app_private_key: SecretStr = Field(
        validation_alias="GITHUB_APP_PRIVATE_KEY"
    )


config = Config()

github_client_factory = GitHubAppClientFactory(
    id=config.github_app_id,
    key=config.github_app_private_key.get_secret_value(),
    name="lsst-sqre/example",
    http_client=http_client,  # from http_client dependency
)

Tip

The name parameter is the name of your GitHub App. It is used to generate the User-Agent header for requests made by the client. There isn’t a hard-and-fast rule for what to use here, but it is recommended to use the name of the repository your app is built from (like lsst-sqre/example).

Authenticating as the app

Your app can authenticate to GitHub in two modes: as the app itself, or as an installation of the app. Authenticating as the GitHub App enables your app to access information about the app itself, such as its installations.

To do this, get the app’s JWT and use it with an GitHub client requests:

client = github_client_factory.create_anonymous_client()
jwt = github_client_factory.get_app_jwt()

Example of using the JWT to get the app’s installations:

data = await client.getitem("/app/installations", jwt=jwt)

Authenticating as the app installation

To authenticate as an installation of the app, you need to know the installation ID. Depending on the scenario, there are multiple ways of getting the installation ID.

In a webhook handler

If your app receives a webhook from GitHub, the installation ID will be included in the payload (as the installation.id field, see the GitHubAppInstallationModel).

installation_client = (
    await github_client_factory.create_installation_client(
        installation_id=webhook_data.installation.id,
    )
)

For a known repository

If the repository is known to your app (through configuration, a database, an API request, etc.), you can use the GitHubAppClientFactory.create_installation_client_for_repo method. This method gets the installation ID for you:

installation_client = (
    await github_client_factory.create_installation_client_for_repo(
        owner="lsst-sqre",
        repo="example",
    )
)

Using the Gidgethub client

Once you have an authenticated Gidgethub client (an instance of gidgethub.httpx.GitHubAPI), you can use it to make requests to the GitHub API using GitHub REST API endpoints. Where possible, you can also parse the response data into Pydantic models (see GitHub API resource models):

from safir.github.models import GitHubRepositoryModel

response = await installation_client.getitem(
    "/repos/{owner}/{repo}/",
    url_vars={"owner": "lsst-sqre", "repo": "example"},
)
repository = GitHubRepositoryModel.parse_obj(response)

For more information on using Gidgethub, see the Gidgethub documentation.