Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added backend/api_v2/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions backend/api_v2/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.contrib import admin

from .models import APIDeployment, APIKey

admin.site.register([APIDeployment, APIKey])
152 changes: 152 additions & 0 deletions backend/api_v2/api_deployment_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import json
import logging
from typing import Any, Optional

from api_v2.constants import ApiExecution
from api_v2.deployment_helper import DeploymentHelper
from api_v2.exceptions import InvalidAPIRequest, NoActiveAPIKeyError
from api_v2.models import APIDeployment
from api_v2.postman_collection.dto import PostmanCollection
from api_v2.serializers import (
APIDeploymentListSerializer,
APIDeploymentSerializer,
DeploymentResponseSerializer,
ExecutionRequestSerializer,
)
from django.db.models import QuerySet
from django.http import HttpResponse
from permissions.permission import IsOwner
from rest_framework import serializers, status, views, viewsets
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from utils.enums import CeleryTaskState
from workflow_manager.workflow_v2.dto import ExecutionResponse

logger = logging.getLogger(__name__)


class DeploymentExecution(views.APIView):
def initialize_request(
self, request: Request, *args: Any, **kwargs: Any
) -> Request:
"""To remove csrf request for public API.

Args:
request (Request): _description_

Returns:
Request: _description_
"""
setattr(request, "csrf_processing_done", True)
return super().initialize_request(request, *args, **kwargs)

@DeploymentHelper.validate_api_key
def post(
self, request: Request, org_name: str, api_name: str, api: APIDeployment
) -> Response:
file_objs = request.FILES.getlist(ApiExecution.FILES_FORM_DATA)
serializer = ExecutionRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
timeout = serializer.get_timeout(serializer.validated_data)
include_metadata = (
request.data.get(ApiExecution.INCLUDE_METADATA, "false").lower() == "true"
)
if not file_objs or len(file_objs) == 0:
raise InvalidAPIRequest("File shouldn't be empty")
response = DeploymentHelper.execute_workflow(
organization_name=org_name,
api=api,
file_objs=file_objs,
timeout=timeout,
include_metadata=include_metadata,
)
if "error" in response and response["error"]:
return Response(
{"message": response},
status=status.HTTP_422_UNPROCESSABLE_ENTITY,
)
return Response({"message": response}, status=status.HTTP_200_OK)

@DeploymentHelper.validate_api_key
def get(
self, request: Request, org_name: str, api_name: str, api: APIDeployment
) -> Response:
execution_id = request.query_params.get("execution_id")
if not execution_id:
raise InvalidAPIRequest("execution_id shouldn't be empty")
response: ExecutionResponse = DeploymentHelper.get_execution_status(
execution_id=execution_id
)
if response.execution_status != CeleryTaskState.SUCCESS.value:
return Response(
{
"status": response.execution_status,
"message": response.result,
},
status=status.HTTP_422_UNPROCESSABLE_ENTITY,
)
return Response(
{"status": response.execution_status, "message": response.result},
status=status.HTTP_200_OK,
)


class APIDeploymentViewSet(viewsets.ModelViewSet):
permission_classes = [IsOwner]

def get_queryset(self) -> Optional[QuerySet]:
return APIDeployment.objects.filter(created_by=self.request.user)

def get_serializer_class(self) -> serializers.Serializer:
if self.action in ["list"]:
return APIDeploymentListSerializer
return APIDeploymentSerializer

@action(detail=True, methods=["get"])
def fetch_one(self, request: Request, pk: Optional[str] = None) -> Response:
"""Custom action to fetch a single instance."""
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)

def create(
self, request: Request, *args: tuple[Any], **kwargs: dict[str, Any]
) -> Response:
serializer: Serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
api_key = DeploymentHelper.create_api_key(serializer=serializer)
response_serializer = DeploymentResponseSerializer(
{"api_key": api_key.api_key, **serializer.data}
)

headers = self.get_success_headers(serializer.data)
return Response(
response_serializer.data,
status=status.HTTP_201_CREATED,
headers=headers,
)

@action(detail=True, methods=["get"])
def download_postman_collection(
self, request: Request, pk: Optional[str] = None
) -> Response:
"""Downloads a Postman Collection of the API deployment instance."""
instance = self.get_object()
api_key_inst = instance.apikey_set.filter(is_active=True).first()
if not api_key_inst:
logger.error(f"No active API key set for deployment {instance.pk}")
raise NoActiveAPIKeyError(deployment_name=instance.display_name)

postman_collection = PostmanCollection.create(
instance=instance, api_key=api_key_inst.api_key
)
response = HttpResponse(
json.dumps(postman_collection.to_dict()), content_type="application/json"
)
response["Content-Disposition"] = (
f'attachment; filename="{instance.display_name}.json"'
)
return response
28 changes: 28 additions & 0 deletions backend/api_v2/api_key_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from api_v2.deployment_helper import DeploymentHelper
from api_v2.exceptions import APINotFound
from api_v2.key_helper import KeyHelper
from api_v2.models import APIKey
from api_v2.serializers import APIKeyListSerializer, APIKeySerializer
from rest_framework import serializers, viewsets
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response


class APIKeyViewSet(viewsets.ModelViewSet):
queryset = APIKey.objects.all()

def get_serializer_class(self) -> serializers.Serializer:
if self.action in ["api_keys"]:
return APIKeyListSerializer
return APIKeySerializer

@action(detail=True, methods=["get"])
def api_keys(self, request: Request, api_id: str) -> Response:
"""Custom action to fetch api keys of an api deployment."""
api = DeploymentHelper.get_api_by_id(api_id=api_id)
if not api:
raise APINotFound()
keys = KeyHelper.list_api_keys_of_api(api_instance=api)
serializer = self.get_serializer(keys, many=True)
return Response(serializer.data)
5 changes: 5 additions & 0 deletions backend/api_v2/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class ApiConfig(AppConfig):
name = "api_v2"
6 changes: 6 additions & 0 deletions backend/api_v2/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class ApiExecution:
PATH: str = "deployment/api"
MAXIMUM_TIMEOUT_IN_SEC: int = 300 # 5 minutes
FILES_FORM_DATA: str = "files"
TIMEOUT_FORM_DATA: str = "timeout"
INCLUDE_METADATA: str = "include_metadata"
Loading