Cleanup/reorg (#10)

* Cleanup/reorg WIP - serializers next

* PR comments, fixes

* fix URLs
This commit is contained in:
Ethan Roseman
2022-08-26 11:02:16 +09:00
committed by GitHub
parent 5790357ac4
commit 686e15385e
9 changed files with 375 additions and 396 deletions

34
frog_api/views/common.py Normal file
View File

@@ -0,0 +1,34 @@
from frog_api.exceptions import (
InvalidAPIKeyException,
NonexistentCategoryException,
NonexistentProjectException,
NonexistentVersionException,
)
from frog_api.models import Category, Project, Version
def get_project(slug: str) -> Project:
ret = Project.objects.filter(slug=slug).first()
if not ret:
raise NonexistentProjectException(slug)
return ret
def get_version(slug: str, project: Project) -> Version:
ret = Version.objects.filter(slug=slug, project=project).first()
if not ret:
raise NonexistentVersionException(project.slug, slug)
return ret
def get_category(slug: str, version: Version) -> Category:
ret = Category.objects.filter(slug=slug, version=version).first()
if not ret:
raise NonexistentCategoryException(version.project.slug, version.slug, slug)
return ret
def validate_api_key(key: str, project: Project) -> bool:
if key != project.auth_key:
raise InvalidAPIKeyException()
return True

173
frog_api/views/data.py Normal file
View File

@@ -0,0 +1,173 @@
from typing import Any, List
from django.db import models
from frog_api.exceptions import (
InvalidDataException,
MissingAPIKeyException,
NoEntriesException,
)
from frog_api.models import Entry, Measure, Project, Version
from frog_api.serializers import TerseEntrySerializer
from frog_api.views.common import (
get_category,
get_project,
get_version,
validate_api_key,
)
from rest_framework import status
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
def get_latest_entry(
project_slug: str, version_slug: str, category_slug: str
) -> dict[str, Any]:
project = get_project(project_slug)
version = get_version(version_slug, project)
category = get_category(category_slug, version)
entry = Entry.objects.filter(category=category).first()
if entry is None:
raise NoEntriesException(project_slug, version_slug, category_slug)
# Re-format the measures (TODO: handle this in a DRF serializer)
entry_data = TerseEntrySerializer(entry).data
entry_data["measures"] = {m["type"]: m["value"] for m in entry_data["measures"]}
return entry_data
def get_versions_digest_for_project(project: Project) -> dict[Any, Any]:
versions = {}
for version in Version.objects.filter(project=project):
entry = get_latest_entry(project.slug, version.slug, "default")
if entry is not None:
versions[version.slug] = entry
return versions
class RootDataView(APIView):
"""
API endpoint that returns the most recent entry for overall progress of each version of each project.
"""
def get(self, request: Request) -> Response:
"""
Return the most recent entry for overall progress of each version of each project.
"""
projects = {}
for project in Project.objects.all():
versions = get_versions_digest_for_project(project)
if len(versions) > 0:
projects[project.slug] = versions
return Response({"progress": projects})
class ProjectDataView(APIView):
"""
API endpoint that returns the most recent entry for each version of a project.
"""
def get(self, request: Request, project_slug: str) -> Response:
"""
Return the most recent entry for overall progress for each version of a project.
"""
project = get_project(project_slug)
versions = get_versions_digest_for_project(project)
projects = {}
if len(versions) > 0:
projects[project_slug] = versions
return Response({"progress": projects})
class VersionDataView(APIView):
"""
API endpoint that returns the most recent entry for overall progress for a version of a project.
"""
@staticmethod
def create_entries(
req_data: dict[str, Any], project_slug: str, version_slug: str
) -> int:
project = get_project(project_slug)
version = get_version(version_slug, project)
if "api_key" not in req_data:
raise MissingAPIKeyException()
validate_api_key(req_data["api_key"], project)
to_save: List[models.Model] = []
for entry in req_data["data"]:
timestamp = entry["timestamp"]
git_hash = entry["git_hash"]
for cat in entry:
if cat in ["timestamp", "git_hash"]:
continue
if type(entry[cat]) is not dict:
continue
category = get_category(cat, version)
entry = Entry(category=category, timestamp=timestamp, git_hash=git_hash)
to_save.append(entry)
for measure_type in entry[cat]:
value = entry[cat][measure_type]
if type(value) != int:
raise InvalidDataException(
f"{cat}:{measure_type} must be an integer"
)
to_save.append(Measure(entry=entry, type=measure_type, value=value))
for s in to_save:
s.save()
return len(to_save)
def get(self, request: Request, project_slug: str, version_slug: str) -> Response:
"""
Return the most recent entry for overall progress for a version of a project.
"""
entry = get_latest_entry(project_slug, version_slug, "default")
return Response(entry)
def post(self, request: Request, project_slug: str, version_slug: str) -> Response:
result = VersionDataView.create_entries(
request.data, project_slug, version_slug
)
success_data = {
"result": "success",
"wrote": result,
}
return Response(success_data, status=status.HTTP_201_CREATED)
class CategoryDataView(APIView):
"""
API endpoint that returns data for a specific cagory and a version of a project.
"""
def get(
self, request: Request, project_slug: str, version_slug: str, category_slug: str
) -> Response:
"""
Return data for a specific cagory and a version of a project.
"""
entry = get_latest_entry(project_slug, version_slug, category_slug)
return Response(entry)

View File

@@ -0,0 +1,70 @@
from typing import Any
from django.db import models
from frog_api.exceptions import AlreadyExistsException, MissingAPIKeyException
from frog_api.models import Category, Project
from frog_api.serializers import ProjectSerializer
from frog_api.views.common import get_project, get_version, validate_api_key
from rest_framework import status
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
class ProjectStructureView(APIView):
"""
API endpoint that allows projects to be viewed.
"""
def get(self, request: Request) -> Response:
"""
Return a list of all projects.
"""
projects = Project.objects.all()
serializer = ProjectSerializer(projects, many=True)
return Response(serializer.data)
class CategoryStructureView(APIView):
"""
API endpoint for adding new categories
"""
@staticmethod
def create_categories(
req_data: dict[str, Any], project_slug: str, version_slug: str
) -> int:
project = get_project(project_slug)
version = get_version(version_slug, project)
if "api_key" not in req_data:
raise MissingAPIKeyException()
validate_api_key(req_data["api_key"], project)
categories = req_data["data"]
to_save: list[models.Model] = []
for cat in categories:
if Category.objects.filter(slug=cat, version=version).exists():
raise AlreadyExistsException(
f"Category {cat} already exists for project '{project_slug}', version '{version_slug}'"
)
to_save.append(Category(version=version, slug=cat, name=categories[cat]))
for s in to_save:
s.save()
return len(to_save)
def post(self, request: Request, project_slug: str, version_slug: str) -> Response:
result = CategoryStructureView.create_categories(
request.data, project_slug, version_slug
)
success_data = {
"result": "success",
"wrote": result,
}
return Response(success_data, status=status.HTTP_201_CREATED)