issoufzousko07 commited on
Commit
52cf52c
Β·
verified Β·
1 Parent(s): 74b2d02

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. database.py +91 -0
  2. main.py +36 -28
  3. upload_space.py +1 -1
database.py CHANGED
@@ -84,6 +84,21 @@ def init_db():
84
  FOREIGN KEY(owner_username) REFERENCES users(username)
85
  )
86
  ''')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
  conn.commit()
89
  conn.close()
@@ -349,6 +364,7 @@ def update_patient(username: str, patient_db_id: int, updates: Dict[str, Any]) -
349
  values.extend([patient_db_id, username])
350
  query = f"UPDATE patients SET {', '.join(fields)} WHERE id = ? AND owner_username = ?"
351
 
 
352
  c.execute(query, values)
353
  count = c.rowcount
354
  conn.commit()
@@ -358,4 +374,79 @@ def update_patient(username: str, patient_db_id: int, updates: Dict[str, Any]) -
358
  logging.error(f"Error updating patient: {e}")
359
  return False
360
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
 
 
84
  FOREIGN KEY(owner_username) REFERENCES users(username)
85
  )
86
  ''')
87
+
88
+ # Create Jobs Table (PERSISTENCE)
89
+ c.execute('''
90
+ CREATE TABLE IF NOT EXISTS jobs (
91
+ id TEXT PRIMARY KEY,
92
+ status TEXT NOT NULL,
93
+ result TEXT, -- JSON serialized
94
+ error TEXT,
95
+ created_at REAL,
96
+ storage_path TEXT,
97
+ username TEXT,
98
+ file_type TEXT,
99
+ FOREIGN KEY(username) REFERENCES users(username)
100
+ )
101
+ ''')
102
 
103
  conn.commit()
104
  conn.close()
 
364
  values.extend([patient_db_id, username])
365
  query = f"UPDATE patients SET {', '.join(fields)} WHERE id = ? AND owner_username = ?"
366
 
367
+
368
  c.execute(query, values)
369
  count = c.rowcount
370
  conn.commit()
 
374
  logging.error(f"Error updating patient: {e}")
375
  return False
376
 
377
+ # --- Job Operations (Persistence) ---
378
+
379
+ import json
380
+
381
+ def create_job(job_data: Dict[str, Any]):
382
+ """Create a new job record."""
383
+ try:
384
+ conn = get_db_connection()
385
+ c = conn.cursor()
386
+ c.execute('''
387
+ INSERT INTO jobs (id, status, result, error, created_at, storage_path, username, file_type)
388
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
389
+ ''', (
390
+ job_data['id'],
391
+ job_data.get('status', 'pending'),
392
+ json.dumps(job_data.get('result')) if job_data.get('result') else None,
393
+ job_data.get('error'),
394
+ job_data['created_at'],
395
+ job_data.get('storage_path'),
396
+ job_data.get('username'),
397
+ job_data.get('file_type')
398
+ ))
399
+ conn.commit()
400
+ conn.close()
401
+ return True
402
+ except Exception as e:
403
+ logging.error(f"Error creating job: {e}")
404
+ return False
405
+
406
+ def get_job(job_id: str) -> Optional[Dict[str, Any]]:
407
+ """Retrieve job by ID."""
408
+ conn = get_db_connection()
409
+ c = conn.cursor()
410
+ c.execute('SELECT * FROM jobs WHERE id = ?', (job_id,))
411
+ row = c.fetchone()
412
+ conn.close()
413
+
414
+ if row:
415
+ job = dict(row)
416
+ if job['result']:
417
+ try:
418
+ job['result'] = json.loads(job['result'])
419
+ except:
420
+ job['result'] = None
421
+ return job
422
+ return None
423
+
424
+ def update_job_status(job_id: str, status: str, result: Optional[Dict] = None, error: Optional[str] = None):
425
+ """Update job status and result."""
426
+ try:
427
+ conn = get_db_connection()
428
+ c = conn.cursor()
429
+
430
+ updates = ["status = ?"]
431
+ params = [status]
432
+
433
+ if result is not None:
434
+ updates.append("result = ?")
435
+ params.append(json.dumps(result))
436
+
437
+ if error is not None:
438
+ updates.append("error = ?")
439
+ params.append(error)
440
+
441
+ params.append(job_id)
442
+
443
+ query = f"UPDATE jobs SET {', '.join(updates)} WHERE id = ?"
444
+ c.execute(query, params)
445
+ conn.commit()
446
+ conn.close()
447
+ return True
448
+ except Exception as e:
449
+ logging.error(f"Error updating job: {e}")
450
+ return False
451
+
452
 
main.py CHANGED
@@ -699,7 +699,7 @@ class FeedbackModel(BaseModel):
699
  # =========================================================================
700
  # GLOBAL STATE
701
  # =========================================================================
702
- jobs: Dict[str, Job] = {}
703
  storage_provider = get_storage_provider(os.getenv("STORAGE_MODE", "LOCAL"))
704
 
705
  # Initialize Database
@@ -1232,12 +1232,20 @@ async def limit_concurrency(request: Request, call_next):
1232
  # =========================================================================
1233
  async def process_analysis(job_id: str, image_bytes: bytes):
1234
  """Background task to run inference and log to registry."""
1235
- job = jobs.get(job_id)
 
1236
  if not job:
 
1237
  return
 
 
 
 
 
1238
 
1239
  logger.info(f"Processing Job {job_id}")
1240
- job.status = JobStatus.PROCESSING
 
1241
  start_time = time.time()
1242
 
1243
  try:
@@ -1247,27 +1255,27 @@ async def process_analysis(job_id: str, image_bytes: bytes):
1247
  loop = asyncio.get_event_loop()
1248
  result = await loop.run_in_executor(None, model_wrapper.predict, image_bytes)
1249
 
1250
- job.result = result
1251
- job.status = JobStatus.COMPLETED
1252
-
1253
  # Calculate computation time
1254
  computation_time_ms = int((time.time() - start_time) * 1000)
1255
 
 
 
 
1256
  # Log to registry (REAL DATA)
1257
- if job.username and result:
1258
  domain = result.get('domain', {}).get('label', 'Unknown')
1259
  top_diag = result.get('specific', [{}])[0].get('label', 'Unknown') if result.get('specific') else 'Unknown'
1260
  confidence = result.get('specific', [{}])[0].get('probability', 0) if result.get('specific') else 0
1261
  priority = result.get('priority', 'Normale')
1262
 
1263
  database.log_analysis(
1264
- username=job.username,
1265
  domain=domain,
1266
  top_diagnosis=top_diag,
1267
  confidence=confidence,
1268
  priority=priority,
1269
  computation_time_ms=computation_time_ms,
1270
- file_type=job.file_type or 'Unknown'
1271
  )
1272
  logger.info(f"βœ… Job {job_id} logged to registry")
1273
 
@@ -1275,8 +1283,7 @@ async def process_analysis(job_id: str, image_bytes: bytes):
1275
 
1276
  except Exception as e:
1277
  logger.error(f"❌ Job {job_id} failed: {str(e)}")
1278
- job.error = str(e)
1279
- job.status = JobStatus.FAILED
1280
 
1281
  # =========================================================================
1282
  # API ENDPOINTS
@@ -1403,15 +1410,16 @@ async def analyze_image(
1403
  else:
1404
  file_type = 'OTHER'
1405
 
1406
- jobs[job_id] = Job(
1407
- id=job_id,
1408
- status=JobStatus.PENDING,
1409
- created_at=time.time(),
1410
- encrypted_user=enc_user,
1411
- storage_path=storage_path,
1412
- username=current_user.username, # For registry logging
1413
- file_type=file_type # For registry logging
1414
- )
 
1415
 
1416
  background_tasks.add_task(process_analysis, job_id, image_bytes)
1417
 
@@ -1425,19 +1433,19 @@ async def get_result(task_id: str, current_user: User = Depends(get_current_user
1425
  - **Requires authentication**
1426
  - Returns job status and results when complete
1427
  """
1428
- job = jobs.get(task_id)
1429
  if not job:
1430
  logger.warning(f"Job not found: {task_id}")
 
 
1431
  raise HTTPException(status_code=404, detail="Job not found")
1432
 
1433
- # Verify ownership: decrypt stored user and compare
1434
- if job.encrypted_user:
1435
- stored_user = encryption.decrypt_data(job.encrypted_user)
1436
- if stored_user != current_user.username:
1437
- logger.warning(f"Unauthorized access attempt to job {task_id} by {current_user.username}")
1438
- raise HTTPException(status_code=403, detail="Access denied")
1439
 
1440
- logger.info(f"Polling Job {task_id}: Status={job.status}")
1441
  return job
1442
 
1443
  @app.get("/health")
 
699
  # =========================================================================
700
  # GLOBAL STATE
701
  # =========================================================================
702
+ jobs: Dict[str, Job] = {} # REMOVED: Now using SQLite persistence
703
  storage_provider = get_storage_provider(os.getenv("STORAGE_MODE", "LOCAL"))
704
 
705
  # Initialize Database
 
1232
  # =========================================================================
1233
  async def process_analysis(job_id: str, image_bytes: bytes):
1234
  """Background task to run inference and log to registry."""
1235
+ # RESILIENCE: Retrieve job from DB
1236
+ job = database.get_job(job_id)
1237
  if not job:
1238
+ logger.error(f"❌ Job {job_id} not found in DB during background processing")
1239
  return
1240
+
1241
+ # We must construct a Job object or just work with the dict
1242
+ # Let's work with the dict for consistency, or simple variables
1243
+ username = job.get('username')
1244
+ file_type = job.get('file_type')
1245
 
1246
  logger.info(f"Processing Job {job_id}")
1247
+ database.update_job_status(job_id, JobStatus.PROCESSING.value)
1248
+
1249
  start_time = time.time()
1250
 
1251
  try:
 
1255
  loop = asyncio.get_event_loop()
1256
  result = await loop.run_in_executor(None, model_wrapper.predict, image_bytes)
1257
 
 
 
 
1258
  # Calculate computation time
1259
  computation_time_ms = int((time.time() - start_time) * 1000)
1260
 
1261
+ # Update Job in DB
1262
+ database.update_job_status(job_id, JobStatus.COMPLETED.value, result=result)
1263
+
1264
  # Log to registry (REAL DATA)
1265
+ if username and result:
1266
  domain = result.get('domain', {}).get('label', 'Unknown')
1267
  top_diag = result.get('specific', [{}])[0].get('label', 'Unknown') if result.get('specific') else 'Unknown'
1268
  confidence = result.get('specific', [{}])[0].get('probability', 0) if result.get('specific') else 0
1269
  priority = result.get('priority', 'Normale')
1270
 
1271
  database.log_analysis(
1272
+ username=username,
1273
  domain=domain,
1274
  top_diagnosis=top_diag,
1275
  confidence=confidence,
1276
  priority=priority,
1277
  computation_time_ms=computation_time_ms,
1278
+ file_type=file_type or 'Unknown'
1279
  )
1280
  logger.info(f"βœ… Job {job_id} logged to registry")
1281
 
 
1283
 
1284
  except Exception as e:
1285
  logger.error(f"❌ Job {job_id} failed: {str(e)}")
1286
+ database.update_job_status(job_id, JobStatus.FAILED.value, error=str(e))
 
1287
 
1288
  # =========================================================================
1289
  # API ENDPOINTS
 
1410
  else:
1411
  file_type = 'OTHER'
1412
 
1413
+ # Persist Job to DB
1414
+ job_data = {
1415
+ "id": job_id,
1416
+ "status": JobStatus.PENDING.value,
1417
+ "created_at": time.time(),
1418
+ "storage_path": storage_path,
1419
+ "username": current_user.username,
1420
+ "file_type": file_type
1421
+ }
1422
+ database.create_job(job_data)
1423
 
1424
  background_tasks.add_task(process_analysis, job_id, image_bytes)
1425
 
 
1433
  - **Requires authentication**
1434
  - Returns job status and results when complete
1435
  """
1436
+ job = database.get_job(task_id)
1437
  if not job:
1438
  logger.warning(f"Job not found: {task_id}")
1439
+ # If job is lost (server restart before persistence, or bad ID), return 404
1440
+ # Frontend should handle this by stopping polling
1441
  raise HTTPException(status_code=404, detail="Job not found")
1442
 
1443
+ # Verify ownership
1444
+ if job.get('username') != current_user.username:
1445
+ logger.warning(f"Unauthorized access attempt to job {task_id} by {current_user.username}")
1446
+ raise HTTPException(status_code=403, detail="Access denied")
 
 
1447
 
1448
+ logger.info(f"Polling Job {task_id}: Status={job.get('status')}")
1449
  return job
1450
 
1451
  @app.get("/health")
upload_space.py CHANGED
@@ -17,5 +17,5 @@ upload_folder(
17
  ignore_patterns=["models/*", "*.pyc", "__pycache__", "*.db", "storage/*", "data_storage/*", ".env", "venv", ".git", ".idea"]
18
  )
19
 
20
- print("βœ… Upload complete!")
21
  print("Your Space should start building at: https://huggingface.co/spaces/issoufzousko07/elephmind-api")
 
17
  ignore_patterns=["models/*", "*.pyc", "__pycache__", "*.db", "storage/*", "data_storage/*", ".env", "venv", ".git", ".idea"]
18
  )
19
 
20
+ print("[OK] Upload complete!")
21
  print("Your Space should start building at: https://huggingface.co/spaces/issoufzousko07/elephmind-api")