"""
AUTHOR:
Khushal P Soonderji
DATE:
Saturday, 21st Dec., 2024
OBJECTIVE:
To receive callbacks (webhooks) from stockbrokers for trading API integrations.
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,
handle_failed_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
# Common:
from shared import constants
# For asynchronous activities:
import asyncio
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# Related to Quart:
trading_oauth_callback_bp = Blueprint("trading_oauth_cb", __name__)
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
@trading_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
# ---------------------------------------------------------------------------------------------------------------------
async def handle_auth_exception():
"""
Use this to handle any exceptions that occur in the process of accepting authorization details. Zero's direct
library, for instance, raise several exceptions for cases like expired tokens, checksum failures, etc.
:return: A web-view that indicates failure.
"""
return await render_template(
"/finstitutions/trading/oauth/oauth_failure_v2.html",
client = g.client_label,
failure_hint = (
f"Something went wrong (E). "
f"Please use log-id '{g.log_id}' to check with the support team."
)
)
# ---------------------------------------------------------------------------------------------------------------------
@trading_oauth_callback_bp.route("/callback/", 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 = "trdngOauthCllBckApi",
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()
@handle_failed_request(cleanup_coro = handle_auth_exception)
async def trading_oauth_callback(
trading_client: str = None,
inbound_headers: dict = None,
inbound_data: dict = None,
inbound_files: dict = None,
**kwargs
):
"""
This endpoint gets triggered by the stockbroker's servers to let you know when a user accepted or rejected an
authorization request.
:param trading_client: The name of the stockbroker that you have received the callback 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.
"""
# ┓┏ ┓┓ ┓┏ • ┓ ┓
# ┣┫┏┓┏┓┏┫┃┏┓ ┃┃┏┓┏┓┓┏┓┣┓┃┏┓┏
# ┛┗┗┻┛┗┗┻┗┗ ┗┛┗┻┛ ┗┗┻┗┛┗┗ ┛
# Store needed values in 'g':
g.log_id = kwargs.get("log_id")
g.client_label = "Zerodha (Kite)"
# Start by assuming failure:
success = None
# ┏┓ ┏┓ ┓┓ ┓┏┓•
# ┣ ┏┓┏┓ ┏┛┏┓┏┓┏┓┏┫┣┓┏┓ ┃┫ ┓╋┏┓
# ┻ ┗┛┛ ┗┛┗ ┛ ┗┛┗┻┛┗┗┻ ┛┗┛┗┗┗
if trading_client == "zerodha":
success = await current_app.zerodha_kite_controller.handle_authorization_callback(
sql_conn = current_app.sql_writer,
mongo_data_conn = current_app.data_mongo,
inbound_data = inbound_data
)
# ┳┓
# ┣┫┏┓┏┏┓┏┓┏┓┏┏┓
# ┛┗┗ ┛┣┛┗┛┛┗┛┗
# ┛
# No valid client:
if success is None: return await render_template(
"/finstitutions/trading/oauth/oauth_failure_v2.html",
client = g.client_label,
failure_hint = (
f"Invalid client '{g.client_label}' selected. "
f"Please use log-id '{g.log_id}' to check with the support team."
)
)
# Successful auth:
if success: return await render_template(
"/finstitutions/trading/oauth/oauth_success_v2.html",
client = g.client_label
)
# Failed auth:
if success is None: return await render_template(
"/finstitutions/trading/oauth/oauth_failure_v2.html",
client = g.client_label,
failure_hint = (
f"Something went wrong (NE). "
f"Please use log-id '{g.log_id}' to check with the support team."
)
)
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass