(20241213) Return code is not the HTTP Status Code.

This commit is contained in:
2024-12-13 14:13:47 +05:30
parent e9a106dd91
commit a702e5ca20
13 changed files with 197 additions and 122 deletions
@@ -69,7 +69,7 @@ from utils_v2.goog.models.data.auth_tokens import GoogleAuthTokens
from shared import constants
# Data Models:
from models.data.api.mail.retrieve import MailGetRequestHeaders, MailGetRequestData
from models.api.mail.get import MailGetRequestHeaders, MailGetRequestData
# To work with datatypes:
from typing import Literal
@@ -89,7 +89,7 @@ import datetime
# Related to Quart:
mail_retrieve_bp = Blueprint("mail_retrieve", __name__)
mail_get_bp = Blueprint("mail_get", __name__)
# *****************************************************************************************************************
@@ -109,7 +109,7 @@ mail_retrieve_bp = Blueprint("mail_retrieve", __name__)
# *****************************************************************************************************************
@mail_retrieve_bp.record_once
@mail_get_bp.record_once
def init(blueprint_setup_state):
# This gets called when the blueprint is registered.
@@ -120,7 +120,7 @@ def init(blueprint_setup_state):
# ---------------------------------------------------------------------------------------------------------------------
@mail_retrieve_bp.route("/get", methods = ["GET"])
@mail_get_bp.route("", methods = ["GET"])
@set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False)
@get_session_info(key = "X-Session-Token", session_coro = "get_session")
@@ -148,7 +148,7 @@ async def get_one_mail(
):
"""
Use this endpoint when the user wants to fetch one mail.
Use this endpoint when the user wants to fetch one mail's full payload.
:param inbound_headers: auto-extracted by the decorators.
:param inbound_data: auto-extracted by the decorators.
:param inbound_files: auto-extracted by the decorators.
@@ -164,16 +164,17 @@ async def get_one_mail(
)
# Get the mail:
mail_data = await current_app.mail_retrieve_model.get_mail(
message = await current_app.mail_controller.get_one_mail(
mongo_conn = current_app.data_mongo,
mail_id = inbound_data.mailId
token_id = inbound_data.tokenId,
message_id = inbound_data.messageId
)
# Done here:
return ResponseModel(
status_code = StatusCodes.OK if mail_data else StatusCodes.FAILED,
http_code = HttpCodes.SUCCESS if mail_data else HttpCodes.NOT_FOUND,
data = mail_data
status_code = StatusCodes.OK if message else StatusCodes.FAILED,
http_code = HttpCodes.SUCCESS if message else HttpCodes.NOT_FOUND,
data = message.full
)
@@ -10,8 +10,8 @@
OBJECTIVE:
To list e-mails by their account identifier. Remember that the 'account identifier' is the '_id' of the document
in MongoDB that holds the tokens to authorize the e-mail id whose mails are being accessed.
To list e-mails by their token identifier. Remember that the 'token identifier' is the '_id' of the document in
MongoDB that holds the tokens to authorize the e-mail id whose mails are being accessed.
REFERENCES:
@@ -70,7 +70,7 @@ from utils_v2.goog.models.data.auth_tokens import GoogleAuthTokens
from shared import constants
# Data Models:
from models.data.api.mail.list import MailListRequestHeaders, MailListByAccountIdRequestData
from models.api.mail.list import MailListRequestHeaders, MailListRequestData
# To work with datatypes:
from typing import Literal
@@ -121,7 +121,9 @@ def init(blueprint_setup_state):
# ---------------------------------------------------------------------------------------------------------------------
@mail_list_bp.route("/list", methods = ["GET"])
@mail_list_bp.route("/list/id/token", methods = ["GET", "POST"])
@mail_list_bp.route("/list/tags", methods = ["GET", "POST"])
@set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False)
@get_session_info(key = "X-Session-Token", session_coro = "get_session")
@@ -138,18 +140,19 @@ def init(blueprint_setup_state):
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
@validate_input(
header_validator = lambda x: MailListRequestHeaders(**x).model_dump(),
data_validator = lambda x: MailListByAccountIdRequestData(**x)
data_validator = lambda x: MailListRequestData(**x)
)
@handle_cancelled_request()
async def list_mails_for_account_id(
async def list_mails(
inbound_headers: dict | MailListRequestHeaders = None,
inbound_data: dict | MailListByAccountIdRequestData = None,
inbound_data: dict | MailListRequestData = None,
inbound_files: dict = None,
**kwargs
):
"""
Use this endpoint when the user wants to fetch the list of mails.
Use this endpoint when the user wants to fetch the list of mails. The shortlisting here will be done by way of the
token identifier. Any no. of 'tokenId' objects will be given by the client.
:param inbound_headers: auto-extracted by the decorators.
:param inbound_data: auto-extracted by the decorators.
:param inbound_files: auto-extracted by the decorators.
@@ -164,19 +167,26 @@ async def list_mails_for_account_id(
http_code = HttpCodes.UNAUTHORIZED
)
# Get the mail:
mails_list = await current_app.mail_retrieve_model.list_for_token_id(
# Build the additional filter:
additional_filter = {}
if inbound_data.tags: additional_filter["tags"] = {"$in": inbound_data.tags}
additional_filter = additional_filter or None
print("AddFil:", additional_filter)
# Get the mails:
mails_list = await current_app.mail_controller.list_mails(
mongo_conn = current_app.data_mongo,
token_id = inbound_data.tokenId,
token_ids = inbound_data.tokenIds,
limit = inbound_data.count,
skip = inbound_data.fromCount
skip = inbound_data.fromCount,
additional_filter = additional_filter
)
# Done here:
return ResponseModel(
status_code = StatusCodes.OK if mails_list else StatusCodes.FAILED,
http_code = HttpCodes.SUCCESS if mails_list else HttpCodes.NOT_FOUND,
data = mails_list,
data = [mail.preview for mail in mails_list],
message = f"{len(mails_list) if mails_list else 0} mail(s) found"
)
@@ -6,13 +6,11 @@
DATE:
Monday, 2nd Dec., 2024
Friday, 13th Dec., 2024
OBJECTIVE:
To receive requests for synchronising mails from various mail clients to the database. Sync'ing means we pull
the mail from the mail client (like GMail) and store it to our database. The mail is then ready for showing on
the UI at any time.
To update the tags on one mail message.
REFERENCES:
@@ -50,6 +48,7 @@ from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.api.codes import StatusCodes, HttpCodes
from utils_v2.api.response import ResponseModel
from utils_v2.api.async_quart import (
make_ordered_json,
set_api_version,
read_input,
get_session_info,
@@ -70,9 +69,7 @@ from utils_v2.goog.models.data.auth_tokens import GoogleAuthTokens
from shared import constants
# Data Models:
from models.data.api.mail.sync import MailSyncRequestHeaders, MailSyncRequestData
from models.data.api.mail.sync import MailSyncOneResult, MailSyncManyResults
from models.data.core.user import CoreUserInfoModel
from models.api.mail.tags import MailUpdateTagsRequestHeaders, MailUpdateTagsRequestData
# To work with datatypes:
from typing import Literal
@@ -80,9 +77,6 @@ from typing import Literal
# For asynchronous activities:
import asyncio
# To work with LLMs:
from langchain_openai import ChatOpenAI
# To work with date and time:
import datetime
@@ -95,7 +89,7 @@ import datetime
# Related to Quart:
mail_sync_bp = Blueprint("mail_sync", __name__)
mail_tags_update_bp = Blueprint("mail_tags_update", __name__)
# *****************************************************************************************************************
@@ -115,7 +109,7 @@ mail_sync_bp = Blueprint("mail_sync", __name__)
# *****************************************************************************************************************
@mail_sync_bp.record_once
@mail_tags_update_bp.record_once
def init(blueprint_setup_state):
# This gets called when the blueprint is registered.
@@ -126,44 +120,7 @@ def init(blueprint_setup_state):
# ---------------------------------------------------------------------------------------------------------------------
async def sync_mails(
user_info: CoreUserInfoModel,
mongo_conn: AsyncMongo,
llm: ChatOpenAI,
inbound_headers: dict,
inbound_data: MailSyncRequestData
) -> MailSyncManyResults:
"""
A very simple function, but kept separate so that we get the option to switch between running it in the foreground
and running it in the background.
:param user_info: The information of the user as extracted from the session token.
:param mongo_conn: The instance of the database connector to use to sync the mails.
:param llm: The instance of the LLM to use to summarize the mails.
:param inbound_headers: The headers that came in with the request.
:param inbound_data: The data that came in with the request.
:return: The results of the mail-sync'ing attempt.
"""
# Try to sync the mails:
return await current_app.mail_sync_model.sync(
session_token = inbound_headers["X-Session-Token"],
user_info = user_info,
mongo_conn = mongo_conn,
token_id = inbound_data.tokenId,
llm = llm,
force_sync = inbound_data.forceSync,
start_date = inbound_data.startDate,
end_date = inbound_data.endDate,
max_count = inbound_data.maxCount
)
# ---------------------------------------------------------------------------------------------------------------------
@mail_sync_bp.route("/sync", methods = ["POST"])
@mail_sync_bp.route("/sync/<mode>", methods = ["POST"])
@mail_tags_update_bp.route("/tags", methods = ["PATCH"])
@set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False)
@get_session_info(key = "X-Session-Token", session_coro = "get_session")
@@ -171,7 +128,7 @@ async def sync_mails(
attr_name = "logs_mongo",
project = constants.PROJECT_NAME,
log_type = constants.MODULE_NAME,
operation = "mailSyncApi",
operation = "mailTagsUpdtApi",
log_input = True,
log_output = True,
sensitive_keys = ["sessionToken", "X-Session-Token"]
@@ -179,22 +136,19 @@ async def sync_mails(
@log_chain_to_mongo(attr_name = "logs_mongo")
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
@validate_input(
header_validator = lambda x: MailSyncRequestHeaders(**x).model_dump(),
data_validator = lambda x: MailSyncRequestData(**x)
header_validator = lambda x: MailGetRequestHeaders(**x).model_dump(),
data_validator = lambda x: MailGetRequestData(**x)
)
@handle_cancelled_request()
async def sync_mail(
mode: Literal["background", "bg"] = None,
inbound_headers: dict | MailSyncRequestHeaders = None,
inbound_data: dict | MailSyncRequestData = None,
async def update_mail_tags(
inbound_headers: dict | MailGetRequestHeaders = None,
inbound_data: dict | MailGetRequestData = None,
inbound_files: dict = None,
**kwargs
):
"""
Use this when the user wants to pull old mails from some mail client (like GMail) and save it to the database for
ready access on the UI.
:param mode: Set it to one of the specified options to make the sync'ing process go to the background.
Use this endpoint to update the tags on an e-mail message.
:param inbound_headers: auto-extracted by the decorators.
:param inbound_data: auto-extracted by the decorators.
:param inbound_files: auto-extracted by the decorators.
@@ -209,45 +163,18 @@ async def sync_mail(
http_code = HttpCodes.UNAUTHORIZED
)
# Make the variables available in the scope of the current request:
g.inbound_headers = inbound_headers
g.inbound_data = inbound_data
# If we've been asked to sync the mails in the background:
if mode in ["background", "bg"]:
current_app.add_background_task(
sync_mails,
user_info = CoreUserInfoModel(**kwargs["session_info"]),
mongo_conn = current_app.data_mongo,
llm = current_app.llm,
inbound_headers = inbound_headers,
inbound_data = inbound_data
)
return ResponseModel(
status_code = StatusCodes.OK,
http_code = HttpCodes.ACCEPTED,
message = "your mails are being sync'd in the background"
)
# Otherwise we process it right here:
sync_results = await sync_mails(
user_info = CoreUserInfoModel(**kwargs["session_info"]),
# Get the mail:
message = await current_app.mail_controller.get_one_mail(
mongo_conn = current_app.data_mongo,
llm = current_app.llm,
inbound_headers = inbound_headers,
inbound_data = inbound_data
token_id = inbound_data.tokenId,
message_id = inbound_data.messageId
)
# Response:
# Done here:
return ResponseModel(
status_code = StatusCodes.FAILED if sync_results.failureCount > 0 else StatusCodes.OK,
http_code = HttpCodes.INTERNAL_SERVER_ERROR if sync_results.failureCount > 0 else HttpCodes.SUCCESS,
message = sync_results.message,
data = {
"totalCount": sync_results.totalCount,
"successCount": sync_results.successCount,
"failureCount": sync_results.failureCount
}
status_code = StatusCodes.OK if message else StatusCodes.FAILED,
http_code = HttpCodes.SUCCESS if message else HttpCodes.NOT_FOUND,
data = message.full
)
@@ -101,7 +101,12 @@ class MailGetRequestHeaders(BaseModel):
class MailGetRequestData(BaseModel):
mailId: str = Field(
tokenId: str = Field(
description = "the id of the token associated with the mail; needed for security",
frozen = True
)
messageId: str = Field(
description = "the mail identifier (Mongo ObjectId) of the document that holds the mail",
frozen = True
)
+132
View File
@@ -0,0 +1,132 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Tuesday, 3rd Dec., 2024.
OBJECTIVE:
To provide a structure to query the full payload of an email.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# For making data behaviour_models:
from pydantic import BaseModel, Field, field_validator, PastDatetime
from typing import Optional, Literal
# My utils:
from utils_v2.string import regex
from utils_v2.date_time import date_time
# To work with date and time:
import datetime
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# RegEx Patterns:
REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$"
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
class MailGetRequestHeaders(BaseModel):
sessionToken: str = Field(
description = "the session token of the user who is requesting the service",
pattern = REGEX_SESSION_TOKEN,
frozen = True,
alias = "X-Session-Token"
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "allow"
def model_dump(self, *args, **kwargs):
return super().model_dump(*args, by_alias = True, **kwargs)
# ---------------------------------------------------------------------------------------------------------------------
class MailGetRequestData(BaseModel):
tokenId: str = Field(
description = "the id of the token associated with the mail; needed for security",
frozen = True
)
messageId: str = Field(
description = "the mail identifier (Mongo ObjectId) of the document that holds the mail",
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
+4 -4
View File
@@ -86,10 +86,13 @@ class ResponseModel(BaseModel):
:return: The output as expected by Flask and Quart.
"""
# Figure out the HTTP code:
response_http_code = self.http_code.value if self.http_code is not None else self.status_code.value[2]
# Construct the basic structure:
response_dict = {
"status": 1 if self.status_code.value[0] else 0,
"code": self.status_code.value[1],
"code": response_http_code,
"message": self.message or self.status_code.name.replace("_", " ").lower(),
"data": self.data,
"apiVer": self.api_version
@@ -99,9 +102,6 @@ class ResponseModel(BaseModel):
if self.seconds is not None: response_dict["seconds"] = self.seconds
if self.log_id is not None: response_dict["logId"] = self.log_id
# Figure out the HTTP code:
response_http_code = self.http_code.value if self.http_code is not None else self.status_code.value[2]
# Done here:
return response_dict, response_http_code