Skip to content

Commit e2d4403

Browse files
remove auto detection of webhook type and use app settings (#36)
* remove auto detection of webhook type and use app settings * ordering * remove extra paren * adjust test
1 parent d4d652e commit e2d4403

File tree

10 files changed

+96
-107
lines changed

10 files changed

+96
-107
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ and this project attempts to adhere to [Semantic Versioning](https://semver.org/
1818

1919
## [Unreleased]
2020

21+
### Added
22+
23+
- Added `GITHUB_APP["WEBHOOK_TYPE"]` setting to configure async/sync handler selection.
24+
25+
### Removed
26+
27+
- Removed automatic detection of webhook type from URL configuration.
28+
2129
## [0.4.0]
2230

2331
### Added

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ Fully supports both sync (WSGI) and async (ASGI) Django applications.
7575
]
7676
```
7777
78+
> [!IMPORTANT]
79+
> Make sure your `GITHUB_APP["WEBHOOK_TYPE"]` setting matches your view choice:
80+
>
81+
> - Use `"async"` with `AsyncWebhookView`
82+
> - Use `"sync"` with `SyncWebhookView`
83+
7884
5. Setup your GitHub App, either by registering a new one or importing an existing one, and configure django-github-app using your GitHub App's information.
7985
8086
You will need the following information from your GitHub App:
@@ -126,6 +132,7 @@ Fully supports both sync (WSGI) and async (ASGI) Django applications.
126132
"NAME": env.str("GITHUB_NAME"),
127133
"PRIVATE_KEY": env.str("GITHUB_PRIVATE_KEY"),
128134
"WEBHOOK_SECRET": env.str("GITHUB_WEBHOOK_SECRET"),
135+
"WEBHOOK_TYPE": "async", # Use "async" for ASGI projects or "sync" for WSGI projects
129136
}
130137
```
131138
@@ -162,6 +169,7 @@ Fully supports both sync (WSGI) and async (ASGI) Django applications.
162169
"NAME": env.str("GITHUB_NAME"),
163170
"PRIVATE_KEY": env.str("GITHUB_PRIVATE_KEY"),
164171
"WEBHOOK_SECRET": env.str("GITHUB_WEBHOOK_SECRET"),
172+
"WEBHOOK_TYPE": "async", # Use "async" for ASGI projects or "sync" for WSGI projects
165173
}
166174
```
167175
@@ -452,7 +460,7 @@ The library includes event handlers for managing GitHub App installations and re
452460
- Repository events:
453461
- `repository.renamed`: Updates repository details
454462
455-
The library automatically detects whether you're using `AsyncWebhookView` or `SyncWebhookView` in your URL configuration and loads the corresponding async or sync versions of these handlers.
463+
The library loads either async or sync versions of these handlers based on your `GITHUB_APP["WEBHOOK_TYPE"]` setting.
456464
457465
### System Checks
458466
@@ -482,6 +490,7 @@ GITHUB_APP = {
482490
"NAME": "",
483491
"PRIVATE_KEY": "",
484492
"WEBHOOK_SECRET": "",
493+
"WEBHOOK_TYPE": "async",
485494
}
486495
```
487496
@@ -492,6 +501,7 @@ The following settings are required:
492501
- `NAME`
493502
- `PRIVATE_KEY`
494503
- `WEBHOOK_SECRET`
504+
- `WEBHOOK_TYPE`
495505
496506
### `APP_ID`
497507
@@ -573,6 +583,15 @@ GITHUB_APP = {
573583
574584
Secret used to verify webhook payloads from GitHub.
575585
586+
### `WEBHOOK_TYPE`
587+
588+
> 🔴 **Required** | `Literal["async", "sync"]` | Default: `"async"`
589+
590+
Determines whether the library uses async or sync handlers for processing webhook events:
591+
592+
- `"async"`: Use with `AsyncWebhookView` in ASGI projects
593+
- `"sync"`: Use with `SyncWebhookView` in WSGI projects
594+
576595
## Development
577596
578597
For detailed instructions on setting up a development environment and contributing to this project, see [CONTRIBUTING.md](CONTRIBUTING.md).

src/django_github_app/apps.py

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,9 @@ class GitHubAppConfig(AppConfig):
1313
@override
1414
def ready(self):
1515
from . import checks # noqa: F401
16+
from .conf import app_settings
1617

17-
try:
18-
webhook_type = self.detect_webhook_type()
19-
if webhook_type == "async":
20-
from .events import ahandlers # noqa: F401
21-
elif webhook_type == "sync":
22-
from .events import handlers # noqa: F401
23-
except (ImportError, ValueError):
24-
pass
25-
26-
@classmethod
27-
def detect_webhook_type(cls):
28-
from .views import AsyncWebhookView
29-
from .views import get_webhook_views
30-
31-
views = get_webhook_views()
32-
if views:
33-
return "async" if issubclass(views[0], AsyncWebhookView) else "sync"
34-
return None
18+
if app_settings.WEBHOOK_TYPE == "async":
19+
from .events import ahandlers # noqa: F401
20+
elif app_settings.WEBHOOK_TYPE == "sync":
21+
from .events import handlers # noqa: F401

src/django_github_app/checks.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,25 @@
33
from django.core.checks import Error
44
from django.core.checks import Tags
55
from django.core.checks import register
6+
from django.urls import get_resolver
67

7-
from django_github_app.views import AsyncWebhookView
8-
from django_github_app.views import get_webhook_views
8+
from .views import AsyncWebhookView
9+
from .views import SyncWebhookView
10+
11+
12+
def get_webhook_views():
13+
resolver = get_resolver()
14+
found_views = []
15+
16+
for pattern in resolver.url_patterns:
17+
if hasattr(pattern, "callback"):
18+
callback = pattern.callback
19+
view_class = getattr(callback, "view_class", None)
20+
if view_class:
21+
if issubclass(view_class, (AsyncWebhookView, SyncWebhookView)):
22+
found_views.append(view_class)
23+
24+
return found_views
925

1026

1127
@register(Tags.urls)

src/django_github_app/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from dataclasses import dataclass
44
from pathlib import Path
55
from typing import Any
6+
from typing import Literal
67

78
from django.conf import settings
89
from django.utils.text import slugify
@@ -21,6 +22,7 @@ class AppSettings:
2122
NAME: str = ""
2223
PRIVATE_KEY: str = ""
2324
WEBHOOK_SECRET: str = ""
25+
WEBHOOK_TYPE: Literal["async", "sync"] = "async"
2426

2527
@override
2628
def __getattribute__(self, __name: str) -> Any:

src/django_github_app/views.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from django.core.exceptions import BadRequest
1313
from django.http import HttpRequest
1414
from django.http import JsonResponse
15-
from django.urls import get_resolver
1615
from django.utils.decorators import method_decorator
1716
from django.views.decorators.csrf import csrf_exempt
1817
from django.views.generic import View
@@ -107,18 +106,3 @@ def post(self, request: HttpRequest) -> JsonResponse: # pragma: no cover
107106
self.router.dispatch(event, gh)
108107

109108
return self.get_response(event_log)
110-
111-
112-
def get_webhook_views():
113-
resolver = get_resolver()
114-
found_views = []
115-
116-
for pattern in resolver.url_patterns:
117-
if hasattr(pattern, "callback"):
118-
callback = pattern.callback
119-
view_class = getattr(callback, "view_class", None)
120-
if view_class:
121-
if issubclass(view_class, (AsyncWebhookView, SyncWebhookView)):
122-
found_views.append(view_class)
123-
124-
return found_views

tests/test_apps.py

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
from __future__ import annotations
22

3-
from unittest.mock import patch
4-
53
import pytest
6-
from django.views.generic import View
74

85
from django_github_app.apps import GitHubAppConfig
9-
from django_github_app.views import AsyncWebhookView
10-
from django_github_app.views import SyncWebhookView
116

127

138
class TestGitHubAppConfig:
@@ -16,34 +11,12 @@ def app(self):
1611
return GitHubAppConfig.create("django_github_app")
1712

1813
@pytest.mark.parametrize(
19-
"urls",
14+
"webhook_type",
2015
[
21-
[SyncWebhookView],
22-
[AsyncWebhookView],
23-
[View],
24-
[],
16+
"async",
17+
"sync",
2518
],
2619
)
27-
def test_app_ready_urls(self, urls, app, urlpatterns):
28-
with urlpatterns(urls):
29-
app.ready()
30-
31-
@pytest.mark.parametrize("error", [ImportError, ValueError])
32-
def test_app_ready_error(self, error, app):
33-
with patch.object(GitHubAppConfig, "detect_webhook_type", side_effect=error):
20+
def test_app_ready_urls(self, webhook_type, app, override_app_settings):
21+
with override_app_settings(WEBHOOK_TYPE=webhook_type):
3422
app.ready()
35-
36-
@pytest.mark.parametrize(
37-
"urls, expected",
38-
[
39-
([SyncWebhookView], "sync"),
40-
([AsyncWebhookView], "async"),
41-
([View], None),
42-
([], None),
43-
],
44-
)
45-
def test_detect_webhook_type(self, urls, expected, urlpatterns):
46-
with urlpatterns(urls):
47-
webhook_type = GitHubAppConfig.detect_webhook_type()
48-
49-
assert webhook_type == expected

tests/test_checks.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,47 @@
44
from django.views.generic import View
55

66
from django_github_app.checks import check_webhook_views
7+
from django_github_app.checks import get_webhook_views
78
from django_github_app.views import AsyncWebhookView
89
from django_github_app.views import SyncWebhookView
910

1011

12+
class TestProjectWebhookViews:
13+
def test_get_async(self, urlpatterns):
14+
with urlpatterns([AsyncWebhookView]):
15+
views = get_webhook_views()
16+
17+
assert len(views) == 1
18+
assert views[0] == AsyncWebhookView
19+
20+
def test_get_sync(self, urlpatterns):
21+
with urlpatterns([SyncWebhookView]):
22+
views = get_webhook_views()
23+
24+
assert len(views) == 1
25+
assert views[0] == SyncWebhookView
26+
27+
def test_get_both(self, urlpatterns):
28+
with urlpatterns([AsyncWebhookView, SyncWebhookView]):
29+
views = get_webhook_views()
30+
31+
assert len(views) == 2
32+
assert AsyncWebhookView in views
33+
assert SyncWebhookView in views
34+
35+
def test_get_normal_view(self, urlpatterns):
36+
with urlpatterns([View]):
37+
views = get_webhook_views()
38+
39+
assert len(views) == 0
40+
41+
def test_get_none(self, urlpatterns):
42+
with urlpatterns([]):
43+
views = get_webhook_views()
44+
45+
assert len(views) == 0
46+
47+
1148
class TestCheckWebhookViews:
1249
def test_async(self, urlpatterns):
1350
with urlpatterns([AsyncWebhookView]):

tests/test_conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
("NAME", ""),
2020
("PRIVATE_KEY", ""),
2121
("WEBHOOK_SECRET", ""),
22+
("WEBHOOK_TYPE", "async"),
2223
],
2324
)
2425
def test_default_settings(setting, default_setting):

tests/test_views.py

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from django.core.exceptions import BadRequest
1111
from django.http import JsonResponse
1212
from django.utils import timezone
13-
from django.views.generic import View
1413
from gidgethub import sansio
1514
from gidgethub.abc import GitHubAPI
1615
from model_bakery import baker
@@ -22,7 +21,6 @@
2221
from django_github_app.views import AsyncWebhookView
2322
from django_github_app.views import BaseWebhookView
2423
from django_github_app.views import SyncWebhookView
25-
from django_github_app.views import get_webhook_views
2624

2725
pytestmark = pytest.mark.django_db
2826

@@ -305,39 +303,3 @@ def test_router_dispatch_unhandled_event(
305303
response = view.post(request)
306304

307305
assert response.status_code == HTTPStatus.OK
308-
309-
310-
class TestProjectWebhookViews:
311-
def test_get_async(self, urlpatterns):
312-
with urlpatterns([AsyncWebhookView]):
313-
views = get_webhook_views()
314-
315-
assert len(views) == 1
316-
assert views[0] == AsyncWebhookView
317-
318-
def test_get_sync(self, urlpatterns):
319-
with urlpatterns([SyncWebhookView]):
320-
views = get_webhook_views()
321-
322-
assert len(views) == 1
323-
assert views[0] == SyncWebhookView
324-
325-
def test_get_both(self, urlpatterns):
326-
with urlpatterns([AsyncWebhookView, SyncWebhookView]):
327-
views = get_webhook_views()
328-
329-
assert len(views) == 2
330-
assert AsyncWebhookView in views
331-
assert SyncWebhookView in views
332-
333-
def test_get_normal_view(self, urlpatterns):
334-
with urlpatterns([View]):
335-
views = get_webhook_views()
336-
337-
assert len(views) == 0
338-
339-
def test_get_none(self, urlpatterns):
340-
with urlpatterns([]):
341-
views = get_webhook_views()
342-
343-
assert len(views) == 0

0 commit comments

Comments
 (0)