(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 from shared import constants
# Data Models: # Data Models:
from models.data.api.mail.retrieve import MailGetRequestHeaders, MailGetRequestData from models.api.mail.get import MailGetRequestHeaders, MailGetRequestData
# To work with datatypes: # To work with datatypes:
from typing import Literal from typing import Literal
@@ -89,7 +89,7 @@ import datetime
# Related to Quart: # 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): def init(blueprint_setup_state):
# This gets called when the blueprint is registered. # 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") @set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False) @read_input(sanitize_headers = False, sanitize_data = False)
@get_session_info(key = "X-Session-Token", session_coro = "get_session") @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_headers: auto-extracted by the decorators.
:param inbound_data: auto-extracted by the decorators. :param inbound_data: auto-extracted by the decorators.
:param inbound_files: 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: # 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, mongo_conn = current_app.data_mongo,
mail_id = inbound_data.mailId token_id = inbound_data.tokenId,
message_id = inbound_data.messageId
) )
# Done here: # Done here:
return ResponseModel( return ResponseModel(
status_code = StatusCodes.OK if mail_data else StatusCodes.FAILED, status_code = StatusCodes.OK if message else StatusCodes.FAILED,
http_code = HttpCodes.SUCCESS if mail_data else HttpCodes.NOT_FOUND, http_code = HttpCodes.SUCCESS if message else HttpCodes.NOT_FOUND,
data = mail_data data = message.full
) )
@@ -10,8 +10,8 @@
OBJECTIVE: OBJECTIVE:
To list e-mails by their account identifier. Remember that the 'account identifier' is the '_id' of the document To list e-mails by their token identifier. Remember that the 'token identifier' is the '_id' of the document in
in MongoDB that holds the tokens to authorize the e-mail id whose mails are being accessed. MongoDB that holds the tokens to authorize the e-mail id whose mails are being accessed.
REFERENCES: REFERENCES:
@@ -70,7 +70,7 @@ from utils_v2.goog.models.data.auth_tokens import GoogleAuthTokens
from shared import constants from shared import constants
# Data Models: # Data Models:
from models.data.api.mail.list import MailListRequestHeaders, MailListByAccountIdRequestData from models.api.mail.list import MailListRequestHeaders, MailListRequestData
# To work with datatypes: # To work with datatypes:
from typing import Literal 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/id/token", methods = ["GET", "POST"])
@mail_list_bp.route("/list/tags", methods = ["GET", "POST"])
@set_api_version(api_version = "1.0.0") @set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False) @read_input(sanitize_headers = False, sanitize_data = False)
@get_session_info(key = "X-Session-Token", session_coro = "get_session") @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") @should_not_be_under_maintenance(attr_name = "is_under_maintenance")
@validate_input( @validate_input(
header_validator = lambda x: MailListRequestHeaders(**x).model_dump(), header_validator = lambda x: MailListRequestHeaders(**x).model_dump(),
data_validator = lambda x: MailListByAccountIdRequestData(**x) data_validator = lambda x: MailListRequestData(**x)
) )
@handle_cancelled_request() @handle_cancelled_request()
async def list_mails_for_account_id( async def list_mails(
inbound_headers: dict | MailListRequestHeaders = None, inbound_headers: dict | MailListRequestHeaders = None,
inbound_data: dict | MailListByAccountIdRequestData = None, inbound_data: dict | MailListRequestData = None,
inbound_files: dict = None, inbound_files: dict = None,
**kwargs **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_headers: auto-extracted by the decorators.
:param inbound_data: auto-extracted by the decorators. :param inbound_data: auto-extracted by the decorators.
:param inbound_files: 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 http_code = HttpCodes.UNAUTHORIZED
) )
# Get the mail: # Build the additional filter:
mails_list = await current_app.mail_retrieve_model.list_for_token_id( 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, mongo_conn = current_app.data_mongo,
token_id = inbound_data.tokenId, token_ids = inbound_data.tokenIds,
limit = inbound_data.count, limit = inbound_data.count,
skip = inbound_data.fromCount skip = inbound_data.fromCount,
additional_filter = additional_filter
) )
# Done here: # Done here:
return ResponseModel( return ResponseModel(
status_code = StatusCodes.OK if mails_list else StatusCodes.FAILED, status_code = StatusCodes.OK if mails_list else StatusCodes.FAILED,
http_code = HttpCodes.SUCCESS if mails_list else HttpCodes.NOT_FOUND, 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" message = f"{len(mails_list) if mails_list else 0} mail(s) found"
) )
@@ -6,13 +6,11 @@
DATE: DATE:
Monday, 2nd Dec., 2024 Friday, 13th Dec., 2024
OBJECTIVE: OBJECTIVE:
To receive requests for synchronising mails from various mail clients to the database. Sync'ing means we pull To update the tags on one mail message.
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.
REFERENCES: 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.codes import StatusCodes, HttpCodes
from utils_v2.api.response import ResponseModel from utils_v2.api.response import ResponseModel
from utils_v2.api.async_quart import ( from utils_v2.api.async_quart import (
make_ordered_json,
set_api_version, set_api_version,
read_input, read_input,
get_session_info, get_session_info,
@@ -70,9 +69,7 @@ from utils_v2.goog.models.data.auth_tokens import GoogleAuthTokens
from shared import constants from shared import constants
# Data Models: # Data Models:
from models.data.api.mail.sync import MailSyncRequestHeaders, MailSyncRequestData from models.api.mail.tags import MailUpdateTagsRequestHeaders, MailUpdateTagsRequestData
from models.data.api.mail.sync import MailSyncOneResult, MailSyncManyResults
from models.data.core.user import CoreUserInfoModel
# To work with datatypes: # To work with datatypes:
from typing import Literal from typing import Literal
@@ -80,9 +77,6 @@ from typing import Literal
# For asynchronous activities: # For asynchronous activities:
import asyncio import asyncio
# To work with LLMs:
from langchain_openai import ChatOpenAI
# To work with date and time: # To work with date and time:
import datetime import datetime
@@ -95,7 +89,7 @@ import datetime
# Related to Quart: # 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): def init(blueprint_setup_state):
# This gets called when the blueprint is registered. # This gets called when the blueprint is registered.
@@ -126,44 +120,7 @@ def init(blueprint_setup_state):
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
async def sync_mails( @mail_tags_update_bp.route("/tags", methods = ["PATCH"])
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"])
@set_api_version(api_version = "1.0.0") @set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False) @read_input(sanitize_headers = False, sanitize_data = False)
@get_session_info(key = "X-Session-Token", session_coro = "get_session") @get_session_info(key = "X-Session-Token", session_coro = "get_session")
@@ -171,7 +128,7 @@ async def sync_mails(
attr_name = "logs_mongo", attr_name = "logs_mongo",
project = constants.PROJECT_NAME, project = constants.PROJECT_NAME,
log_type = constants.MODULE_NAME, log_type = constants.MODULE_NAME,
operation = "mailSyncApi", operation = "mailTagsUpdtApi",
log_input = True, log_input = True,
log_output = True, log_output = True,
sensitive_keys = ["sessionToken", "X-Session-Token"] sensitive_keys = ["sessionToken", "X-Session-Token"]
@@ -179,22 +136,19 @@ async def sync_mails(
@log_chain_to_mongo(attr_name = "logs_mongo") @log_chain_to_mongo(attr_name = "logs_mongo")
@should_not_be_under_maintenance(attr_name = "is_under_maintenance") @should_not_be_under_maintenance(attr_name = "is_under_maintenance")
@validate_input( @validate_input(
header_validator = lambda x: MailSyncRequestHeaders(**x).model_dump(), header_validator = lambda x: MailGetRequestHeaders(**x).model_dump(),
data_validator = lambda x: MailSyncRequestData(**x) data_validator = lambda x: MailGetRequestData(**x)
) )
@handle_cancelled_request() @handle_cancelled_request()
async def sync_mail( async def update_mail_tags(
mode: Literal["background", "bg"] = None, inbound_headers: dict | MailGetRequestHeaders = None,
inbound_headers: dict | MailSyncRequestHeaders = None, inbound_data: dict | MailGetRequestData = None,
inbound_data: dict | MailSyncRequestData = None,
inbound_files: dict = None, inbound_files: dict = None,
**kwargs **kwargs
): ):
""" """
Use this when the user wants to pull old mails from some mail client (like GMail) and save it to the database for Use this endpoint to update the tags on an e-mail message.
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.
:param inbound_headers: auto-extracted by the decorators. :param inbound_headers: auto-extracted by the decorators.
:param inbound_data: auto-extracted by the decorators. :param inbound_data: auto-extracted by the decorators.
:param inbound_files: 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 http_code = HttpCodes.UNAUTHORIZED
) )
# Make the variables available in the scope of the current request: # Get the mail:
g.inbound_headers = inbound_headers message = await current_app.mail_controller.get_one_mail(
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, mongo_conn = current_app.data_mongo,
llm = current_app.llm, token_id = inbound_data.tokenId,
inbound_headers = inbound_headers, message_id = inbound_data.messageId
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: # Done here:
sync_results = await 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
)
# Response:
return ResponseModel( return ResponseModel(
status_code = StatusCodes.FAILED if sync_results.failureCount > 0 else StatusCodes.OK, status_code = StatusCodes.OK if message else StatusCodes.FAILED,
http_code = HttpCodes.INTERNAL_SERVER_ERROR if sync_results.failureCount > 0 else HttpCodes.SUCCESS, http_code = HttpCodes.SUCCESS if message else HttpCodes.NOT_FOUND,
message = sync_results.message, data = message.full
data = {
"totalCount": sync_results.totalCount,
"successCount": sync_results.successCount,
"failureCount": sync_results.failureCount
}
) )
@@ -101,7 +101,12 @@ class MailGetRequestHeaders(BaseModel):
class MailGetRequestData(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", description = "the mail identifier (Mongo ObjectId) of the document that holds the mail",
frozen = True 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. :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: # Construct the basic structure:
response_dict = { response_dict = {
"status": 1 if self.status_code.value[0] else 0, "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(), "message": self.message or self.status_code.name.replace("_", " ").lower(),
"data": self.data, "data": self.data,
"apiVer": self.api_version "apiVer": self.api_version
@@ -99,9 +102,6 @@ class ResponseModel(BaseModel):
if self.seconds is not None: response_dict["seconds"] = self.seconds if self.seconds is not None: response_dict["seconds"] = self.seconds
if self.log_id is not None: response_dict["logId"] = self.log_id 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: # Done here:
return response_dict, response_http_code return response_dict, response_http_code