__package__ = "archivebox.api" from io import StringIO from traceback import format_exception from contextlib import redirect_stdout, redirect_stderr from django.http import HttpRequest, HttpResponse from django.core.exceptions import ObjectDoesNotExist, EmptyResultSet, PermissionDenied from django.contrib.auth.models import User from ninja import NinjaAPI, Swagger # TODO: explore adding https://eadwincode.github.io/django-ninja-extra/ from archivebox.config import VERSION from archivebox.config.version import get_COMMIT_HASH from archivebox.api.auth import API_AUTH_METHODS from archivebox.api.models import APIToken COMMIT_HASH = get_COMMIT_HASH() or "unknown" html_description = f"""

Welcome to your ArchiveBox server's REST API [v1 ALPHA] homepage!


WARNING: This API is still in an early development stage and may change!
Served by ArchiveBox v{VERSION} ({COMMIT_HASH[:8]}), API powered by django-ninja. """ def register_urls(api: NinjaAPI) -> NinjaAPI: api.add_router("/auth/", "archivebox.api.v1_auth.router") api.add_router("/core/", "archivebox.api.v1_core.router") api.add_router("/crawls/", "archivebox.api.v1_crawls.router") api.add_router("/cli/", "archivebox.api.v1_cli.router") api.add_router("/machine/", "archivebox.api.v1_machine.router") return api class NinjaAPIWithIOCapture(NinjaAPI): def create_temporal_response(self, request: HttpRequest) -> HttpResponse: stdout, stderr = StringIO(), StringIO() with redirect_stderr(stderr): with redirect_stdout(stdout): setattr(request, "stdout", stdout) setattr(request, "stderr", stderr) response = super().create_temporal_response(request) # Disable caching of API responses entirely response["Cache-Control"] = "no-store" # Add debug stdout and stderr headers to response response["X-ArchiveBox-Stdout"] = stdout.getvalue().replace("\n", "\\n")[:200] response["X-ArchiveBox-Stderr"] = stderr.getvalue().replace("\n", "\\n")[:200] # response['X-ArchiveBox-View'] = self.get_openapi_operation_id(request) or 'Unknown' # Add Auth Headers to response api_token_attr = getattr(request, "_api_token", None) api_token = api_token_attr if isinstance(api_token_attr, APIToken) else None token_expiry = api_token.expires.isoformat() if api_token and api_token.expires else "Never" response["X-ArchiveBox-Auth-Method"] = str(getattr(request, "_api_auth_method", "None")) response["X-ArchiveBox-Auth-Expires"] = token_expiry response["X-ArchiveBox-Auth-Token-Id"] = str(api_token.id) if api_token else "None" response["X-ArchiveBox-Auth-User-Id"] = str(request.user.pk) if getattr(request.user, "pk", None) else "None" response["X-ArchiveBox-Auth-User-Username"] = request.user.username if isinstance(request.user, User) else "None" # import ipdb; ipdb.set_trace() # print('RESPONDING NOW', response) return response api = NinjaAPIWithIOCapture( title="ArchiveBox API", description=html_description, version=VERSION, auth=API_AUTH_METHODS, urls_namespace="api-1", docs=Swagger(settings={"persistAuthorization": True}), # docs_decorator=login_required, # renderer=ORJSONRenderer(), ) api = register_urls(api) urls = api.urls @api.exception_handler(Exception) def generic_exception_handler(request, err): status = 503 if isinstance(err, (ObjectDoesNotExist, EmptyResultSet, PermissionDenied)): status = 404 print("".join(format_exception(err))) return api.create_response( request, { "succeeded": False, "message": f"{err.__class__.__name__}: {err}", "errors": [ "".join(format_exception(err)), # or send simpler parent-only traceback: # *([str(err.__context__)] if getattr(err, '__context__', None) else []), ], }, status=status, ) # import orjson # from ninja.renderers import BaseRenderer # class ORJSONRenderer(BaseRenderer): # media_type = "application/json" # def render(self, request, data, *, response_status): # return { # "success": True, # "errors": [], # "result": data, # "stdout": ansi_to_html(stdout.getvalue().strip()), # "stderr": ansi_to_html(stderr.getvalue().strip()), # } # return orjson.dumps(data)