start adding new ninja-based OpenAPI-compatible API v2, starting with mesh
This commit is contained in:
parent
0b6362c8ab
commit
f5c33724dc
9 changed files with 101 additions and 4 deletions
|
@ -4,6 +4,7 @@ from collections import OrderedDict
|
||||||
|
|
||||||
from django.urls import include, path, re_path
|
from django.urls import include, path, re_path
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
from ninja import NinjaAPI
|
||||||
from rest_framework.generics import GenericAPIView
|
from rest_framework.generics import GenericAPIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.routers import SimpleRouter
|
from rest_framework.routers import SimpleRouter
|
||||||
|
@ -18,8 +19,16 @@ from c3nav.mapdata.api import (AccessRestrictionGroupViewSet, AccessRestrictionV
|
||||||
SpaceViewSet, StairViewSet, UpdatesViewSet)
|
SpaceViewSet, StairViewSet, UpdatesViewSet)
|
||||||
from c3nav.mapdata.utils.user import can_access_editor
|
from c3nav.mapdata.utils.user import can_access_editor
|
||||||
from c3nav.mesh.api import FirmwareViewSet
|
from c3nav.mesh.api import FirmwareViewSet
|
||||||
|
from c3nav.mesh.newapi import api_router as mesh_api_router
|
||||||
from c3nav.routing.api import RoutingViewSet
|
from c3nav.routing.api import RoutingViewSet
|
||||||
|
|
||||||
|
ninja_api = NinjaAPI(
|
||||||
|
title="c3nav API",
|
||||||
|
version="v2",
|
||||||
|
docs_url="/",
|
||||||
|
)
|
||||||
|
ninja_api.add_router("/mesh/", mesh_api_router)
|
||||||
|
|
||||||
router = SimpleRouter()
|
router = SimpleRouter()
|
||||||
router.register(r'map', MapViewSet, basename='map')
|
router.register(r'map', MapViewSet, basename='map')
|
||||||
router.register(r'levels', LevelViewSet)
|
router.register(r'levels', LevelViewSet)
|
||||||
|
@ -63,6 +72,8 @@ class APIRoot(GenericAPIView):
|
||||||
The HTML preview is only shown because your Browser sent text/html in its Accept header.
|
The HTML preview is only shown because your Browser sent text/html in its Accept header.
|
||||||
If you want to use this API on a large scale, please use a client that supports E-Tags.
|
If you want to use this API on a large scale, please use a client that supports E-Tags.
|
||||||
For more information on a specific API endpoint, access it with a browser.
|
For more information on a specific API endpoint, access it with a browser.
|
||||||
|
|
||||||
|
This is the old API which is slowly being phased out in favor of the new API at /api/v2/.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _format_pattern(self, pattern):
|
def _format_pattern(self, pattern):
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
from enum import EnumMeta
|
||||||
|
from typing import Any, Optional, cast, Iterator, Callable
|
||||||
|
|
||||||
|
from pydantic.fields import ModelField
|
||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,3 +14,26 @@ def get_api_post_data(request):
|
||||||
raise ParseError('Invalid JSON.')
|
raise ParseError('Invalid JSON.')
|
||||||
return data
|
return data
|
||||||
return request.POST
|
return request.POST
|
||||||
|
|
||||||
|
|
||||||
|
class EnumSchemaByNameMixin:
|
||||||
|
@classmethod
|
||||||
|
def __modify_schema__(cls, field_schema: dict[str, Any], field: Optional[ModelField]) -> None:
|
||||||
|
if field is None:
|
||||||
|
return
|
||||||
|
field_schema["enum"] = list(cast(EnumMeta, field.type_).__members__.keys())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _validate(cls, v: Any, field: ModelField) -> Any:
|
||||||
|
if isinstance(v, cls):
|
||||||
|
# it's already the object, so it's going to json, return string
|
||||||
|
return v.name
|
||||||
|
try:
|
||||||
|
# it's a string, so it's coming from json, return object
|
||||||
|
return cls[v]
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError(f"Invalid value for {cls}: `{v}`")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __get_validators__(cls) -> Iterator[Callable[..., Any]]:
|
||||||
|
yield cls._validate
|
||||||
|
|
|
@ -2,6 +2,7 @@ import re
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from enum import IntEnum, unique
|
from enum import IntEnum, unique
|
||||||
|
|
||||||
|
from c3nav.api.utils import EnumSchemaByNameMixin
|
||||||
from c3nav.mesh.baseformats import (BoolFormat, EnumFormat, FixedHexFormat, FixedStrFormat, SimpleFormat, StructType,
|
from c3nav.mesh.baseformats import (BoolFormat, EnumFormat, FixedHexFormat, FixedStrFormat, SimpleFormat, StructType,
|
||||||
VarArrayFormat)
|
VarArrayFormat)
|
||||||
|
|
||||||
|
@ -76,7 +77,7 @@ class UWBConfig(StructType):
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class BoardType(IntEnum):
|
class BoardType(EnumSchemaByNameMixin, IntEnum):
|
||||||
CUSTOM = 0x00
|
CUSTOM = 0x00
|
||||||
|
|
||||||
# devboards
|
# devboards
|
||||||
|
|
|
@ -5,6 +5,7 @@ from typing import TypeVar
|
||||||
import channels
|
import channels
|
||||||
from channels.db import database_sync_to_async
|
from channels.db import database_sync_to_async
|
||||||
|
|
||||||
|
from c3nav.api.utils import EnumSchemaByNameMixin
|
||||||
from c3nav.mesh.baseformats import (BoolFormat, EnumFormat, FixedStrFormat, SimpleFormat, StructType, VarArrayFormat,
|
from c3nav.mesh.baseformats import (BoolFormat, EnumFormat, FixedStrFormat, SimpleFormat, StructType, VarArrayFormat,
|
||||||
VarBytesFormat, VarStrFormat, normalize_name)
|
VarBytesFormat, VarStrFormat, normalize_name)
|
||||||
from c3nav.mesh.dataformats import (BoardConfig, FirmwareAppDescription, MacAddressesListFormat, MacAddressFormat,
|
from c3nav.mesh.dataformats import (BoardConfig, FirmwareAppDescription, MacAddressesListFormat, MacAddressFormat,
|
||||||
|
@ -68,7 +69,7 @@ M = TypeVar('M', bound='MeshMessage')
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class ChipType(IntEnum):
|
class ChipType(EnumSchemaByNameMixin, IntEnum):
|
||||||
ESP32_S2 = 2
|
ESP32_S2 = 2
|
||||||
ESP32_C3 = 5
|
ESP32_C3 = 5
|
||||||
|
|
||||||
|
|
|
@ -385,7 +385,7 @@ class FirmwareBuild(models.Model):
|
||||||
'chip': ChipType(self.chip).name,
|
'chip': ChipType(self.chip).name,
|
||||||
'sha256_hash': self.sha256_hash,
|
'sha256_hash': self.sha256_hash,
|
||||||
'url': self.binary.url,
|
'url': self.binary.url,
|
||||||
'boards': self.boards,
|
'boards': [board.name for board in self.boards],
|
||||||
}
|
}
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
|
52
src/c3nav/mesh/newapi.py
Normal file
52
src/c3nav/mesh/newapi.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from ninja import Router as APIRouter, Field as APIField, Schema
|
||||||
|
from ninja.pagination import paginate
|
||||||
|
|
||||||
|
from c3nav.mesh.dataformats import BoardType
|
||||||
|
from c3nav.mesh.messages import ChipType
|
||||||
|
from c3nav.mesh.models import FirmwareVersion
|
||||||
|
|
||||||
|
api_router = APIRouter(tags=["mesh"])
|
||||||
|
|
||||||
|
|
||||||
|
class FirmwareBuildSchema(Schema):
|
||||||
|
id: int
|
||||||
|
chip: ChipType = APIField(..., example=ChipType.ESP32_C3.name)
|
||||||
|
sha256_hash: str = APIField(..., regex=r"^[0-9a-f]{64}$")
|
||||||
|
url: str = APIField(..., alias="binary", example="/media/firmware/012345/firmware.bin")
|
||||||
|
boards: list[BoardType] = APIField(..., example=[BoardType.C3NAV_LOCATION_PCB_REV_0_2.name, ])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_chip(obj):
|
||||||
|
# todo: do this in model? idk
|
||||||
|
return ChipType(obj.chip)
|
||||||
|
|
||||||
|
|
||||||
|
class FirmwareSchema(Schema):
|
||||||
|
id: int
|
||||||
|
project_name: str = APIField(..., example="c3nav_positioning")
|
||||||
|
version: str = APIField(..., example="499837d-dirty")
|
||||||
|
idf_version: str = APIField(..., example="v5.1-476-g3187b8b326")
|
||||||
|
created: datetime
|
||||||
|
builds: list[FirmwareBuildSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Schema):
|
||||||
|
detail: str
|
||||||
|
|
||||||
|
|
||||||
|
@api_router.get('/firmwares/', response=list[FirmwareSchema],
|
||||||
|
summary="List available firmwares")
|
||||||
|
@paginate
|
||||||
|
def firmware_list(request):
|
||||||
|
return FirmwareVersion.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@api_router.get('/firmwares/{firmware_id}/', response={200: FirmwareSchema, 404: Error},
|
||||||
|
summary="Get specific firmware")
|
||||||
|
def firmware_detail(request, firmware_id: int):
|
||||||
|
try:
|
||||||
|
return FirmwareVersion.objects.get(id=firmware_id)
|
||||||
|
except FirmwareVersion.DoesNotExist:
|
||||||
|
return 404, {"detail": "firmware not found"}
|
|
@ -248,6 +248,7 @@ INSTALLED_APPS = [
|
||||||
'channels',
|
'channels',
|
||||||
'compressor',
|
'compressor',
|
||||||
'bootstrap3',
|
'bootstrap3',
|
||||||
|
'ninja',
|
||||||
'c3nav.api',
|
'c3nav.api',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'c3nav.mapdata',
|
'c3nav.mapdata',
|
||||||
|
@ -329,6 +330,8 @@ REST_FRAMEWORK = {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NINJA_PAGINATION_CLASS = "ninja.pagination.LimitOffsetPagination"
|
||||||
|
|
||||||
LOCALE_PATHS = (
|
LOCALE_PATHS = (
|
||||||
PROJECT_DIR / 'locale',
|
PROJECT_DIR / 'locale',
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import c3nav.site.urls
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('editor/', include(c3nav.editor.urls)),
|
path('editor/', include(c3nav.editor.urls)),
|
||||||
|
path('api/v2/', c3nav.api.urls.ninja_api.urls),
|
||||||
path('api/', include((c3nav.api.urls, 'api'), namespace='api')),
|
path('api/', include((c3nav.api.urls, 'api'), namespace='api')),
|
||||||
path('map/', include(c3nav.mapdata.urls)),
|
path('map/', include(c3nav.mapdata.urls)),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
|
|
@ -3,6 +3,7 @@ django-bootstrap3==23.1
|
||||||
django-compressor==4.3.1
|
django-compressor==4.3.1
|
||||||
csscompressor==0.9.5
|
csscompressor==0.9.5
|
||||||
djangorestframework==3.14.0
|
djangorestframework==3.14.0
|
||||||
|
django-ninja==0.22.2
|
||||||
django-filter==23.2
|
django-filter==23.2
|
||||||
shapely==2.0.1
|
shapely==2.0.1
|
||||||
pybind11==2.10.4
|
pybind11==2.10.4
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue