initializer service deployed and tested
This commit is contained in:
16
api_services/api_initializer/app.py
Normal file
16
api_services/api_initializer/app.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import uvicorn
|
||||
|
||||
from api_initializer.config import api_config
|
||||
from api_initializer.create_app import create_app
|
||||
|
||||
# from prometheus_fastapi_instrumentator import Instrumentator
|
||||
|
||||
|
||||
app = create_app() # Create FastAPI application
|
||||
# Instrumentator().instrument(app=app).expose(app=app) # Setup Prometheus metrics
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the application with Uvicorn Server
|
||||
uvicorn_config = uvicorn.Config(**api_config.app_as_dict)
|
||||
uvicorn.Server(uvicorn_config).run()
|
||||
64
api_services/api_initializer/config.py
Normal file
64
api_services/api_initializer/config.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
|
||||
class Configs(BaseSettings):
|
||||
"""
|
||||
ApiTemplate configuration settings.
|
||||
"""
|
||||
|
||||
PATH: str = ""
|
||||
HOST: str = ""
|
||||
PORT: int = 0
|
||||
LOG_LEVEL: str = "info"
|
||||
RELOAD: int = 0
|
||||
ACCESS_TOKEN_TAG: str = ""
|
||||
|
||||
ACCESS_EMAIL_EXT: str = ""
|
||||
TITLE: str = ""
|
||||
ALGORITHM: str = ""
|
||||
ACCESS_TOKEN_LENGTH: int = 90
|
||||
REFRESHER_TOKEN_LENGTH: int = 144
|
||||
EMAIL_HOST: str = ""
|
||||
DATETIME_FORMAT: str = ""
|
||||
FORGOT_LINK: str = ""
|
||||
ALLOW_ORIGINS: list = ["http://localhost:3000", "http://localhost:3001"]
|
||||
VERSION: str = "0.1.001"
|
||||
DESCRIPTION: str = ""
|
||||
|
||||
@property
|
||||
def app_as_dict(self) -> dict:
|
||||
"""
|
||||
Convert the settings to a dictionary.
|
||||
"""
|
||||
return {
|
||||
"app": self.PATH,
|
||||
"host": self.HOST,
|
||||
"port": int(self.PORT),
|
||||
"log_level": self.LOG_LEVEL,
|
||||
"reload": bool(self.RELOAD),
|
||||
}
|
||||
|
||||
@property
|
||||
def api_info(self):
|
||||
"""
|
||||
Returns a dictionary with application information.
|
||||
"""
|
||||
return {
|
||||
"title": self.TITLE,
|
||||
"description": self.DESCRIPTION,
|
||||
"default_response_class": JSONResponse,
|
||||
"version": self.VERSION,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def forgot_link(cls, forgot_key):
|
||||
"""
|
||||
Generate a forgot password link.
|
||||
"""
|
||||
return cls.FORGOT_LINK + forgot_key
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="API_")
|
||||
|
||||
|
||||
api_config = Configs()
|
||||
57
api_services/api_initializer/create_app.py
Normal file
57
api_services/api_initializer/create_app.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import RedirectResponse
|
||||
from event_clusters import RouterCluster, EventCluster
|
||||
from config import api_config
|
||||
|
||||
cluster_is_set = False
|
||||
|
||||
|
||||
def create_events_if_any_cluster_set():
|
||||
import events
|
||||
|
||||
global cluster_is_set
|
||||
if not events.__all__ or cluster_is_set:
|
||||
return
|
||||
|
||||
router_cluster_stack: list[RouterCluster] = [getattr(events, e, None) for e in events.__all__]
|
||||
for router_cluster in router_cluster_stack:
|
||||
event_cluster_stack: list[EventCluster] = list(router_cluster.event_clusters.values())
|
||||
for event_cluster in event_cluster_stack:
|
||||
try:
|
||||
event_cluster.set_events_to_database()
|
||||
except Exception as e:
|
||||
print(f"Error creating event cluster: {e}")
|
||||
|
||||
cluster_is_set = True
|
||||
|
||||
|
||||
def create_app():
|
||||
from open_api_creator import create_openapi_schema
|
||||
from middlewares.token_middleware import token_middleware
|
||||
from create_route import RouteRegisterController
|
||||
from endpoints.routes import get_routes
|
||||
|
||||
application = FastAPI(**api_config.api_info)
|
||||
application.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=api_config.ALLOW_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@application.middleware("http")
|
||||
async def add_token_middleware(request: Request, call_next):
|
||||
return await token_middleware(request, call_next)
|
||||
|
||||
@application.get("/", description="Redirect Route", include_in_schema=False)
|
||||
async def redirect_to_docs():
|
||||
return RedirectResponse(url="/docs")
|
||||
|
||||
route_register = RouteRegisterController(app=application, router_list=get_routes())
|
||||
application = route_register.register_routes()
|
||||
|
||||
create_events_if_any_cluster_set()
|
||||
application.openapi = lambda _=application: create_openapi_schema(_)
|
||||
return application
|
||||
58
api_services/api_initializer/create_route.py
Normal file
58
api_services/api_initializer/create_route.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from typing import List
|
||||
from fastapi import APIRouter, FastAPI
|
||||
|
||||
|
||||
class RouteRegisterController:
|
||||
|
||||
def __init__(self, app: FastAPI, router_list: List[APIRouter]):
|
||||
self.router_list = router_list
|
||||
self.app = app
|
||||
|
||||
@staticmethod
|
||||
def add_router_with_event_to_database(router: APIRouter):
|
||||
from schemas import EndpointRestriction
|
||||
|
||||
# Endpoint operation_id is static now if record exits update() record else create()
|
||||
with EndpointRestriction.new_session() as db_session:
|
||||
for route in router.routes:
|
||||
route_path = str(getattr(route, "path"))
|
||||
route_summary = str(getattr(route, "name"))
|
||||
operation_id = getattr(route, "operation_id", None)
|
||||
if not operation_id:
|
||||
raise ValueError(f"Route {route_path} operation_id is not found")
|
||||
|
||||
for route_method in [
|
||||
method.lower() for method in getattr(route, "methods")
|
||||
]:
|
||||
methods = [method.lower() for method in getattr(route, "methods")]
|
||||
print('methods count : ', len(methods))
|
||||
print(dict(
|
||||
route_method=route_method,
|
||||
operation_uu_id=operation_id,
|
||||
route_path=route_path,
|
||||
route_summary=route_summary,
|
||||
))
|
||||
# add_or_update_dict = dict(
|
||||
# endpoint_method=route_method,
|
||||
# endpoint_name=route_path,
|
||||
# endpoint_desc=route_summary.replace("_", " "),
|
||||
# endpoint_function=route_summary,
|
||||
# operation_uu_id=operation_id,
|
||||
# is_confirmed=True,
|
||||
# )
|
||||
# endpoint_restriction_found = EndpointRestriction.filter_one_system(
|
||||
# EndpointRestriction.operation_uu_id == operation_id, db=db_session,
|
||||
# ).data
|
||||
# if endpoint_restriction_found:
|
||||
# endpoint_restriction_found.update(**add_or_update_dict, db=db_session)
|
||||
# endpoint_restriction_found.save(db=db_session)
|
||||
# else:
|
||||
# restriction = EndpointRestriction.find_or_create(**add_or_update_dict, db=db_session)
|
||||
# if restriction.meta_data.created:
|
||||
# restriction.save(db=db_session)
|
||||
|
||||
def register_routes(self):
|
||||
for router in self.router_list:
|
||||
self.app.include_router(router)
|
||||
self.add_router_with_event_to_database(router)
|
||||
return self.app
|
||||
134
api_services/api_initializer/event_clusters.py
Normal file
134
api_services/api_initializer/event_clusters.py
Normal file
@@ -0,0 +1,134 @@
|
||||
from typing import Optional, Type
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class EventCluster:
|
||||
"""
|
||||
EventCluster
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f"EventCluster(name={self.name})"
|
||||
|
||||
def __init__(self, endpoint_uu_id: str, name: str):
|
||||
self.endpoint_uu_id = endpoint_uu_id
|
||||
self.name = name
|
||||
self.events: list["Event"] = []
|
||||
|
||||
def add_event(self, event: "Event"):
|
||||
"""
|
||||
Add an event to the cluster
|
||||
"""
|
||||
if event.key not in [e.key for e in self.events]:
|
||||
self.events.append(event)
|
||||
|
||||
def get_event(self, event_key: str):
|
||||
"""
|
||||
Get an event by its key
|
||||
"""
|
||||
|
||||
for event in self.events:
|
||||
if event.key == event_key:
|
||||
return event
|
||||
return None
|
||||
|
||||
def set_events_to_database(self):
|
||||
from schemas import Events, EndpointRestriction
|
||||
|
||||
with Events.new_session() as db_session:
|
||||
# if to_save_endpoint := EndpointRestriction.filter_one(
|
||||
# EndpointRestriction.operation_uu_id == self.endpoint_uu_id,
|
||||
# db=db_session,
|
||||
# ).data:
|
||||
for event in self.events:
|
||||
event_dict_to_save = dict(
|
||||
function_code=event.key,
|
||||
function_class=event.name,
|
||||
description=event.description,
|
||||
endpoint_code=self.endpoint_uu_id,
|
||||
endpoint_id=to_save_endpoint.id,
|
||||
endpoint_uu_id=str(to_save_endpoint.uu_id),
|
||||
is_confirmed=True,
|
||||
db=db_session,
|
||||
)
|
||||
print('event_dict_to_save', event_dict_to_save)
|
||||
# event_found = Events.filter_one(
|
||||
# Events.function_code == event_dict_to_save["function_code"],
|
||||
# db=db_session,
|
||||
# ).data
|
||||
# if event_found:
|
||||
# event_found.update(**event_dict_to_save)
|
||||
# event_found.save(db=db_session)
|
||||
# else:
|
||||
# event_to_save_database = Events.find_or_create(
|
||||
# **event_dict_to_save,
|
||||
# include_args=[
|
||||
# Events.function_code,
|
||||
# Events.function_class,
|
||||
# Events.endpoint_code,
|
||||
# Events.endpoint_uu_id,
|
||||
# ]
|
||||
# )
|
||||
# if event_to_save_database.meta_data.created:
|
||||
# print(f"UUID: {event_to_save_database.uu_id} event is saved to {to_save_endpoint.uu_id}")
|
||||
# event_to_save_database.save(db=db_session)
|
||||
|
||||
def match_event(self, event_key: str) -> "Event":
|
||||
"""
|
||||
Match an event by its key
|
||||
"""
|
||||
if event := self.get_event(event_key=event_key):
|
||||
return event
|
||||
raise ValueError("Event key not found")
|
||||
|
||||
|
||||
class Event:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
key: str,
|
||||
request_validator: Optional[Type[BaseModel]] = None,
|
||||
response_validator: Optional[Type[BaseModel]] = None,
|
||||
description: str = "",
|
||||
):
|
||||
self.name = name
|
||||
self.key = key
|
||||
self.request_validator = request_validator
|
||||
self.response_validator = response_validator
|
||||
self.description = description
|
||||
|
||||
def event_callable(self):
|
||||
"""
|
||||
Example callable method
|
||||
"""
|
||||
print(self.name)
|
||||
return {}
|
||||
|
||||
|
||||
class RouterCluster:
|
||||
"""
|
||||
RouterCluster
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return f"RouterCluster(name={self.name})"
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.event_clusters: dict[str, EventCluster] = {}
|
||||
|
||||
def set_event_cluster(self, event_cluster: EventCluster):
|
||||
"""
|
||||
Add an event cluster to the set
|
||||
"""
|
||||
print("Setting event cluster:", event_cluster.name)
|
||||
if event_cluster.name not in self.event_clusters:
|
||||
self.event_clusters[event_cluster.name] = event_cluster
|
||||
|
||||
def get_event_cluster(self, event_cluster_name: str) -> EventCluster:
|
||||
"""
|
||||
Get an event cluster by its name
|
||||
"""
|
||||
if event_cluster_name not in self.event_clusters:
|
||||
raise ValueError("Event cluster not found")
|
||||
return self.event_clusters[event_cluster_name]
|
||||
116
api_services/api_initializer/open_api_creator.py
Normal file
116
api_services/api_initializer/open_api_creator.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from typing import Any, Dict
|
||||
from fastapi import FastAPI
|
||||
from fastapi.routing import APIRoute
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
|
||||
from endpoints.routes import get_safe_endpoint_urls
|
||||
from config import api_config
|
||||
|
||||
|
||||
class OpenAPISchemaCreator:
|
||||
"""
|
||||
OpenAPI schema creator and customizer for FastAPI applications.
|
||||
"""
|
||||
|
||||
def __init__(self, app: FastAPI):
|
||||
"""
|
||||
Initialize the OpenAPI schema creator.
|
||||
|
||||
Args:
|
||||
app: FastAPI application instance
|
||||
"""
|
||||
self.app = app
|
||||
self.safe_endpoint_list: list[tuple[str, str]] = get_safe_endpoint_urls()
|
||||
self.routers_list = self.app.routes
|
||||
|
||||
@staticmethod
|
||||
def create_security_schemes() -> Dict[str, Any]:
|
||||
"""
|
||||
Create security scheme definitions.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Security scheme configurations
|
||||
"""
|
||||
|
||||
return {
|
||||
"BearerAuth": {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": api_config.ACCESS_TOKEN_TAG,
|
||||
"description": "Enter: **'Bearer <JWT>'**, where JWT is the access token",
|
||||
}
|
||||
}
|
||||
|
||||
def configure_route_security(
|
||||
self, path: str, method: str, schema: Dict[str, Any]
|
||||
) -> None:
|
||||
"""
|
||||
Configure security requirements for a specific route.
|
||||
|
||||
Args:
|
||||
path: Route path
|
||||
method: HTTP method
|
||||
schema: OpenAPI schema to modify
|
||||
"""
|
||||
if not schema.get("paths", {}).get(path, {}).get(method):
|
||||
return
|
||||
|
||||
# Check if endpoint is in safe list
|
||||
endpoint_path = f"{path}:{method}"
|
||||
list_of_safe_endpoints = [
|
||||
f"{e[0]}:{str(e[1]).lower()}" for e in self.safe_endpoint_list
|
||||
]
|
||||
if endpoint_path not in list_of_safe_endpoints:
|
||||
if "security" not in schema["paths"][path][method]:
|
||||
schema["paths"][path][method]["security"] = []
|
||||
schema["paths"][path][method]["security"].append({"BearerAuth": []})
|
||||
|
||||
def create_schema(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Create the complete OpenAPI schema.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Complete OpenAPI schema
|
||||
"""
|
||||
openapi_schema = get_openapi(
|
||||
title=api_config.TITLE,
|
||||
description=api_config.DESCRIPTION,
|
||||
version=api_config.VERSION,
|
||||
routes=self.app.routes,
|
||||
)
|
||||
|
||||
# Add security schemes
|
||||
if "components" not in openapi_schema:
|
||||
openapi_schema["components"] = {}
|
||||
|
||||
openapi_schema["components"]["securitySchemes"] = self.create_security_schemes()
|
||||
|
||||
# Configure route security and responses
|
||||
for route in self.app.routes:
|
||||
if isinstance(route, APIRoute) and route.include_in_schema:
|
||||
path = str(route.path)
|
||||
methods = [method.lower() for method in route.methods]
|
||||
for method in methods:
|
||||
self.configure_route_security(path, method, openapi_schema)
|
||||
|
||||
# Add custom documentation extensions
|
||||
openapi_schema["x-documentation"] = {
|
||||
"postman_collection": "/docs/postman",
|
||||
"swagger_ui": "/docs",
|
||||
"redoc": "/redoc",
|
||||
}
|
||||
return openapi_schema
|
||||
|
||||
|
||||
def create_openapi_schema(app: FastAPI) -> Dict[str, Any]:
|
||||
"""
|
||||
Create OpenAPI schema for a FastAPI application.
|
||||
|
||||
Args:
|
||||
app: FastAPI application instance
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Complete OpenAPI schema
|
||||
"""
|
||||
creator = OpenAPISchemaCreator(app)
|
||||
return creator.create_schema()
|
||||
8
api_services/api_initializer/wh.py
Normal file
8
api_services/api_initializer/wh.py
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import time
|
||||
|
||||
while True:
|
||||
|
||||
time.sleep(10)
|
||||
Reference in New Issue
Block a user