Spaces:
Running
Running
ok
Browse files- app/api/server.py +0 -16
- app/config.py +1 -1
- app/core/banner.py +0 -20
- app/core/logger.py +10 -59
- requirements.txt +0 -2
- start.sh +1 -63
app/api/server.py
CHANGED
|
@@ -1,24 +1,18 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import asyncio
|
| 4 |
-
import os
|
| 5 |
-
import time
|
| 6 |
from contextlib import asynccontextmanager
|
| 7 |
-
from datetime import datetime, timezone
|
| 8 |
|
| 9 |
from fastapi import FastAPI
|
| 10 |
from fastapi.middleware.cors import CORSMiddleware
|
| 11 |
from fastapi.middleware.gzip import GZipMiddleware
|
| 12 |
|
| 13 |
from app.config import get_settings
|
| 14 |
-
from app.core.banner import print_banner
|
| 15 |
-
from app.core.constants import SUPPORTED_EXTENSIONS
|
| 16 |
from app.core.logger import get_logger
|
| 17 |
from app.api.v1.router import api_v1_router
|
| 18 |
|
| 19 |
_logger = get_logger(__name__)
|
| 20 |
_settings = get_settings()
|
| 21 |
-
_START_TIME = time.time()
|
| 22 |
|
| 23 |
|
| 24 |
async def _self_ping():
|
|
@@ -39,15 +33,8 @@ async def _self_ping():
|
|
| 39 |
|
| 40 |
@asynccontextmanager
|
| 41 |
async def lifespan(app: FastAPI):
|
| 42 |
-
print_banner()
|
| 43 |
-
_logger.info("Server ready at http://%s:%s", _settings.host, _settings.port)
|
| 44 |
-
_logger.info("Swagger UI : http://%s:%s/docs", _settings.host, _settings.port)
|
| 45 |
-
_logger.info("ReDoc : http://%s:%s/redoc", _settings.host, _settings.port)
|
| 46 |
-
_logger.info("Started at : %s", datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC"))
|
| 47 |
asyncio.create_task(_self_ping())
|
| 48 |
-
_logger.info("Self-ping task started (every hour)")
|
| 49 |
yield
|
| 50 |
-
_logger.info("Application shutting down gracefully")
|
| 51 |
|
| 52 |
|
| 53 |
def create_application() -> FastAPI:
|
|
@@ -83,9 +70,6 @@ def create_application() -> FastAPI:
|
|
| 83 |
async def ping():
|
| 84 |
return {"message": f"{_settings.app_name} is running..."}
|
| 85 |
|
| 86 |
-
_max_workers = min(32, (os.cpu_count() or 1) + 4)
|
| 87 |
-
_logger.info("Initialized thread pool with %s workers", _max_workers)
|
| 88 |
-
|
| 89 |
return app
|
| 90 |
|
| 91 |
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import asyncio
|
|
|
|
|
|
|
| 4 |
from contextlib import asynccontextmanager
|
|
|
|
| 5 |
|
| 6 |
from fastapi import FastAPI
|
| 7 |
from fastapi.middleware.cors import CORSMiddleware
|
| 8 |
from fastapi.middleware.gzip import GZipMiddleware
|
| 9 |
|
| 10 |
from app.config import get_settings
|
|
|
|
|
|
|
| 11 |
from app.core.logger import get_logger
|
| 12 |
from app.api.v1.router import api_v1_router
|
| 13 |
|
| 14 |
_logger = get_logger(__name__)
|
| 15 |
_settings = get_settings()
|
|
|
|
| 16 |
|
| 17 |
|
| 18 |
async def _self_ping():
|
|
|
|
| 33 |
|
| 34 |
@asynccontextmanager
|
| 35 |
async def lifespan(app: FastAPI):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
asyncio.create_task(_self_ping())
|
|
|
|
| 37 |
yield
|
|
|
|
| 38 |
|
| 39 |
|
| 40 |
def create_application() -> FastAPI:
|
|
|
|
| 70 |
async def ping():
|
| 71 |
return {"message": f"{_settings.app_name} is running..."}
|
| 72 |
|
|
|
|
|
|
|
|
|
|
| 73 |
return app
|
| 74 |
|
| 75 |
|
app/config.py
CHANGED
|
@@ -13,7 +13,7 @@ class Settings(BaseSettings):
|
|
| 13 |
extra="ignore",
|
| 14 |
)
|
| 15 |
|
| 16 |
-
app_name: str = "
|
| 17 |
app_version: str = "1.0.0"
|
| 18 |
environment: str = "production"
|
| 19 |
host: str = "0.0.0.0"
|
|
|
|
| 13 |
extra="ignore",
|
| 14 |
)
|
| 15 |
|
| 16 |
+
app_name: str = "MarkItDown API"
|
| 17 |
app_version: str = "1.0.0"
|
| 18 |
environment: str = "production"
|
| 19 |
host: str = "0.0.0.0"
|
app/core/banner.py
CHANGED
|
@@ -1,21 +1 @@
|
|
| 1 |
-
from __future__ import annotations
|
| 2 |
|
| 3 |
-
import pyfiglet
|
| 4 |
-
from rich.console import Console
|
| 5 |
-
from rich.rule import Rule
|
| 6 |
-
from rich.text import Text
|
| 7 |
-
|
| 8 |
-
from app.config import get_settings
|
| 9 |
-
|
| 10 |
-
_console = Console()
|
| 11 |
-
_settings = get_settings()
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
def print_banner() -> None:
|
| 15 |
-
art = pyfiglet.figlet_format("ValidOps", font="slant")
|
| 16 |
-
_console.print(Text(art, style="bold cyan"))
|
| 17 |
-
_console.print(f" [bold white]{'Service:':<14}[/bold white] [cyan]{_settings.app_name}[/cyan]")
|
| 18 |
-
_console.print(f" [bold white]{'Version:':<14}[/bold white] [cyan]v{_settings.app_version}[/cyan]")
|
| 19 |
-
_console.print(f" [bold white]{'Environment:':<14}[/bold white] [cyan]{_settings.environment}[/cyan]")
|
| 20 |
-
_console.print(Rule(style="dim cyan"))
|
| 21 |
-
_console.print()
|
|
|
|
|
|
|
| 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/core/logger.py
CHANGED
|
@@ -2,65 +2,16 @@ from __future__ import annotations
|
|
| 2 |
|
| 3 |
import logging
|
| 4 |
import sys
|
| 5 |
-
from pathlib import Path
|
| 6 |
-
from typing import Optional
|
| 7 |
-
|
| 8 |
-
from rich.console import Console
|
| 9 |
-
from rich.logging import RichHandler
|
| 10 |
-
|
| 11 |
-
from app.config import get_settings
|
| 12 |
-
|
| 13 |
-
_settings = get_settings()
|
| 14 |
-
_console = Console()
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
class AppLogger:
|
| 18 |
-
def __init__(self) -> None:
|
| 19 |
-
self._loggers: dict[str, logging.Logger] = {}
|
| 20 |
-
|
| 21 |
-
def get_logger(self, name: str) -> logging.Logger:
|
| 22 |
-
if name in self._loggers:
|
| 23 |
-
return self._loggers[name]
|
| 24 |
-
|
| 25 |
-
logger = logging.getLogger(name)
|
| 26 |
-
logger.setLevel(getattr(logging, _settings.log_level.upper(), logging.INFO))
|
| 27 |
-
logger.handlers.clear()
|
| 28 |
-
logger.propagate = False
|
| 29 |
-
|
| 30 |
-
rich_handler = RichHandler(
|
| 31 |
-
console=_console,
|
| 32 |
-
rich_tracebacks=True,
|
| 33 |
-
show_path=False,
|
| 34 |
-
show_time=True,
|
| 35 |
-
)
|
| 36 |
-
rich_handler.setFormatter(logging.Formatter("%(message)s"))
|
| 37 |
-
logger.addHandler(rich_handler)
|
| 38 |
-
|
| 39 |
-
file_handler = self._build_file_handler()
|
| 40 |
-
if file_handler:
|
| 41 |
-
logger.addHandler(file_handler)
|
| 42 |
-
|
| 43 |
-
self._loggers[name] = logger
|
| 44 |
-
return logger
|
| 45 |
-
|
| 46 |
-
def _build_file_handler(self) -> Optional[logging.FileHandler]:
|
| 47 |
-
log_dir = Path("logs")
|
| 48 |
-
log_dir.mkdir(parents=True, exist_ok=True)
|
| 49 |
-
file_handler = logging.FileHandler(
|
| 50 |
-
log_dir / f"{_settings.app_name}.log",
|
| 51 |
-
encoding="utf-8",
|
| 52 |
-
)
|
| 53 |
-
file_handler.setFormatter(
|
| 54 |
-
logging.Formatter(
|
| 55 |
-
fmt="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
|
| 56 |
-
datefmt="%Y-%m-%d %H:%M:%S",
|
| 57 |
-
)
|
| 58 |
-
)
|
| 59 |
-
return file_handler
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
_logger_factory = AppLogger()
|
| 63 |
|
| 64 |
|
| 65 |
def get_logger(name: str) -> logging.Logger:
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
import logging
|
| 4 |
import sys
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
def get_logger(name: str) -> logging.Logger:
|
| 8 |
+
logger = logging.getLogger(name)
|
| 9 |
+
if not logger.handlers:
|
| 10 |
+
logger.setLevel(logging.INFO)
|
| 11 |
+
handler = logging.StreamHandler(sys.stdout)
|
| 12 |
+
handler.setFormatter(logging.Formatter(
|
| 13 |
+
fmt="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
|
| 14 |
+
datefmt="%Y-%m-%d %H:%M:%S",
|
| 15 |
+
))
|
| 16 |
+
logger.addHandler(handler)
|
| 17 |
+
return logger
|
requirements.txt
CHANGED
|
@@ -5,8 +5,6 @@ pydantic>=2.7.0
|
|
| 5 |
pydantic-settings>=2.0.0
|
| 6 |
python-multipart>=0.0.9
|
| 7 |
httpx>=0.27.0
|
| 8 |
-
pyfiglet>=1.0.0
|
| 9 |
-
rich>=13.7.0
|
| 10 |
numpy>=1.26.0
|
| 11 |
rapidocr-onnxruntime>=1.4.4
|
| 12 |
onnxruntime>=1.18.0
|
|
|
|
| 5 |
pydantic-settings>=2.0.0
|
| 6 |
python-multipart>=0.0.9
|
| 7 |
httpx>=0.27.0
|
|
|
|
|
|
|
| 8 |
numpy>=1.26.0
|
| 9 |
rapidocr-onnxruntime>=1.4.4
|
| 10 |
onnxruntime>=1.18.0
|
start.sh
CHANGED
|
@@ -1,75 +1,13 @@
|
|
| 1 |
#!/usr/bin/env bash
|
| 2 |
set -euo pipefail
|
| 3 |
|
| 4 |
-
|
| 5 |
-
mkdir -p "$LOG_DIR"
|
| 6 |
-
LOG_FILE="$LOG_DIR/startup.log"
|
| 7 |
-
|
| 8 |
-
log() {
|
| 9 |
-
local level="$1"
|
| 10 |
-
shift
|
| 11 |
-
local ts
|
| 12 |
-
ts="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
| 13 |
-
local msg="[$ts] [$level] $*"
|
| 14 |
-
echo "$msg"
|
| 15 |
-
echo "$msg" >> "$LOG_FILE"
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
info() { log "INFO " "$@"; }
|
| 19 |
-
ok() { log "OK " "$@"; }
|
| 20 |
-
warn() { log "WARN " "$@"; }
|
| 21 |
-
err() { log "ERROR" "$@"; }
|
| 22 |
-
step() { echo ""; log "STEP " "──── $* ────"; }
|
| 23 |
-
|
| 24 |
-
info "========================================================"
|
| 25 |
-
info " Reconciliation Non-AI Extractor v1.0.0 — Production Startup"
|
| 26 |
-
info " $(date -u)"
|
| 27 |
-
info "========================================================"
|
| 28 |
-
|
| 29 |
-
step "1/2 Python dependencies"
|
| 30 |
-
|
| 31 |
-
if python -c "import markitdown, fastapi, httpx" 2>/dev/null; then
|
| 32 |
-
ok "Core dependencies verified."
|
| 33 |
-
else
|
| 34 |
-
err "Missing core dependencies. Check requirements.txt installation."
|
| 35 |
-
exit 1
|
| 36 |
-
fi
|
| 37 |
-
|
| 38 |
-
step "2/2 Core functionality check"
|
| 39 |
-
|
| 40 |
-
python - << 'PYEOF' >> "$LOG_FILE" 2>&1
|
| 41 |
-
import sys
|
| 42 |
-
print("[functionality-test] Testing core imports...")
|
| 43 |
-
try:
|
| 44 |
-
import markitdown
|
| 45 |
-
import fastapi
|
| 46 |
-
import httpx
|
| 47 |
-
import pandas
|
| 48 |
-
print("[functionality-test] All core dependencies imported successfully.")
|
| 49 |
-
except ImportError as e:
|
| 50 |
-
print(f"[functionality-test] Import error: {e}")
|
| 51 |
-
sys.exit(1)
|
| 52 |
-
print("[functionality-test] Core functionality check completed.")
|
| 53 |
-
PYEOF
|
| 54 |
-
|
| 55 |
-
if [ $? -eq 0 ]; then
|
| 56 |
-
ok "Core functionality check completed."
|
| 57 |
-
else
|
| 58 |
-
warn "Core functionality check failed. Continuing startup..."
|
| 59 |
-
fi
|
| 60 |
-
|
| 61 |
-
step "Starting FastAPI server"
|
| 62 |
|
| 63 |
HOST="${HOST:-0.0.0.0}"
|
| 64 |
PORT="${PORT:-7860}"
|
| 65 |
WORKERS="${WORKERS:-1}"
|
| 66 |
LOG_LEVEL="${LOG_LEVEL:-info}"
|
| 67 |
|
| 68 |
-
info "Starting uvicorn on $HOST:$PORT (workers=$WORKERS, log_level=$LOG_LEVEL) ..."
|
| 69 |
-
info "Swagger UI : http://$HOST:$PORT/docs"
|
| 70 |
-
info "ReDoc : http://$HOST:$PORT/redoc"
|
| 71 |
-
info "========================================================"
|
| 72 |
-
|
| 73 |
exec python -m uvicorn app.api.server:app \
|
| 74 |
--host "$HOST" \
|
| 75 |
--port "$PORT" \
|
|
|
|
| 1 |
#!/usr/bin/env bash
|
| 2 |
set -euo pipefail
|
| 3 |
|
| 4 |
+
python -c "import markitdown, fastapi, httpx, pandas" 2>/dev/null || { echo "Missing dependencies"; exit 1; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
HOST="${HOST:-0.0.0.0}"
|
| 7 |
PORT="${PORT:-7860}"
|
| 8 |
WORKERS="${WORKERS:-1}"
|
| 9 |
LOG_LEVEL="${LOG_LEVEL:-info}"
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
exec python -m uvicorn app.api.server:app \
|
| 12 |
--host "$HOST" \
|
| 13 |
--port "$PORT" \
|