Skip to content

Commit

Permalink
migrate to FastAPI (#170)
Browse files Browse the repository at this point in the history
* migrate mongoengine to motor for mongodb
* migrate from flask to fastapi for web framework
  • Loading branch information
alfredfrancis authored Jan 10, 2025
1 parent acf4973 commit a3c3a13
Show file tree
Hide file tree
Showing 45 changed files with 768 additions and 820 deletions.
20 changes: 12 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
FROM python:3.12.7-slim

# Install common libraries
RUN apt-get update -qq \
&& apt-get install -y --no-install-recommends build-essential && apt-get autoremove -y

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
python3-dev \
&& rm -rf /var/lib/apt/lists/*

EXPOSE 80
# Copy requirements first to leverage Docker cache
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application
COPY . .

CMD ["gunicorn", "run:app" ,"--log-level=debug", "--timeout", "90","--bind", "0.0.0.0:80" ]
EXPOSE 80

CMD ["fastapi", "run" ,"--host", "0.0.0.0","--port", "80" ]
68 changes: 0 additions & 68 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,68 +0,0 @@
import os
from flask import Flask, send_from_directory, Blueprint
from flask_cors import CORS
from flask_mongoengine import MongoEngine
from config import config
from app.bot.dialogue_manager.dialogue_manager import DialogueManager

admin_panel_dist = 'static/'

db = MongoEngine()

def create_app(env="Development"):
app = Flask(__name__)

# Configurations
try:
env = os.environ['APPLICATION_ENV']
except KeyError as e:
app.logger.info('Unknown environment key, defaulting to Development')

app.config.from_object(config[env])
app.config.from_prefixed_env(prefix='APP')

CORS(app)
db.init_app(app)

# initialize dialogue_manager
dialogue_manager = DialogueManager.from_config(app)
dialogue_manager.update_model(app.config["MODELS_DIR"])
app.dialogue_manager : DialogueManager = dialogue_manager

from app.admin.bots.controllers import bots
from app.admin.intents.controllers import intents
from app.admin.train.controllers import train
from app.bot.chat.controllers import chat
from app.admin.entities.controllers import entities_blueprint

# bot endpoints
# TODO: move to a isolated web server
app.register_blueprint(chat)

# admin endpoints
admin_routes = Blueprint('admin', __name__, url_prefix='/admin/')
admin_routes.register_blueprint(intents)
admin_routes.register_blueprint(train)
admin_routes.register_blueprint(bots)
admin_routes.register_blueprint(entities_blueprint)
app.register_blueprint(admin_routes)


@app.route('/ready')
def ready():
return "ok",200

@app.route('/<path:path>', methods=['GET'])
def static_proxy(path):
return send_from_directory(admin_panel_dist, path)

@app.errorhandler(404)
def not_found(error):
return "Not found", 404

return app





103 changes: 0 additions & 103 deletions app/admin/bots/controllers.py

This file was deleted.

47 changes: 47 additions & 0 deletions app/admin/bots/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from fastapi import APIRouter, HTTPException, UploadFile, File
from fastapi.responses import Response
from typing import Dict, Any
import json

from app.admin.bots import store

router = APIRouter(prefix="/bots")

@router.put("/{name}/config")
async def set_config(name: str, config: Dict[str, Any]):
"""
Update bot config
"""
return await store.update_config(name, config)

@router.get("/{name}/config")
async def get_config(name: str):
"""
Get bot config
"""
return await store.get_config(name)

@router.get("/{name}/export")
async def export_bot(name: str):
"""
Export all intents and entities for the bot as a JSON file
"""
data = await store.export_bot(name)
return Response(
content=json.dumps(data),
media_type='application/json',
headers={'Content-Disposition': 'attachment;filename=chatbot_data.json'}
)

@router.post("/{name}/import")
async def import_agent(name: str, file: UploadFile = File(...)):
"""
Import intents and entities from a JSON file for the bot
"""
if not file:
raise HTTPException(status_code=400, detail="No file part")

content = await file.read()
json_data = json.loads(content)

return await store.import_bot(name, json_data)
12 changes: 12 additions & 0 deletions app/admin/bots/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from pydantic import BaseModel, Field
from typing import Dict, Any
from app.database import ObjectIdField

class Bot(BaseModel):
"""Base schema for bot"""
id: ObjectIdField = Field(validation_alias="_id", default=None)
name: str
config: Dict[str, Any] = {}

class Config:
arbitrary_types_allowed=True
66 changes: 66 additions & 0 deletions app/admin/bots/store.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from typing import Dict
from app.admin.bots.schemas import Bot
from app.admin.entities.store import list_entities
from app.admin.intents.store import list_intents
from app.database import database

bot_collection = database.get_collection("bot")
intent_collection = database.get_collection("intent")
entity_collection = database.get_collection("entity")

async def add_bot(data: dict):
return await bot_collection.insert_one(data)

async def get_bot(name: str)-> Bot:
bot = await bot_collection.find_one({"name": name})
return Bot.model_validate(bot)

async def get_config(name: str) -> Dict:
bot = await get_bot(name)
return bot.config

async def update_config(name: str, entity_data: dict) -> dict:
await bot_collection.update_one({"name": name}, {"$set": {"config": entity_data}})

async def export_bot(name) -> Dict:
# Get all intents and entities
intents = await list_intents()
entities = await list_entities()

entities = [entity.model_dump(exclude={"id"}) for entity in entities]
intents = [intent.model_dump(exclude={"id"}) for intent in intents]

export_data = {
"intents": intents,
"entities": entities
}
return export_data

async def import_bot(name: str, data: Dict):
intents = data.get("intents",[])
entities = data.get("entities",[])

created_intents = []
created_entities = []

# Import intents
if intents:
for intent in intents:
result = await intent_collection.update_one({"name": intent.get("name")}, {"$set": intent},upsert=True)
print(result)
if result.upserted_id:
created_intents.append(str(result.upserted_id))

# Import entities
if entities:
for entity in entities:
result = await entity_collection.update_one({"name": entity.get("name")}, {"$set": entity},upsert=True)
print(result)
if result.upserted_id:
created_entities.append(str(result.upserted_id))

return {
"num_intents_created": len(created_intents),
"num_entities_created": len(created_entities)
}

Loading

0 comments on commit a3c3a13

Please sign in to comment.