File size: 6,207 Bytes
0f9f853 2646146 c2e02ba 0b5fa25 0f9f853 72f4cb5 d055f1a 0b5fa25 0f9f853 1436e78 c2e02ba 0f9f853 0b5fa25 0f9f853 c2e02ba 0f9f853 2646146 0f9f853 2646146 0f9f853 1436e78 0f9f853 1436e78 0f9f853 0b5fa25 0f9f853 0b5fa25 0f9f853 25a7cd3 0f9f853 0b5fa25 0f9f853 0b5fa25 0f9f853 0b5fa25 0f9f853 0b5fa25 0f9f853 0b5fa25 0f9f853 0b5fa25 0f9f853 0b5fa25 0f9f853 0b5fa25 0f9f853 0b5fa25 0f9f853 5c3c401 d055f1a 0f9f853 dd6dfe4 0f9f853 dd6dfe4 0f9f853 dd6dfe4 0f9f853 dd6dfe4 0f9f853 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
"""FastAPI application entry point."""
import json
import logging
import importlib
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from routes import category, summary, keyword, lda, entity # pylint: disable=import-error
class Config: # pylint: disable=too-few-public-methods
"""
Configuration class for the FastAPI application.
This class contains configuration settings for the application, such as
the database URL and other environment-specific settings.
"""
def __init__(self):
self.scheduler_api_enabled = True
self.jobs = self.load_jobs()
def load_jobs(self):
"""
Loads scheduled jobs from a JSON file.
Returns:
dict: A dictionary containing the scheduled jobs.
"""
try:
with open('jobs.json', 'r', encoding='utf-8') as jobs_file:
jobs_data = json.load(jobs_file)
# Return the jobs list if jobs.json has {"jobs": [...]} structure
if isinstance(jobs_data, dict) and 'jobs' in jobs_data:
return jobs_data['jobs']
# Return as-is if it's already a list
if isinstance(jobs_data, list):
return jobs_data
logging.warning("Invalid jobs.json format")
return []
except (FileNotFoundError, json.JSONDecodeError) as e:
logging.error("Error loading jobs: %s", e)
return []
#Global scheduler instance
scheduler = AsyncIOScheduler()
config = Config()
@asynccontextmanager
async def lifespan(_app: FastAPI):
"""
Lifespan context manager for the FastAPI application.
This function initializes the scheduler and sets up the application
lifespan context.
"""
#startup
logging.basicConfig(
format='%(asctime)s - %(levelname)s - %(funcName)s - %(message)s'
)
logging.getLogger().setLevel(logging.ERROR)
#start scheduler anf add jobs
scheduler.start()
await setup_scheduled_jobs()
logging.info("Application startup complete")
yield
#shutdown
# Shutdown
logging.info("Shutting down scheduler...")
scheduler.shutdown()
logging.info("Application shutdown complete")
async def setup_scheduled_jobs():
"""
Set up scheduled jobs based on the configuration.
This function iterates through the jobs defined in the configuration
and schedules them using the APScheduler.
"""
if not config.jobs:
logging.info("No jobs to schedule")
return
for job_config in config.jobs:
try:
#get the function reference
func_ref = get_function_reference(job_config['func'])
if not func_ref:
logging.error("Function %s not found", job_config['func'])
continue
#Create the trigger
trigger = CronTrigger(
hour=job_config.get('hour', '*'),
minute=job_config.get('minute', '*'),
second=job_config.get('second', '0')
)
#add the job to the scheduler
scheduler.add_job(
func=func_ref,
trigger=trigger,
id=job_config['id'],
replace_existing=True
)
logging.info("Scheduled job: %s", job_config['id'])
except (FileNotFoundError, json.JSONDecodeError, ImportError, AttributeError) as e:
logging.error("Failed to schedule job %s: %s", job_config['id'], e)
def get_function_reference(func_string):
"""
Converts a string representation of a function to an actual function reference.
Supports formats like:
- "module.function"
- "package.module.function"
- "package.module:function"
Args:
func_string (str): String representation of the function
Returns:
callable: The function reference, or None if not found
"""
try:
# Handle both ":" and "." as separators
if ':' in func_string:
module_path, func_name = func_string.rsplit(':', 1)
elif '.' in func_string:
parts = func_string.split('.')
func_name = parts[-1]
module_path = '.'.join(parts[:-1])
else:
logging.error("Invalid function string format: %s", func_string)
return None
# Import the module
module = importlib.import_module(module_path)
# Get the function
func = getattr(module, func_name)
return func
except (ImportError, AttributeError) as e:
logging.error("Could not import function %s: %s", func_string, e)
return None
#Create FastAPI app instance
app = FastAPI(docs_url="/", lifespan=lifespan)
#include CORS middleware
origins = [
"*"
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials = True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(category.router)
app.include_router(summary.router)
app.include_router(keyword.router)
app.include_router(lda.router)
app.include_router(entity.router)
@app.get("/_health")
def health():
"""
Returns the health status of the application.
:return: A string "OK" indicating the health status.
"""
return "OK"
@app.get("/scheduler/status")
async def scheduler_status():
"""Get scheduler status and job information."""
if not config.scheduler_api_enabled:
return JSONResponse({"error": "Scheduler API disabled"}, status_code=403)
jobs = []
for job in scheduler.get_jobs():
jobs.append({
"id": job.id,
"name": job.name,
"func": str(job.func),
"trigger": str(job.trigger),
"next_run": job.next_run_time.isoformat() if job.next_run_time else None
})
return {
"scheduler_running": scheduler.running,
"job_count": len(jobs),
"jobs": jobs
}
|