From e2385c31637aa29ef5e7225d0d0f0997c2e727c7 Mon Sep 17 00:00:00 2001 From: khushal Date: Sat, 4 Jan 2025 16:19:46 +0530 Subject: [PATCH] (20250104) Breeze authorization cycle ready for testing on live server. --- .../finstitutions/trading/icici_breeze.py | 79 ++++++- .../finstitutions/trading/zerodha_kite.py | 4 +- playground/trading/icici_breeze.py | 21 ++ utils_v2/trading/icici_breeze/__init__.py | 0 .../trading/icici_breeze/models/__init__.py | 0 .../icici_breeze/models/auth_tokens.py | 200 ++++++++++++++++++ .../zerodha_kite/models/auth_tokens.py | 2 +- 7 files changed, 302 insertions(+), 4 deletions(-) create mode 100644 utils_v2/trading/icici_breeze/__init__.py create mode 100644 utils_v2/trading/icici_breeze/models/__init__.py create mode 100644 utils_v2/trading/icici_breeze/models/auth_tokens.py diff --git a/controllers_v2/finstitutions/trading/icici_breeze.py b/controllers_v2/finstitutions/trading/icici_breeze.py index fdfb912..133d651 100644 --- a/controllers_v2/finstitutions/trading/icici_breeze.py +++ b/controllers_v2/finstitutions/trading/icici_breeze.py @@ -46,6 +46,7 @@ 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, @@ -194,7 +195,83 @@ class ICICIBreezeTradingController(TradingController): :return: A structured response to capture the process of callback handling. """ - raise NotImplementedError + # Start by assuming failure: + response = TradingOAuthCallbackResponse() + icici_auth_token = None + + # Check if the callback URL was configured properly: + if not client_user_id: + response.message = "Your callback URL hasn't been configured properly." + return response + + # Get the token from the database: + condition = mongo_data_conn.dict_to_dot_notation({"auth": {"userId": client_user_id}}) + auth_token = await self.get_token_from_filter( + mongo_data_conn = mongo_data_conn, + filter_json = condition + ) + + # If not such auth token exists: + if not auth_token: + response.message = ( + f"We couldn't find such an integration in our system. " + "Please add this integration first and then try again." + ) + return response + + # Get the access token and user information from ICICI Breeze: + breeze_session_token = inbound_data.get("apisession") + try: + breeze = BreezeConnect(api_key = auth_token.auth["apiKey"]) + breeze.generate_session( + api_secret = auth_token.auth["apiSecret"], + session_token = breeze_session_token + ) + session_data = breeze.get_customer_details(api_session = breeze_session_token) + session_data = session_data.get("Success") + icici_auth_token = ICICIBreezeAuthTokens(**session_data) + except Exception as exception: + response.message = f"Client exception: {exception}" + response.exception = exception + return response + + # Ensure that the client user id of the incoming callback and the one given in Zerodha's session data match: + if icici_auth_token.userId != client_user_id: + response.message = ( + f"We were expecting authorization for the account '{client_user_id}', " + f"but ICICI says the authorization was granted for the account '{icici_auth_token.userId}'. " + "This could be because of a misconfigured callback URL." + ) + return response + + # Prepare the inputs to save to the database: + auth_url = await self.get_authorization_url(api_key = auth_token.auth["apiKey"]) + icici_auth_token.sessionToken = breeze_session_token + auth_token.token = icici_auth_token.model_dump() + + # Save the additional auth info to the database: + success = await self.set_token( + sql_conn = sql_conn, + mongo_data_conn = mongo_data_conn, + token_key = auth_token.key, + auth_token = auth_token, + token_notes = { + "apiKey": auth_token.auth["apiKey"], + "authUrl": auth_url + }, + display_name = icici_auth_token.userId, + display_picture = icici_auth_token.displayPictureUrl + ) + + # If saving the token fails: + if not success: + response.message = "Something went wrong towards the end of the authorization cycle." + return response + + # Done here: + response.success = True + response.message = "Authorization cycle successfully completed." + return response # ┏┳┓ ┓• ┏┓ ┓ ┓ # ┃ ┏┓┏┓┏┫┓┏┓┏┓ ┗┓┓┏┏┳┓┣┓┏┓┃┏ diff --git a/controllers_v2/finstitutions/trading/zerodha_kite.py b/controllers_v2/finstitutions/trading/zerodha_kite.py index 4513961..a68201d 100644 --- a/controllers_v2/finstitutions/trading/zerodha_kite.py +++ b/controllers_v2/finstitutions/trading/zerodha_kite.py @@ -224,7 +224,7 @@ class ZerodhaKiteTradingController(TradingController): # If not such auth token exists: if not auth_token: response.message = ( - f"No such integration found in our system. " + f"We couldn't find such an integration in our system. " "Please add this integration first and then try again." ) return response @@ -238,8 +238,8 @@ class ZerodhaKiteTradingController(TradingController): ) zerodha_auth_token = ZerodhaKiteAuthTokens(**session_data) except Exception as exception: + response.message = f"Client exception: {exception}" response.exception = exception - response.message = str(exception) return response # Ensure that the client user id of the incoming callback and the one given in Zerodha's session data match: diff --git a/playground/trading/icici_breeze.py b/playground/trading/icici_breeze.py index e69de29..0ce2026 100644 --- a/playground/trading/icici_breeze.py +++ b/playground/trading/icici_breeze.py @@ -0,0 +1,21 @@ +# To work with ICICI Breeze's platform: +from breeze_connect import BreezeConnect + +from utils_v2.string import json + +api_key = r"2678j1551CSkQ96I5T1862k1685t0b2d" +api_secret = r"kQWD507T9R1169I7AR3473+54@1940P5" +session_token = r"50114591" + +print("Init") +breeze = BreezeConnect(api_key = api_key) +print("Creating session.") +breeze.generate_session( + api_secret = api_secret, + session_token = session_token +) + +# Getting Customer details: +customer_details = breeze.get_customer_details(api_session = session_token) +print(customer_details) +print("CUSTOMER:", json.to_string(customer_details, default=str)) diff --git a/utils_v2/trading/icici_breeze/__init__.py b/utils_v2/trading/icici_breeze/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils_v2/trading/icici_breeze/models/__init__.py b/utils_v2/trading/icici_breeze/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils_v2/trading/icici_breeze/models/auth_tokens.py b/utils_v2/trading/icici_breeze/models/auth_tokens.py new file mode 100644 index 0000000..a33f04a --- /dev/null +++ b/utils_v2/trading/icici_breeze/models/auth_tokens.py @@ -0,0 +1,200 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Saturday, 4th Jan., 2025. + + OBJECTIVE: + + To provide a data model for describing the tokens to be used for ICICI Breeze. + + 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, model_validator, AwareDatetime +from typing import Optional, Literal, Union, Dict, List, Any + +# Related to Google: +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials + +# 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 +import dateparser + +# To make API calls: +import httpx + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +class ICICIBreezeAuthTokens(BaseModel): + + userId: str = Field( + description = "The id of the user as assigned by ICICI Breeze.", + alias = "idirect_userid" + ) + + name: str = Field( + description = "The name of the user.", + alias = "idirect_user_name" + ) + + displayName: str | None = Field( + description = "The short display name of the user.", + default = None, + alias = "idirect_user_name" + ) + + displayPictureUrl: str | None = Field( + description = "The display picture of the user.", + default = None, + alias = "avatar_url" + ) + + loginTs: AwareDatetime = Field( + description = "The time (utc) at which this user logged in.", + alias = "idirect_lastlogin_time" + ) + + sessionToken: str | None = Field( + description = "The main access token to be used in actual requests.", + default = None + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "ignore" + populate_by_name = True + + # ┓┏ ┓• ┓ • + # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ + # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ + + @field_validator("loginTs", mode = "before") + def parse_dates(cls, value): + if not isinstance(value, datetime.datetime): + parsed = date_time.parse_date_time( + value, + date_formats = ["%d-%b-%Y %H:%M:%S", "%Y-%m-%d %H:%M:%S"], + timezone = date_time.TIMEZONE_IST + ) + value = parsed if isinstance(parsed, datetime.datetime) else dateparser.parse(value) + if isinstance(value, datetime.datetime): value = date_time.to_timezone(value, date_time.TIMEZONE_UTC) + return value + + # ┏┓ • + # ┃┃┏┓┏┓┏┓┏┓┏┓╋┓┏┓┏ + # ┣┛┛ ┗┛┣┛┗ ┛ ┗┗┗ ┛ + # ┛ + + pass + + # ┏┓ ┏┓ + # ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏ + # ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛ + + pass + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + print("BHOPLI & MUCHHI") + + sample_token = { + "exg_trade_date": { + "NSE": "06-Jan-2025", + "BSE": "06-Jan-2025", + "FNO": "06-Jan-2025", + "NDX": "06-Jan-2025" + }, + "exg_status": { + "NSE": "C", + "BSE": "C", + "FNO": "Y", + "NDX": "C" + }, + "segments_allowed": { + "Trading": "Y", + "Equity": "Y", + "Derivatives": "Y", + "Currency": "Y" + }, + "idirect_userid": "BHUSHaI4", + "idirect_user_name": "BHUSHAN C THAKKAR", + "idirect_ORD_TYP": "N", + "idirect_lastlogin_time": "04-Jan-2025 15:41:53", + "mf_holding_mode_popup_flg": "N", + "commodity_exchange_status": "Y", + "commodity_trade_date": "06-Jan-2025", + "commodity_allowed": "C" + } + + model = ICICIBreezeAuthTokens(**sample_token) + print(json.to_string(model.model_dump(), default = str)) diff --git a/utils_v2/trading/zerodha_kite/models/auth_tokens.py b/utils_v2/trading/zerodha_kite/models/auth_tokens.py index 216b609..f8105cf 100644 --- a/utils_v2/trading/zerodha_kite/models/auth_tokens.py +++ b/utils_v2/trading/zerodha_kite/models/auth_tokens.py @@ -10,7 +10,7 @@ OBJECTIVE: - To provide a data model for describing the tokens to be used for Google's APIs. + To provide a data model for describing the tokens to be used for Zerodha Kite. REFERENCES: