Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,21 @@ app.router.add_get('/health', aiohttp_endpoint)
if __name__ == '__main__':
web.run_app(app)
```

### Django Integration

```python
# urls.py
from django.urls import path
from probirka import Probirka, make_django_view

probirka_instance = Probirka()

@probirka_instance.add(name="api")
async def check_api():
return True

urlpatterns = [
path("health/", make_django_view(probirka_instance)),
]
```
8 changes: 8 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,11 @@ AIOHTTP
:members:
:undoc-members:
:show-inheritance:

Django
~~~~~~

.. automodule:: probirka._django
:members:
:undoc-members:
:show-inheritance:
23 changes: 23 additions & 0 deletions docs/source/integration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,26 @@ Here's an example of aiohttp integration:
web.run_app(app, host="0.0.0.0", port=8000)

After running, you can get the check results by sending a GET request to `/health`. The response will be in the same JSON format as for FastAPI.

Django
------

Here's an example of Django integration:

.. code-block:: python


from django.urls import path
from probirka import Probirka, make_django_view

probirka_instance = Probirka()

@probirka_instance.add(name="api")
async def check_api():
return True

urlpatterns = [
path("health/", make_django_view(probirka_instance)),
]

After running, you can get the check results by sending a GET request to `/health`. The response will be in the same JSON format as for FastAPI.
5 changes: 5 additions & 0 deletions probirka/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@
from probirka._fastapi import make_fastapi_endpoint # noqa

__all__.append('make_fastapi_endpoint')

with suppress(ImportError):
from probirka._django import make_django_view # noqa

__all__.append('make_django_view')
54 changes: 54 additions & 0 deletions probirka/_django.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from dataclasses import asdict
from typing import Any, Callable, Coroutine, List, Optional, Union

from django.http import HttpRequest, HttpResponse, JsonResponse

from probirka import Probirka


def make_django_view(
probirka: Probirka,
timeout: Optional[int] = None,
with_groups: Union[str, List[str]] = '',
skip_required: bool = False,
return_results: bool = True,
success_code: int = 200,
error_code: int = 500,
) -> Callable[[HttpRequest], Coroutine[Any, Any, HttpResponse]]:
"""
Create a Django async view for a given Probirka instance.

Args:
probirka (Probirka): The Probirka instance to run.
timeout (Optional[int]): The timeout for the Probirka run.
with_groups (Union[str, List[str]]): Groups to include in the Probirka run.
skip_required (bool): Whether to skip required checks.
return_results (bool): Whether to return the results in the response.
success_code (int): The HTTP status code for a successful response.
error_code (int): The HTTP status code for an error response.

Returns:
Callable[[HttpRequest], Coroutine[Any, Any, HttpResponse]]: The Django async view.
"""

async def view(_: HttpRequest) -> HttpResponse:
"""
The Django view that runs the Probirka instance.

Args:
_: The Django request object.

Returns:
HttpResponse: The HTTP response with the Probirka results.
"""
res = await probirka.run(
timeout=timeout,
with_groups=with_groups,
skip_required=skip_required,
)
status_code = success_code if res.ok else error_code
if return_results:
return JsonResponse(asdict(res), status=status_code, safe=False)
return HttpResponse(status=status_code)

return view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ build-backend = "hatchling.build"
[dependency-groups]
dev = [
"aiohttp>=3.10.11",
"django>=4.2",
"bandit>=1.7.10",
"fastapi>=0.115.6",
"httpx>=0.28.1",
Expand Down
177 changes: 177 additions & 0 deletions tests/test_django.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
from typing import List

import pytest

from probirka import Probirka, ProbeBase

# Django expects a module-level urlpatterns
urlpatterns: List[object] = []
from probirka._django import make_django_view


class SuccessProbe(ProbeBase):
async def _check(self) -> bool:
return True


class FailureProbe(ProbeBase):
async def _check(self) -> bool:
return False


def _ensure_django_configured() -> None:
# Minimal lazy configuration for Django in tests
from django.conf import settings

if settings.configured: # type: ignore[attr-defined]
return

settings.configure( # type: ignore[attr-defined]
DEBUG=False,
SECRET_KEY='test-secret-key',
ROOT_URLCONF=__name__,
ALLOWED_HOSTS=['*'],
INSTALLED_APPS=[],
MIDDLEWARE=[],
TEMPLATES=[],
USE_TZ=True,
)

import django

django.setup()


# Build URLs dynamically per-test using a helper
def _make_urls(patterns: List[object]) -> None:
global urlpatterns # Django discovers this symbol by name
urlpatterns = list(patterns) # type: ignore[assignment]
from django.urls import clear_url_caches, set_urlconf

clear_url_caches()
set_urlconf(__name__)


@pytest.fixture
def probirka() -> Probirka:
return Probirka()


def test_successful_response(probirka: Probirka) -> None:
_ensure_django_configured()

from django.test import Client
from django.urls import path

probirka.add_info('some_field', 'value')
probirka.add_probes(SuccessProbe())

view = make_django_view(probirka)
_make_urls([path('health/', view)])

client = Client()
response = client.get('/health/')

assert response.status_code == 200
data = response.json()
assert data['ok'] is True
assert data['info']['some_field'] == 'value'
assert len(data['checks']) == 1
assert data['checks'][0]['ok'] is True


def test_error_response(probirka: Probirka) -> None:
_ensure_django_configured()

from django.test import Client
from django.urls import path

probirka.add_probes(FailureProbe())

view = make_django_view(probirka)
_make_urls([path('health/', view)])

client = Client()
response = client.get('/health/')

assert response.status_code == 500
data = response.json()
assert data['ok'] is False
assert len(data['checks']) == 1
assert data['checks'][0]['ok'] is False


def test_custom_status_codes(probirka: Probirka) -> None:
_ensure_django_configured()

from django.test import Client
from django.urls import path

probirka.add_probes(SuccessProbe())

view = make_django_view(
probirka,
success_code=201,
error_code=400,
)
_make_urls([path('health/', view)])

client = Client()
response = client.get('/health/')

assert response.status_code == 201
data = response.json()
assert data['ok'] is True


def test_without_results(probirka: Probirka) -> None:
_ensure_django_configured()

from django.test import Client
from django.urls import path

probirka.add_probes(SuccessProbe())

view = make_django_view(
probirka,
return_results=False,
)
_make_urls([path('health/', view)])

client = Client()
response = client.get('/health/')

assert response.status_code == 200
assert response.content == b''


def test_with_custom_parameters(probirka: Probirka) -> None:
_ensure_django_configured()

from django.test import Client
from django.urls import path

success_probe_1 = SuccessProbe()
success_probe_2 = SuccessProbe()

probirka.add_probes(success_probe_1) # required probe
probirka.add_probes(success_probe_2, groups=['group1']) # optional probe

view = make_django_view(
probirka,
timeout=30,
with_groups=['group1'],
skip_required=True,
)
_make_urls([path('health/', view)])

client = Client()
response = client.get('/health/')

assert response.status_code == 200
data = response.json()
assert data['ok'] is True
# Ensure only one probe ran (the optional one from group1)
assert len(data['checks']) == 1


Loading