commit 69416eb8966ee4ea1f90eee5db42a50e84972a0b Author: H4CK3R-01 Date: Wed Mar 8 00:17:34 2023 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..765d5c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,133 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.env.* +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ +.idea/ + +.vercel diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app.py b/app.py new file mode 100644 index 0000000..c07ce5a --- /dev/null +++ b/app.py @@ -0,0 +1,18 @@ +from fastapi import FastAPI + +from config.config import initiate_database +from routes.request import router as RequestRouter +from routes.admin import router as AdminRouter +from config import Settings + +app = FastAPI() + +@app.on_event("startup") +async def start_database(): + await initiate_database() + + +app.include_router(RequestRouter, tags=["Requests"]) + +if Settings().ENV == "dev": + app.include_router(AdminRouter, tags=["Admin"]) \ No newline at end of file diff --git a/azure/__init__.py b/azure/__init__.py new file mode 100644 index 0000000..f414067 --- /dev/null +++ b/azure/__init__.py @@ -0,0 +1 @@ +from .functions import translate diff --git a/azure/functions.py b/azure/functions.py new file mode 100644 index 0000000..3053ef8 --- /dev/null +++ b/azure/functions.py @@ -0,0 +1,27 @@ +import requests +import uuid +from config import Settings + + +def translate(data): + constructed_url = Settings().AZURE_ENDPOINT + '/translate' + + params = { + 'api-version': '3.0', + 'from': data.language_from, + 'to': [data.language_to] + } + + headers = { + 'Ocp-Apim-Subscription-Key': Settings().AZURE_KEY, + 'Ocp-Apim-Subscription-Region': Settings().AZURE_LOCATION, + 'Content-type': 'application/json', + 'X-ClientTraceId': str(uuid.uuid4()) + } + + body = [{ + 'text': data.requested_text + }] + + request = requests.post(constructed_url, params=params, headers=headers, json=body) + return request.json() diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..22e4d85 --- /dev/null +++ b/config/__init__.py @@ -0,0 +1 @@ +from .config import Settings \ No newline at end of file diff --git a/config/config.py b/config/config.py new file mode 100644 index 0000000..eaac93b --- /dev/null +++ b/config/config.py @@ -0,0 +1,23 @@ +from beanie import init_beanie +from motor.motor_asyncio import AsyncIOMotorClient +from pydantic import BaseSettings +from typing import Literal + +from models.request import Request + + +class Settings(BaseSettings): + DATABASE_URL: str + AZURE_KEY: str + AZURE_ENDPOINT: str + AZURE_LOCATION: str + ENV: Literal["dev", "prod"] + + class Config: + env_file = ".env" + orm_mode = True + + +async def initiate_database(): + client = AsyncIOMotorClient(Settings().DATABASE_URL) + await init_beanie(database=client.get_default_database(), document_models=[Request]) diff --git a/database/database.py b/database/database.py new file mode 100644 index 0000000..0fe959e --- /dev/null +++ b/database/database.py @@ -0,0 +1,18 @@ +from typing import List +from models.request import Request + + +request_collection = Request + + +async def add_request(item: Request) -> Request: + return await item.create() + +async def retrieve_requests() -> List[Request]: + return await request_collection.all().to_list() + +async def retrieve_request(text: str, language_from: str, language_to: str) -> List[Request]: + return await request_collection.find({"requested_text": text, "language_from": language_from, "language_to": language_to}).to_list() + +async def clear_requests() -> None: + return await request_collection.delete_all() \ No newline at end of file diff --git a/models/request.py b/models/request.py new file mode 100644 index 0000000..688e8d2 --- /dev/null +++ b/models/request.py @@ -0,0 +1,40 @@ +from beanie import Document +from pydantic import BaseModel + + +class Request(Document): + id: str + requested_text: str + response_text: str + language_from: str + language_to: str + unixdate: int + + class Settings: + name = "request" + + class Config: + schema_extra = { + "example": { + "id": "9e90b529-05d0-4b0e-a8f8-de17e72e553c", + "requested_text": "I would really like to drive your car around the block a few times!", + "response_text": "Ich würde wirklich gerne mit Ihrem Auto ein paar Mal um den Block fahren!", + "language_from": "en", + "language_to": "de", + "date": 1678225669 + } + } + +class RequestData(BaseModel): + requested_text: str + language_from: str + language_to: str + + class Config: + schema_extra = { + "example": { + "requested_text": "I would really like to drive your car around the block a few times!", + "language_from": "en", + "language_to": "de", + } + } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..eaa6b96 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +requests==2.28.2 +uuid==1.30 +motor==3.1.1 +uvicorn==0.20.0 +pymongo==4.3.3 +fastapi==0.93.0 +pydantic==1.10.5 +passlib==1.7.4 +beanie==1.17.0 diff --git a/routes/admin.py b/routes/admin.py new file mode 100644 index 0000000..af6ea96 --- /dev/null +++ b/routes/admin.py @@ -0,0 +1,14 @@ +from fastapi import APIRouter + +from database.database import * +from models.request import * + +router = APIRouter() + +@router.post("/cleardb", response_description="Clears database cache") +async def cleardb(): + await clear_requests() + +@router.get("/dbcache", response_description="Show database contents", response_model=List[Request]) +async def showcache(): + return await retrieve_requests() \ No newline at end of file diff --git a/routes/request.py b/routes/request.py new file mode 100644 index 0000000..aabbaab --- /dev/null +++ b/routes/request.py @@ -0,0 +1,37 @@ +from fastapi import APIRouter +import uuid +import time + +from azure import translate as azure_translate +from database.database import * +from models.request import * + +router = APIRouter() + + +@router.post("/translate", response_description="Translate text", response_model=Request) +async def translate(data: RequestData): + # Check if this request is saved in the database + check_db = await retrieve_request(data.requested_text, data.language_from, data.language_to) + + if len(check_db) == 0: # Not in database, use azure api + print("Not in database, use Azure api") + # Make api request + response = azure_translate(data) + print(response) + + # Create object + item = Request( + id=str(uuid.uuid4()), + requested_text=data.requested_text, + response_text=response[0]['translations'][0]['text'], + language_from=data.language_from, + language_to=data.language_to, + unixdate=int(time.time()) + ) + + # Save it to the database and return it + return await add_request(item=item) + else: + print("Already in database, serving the cached result") + return check_db[0]