(20251114) Ready to start API testing.

This commit is contained in:
2025-11-14 18:20:54 +05:30
parent 6e19717e0b
commit 5ac5ad9ece
31 changed files with 32194 additions and 15 deletions
+2 -1
View File
@@ -5,4 +5,5 @@ __pycache__/
*.pem *.pem
*.pyc *.pyc
*.pyd *.pyd
/downloads/
View File
View File
View File
@@ -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
+413
View File
@@ -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
)
View File
View File
+115
View File
@@ -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
+137
View File
@@ -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
View File
+55
View File
@@ -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"
View File
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.
+27
View File
@@ -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.
+1 -1
View File
@@ -2,7 +2,7 @@
# Use this to run the microservice without any docker setup. # Use this to run the microservice without any docker setup.
source .venv/bin/activate 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 deactivate
# All done: # All done:
@@ -43,7 +43,7 @@ import io
import torch import torch
from transformers import pipeline from transformers import pipeline
# To download images from URLs: # To downloads images from URLs:
import requests import requests
# To read images: # To read images:
@@ -43,7 +43,7 @@ import io
import torch import torch
from transformers import pipeline from transformers import pipeline
# To download images from URLs: # To downloads images from URLs:
import requests import requests
# To read images: # To read images:
+3 -3
View File
@@ -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. 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_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 :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. 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 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. :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: # Ensure you are connected:
@@ -1671,7 +1671,7 @@ class AsyncMongoStorage(AsyncMongo):
# Assume failure: # Assume failure:
stream = None stream = None
# Try to open a download stream: # Try to open a downloads stream:
try: try:
# If a file id is supplied (preferred way): # 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 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 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 :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. specify this if the input file is specified as a path. Needed when you give the input file as a buffer.
+1 -1
View File
@@ -50,7 +50,7 @@ import numpy as np
# To run OCR: # To run OCR:
import easyocr import easyocr
# To download images from the web: # To downloads images from the web:
import requests import requests
# My utils: # My utils:
+1 -1
View File
@@ -49,7 +49,7 @@ import numpy as np
# To run OCR: # To run OCR:
import easyocr import easyocr
# To download images from the web: # To downloads images from the web:
import requests import requests
# My utils: # My utils:
+4 -4
View File
@@ -230,7 +230,7 @@ class PDFMaker:
""" """
Download a file from a URL. 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. :param follow_redirects: Whether, or not, to follow along with any redirects when downloading the file.
:return: The downloaded file or None. :return: The downloaded file or None.
""" """
@@ -458,7 +458,7 @@ class PDFMaker:
""" """
Download a font from a URL and then register it for use. 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 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. :param follow_redirects: Whether, or not, to follow along with any redirects when downloading the file.
:return: True if registered, else False. :return: True if registered, else False.
""" """
@@ -719,7 +719,7 @@ class PDFMaker:
""" """
Asynchronously downloads an image and returns it as a PIL object. 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. 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 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 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. :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 width = end_x - start_x
height = end_y - start_y 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 isinstance(image, str):
if image.startswith("https://") or image.startswith("http://"): if image.startswith("https://") or image.startswith("http://"):
image = Image.open(io.BytesIO(requests.get(image).content)) image = Image.open(io.BytesIO(requests.get(image).content))
+1 -1
View File
@@ -405,7 +405,7 @@ class AsyncTelegramBot:
) -> TelegramApiResponse: ) -> 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 NOTE: THIS DOES NOT WORK WHILE A WEBHOOK IS SET UP. YOU CAN EITHER USE THIS OR A WEBHOOK, NOT BOTH
SIMULTANEOUSLY. SIMULTANEOUSLY.
:param file_id: The 'file_id' of this file. Not to be confused with 'file_unique_id'. :param file_id: The 'file_id' of this file. Not to be confused with 'file_unique_id'.