(20250116) Started upgrading the mail module (to eventually work on cron).

This commit is contained in:
2025-01-16 16:39:23 +05:30
parent efd7dda9f0
commit ed1e86c5e9
71 changed files with 2423 additions and 464 deletions
@@ -66,9 +66,9 @@ from shared import constants
from models.core.auth_token import CoreAuthTokenModel
from models.api.finstitutions.trading.symbols.list import (
TradingSymbolListRequestHeaders,
TradingSymbolListRequestData,
TradingSymbolListBrokerResponse
TradingSymbolListRequestData
)
from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse
# To work with dat and time:
import datetime
@@ -60,7 +60,7 @@ from utils_v2.api.async_quart import (
)
# Data Models:
from models.api.chat.auth import ChatAuthRequestHeaders, ChatAuthRequestData
from models.api.message.chat.auth import ChatAuthRequestHeaders, ChatAuthRequestData
from models.core.auth_token import CoreAuthTokenModel
# Common:
@@ -0,0 +1,275 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Thursday, 16th Jan., 2025.
OBJECTIVE:
To receive callbacks (webhooks).
REFERENCES:
N/A
DOWNLOADS:
N/A
NOTES:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# For using Quart:
from quart import Blueprint, current_app, g, request, render_template
# My utils:
from utils_v2.string import json
from utils_v2.logging.context import AsyncLoggerContext
from utils_v2.api.codes import StatusCodes, HttpCodes
from utils_v2.api.response import ResponseModel
from utils_v2.api.async_quart import (
set_api_version,
read_input,
get_session_info,
log_request_to_mongo,
log_chain_to_mongo,
should_not_be_under_maintenance,
only_whitelisted_ips,
limit_rate,
validate_input,
handle_cancelled_request
)
# GMail-related utils:
from utils_v2.goog.controllers.gmail.gmail_client import SCOPES_GMAIL_MAIL_MANAGEMENT
# Data Models:
from models.core.auth_token import CoreAuthTokenModel
from models.message.mail.oauth import OAuthMailHandleCallbackResponse
# Common:
from shared import constants
# For asynchronous activities:
import asyncio
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# Related to Quart:
mail_oauth_callback_bp = Blueprint("mail_cb", __name__)
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
@mail_oauth_callback_bp.record_once
def init(blueprint_setup_state):
# This gets called when the blueprint is registered.
# Consider this to be a one-time setup for the whole blueprint:
pass
# ---------------------------------------------------------------------------------------------------------------------
@AsyncLoggerContext.log_it(
api_version = "1.0.0",
project = constants.PROJECT_NAME,
log_type = constants.MODULE_NAME,
operation = "mailOAuthClbk",
log_input = 2,
log_output = 1,
sensitive_keys = ["sessionToken", "X-Session-Token"]
)
async def handle_mail_callback(
client_controller,
client_connector,
request_url: str,
inbound_data: dict,
) -> OAuthMailHandleCallbackResponse:
"""
This function has been kept separate only for convenience of logging.
:param client_controller: The mail controller instance.
:param client_connector: The instance of the third-party client to send to the mail controller.
:param request_url: The full request URL that came in.
:param inbound_data: The data received in the request.
:return: The client's response.
"""
return await client_controller.handle_authorization_callback(
sql_conn = current_app.sql_writer,
mongo_data_conn = current_app.data_mongo,
mail_client = client_connector,
request_url = request_url,
inbound_data = inbound_data,
session_token = None
)
# ---------------------------------------------------------------------------------------------------------------------
@mail_oauth_callback_bp.route("/callback/<mail_client>", methods = ["POST", "GET"])
@set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False)
@log_request_to_mongo(
attr_name = "logs_mongo",
project = constants.PROJECT_NAME,
log_type = constants.MODULE_NAME,
operation = "mailOAuthClbkApi",
log_input = True,
log_output = True,
sensitive_keys = None
)
@log_chain_to_mongo(attr_name = "logs_mongo")
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
@handle_cancelled_request()
async def mail_auth_callback(
mail_client: str = None,
inbound_headers: dict = None,
inbound_data: dict = None,
inbound_files: dict = None,
**kwargs
):
"""
This is the callback received when authorizing someone's mail client.
:param mail_client: The mail company/brand that you want the authorization from.
:param inbound_headers: auto-extracted by the decorators.
:param inbound_data: auto-extracted by the decorators.
:param inbound_files: auto-extracted by the decorators.
:param kwargs: Any number of extra inputs supplied by the decorators.
:return: A standard response structure.
"""
# ┳┓ ┳┳┓ •┓ ┏┓┓•
# ┣┫┏┓┓┏╋┏┓ ╋┏┓ ┃┃┃┏┓┓┃ ┃ ┃┓┏┓┏┓╋
# ┛┗┗┛┗┻┗┗ ┗┗┛ ┛ ┗┗┻┗┗ ┗┛┗┗┗ ┛┗┗
# Start by assuming failure:
client_controller = None
client_connector = None
client_response = None
exception = None
# Figure out the client connector:
match mail_client:
case "gmail": client_controller, client_connector = current_app.gmail_controller, current_app.gmail_client
case _: client_controller, client_connector = None, None
# Invoke the mail client:
if client_controller is not None and client_connector is not None:
try: client_response = await handle_mail_callback(
client_controller,
client_connector,
request_url = request.url,
inbound_data = inbound_data
)
except Exception as excp: exception = excp
# ┳┓
# ┣┫┏┓┏┏┓┏┓┏┓┏┏┓
# ┛┗┗ ┛┣┛┗┛┛┗┛┗
# ┛
# Make the mail client a label:
mail_client = {
"gmail": "Gmail",
"outlook": "Outlook"
}.get(mail_client, mail_client)
# If there was some exception:
if exception: return await render_template(
"/message/mail/oauth/oauth_failure_v2.html",
mail_client = mail_client,
failure_hint = (
f"An internal server error occurred. "
f"Please use log-id '{kwargs.get('log_id')}' to check with the support team."
)
)
# For an invalid client:
if client_controller is None or client_connector is None:
return await render_template(
"/message/mail/oauth/oauth_failure_v2.html",
mail_client = mail_client,
failure_hint = (
f"Invalid client '{mail_client}' selected. "
f"Please use log-id '{kwargs.get('log_id')}' to check with the support team."
)
)
# For a valid client whose authorization was denied/cancelled:
if client_response.action in ["denied", "cancelled"]:
return await render_template(
"/message/mail/oauth/oauth_cancelled_v2.html",
mail_client = mail_client
)
# For successful authorization:
if client_response.success:
return await render_template(
"/message/mail/oauth/oauth_success_v2.html",
mail_client = mail_client
)
# For failed authorization:
return await render_template(
"/message/mail/oauth/oauth_failure_v2.html",
mail_client = mail_client.title(),
failure_hint = f"Unknown error. Please use log-id '{kwargs.get('log_id')}' to check with the support team."
)
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
@@ -67,7 +67,7 @@ from utils_v2.goog.controllers.gmail.gmail_client import SCOPES_GMAIL_MAIL_MANAG
from shared import constants
# Data Models:
from models.api.mail.oauth import (
from models.api.message.mail.oauth import (
OAuthMailAuthorizationRequestHeaders,
OAuthMailAuthorizationRequestData
)
@@ -0,0 +1,229 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Thursday, 16th jan., 2025.
OBJECTIVE:
To receive authorization requests (OAuth2.0) for various mail providers and accordingly respond with the
authorization request URLs.
REFERENCES:
N/A
DOWNLOADS:
N/A
NOTES:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# For using Quart:
from quart import Blueprint, current_app, request
# My utils:
from utils_v2.string import json
from utils_v2.api.codes import StatusCodes, HttpCodes
from utils_v2.api.response import ResponseModel
from utils_v2.api.async_quart import (
set_api_version,
read_input,
get_session_info,
log_request_to_mongo,
log_chain_to_mongo,
should_not_be_under_maintenance,
only_whitelisted_ips,
limit_rate,
validate_input,
handle_cancelled_request
)
# GMail-related utils:
from utils_v2.goog.controllers.gmail.gmail_client import SCOPES_GMAIL_MAIL_MANAGEMENT
# Common:
from shared import constants
# Data Models:
from models.core.user import CoreUserInfoModel
from models.api.message.mail.oauth import (
OAuthMailAuthorizationRequestHeaders,
OAuthMailAuthorizationRequestData
)
from models.core.auth_token import CoreAuthTokenModel
# For asynchronous activities:
import asyncio
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# Related to Quart:
mail_oauth_request_bp = Blueprint("mail_oauth", __name__)
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
@mail_oauth_request_bp.record_once
def init(blueprint_setup_state):
# This gets called when the blueprint is registered.
# Consider this to be a one-time setup for the whole blueprint:
pass
# ---------------------------------------------------------------------------------------------------------------------
@mail_oauth_request_bp.route("/oauth", 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")
@log_request_to_mongo(
attr_name = "logs_mongo",
project = constants.PROJECT_NAME,
log_type = constants.MODULE_NAME,
operation = "mailOAuthUrlReqApi",
log_input = True,
log_output = True,
sensitive_keys = ["sessionToken", "X-Session-Token"]
)
@log_chain_to_mongo(attr_name = "logs_mongo")
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
@validate_input(
header_validator = lambda x: OAuthMailAuthorizationRequestHeaders(**x).model_dump(),
data_validator = lambda x: OAuthMailAuthorizationRequestData(**x)
)
@handle_cancelled_request()
async def request_oauth_authorization_url(
inbound_headers: dict | OAuthMailAuthorizationRequestHeaders = None,
inbound_data: dict | OAuthMailAuthorizationRequestData = None,
inbound_files: dict = None,
**kwargs
):
"""
Use this when requesting access to someone's GMail account. This API should be used from the UI. A button click
"Connect to GMail" should hit this API, which will generate a request to gain access to the user's GMail account.
When the URL is hit, it opens Google's own UI, and, when the user clicks "Continue", Google hits your 'redirect_url'
to inform you about the user's action.
:param inbound_headers: auto-extracted by the decorators.
:param inbound_data: auto-extracted by the decorators.
:param inbound_files: auto-extracted by the decorators.
:param kwargs: Any number of extra inputs supplied by the decorators.
:return: A standard response structure.
"""
# ┏┓
# ┃┃┏┓┏┓┏┓┏┓┏┓┏┏┓┏┏
# ┣┛┛ ┗ ┣┛┛ ┗┛┗┗ ┛┛
# ┛
# If the session token is invalid/expired:
if kwargs.get("session_info") is None:
return ResponseModel(
status_code = StatusCodes.FAILED,
http_code = HttpCodes.UNAUTHORIZED
)
# ┳┓ ┳┳┓ •┓ ┏┓┓•
# ┣┫┏┓┓┏╋┏┓ ╋┏┓ ┃┃┃┏┓┓┃ ┃ ┃┓┏┓┏┓╋
# ┛┗┗┛┗┻┗┗ ┗┗┛ ┛ ┗┗┻┗┗ ┗┛┗┗┗ ┛┗┗
# Start by assuming failure:
client_controller = None
client_connector = None
client_response = None
# Figure out the client connector:
match inbound_data.mailClient:
case "gmail": client_controller, client_connector = current_app.gmail_controller, current_app.gmail_client
case _: client_controller, client_connector = None, None
# If a client connector was matched:
if client_controller is not None and client_connector is not None:
client_response = await client_controller.get_authorization_url(
sql_conn = current_app.sql_writer,
mongo_data_conn = current_app.data_mongo,
mail_client = client_connector,
user_info = CoreUserInfoModel(**kwargs["session_info"]),
inbound_data = inbound_data,
session_token = inbound_headers["X-Session-Token"]
)
# ┳┓
# ┣┫┏┓┏┏┓┏┓┏┓┏┏┓
# ┛┗┗ ┛┣┛┗┛┛┗┛┗
# ┛
# Unknown client:
if client_controller is None or client_connector is None: return ResponseModel(
status_code = StatusCodes.FAILED,
http_code = HttpCodes.BAD_REQUEST,
message = f"Unknown/unimplemented client '{inbound_data.mailClient}'."
)
# Known client (could be a success or a failure):
return ResponseModel(
status_code = StatusCodes.OK if client_response.success else StatusCodes.FAILED,
http_code = HttpCodes.SUCCESS if client_response.success else HttpCodes.INTERNAL_SERVER_ERROR,
message = client_response.message,
data = {
"client": inbound_data.mailClient,
"authorizationUrl": client_response.url
} if client_response.success else None
)
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
@@ -69,7 +69,7 @@ from utils_v2.goog.models.auth_tokens import GoogleAuthTokens
from shared import constants
# Data Models:
from models.api.mail.get import MailGetRequestHeaders, MailGetRequestData
from models.api.message.mail.get import MailGetRequestHeaders, MailGetRequestData
from models.core.user import CoreUserInfoModel
# To work with datatypes:
@@ -70,7 +70,7 @@ from utils_v2.goog.models.auth_tokens import GoogleAuthTokens
from shared import constants
# Data Models:
from models.api.mail.list import MailListRequestHeaders, MailListRequestData
from models.api.message.mail.list import MailListRequestHeaders, MailListRequestData
# To work with datatypes:
from typing import Literal
@@ -69,7 +69,7 @@ from utils_v2.goog.models.auth_tokens import GoogleAuthTokens
from shared import constants
# Data Models:
from models.api.mail.send import MailSendRequestHeaders, MailSendRequestData
from models.api.message.mail.send import MailSendRequestHeaders, MailSendRequestData
from models.core.user import CoreUserInfoModel
# To work with datatypes:
@@ -71,8 +71,8 @@ from shared import constants
# Data Models:
from models.core.user import CoreUserInfoModel
from models.api.mail.sync import MailSyncRequestHeaders, MailSyncRequestData
from models.api.mail.sync import MailSyncOneResult, MailSyncManyResults
from models.api.message.mail.sync import MailSyncRequestHeaders, MailSyncRequestData
from models.message.mail.sync import MailSyncOneResult, MailSyncManyResults
# To work with datatypes:
from typing import Literal
@@ -69,7 +69,7 @@ from utils_v2.goog.models.auth_tokens import GoogleAuthTokens
from shared import constants
# Data Models:
from models.api.mail.tags import MailUpdateTagsRequestHeaders, MailUpdateTagsRequestData
from models.api.message.mail.tags import MailUpdateTagsRequestHeaders, MailUpdateTagsRequestData
from models.core.user import CoreUserInfoModel
# To work with datatypes:
@@ -63,7 +63,7 @@ from utils_v2.api.async_quart import (
from shared import constants
# Data Models:
from models.api.sms.auth import SMSAuthRequestHeaders, SMSAuthRequestData
from models.api.message.sms.auth import SMSAuthRequestHeaders, SMSAuthRequestData
from models.core.auth_token import CoreAuthTokenModel
# For asynchronous activities:
@@ -64,7 +64,7 @@ from shared import constants
# Data Models:
from models.core.user import CoreUserInfoModel
from models.api.sms.list import SMSListRequestHeaders, SMSListRequestData
from models.api.message.sms.list import SMSListRequestHeaders, SMSListRequestData
# Helpers:
from api.helpers.user import token_check
@@ -62,8 +62,8 @@ from utils_v2.api.async_quart import (
# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.api.sms.send import SMSSendRequestHeaders, SMSSendRequestData
from models.api.sms.send import (
from models.api.message.sms.send import SMSSendRequestHeaders, SMSSendRequestData
from models.message.sms.send import (
NimbusSMSIndiaMessage,
SavvyBulkSMSKenyaMessage,
SMSSendManyResults
@@ -64,7 +64,7 @@ from shared import constants
# Data Models:
from models.core.user import CoreUserInfoModel
from models.api.sms.tags import SMSUpdateTagsRequestHeaders, SMSUpdateTagsRequestData
from models.api.message.sms.tags import SMSUpdateTagsRequestHeaders, SMSUpdateTagsRequestData
# Helpers:
from api.helpers.user import token_check
+39 -29
View File
@@ -80,6 +80,8 @@ from controllers_v2.message.sms.all_sms import AllSMSController
from controllers_v2.message.sms.nimbus_sms_india import NimbusSMSIndiaController
from controllers_v2.message.sms.savvy_bulk_sms_kenya import SavvyBulkSMSKenyaController
# ---
from controllers_v2.message.mail.gmail import GmailController
# ---
from controllers_v2.message.chat.all_chat import AllChatController
from controllers_v2.message.chat.whatsapp_nimbus import WhatsAppNimbusController
# ---
@@ -98,23 +100,23 @@ import httpx
from icecream import IceCreamDebugger
# Mail Blueprints:
from api.blueprints.mail.oauth.request import mail_oauth_request_bp
from api.blueprints.mail.oauth.callback import mail_oauth_callback_bp
from api.blueprints.mail.sync.sync_v2 import mail_sync_bp
from api.blueprints.mail.retrieve.list import mail_list_bp
from api.blueprints.mail.retrieve.get import mail_get_bp
from api.blueprints.mail.tags.update import mail_tags_update_bp
from api.blueprints.mail.send.send import mail_send_bp
from api.blueprints.message.mail.oauth.request_v2 import mail_oauth_request_bp
from api.blueprints.message.mail.oauth.callback_v2 import mail_oauth_callback_bp
from api.blueprints.message.mail.sync.sync_v2 import mail_sync_bp
from api.blueprints.message.mail.retrieve.list import mail_list_bp
from api.blueprints.message.mail.retrieve.get import mail_get_bp
from api.blueprints.message.mail.tags.update import mail_tags_update_bp
from api.blueprints.message.mail.send.send import mail_send_bp
# SMS Blueprints:
from api.blueprints.sms.auth_v2 import sms_auth_bp
from api.blueprints.sms.send_v2 import sms_send_bp
from api.blueprints.sms.list import sms_list_bp
from api.blueprints.sms.tags import sms_update_tags_bp
from api.blueprints.message.sms.auth_v2 import sms_auth_bp
from api.blueprints.message.sms.send_v2 import sms_send_bp
from api.blueprints.message.sms.list import sms_list_bp
from api.blueprints.message.sms.tags import sms_update_tags_bp
# Chat Blueprints:
from api.blueprints.chat.auth import chat_auth_bp
# from api.blueprints.chat.webhook import chat_webhook_bp
from api.blueprints.message.chat.auth import chat_auth_bp
# from api.blueprints.message.chat.webhook import chat_webhook_bp
# Software Blueprints:
from api.blueprints.software.auth import sw_auth_bp
@@ -393,22 +395,22 @@ async def app_startup(**kwargs):
# ┃ ┏┓┏┓┏┓ ┃┃┃┏┓┏┫┏┓┃┏
# ┗┛┗┛┛ ┗ ┛ ┗┗┛┗┻┗ ┗┛
current_app.core_auth_token_controller = CoreAuthTokenController(
cache = current_app.module_cache,
alert_url = current_app.script_data["alerts"]["url"],
http_client = current_app.http_client,
debug = enable_debugging,
debug_prefix = "AuthToken (CM) | ",
debug_only_errors = True
)
current_app.core_message_controller = CoreMessageController(
cache = current_app.module_cache,
alert_url = current_app.script_data["alerts"]["url"],
http_client = current_app.http_client,
debug = enable_debugging,
debug_prefix = "Message (CM) | ",
debug_only_errors = True
)
# current_app.core_auth_token_controller = CoreAuthTokenController(
# cache = current_app.module_cache,
# alert_url = current_app.script_data["alerts"]["url"],
# http_client = current_app.http_client,
# debug = enable_debugging,
# debug_prefix = "AuthToken (CM) | ",
# debug_only_errors = True
# )
# current_app.core_message_controller = CoreMessageController(
# cache = current_app.module_cache,
# alert_url = current_app.script_data["alerts"]["url"],
# http_client = current_app.http_client,
# debug = enable_debugging,
# debug_prefix = "Message (CM) | ",
# debug_only_errors = True
# )
# current_app.core_payment_controller = CorePaymentController(
# cache = current_app.module_cache,
# alert_url = current_app.script_data["alerts"]["url"],
@@ -458,6 +460,14 @@ async def app_startup(**kwargs):
debug = enable_debugging
)
# Messages / Mail Controllers:
current_app.gmail_controller = GmailController(
cache = current_app.module_cache,
http_client = current_app.http_client,
alert_url = current_app.script_data["alerts"]["url"],
debug = enable_debugging
)
# Messages / Chat Controllers:
current_app.chat_controller = AllChatController(
cache = current_app.module_cache,
+3 -2
View File
@@ -50,8 +50,9 @@ from controllers.base import BaseModel
from models.core.user import CoreUserInfoModel
from models.core.auth_token import CoreAuthTokenModel
from models.core.message import CoreMessageModel
from models.api.mail.sync import MailSyncOneResult, MailSyncManyResults, MailSendOneResult
from models.api.mail.send import MailSendRequestData
from models.message.mail.sync import MailSyncOneResult, MailSyncManyResults
from models.message.mail.send import MailSendOneResult
from models.api.message.mail.send import MailSendRequestData
# Mail Clients:
from utils_v2.goog.controllers.gmail.gmail_client import AsyncGMailClient
@@ -45,7 +45,8 @@ from controllers_v2.finstitutions.trading.base import TradingController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData, TradingSymbolListBrokerResponse
from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData
from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse
from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse
# To work with datatypes:
+2 -1
View File
@@ -45,7 +45,8 @@ from controllers_v2.core.auth_token import CoreAuthTokenController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData, TradingSymbolListBrokerResponse
from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData
from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse
from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse
# To work with datatypes:
@@ -47,11 +47,8 @@ from controllers_v2.finstitutions.trading.base import TradingController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from utils_v2.trading.icici_breeze.models.auth_tokens import ICICIBreezeAuthTokens
from models.api.finstitutions.trading.symbols.list import (
TradingSymbolListRequestData,
TradingSymbolListBrokerResponse,
TradingSymbol
)
from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData
from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse, TradingSymbol
from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse
# To work with MongoDB:
@@ -47,11 +47,8 @@ from controllers_v2.finstitutions.trading.base import TradingController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from utils_v2.trading.zerodha_kite.models.auth_tokens import ZerodhaKiteAuthTokens
from models.api.finstitutions.trading.symbols.list import (
TradingSymbolListRequestData,
TradingSymbolListBrokerResponse,
TradingSymbol
)
from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData
from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse, TradingSymbol
from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse
# To work with MongoDB:
@@ -47,11 +47,8 @@ from controllers_v2.finstitutions.trading.base import TradingController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from utils_v2.trading.zerodha_kite.models.auth_tokens import ZerodhaKiteAuthTokens
from models.api.finstitutions.trading.symbols.list import (
TradingSymbolListRequestData,
TradingSymbolListBrokerResponse,
TradingSymbol
)
from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData
from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse, TradingSymbol
from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse
# To work with MongoDB:
+1 -1
View File
@@ -44,7 +44,7 @@ from controllers_v2.core.message import CoreMessageController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.api.sms.send import (
from models.api.message.sms.send import (
NimbusSMSIndiaMessage,
SavvyBulkSMSKenyaMessage,
SMSSendOneResult,
+201
View File
@@ -0,0 +1,201 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Thursday, 16th Jan., 2025.
OBJECTIVE:
To handle all mail-related behaviour for Gmail from one place.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# My async utils:
from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
# Controllers:
from controllers_v2.message.mail.base import MailController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.api.sms.send import (
NimbusSMSIndiaMessage,
SavvyBulkSMSKenyaMessage,
SMSSendOneResult,
SMSSendManyResults
)
# SMS clients:
from utils_v2.sms.india.nimbus.controllers.async_nimbus import AsyncNimbusSMS
from utils_v2.sms.kenya.savvy_bulk_sms.controllers.async_savvy_bulk_sms import AsyncSavvyBulkSMS
# To work with datatypes:
from typing import List, Any
# To make HTTP requests:
import httpx
# To make abstract classes:
from abc import ABC, abstractmethod
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** CLASSES ***
# ***** ****
# *****************************************************************************************************************
class AllMailController(MailController):
# ┏┓
# ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓
# ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛
def __init__(
self,
cache: AsyncRedisCache = None,
http_client: httpx.AsyncClient = None,
alert_url: str = None,
base_filter: dict = None,
debug: bool = True,
debug_prefix: str = "Mail (C) | ",
debug_only_errors: bool = True
):
"""
This is the foundational controller for all mail services. This is built on top of the core message controller,
and, in turn, all individual mail client controllers must be built on top of this.
:param cache: The object to use for caching results from database calls.
:param http_client: The HTTP client
:param base_filter: The basic filter that will be applied to all fetching/updating queries. WARNING: THE BASE
FILTER WILL ALWAYS BE APPLIED AUTOMATICALLY. SET THIS UP WISELY.
:param debug: Whether, or not, you would like to print debugging messages:
:param debug_prefix: The prefix to print with the debugging messages.
:param debug_only_errors: Whether you would like to print only error messages or all messages.
:return: None.
"""
# Prepare the combined base filter:
sms_filter = {}
for k, v in (base_filter or {}).items(): sms_filter[k] = v
sms_filter["serviceType"] = "sms"
# Invoke the parent's constructor:
CoreMessageController.__init__(
self,
cache = cache,
alert_url = alert_url,
http_client = http_client,
base_filter = sms_filter,
debug = debug,
debug_prefix = debug_prefix,
debug_only_errors = debug_only_errors
)
# ┏┓┏┓ ┓ ┏┓ ┏┓
# ┃┃┣┫┓┏╋┣┓┏┛ ┃┫
# ┗┛┛┗┗┻┗┛┗┗━•┗┛
pass
# ┳┳┓ •┓ ┏┓ • •
# ┃┃┃┏┓┓┃ ┗┓┓┏┏┳┓┏┳┓┏┓┏┓┓┓┏┓╋┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗┻┛┗┗┛┗┗┗┻┛ ┗┗┗┻┗┗┗┛┛┗
pass
# ┳┳┓ •┓ ┏┓ ╹•
# ┃┃┃┏┓┓┃ ┗┓┓┏┏┓┏ ┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗┫┛┗┗ ┗┛┗┗┫
# ┛ ┛
# To synchronize the mails on the third-party client's server and your server. You are effectively making a copy of
# the mail on your database.
pass
# ┳┳┓ •┓ ┓ • •
# ┃┃┃┏┓┓┃ ┃ ┓┏╋┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗┛┗┗┛┗┗┫
# ┛
# Use these to show your users their mails once the mails are on your server. This would include activities like
# listing mails, showing full mails, showing mail trails, etc.
pass
# ┳┳┓ •┓ ┏┓ ┓•
# ┃┃┃┏┓┓┃ ┗┓┏┓┏┓┏┫┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗ ┛┗┗┻┗┛┗┗┫
# ┛
pass
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
+224
View File
@@ -0,0 +1,224 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Thursday, 16th Jan., 2025.
OBJECTIVE:
To handle all mail-related behaviour from one place. The initially known client is only Gmail.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# My async utils:
from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.database.async_mysql_v2 import AsyncMySQL
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
# Controllers:
from controllers_v2.core.message import CoreMessageController
# Models:
from models.core.user import CoreUserInfoModel
from models.core.auth_token import CoreAuthTokenModel
from models.api.message.mail.oauth import (
OAuthMailAuthorizationRequestHeaders,
OAuthMailAuthorizationRequestData
)
from models.message.mail.oauth import OAuthMailGetAuthorizationURLResponse
# Mail Client(s):
from utils_v2.goog.controllers.gmail.gmail_client import AsyncGMailClient
# To work with datatypes:
from typing import List, Any
# To make HTTP requests:
import httpx
# To make abstract classes:
from abc import ABC, abstractmethod
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** CLASSES ***
# ***** ****
# *****************************************************************************************************************
class MailController(CoreMessageController, ABC):
# ┏┓
# ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓
# ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛
def __init__(
self,
cache: AsyncRedisCache = None,
http_client: httpx.AsyncClient = None,
alert_url: str = None,
base_filter: dict = None,
debug: bool = True,
debug_prefix: str = "Mail (C) | ",
debug_only_errors: bool = True
):
"""
This is the foundational controller for all mail services. This is built on top of the core message controller,
and, in turn, all individual mail client controllers must be built on top of this.
:param cache: The object to use for caching results from database calls.
:param http_client: The HTTP client
:param base_filter: The basic filter that will be applied to all fetching/updating queries. WARNING: THE BASE
FILTER WILL ALWAYS BE APPLIED AUTOMATICALLY. SET THIS UP WISELY.
:param debug: Whether, or not, you would like to print debugging messages:
:param debug_prefix: The prefix to print with the debugging messages.
:param debug_only_errors: Whether you would like to print only error messages or all messages.
:return: None.
"""
# Prepare the combined base filter:
sms_filter = {}
for k, v in (base_filter or {}).items(): sms_filter[k] = v
sms_filter["serviceType"] = "sms"
# Invoke the parent's constructor:
CoreMessageController.__init__(
self,
cache = cache,
alert_url = alert_url,
http_client = http_client,
base_filter = sms_filter,
debug = debug,
debug_prefix = debug_prefix,
debug_only_errors = debug_only_errors
)
# ┏┓┏┓ ┓ ┏┓ ┏┓
# ┃┃┣┫┓┏╋┣┓┏┛ ┃┫
# ┗┛┛┗┗┻┗┛┗┗━•┗┛
@abstractmethod
async def get_authorization_url(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
mail_client: AsyncGMailClient,
user_info: CoreUserInfoModel,
inbound_data: OAuthMailAuthorizationRequestData,
session_token: str
) -> OAuthMailGetAuthorizationURLResponse:
"""
To accept an incoming request for mail integration and provide a URL that the user can use to authorize your
service to access his mail inbox.
:param sql_conn: The database connection to use to perform this task.
:param mongo_data_conn: The database connection to use to perform this task.
:param mail_client: The instance of the third-party mail client that will be used to get the URL.
:param user_info: The information about your user who is trying to use this system.
:param inbound_data: The data that came in with the request (API call).
:param session_token: The session token of the user.
:return: A structure response with details about the URL generation process.
"""
pass
# ┳┳┓ •┓ ┏┓ • •
# ┃┃┃┏┓┓┃ ┗┓┓┏┏┳┓┏┳┓┏┓┏┓┓┓┏┓╋┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗┻┛┗┗┛┗┗┗┻┛ ┗┗┗┻┗┗┗┛┛┗
pass
# ┳┳┓ •┓ ┏┓ ╹•
# ┃┃┃┏┓┓┃ ┗┓┓┏┏┓┏ ┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗┫┛┗┗ ┗┛┗┗┫
# ┛ ┛
# To synchronize the mails on the third-party client's server and your server. You are effectively making a copy of
# the mail on your database.
pass
# ┳┳┓ •┓ ┓ • •
# ┃┃┃┏┓┓┃ ┃ ┓┏╋┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗┛┗┗┛┗┗┫
# ┛
# Use these to show your users their mails once the mails are on your server. This would include activities like
# listing mails, showing full mails, showing mail trails, etc.
pass
# ┳┳┓ •┓ ┏┓ ┓•
# ┃┃┃┏┓┓┃ ┗┓┏┓┏┓┏┫┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗ ┛┗┗┻┗┛┗┗┫
# ┛
pass
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
+385
View File
@@ -0,0 +1,385 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Thursday, 16th Jan., 2025.
OBJECTIVE:
To handle all mail-related behaviour for Gmail from one place.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# My async utils:
from utils_v2.date_time import date_time
from utils_v2.database.async_mysql_v2 import AsyncMySQL
from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
# Controllers:
from controllers_v2.message.mail.base import MailController
# Models:
from models.core.user import CoreUserInfoModel
from models.core.auth_token import CoreAuthTokenModel
from models.core.message import CoreMessageModel
from models.api.message.mail.oauth import (
OAuthMailAuthorizationRequestHeaders,
OAuthMailAuthorizationRequestData
)
from models.message.mail.oauth import OAuthMailGetAuthorizationURLResponse, OAuthMailHandleCallbackResponse
# Mail Client(s):
from utils_v2.goog.controllers.gmail.gmail_client import AsyncGMailClient, SCOPES_GMAIL_MAIL_MANAGEMENT
# To work with datatypes:
from typing import List, Any
# To make HTTP requests:
import httpx
# For asynchronous activities:
import asyncio
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** CLASSES ***
# ***** ****
# *****************************************************************************************************************
class GmailController(MailController):
# ┏┓
# ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓
# ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛
def __init__(
self,
cache: AsyncRedisCache = None,
http_client: httpx.AsyncClient = None,
alert_url: str = None,
debug: bool = True,
debug_prefix: str = "Gmail (C) | ",
debug_only_errors: bool = True
):
"""
This is the controller specifically built for Gmail's services. It is built on top of the base mail controller.
:param cache: The object to use for caching results from database calls.
:param http_client: The HTTP client
:param debug: Whether, or not, you would like to print debugging messages:
:param debug_prefix: The prefix to print with the debugging messages.
:param debug_only_errors: Whether you would like to print only error messages or all messages.
:return: None.
"""
# Invoke the parent's constructor:
super().__init__(
cache = cache,
alert_url = alert_url,
http_client = http_client,
base_filter = {"client": "nimbusSmsIndia"},
debug = debug,
debug_prefix = debug_prefix,
debug_only_errors = debug_only_errors
)
# ┏┓┏┓ ┓ ┏┓ ┏┓
# ┃┃┣┫┓┏╋┣┓┏┛ ┃┫
# ┗┛┛┗┗┻┗┛┗┗━•┗┛
async def get_authorization_url(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
mail_client: AsyncGMailClient,
user_info: CoreUserInfoModel,
inbound_data: OAuthMailAuthorizationRequestData,
session_token: str
) -> OAuthMailGetAuthorizationURLResponse:
"""
To accept an incoming request for mail integration and provide a URL that the user can use to authorize your
service to access his mail inbox.
:param sql_conn: The database connection to use to perform this task.
:param mongo_data_conn: The database connection to use to perform this task.
:param mail_client: The instance of the third-party mail client that will be used to get the URL.
:param user_info: The information about your user who is trying to use this system.
:param inbound_data: The data that came in with the request (API call).
:param session_token: The session token of the user.
:return: A structure response with details about the URL generation process.
"""
# Start by assuming failure:
response = OAuthMailGetAuthorizationURLResponse()
# First, we create/update a record for this integration request:
token_key = await self.generate_token_key(
sql_conn = sql_conn,
mongo_data_conn = mongo_data_conn,
auth_token = CoreAuthTokenModel(
serviceType = "email",
client = inbound_data.mailClient,
authType = "oauth",
user = user_info,
clientUserId = {"email": inbound_data.mailId},
status = "pending",
syncFreq = inbound_data.syncFreq,
),
token_notes = {
"email": inbound_data.mailId,
"client": inbound_data.mailClient
},
display_name = inbound_data.mailId,
display_picture = None,
session_token = session_token
)
# If generating the token key fails:
if token_key is None:
response.message = "Failed to generate token key."
return response
# Now we create the URL:
response.url = await mail_client.get_authorization_url(
scopes = SCOPES_GMAIL_MAIL_MANAGEMENT,
state = str(token_key),
access_type = "offline",
approval_prompt = "force",
include_granted_scopes = "true",
user_email = inbound_data.mailId
)
response.success = True
response.message = "Please use the URL to integrate your Gmail account."
# Done here:
return response
async def handle_authorization_callback(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
mail_client: AsyncGMailClient,
request_url: str,
inbound_data: dict,
session_token: str = None
) -> OAuthMailHandleCallbackResponse:
# Start by assuming failure:
response = OAuthMailHandleCallbackResponse()
# In case the user denied access:
if inbound_data.get("error") == "access_denied":
response.action = "denied"
response.message = "The user denied authorization."
return response
# Otherwise we know that the user authorized access:
else:
response.action = "authorized"
response.message = "The user has given authorization."
# Generate the tokens from the callback. Google sends all the needed params in the callback as the URL's query
# params. We can simply use the exact URL that was hit to generate the tokens. In Quart (and Flask) this can be
# achieved by 'request.url' like this:
google_tokens = await mail_client.get_authorization_tokens(
redirect_url = request_url,
scopes = None
)
# If no tokens were generated:
if not google_tokens:
response.message = "Failed to get access token(s) from Gmail."
return response
# Try getting the user's profile from Gmail:
user_profile = await mail_client.get_user_profile(tokens = google_tokens)
if user_profile.success:
google_tokens.email = user_profile.data["emailAddress"]
google_tokens.displayName = user_profile.data["displayName"]
google_tokens.displayPictureUrl = user_profile.data["displayPictureUrl"]
else:
response.message = "Failed to get the user's profile from Gmail."
return response
# Now we check if the email that the user originally claimed to authorize is the same as the one that gave the
# authorization. We must fetch the auth-token for that:
auth_token = await self.get_token_from_key(
mongo_data_conn = mongo_data_conn,
token_key = inbound_data["state"]
)
if not auth_token:
response.message = "Failed to load the auth-token for this flow."
return response
# If the two email ids don't match:
if auth_token.clientUserId["email"] != str(google_tokens.email):
response.message = (
f"We were expecting authorization from '{auth_token.clientUserId['email']}', "
f"but got authorization from '{google_tokens.email}' instead."
)
return response
# Now we try to create the standard set of Labels:
labels = [
{
"name": "TCAOFF",
"textColor": "#434343",
"backgroundColor": "#e7e7e7"
},
{
"name": "CA-Doc",
"textColor": "#434343",
"backgroundColor": "#e7e7e7"
},
{
"name": "CA-AI",
"textColor": "#434343",
"backgroundColor": "#e7e7e7"
}
]
tasks = [
mail_client.create_label(
tokens = google_tokens,
label_name = label["name"],
label_visibility = "labelShow",
message_visibility = "show",
label_text_color = label["textColor"],
label_background_color = label["backgroundColor"]
) for label in labels
]
client_responses = await asyncio.gather(*tasks)
# Add the labels to the tokens data:
client_response = await mail_client.list_labels(tokens = google_tokens)
google_tokens.labels = client_response.data if client_response.success else None
# Now that we have passed the check,
# we save the tokens to the database:
auth_token.clientUserId = google_tokens.client_user_id
auth_token.token = google_tokens.model_dump()
auth_token.status = "active"
tokens_saved = await self.set_token(
sql_conn = sql_conn,
mongo_data_conn = mongo_data_conn,
token_key = inbound_data["state"],
auth_token = auth_token,
token_notes = {
"email": google_tokens.email,
"client": auth_token.client
},
display_name = google_tokens.displayName,
display_picture = google_tokens.displayPictureUrl,
session_token = session_token
)
# Note down the final result:
if tokens_saved:
response.success = True
response.message = "Authorization flow completed successfully."
else: response.message = "Failed to save the token(s)."
# Done here:
return response
# ┳┳┓ •┓ ┏┓ • •
# ┃┃┃┏┓┓┃ ┗┓┓┏┏┳┓┏┳┓┏┓┏┓┓┓┏┓╋┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗┻┛┗┗┛┗┗┗┻┛ ┗┗┗┻┗┗┗┛┛┗
pass
# ┳┳┓ •┓ ┏┓ ╹•
# ┃┃┃┏┓┓┃ ┗┓┓┏┏┓┏ ┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗┫┛┗┗ ┗┛┗┗┫
# ┛ ┛
# To synchronize the mails on the third-party client's server and your server. You are effectively making a copy of
# the mail on your database.
pass
# ┳┳┓ •┓ ┓ • •
# ┃┃┃┏┓┓┃ ┃ ┓┏╋┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗┛┗┗┛┗┗┫
# ┛
# Use these to show your users their mails once the mails are on your server. This would include activities like
# listing mails, showing full mails, showing mail trails, etc.
pass
# ┳┳┓ •┓ ┏┓ ┓•
# ┃┃┃┏┓┓┃ ┗┓┏┓┏┓┏┫┓┏┓┏┓
# ┛ ┗┗┻┗┗ ┗┛┗ ┛┗┗┻┗┛┗┗┫
# ┛
pass
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
+1 -1
View File
@@ -46,7 +46,7 @@ from controllers_v2.message.sms.base import SMSController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.core.message import CoreMessageModel
from models.api.sms.send import (
from models.api.message.sms.send import (
NimbusSMSIndiaMessage,
SMSSendOneResult,
SMSSendManyResults
+1 -1
View File
@@ -44,7 +44,7 @@ from controllers_v2.core.message import CoreMessageController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.api.sms.send import (
from models.api.message.sms.send import (
NimbusSMSIndiaMessage,
SavvyBulkSMSKenyaMessage,
SMSSendOneResult,
@@ -47,7 +47,7 @@ from controllers_v2.message.sms.base import SMSController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.core.message import CoreMessageModel
from models.api.sms.send import (
from models.api.message.sms.send import (
NimbusSMSIndiaMessage,
SMSSendOneResult,
SMSSendManyResults
@@ -47,7 +47,7 @@ from controllers_v2.message.sms.base import SMSController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.core.message import CoreMessageModel
from models.api.sms.send import (
from models.api.message.sms.send import (
SavvyBulkSMSKenyaMessage,
SMSSendOneResult,
SMSSendManyResults
+7 -98
View File
@@ -39,6 +39,13 @@ sys.path.append("..")
from pydantic import BaseModel, Field, field_validator, PastDatetime, model_validator
from typing import Optional, Literal, Union
# Models:
from models.finstitutions.trading.oauth import (
PaperTradingAuth,
ZerodhaKiteAuth,
ICICIBreezeAuth
)
# My utils:
from utils_v2.string import regex
from utils_v2.date_time import date_time
@@ -75,104 +82,6 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]
# *****************************************************************************************************************
class PaperTradingAuth(BaseModel):
username: str = Field(
description = "??",
frozen = True
)
password: str = Field(
description = "??",
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class ZerodhaKiteAuth(BaseModel):
userId: str = Field(
description = "How Zerodha's Kite platform identifies this user.",
frozen = True,
alias = "clientId"
)
apiKey: str = Field(
description = (
"The API key of your Kite app. "
"This remains constant throughout the life of the app."
),
frozen = True
)
apiSecret: str = Field(
description = (
"The API secret of your Kite app. "
"This can be changed if you think the security of your app has been compromised."
),
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
populate_by_name = True
# ---------------------------------------------------------------------------------------------------------------------
class ICICIBreezeAuth(BaseModel):
userId: str = Field(
description = "How ICICI's Breeze platform identifies this user.",
frozen = True,
alias = "clientId"
)
apiKey: str = Field(
description = (
"The API key of your Breeze app. "
"This remains constant throughout the life of the app."
),
frozen = True
)
apiSecret: str = Field(
description = (
"The API secret of your Breeze app. "
"This can be changed if you think the security of your app has been compromised."
),
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
populate_by_name = True
# ---------------------------------------------------------------------------------------------------------------------
class TradingAuthRequestHeaders(BaseModel):
sessionToken: str = Field(
@@ -79,45 +79,6 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]
# *****************************************************************************************************************
class TradingSymbolListBrokerResponse(BaseModel):
success: bool = Field(
description = "whether, or not, the symbol list request was successful",
default = False,
frozen = False
)
message: str = Field(
description = "a brief message to help debug in failed cases",
default = "",
frozen = False
)
data: List[TradingSymbol] | None = Field(
description = "the actual response from the broker with his list of tradeable symbols",
default = None,
frozen = False
)
exception: Exception | None = Field(
description = "if something goes wrong, the exception will be held here",
default = None,
frozen = False
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
arbitrary_types_allowed = True
# ---------------------------------------------------------------------------------------------------------------------
class TradingSymbolListRequestHeaders(BaseModel):
sessionToken: str = Field(
View File
@@ -39,6 +39,9 @@ sys.path.append("..")
from pydantic import BaseModel, Field, field_validator, PastDatetime, model_validator
from typing import Optional, Literal, Union
# Models:
from models.message.chat.auth import TelegramAuth, WhatsAppNimbusAuth
# My utils:
from utils_v2.string import regex
from utils_v2.date_time import date_time
@@ -75,52 +78,6 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]
# *****************************************************************************************************************
class TelegramAuth(BaseModel):
botToken: str = Field(
description = "the token granted by BotFather",
min_length = 1,
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class WhatsAppNimbusAuth(BaseModel):
apiKey: str = Field(
description = "???",
min_length = 1,
frozen = True
)
senderId: str = Field(
description = "???",
min_length = 1,
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class ChatAuthRequestHeaders(BaseModel):
sessionToken: str = Field(
View File
@@ -156,98 +156,6 @@ class MailSyncRequestData(BaseModel):
return value
# ---------------------------------------------------------------------------------------------------------------------
class MailSyncOneResult(BaseModel):
success: bool = Field(
description = "whether, or not, the mail was successfully sync'd",
default = False
)
message: str | None = Field(
description = "a brief message to summarize the result of the process",
default = None
)
mailMessage: CoreMessageModel | None = Field(
description = "the actual data of the mail; can be null in a successful process if the mail is already sync'd",
default = None
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class MailSyncManyResults(BaseModel):
totalCount: int = Field(
description = "the total no. of mails that were to be sync'd",
default = 0
)
successCount: int = Field(
description = "the no. of mails that were successfully sync'd",
default = 0
)
failureCount: int = Field(
description = "the no. of mails that were successfully sync'd",
default = 0
)
message: str = Field(
description = "a brief message to summarize the results of the process",
default = None
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class MailSendOneResult(BaseModel):
success: bool = Field(
description = "whether, or not, the mail was successfully sent",
default = False
)
message: str | None = Field(
description = "a brief message to summarize the result of the process",
default = None
)
mailMessage: CoreMessageModel | None = Field(
description = "the actual data of the mail; can be null in a successful process if the mail is already sent",
default = None
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
View File
+144
View File
@@ -0,0 +1,144 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Thursday, 5th Dec., 2024.
OBJECTIVE:
To provide a structure to receive auth details of various SMS providers.
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, model_validator
from typing import Optional, Literal, Union
# Models:
from models.message.sms.auth import NimbusSMSIndiaAuth, SavvyBulkSMSKenyaAuth
# 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 SMSAuthRequestHeaders(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 SMSAuthRequestData(BaseModel):
smsClient: Literal["nimbusSmsIndia", "savvyBulkSmsKenya"] = Field(alias = "client")
auth: Union[NimbusSMSIndiaAuth, SavvyBulkSMSKenyaAuth]
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ┓┏ ┓• ┓ •
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
@model_validator(mode = "after")
def ensure_harmony(cls, values):
client = values.smsClient
auth = values.auth
harmony_map = {
"nimbusSmsIndia": NimbusSMSIndiaAuth,
"savvyBulkSmsKenya": SavvyBulkSMSKenyaAuth
}
if not isinstance(auth, harmony_map[client]):
raise ValueError(f"incorrect 'auth' for selected client '{client}'")
return values
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
+160
View File
@@ -0,0 +1,160 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Monday, 9th Dec., 2024.
OBJECTIVE:
To provide a structure to receive API calls to send SMS messages from various third-party clients.
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, Union, List, Any
# My utils:
from utils_v2.string import regex
from utils_v2.date_time import date_time
# Models:
from models.core.message import CoreMessageModel
from utils_v2.sms.models.sms_message import SentSMSMessageModel
from models.message.sms.send import (
NimbusSMSIndiaMessage,
SavvyBulkSMSKenyaMessage,
SMSSendOneResult,
SMSSendManyResults
)
# To work with date and time:
import datetime
# To work with MongoDB:
from bson.objectid import ObjectId
# *****************************************************************************************************************
# ***** ****
# *** 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 SMSSendRequestHeaders(BaseModel):
sessionToken: str | None = Field(
description = "the session token of the user who is requesting the service",
pattern = REGEX_SESSION_TOKEN,
frozen = True,
default = None,
alias = "X-Session-Token"
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "allow"
def model_dump(self, *args, **kwargs):
return super().model_dump(*args, by_alias = True, **kwargs)
# ---------------------------------------------------------------------------------------------------------------------
class SMSSendRequestData(BaseModel):
tokenKey: ObjectId
message: List[NimbusSMSIndiaMessage] | List[SavvyBulkSMSKenyaMessage]
tags: List[Any] | None = Field(default = None, validate_default = True)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
arbitrary_types_allowed = True
# ┓┏ ┓• ┓ •
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
@field_validator("tokenKey", mode = "before")
def parse_oid(cls, value):
try: value = ObjectId(value)
except: pass
return value
@field_validator("message", mode = "before")
def ensure_list(cls, value):
if not isinstance(value, list): value = [value]
return value
@field_validator("tags", mode = "after")
def null_to_list(cls, value):
return [] if value is None else value
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
+98
View File
@@ -74,6 +74,104 @@ import datetime
# *****************************************************************************************************************
class PaperTradingAuth(BaseModel):
username: str = Field(
description = "??",
frozen = True
)
password: str = Field(
description = "??",
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class ZerodhaKiteAuth(BaseModel):
userId: str = Field(
description = "How Zerodha's Kite platform identifies this user.",
frozen = True,
alias = "clientId"
)
apiKey: str = Field(
description = (
"The API key of your Kite app. "
"This remains constant throughout the life of the app."
),
frozen = True
)
apiSecret: str = Field(
description = (
"The API secret of your Kite app. "
"This can be changed if you think the security of your app has been compromised."
),
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
populate_by_name = True
# ---------------------------------------------------------------------------------------------------------------------
class ICICIBreezeAuth(BaseModel):
userId: str = Field(
description = "How ICICI's Breeze platform identifies this user.",
frozen = True,
alias = "clientId"
)
apiKey: str = Field(
description = (
"The API key of your Breeze app. "
"This remains constant throughout the life of the app."
),
frozen = True
)
apiSecret: str = Field(
description = (
"The API secret of your Breeze app. "
"This can be changed if you think the security of your app has been compromised."
),
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
populate_by_name = True
# ---------------------------------------------------------------------------------------------------------------------
class TradingOAuthCallbackResponse(BaseModel):
success: bool = Field(
+39
View File
@@ -220,6 +220,45 @@ class TradingSymbol(BaseModel):
return value
# ---------------------------------------------------------------------------------------------------------------------
class TradingSymbolListBrokerResponse(BaseModel):
success: bool = Field(
description = "whether, or not, the symbol list request was successful",
default = False,
frozen = False
)
message: str = Field(
description = "a brief message to help debug in failed cases",
default = "",
frozen = False
)
data: List[TradingSymbol] | None = Field(
description = "the actual response from the broker with his list of tradeable symbols",
default = None,
frozen = False
)
exception: Exception | None = Field(
description = "if something goes wrong, the exception will be held here",
default = None,
frozen = False
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
arbitrary_types_allowed = True
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
View File
View File
+130
View File
@@ -0,0 +1,130 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Friday, 6th Dec., 2024.
OBJECTIVE:
To provide a structure to receive auth details of various chat apps (like Telegram and WhatsApp).
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, model_validator
from typing import Optional, Literal, Union
# 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 TelegramAuth(BaseModel):
botToken: str = Field(
description = "the token granted by BotFather",
min_length = 1,
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class WhatsAppNimbusAuth(BaseModel):
apiKey: str = Field(
description = "???",
min_length = 1,
frozen = True
)
senderId: str = Field(
description = "???",
min_length = 1,
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
View File
+185
View File
@@ -0,0 +1,185 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Thursday, 16th Jan., 2025.
OBJECTIVE:
To provide the structure for the request and response of the APIs that will be used to request OAuth2.0
authorization for mail services.
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
from typing import Optional, Literal, Any
# My utils:
from utils_v2.string import regex
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
class OAuthMailGetAuthorizationURLResponse(BaseModel):
success: bool = Field(
description = "To indicate whether or not, the action was a success",
frozen = False,
default = False
)
url: str | None = Field(
description = "The URL to use to integrate the mail client.",
frozen = False,
default = None
)
message: str = Field(
description = "To explain what happened in the process generating a URL.",
frozen = False,
default = "ERR: Message not captured."
)
exception: Any = Field(
description = "To pass on any exception that occurred in the process.",
frozen = False,
default = None
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ┏┓ ┏┓
# ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏
# ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛
pass
# ┓┏ ┓• ┓ •
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
pass
# ---------------------------------------------------------------------------------------------------------------------
class OAuthMailHandleCallbackResponse(BaseModel):
success: bool = Field(
description = "To indicate whether or not, the action was a success",
frozen = False,
default = False
)
action: Literal[
"unknown", # ..... Initial value when the user's intent is not known.
"denied", # ...... The user consciously denied permission.
"cancelled", # ... The user cancelled the process midway.
"authorized" # ... The user gave authorization.
] = Field(
description = "To describe what the user did with the authorization URL.",
frozen = True,
default = "unknown"
)
message: str = Field(
description = "To explain what happened in the process of handling the OAuth callback.",
frozen = False,
default = "ERR: Message not captured."
)
exception: Any = Field(
description = "To pass on any exception that occurred in the process.",
frozen = False,
default = None
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ┏┓ ┏┓
# ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏
# ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛
pass
# ┓┏ ┓• ┓ •
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
pass
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
+120
View File
@@ -0,0 +1,120 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Thursday, 19th Dec., 2024.
OBJECTIVE:
To provide a structure to send mails.
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, EmailStr
from typing import Optional, Literal, List
# Models:
from models.core.message import CoreMessageModel
# My utils:
from utils_v2.string import json
from utils_v2.string import regex
from utils_v2.date_time import date_time
# To work with date and time:
import datetime
# To work with MongoDB:
from bson.objectid import ObjectId
# *****************************************************************************************************************
# ***** ****
# *** 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 MailSendOneResult(BaseModel):
success: bool = Field(
description = "whether, or not, the mail was successfully sent",
default = False
)
message: str | None = Field(
description = "a brief message to summarize the result of the process",
default = None
)
mailMessage: CoreMessageModel | None = Field(
description = "the actual data of the mail; can be null in a successful process if the mail is already sent",
default = None
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
+150
View File
@@ -0,0 +1,150 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Monday, 2nd Dec., 2024.
OBJECTIVE:
To provide the structure for the request that will come in to sync the mails of a particular user.
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, AwareDatetime
from typing import Optional, Literal
# My utils:
from utils_v2.string import regex
from utils_v2.date_time import date_time
# Data models:
from models.core.message import CoreMessageModel
# 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 MailSyncOneResult(BaseModel):
success: bool = Field(
description = "whether, or not, the mail was successfully sync'd",
default = False
)
message: str | None = Field(
description = "a brief message to summarize the result of the process",
default = None
)
mailMessage: CoreMessageModel | None = Field(
description = "the actual data of the mail; can be null in a successful process if the mail is already sync'd",
default = None
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class MailSyncManyResults(BaseModel):
totalCount: int = Field(
description = "the total no. of mails that were to be sync'd",
default = 0
)
successCount: int = Field(
description = "the no. of mails that were successfully sync'd",
default = 0
)
failureCount: int = Field(
description = "the no. of mails that were successfully sync'd",
default = 0
)
message: str = Field(
description = "a brief message to summarize the results of the process",
default = None
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
View File
@@ -143,63 +143,6 @@ class SavvyBulkSMSKenyaAuth(BaseModel):
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class SMSAuthRequestHeaders(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 SMSAuthRequestData(BaseModel):
smsClient: Literal["nimbusSmsIndia", "savvyBulkSmsKenya"] = Field(alias = "client")
auth: Union[NimbusSMSIndiaAuth, SavvyBulkSMSKenyaAuth]
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ┓┏ ┓• ┓ •
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
@model_validator(mode = "after")
def ensure_harmony(cls, values):
client = values.smsClient
auth = values.auth
harmony_map = {
"nimbusSmsIndia": NimbusSMSIndiaAuth,
"savvyBulkSmsKenya": SavvyBulkSMSKenyaAuth
}
if not isinstance(auth, harmony_map[client]):
raise ValueError(f"incorrect 'auth' for selected client '{client}'")
return values
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
@@ -151,69 +151,6 @@ class SavvyBulkSMSKenyaMessage(BaseModel):
# ---------------------------------------------------------------------------------------------------------------------
class SMSSendRequestHeaders(BaseModel):
sessionToken: str | None = Field(
description = "the session token of the user who is requesting the service",
pattern = REGEX_SESSION_TOKEN,
frozen = True,
default = None,
alias = "X-Session-Token"
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "allow"
def model_dump(self, *args, **kwargs):
return super().model_dump(*args, by_alias = True, **kwargs)
# ---------------------------------------------------------------------------------------------------------------------
class SMSSendRequestData(BaseModel):
tokenKey: ObjectId
message: List[NimbusSMSIndiaMessage] | List[SavvyBulkSMSKenyaMessage]
tags: List[Any] | None = Field(default = None, validate_default = True)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
arbitrary_types_allowed = True
# ┓┏ ┓• ┓ •
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
@field_validator("tokenKey", mode = "before")
def parse_oid(cls, value):
try: value = ObjectId(value)
except: pass
return value
@field_validator("message", mode = "before")
def ensure_list(cls, value):
if not isinstance(value, list): value = [value]
return value
@field_validator("tags", mode = "after")
def null_to_list(cls, value):
return [] if value is None else value
# ---------------------------------------------------------------------------------------------------------------------
class SMSSendOneResult(BaseModel):
success: bool = Field(
+1 -1
View File
@@ -218,7 +218,7 @@ class AsyncGoogleBase:
async def get_authorization_tokens(
self,
scopes: List[str],
scopes: List[str] | None,
redirect_url: str
) -> GoogleAuthTokens: