Cleanup/reorg (#10)
* Cleanup/reorg WIP - serializers next * PR comments, fixes * fix URLs
This commit is contained in:
parent
5790357ac4
commit
686e15385e
|
@ -0,0 +1,54 @@
|
||||||
|
from rest_framework.exceptions import APIException
|
||||||
|
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
|
||||||
|
class NonexistentProjectException(APIException):
|
||||||
|
status_code = status.HTTP_404_NOT_FOUND
|
||||||
|
|
||||||
|
def __init__(self, project: str):
|
||||||
|
super().__init__(f"Project {project} not found")
|
||||||
|
|
||||||
|
|
||||||
|
class NonexistentVersionException(APIException):
|
||||||
|
status_code = status.HTTP_404_NOT_FOUND
|
||||||
|
|
||||||
|
def __init__(self, project: str, version: str):
|
||||||
|
super().__init__(f"Version '{version}' for project '{project}' not found")
|
||||||
|
|
||||||
|
|
||||||
|
class NonexistentCategoryException(APIException):
|
||||||
|
status_code = status.HTTP_404_NOT_FOUND
|
||||||
|
|
||||||
|
def __init__(self, project: str, version: str, category: str):
|
||||||
|
super().__init__(
|
||||||
|
f"Category '{category}' not found for project '{project}', version '{version}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NoEntriesException(APIException):
|
||||||
|
status_code = status.HTTP_404_NOT_FOUND
|
||||||
|
|
||||||
|
def __init__(self, project: str, version: str, category: str):
|
||||||
|
super().__init__(
|
||||||
|
f"No data exists for project '{project}', version '{version}', and category '{category}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MissingAPIKeyException(APIException):
|
||||||
|
status_code = status.HTTP_403_FORBIDDEN
|
||||||
|
default_detail = "No API key provided"
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAPIKeyException(APIException):
|
||||||
|
status_code = status.HTTP_403_FORBIDDEN
|
||||||
|
default_detail = "Incorrect API key provided"
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidDataException(APIException):
|
||||||
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
|
# Maybe?
|
||||||
|
class AlreadyExistsException(APIException):
|
||||||
|
status_code = status.HTTP_400_BAD_REQUEST
|
|
@ -1,26 +1,31 @@
|
||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
from frog_api import views
|
from frog_api.views import data, structure
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("projects/", views.ProjectView.as_view()),
|
# structure (/project)
|
||||||
re_path(
|
path(
|
||||||
"projects/(?P<project>.+)/(?P<version>.+)/$",
|
"projects/",
|
||||||
views.AddNewCategoryView.as_view(),
|
structure.ProjectStructureView.as_view(),
|
||||||
),
|
),
|
||||||
re_path(
|
re_path(
|
||||||
"data/(?P<project>.+)/(?P<version>.+)/(?P<category>.+)/$",
|
"projects/(?P<project_slug>.+)/(?P<version_slug>.+)/$",
|
||||||
views.CategoryDigestView.as_view(),
|
structure.CategoryStructureView.as_view(),
|
||||||
|
),
|
||||||
|
# data (/data)
|
||||||
|
re_path(
|
||||||
|
"data/(?P<project_slug>.+)/(?P<version_slug>.+)/(?P<category_slug>.+)/$",
|
||||||
|
data.CategoryDataView.as_view(),
|
||||||
),
|
),
|
||||||
re_path(
|
re_path(
|
||||||
"data/(?P<project>.+)/(?P<version>.+)/$",
|
"data/(?P<project_slug>.+)/(?P<version_slug>.+)/$",
|
||||||
views.VersionDigestView.as_view(),
|
data.VersionDataView.as_view(),
|
||||||
),
|
),
|
||||||
re_path(
|
re_path(
|
||||||
"data/(?P<project>.+)/$",
|
"data/(?P<project_slug>.+)/$",
|
||||||
views.ProjectDigestView.as_view(),
|
data.ProjectDataView.as_view(),
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"data/",
|
"data/",
|
||||||
views.RootDigestView.as_view(),
|
data.RootDataView.as_view(),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,271 +0,0 @@
|
||||||
from typing import Any, List
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework.exceptions import APIException
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.request import Request
|
|
||||||
from rest_framework.views import APIView
|
|
||||||
from django.db import models
|
|
||||||
import json
|
|
||||||
|
|
||||||
from frog_api.models import Category, Entry, Measure, Project, Version
|
|
||||||
from frog_api.serializers import (
|
|
||||||
ProjectSerializer,
|
|
||||||
TerseEntrySerializer,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MissingModelException(APIException):
|
|
||||||
status_code = status.HTTP_404_NOT_FOUND
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidAPIKeyException(APIException):
|
|
||||||
status_code = status.HTTP_403_FORBIDDEN
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidDataException(APIException):
|
|
||||||
status_code = status.HTTP_400_BAD_REQUEST
|
|
||||||
|
|
||||||
|
|
||||||
# Maybe?
|
|
||||||
class AlreadyExistsException(APIException):
|
|
||||||
status_code = status.HTTP_400_BAD_REQUEST
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectView(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)
|
|
||||||
|
|
||||||
|
|
||||||
def get_latest_entry(project: str, version: str, category: str) -> dict[Any, Any]:
|
|
||||||
if not Project.objects.filter(slug=project).exists():
|
|
||||||
raise MissingModelException(f"Project {project} not found")
|
|
||||||
|
|
||||||
if not Version.objects.filter(slug=version, project__slug=project).exists():
|
|
||||||
raise MissingModelException(
|
|
||||||
f"Version '{version}' not found for project '{project}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not Category.objects.filter(
|
|
||||||
slug=category, version__slug=version, version__project__slug=project
|
|
||||||
).exists():
|
|
||||||
raise MissingModelException(
|
|
||||||
f"Category '{category}' not found for project '{project}' and version '{version}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
entry = Entry.objects.filter(
|
|
||||||
category__slug=category,
|
|
||||||
category__version__slug=version,
|
|
||||||
category__version__project__slug=project,
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if entry is None:
|
|
||||||
raise MissingModelException(
|
|
||||||
f"No data exists for project '{project}', version '{version}', and category '{category}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Re-format the measures (TODO: DRF-ify this?)
|
|
||||||
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: str) -> dict[Any, Any]:
|
|
||||||
versions = {}
|
|
||||||
for version in Version.objects.filter(project__slug=project):
|
|
||||||
entry = get_latest_entry(project, version.slug, "default")
|
|
||||||
if entry is not None:
|
|
||||||
versions[version.slug] = entry
|
|
||||||
return versions
|
|
||||||
|
|
||||||
|
|
||||||
class RootDigestView(APIView):
|
|
||||||
"""
|
|
||||||
API endpoint that returns the most recent entry for each version of each project.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get(self, request: Request) -> Response:
|
|
||||||
"""
|
|
||||||
Return the most recent entry for ovreall progress for each version of each project.
|
|
||||||
"""
|
|
||||||
|
|
||||||
projects = {}
|
|
||||||
for project in Project.objects.all():
|
|
||||||
versions = get_versions_digest_for_project(project.slug)
|
|
||||||
if len(versions) > 0:
|
|
||||||
projects[project.slug] = versions
|
|
||||||
|
|
||||||
return Response({"progress": projects})
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectDigestView(APIView):
|
|
||||||
"""
|
|
||||||
API endpoint that returns the most recent entry for each version of a project.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get(self, request: Request, project: str) -> Response:
|
|
||||||
"""
|
|
||||||
Return the most recent entry for overall progress for each version of a project.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not Project.objects.filter(slug=project).exists():
|
|
||||||
raise MissingModelException(f"Project {project} not found")
|
|
||||||
|
|
||||||
projects = {}
|
|
||||||
|
|
||||||
versions = get_versions_digest_for_project(project)
|
|
||||||
if len(versions) > 0:
|
|
||||||
projects[project] = versions
|
|
||||||
|
|
||||||
return Response({"progress": projects})
|
|
||||||
|
|
||||||
|
|
||||||
def write_new_entries(request: Request, project: str, version: str) -> List[Any]:
|
|
||||||
found_project = Project.objects.filter(slug=project).first()
|
|
||||||
if not found_project:
|
|
||||||
raise MissingModelException(f"Project {project} not found")
|
|
||||||
|
|
||||||
found_version = Version.objects.filter(slug=version, project__slug=project).first()
|
|
||||||
if not found_version:
|
|
||||||
raise MissingModelException(
|
|
||||||
f"Version '{version}' not found for project '{project}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
print(request.data)
|
|
||||||
input = request.data
|
|
||||||
|
|
||||||
if "api_key" not in input:
|
|
||||||
raise InvalidAPIKeyException(f"No api_key provided, cannot POST.")
|
|
||||||
if input["api_key"] != found_project.auth_key:
|
|
||||||
raise InvalidAPIKeyException(
|
|
||||||
f"Provided api_key does not match authorization, cannot POST."
|
|
||||||
)
|
|
||||||
|
|
||||||
to_save: List[models.Model] = []
|
|
||||||
for row in input["data"]:
|
|
||||||
timestamp = row["timestamp"]
|
|
||||||
git_hash = row["git_hash"]
|
|
||||||
for cat in row:
|
|
||||||
if cat in ["timestamp", "git_hash"]:
|
|
||||||
continue
|
|
||||||
if type(row[cat]) is not dict:
|
|
||||||
continue
|
|
||||||
|
|
||||||
category = Category.objects.filter(
|
|
||||||
slug=cat, version__slug=version, version__project__slug=project
|
|
||||||
).first()
|
|
||||||
if not category:
|
|
||||||
raise MissingModelException(
|
|
||||||
f"Attempted to write to Category '{cat}' not found in project '{project}', version '{version}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
entry = Entry(category=category, timestamp=timestamp, git_hash=git_hash)
|
|
||||||
print(entry)
|
|
||||||
to_save.append(entry)
|
|
||||||
|
|
||||||
for measure_type in row[cat]:
|
|
||||||
value = row[cat][measure_type]
|
|
||||||
if type(value) != int:
|
|
||||||
raise InvalidDataException(f"{cat}:{measure_type} must be an int")
|
|
||||||
to_save.append(Measure(entry=entry, type=measure_type, value=value))
|
|
||||||
|
|
||||||
for s in to_save:
|
|
||||||
s.save()
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class VersionDigestView(APIView):
|
|
||||||
"""
|
|
||||||
API endpoint that returns the most recent entry for overall progress for a version of a project.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get(self, request: Request, project: str, version: str) -> Response:
|
|
||||||
"""
|
|
||||||
Return the most recent entry for overall progress for a version of a project.
|
|
||||||
"""
|
|
||||||
|
|
||||||
entry = get_latest_entry(project, version, "default")
|
|
||||||
|
|
||||||
return Response(entry)
|
|
||||||
|
|
||||||
def post(self, request: Request, project: str, version: str) -> Response:
|
|
||||||
|
|
||||||
result = write_new_entries(request, project, version)
|
|
||||||
|
|
||||||
return Response(result, status=status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
|
|
||||||
class CategoryDigestView(APIView):
|
|
||||||
"""
|
|
||||||
API endpoint that returns the most recent entry for a specific cagory and a version of a project.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get(
|
|
||||||
self, request: Request, project: str, version: str, category: str
|
|
||||||
) -> Response:
|
|
||||||
"""
|
|
||||||
Return the most recent entry for a specific cagory and a version of a project.
|
|
||||||
"""
|
|
||||||
|
|
||||||
entry = get_latest_entry(project, version, category)
|
|
||||||
|
|
||||||
return Response(entry)
|
|
||||||
|
|
||||||
|
|
||||||
def add_new_category(request: Request, project: str, version: str) -> List[Any]:
|
|
||||||
found_project = Project.objects.filter(slug=project).first()
|
|
||||||
if not found_project:
|
|
||||||
raise MissingModelException(f"Project {project} not found")
|
|
||||||
|
|
||||||
found_version = Version.objects.filter(slug=version, project__slug=project).first()
|
|
||||||
if not found_version:
|
|
||||||
raise MissingModelException(
|
|
||||||
f"Version '{version}' not found for project '{project}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
print(request.data)
|
|
||||||
input = request.data
|
|
||||||
categories = input["data"]
|
|
||||||
|
|
||||||
if "api_key" not in input:
|
|
||||||
raise InvalidAPIKeyException(f"No api_key provided, cannot POST.")
|
|
||||||
if input["api_key"] != found_project.auth_key:
|
|
||||||
raise InvalidAPIKeyException(
|
|
||||||
f"Provided api_key does not match authorization, cannot POST."
|
|
||||||
)
|
|
||||||
|
|
||||||
to_save = []
|
|
||||||
for cat in categories:
|
|
||||||
if Category.objects.filter(
|
|
||||||
slug=cat, version__slug=version, version__project__slug=project
|
|
||||||
).exists():
|
|
||||||
raise AlreadyExistsException(
|
|
||||||
f"Category {cat} already exists for project '{project}', version '{version}'"
|
|
||||||
)
|
|
||||||
to_save.append(Category(version=found_version, slug=cat, name=categories[cat]))
|
|
||||||
|
|
||||||
for s in to_save:
|
|
||||||
s.save()
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class AddNewCategoryView(APIView):
|
|
||||||
"""
|
|
||||||
API endpoint for adding new categories
|
|
||||||
"""
|
|
||||||
|
|
||||||
def post(self, request: Request, project: str, version: str) -> Response:
|
|
||||||
|
|
||||||
result = add_new_category(request, project, version)
|
|
||||||
|
|
||||||
return Response(result, status=status.HTTP_201_CREATED)
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -7,7 +7,7 @@ optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["mypy (>=0.800)", "pytest-asyncio", "pytest"]
|
tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backports.zoneinfo"
|
name = "backports.zoneinfo"
|
||||||
|
@ -37,10 +37,10 @@ tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
|
||||||
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
uvloop = ["uvloop (>=0.15.2)"]
|
|
||||||
jupyter = ["tokenize-rt (>=3.2.0)", "ipython (>=7.8.0)"]
|
|
||||||
d = ["aiohttp (>=3.7.4)"]
|
|
||||||
colorama = ["colorama (>=0.4.3)"]
|
colorama = ["colorama (>=0.4.3)"]
|
||||||
|
d = ["aiohttp (>=3.7.4)"]
|
||||||
|
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||||
|
uvloop = ["uvloop (>=0.15.2)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
|
@ -120,8 +120,8 @@ sqlparse = ">=0.2.2"
|
||||||
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
bcrypt = ["bcrypt"]
|
|
||||||
argon2 = ["argon2-cffi (>=19.1.0)"]
|
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||||
|
bcrypt = ["bcrypt"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-nested-admin"
|
name = "django-nested-admin"
|
||||||
|
@ -252,9 +252,9 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||||
typing-extensions = ">=3.10"
|
typing-extensions = ">=3.10"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
reports = ["lxml"]
|
|
||||||
python2 = ["typed-ast (>=1.4.0,<2)"]
|
|
||||||
dmypy = ["psutil (>=4.0)"]
|
dmypy = ["psutil (>=4.0)"]
|
||||||
|
python2 = ["typed-ast (>=1.4.0,<2)"]
|
||||||
|
reports = ["lxml"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy-extensions"
|
name = "mypy-extensions"
|
||||||
|
@ -281,8 +281,8 @@ optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
test = ["pytest (>=6)", "pytest-mock (>=3.6)", "pytest-cov (>=2.7)", "appdirs (==1.4.4)"]
|
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
|
||||||
docs = ["sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)", "proselint (>=0.10.2)", "furo (>=2021.7.5b38)"]
|
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-monkey-business"
|
name = "python-monkey-business"
|
||||||
|
@ -318,8 +318,8 @@ idna = ">=2.5,<4"
|
||||||
urllib3 = ">=1.21.1,<1.27"
|
urllib3 = ">=1.21.1,<1.27"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
|
|
||||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||||
|
use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "six"
|
name = "six"
|
||||||
|
@ -435,24 +435,7 @@ asgiref = [
|
||||||
{file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"},
|
{file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"},
|
||||||
{file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
|
{file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
|
||||||
]
|
]
|
||||||
"backports.zoneinfo" = [
|
"backports.zoneinfo" = []
|
||||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"},
|
|
||||||
{file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"},
|
|
||||||
]
|
|
||||||
black = [
|
black = [
|
||||||
{file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"},
|
{file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"},
|
||||||
{file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"},
|
{file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"},
|
||||||
|
@ -482,10 +465,7 @@ certifi = [
|
||||||
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
|
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
|
||||||
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
|
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
|
||||||
]
|
]
|
||||||
charset-normalizer = [
|
charset-normalizer = []
|
||||||
{file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
|
|
||||||
{file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
|
|
||||||
]
|
|
||||||
click = [
|
click = [
|
||||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||||
|
@ -502,22 +482,10 @@ coreschema = [
|
||||||
{file = "coreschema-0.0.4-py2-none-any.whl", hash = "sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f"},
|
{file = "coreschema-0.0.4-py2-none-any.whl", hash = "sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f"},
|
||||||
{file = "coreschema-0.0.4.tar.gz", hash = "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"},
|
{file = "coreschema-0.0.4.tar.gz", hash = "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"},
|
||||||
]
|
]
|
||||||
django = [
|
django = []
|
||||||
{file = "Django-4.1-py3-none-any.whl", hash = "sha256:031ccb717782f6af83a0063a1957686e87cb4581ea61b47b3e9addf60687989a"},
|
django-nested-admin = []
|
||||||
{file = "Django-4.1.tar.gz", hash = "sha256:032f8a6fc7cf05ccd1214e4a2e21dfcd6a23b9d575c6573cacc8c67828dbe642"},
|
django-stubs = []
|
||||||
]
|
django-stubs-ext = []
|
||||||
django-nested-admin = [
|
|
||||||
{file = "django-nested-admin-3.4.0.tar.gz", hash = "sha256:fbcf20d75a73dcbcc6285793ff936eff8df4deba5b169e0c1ab765394c562805"},
|
|
||||||
{file = "django_nested_admin-3.4.0-py2.py3-none-any.whl", hash = "sha256:c6852c5ac632f4e698b6beda455006fd464c852459e5e858a6db832cdb23d9e1"},
|
|
||||||
]
|
|
||||||
django-stubs = [
|
|
||||||
{file = "django-stubs-1.12.0.tar.gz", hash = "sha256:ea8b35d0da49f7b2ee99a79125f1943e033431dd114726d6643cc35de619230e"},
|
|
||||||
{file = "django_stubs-1.12.0-py3-none-any.whl", hash = "sha256:0dff8ec0ba3abe046450b3d8a29ce9e72629893d2c1ef679189cc2bfdb6d2f64"},
|
|
||||||
]
|
|
||||||
django-stubs-ext = [
|
|
||||||
{file = "django-stubs-ext-0.5.0.tar.gz", hash = "sha256:9bd7418376ab00b7f88d6d56be9fece85bfa0c7c348ac621155fa4d7a91146f2"},
|
|
||||||
{file = "django_stubs_ext-0.5.0-py3-none-any.whl", hash = "sha256:c5d8db53d29c756e7e3d0820a5a079a43bc38d8fab0e1b8bd5df2f3366c54b5a"},
|
|
||||||
]
|
|
||||||
djangorestframework = [
|
djangorestframework = [
|
||||||
{file = "djangorestframework-3.13.1-py3-none-any.whl", hash = "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"},
|
{file = "djangorestframework-3.13.1-py3-none-any.whl", hash = "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"},
|
||||||
{file = "djangorestframework-3.13.1.tar.gz", hash = "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee"},
|
{file = "djangorestframework-3.13.1.tar.gz", hash = "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee"},
|
||||||
|
@ -580,31 +548,7 @@ markupsafe = [
|
||||||
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
|
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
|
||||||
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
|
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
|
||||||
]
|
]
|
||||||
mypy = [
|
mypy = []
|
||||||
{file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"},
|
|
||||||
{file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"},
|
|
||||||
{file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"},
|
|
||||||
{file = "mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655"},
|
|
||||||
{file = "mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103"},
|
|
||||||
{file = "mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca"},
|
|
||||||
{file = "mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417"},
|
|
||||||
{file = "mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09"},
|
|
||||||
{file = "mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8"},
|
|
||||||
{file = "mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0"},
|
|
||||||
{file = "mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2"},
|
|
||||||
{file = "mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27"},
|
|
||||||
{file = "mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856"},
|
|
||||||
{file = "mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71"},
|
|
||||||
{file = "mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27"},
|
|
||||||
{file = "mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58"},
|
|
||||||
{file = "mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6"},
|
|
||||||
{file = "mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe"},
|
|
||||||
{file = "mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9"},
|
|
||||||
{file = "mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf"},
|
|
||||||
{file = "mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0"},
|
|
||||||
{file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"},
|
|
||||||
{file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"},
|
|
||||||
]
|
|
||||||
mypy-extensions = [
|
mypy-extensions = [
|
||||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||||
|
@ -617,22 +561,13 @@ platformdirs = [
|
||||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
||||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
||||||
]
|
]
|
||||||
python-monkey-business = [
|
python-monkey-business = []
|
||||||
{file = "python-monkey-business-1.0.0.tar.gz", hash = "sha256:9976522989766f00b2aaa24ec96eacb91a6de7b7001d1452079323b071988e0e"},
|
pytz = []
|
||||||
{file = "python_monkey_business-1.0.0-py2.py3-none-any.whl", hash = "sha256:6d4cf47f011945db838ccf04643acd49b82f7ad6ab7ecba4c8165385687a828a"},
|
|
||||||
]
|
|
||||||
pytz = [
|
|
||||||
{file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"},
|
|
||||||
{file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"},
|
|
||||||
]
|
|
||||||
requests = [
|
requests = [
|
||||||
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
||||||
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
||||||
]
|
]
|
||||||
six = [
|
six = []
|
||||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
|
||||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
|
||||||
]
|
|
||||||
sqlparse = [
|
sqlparse = [
|
||||||
{file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"},
|
{file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"},
|
||||||
{file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
|
{file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
|
||||||
|
@ -641,39 +576,18 @@ tomli = [
|
||||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||||
]
|
]
|
||||||
types-markdown = [
|
types-markdown = []
|
||||||
{file = "types-Markdown-3.4.1.tar.gz", hash = "sha256:cda9bfd1fcb11e8133a037f8a184e3059ae7389a5f5cc0b53117bf2902aca10d"},
|
types-pytz = []
|
||||||
{file = "types_Markdown-3.4.1-py3-none-any.whl", hash = "sha256:2d1e5bfff192c78d6644bc3820fea9c7c7cb42dc87558020728ec5b728448ce2"},
|
types-pyyaml = []
|
||||||
]
|
types-requests = []
|
||||||
types-pytz = [
|
types-urllib3 = []
|
||||||
{file = "types-pytz-2022.2.1.0.tar.gz", hash = "sha256:47cfb19c52b9f75896440541db392fd312a35b279c6307a531db71152ea63e2b"},
|
|
||||||
{file = "types_pytz-2022.2.1.0-py3-none-any.whl", hash = "sha256:50ead2254b524a3d4153bc65d00289b66898060d2938e586170dce918dbaf3b3"},
|
|
||||||
]
|
|
||||||
types-pyyaml = [
|
|
||||||
{file = "types-PyYAML-6.0.11.tar.gz", hash = "sha256:7f7da2fd11e9bc1e5e9eb3ea1be84f4849747017a59fc2eee0ea34ed1147c2e0"},
|
|
||||||
{file = "types_PyYAML-6.0.11-py3-none-any.whl", hash = "sha256:8f890028123607379c63550179ddaec4517dc751f4c527a52bb61934bf495989"},
|
|
||||||
]
|
|
||||||
types-requests = [
|
|
||||||
{file = "types-requests-2.28.9.tar.gz", hash = "sha256:feaf581bd580497a47fe845d506fa3b91b484cf706ff27774e87659837de9962"},
|
|
||||||
{file = "types_requests-2.28.9-py3-none-any.whl", hash = "sha256:86cb66d3de2f53eac5c09adc42cf6547eefbd0c7e1210beca1ee751c35d96083"},
|
|
||||||
]
|
|
||||||
types-urllib3 = [
|
|
||||||
{file = "types-urllib3-1.26.23.tar.gz", hash = "sha256:b78e819f0e350221d0689a5666162e467ba3910737bafda14b5c2c85e9bb1e56"},
|
|
||||||
{file = "types_urllib3-1.26.23-py3-none-any.whl", hash = "sha256:333e675b188a1c1fd980b4b352f9e40572413a4c1ac689c23cd546e96310070a"},
|
|
||||||
]
|
|
||||||
typing-extensions = [
|
typing-extensions = [
|
||||||
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
|
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
|
||||||
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
|
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
|
||||||
]
|
]
|
||||||
tzdata = [
|
tzdata = []
|
||||||
{file = "tzdata-2022.2-py2.py3-none-any.whl", hash = "sha256:c3119520447d68ef3eb8187a55a4f44fa455f30eb1b4238fa5691ba094f2b05b"},
|
|
||||||
{file = "tzdata-2022.2.tar.gz", hash = "sha256:21f4f0d7241572efa7f7a4fdabb052e61b55dc48274e6842697ccdf5253e5451"},
|
|
||||||
]
|
|
||||||
uritemplate = [
|
uritemplate = [
|
||||||
{file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
|
{file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
|
||||||
{file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
|
{file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
|
||||||
]
|
]
|
||||||
urllib3 = [
|
urllib3 = []
|
||||||
{file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
|
|
||||||
{file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
|
|
||||||
]
|
|
||||||
|
|
Loading…
Reference in New Issue