This commit is contained in:
2025-09-30 14:15:21 +05:30
parent 0a146c2169
commit c21ec7ba42
8 changed files with 326 additions and 1160 deletions
+110 -127
View File
@@ -6,16 +6,16 @@
DATE:
Create: Saturday, 18th May, 2022
Update: Thursday, 22nd Aug. 2024
Create: Monday, 29th Sept., 2025
OBJECTIVE:
To provide an easy way to work with '.json' data and files.
To have a centralized Socket.IO app from where several namespaces can be registered. This is kind of like how
you can have one Quart app and register several blueprints.
REFERENCES:
1) https://www.w3schools.com/python/python_json.asp
N/A
DOWNLOADS:
@@ -37,13 +37,21 @@ sys.path.append(".")
sys.path.append("..")
# System-level activities:
import io
import os
# To work with the JSON standard:
import json
# To make HTTP calls:
import httpx
# To work with files:
from utils_v2.system import files
# To work with SocketIO:
import socket
import socketio
# To maintain the app's state:
from wsio_v2.app_state import AppState
from wsio_v2.test.echo import EchoNamespace
# Debugging:
from icecream import IceCreamDebugger
# *****************************************************************************************************************
@@ -53,7 +61,30 @@ from utils_v2.system import files
# *****************************************************************************************************************
# --- Nothing Yet
# A custom class to maintain the app's state:
app_state = AppState()
# Debugging:
app_state.printer = IceCreamDebugger(prefix = "WSIO | ", includeContext = True)
app_state.no_context_printer = IceCreamDebugger(prefix = "WSIO | ", includeContext = False)
# To make API calls:
app_state.http_client = httpx.AsyncClient(
limits = httpx.Limits(
max_connections = 100, # ............ Maximum number of connections allowed in the pool.
max_keepalive_connections = 50, # ... Maximum number of connections that can be kept alive.
),
timeout = httpx.Timeout(
pool = 120.0, # .... Time to wait for a free connection from the pool.
connect = 2.5, # ... Time to wait for establishing a connection to the server.
write = 10.0, # .... Time to wait for sending data.
read = 9.9 # ....... Time to wait for receiving data.
)
)
# General:
app_state.SERVER_HOSTNAME = str(socket.gethostname())
app_state.ALLOWED_ORIGINS = []
# *****************************************************************************************************************
@@ -63,7 +94,15 @@ from utils_v2.system import files
# *****************************************************************************************************************
# --- Nothing Yet
# For SocketIO:
sio = socketio.AsyncServer(
cors_allowed_origins = "*",
async_mode = "asgi"
)
app = socketio.ASGIApp(sio)
# Register the namespaces:
sio.register_namespace(EchoNamespace(namespace = "/test", app_state = app_state))
# *****************************************************************************************************************
@@ -73,121 +112,7 @@ from utils_v2.system import files
# *****************************************************************************************************************
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
# --- Nothing Yet
# *****************************************************************************************************************
@@ -199,4 +124,62 @@ def to_file(
if __name__ == "__main__":
pass
app_state.printer("Main.")
# To get args from the terminal:
import argparse
# To run the ASGI:
import uvicorn
from multiprocessing import freeze_support
# Get the config from the command-line:
parser = argparse.ArgumentParser(description = f"SocketIO to serve live market data (and a general passthrough).")
parser.add_argument(
"-w", "--workers",
type = int,
help = "The no. of threads to spin up for this instance!",
default = 2
)
parser.add_argument(
"-a", "--host",
type = str,
help = "The host for the app. e.g.: '0.0.0.0' or '127.0.0.1'.",
default = "0.0.0.0"
)
parser.add_argument(
"-p", "--port",
type = int,
help = "The port no. to bind the app to.",
default = 8080
)
parser.add_argument(
"-s", "--script-id",
type = str,
help = "The id of this script (will affect the loaded config)."
)
parser.add_argument(
"-d", "--debug",
action = "store_true",
help = "Whether, or not, you want to see debugging messages in the terminal.",
default = False
)
args = parser.parse_args()
# Note down the config;
os.environ["SCRIPT_ID"] = args.script_id
os.environ["DEBUG"] = str(args.debug)
# Startup message:
app_state.printer.enable()
app_state.printer(str(args.debug))
if str(args.debug).lower().find("false") >= 0: app_state.printer.disable()
# Run the gateway:
freeze_support()
uvicorn.run(
app = "app:app",
workers = args.workers,
host = args.host,
port = args.port
)