edusync / app /core /config.py
sakshusat's picture
feat: implement initial face recognition service with API endpoints for registration, attendance, and anti-spoofing capabilities.
e51f7f4
import logging
import requests
import threading
import time
import os
from pydantic import Field, AliasChoices
from pydantic_settings import BaseSettings, SettingsConfigDict
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("face-service-config")
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore"
)
PROJECT_NAME: str = "FaceAttendanceAPI"
MONGODB_URL: str = Field("mongodb://localhost:27017", validation_alias=AliasChoices("MONGODB_URL", "MONGODB_URI"))
DB_NAME: str = "EduSync"
COLLECTION_NAME: str = "students"
# Model settings (Default values, will be overridden by Core Server)
FACE_SIMILARITY_THRESHOLD: float = 0.48
LIVENESS_THRESHOLD: float = 0.20
BLINK_EAR_THRESHOLD: float = 0.18
# Lighting Settings
LIGHTING_BRIGHTNESS_MIN: float = 60
LIGHTING_BRIGHTNESS_FAIR_LOW: float = 90
LIGHTING_BRIGHTNESS_FAIR_HIGH: float = 210
LIGHTING_BRIGHTNESS_MAX: float = 180
LIGHTING_VARIANCE_THRESHOLD: float = 5000
# Platform settings
CORE_SERVER_URL: str = Field("https://edusyncserver.onrender.com", validation_alias=AliasChoices("CORE_SERVER_URL"))
MODEL_PATH: str = "./models/w600k_mbf.onnx"
# --- BONUS FIX #1: SECURITY INTEGRITY ---
# Python API Key for server-to-server auth.
# In production, must be set to a strong random string.
FACE_API_KEY: str = ""
def update_from_server(self):
"""Fetch latest config from Core Server."""
try:
logger.info(f"🔄 Syncing system config from {self.CORE_SERVER_URL}")
# Use API key if available
headers = {"x-api-key": self.FACE_API_KEY} if self.FACE_API_KEY else {}
response = requests.get(f"{self.CORE_SERVER_URL}/api/v1/config/system", headers=headers, timeout=15)
if response.status_code == 200:
data = response.json()
if data.get("success") and "config" in data:
remote_config = data["config"]
mapping = {
"faceSimilarityThreshold": "FACE_SIMILARITY_THRESHOLD",
"livenessThreshold": "LIVENESS_THRESHOLD",
"blinkEarThreshold": "BLINK_EAR_THRESHOLD",
"lightingBrightnessMin": "LIGHTING_BRIGHTNESS_MIN",
"lightingBrightnessFairLow": "LIGHTING_BRIGHTNESS_FAIR_LOW",
"lightingBrightnessFairHigh": "LIGHTING_BRIGHTNESS_FAIR_HIGH",
"lightingBrightnessMax": "LIGHTING_BRIGHTNESS_MAX",
"lightingVarianceThreshold": "LIGHTING_VARIANCE_THRESHOLD"
}
for remote_key, local_key in mapping.items():
if remote_key in remote_config:
setattr(self, local_key, remote_config[remote_key])
logger.info("✅ System config updated from server")
else:
logger.warning(f"⚠️ Failed to sync config: HTTP {response.status_code}")
except Exception as e:
logger.error(f"❌ Config sync failure: {e}. Using local defaults.")
def start_config_sync(self):
"""
Starts a background thread to sync config every 10 minutes.
Uses a file-based lock to ensure only ONE sync thread runs across all workers.
"""
lock_file = "config_sync.lock"
try:
# Atomic lock creation
fd = os.open(lock_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
os.write(fd, str(os.getpid()).encode())
def sync_worker():
while True:
time.sleep(600) # 10 mins
self.update_from_server()
thread = threading.Thread(target=sync_worker, daemon=True)
thread.start()
logger.info(f"🚀 [Worker {os.getpid()}] Config sync thread started.")
except FileExistsError:
# Handle stale lock
try:
if time.time() - os.path.getmtime(lock_file) > 900:
os.remove(lock_file)
return self.start_config_sync()
except Exception:
pass
logger.info("ℹ️ Config sync already active in another worker.")
except Exception as e:
logger.error(f"⚠️ Could not start config sync: {e}")
settings = Settings()