Skip to content

Commit c5643df

Browse files
add management command for importing an existing installation
1 parent b1c1af1 commit c5643df

File tree

8 files changed

+188
-2
lines changed

8 files changed

+188
-2
lines changed

Justfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,23 @@ lint:
2929
lock *ARGS:
3030
uv lock {{ ARGS }}
3131

32+
manage *COMMAND:
33+
#!/usr/bin/env python
34+
import sys
35+
36+
try:
37+
from django.conf import settings
38+
from django.core.management import execute_from_command_line
39+
except ImportError as exc:
40+
raise ImportError(
41+
"Couldn't import Django. Are you sure it's installed and "
42+
"available on your PYTHONPATH environment variable? Did you "
43+
"forget to activate a virtual environment?"
44+
) from exc
45+
46+
settings.configure(INSTALLED_APPS=["django_github_app"])
47+
execute_from_command_line(sys.argv + "{{ COMMAND }}".split(" "))
48+
3249
test *ARGS:
3350
@just nox test {{ ARGS }}
3451

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ classifiers = [
4949
]
5050
dependencies = [
5151
"cachetools>=5.5.0",
52+
"django-typer[rich]>=2.4.0",
5253
"django>=4.2",
5354
"gidgethub>=5.3.0",
54-
"httpx>=0.27.2"
55+
"httpx>=0.27.2",
5556
]
5657
description = "A Django toolkit for GitHub Apps with batteries included."
5758
dynamic = ["version"]

src/django_github_app/github.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
8585

8686
class GitHubAPIEndpoint(Enum):
8787
INSTALLATION_REPOS = "/installation/repositories"
88+
ORG_INSTALLATIONS = "/orgs/{org}/installations"
89+
REPO_INSTALLATION = "/repos/{owner}/{repo}/installation"
90+
REPO_ISSUE = "/repos/{owner}/{repo}/issues/{issue_number}"
8891
REPO_ISSUES = "/repos/{owner}/{repo}/issues"
92+
USER_INSTALLATION = "/users/{username}/installation"
8993

9094

9195
@dataclass(frozen=True, slots=True)

src/django_github_app/management/__init__.py

Whitespace-only changes.

src/django_github_app/management/commands/__init__.py

Whitespace-only changes.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from __future__ import annotations
2+
3+
from typing import Annotated
4+
from typing import Literal
5+
6+
from asgiref.sync import async_to_sync
7+
from django_typer.management import Typer
8+
from typer import Option
9+
10+
from django_github_app.conf import app_settings
11+
from django_github_app.github import AsyncGitHubAPI
12+
from django_github_app.github import GitHubAPIEndpoint
13+
from django_github_app.github import GitHubAPIUrl
14+
from django_github_app.models import Installation
15+
from django_github_app.models import Repository
16+
17+
cli = Typer(help="Manage your GitHub App")
18+
19+
20+
async def get_installation(name, installation_id, oauth_token):
21+
async with AsyncGitHubAPI(app_settings.SLUG) as gh:
22+
gh.oauth_token = oauth_token
23+
endpoint = GitHubAPIUrl(
24+
GitHubAPIEndpoint.ORG_INSTALLATIONS,
25+
{"org": name},
26+
)
27+
data = await gh.getitem(endpoint.full_url)
28+
for installation in data.get("installations"):
29+
if installation["id"] == installation_id:
30+
return installation
31+
return None
32+
33+
34+
async def get_repos(installation):
35+
async with AsyncGitHubAPI(installation.app_slug) as gh:
36+
gh.oauth_token = await installation.aget_access_token(gh)
37+
url = GitHubAPIUrl(GitHubAPIEndpoint.INSTALLATION_REPOS)
38+
repos = [
39+
repo async for repo in gh.getiter(url.full_url, iterable_key="repositories")
40+
]
41+
print(f"{repos=}")
42+
43+
44+
@cli.command()
45+
def import_app(
46+
name: Annotated[
47+
str,
48+
Option(
49+
help="The name of the user, repository (owner/repo), or organization the GitHub App is installed on"
50+
),
51+
],
52+
type: Annotated[
53+
Literal["user", "repo", "org"],
54+
Option(help="The type of account the GitHub App is installed on"),
55+
],
56+
installation_id: Annotated[
57+
int, Option(help="The installation id of the existing GitHub App")
58+
],
59+
oauth_token: Annotated[
60+
str, Option(help="PAT for accessing GitHub App installations")
61+
],
62+
):
63+
"""
64+
Import an existing GitHub App to database Models.
65+
"""
66+
installation_data = async_to_sync(get_installation)(
67+
name, installation_id, oauth_token
68+
)
69+
if installation_data:
70+
installation = Installation.objects.create_from_gh_data(installation_data)
71+
repository_data = installation.get_repos()
72+
Repository.objects.create_from_gh_data(repository_data)

src/django_github_app/views.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from django.utils.decorators import method_decorator
1515
from django.views.decorators.csrf import csrf_exempt
1616
from django.views.generic import View
17+
from gidgethub.abc import GitHubAPI
1718
from gidgethub.routing import Router as GidgetHubRouter
1819
from gidgethub.sansio import Event
1920

@@ -25,7 +26,7 @@
2526
from .models import Installation
2627
from .routing import GitHubRouter
2728

28-
GitHubAPIType = TypeVar("GitHubAPIType", AsyncGitHubAPI, SyncGitHubAPI)
29+
GitHubAPIType = TypeVar("GitHubAPIType", AsyncGitHubAPI, GitHubAPI, SyncGitHubAPI)
2930

3031

3132
class BaseWebhookView(View, ABC, Generic[GitHubAPIType]):

uv.lock

Lines changed: 91 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)