Skip to content

Commit

Permalink
views: move view utils into views.utils
Browse files Browse the repository at this point in the history
  • Loading branch information
enku committed Sep 14, 2024
1 parent 2d2f715 commit 4ca6c1a
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 107 deletions.
2 changes: 1 addition & 1 deletion src/gentoo_build_publisher/urls.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Default urlconf for gentoo_build_publisher"""

from gentoo_build_publisher.views import ViewFinder
from gentoo_build_publisher.views.utils import ViewFinder

urlpatterns = ViewFinder.find()
79 changes: 8 additions & 71 deletions src/gentoo_build_publisher/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@

from __future__ import annotations

from functools import wraps
from typing import Any, Callable, Mapping, TypeAlias

from ariadne_django.views import GraphQLView
from django.conf import settings
from django.core.cache import cache
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import render as _render
from django.urls import URLPattern, path
from django.http import Http404, HttpRequest

from gentoo_build_publisher import publisher
from gentoo_build_publisher.graphql import schema
Expand All @@ -22,72 +17,14 @@
create_dashboard_context,
create_machine_context,
)
from gentoo_build_publisher.views.utils import get_query_value_from_request
from gentoo_build_publisher.views.utils import (
ViewContext,
get_query_value_from_request,
render,
view,
)

GBP_SETTINGS = getattr(settings, "BUILD_PUBLISHER", {})
View: TypeAlias = Callable[..., HttpResponse]
ViewContext: TypeAlias = Mapping[str, Any]
TemplateView: TypeAlias = Callable[..., ViewContext]


def view(pattern: str, **kwargs: Any) -> Callable[[View], View]:
"""Decorator to register a view"""

def dec(view_func: View) -> View:
ViewFinder.register(pattern, view_func, **kwargs)
return view_func

return dec


class ViewFinder:
"""Django view registry"""

pattern_views: list[URLPattern] = []

@classmethod
def register(cls, pattern: str, view_func: View, **kwargs: Any) -> None:
"""Register the given view for the given pattern"""
cls.pattern_views.append(path(pattern, view_func, **kwargs))

@classmethod
def find(cls) -> list[URLPattern]:
"""Return a list of url_path/view mappings for the Django url resolver"""
return cls.pattern_views


def render(
template_name: str, content_type: str | None = None
) -> Callable[[TemplateView], View]:
"""Instruct a view to render the given template
The view should return a context mapping
"""

def dec(view_func: TemplateView) -> View:
@wraps(view_func)
def wrapper(request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
context = view_func(request, *args, **kwargs)
return _render(request, template_name, context, content_type=content_type)

return wrapper

return dec


def experimental(view_func: View) -> View:
"""Mark a view as experimental
Experimental views return 404s when not in DEBUG mode.
"""

@wraps(view_func)
def wrapper(request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
if not settings.DEBUG:
raise Http404
return view_func(request, *args, **kwargs)

return wrapper


@view("", name="dashboard")
Expand Down Expand Up @@ -137,7 +74,7 @@ def binrepos_dot_conf(request: HttpRequest, machine: str) -> ViewContext:
return {"machine": machine, "uri": uri}


graphql = view("graphql")(GraphQLView.as_view(schema=schema))
view("graphql")(GraphQLView.as_view(schema=schema))


def parse_tag_or_raise_404(machine_tag: str) -> tuple[Build, str, str]:
Expand Down
72 changes: 69 additions & 3 deletions src/gentoo_build_publisher/views/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
from __future__ import annotations

import datetime as dt
from functools import lru_cache
from typing import Any, TypeAlias
from functools import lru_cache, wraps
from typing import Any, Callable, Mapping, TypeAlias

from django.http import HttpRequest
from django.conf import settings
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import render as _render
from django.urls import URLPattern, path

from gentoo_build_publisher import publisher
from gentoo_build_publisher.records import BuildRecord
Expand All @@ -18,11 +21,74 @@
CPV: TypeAlias = str # pylint: disable=invalid-name
Gradient: TypeAlias = list[str]
MachineName: TypeAlias = str
View: TypeAlias = Callable[..., HttpResponse]
ViewContext: TypeAlias = Mapping[str, Any]
TemplateView: TypeAlias = Callable[..., ViewContext]


_NOT_FOUND = object()


def view(pattern: str, **kwargs: Any) -> Callable[[View], View]:
"""Decorator to register a view"""

def dec(view_func: View) -> View:
ViewFinder.register(pattern, view_func, **kwargs)
return view_func

return dec


class ViewFinder:
"""Django view registry"""

pattern_views: list[URLPattern] = []

@classmethod
def register(cls, pattern: str, view_func: View, **kwargs: Any) -> None:
"""Register the given view for the given pattern"""
cls.pattern_views.append(path(pattern, view_func, **kwargs))

@classmethod
def find(cls) -> list[URLPattern]:
"""Return a list of url_path/view mappings for the Django url resolver"""
return cls.pattern_views


def render(
template_name: str, content_type: str | None = None
) -> Callable[[TemplateView], View]:
"""Instruct a view to render the given template
The view should return a context mapping
"""

def dec(view_func: TemplateView) -> View:
@wraps(view_func)
def wrapper(request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
context = view_func(request, *args, **kwargs)
return _render(request, template_name, context, content_type=content_type)

return wrapper

return dec


def experimental(view_func: View) -> View:
"""Mark a view as experimental
Experimental views return 404s when not in DEBUG mode.
"""

@wraps(view_func)
def wrapper(request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
if not settings.DEBUG:
raise Http404
return view_func(request, *args, **kwargs)

return wrapper


class StatsCollector:
"""Interface to collect statistics about the Publisher"""

Expand Down
32 changes: 1 addition & 31 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
# pylint: disable=missing-class-docstring,missing-function-docstring,too-many-ancestors
import datetime as dt
from functools import partial
from unittest import mock

import unittest_fixtures as fixture
from django import urls
from django.http import Http404, HttpRequest, HttpResponse
from django.http import HttpResponse

from gentoo_build_publisher import publisher
from gentoo_build_publisher.types import Build
from gentoo_build_publisher.views import experimental

from . import DjangoTestCase as BaseTestCase

Expand Down Expand Up @@ -170,34 +168,6 @@ def test_returns_404_on_nonbuild_machines(self) -> None:
self.assertEqual(response.status_code, 404)


class ExperimentalMarkerTests(TestCase):
def test_debug_is_false(self) -> None:
request = mock.MagicMock(spec=HttpRequest)
experimental_view = experimental(dummy_view)

with self.settings(DEBUG=False):
response = dummy_view(request)
self.assertEqual(response.status_code, 200)

with self.assertRaises(Http404):
response = experimental_view(request)

def test_debug_is_true(self) -> None:
request = mock.MagicMock(spec=HttpRequest)
experimental_view = experimental(dummy_view)

with self.settings(DEBUG=True):
response = dummy_view(request)
self.assertEqual(response.status_code, 200)

response = experimental_view(request)
self.assertEqual(response.status_code, 200)


def dummy_view(request: HttpRequest) -> HttpResponse:
return HttpResponse("Hi!")


def first_build(build_dict: dict[str, list[Build]], name: str) -> Build:
return build_dict[name][0]

Expand Down
33 changes: 32 additions & 1 deletion tests/test_views_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@
import datetime as dt
from unittest import mock

import unittest_fixtures as fixture
from django.http import Http404, HttpRequest, HttpResponse
from django.utils import timezone

from gentoo_build_publisher import publisher
from gentoo_build_publisher.types import Build, Content
from gentoo_build_publisher.utils.time import localtime
from gentoo_build_publisher.views.utils import (
StatsCollector,
experimental,
get_metadata,
get_query_value_from_request,
)

from . import TestCase, fixture
from . import DjangoTestCase, TestCase
from .factories import (
ArtifactFactory,
BuildFactory,
Expand Down Expand Up @@ -247,6 +250,34 @@ def test_packages_by_day(self) -> None:
self.assertEqual(len(pbd[d2.date()]), 6)


class ExperimentalMarkerTests(DjangoTestCase):
def test_debug_is_false(self) -> None:
request = mock.MagicMock(spec=HttpRequest)
experimental_view = experimental(dummy_view)

with self.settings(DEBUG=False):
response = dummy_view(request)
self.assertEqual(response.status_code, 200)

with self.assertRaises(Http404):
response = experimental_view(request)

def test_debug_is_true(self) -> None:
request = mock.MagicMock(spec=HttpRequest)
experimental_view = experimental(dummy_view)

with self.settings(DEBUG=True):
response = dummy_view(request)
self.assertEqual(response.status_code, 200)

response = experimental_view(request)
self.assertEqual(response.status_code, 200)


def dummy_view(request: HttpRequest) -> HttpResponse:
return HttpResponse("Hi!")


def create_builds_and_packages(
machine: str,
number_of_builds: int,
Expand Down

0 comments on commit 4ca6c1a

Please sign in to comment.