Squashed 'utils_v2/' content from commit ef9630d

git-subtree-dir: utils_v2
git-subtree-split: ef9630d728847764d4832ce8f3f956571901383a
This commit is contained in:
2024-10-01 10:33:59 +05:30
commit 8494a2c0c0
84 changed files with 13541 additions and 0 deletions
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1007
View File
File diff suppressed because it is too large Load Diff
+164
View File
@@ -0,0 +1,164 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Thursday, 12th Sept., 2024
OBJECTIVE:
To maintain all status codes in one place.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
from enum import Enum, unique
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
@unique
class HttpCodes(Enum):
"""
Commonly used standard HTTP status codes.
Can be sent after an API Call is processed.
Refer to: https://http.dev/status
NOTE: THIS LIST IS NOT EXHAUSTIVE!
"""
# 1XX - Informational:
CONTINUE = 100 # .............. https://http.dev/102
SWITCHING_PROTOCOLS = 101 # ... https://http.dev/101
PROCESSING = 102 # ............ https://http.dev/102
EARLY_HINTS = 103 # ........... https://http.dev/103
# 2XX - Success:
SUCCESS = 200 # .................. https://http.dev/200
CREATED = 201 # .................. https://http.dev/201
ACCEPTED = 202 # ................. https://http.dev/202
NON_AUTHORITATIVE_INFO = 203 # ... https://http.dev/203
NO_CONTENT = 204 # ............... https://http.dev/204
RESET_CONTENT = 205 # ............ https://http.dev/205
PARTIAL_CONTENT = 206 # .......... https://http.dev/206
MULTI_STATUS = 207 # ............. https://http.dev/207
ALREADY_REPORTED = 208 # ......... https://http.dev/208
THIS_IS_FINE = 218 # ............. https://http.dev/218
IM_USED = 226 # .................. https://http.dev/226
# 3XX - Redirection:
MULTIPLE_CHOICES = 300 # ..... https://http.dev/300
MOVED_PERMANENTLY = 301 # .... https://http.dev/301
MOVED_TEMPORARILY = 302 # .... https://http.dev/302
SEE_OTHER = 303 # ............ https://http.dev/303
NOT_MODIFIED = 304 # ......... https://http.dev/304
USE_PROXY = 305 # ............ https://http.dev/305
SWITCH_PROXY = 306 # ......... https://http.dev/306
TEMPORARY_REDIRECT = 307 # ... https://http.dev/307
PERMANENT_REDIRECT = 308 # ... https://http.dev/308
# 4XX - Client Errors:
BAD_REQUEST = 400 # ..................... https://http.dev/401
UNAUTHORIZED = 401 # .................... https://http.dev/401
PAYMENT_REQUIRED = 402 # ................ https://http.dev/402
FORBIDDEN = 403 # ....................... https://http.dev/403
NOT_FOUND = 404 # ....................... https://http.dev/404
METHOD_NOT_ALLOWED = 405 # .............. https://http.dev/405
NOT_ACCEPTABLE = 406 # .................. https://http.dev/406
PROXY_AUTH_REQUIRED = 407 # ............. https://http.dev/407
REQUEST_TIMEOUT = 408 # ................. https://http.dev/408
CONFLICT = 409 # ........................ https://http.dev/409
GONE = 410 # ............................ https://http.dev/410
LENGTH_REQUIRED = 411 # ................. https://http.dev/411
PRECONDITION_FAILED = 412 # ............. https://http.dev/412
PAYLOAD_TOO_LARGE = 413 # ............... https://http.dev/413
URI_TOO_LONG = 414 # .................... https://http.dev/414
UNSUPPORTED_MEDIA_TYPE = 415 # .......... https://http.dev/415
PAGE_EXPIRED = 419 # .................... https://http.dev/419
TOO_MANY_REQUESTS = 429 # ............... https://http.dev/429
UNAVAILABLE_FOR_LEGAL_REASONS = 451 # ... https://http.dev/451
INVALID_TOKEN = 498 # ................... https://http.dev/498
CLIENT_CLOSED_REQUEST = 499 # ........... https://http.dev/499
# 5XX - Server Errors:
INTERNAL_SERVER_ERROR = 500 # ........... https://http.dev/500
NOT_IMPLEMENTED = 501 # ................. https://http.dev/501
BAD_GATEWAY = 502 # ..................... https://http.dev/502
SERVICE_UNAVAILABLE = 503 # ............. https://http.dev/503
GATEWAY_TIMEOUT = 504 # ................. https://http.dev/504
HTTP_VERSION_NOT_SUPPORTED = 505 # ...... https://http.dev/505
VARIANT_ALSO_NEGOTIATES = 506 # ......... https://http.dev/506
INSUFFICIENT_STORAGE = 507 # ............ https://http.dev/507
LOOP_DETECTED = 508 # ................... https://http.dev/508
BANDWIDTH_LIMIT_EXCEEDED = 509 # ........ https://http.dev/509
WEB_SERVER_DOWN = 521 # ................. https://http.dev/521
ORIGIN_IS_UNREACHABLE = 523 # ........... https://http.dev/523
SERVICE_IS_OVERLOADED = 529 # ........... https://http.dev/529
NETWORK_READ_TIMEOUT_ERROR = 598 # ...... https://http.dev/598
NETWORK_CONNECT_TIMEOUT_ERROR = 599 # ... https://http.dev/599
# ---------------------------------------------------------------------------------------------------------------------
@unique
class StatusCodes(Enum):
"""
To be used internally within the context of your service. Customize these to match your service.
The format is: (SUCCESS_INDICATOR, INTERNAL_NUMERIC_CODE, HTTP_CODE)
Example: (True, 1, 200)
"""
# Legacy Codes:
OK = (True, 1, HttpCodes.SUCCESS.value)
FAILED = (False, 0, HttpCodes.INTERNAL_SERVER_ERROR.value)
PARTIAL_SUCCESS = (True, 2, HttpCodes.PARTIAL_CONTENT.value)
PARTIAL_FAILURE = (False, 3, HttpCodes.PARTIAL_CONTENT.value)
# Authentication Codes:
LOGGED_IN_SUCCESSFULLY = (True, 200, HttpCodes.SUCCESS.value)
LOGIN_FAILED = (False, 201, HttpCodes.UNAUTHORIZED.value)
INVALID_SESSION_TOKEN = (False, 202, HttpCodes.UNAUTHORIZED.value)
AUTHENTICATION_DETAILS_INCOMPLETE = (False, 203, HttpCodes.BAD_REQUEST.value)
# Authorization Codes:
AUTHORIZED_SUCCESSFULLY = (True, 300, HttpCodes.SUCCESS.value)
NOT_ALLOWED = (False, 301, HttpCodes.FORBIDDEN.value)
AUTHORIZATION_DETAILS_INCOMPLETE = (False, 302, HttpCodes.BAD_REQUEST.value)
AUTHORIZATION_FAILED = (False, 303, HttpCodes.UNAUTHORIZED.value)
# General failures:
DOWN_FOR_MAINTENANCE = (False, 800, HttpCodes.SERVICE_UNAVAILABLE.value)
UNKNOWN_ERROR = (False, 801, HttpCodes.INTERNAL_SERVER_ERROR.value)
DATA_INCOMPLETE = (False, 802, HttpCodes.BAD_REQUEST.value)
HEADERS_INCOMPLETE = (False, 803, HttpCodes.BAD_REQUEST.value)
FILES_MISSING = (False, 804, HttpCodes.BAD_REQUEST.value)
CLIENT_CLOSED_REQUEST = (False, 805, HttpCodes.CLIENT_CLOSED_REQUEST.value)
# Validation failure:
DATA_VALIDATION_FAILURE = (False, 900, HttpCodes.BAD_REQUEST.value)
RATE_LIMIT_EXCEEDED = (False, 901, HttpCodes.TOO_MANY_REQUESTS.value)
+198
View File
@@ -0,0 +1,198 @@
"""
AUTHOR:
Khushal P Soonderji
Sharvil J Daiya
DATE:
Saturday, 28th Sept., 2024
OBJECTIVE:
To have one place from where several metrics are measured using easy to use context managers.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# To measure time:
import time
# To capture metrics for Prometheus:
from prometheus_client import Counter, Summary, Gauge
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# Define the Prometheus metrics.
# FOR THE MICROSERVICE AS A WHOLE:
WORKER_COUNT = Counter(
name = "ms_workers_active_total",
documentation = "The number of threads for the microservice being monitored.",
labelnames = ["project_name", "service_name", "host_name"]
)
# Define the Prometheus metrics.
# FOR INDIVIDUAL API ENDPOINTS:
REQUEST_LATENCY = Summary(
name = "http_request_latency_seconds",
documentation = "Latency of HTTP requests in seconds.",
labelnames = ["method", "endpoint", "http_status"]
)
TOTAL_REQUEST_COUNT = Counter(
name = "http_requests_total",
documentation = "Total HTTP requests.",
labelnames = ["method", "endpoint", "http_status"]
)
LIVE_REQUEST_COUNT = Gauge(
name = "http_requests_live_total",
documentation = "To check if an API endpoint is being served right now.",
labelnames = ["endpoint"]
)
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** CLASSES ***
# ***** ****
# *****************************************************************************************************************
class MetricsAPI:
"""
Use this class through its context manager to automatically measure all the metrics in one place.
This was originally created to measure the performance of API endpoints made in Quart, but it should work with
other frameworks as well.
"""
def __init__(
self,
method = None,
endpoint = None,
raise_exception = False
):
# Make provisions for things to note.
# NOTE: THESE MUST BE SET FROM OUTSIDE:
self.method = method
self.endpoint = endpoint
self.http_code = None
self.__raise_exception = raise_exception
async def __aenter__(self):
# Note down the start time immediately:
self.__start_ts = time.perf_counter()
self.__cpu_start_ts = time.process_time()
# Note down the metrics:
LIVE_REQUEST_COUNT.labels(self.endpoint).inc(1)
# Setup done:
return self
async def __aexit__(self, exc_type, exc_value, traceback):
# Note down the metrics:
LIVE_REQUEST_COUNT.labels(self.endpoint).dec(1)
TOTAL_REQUEST_COUNT.labels(
self.method,
self.endpoint,
self.http_code
).inc()
REQUEST_LATENCY.labels(
self.method,
self.endpoint,
self.http_code
).observe(time.perf_counter() - self.__start_ts)
# Handle the exception as per the user's preference:
return False if self.__raise_exception else True
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
import asyncio
import random
from prometheus_client import generate_latest
async def simulate_endpoint():
async with MetricsAPI(
method = random.choice(["GET", "POST"]),
endpoint = f"https://my.domain.com/api/{random.choice([0, 1, 2, 3])}"
) as metrics:
# Simulate some action on some endpoint:
await asyncio.sleep(1.0)
# Note down the values:
metrics.http_code = 200
async def main():
print("Simulating endpoints...")
tasks = [simulate_endpoint() for _ in range(250)]
await asyncio.gather(*tasks)
print("Done!")
print("METRICS:")
print(generate_latest().decode())
asyncio.run(main())
+121
View File
@@ -0,0 +1,121 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Thursday, 12th Sept., 2024
OBJECTIVE:
To have a structure to the response sent from the API calls.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
from pydantic import BaseModel
from typing import Any, Optional, List
from utils_v2.api.codes import StatusCodes, HttpCodes
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** CLASSES ***
# ***** ****
# *****************************************************************************************************************
class ResponseModel(BaseModel):
status_code: StatusCodes
message: Optional[str | List] = None
data: Optional[Any] = None
seconds: Optional[float | int] = None
log_id: Optional[str] = None
http_code: Optional[HttpCodes] = None
api_version: Optional[str] = None
def for_quart(self):
# Construct the basic structure:
response_dict = {
"status": 1 if self.status_code.value[0] else 0,
"code": self.status_code.value[1],
"message": self.message or self.status_code.name.replace("_", " ").lower(),
"data": self.data,
"apiVer": self.api_version
}
# Now add the additional fields:
if self.seconds is not None: response_dict["seconds"] = self.seconds
if self.log_id is not None: response_dict["logId"] = self.log_id
# Figure out the HTTP code:
response_http_code = self.http_code.value if self.http_code is not None else self.status_code.value[2]
# Done here:
return response_dict, response_http_code
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
my_response = ResponseModel(
status_code = StatusCodes.RATE_LIMIT_EXCEEDED
)
my_response.log_id = "abc123"
print(my_response.for_quart())