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.utils.functional import cached_property
|
||||
from ninja import NinjaAPI
|
||||
from rest_framework.generics import GenericAPIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.routers import SimpleRouter
|
||||
|
@ -18,8 +19,16 @@ from c3nav.mapdata.api import (AccessRestrictionGroupViewSet, AccessRestrictionV
|
|||
SpaceViewSet, StairViewSet, UpdatesViewSet)
|
||||
from c3nav.mapdata.utils.user import can_access_editor
|
||||
from c3nav.mesh.api import FirmwareViewSet
|
||||
from c3nav.mesh.newapi import api_router as mesh_api_router
|
||||
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.register(r'map', MapViewSet, basename='map')
|
||||
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.
|
||||
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.
|
||||
|
||||
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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
@ -10,3 +14,26 @@ def get_api_post_data(request):
|
|||
raise ParseError('Invalid JSON.')
|
||||
return data
|
||||
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 enum import IntEnum, unique
|
||||
|
||||
from c3nav.api.utils import EnumSchemaByNameMixin
|
||||
from c3nav.mesh.baseformats import (BoolFormat, EnumFormat, FixedHexFormat, FixedStrFormat, SimpleFormat, StructType,
|
||||
VarArrayFormat)
|
||||
|
||||
|
@ -76,7 +77,7 @@ class UWBConfig(StructType):
|
|||
|
||||
|
||||
@unique
|
||||
class BoardType(IntEnum):
|
||||
class BoardType(EnumSchemaByNameMixin, IntEnum):
|
||||
CUSTOM = 0x00
|
||||
|
||||
# devboards
|
||||
|
|
|
@ -5,6 +5,7 @@ from typing import TypeVar
|
|||
import channels
|
||||
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,
|
||||
VarBytesFormat, VarStrFormat, normalize_name)
|
||||
from c3nav.mesh.dataformats import (BoardConfig, FirmwareAppDescription, MacAddressesListFormat, MacAddressFormat,
|
||||
|
@ -68,7 +69,7 @@ M = TypeVar('M', bound='MeshMessage')
|
|||
|
||||
|
||||
@unique
|
||||
class ChipType(IntEnum):
|
||||
class ChipType(EnumSchemaByNameMixin, IntEnum):
|
||||
ESP32_S2 = 2
|
||||
ESP32_C3 = 5
|
||||
|
||||
|
|
|
@ -385,7 +385,7 @@ class FirmwareBuild(models.Model):
|
|||
'chip': ChipType(self.chip).name,
|
||||
'sha256_hash': self.sha256_hash,
|
||||
'url': self.binary.url,
|
||||
'boards': self.boards,
|
||||
'boards': [board.name for board in self.boards],
|
||||
}
|
||||
|
||||
@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',
|
||||
'compressor',
|
||||
'bootstrap3',
|
||||
'ninja',
|
||||
'c3nav.api',
|
||||
'rest_framework',
|
||||
'c3nav.mapdata',
|
||||
|
@ -329,6 +330,8 @@ REST_FRAMEWORK = {
|
|||
)
|
||||
}
|
||||
|
||||
NINJA_PAGINATION_CLASS = "ninja.pagination.LimitOffsetPagination"
|
||||
|
||||
LOCALE_PATHS = (
|
||||
PROJECT_DIR / 'locale',
|
||||
)
|
||||
|
|
|
@ -15,6 +15,7 @@ import c3nav.site.urls
|
|||
|
||||
urlpatterns = [
|
||||
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('map/', include(c3nav.mapdata.urls)),
|
||||
path('admin/', admin.site.urls),
|
||||
|
|
|
@ -3,6 +3,7 @@ django-bootstrap3==23.1
|
|||
django-compressor==4.3.1
|
||||
csscompressor==0.9.5
|
||||
djangorestframework==3.14.0
|
||||
django-ninja==0.22.2
|
||||
django-filter==23.2
|
||||
shapely==2.0.1
|
||||
pybind11==2.10.4
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue