Skip to content

Commit

Permalink
- Update serve_static_file logic to handle redirects, HTML responses.
Browse files Browse the repository at this point in the history
- Show current list of gcs static files on the webflow page
- parallel zip upload
- Enhance unknown error template with detailed error reporting and GitHub link
- Add `CustomAPIRouter` class and refactor routes to standardize trailing slashes handling
- replace healthcheck / with /status to support index.html static page
  • Loading branch information
devxpy committed Aug 2, 2024
1 parent ce7d6bb commit 40f7dfc
Show file tree
Hide file tree
Showing 22 changed files with 331 additions and 277 deletions.
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ COPY . .
ENV FORWARDED_ALLOW_IPS='*'
ENV PYTHONUNBUFFERED=1

ARG CAPROVER_GIT_COMMIT_SHA=${CAPROVER_GIT_COMMIT_SHA}
ENV CAPROVER_GIT_COMMIT_SHA=${CAPROVER_GIT_COMMIT_SHA}

EXPOSE 8000
EXPOSE 8501

Expand Down
37 changes: 27 additions & 10 deletions daras_ai/image_input.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import math
import mimetypes
import os
import re
import typing
import uuid
from pathlib import Path

import math
import numpy as np
import requests
from PIL import Image, ImageOps
Expand All @@ -13,6 +14,9 @@
from daras_ai_v2 import settings
from daras_ai_v2.exceptions import UserError

if typing.TYPE_CHECKING:
from google.cloud.storage import Blob, Bucket


def resize_img_pad(img_bytes: bytes, size: tuple[int, int]) -> bytes:
img_cv2 = bytes_to_cv2_img(img_bytes)
Expand Down Expand Up @@ -57,25 +61,38 @@ def upload_file_from_bytes(
data: bytes,
content_type: str = None,
) -> str:
if not content_type:
content_type = mimetypes.guess_type(filename)[0]
content_type = content_type or "application/octet-stream"
blob = storage_blob_for(filename)
blob.upload_from_string(data, content_type=content_type)
blob = gcs_blob_for(filename)
upload_gcs_blob_from_bytes(blob, data, content_type)
return blob.public_url


def storage_blob_for(filename: str) -> "storage.storage.Blob":
from firebase_admin import storage

def gcs_blob_for(filename: str) -> "Blob":
filename = safe_filename(filename)
bucket = storage.bucket(settings.GS_BUCKET_NAME)
bucket = gcs_bucket()
blob = bucket.blob(
os.path.join(settings.GS_MEDIA_PATH, str(uuid.uuid1()), filename)
)
return blob


def upload_gcs_blob_from_bytes(
blob: "Blob",
data: bytes,
content_type: str = None,
) -> str:
if not content_type:
content_type = mimetypes.guess_type(blob.path)[0]
content_type = content_type or "application/octet-stream"
blob.upload_from_string(data, content_type=content_type)
return blob.public_url


def gcs_bucket() -> "Bucket":
from firebase_admin import storage

return storage.bucket(settings.GS_BUCKET_NAME)


def cv2_img_to_bytes(img: np.ndarray) -> bytes:
import cv2

Expand Down
2 changes: 1 addition & 1 deletion daras_ai_v2/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def __init__(
@classmethod
@property
def endpoint(cls) -> str:
return f"/v2/{cls.slug_versions[0]}/"
return f"/v2/{cls.slug_versions[0]}"

@classmethod
def current_app_url(
Expand Down
2 changes: 1 addition & 1 deletion daras_ai_v2/bot_integration_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ def get_web_widget_embed_code(bi: BotIntegration) -> str:
integration_id=bi.api_integration_id(),
integration_name=slugify(bi.name) or "untitled",
),
)
).rstrip("/")
return dedent(
f"""
<div id="gooey-embed"></div>
Expand Down
2 changes: 1 addition & 1 deletion daras_ai_v2/bots.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ def build_run_vars(convo: Conversation, user_msg_id: str):

bi = convo.bot_integration
if bi.platform == Platform.WEB:
user_msg_id = user_msg_id.lstrip(MSG_ID_PREFIX)
user_msg_id = user_msg_id.removeprefix(MSG_ID_PREFIX)
variables = dict(
platform=Platform(bi.platform).name,
integration_id=bi.api_integration_id(),
Expand Down
4 changes: 2 additions & 2 deletions daras_ai_v2/gpu_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import typing
from time import time

from daras_ai.image_input import storage_blob_for
from daras_ai.image_input import gcs_blob_for
from daras_ai_v2 import settings
from daras_ai_v2.exceptions import GPUError, UserError
from gooeysite.bg_db_conn import get_celery_result_db_safe
Expand Down Expand Up @@ -43,7 +43,7 @@ def call_celery_task_outfile(
filename: str,
num_outputs: int = 1,
):
blobs = [storage_blob_for(filename) for i in range(num_outputs)]
blobs = [gcs_blob_for(filename) for i in range(num_outputs)]
pipeline["upload_urls"] = [
blob.generate_signed_url(
version="v4",
Expand Down
2 changes: 2 additions & 0 deletions daras_ai_v2/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@

GS_BUCKET_NAME = config("GS_BUCKET_NAME", default=f"{GCP_PROJECT}.appspot.com")
GS_MEDIA_PATH = config("GS_MEDIA_PATH", default="daras_ai/media")
GS_STATIC_PATH = config("GS_STATIC_PATH", default="gooeyai/static")


GOOGLE_CLIENT_ID = config("GOOGLE_CLIENT_ID", default="")
FIREBASE_CONFIG = config("FIREBASE_CONFIG", default="")
Expand Down
4 changes: 2 additions & 2 deletions glossary_resources/tests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from daras_ai.image_input import storage_blob_for
from daras_ai.image_input import gcs_blob_for
from daras_ai_v2 import settings
from daras_ai_v2.crypto import get_random_doc_id
from glossary_resources.models import GlossaryResource
Expand Down Expand Up @@ -64,7 +64,7 @@ def glossary_url():
import pandas as pd

df = pd.DataFrame.from_records(GLOSSARY)
blob = storage_blob_for("test glossary.csv")
blob = gcs_blob_for("test glossary.csv")
blob.upload_from_string(df.to_csv(index=False).encode(), content_type="text/csv")

try:
Expand Down
4 changes: 3 additions & 1 deletion routers/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
from payments.webhooks import PaypalWebhookHandler
from routers.root import page_wrapper, get_og_url_path

app = APIRouter()
from routers.custom_api_router import CustomAPIRouter

app = CustomAPIRouter()


@gui.route(app, "/payment-processing/")
Expand Down
56 changes: 11 additions & 45 deletions routers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@
from functions.models import CalledFunctionResponse
from gooeysite.bg_db_conn import get_celery_result_db_safe

app = APIRouter()
from routers.custom_api_router import CustomAPIRouter

app = CustomAPIRouter()


O = typing.TypeVar("O")
Expand Down Expand Up @@ -140,7 +142,7 @@ def script_to_api(page_cls: typing.Type[BasePage]):
}

@app.post(
os.path.join(endpoint, ""),
endpoint,
response_model=response_model,
responses={
HTTP_500_INTERNAL_SERVER_ERROR: {"model": FailedReponseModelV2},
Expand All @@ -150,15 +152,6 @@ def script_to_api(page_cls: typing.Type[BasePage]):
tags=[page_cls.title],
name=page_cls.title + " (v2 sync)",
)
@app.post(
endpoint,
response_model=response_model,
responses={
HTTP_500_INTERNAL_SERVER_ERROR: {"model": FailedReponseModelV2},
**common_errs,
},
include_in_schema=False,
)
def run_api_json(
request: Request,
page_request: request_model,
Expand All @@ -174,16 +167,6 @@ def run_api_json(
)
return build_sync_api_response(page=page, result=result, run_id=run_id, uid=uid)

@app.post(
os.path.join(endpoint, "form/"),
response_model=response_model,
responses={
HTTP_500_INTERNAL_SERVER_ERROR: {"model": FailedReponseModelV2},
HTTP_400_BAD_REQUEST: {"model": GenericErrorResponse},
**common_errs,
},
include_in_schema=False,
)
@app.post(
os.path.join(endpoint, "form"),
response_model=response_model,
Expand All @@ -209,21 +192,14 @@ def run_api_form(
response_model = AsyncApiResponseModelV3

@app.post(
os.path.join(endpoint, "async/"),
os.path.join(endpoint, "async"),
response_model=response_model,
responses=common_errs,
operation_id="async__" + page_cls.slug_versions[0],
name=page_cls.title + " (v3 async)",
tags=[page_cls.title],
status_code=202,
)
@app.post(
os.path.join(endpoint, "async"),
response_model=response_model,
responses=common_errs,
include_in_schema=False,
status_code=202,
)
def run_api_json_async(
request: Request,
response: Response,
Expand All @@ -243,15 +219,6 @@ def run_api_json_async(
response.headers["Access-Control-Expose-Headers"] = "Location"
return ret

@app.post(
os.path.join(endpoint, "async/form/"),
response_model=response_model,
responses={
HTTP_400_BAD_REQUEST: {"model": GenericErrorResponse},
**common_errs,
},
include_in_schema=False,
)
@app.post(
os.path.join(endpoint, "async/form"),
response_model=response_model,
Expand Down Expand Up @@ -281,19 +248,13 @@ def run_api_form_async(
)

@app.get(
os.path.join(endpoint, "status/"),
os.path.join(endpoint, "status"),
response_model=response_model,
responses=common_errs,
operation_id="status__" + page_cls.slug_versions[0],
tags=[page_cls.title],
name=page_cls.title + " (v3 status)",
)
@app.get(
os.path.join(endpoint, "status"),
response_model=response_model,
responses=common_errs,
include_in_schema=False,
)
def get_run_status(
run_id: str,
user: AppUser = Depends(api_auth_header),
Expand Down Expand Up @@ -471,3 +432,8 @@ class BalanceResponse(BaseModel):
@app.get("/v1/balance/", response_model=BalanceResponse, tags=["Misc"])
def get_balance(user: AppUser = Depends(api_auth_header)):
return BalanceResponse(balance=user.balance)


@app.get("/status")
async def health():
return "OK"
21 changes: 5 additions & 16 deletions routers/bots_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import Any

import hashids
from fastapi import APIRouter, HTTPException
from fastapi import HTTPException
from furl import furl
from pydantic import BaseModel, Field
from starlette.responses import StreamingResponse, Response
Expand All @@ -22,8 +22,9 @@
AsyncStatusResponseModelV3,
build_async_api_response,
)
from routers.custom_api_router import CustomAPIRouter

app = APIRouter()
app = CustomAPIRouter()

api_hashids = hashids.Hashids(salt=settings.HASHIDS_API_SALT)
MSG_ID_PREFIX = "web-"
Expand Down Expand Up @@ -78,19 +79,13 @@ class CreateStreamResponse(BaseModel):


@app.post(
"/v3/integrations/stream/",
"/v3/integrations/stream",
response_model=CreateStreamResponse,
responses={402: {}},
operation_id=VideoBotsPage.slug_versions[0] + "__stream_create",
tags=["Copilot Integrations"],
name="Copilot Integrations Create Stream",
)
@app.post(
"/v3/integrations/stream/",
response_model=CreateStreamResponse,
responses={402: {}},
include_in_schema=False,
)
def stream_create(request: CreateStreamRequest, response: Response):
request_id = str(uuid.uuid4())
get_redis_cache().set(
Expand Down Expand Up @@ -172,19 +167,13 @@ class StreamError(BaseModel):


@app.get(
"/v3/integrations/stream/{request_id}/",
"/v3/integrations/stream/{request_id}",
response_model=StreamEvent,
responses={402: {}},
operation_id=VideoBotsPage.slug_versions[0] + "__stream",
tags=["Copilot Integrations"],
name="Copilot integrations Stream Response",
)
@app.get(
"/v3/integrations/stream/{request_id}",
response_model=StreamEvent,
responses={402: {}},
include_in_schema=False,
)
def stream_response(request_id: str):
r = get_redis_cache().getdel(f"gooey/stream-init/v1/{request_id}")
if not r:
Expand Down
4 changes: 2 additions & 2 deletions routers/broadcast_api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import typing

from django.db.models import Q
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from pydantic import BaseModel, Field
Expand All @@ -11,8 +10,9 @@
from bots.models import BotIntegration
from bots.tasks import send_broadcast_msgs_chunked
from recipes.VideoBots import ReplyButton, VideoBotsPage
from routers.custom_api_router import CustomAPIRouter

app = APIRouter()
app = CustomAPIRouter()


class BotBroadcastFilters(BaseModel):
Expand Down
14 changes: 14 additions & 0 deletions routers/custom_api_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from fastapi import APIRouter


class CustomAPIRouter(APIRouter):
def add_api_route(self, path: str, *args, **kwargs) -> None:
super().add_api_route(path, *args, **kwargs)
if path.endswith("/"):
path = path[:-1]
else:
path += "/"
kwargs["include_in_schema"] = False
kwargs.pop("name", None)
kwargs.pop("tags", None)
super().add_api_route(path, *args, **kwargs)
4 changes: 2 additions & 2 deletions routers/facebook_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import requests
from fastapi import APIRouter
from fastapi.responses import RedirectResponse
from furl import furl
from starlette.background import BackgroundTasks
Expand All @@ -13,8 +12,9 @@
from daras_ai_v2.facebook_bots import WhatsappBot, FacebookBot
from daras_ai_v2.fastapi_tricks import fastapi_request_json
from daras_ai_v2.functional import map_parallel
from routers.custom_api_router import CustomAPIRouter

app = APIRouter()
app = CustomAPIRouter()


@app.get("/__/fb/connect_whatsapp/")
Expand Down
Loading

0 comments on commit 40f7dfc

Please sign in to comment.