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
31 changes: 30 additions & 1 deletion public_api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
TopicDetailViewV2,
TopicListViewV2,
)
from public_api.version_02.views.timeseries_viewset import APITimeSeriesViewSetV2
from public_api.version_02.views.timeseries_viewset import (
APITimeSeriesViewSetV2,
APITimeSeriesViewSetV3,
)
from public_api.views import (
GeographyDetailView,
GeographyListView,
Expand Down Expand Up @@ -50,6 +53,7 @@ def construct_url_patterns_for_public_api(
urls = []
urls.extend(_construct_version_one_urls(prefix=prefix))
urls.extend(_construct_version_two_urls(prefix=prefix))
urls.extend(_construct_version_three_urls(prefix=prefix))

if MetricsPublicAPIInterface.is_auth_enabled():
urls.append(
Expand Down Expand Up @@ -222,3 +226,28 @@ def _construct_version_two_urls(
name="timeseries-list-v2",
),
]


def _construct_version_three_urls(
*,
prefix: str,
) -> list[resolvers.URLResolver]:
"""Returns a list of URLResolvers for the public_api version 3

Args:
prefix: The prefix to add to the start of the url paths

Returns:
List of `URLResolver` objects each representing a
set of versioned URLS.
"""

return [
path(
f"{prefix}v3/themes/<str:theme>",
APITimeSeriesViewSetV3.as_view(
{"get": "list"}, name=APITimeSeriesViewSetV3.name
),
name="timeseries-list-v3",
),
]
96 changes: 96 additions & 0 deletions public_api/version_02/views/timeseries_viewset.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import django_filters
from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import extend_schema
from rest_framework import pagination, viewsets
Expand Down Expand Up @@ -101,3 +102,98 @@ def get_queryset(self):
metric=self.kwargs["metric"],
restrict_to_public=True,
)


class APITimeSeriesFilterSetv3(django_filters.FilterSet):
geography = django_filters.CharFilter(field_name="geography", lookup_expr="iexact")
geography_type = django_filters.CharFilter(
field_name="geography_type", lookup_expr="iexact"
)

class Meta:
model = MetricsPublicAPIInterface.get_api_timeseries_model()
fields = [
"sub_theme",
"topic",
"geography_type",
"geography",
"metric",
"stratum",
"sex",
"age",
"year",
"epiweek",
"date",
"in_reporting_delay_period",
]


@extend_schema(tags=[PUBLIC_API_TAG])
class APITimeSeriesViewSetV3(viewsets.ReadOnlyModelViewSet):
"""
This endpoint will provide the full timeseries of a slice of data.

There is one mandatory URL parameter and other optional query parameters:

Note that by default, results are paginated by a page size of 5

This page size can be changed using the *page_size* parameter.
The maximum supported page size is **365**.

---

The mandatory URL parameter is:

- `theme` - The largest topical subgroup e.g. **infectious_disease**

---

The optional query parameters are as follows in order from first to last:

- `sub_theme` - A topical subgroup e.g. **respiratory**

- `topic` - The name of the topic e.g. **COVID-19**

- `geography_type` - The type of the geography type e.g. **Nation**

- `geography` - The name of the geography associated with metric e.g. **London**

- `metric` - The name of the metric being queried for e.g. **COVID-19_deaths_ONSByDay**

- `stratum` - Smallest subgroup a metric can be broken down into e.g. ethnicity, testing pillar

- `age` - Smallest subgroup a metric can be broken down into e.g. **15_44** for the age group of 15-44 years

- `sex` - Patient gender e.g. **f** for Female or **all** for all genders

- `year` - Epi year of the metrics value (important for annual metrics) e.g. **2020**

- `month` - Epi month of the metric value (important for monthly metrics) e.g. **12**

- `epiweek` - Epi week of the metric value (important for weekly metrics) e.g. **30**

- `date` - The date which this metric value was recorded in the format **YYYY-MM-DD** e.g. **2020-07-20**

- `in_reporting_delay_period` - A boolean indicating whether the data point is considered to be subject
to retrospective updates.

"""

permission_classes = []
name = "API Time Series Version 3"
queryset = (
MetricsPublicAPIInterface.get_api_timeseries_model()
.objects.all()
.order_by("date")
)
serializer_class = APITimeSeriesListSerializerv2
pagination_class = APITimeSeriesPaginationv2
filter_backends = [DjangoFilterBackend]
filterset_class = APITimeSeriesFilterSetv3

def get_queryset(self):
queryset = super().get_queryset()

return queryset.filter(
theme=self.kwargs["theme"],
)
43 changes: 43 additions & 0 deletions tests/unit/public_api/test_timeseries_viewset_v3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import pytest
from rest_framework.test import APIRequestFactory

from public_api.version_02.views.timeseries_viewset import APITimeSeriesViewSetV3
from tests.factories.metrics.api_models.time_series import APITimeSeriesFactory


@pytest.mark.django_db
class TestAPITimeSeriesViewSetV3GetQueryset:
def test_filters_queryset_by_theme_kwarg(self) -> None:
"""
Ensure `get_queryset` applies the `theme` URL kwarg as a filter.
"""
# Given: three records with different themes
infectious_record_1 = APITimeSeriesFactory.create_record(
theme_name="infectious_disease",
date="2023-01-01",
)
infectious_record_2 = APITimeSeriesFactory.create_record(
theme_name="infectious_disease",
date="2023-01-02",
)
APITimeSeriesFactory.create_record(
theme_name="extreme_event",
)

# And a view instance with the theme URL kwarg set
view = APITimeSeriesViewSetV3()
view.kwargs = {"theme": "infectious_disease"}

# DRF's GenericAPIView expects a `request` attribute, even though our
# overridden `get_queryset` does not use it directly.
view.request = APIRequestFactory().get("/<prefix>/v3/themes/infectious_disease")

# When
queryset = view.get_queryset()

# Then: only records matching the URL theme are returned
themes = {instance.theme for instance in queryset}
assert themes == {"infectious_disease"}
assert queryset.count() == 2
assert queryset.first() == infectious_record_1
assert queryset.last() == infectious_record_2