Upgrade deps, cli beginnings, api reorg (#18)
This commit is contained in:
parent
ff0116dbb8
commit
693d4325d9
|
@ -5,3 +5,4 @@ __pycache__
|
|||
.env.*
|
||||
db.sqlite3
|
||||
caddy.env
|
||||
.vscode/launch.json
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[frogress]
|
||||
domain = http://127.0.0.1:8000
|
||||
api_key = doggy
|
|
@ -0,0 +1,106 @@
|
|||
#! /usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import requests
|
||||
|
||||
|
||||
def get_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
subparsers = parser.add_subparsers(help="the action to perform", required=True)
|
||||
|
||||
# Create
|
||||
create_parser = subparsers.add_parser("create", help="create a new db object")
|
||||
create_subparsers = create_parser.add_subparsers(
|
||||
help="the db layer on which to operate", required=True
|
||||
)
|
||||
create_version_parser = create_subparsers.add_parser(
|
||||
"version",
|
||||
help="create a new version",
|
||||
)
|
||||
create_version_parser.add_argument(
|
||||
"project", help="the project for which to create the version"
|
||||
)
|
||||
create_version_parser.add_argument("slug", help="the slug for the version")
|
||||
create_version_parser.add_argument("--name", help="the name for the version")
|
||||
create_version_parser.set_defaults(func=create_version)
|
||||
|
||||
# Delete
|
||||
delete_parser = subparsers.add_parser("delete", help="delete a db object")
|
||||
delete_subparsers = delete_parser.add_subparsers(
|
||||
help="the db layer on which to operate", required=True
|
||||
)
|
||||
delete_version_parser = delete_subparsers.add_parser(
|
||||
"version",
|
||||
help="delete a version",
|
||||
)
|
||||
delete_version_parser.add_argument(
|
||||
"project", help="the project for which to delete the version"
|
||||
)
|
||||
delete_version_parser.add_argument("slug", help="the slug for the version")
|
||||
delete_version_parser.set_defaults(func=delete_version)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def parse_config() -> configparser.SectionProxy:
|
||||
config = configparser.ConfigParser()
|
||||
config.read("cli.ini")
|
||||
|
||||
if "frogress" not in config.sections():
|
||||
raise Exception("Missing [frogress] section in cli.ini")
|
||||
|
||||
if "domain" not in config["frogress"]:
|
||||
raise Exception("Missing domain in cli.ini")
|
||||
|
||||
if "api_key" not in config["frogress"]:
|
||||
raise Exception("Missing api_key in cli.ini")
|
||||
|
||||
if "debug" not in config["frogress"]:
|
||||
config["frogress"]["debug"] = "false"
|
||||
|
||||
return config["frogress"]
|
||||
|
||||
|
||||
def debug(msg: str) -> None:
|
||||
if dbg:
|
||||
print(msg)
|
||||
|
||||
|
||||
def create_version(args: argparse.Namespace) -> None:
|
||||
url = f"{domain}/projects/{args.project}/{args.slug}/"
|
||||
|
||||
name = args.name or args.slug
|
||||
|
||||
data = {
|
||||
"api_key": api_key,
|
||||
"name": name,
|
||||
}
|
||||
|
||||
debug("POST " + url)
|
||||
|
||||
response = requests.post(url, json=data)
|
||||
print(response.text)
|
||||
|
||||
|
||||
def delete_version(args: argparse.Namespace) -> None:
|
||||
url = f"{domain}/projects/{args.project}/{args.slug}/"
|
||||
|
||||
data = {"api_key": api_key}
|
||||
|
||||
debug("DELETE " + url)
|
||||
|
||||
response = requests.delete(url, json=data)
|
||||
print(response.text)
|
||||
|
||||
|
||||
config = parse_config()
|
||||
|
||||
dbg = config["debug"]
|
||||
domain = config["domain"]
|
||||
api_key = config["api_key"]
|
||||
|
||||
|
||||
args = get_parser().parse_args()
|
||||
args.func(args)
|
|
@ -1,6 +1,6 @@
|
|||
from rest_framework import serializers
|
||||
from frog_api.models import AUTH_KEY_LEN
|
||||
from frog_api.serializers.model_serializers import ProjectSerializer, VersionSerializer
|
||||
from frog_api.serializers.model_serializers import ProjectSerializer
|
||||
|
||||
|
||||
class ApiKeySerializer(serializers.CharField):
|
||||
|
@ -15,7 +15,12 @@ class CreateProjectSerializer(serializers.Serializer): # type:ignore
|
|||
|
||||
class CreateVersionSerializer(serializers.Serializer): # type:ignore
|
||||
api_key = ApiKeySerializer()
|
||||
version = VersionSerializer()
|
||||
name = serializers.CharField()
|
||||
|
||||
|
||||
class CreateCategorySerializer(serializers.Serializer): # type:ignore
|
||||
api_key = ApiKeySerializer()
|
||||
name = serializers.CharField()
|
||||
|
||||
|
||||
# Classes for valdating requests to create new entries
|
||||
|
@ -32,10 +37,3 @@ class CreateEntriesSerializer(serializers.Serializer): # type:ignore
|
|||
entries = serializers.ListField(
|
||||
child=CreateEntrySerializer(), required=True, allow_empty=False
|
||||
)
|
||||
|
||||
|
||||
class CreateCategoriesSerializer(serializers.Serializer): # type:ignore
|
||||
api_key = ApiKeySerializer()
|
||||
categories = serializers.DictField(
|
||||
required=True, allow_empty=False, child=serializers.CharField()
|
||||
)
|
||||
|
|
|
@ -5,20 +5,12 @@ from rest_framework.test import APITestCase
|
|||
from frog_api.models import Category, Entry, Measure, Project, Version
|
||||
|
||||
|
||||
class CreateCategoriesTests(APITestCase):
|
||||
class CreateCategoryTests(APITestCase):
|
||||
def test_create_categories(self) -> None:
|
||||
"""
|
||||
Ensure that the category creation endpoint works
|
||||
"""
|
||||
|
||||
create_json = {
|
||||
"api_key": "test_key_123",
|
||||
"categories": {
|
||||
"total": "Total",
|
||||
"actors": "Actors",
|
||||
},
|
||||
}
|
||||
|
||||
# Create a test Project and Version
|
||||
project = Project(slug="oot", name="Ocarina of Time", auth_key="test_key_123")
|
||||
project.save()
|
||||
|
@ -27,14 +19,26 @@ class CreateCategoriesTests(APITestCase):
|
|||
version.save()
|
||||
|
||||
response = self.client.post(
|
||||
reverse("version-structure", args=[project.slug, version.slug]),
|
||||
create_json,
|
||||
reverse("category-structure", args=[project.slug, version.slug, "total"]),
|
||||
{
|
||||
"api_key": "test_key_123",
|
||||
"name": "Total",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("category-structure", args=[project.slug, version.slug, "actors"]),
|
||||
{
|
||||
"api_key": "test_key_123",
|
||||
"name": "Actors",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
|
||||
# Confirm we created the categories and that they are in the DB
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(Category.objects.count(), len(create_json["categories"]))
|
||||
self.assertEqual(Category.objects.count(), 2)
|
||||
|
||||
|
||||
class CreateEntriesTests(APITestCase):
|
||||
|
|
|
@ -3,6 +3,11 @@ from frog_api.views import data, structure
|
|||
|
||||
urlpatterns = [
|
||||
# structure (/project)
|
||||
re_path(
|
||||
"projects/(?P<project_slug>.+)/(?P<version_slug>.+)/(?P<category_slug>.+)/$",
|
||||
structure.CategoryStructureView.as_view(),
|
||||
name="category-structure",
|
||||
),
|
||||
re_path(
|
||||
"projects/(?P<project_slug>.+)/(?P<version_slug>.+)/$",
|
||||
structure.VersionStructureView.as_view(),
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
from typing import Any
|
||||
|
||||
from django.db import models
|
||||
from frog_api.exceptions import AlreadyExistsException
|
||||
from frog_api.models import Category, Project, Version
|
||||
from frog_api.serializers.model_serializers import ProjectSerializer
|
||||
from frog_api.serializers.request_serializers import (
|
||||
CreateCategoriesSerializer,
|
||||
CreateCategorySerializer,
|
||||
CreateProjectSerializer,
|
||||
CreateVersionSerializer,
|
||||
)
|
||||
|
@ -24,19 +23,21 @@ from frog_api.views.data import DEFAULT_CATEGORY_NAME, DEFAULT_CATEGORY_SLUG
|
|||
|
||||
|
||||
class RootStructureView(APIView):
|
||||
"""
|
||||
API endpoint that allows the structure of the database to be viewed or edited.
|
||||
"""
|
||||
|
||||
def get(self, request: Request) -> Response:
|
||||
def get(self, request: Request, format: Any = None) -> Response:
|
||||
"""
|
||||
Return a digest of the database structure.
|
||||
Get a list of all projects
|
||||
"""
|
||||
projects = Project.objects.all()
|
||||
serializer = ProjectSerializer(projects, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
def post(self, request: Request) -> Response:
|
||||
|
||||
class ProjectStructureView(APIView):
|
||||
"""
|
||||
API endpoint for modifying projects
|
||||
"""
|
||||
|
||||
def post(self, request: Request, project_slug: str) -> Response:
|
||||
"""
|
||||
Create a new project.
|
||||
"""
|
||||
|
@ -50,54 +51,68 @@ class RootStructureView(APIView):
|
|||
return Response(request_ser.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class ProjectStructureView(APIView):
|
||||
class VersionStructureView(APIView):
|
||||
"""
|
||||
API endpoint for adding a new version
|
||||
API endpoint for modifying versions
|
||||
"""
|
||||
|
||||
def post(self, request: Request, project_slug: str) -> Response:
|
||||
def post(self, request: Request, project_slug: str, version_slug: str) -> Response:
|
||||
request_ser = CreateVersionSerializer(data=request.data)
|
||||
|
||||
project = get_project(project_slug)
|
||||
|
||||
if not request_ser.is_valid():
|
||||
return Response(request_ser.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
validate_api_key(request_ser.data["api_key"], project)
|
||||
|
||||
if request_ser.is_valid():
|
||||
if Version.objects.filter(
|
||||
slug=request_ser.data["version"]["slug"], project=project
|
||||
).exists():
|
||||
raise AlreadyExistsException(
|
||||
f"Version with slug {request_ser.data['version']['slug']} already exists"
|
||||
)
|
||||
|
||||
version = Version(
|
||||
project=project,
|
||||
slug=request_ser.data["version"]["slug"],
|
||||
name=request_ser.data["version"]["name"],
|
||||
if Version.objects.filter(slug=version_slug, project=project).exists():
|
||||
raise AlreadyExistsException(
|
||||
f"Version {version_slug} already exists in project {project_slug}"
|
||||
)
|
||||
version.save()
|
||||
|
||||
# Create the default category
|
||||
default_cat = Category(
|
||||
version=version,
|
||||
slug=DEFAULT_CATEGORY_SLUG,
|
||||
name=DEFAULT_CATEGORY_NAME,
|
||||
)
|
||||
default_cat.save()
|
||||
return Response(request_ser.version.data, status=status.HTTP_201_CREATED)
|
||||
return Response(request_ser.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
version = Version(
|
||||
project=project,
|
||||
slug=version_slug,
|
||||
name=request_ser.data["name"],
|
||||
)
|
||||
version.save()
|
||||
|
||||
# Create the default category
|
||||
default_cat = Category(
|
||||
version=version,
|
||||
slug=DEFAULT_CATEGORY_SLUG,
|
||||
name=DEFAULT_CATEGORY_NAME,
|
||||
)
|
||||
default_cat.save()
|
||||
return Response(status=status.HTTP_201_CREATED)
|
||||
|
||||
def delete(
|
||||
self, request: Request, project_slug: str, version_slug: str
|
||||
) -> Response:
|
||||
project = get_project(project_slug)
|
||||
|
||||
validate_api_key(request.data["api_key"], project)
|
||||
|
||||
version = get_version(version_slug, project)
|
||||
|
||||
version.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class VersionStructureView(APIView):
|
||||
class CategoryStructureView(APIView):
|
||||
"""
|
||||
API endpoint for adding new categories
|
||||
API endpoint for modifying categories
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def create_categories(
|
||||
req_data: dict[str, Any], project_slug: str, version_slug: str
|
||||
def create_category(
|
||||
req_data: dict[str, Any],
|
||||
project_slug: str,
|
||||
version_slug: str,
|
||||
category_slug: str,
|
||||
) -> int:
|
||||
request_ser = CreateCategoriesSerializer(data=req_data)
|
||||
request_ser = CreateCategorySerializer(data=req_data)
|
||||
request_ser.is_valid(raise_exception=True)
|
||||
data = request_ser.data
|
||||
|
||||
|
@ -107,29 +122,35 @@ class VersionStructureView(APIView):
|
|||
|
||||
version = get_version(version_slug, project)
|
||||
|
||||
categories: dict[str, str] = data["categories"]
|
||||
if Category.objects.filter(slug=category_slug, version=version).exists():
|
||||
raise AlreadyExistsException(
|
||||
f"Category '{category_slug}' already exists for project '{project_slug}', version '{version_slug}'"
|
||||
)
|
||||
|
||||
to_save: list[models.Model] = []
|
||||
for cat, name in categories.items():
|
||||
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=name))
|
||||
category = Category(version=version, slug=category_slug, name=data["name"])
|
||||
category.save()
|
||||
|
||||
for s in to_save:
|
||||
s.save()
|
||||
return 1
|
||||
|
||||
return len(to_save)
|
||||
|
||||
def post(self, request: Request, project_slug: str, version_slug: str) -> Response:
|
||||
result = VersionStructureView.create_categories(
|
||||
request.data, project_slug, version_slug
|
||||
def post(
|
||||
self, request: Request, project_slug: str, version_slug: str, category_slug: str
|
||||
) -> Response:
|
||||
result = CategoryStructureView.create_category(
|
||||
request.data, project_slug, version_slug, category_slug
|
||||
)
|
||||
|
||||
success_data = {
|
||||
"result": "success",
|
||||
"wrote": result,
|
||||
}
|
||||
return Response(status=status.HTTP_201_CREATED)
|
||||
|
||||
return Response(success_data, status=status.HTTP_201_CREATED)
|
||||
def delete(
|
||||
self, request: Request, project_slug: str, version_slug: str, category_slug: str
|
||||
) -> Response:
|
||||
project = get_project(project_slug)
|
||||
|
||||
validate_api_key(request.data["api_key"], project)
|
||||
|
||||
version = get_version(version_slug, project)
|
||||
|
||||
category = Category.objects.get(slug=category_slug, version=version)
|
||||
|
||||
category.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
@ -22,7 +22,7 @@ tzdata = ["tzdata"]
|
|||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "22.6.0"
|
||||
version = "22.8.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -44,7 +44,7 @@ uvloop = ["uvloop (>=0.15.2)"]
|
|||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.6.15"
|
||||
version = "2022.9.14"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -107,7 +107,7 @@ jinja2 = "*"
|
|||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "4.1"
|
||||
version = "4.1.1"
|
||||
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -182,14 +182,14 @@ typing-extensions = "*"
|
|||
|
||||
[[package]]
|
||||
name = "djangorestframework"
|
||||
version = "3.13.1"
|
||||
version = "3.14.0"
|
||||
description = "Web APIs for Django, made easy."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
django = ">=2.2"
|
||||
django = ">=3.0"
|
||||
pytz = "*"
|
||||
|
||||
[[package]]
|
||||
|
@ -215,7 +215,7 @@ compatible-mypy = ["mypy (>=0.950,<0.970)"]
|
|||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.3"
|
||||
version = "3.4"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -279,11 +279,11 @@ python-versions = "*"
|
|||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.9.0"
|
||||
version = "0.10.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
|
@ -360,7 +360,7 @@ python-versions = ">=3.7"
|
|||
|
||||
[[package]]
|
||||
name = "types-markdown"
|
||||
version = "3.4.1"
|
||||
version = "3.4.2"
|
||||
description = "Typing stubs for Markdown"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -384,7 +384,7 @@ python-versions = "*"
|
|||
|
||||
[[package]]
|
||||
name = "types-requests"
|
||||
version = "2.28.9"
|
||||
version = "2.28.11"
|
||||
description = "Typing stubs for requests"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -395,7 +395,7 @@ types-urllib3 = "<1.27"
|
|||
|
||||
[[package]]
|
||||
name = "types-urllib3"
|
||||
version = "1.26.23"
|
||||
version = "1.26.24"
|
||||
description = "Typing stubs for urllib3"
|
||||
category = "dev"
|
||||
optional = false
|
||||
|
@ -449,35 +449,8 @@ asgiref = [
|
|||
{file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
|
||||
]
|
||||
"backports.zoneinfo" = []
|
||||
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_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"},
|
||||
{file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"},
|
||||
{file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"},
|
||||
{file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"},
|
||||
{file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"},
|
||||
{file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"},
|
||||
{file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"},
|
||||
{file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"},
|
||||
{file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"},
|
||||
{file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"},
|
||||
{file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"},
|
||||
{file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"},
|
||||
{file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"},
|
||||
{file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"},
|
||||
{file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"},
|
||||
{file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"},
|
||||
{file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"},
|
||||
{file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"},
|
||||
{file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"},
|
||||
{file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"},
|
||||
{file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"},
|
||||
{file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
|
||||
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
|
||||
]
|
||||
black = []
|
||||
certifi = []
|
||||
charset-normalizer = []
|
||||
click = [
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
|
@ -500,18 +473,12 @@ django-environ = []
|
|||
django-nested-admin = []
|
||||
django-stubs = []
|
||||
django-stubs-ext = []
|
||||
djangorestframework = [
|
||||
{file = "djangorestframework-3.13.1-py3-none-any.whl", hash = "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"},
|
||||
{file = "djangorestframework-3.13.1.tar.gz", hash = "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee"},
|
||||
]
|
||||
djangorestframework = []
|
||||
djangorestframework-stubs = [
|
||||
{file = "djangorestframework-stubs-1.7.0.tar.gz", hash = "sha256:6e8a80a0716d8af02aa387dae47f8ef97b6c0efdf159d83a5918d582f8b1ea07"},
|
||||
{file = "djangorestframework_stubs-1.7.0-py3-none-any.whl", hash = "sha256:3ac1447fc87f68fe7d8622d4725b5cef820bcc17918f36c7da3f667363fb7a43"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
||||
]
|
||||
idna = []
|
||||
itypes = [
|
||||
{file = "itypes-1.2.0-py2.py3-none-any.whl", hash = "sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6"},
|
||||
{file = "itypes-1.2.0.tar.gz", hash = "sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1"},
|
||||
|
@ -567,10 +534,7 @@ mypy-extensions = [
|
|||
{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"},
|
||||
]
|
||||
pathspec = [
|
||||
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
pathspec = []
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
||||
|
|
Loading…
Reference in New Issue