(20241221) Safety Push.

This commit is contained in:
2024-12-21 15:22:16 +05:30
parent eb29878df6
commit 8904ea394d
10 changed files with 969 additions and 0 deletions
@@ -0,0 +1,171 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Saturday, 21st Dec., 2024
OBJECTIVE:
To handle all trading related behaviour 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.core.auth_token import CoreAuthTokenController
# Models:
from models.core.auth_token import CoreAuthTokenModel
# To work with datatypes:
from typing import List, Any
# To make HTTP requests:
import httpx
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** CLASSES ***
# ***** ****
# *****************************************************************************************************************
class TradingController(CoreAuthTokenController):
# ┏┓
# ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓
# ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛
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 = "Trading (C) | ",
debug_only_errors: bool = True
):
"""
This is the foundational controller for all SMS services. This is built on top of the core message controller,
and, in turn, all individual SMS 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:
this_filter = {}
for k, v in (base_filter or {}).items(): this_filter[k] = v
this_filter["serviceType"] = "stockTrading"
# Invoke the parent's constructor:
CoreAuthTokenController.__init__(
self,
cache = cache,
alert_url = alert_url,
http_client = http_client,
base_filter = this_filter,
debug = debug,
debug_prefix = debug_prefix,
debug_only_errors = debug_only_errors
)
# ┏┓ ┓
# ┣┫┓┏╋┣┓
# ┛┗┗┻┗┛┗
# async def login_url(
# self,
# mongo_data_conn: AsyncMongo,
# auth_token: CoreAuthTokenModel
# ) -> SMSSendOneResult:
#
# """
# To send one SMS message through the third-party client.
# :param mongo_data_conn: The database connection to use to perform this task.
# :param auth_token: The auth token that will be used to send this message.
# :param client: The third-party SMS client to use to send this message.
# :param message: The actual message that needs to be sent.
# :param tags: Any tags to attach with this SMS for filtering when querying in the listing service.
# :return: The structured result of sending one message.
# """
#
# pass
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
@@ -0,0 +1,210 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Thursday, 19th Dec., 2024
OBJECTIVE:
To handle all SMS related behaviour 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.core.message import CoreMessageController
# 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 SMSController(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 = "SMS (C) | ",
debug_only_errors: bool = True
):
"""
This is the foundational controller for all SMS services. This is built on top of the core message controller,
and, in turn, all individual SMS 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
)
# ┏┓┳┳┓┏┓ ┏┓ ┓•
# ┗┓┃┃┃┗┓ ┗┓┏┓┏┓┏┫┓┏┓┏┓
# ┗┛┛ ┗┗┛ ┗┛┗ ┛┗┗┻┗┛┗┗┫
# ┛
async def send_one_sms(
self,
mongo_data_conn: AsyncMongo,
auth_token: CoreAuthTokenModel,
client: AsyncNimbusSMS | AsyncSavvyBulkSMS,
message: NimbusSMSIndiaMessage,
tags: List[Any]
) -> SMSSendOneResult:
"""
To send one SMS message through the third-party client.
:param mongo_data_conn: The database connection to use to perform this task.
:param auth_token: The auth token that will be used to send this message.
:param client: The third-party SMS client to use to send this message.
:param message: The actual message that needs to be sent.
:param tags: Any tags to attach with this SMS for filtering when querying in the listing service.
:return: The structured result of sending one message.
"""
pass
@abstractmethod
async def send_many_sms(
self,
mongo_data_conn: AsyncMongo,
auth_token: CoreAuthTokenModel,
messages: List[NimbusSMSIndiaMessage | SavvyBulkSMSKenyaMessage],
tags: List[Any]
) -> SMSSendManyResults:
"""
To send multiple SMS messages through the third-party client.
individual message, and then aggregates the results.
:param mongo_data_conn: The database connection to use to perform this task.
:param auth_token: The auth token that will be used to send this message.
:param messages: The list of messages to send out.
:param tags: Any tags to attach with these SMS for filtering when querying in the listing service. The same tags
will be applied to all messages. Do not call this method if you need to have different tags for all of them.
:return: The structured result of sending many SMS messages.
"""
pass
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
@@ -0,0 +1,145 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Saturday, 21st Dec., 2024
OBJECTIVE:
To handle all trading related behaviour 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.finstitutions.trading.base import TradingController
# Models:
from models.core.auth_token import CoreAuthTokenModel
# To work with datatypes:
from typing import List, Any
# To make HTTP requests:
import httpx
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** CLASSES ***
# ***** ****
# *****************************************************************************************************************
class AllTradingController(TradingController):
# ┏┓
# ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓
# ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛
def __init__(
self,
cache: AsyncRedisCache = None,
http_client: httpx.AsyncClient = None,
alert_url: str = None,
debug: bool = True,
debug_prefix: str = "All SMS (C) | ",
debug_only_errors: bool = True
):
"""
This is the foundational controller for all trading services. Use this for any smaller common tasks where you
may not know the exact client beforehand.
: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 = None,
debug = debug,
debug_prefix = debug_prefix,
debug_only_errors = debug_only_errors
)
# ┏┓ ┓
# ┣┫┓┏╋┣┓
# ┛┗┗┻┗┛┗
pass
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
+202
View File
@@ -0,0 +1,202 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Create: Saturday, 18th May, 2022
Update: Thursday, 22nd Aug. 2024
OBJECTIVE:
To provide an easy way to work with '.json' data and files.
REFERENCES:
1) https://www.w3schools.com/python/python_json.asp
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# System-level activities:
import io
# To work with the JSON standard:
import json
# To work with files:
from utils_v2.system import files
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
def from_string(json_data):
"""
Decodes a JSON string to a pythonic variable like a dict.
:param json_data: The JSON string to decode.
:return: The decoded pythonic variable.
"""
python_data = json.loads(json_data)
return python_data
# ---------------------------------------------------------------------------------------------------------------------
def to_string(
python_data,
indent = 4,
default = None,
separators = None,
no_space = False
):
"""
Converts the given pythonic data to a JSON string.
:param python_data: The input data like a dict.
:param indent: The tab-width for pretty presentation.
:param default: The function to use on something that cannot be directly parsed into a JSON string.
:param separators: Custom separators to use.
:param no_space: If you want a dense JSON string that saves memory by not using spaces or tabs or line-breaks. Not
good for human readability, very good for saving memory. WARNING: THIS OVERRIDES EVERY OTHER PARAMETER EXCEPT
'default'.
:return: The JSON string representation of the input pythonic data.
"""
if no_space:
json_data = json.dumps(
python_data,
default = default,
separators = (',', ':')
)
else:
json_data = json.dumps(
python_data,
indent = indent,
default = default,
separators = separators
)
return json_data
# ---------------------------------------------------------------------------------------------------------------------
def from_file(file):
"""
Reads a JSON file and returns it as a pythonic variable like a dict.
:param file: The path to the file on the disk or a file held in RAM as a BytesIO object.
:return: The decoded pythonic variable.
"""
if isinstance(file, io.BytesIO):
file.seek(0)
json_data = file.getvalue()
else: json_data = files.read_file(file)
python_data = from_string(json_data)
return python_data
# ---------------------------------------------------------------------------------------------------------------------
def to_file(
file,
python_data,
indent = 4,
default = None,
separators = None,
no_space = False
):
"""
:param file: Either a path to a file on disk, or a buffer in RAM in the form of a BytesIO object.
:param python_data: The pythonic data to be converted to the JSON string.
:param indent: The tab-width for pretty presentation.
:param default: The function to use on something that cannot be directly parsed into a JSON string.
:param separators: Custom separators to use.
:param no_space: If you want a dense JSON string that saves memory by not using spaces or tabs or line-breaks. Not
good for human readability, very good for saving memory. WARNING: THIS OVERRIDES EVERY OTHER PARAMETER EXCEPT
'default'.
:return: True/False if a path was given, else the same BytesIO object with the written JSON data.
"""
json_data = to_string(
python_data,
indent = indent,
default = default,
separators = separators,
no_space = no_space
)
if isinstance(file, io.BytesIO):
file.write(json_data.encode("utf-8"))
file.seek(0)
return file
else:
try:
files.write_file(file, json_data, mode = "w")
return True
except: return False
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
View File
@@ -0,0 +1,241 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Saturday, 21st Dec., 2024.
OBJECTIVE:
To provide a data model for describing the tokens to be used for Google's APIs.
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 ZerodhaKiteAuthTokens(BaseModel):
userType: str | None = Field(
description = "the type of the user",
default = None,
alias = "user_type",
examples = ["individual/ind_with_nom"]
)
email: str | None = Field(
description = "the email id of the user",
default = None,
alias = "email"
)
name: str = Field(
description = "the name of the user",
alias = "user_name"
)
displayName: str | None = Field(
description = "the short display name of the user",
default = None,
alias = "user_shortname"
)
displayPictureUrl: str | None = Field(
description = "the display picture of the user",
default = None,
alias = "avatar_url"
)
exchanges: List[str] = Field(
description = "the list of exchanges this user can trade on",
alias = "exchanges"
)
products: List[str] = Field(
description = "the list of products (offered by the broker) this user can avail",
alias = "products"
)
orderTypes: List[str] = Field(
description = "the list of order types this user can place",
alias = "order_types"
)
accessToken: str = Field(
description = "the main access token to be used in actual requests",
alias = "access_token"
)
refreshToken: str | None = Field(
description = "token to be used to refresh the access token; may not be provided",
default = None,
alias = "refresh_token"
)
publicToken: str | None = Field(
description = "undocumented on their official documentation",
default = None,
alias = "public_token"
)
loginTs: AwareDatetime = Field(
description = "the time (utc) at which this user logged in",
alias = "login_time"
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "ignore"
# ┓┏ ┓• ┓ •
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
@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 = ["%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__":
sample_token = {
"user_type": "individual/ind_with_nom",
"email": "khushalpradipsoonderji@gmail.com",
"user_name": "Khushal Pradip Soonderji",
"user_shortname": "Khushal",
"broker": "ZERODHA",
"exchanges": [
"NSE",
"BSE",
"NFO",
"MF"
],
"products": [
"CNC",
"NRML",
"MIS",
"BO",
"CO"
],
"order_types": [
"MARKET",
"LIMIT",
"SL",
"SL-M"
],
"avatar_url": None,
"user_id": "ABC123",
"api_key": "same-as-api-key-input-to-this-func",
"access_token": "use-this-for-subsequent-activities-like-data-feeds",
"public_token": "???",
"refresh_token": "",
"enctoken": "???",
"login_time": "2024-12-20 13:23:20",
"meta": {
"demat_consent": "consent"
}
}
model = ZerodhaKiteAuthTokens(**sample_token)
print(json.to_string(model.model_dump(), default = str))