(20251114) Ready to start API testing.
This commit is contained in:
+2
-1
@@ -5,4 +5,5 @@ __pycache__/
|
||||
|
||||
*.pem
|
||||
*.pyc
|
||||
*.pyd
|
||||
*.pyd
|
||||
/downloads/
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
"""
|
||||
|
||||
AUTHOR:
|
||||
|
||||
Khushal P Soonderji
|
||||
|
||||
DATE:
|
||||
|
||||
Fri, 14th Nov, 2025
|
||||
|
||||
OBJECTIVE:
|
||||
|
||||
To build APIs to serve In/Out Reports from Cosec's Web portal.
|
||||
|
||||
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 system-level activities:
|
||||
import os
|
||||
|
||||
# For using Quart:
|
||||
from quart import Blueprint, current_app, request
|
||||
|
||||
# My utils:
|
||||
from utils_v2.string import json
|
||||
from utils_v2.date_time import date_time
|
||||
from utils_v2.system import files
|
||||
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
|
||||
)
|
||||
|
||||
# Models:
|
||||
from backend.models.api.common import SimpleCosecCredentialsHeaders
|
||||
from backend.models.api.reports.in_out_report import CosecInOutReportRequestData
|
||||
|
||||
# Cosec-related:
|
||||
from cosec_web.cosec_web import CosecWeb
|
||||
|
||||
# Common:
|
||||
from backend.shared import constants
|
||||
|
||||
# To work with dat and time:
|
||||
import datetime
|
||||
|
||||
# For asynchronous activities:
|
||||
import asyncio
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MACROS / ONE-TIME INIT ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# Related to Quart:
|
||||
in_out_report_bp = Blueprint("in_out_report", __name__)
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** VARIABLES ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# --- Nothing Yet
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** FUNCTIONS ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
@in_out_report_bp.record_once
|
||||
def init(blueprint_setup_state):
|
||||
|
||||
# This gets called when the blueprints is registered.
|
||||
# Consider this to be a one-time setup for the whole blueprints:
|
||||
pass
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@in_out_report_bp.route("/generate", methods = ["GET"])
|
||||
@set_api_version(api_version = "1.0.0")
|
||||
@read_input(sanitize_headers = False, sanitize_data = False)
|
||||
# @get_session_info(key = "X-User-Id", session_coro = "get_session")
|
||||
# @log_request_to_mongo(
|
||||
# attr_name = "logs_mongo",
|
||||
# project = constants.PROJECT_NAME,
|
||||
# log_type = constants.MODULE_NAME,
|
||||
# operation = "ytLnkAddApi",
|
||||
# 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")
|
||||
@validate_input(
|
||||
header_validator = lambda x: SimpleCosecCredentialsHeaders(**x).model_dump(),
|
||||
data_validator = lambda x: CosecInOutReportRequestData(**x)
|
||||
)
|
||||
@handle_cancelled_request()
|
||||
async def in_out_report_generate(
|
||||
inbound_headers: SimpleCosecCredentialsHeaders | dict = None,
|
||||
inbound_data: CosecInOutReportRequestData | dict = None,
|
||||
inbound_files: dict = None,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
"""
|
||||
To automate the web browser interaction with Cosec's web portal and return a JSON of the actual report content.
|
||||
: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.
|
||||
"""
|
||||
|
||||
current_app.printer("Hi")
|
||||
|
||||
# Start by assuming failure:
|
||||
success = False
|
||||
report_data = None
|
||||
|
||||
# Figure out the paths:
|
||||
base_dir = files.get_parent_directory(
|
||||
files.get_file_directory(include_filename = False),
|
||||
depth = 1
|
||||
)
|
||||
|
||||
# Put together the directory for the drivers, the downloads, etc.:
|
||||
chrome_driver_dir = os.path.join(base_dir, r"drivers/chrome")
|
||||
user_data_dir = os.path.join(base_dir, r"browser/user_data")
|
||||
downloads_dir = os.path.join(base_dir, r"downloads")
|
||||
|
||||
# Show all the paths for debugging:
|
||||
current_app.printer("PATHS:", chrome_driver_dir, user_data_dir, downloads_dir)
|
||||
|
||||
# Create an instance of the automation object:
|
||||
cosec = CosecWeb(
|
||||
cosec_url = inbound_headers.cosecUrl,
|
||||
username = inbound_headers.cosecUsername,
|
||||
password = inbound_headers.cosecPassword,
|
||||
driver_dir = chrome_driver_dir,
|
||||
user_data_dir = user_data_dir,
|
||||
downloads_dir = downloads_dir,
|
||||
)
|
||||
|
||||
# Perform the login:
|
||||
cosec.login(initial_sleep = 2.5)
|
||||
|
||||
# Get the in/out report:
|
||||
report_path = cosec.get_in_out_summary(
|
||||
initial_sleep = 1.0,
|
||||
from_date = inbound_data.fromDate,
|
||||
to_date = inbound_data.toDate,
|
||||
group_ids = inbound_data.groupIds,
|
||||
download_timeout = 60.0
|
||||
)
|
||||
|
||||
# Log out to end the cycle:
|
||||
cosec.logout()
|
||||
|
||||
# Close the browser window:
|
||||
cosec.quit()
|
||||
|
||||
# If we didn't get any path, the download failed:
|
||||
if report_path is None:
|
||||
success = False
|
||||
|
||||
# Convert the In/Out Summary to a Pandas DF:
|
||||
else:
|
||||
success = True
|
||||
report_df = cosec.read_in_out_summary_xls(report_path)
|
||||
report_data = report_df.to_dict(orient = "records")
|
||||
|
||||
# ┳┓
|
||||
# ┣┫┏┓┏┏┓┏┓┏┓┏┏┓
|
||||
# ┛┗┗ ┛┣┛┗┛┛┗┛┗
|
||||
# ┛
|
||||
|
||||
if success: return ResponseModel(
|
||||
status_code = StatusCodes.OK,
|
||||
http_code = HttpCodes.SUCCESS,
|
||||
data = report_data,
|
||||
message = f"Successfully fetched {len(report_data)} records."
|
||||
)
|
||||
else: return ResponseModel(
|
||||
status_code = StatusCodes.FAILED,
|
||||
http_code = HttpCodes.INTERNAL_SERVER_ERROR,
|
||||
message = f"Failed to fetch records. Please try again."
|
||||
)
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MAIN PROGRAM ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,413 @@
|
||||
"""
|
||||
|
||||
AUTHOR:
|
||||
|
||||
Khushal P Soonderji
|
||||
|
||||
DATE:
|
||||
|
||||
Fri, 14th Nov, 2025
|
||||
|
||||
OBJECTIVE:
|
||||
|
||||
This is the central location for the Quart module.
|
||||
We define the app here, and import and attach all blueprints here.
|
||||
|
||||
REFERENCES:
|
||||
|
||||
N/A
|
||||
|
||||
DOWNLOADS:
|
||||
|
||||
N/A
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** IMPORT ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# To make sibling directories accessible for imports:
|
||||
import sys
|
||||
sys.path.append("blueprints")
|
||||
sys.path.append("")
|
||||
|
||||
# For system level activities:
|
||||
import gc
|
||||
import os
|
||||
import psutil
|
||||
|
||||
# For using Quart:
|
||||
from quart import Quart, current_app
|
||||
from quart_cors import cors
|
||||
|
||||
# My utils:
|
||||
from utils_v2.date_time import date_time
|
||||
from utils_v2.api.async_quart import (
|
||||
set_api_version,
|
||||
log_request_to_mongo,
|
||||
)
|
||||
|
||||
# Shared elements:
|
||||
from backend.shared import constants
|
||||
|
||||
# To make REST API calls:
|
||||
import httpx
|
||||
|
||||
# For debugging:
|
||||
from icecream import IceCreamDebugger
|
||||
|
||||
# All the blueprints:
|
||||
from backend.api.blueprints.reports.in_out_report import in_out_report_bp
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MACROS / ONE-TIME INIT ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# Quart related:
|
||||
MODULE_BASE = constants.MODULE_NAME
|
||||
APP_VERSION = constants.APP_VERSION
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** VARIABLES ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# The Quart app:
|
||||
app = Quart(__name__)
|
||||
app = cors(app)
|
||||
# ---
|
||||
app.register_blueprint(in_out_report_bp, url_prefix = f"/{MODULE_BASE}/reports/in-out")
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** FUNCTIONS ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
@app.before_serving
|
||||
@set_api_version(api_version = APP_VERSION)
|
||||
# @log_request_to_mongo(
|
||||
# attr_name = "logs_mongo",
|
||||
# project = constants.PROJECT_NAME,
|
||||
# log_type = constants.MODULE_NAME,
|
||||
# operation = "apiStart",
|
||||
# log_input = True,
|
||||
# log_output = True
|
||||
# )
|
||||
async def app_startup(**kwargs):
|
||||
|
||||
"""
|
||||
To initialize the variables that you would like to use in this module.
|
||||
WARNING: ALL VARIABLES WILL BE INITIALIZED 'n' NUMBER OF TIMES, WHERE 'n' IS THE COUNT OF WORKERS DEPLOYED.
|
||||
SO, IF YOU WANT TO CONNECT TO A DATABASE AND YOU ALLOW A POOL-SIZE OF 10 AND IF YOU DEPLOY 4 WORKERS, YOU WILL END
|
||||
UP WITH 40 CONNECTIONS TO THE DATABASE.
|
||||
:return: None.
|
||||
"""
|
||||
|
||||
# Safe-halt mechanism for upgrades (for a single-worker run):
|
||||
current_app.is_under_maintenance = False
|
||||
|
||||
# ┳┓ ┓ •
|
||||
# ┃┃┏┓┣┓┓┏┏┓┏┓┓┏┓┏┓
|
||||
# ┻┛┗ ┗┛┗┻┗┫┗┫┗┛┗┗┫
|
||||
# ┛ ┛ ┛
|
||||
|
||||
# Debugging:
|
||||
enable_debugging = True if os.environ["DEBUG"].strip().lower() == "true" else False
|
||||
current_app.printer = IceCreamDebugger(prefix = f"{MODULE_BASE} (Q) | ", includeContext = True)
|
||||
if not enable_debugging: current_app.printer.disable()
|
||||
current_app.printer("initializing worker.")
|
||||
|
||||
# ┓┏┏┳┓┏┳┓┏┓ ┏┓┓•
|
||||
# ┣┫ ┃ ┃ ┃┃ ┃ ┃┓┏┓┏┓╋
|
||||
# ┛┗ ┻ ┻ ┣┛ ┗┛┗┗┗ ┛┗┗
|
||||
|
||||
# # Make an instance of an HTTP client to use to make API calls:
|
||||
# current_app.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.
|
||||
# )
|
||||
# )
|
||||
# current_app.printer("HTTP client ready.")
|
||||
|
||||
# ┏┓ ┓ ┓ ┳┓
|
||||
# ┃ ┏┓┏┓┏┫ ┏┓┏┓┏┫ ┃┃┏┓╋┏┓
|
||||
# ┗┛┛ ┗ ┗┻ ┗┻┛┗┗┻ ┻┛┗┻┗┗┻
|
||||
|
||||
# # Get the script credentials and data:
|
||||
# script_id = os.environ["SCRIPT_ID"]
|
||||
# response = await current_app.http_client.get(
|
||||
# url = r"https://nexcom.ditscentre.in/internal/cred/get",
|
||||
# headers = {"X-Script-Id": script_id}
|
||||
# )
|
||||
# script_cred = response.json().get("data")
|
||||
# response = await current_app.http_client.get(
|
||||
# url = r"https://nexcom.ditscentre.in/internal/data/get",
|
||||
# headers = {"X-Script-Id": script_id}
|
||||
# )
|
||||
# current_app.script_data = response.json().get("data")
|
||||
# current_app.printer("Cred and data ready.", type(script_cred), type(current_app.script_data))
|
||||
|
||||
# ┏┓
|
||||
# ┗┓┓┏┏╋┏┓┏┳┓
|
||||
# ┗┛┗┫┛┗┗ ┛┗┗
|
||||
# ┛
|
||||
|
||||
pass
|
||||
|
||||
# ┏┓ ┓
|
||||
# ┃ ┏┓┏┣┓┏┓
|
||||
# ┗┛┗┻┗┛┗┗
|
||||
|
||||
pass
|
||||
|
||||
# ┳┳┓ ┳┓┳┓
|
||||
# ┃┃┃┏┓┏┓┏┓┏┓┃┃┣┫
|
||||
# ┛ ┗┗┛┛┗┗┫┗┛┻┛┻┛
|
||||
# ┛
|
||||
|
||||
pass
|
||||
|
||||
# ┏┓
|
||||
# ┃ ┏┓┏┓┏┓┏┓┏╋┏┓┏┓┏
|
||||
# ┗┛┗┛┛┗┛┗┗ ┗┗┗┛┛ ┛
|
||||
|
||||
pass
|
||||
|
||||
# ┏┓ ┓┓
|
||||
# ┃ ┏┓┏┓╋┏┓┏┓┃┃┏┓┏┓┏
|
||||
# ┗┛┗┛┛┗┗┛ ┗┛┗┗┗ ┛ ┛
|
||||
|
||||
pass
|
||||
|
||||
# ┳┳┓•
|
||||
# ┃┃┃┓┏┏
|
||||
# ┛ ┗┗┛┗
|
||||
|
||||
pass
|
||||
|
||||
# ┏┓┓
|
||||
# ┃ ┃┏┓┏┓┏┓┓┏┏┓
|
||||
# ┗┛┗┗ ┗┻┛┗┗┻┣┛
|
||||
# ┛
|
||||
|
||||
# Remove unwanted/sensitive variables from RAM:
|
||||
# del script_cred
|
||||
gc.collect()
|
||||
|
||||
# Done here!
|
||||
current_app.printer("Worker ready!")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@app.after_serving
|
||||
@set_api_version(api_version = APP_VERSION)
|
||||
# @log_request_to_mongo(
|
||||
# attr_name = "logs_mongo",
|
||||
# project = constants.PROJECT_NAME,
|
||||
# log_type = constants.MODULE_NAME,
|
||||
# operation = "apiStop",
|
||||
# log_input = True,
|
||||
# log_output = True
|
||||
# )
|
||||
async def app_shutdown(**kwargs):
|
||||
|
||||
"""
|
||||
This is called when "app.shutdown()" is called.
|
||||
:return: None.
|
||||
"""
|
||||
|
||||
current_app.printer("Shutting down...")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@app.route(f"/", methods = ["GET", "POST"])
|
||||
@app.route(f"/{MODULE_BASE}", methods = ["GET", "POST"])
|
||||
async def root():
|
||||
|
||||
"""
|
||||
To check if the service is running or not.
|
||||
Use this to monitor the service from your "watchman" script.
|
||||
:return: only "ok"
|
||||
"""
|
||||
|
||||
return "ok"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@app.route(f"/metrics/memory", methods = ["GET", "POST"])
|
||||
@app.route(f"/{MODULE_BASE}/metrics/memory", methods = ["GET", "POST"])
|
||||
async def memory_metrics():
|
||||
|
||||
"""
|
||||
TO measure the metrics of the app.
|
||||
:return: A JSON of the metrics.
|
||||
"""
|
||||
|
||||
# Figure pout the parent process.
|
||||
# This is important for multi-worker environments:
|
||||
parent_pid = os.getppid()
|
||||
parent_process = psutil.Process(parent_pid)
|
||||
parent_mem_info = parent_process.memory_info()
|
||||
|
||||
# Figure out all the children of the parent process:
|
||||
child_pids = [child.pid for child in parent_process.children()]
|
||||
child_processes = [psutil.Process(child_pid) for child_pid in child_pids]
|
||||
child_stats = []
|
||||
for pid, process in zip(child_pids, child_processes):
|
||||
mem_info = process.memory_info()
|
||||
child_stats.append({
|
||||
"pid": pid,
|
||||
"mem": round(mem_info.rss / (1024 ** 2), 2)
|
||||
})
|
||||
|
||||
# Construct the response:
|
||||
metrics_json = {
|
||||
"project": constants.PROJECT_NAME,
|
||||
"parent": {
|
||||
"pid": parent_pid,
|
||||
"mem": round(parent_mem_info.rss / (1024 ** 2), 2)
|
||||
},
|
||||
"children": child_stats,
|
||||
"unit": {
|
||||
"mem": "MB"
|
||||
},
|
||||
"ts": date_time.get_current_ist_date_time(as_string = True)
|
||||
}
|
||||
|
||||
# Done here:
|
||||
return metrics_json
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@app.route(f"/{MODULE_BASE}/debug/<action>", methods = ["POST", "GET"])
|
||||
async def change_debug(action):
|
||||
|
||||
"""
|
||||
Enable or disable debugging for the entire microservice.
|
||||
WARNING: NOT RECOMMENDED FOR MULTI-WORKER DEPLOYMENTS.
|
||||
:param action: "enable" to allow debugging on the terminal, or "disable".
|
||||
:return: "enabled"/"disabled" if successful, else "ok"
|
||||
"""
|
||||
|
||||
# Enable or disable debugging only if the password matches:
|
||||
action = action.lower()
|
||||
if action == "enable": current_app.printer.enable()
|
||||
elif action == "disable": current_app.printer.disable()
|
||||
return "ok"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@app.route(f"/{MODULE_BASE}/maintenance/<action>", methods = ["POST", "GET"])
|
||||
async def change_maintenance(action):
|
||||
|
||||
"""
|
||||
Enable or disable debugging for the entire microservice.
|
||||
WARNING: NOT RECOMMENDED FOR MULTI-WORKER DEPLOYMENTS.
|
||||
:param action: "enable" to stop taking new requests on the API, or "disable".
|
||||
:return: "enabled"/"disabled" if successful, else "ok"
|
||||
"""
|
||||
|
||||
# Enable or disable debugging only if the password matches:
|
||||
action = action.lower()
|
||||
if action == "enable":
|
||||
current_app.is_under_maintenance = True
|
||||
os.environ["IS_UNDER_MAINTENANCE"] = "True"
|
||||
elif action == "disable":
|
||||
current_app.is_under_maintenance = False
|
||||
os.environ["IS_UNDER_MAINTENANCE"] = "False"
|
||||
return "ok"
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MAIN PROGRAM ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
if __name__ == "__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"Microservice for '{MODULE_BASE}' API.")
|
||||
parser.add_argument(
|
||||
"--workers",
|
||||
type = int,
|
||||
help = "The no. of threads to spin up for this instance!",
|
||||
default = 2
|
||||
)
|
||||
parser.add_argument(
|
||||
"--host",
|
||||
type = str,
|
||||
help = "The host for the app. e.g.: '0.0.0.0' or '127.0.0.1'.",
|
||||
default = "127.0.0.1"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--port",
|
||||
type = int,
|
||||
help = "The port no. to bind the app to.",
|
||||
default = 8080
|
||||
)
|
||||
parser.add_argument(
|
||||
"--script-id",
|
||||
type = str,
|
||||
help = "The id of this script (will affect the loaded config)."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--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)
|
||||
|
||||
# Run the gateway:
|
||||
freeze_support()
|
||||
uvicorn.run(
|
||||
app = "main:app",
|
||||
workers = args.workers,
|
||||
host = args.host,
|
||||
port = args.port
|
||||
)
|
||||
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
|
||||
AUTHOR:
|
||||
|
||||
Khushal P Soonderji
|
||||
|
||||
DATE:
|
||||
|
||||
Fri, 14th Nov, 2025
|
||||
|
||||
OBJECTIVE:
|
||||
|
||||
To provide data model(s) for common elements of every REST APi request (like the headers).
|
||||
|
||||
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, AwareDatetime
|
||||
from typing import Optional, Literal
|
||||
|
||||
# My utils:
|
||||
from utils_v2.string import regex
|
||||
from utils_v2.date_time import date_time
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MACROS / ONE-TIME INIT ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# --- Nothing Yet
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** VARIABLES ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# --- Nothing Yet
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** FUNCTIONS ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
class SimpleCosecCredentialsHeaders(BaseModel):
|
||||
|
||||
cosecUrl: str = Field(
|
||||
description = "The URL to Cosec's portal.",
|
||||
frozen = True,
|
||||
alias = "X-Cosec-URL"
|
||||
)
|
||||
|
||||
cosecUsername: str = Field(
|
||||
description = "The username on Cosec's portal.",
|
||||
frozen = True,
|
||||
alias = "X-Cosec-Username"
|
||||
)
|
||||
|
||||
cosecPassword: str = Field(
|
||||
description = "The password on Cosec's portal.",
|
||||
frozen = True,
|
||||
alias = "X-Cosec-Password"
|
||||
)
|
||||
|
||||
# ┏┓ ┏•
|
||||
# ┃ ┏┓┏┓╋┓┏┓
|
||||
# ┗┛┗┛┛┗┛┗┗┫
|
||||
# ┛
|
||||
|
||||
class Config:
|
||||
extra = "allow"
|
||||
|
||||
def model_dump(self, *args, **kwargs):
|
||||
return super().model_dump(*args, by_alias = True, **kwargs)
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MAIN PROGRAM ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
|
||||
AUTHOR:
|
||||
|
||||
Khushal P Soonderji
|
||||
|
||||
DATE:
|
||||
|
||||
Fri, 14th Nov, 2025.
|
||||
|
||||
OBJECTIVE:
|
||||
|
||||
To provide data model(s) for receiving API requests to get In?Out Reports.
|
||||
|
||||
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, AwareDatetime
|
||||
from typing import Optional, Literal, List
|
||||
|
||||
# My utils:
|
||||
from utils_v2.string import regex
|
||||
from utils_v2.date_time import date_time
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MACROS / ONE-TIME INIT ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# --- Nothing Yet
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** VARIABLES ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# --- Nothing Yet
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** FUNCTIONS ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
class CosecInOutReportRequestData(BaseModel):
|
||||
|
||||
fromDate: AwareDatetime | None = Field(
|
||||
description = "The start date from when the in-out report is desired.",
|
||||
frozen = True,
|
||||
default = None
|
||||
)
|
||||
|
||||
toDate: AwareDatetime | None = Field(
|
||||
description = "The end date till when the in-out report is desired.",
|
||||
frozen = True,
|
||||
default = None
|
||||
)
|
||||
|
||||
groupIds: List[str] | str = Field(
|
||||
description = "Each company/entity in Cosec's system is represented by a group id. This is a list of those ids.",
|
||||
frozen = True,
|
||||
)
|
||||
|
||||
# ┏┓ ┏•
|
||||
# ┃ ┏┓┏┓╋┓┏┓
|
||||
# ┗┛┗┛┛┗┛┗┗┫
|
||||
# ┛
|
||||
|
||||
class Config:
|
||||
extra = "forbid"
|
||||
|
||||
# ┓┏ ┓• ┓ •
|
||||
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓┏
|
||||
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗┛
|
||||
|
||||
@field_validator("fromDate", "toDate", mode = "before")
|
||||
def parse_date_time(cls, value):
|
||||
if value is None: value = date_time.get_current_utc_date_time(as_string = False)
|
||||
value = date_time.parse_date_time(
|
||||
input_value = value,
|
||||
date_formats = [
|
||||
"%Y%m%d",
|
||||
"%Y-%m-%d",
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
"%Y-%m-%d %H:%M:%S%z",
|
||||
"%Y-%m-%dT%H:%M:%S",
|
||||
"%Y-%m-%dT%H:%M:%S%z",
|
||||
],
|
||||
timezone = date_time.TIMEZONE_UTC
|
||||
)
|
||||
return value
|
||||
|
||||
@field_validator("groupIds", mode = "after")
|
||||
def parse_group_ids(cls, value):
|
||||
if isinstance(value, str): value = value.split(",")
|
||||
return value
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MAIN PROGRAM ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
|
||||
AUTHOR:
|
||||
|
||||
Khushal P Soonderji
|
||||
|
||||
DATE:
|
||||
|
||||
Fri, 14th Nov, 2025
|
||||
|
||||
OBJECTIVE:
|
||||
|
||||
To keep variables and values that will be shared among various parts of the project.
|
||||
|
||||
REFERENCES:
|
||||
|
||||
N/A
|
||||
|
||||
DOWNLOADS:
|
||||
|
||||
N/A
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** IMPORT ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# To make sibling directories accessible for imports:
|
||||
# ---
|
||||
import sys
|
||||
sys.path.append(".")
|
||||
sys.path.append("..")
|
||||
|
||||
# System-level activities:
|
||||
import os
|
||||
|
||||
# For parsing URLs:
|
||||
import urllib
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MACROS / ONE-TIME INIT ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
APP_VERSION = "1.0.0"
|
||||
PROJECT_NAME = "cosec"
|
||||
MODULE_NAME = "cosec"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,27 @@
|
||||
// Copyright 2015 The Chromium Authors
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google LLC nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -2,7 +2,7 @@
|
||||
|
||||
# Use this to run the microservice without any docker setup.
|
||||
source .venv/bin/activate
|
||||
streamlit run main.py &
|
||||
python3 "$(pwd)/backend/api/main.py" --host "127.0.0.1" --port 5000 --workers 1 --script-id "n/a" --debug
|
||||
deactivate
|
||||
|
||||
# All done:
|
||||
|
||||
@@ -43,7 +43,7 @@ import io
|
||||
import torch
|
||||
from transformers import pipeline
|
||||
|
||||
# To download images from URLs:
|
||||
# To downloads images from URLs:
|
||||
import requests
|
||||
|
||||
# To read images:
|
||||
|
||||
@@ -43,7 +43,7 @@ import io
|
||||
import torch
|
||||
from transformers import pipeline
|
||||
|
||||
# To download images from URLs:
|
||||
# To downloads images from URLs:
|
||||
import requests
|
||||
|
||||
# To read images:
|
||||
|
||||
@@ -1655,14 +1655,14 @@ class AsyncMongoStorage(AsyncMongo):
|
||||
):
|
||||
|
||||
"""
|
||||
Returns a GridOut object so that you can implement your own download logic using the built-in 'read' method.
|
||||
Returns a GridOut object so that you can implement your own downloads logic using the built-in 'read' method.
|
||||
Once the reading is done, use the 'close' method to release the resources used by the stream.
|
||||
:param file_id: (RECOMMENDED) the id of the save file.
|
||||
:param file_name: The name of the saved file. NOT RECOMMENDED because you could have many files with the same
|
||||
name. The best way to tell files apart if from the id.
|
||||
:param session: The session if you need to do this in a transaction.
|
||||
:param raise_exception: Whether, or not, you want to raise an exception when something fails.
|
||||
:return: The download stream that implements the 'read' and 'close' methods, or None if something failed.
|
||||
:return: The downloads stream that implements the 'read' and 'close' methods, or None if something failed.
|
||||
"""
|
||||
|
||||
# Ensure you are connected:
|
||||
@@ -1671,7 +1671,7 @@ class AsyncMongoStorage(AsyncMongo):
|
||||
# Assume failure:
|
||||
stream = None
|
||||
|
||||
# Try to open a download stream:
|
||||
# Try to open a downloads stream:
|
||||
try:
|
||||
|
||||
# If a file id is supplied (preferred way):
|
||||
|
||||
@@ -242,7 +242,7 @@ class GmailMessage:
|
||||
|
||||
"""
|
||||
Add a file as an attachment to the mail. This file, even if possible, will not be rendered on the screen in-line
|
||||
with the body. It will be made available as a download.
|
||||
with the body. It will be made available as a downloads.
|
||||
:param attachment_file: The file that you would like to attach.
|
||||
:param file_name: The name of the file. This is the same name by which it will be downloaded. You need not
|
||||
specify this if the input file is specified as a path. Needed when you give the input file as a buffer.
|
||||
|
||||
@@ -50,7 +50,7 @@ import numpy as np
|
||||
# To run OCR:
|
||||
import easyocr
|
||||
|
||||
# To download images from the web:
|
||||
# To downloads images from the web:
|
||||
import requests
|
||||
|
||||
# My utils:
|
||||
|
||||
@@ -49,7 +49,7 @@ import numpy as np
|
||||
# To run OCR:
|
||||
import easyocr
|
||||
|
||||
# To download images from the web:
|
||||
# To downloads images from the web:
|
||||
import requests
|
||||
|
||||
# My utils:
|
||||
|
||||
@@ -230,7 +230,7 @@ class PDFMaker:
|
||||
|
||||
"""
|
||||
Download a file from a URL.
|
||||
:param url: The URL to download the file from.
|
||||
:param url: The URL to downloads the file from.
|
||||
:param follow_redirects: Whether, or not, to follow along with any redirects when downloading the file.
|
||||
:return: The downloaded file or None.
|
||||
"""
|
||||
@@ -458,7 +458,7 @@ class PDFMaker:
|
||||
"""
|
||||
Download a font from a URL and then register it for use.
|
||||
:param name: The name you would later refer to the font by.
|
||||
:param url: The URL to download the font from.
|
||||
:param url: The URL to downloads the font from.
|
||||
:param follow_redirects: Whether, or not, to follow along with any redirects when downloading the file.
|
||||
:return: True if registered, else False.
|
||||
"""
|
||||
@@ -719,7 +719,7 @@ class PDFMaker:
|
||||
"""
|
||||
Asynchronously downloads an image and returns it as a PIL object.
|
||||
Use this instead of just passing the URL to 'draw_image' for better efficiency.
|
||||
:param url: [str] The URL to download the image from.
|
||||
:param url: [str] The URL to downloads the image from.
|
||||
:param follow_redirects: [bool] Whether, or not, to follow redirect URLs when downloading the file.
|
||||
:param as_pil: [bool] If True, a PIL object will be returned, else a PNG file will be returned in RAM (BytesIO).
|
||||
:param format: [str] The type of output file that you want. Not applicable for PIL objects.
|
||||
@@ -1369,7 +1369,7 @@ class PDFMaker:
|
||||
width = end_x - start_x
|
||||
height = end_y - start_y
|
||||
|
||||
# In case the image is passed as a URL, we try to download it and open it as a PIL object:
|
||||
# In case the image is passed as a URL, we try to downloads it and open it as a PIL object:
|
||||
if isinstance(image, str):
|
||||
if image.startswith("https://") or image.startswith("http://"):
|
||||
image = Image.open(io.BytesIO(requests.get(image).content))
|
||||
|
||||
@@ -405,7 +405,7 @@ class AsyncTelegramBot:
|
||||
) -> TelegramApiResponse:
|
||||
|
||||
"""
|
||||
To download a file from Telegram.
|
||||
To downloads a file from Telegram.
|
||||
NOTE: THIS DOES NOT WORK WHILE A WEBHOOK IS SET UP. YOU CAN EITHER USE THIS OR A WEBHOOK, NOT BOTH
|
||||
SIMULTANEOUSLY.
|
||||
:param file_id: The 'file_id' of this file. Not to be confused with 'file_unique_id'.
|
||||
|
||||
Reference in New Issue
Block a user