Skip to content

Commit 3adfffa

Browse files
khvn26Copilot
andauthored
feat: Provide documented routes as labels (#33)
* feat: Provide documented routes as labels * fix docstring Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent 07920e4 commit 3adfffa

File tree

3 files changed

+42
-4
lines changed

3 files changed

+42
-4
lines changed

src/common/gunicorn/middleware.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django.http import HttpRequest, HttpResponse
44

55
from common.gunicorn.constants import WSGI_DJANGO_ROUTE_ENVIRON_KEY
6+
from common.gunicorn.utils import get_route_template
67

78

89
class RouteLoggerMiddleware:
@@ -23,6 +24,8 @@ def __call__(self, request: HttpRequest) -> HttpResponse:
2324
if resolver_match := request.resolver_match:
2425
# https://peps.python.org/pep-3333/#specification-details
2526
# "...the application is allowed to modify the dictionary in any way it desires"
26-
request.META[WSGI_DJANGO_ROUTE_ENVIRON_KEY] = resolver_match.route
27+
request.META[WSGI_DJANGO_ROUTE_ENVIRON_KEY] = get_route_template(
28+
resolver_match.route
29+
)
2730

2831
return response

src/common/gunicorn/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import argparse
22
import os
3+
from functools import lru_cache
34
from typing import Any
45

56
from django.core.handlers.wsgi import WSGIHandler
67
from django.core.wsgi import get_wsgi_application
8+
from drf_yasg.generators import EndpointEnumerator # type: ignore[import-untyped]
79
from environs import Env
810
from gunicorn.app.wsgiapp import ( # type: ignore[import-untyped]
911
WSGIApplication as GunicornWSGIApplication,
@@ -59,3 +61,18 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
5961

6062
def run_server(options: dict[str, Any] | None = None) -> None:
6163
DjangoWSGIApplication(options).run()
64+
65+
66+
@lru_cache
67+
def get_route_template(route: str) -> str:
68+
"""
69+
Convert a Django regex route to a template string that can be
70+
searched for in the API documentation.
71+
72+
e.g.,
73+
74+
`"^api/v1/environments/(?P<environment_api_key>[^/.]+)/api-keys/$"` ->
75+
`"/api/v1/environments/{environment_api_key}/api-keys/"`
76+
"""
77+
route_template: str = EndpointEnumerator().get_path_from_regex(route)
78+
return route_template

tests/unit/common/gunicorn/test_utils.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
import time
55

66
import pytest
7-
import pytest_mock
7+
from drf_yasg.generators import EndpointEnumerator # type: ignore[import-untyped]
8+
from pytest_mock import MockerFixture
89

910
from common.gunicorn.utils import (
1011
DjangoWSGIApplication,
12+
get_route_template,
1113
run_server,
1214
)
1315

1416

1517
def test_django_wsgi_application__defaults__expected_config(
16-
mocker: pytest_mock.MockerFixture,
18+
mocker: MockerFixture,
1719
) -> None:
1820
# Given
1921
wsgi_handler_mock = mocker.Mock()
@@ -37,7 +39,7 @@ def test_django_wsgi_application__defaults__expected_config(
3739

3840
def test_run_server__default_config_file__runs_expected(
3941
unused_tcp_port: int,
40-
mocker: pytest_mock.MockerFixture,
42+
mocker: MockerFixture,
4143
) -> None:
4244
# Given
4345
# prevent real forking from Gunicorn
@@ -58,3 +60,19 @@ def delay_kill(pid: int = pid) -> None:
5860

5961
# Then
6062
mark_process_dead_mock.assert_called_once_with(pid)
63+
64+
65+
def test_get_route_template__returns_expected__caches_expected(
66+
mocker: MockerFixture,
67+
) -> None:
68+
# Given
69+
get_path_from_regex_spy = mocker.spy(EndpointEnumerator, "get_path_from_regex")
70+
regex_route = "^api/v1/environments/(?P<environment_api_key>[^/.]+)/api-keys/$"
71+
72+
# When
73+
result = get_route_template(regex_route)
74+
get_route_template(regex_route)
75+
76+
# Then
77+
assert result == "/api/v1/environments/{environment_api_key}/api-keys/"
78+
get_path_from_regex_spy.assert_called_once()

0 commit comments

Comments
 (0)