PyRunner / core /views /api /datastores.py
Akoda35's picture
Upload 2 files
45b7714 verified
Raw
History Blame Contribute Delete
7.31 kB
"""
API views for datastore access.
"""
import logging
from django.http import JsonResponse, HttpRequest
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from core.models import DataStore, DataStoreEntry
from core.views.api.decorators import api_token_required, add_cors_headers
logger = logging.getLogger(__name__)
# Pagination settings
DEFAULT_PAGE_SIZE = 50
MAX_PAGE_SIZE = 100
@csrf_exempt
@require_http_methods(["GET", "OPTIONS"])
@api_token_required
def list_datastores(request: HttpRequest) -> JsonResponse:
"""
List all datastores accessible to the token.
GET /api/v1/datastores/
Returns:
{
"datastores": [
{
"name": "store_name",
"description": "...",
"entry_count": 42,
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-02-10T14:22:00Z"
}
],
"count": 1
}
"""
if request.method == "OPTIONS":
response = JsonResponse({})
return add_cors_headers(response)
token = request.api_token
if token.datastore:
# Token restricted to single datastore
datastores = [token.datastore]
else:
# Global token - list all datastores
datastores = DataStore.objects.all()
data = {
"datastores": [
{
"name": ds.name,
"description": ds.description,
"entry_count": ds.entry_count,
"created_at": ds.created_at.isoformat(),
"updated_at": ds.updated_at.isoformat(),
}
for ds in datastores
],
"count": len(datastores) if token.datastore else datastores.count(),
}
response = JsonResponse(data)
return add_cors_headers(response)
@csrf_exempt
@require_http_methods(["GET", "OPTIONS"])
@api_token_required
def get_datastore(request: HttpRequest, name: str) -> JsonResponse:
"""
Get datastore metadata.
GET /api/v1/datastores/<name>/
Returns:
{
"name": "store_name",
"description": "...",
"entry_count": 42,
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-02-10T14:22:00Z"
}
"""
if request.method == "OPTIONS":
response = JsonResponse({})
return add_cors_headers(response)
datastore = _get_authorized_datastore(request, name)
if isinstance(datastore, JsonResponse):
return datastore # Error response
data = {
"name": datastore.name,
"description": datastore.description,
"entry_count": datastore.entry_count,
"created_at": datastore.created_at.isoformat(),
"updated_at": datastore.updated_at.isoformat(),
}
response = JsonResponse(data)
return add_cors_headers(response)
@csrf_exempt
@require_http_methods(["GET", "OPTIONS"])
@api_token_required
def list_entries(request: HttpRequest, name: str) -> JsonResponse:
"""
List entries in a datastore with pagination.
GET /api/v1/datastores/<name>/entries/
Query params:
- page: Page number (default: 1)
- page_size: Items per page (default: 50, max: 100)
Returns:
{
"entries": [
{
"key": "config",
"value": {"retries": 3},
"created_at": "2026-01-20T08:00:00Z",
"updated_at": "2026-02-01T12:00:00Z"
}
],
"count": 42,
"page": 1,
"page_size": 50,
"total_pages": 1
}
"""
if request.method == "OPTIONS":
response = JsonResponse({})
return add_cors_headers(response)
datastore = _get_authorized_datastore(request, name)
if isinstance(datastore, JsonResponse):
return datastore # Error response
# Parse pagination params
try:
page = max(1, int(request.GET.get("page", 1)))
except (ValueError, TypeError):
page = 1
try:
page_size = min(MAX_PAGE_SIZE, max(1, int(request.GET.get("page_size", DEFAULT_PAGE_SIZE))))
except (ValueError, TypeError):
page_size = DEFAULT_PAGE_SIZE
# Get total count and calculate pagination
total_count = datastore.entries.count()
total_pages = max(1, (total_count + page_size - 1) // page_size)
# Ensure page is within bounds
page = min(page, total_pages)
# Get paginated entries
offset = (page - 1) * page_size
entries = datastore.entries.all()[offset:offset + page_size]
data = {
"entries": [
{
"key": entry.key,
"value": entry.get_value(),
"created_at": entry.created_at.isoformat(),
"updated_at": entry.updated_at.isoformat(),
}
for entry in entries
],
"count": total_count,
"page": page,
"page_size": page_size,
"total_pages": total_pages,
}
response = JsonResponse(data)
return add_cors_headers(response)
@csrf_exempt
@require_http_methods(["GET", "OPTIONS"])
@api_token_required
def get_entry(request: HttpRequest, name: str, key: str) -> JsonResponse:
"""
Get a single entry by key.
GET /api/v1/datastores/<name>/entries/<key>/
Returns:
{
"key": "config",
"value": {"retries": 3},
"created_at": "2026-01-20T08:00:00Z",
"updated_at": "2026-02-01T12:00:00Z"
}
"""
if request.method == "OPTIONS":
response = JsonResponse({})
return add_cors_headers(response)
datastore = _get_authorized_datastore(request, name)
if isinstance(datastore, JsonResponse):
return datastore # Error response
try:
entry = DataStoreEntry.objects.get(datastore=datastore, key=key)
except DataStoreEntry.DoesNotExist:
response = JsonResponse(
{"error": {"code": "NOT_FOUND", "message": f"Entry '{key}' not found"}},
status=404,
)
return add_cors_headers(response)
data = {
"key": entry.key,
"value": entry.get_value(),
"created_at": entry.created_at.isoformat(),
"updated_at": entry.updated_at.isoformat(),
}
response = JsonResponse(data)
return add_cors_headers(response)
def _get_authorized_datastore(request: HttpRequest, name: str):
"""
Get datastore if token has access.
Returns the DataStore object or a JsonResponse error.
"""
token = request.api_token
try:
datastore = DataStore.objects.get(name=name)
except DataStore.DoesNotExist:
response = JsonResponse(
{"error": {"code": "NOT_FOUND", "message": f"Datastore '{name}' not found"}},
status=404,
)
return add_cors_headers(response)
# Check token access
if token.datastore and token.datastore != datastore:
response = JsonResponse(
{"error": {"code": "FORBIDDEN", "message": "Token does not have access to this datastore"}},
status=403,
)
return add_cors_headers(response)
return datastore