deploy: sync backend to Space root (learn-lesson HF cache fix)
Browse files- app/core/http_status.py +20 -0
- app/routes/auth.py +12 -0
- app/routes/chat_history.py +2 -1
- app/services/file_storage.py +2 -1
- tests/test_http_status.py +6 -0
- tests/test_textbook_video_ingestion.py +15 -8
app/core/http_status.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Starlette status aliases that work on both old and new releases.
|
| 2 |
+
|
| 3 |
+
Starlette renamed 413/422 constants. CI's newer package has the new names and
|
| 4 |
+
deprecates the old ones; older local installs only have the old names.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
from starlette import status as starlette_status
|
| 10 |
+
|
| 11 |
+
HTTP_413_CONTENT_TOO_LARGE = getattr(
|
| 12 |
+
starlette_status,
|
| 13 |
+
"HTTP_413_CONTENT_TOO_LARGE",
|
| 14 |
+
starlette_status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
| 15 |
+
)
|
| 16 |
+
HTTP_422_UNPROCESSABLE_CONTENT = getattr(
|
| 17 |
+
starlette_status,
|
| 18 |
+
"HTTP_422_UNPROCESSABLE_CONTENT",
|
| 19 |
+
starlette_status.HTTP_422_UNPROCESSABLE_ENTITY,
|
| 20 |
+
)
|
app/routes/auth.py
CHANGED
|
@@ -633,6 +633,18 @@ def session(current_user: User = Depends(require_user)) -> User:
|
|
| 633 |
return current_user
|
| 634 |
|
| 635 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 636 |
@router.get("/google/start", name="google_start")
|
| 637 |
def google_start(
|
| 638 |
request: Request,
|
|
|
|
| 633 |
return current_user
|
| 634 |
|
| 635 |
|
| 636 |
+
@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
| 637 |
+
def logout(response: Response) -> None:
|
| 638 |
+
"""Clear the backend-scoped media session cookie for this browser."""
|
| 639 |
+
production = _is_production_environment()
|
| 640 |
+
response.delete_cookie(
|
| 641 |
+
MEDIA_AUTH_COOKIE,
|
| 642 |
+
path="/generated",
|
| 643 |
+
secure=production,
|
| 644 |
+
samesite="none" if production else "lax",
|
| 645 |
+
)
|
| 646 |
+
|
| 647 |
+
|
| 648 |
@router.get("/google/start", name="google_start")
|
| 649 |
def google_start(
|
| 650 |
request: Request,
|
app/routes/chat_history.py
CHANGED
|
@@ -11,6 +11,7 @@ from sqlalchemy.orm import Session
|
|
| 11 |
|
| 12 |
from app.core.auth import require_user
|
| 13 |
from app.core.database import get_db
|
|
|
|
| 14 |
from app.models.chat_session import ChatMessageRecord, ChatSession
|
| 15 |
from app.models.document import Document
|
| 16 |
from app.models.user import User
|
|
@@ -121,7 +122,7 @@ def create_session(
|
|
| 121 |
context_source_id = body.context_data.source_id if body.context_data else None
|
| 122 |
if body.source_id and context_source_id and body.source_id != context_source_id:
|
| 123 |
raise HTTPException(
|
| 124 |
-
status_code=
|
| 125 |
detail="The chat source and academic context source must match.",
|
| 126 |
)
|
| 127 |
source_id = body.source_id or context_source_id
|
|
|
|
| 11 |
|
| 12 |
from app.core.auth import require_user
|
| 13 |
from app.core.database import get_db
|
| 14 |
+
from app.core.http_status import HTTP_422_UNPROCESSABLE_CONTENT
|
| 15 |
from app.models.chat_session import ChatMessageRecord, ChatSession
|
| 16 |
from app.models.document import Document
|
| 17 |
from app.models.user import User
|
|
|
|
| 122 |
context_source_id = body.context_data.source_id if body.context_data else None
|
| 123 |
if body.source_id and context_source_id and body.source_id != context_source_id:
|
| 124 |
raise HTTPException(
|
| 125 |
+
status_code=HTTP_422_UNPROCESSABLE_CONTENT,
|
| 126 |
detail="The chat source and academic context source must match.",
|
| 127 |
)
|
| 128 |
source_id = body.source_id or context_source_id
|
app/services/file_storage.py
CHANGED
|
@@ -5,6 +5,7 @@ import shutil
|
|
| 5 |
|
| 6 |
from fastapi import HTTPException, UploadFile, status
|
| 7 |
|
|
|
|
| 8 |
from app.utils.ids import prefixed_id
|
| 9 |
|
| 10 |
|
|
@@ -78,7 +79,7 @@ def save_upload_file(upload_file: UploadFile, upload_dir: Path) -> StoredFile:
|
|
| 78 |
if too_large:
|
| 79 |
destination.unlink(missing_ok=True)
|
| 80 |
raise HTTPException(
|
| 81 |
-
status_code=
|
| 82 |
detail=(
|
| 83 |
f"File exceeds the 20 MB limit "
|
| 84 |
f"({total_bytes / (1024 * 1024):.1f} MB uploaded so far). "
|
|
|
|
| 5 |
|
| 6 |
from fastapi import HTTPException, UploadFile, status
|
| 7 |
|
| 8 |
+
from app.core.http_status import HTTP_413_CONTENT_TOO_LARGE
|
| 9 |
from app.utils.ids import prefixed_id
|
| 10 |
|
| 11 |
|
|
|
|
| 79 |
if too_large:
|
| 80 |
destination.unlink(missing_ok=True)
|
| 81 |
raise HTTPException(
|
| 82 |
+
status_code=HTTP_413_CONTENT_TOO_LARGE,
|
| 83 |
detail=(
|
| 84 |
f"File exceeds the 20 MB limit "
|
| 85 |
f"({total_bytes / (1024 * 1024):.1f} MB uploaded so far). "
|
tests/test_http_status.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app.core.http_status import HTTP_413_CONTENT_TOO_LARGE, HTTP_422_UNPROCESSABLE_CONTENT
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def test_http_status_aliases_are_valid_codes() -> None:
|
| 5 |
+
assert HTTP_413_CONTENT_TOO_LARGE == 413
|
| 6 |
+
assert HTTP_422_UNPROCESSABLE_CONTENT == 422
|
tests/test_textbook_video_ingestion.py
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
|
|
|
|
|
| 1 |
from pathlib import Path
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
|
| 13 |
def test_contents_parser_preserves_book_page_numbers() -> None:
|
|
|
|
| 1 |
+
import importlib.util
|
| 2 |
+
import sys
|
| 3 |
from pathlib import Path
|
| 4 |
|
| 5 |
+
_SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "ingest_sslc_physics_textbooks.py"
|
| 6 |
+
_SPEC = importlib.util.spec_from_file_location("ingest_sslc_physics_textbooks", _SCRIPT)
|
| 7 |
+
assert _SPEC and _SPEC.loader
|
| 8 |
+
_INGEST = importlib.util.module_from_spec(_SPEC)
|
| 9 |
+
sys.modules[_SPEC.name] = _INGEST
|
| 10 |
+
_SPEC.loader.exec_module(_INGEST)
|
| 11 |
+
|
| 12 |
+
ChapterCandidate = _INGEST.ChapterCandidate
|
| 13 |
+
classify_block = _INGEST.classify_block
|
| 14 |
+
detect_chapters = _INGEST.detect_chapters
|
| 15 |
+
find_chapter_pdf_page = _INGEST.find_chapter_pdf_page
|
| 16 |
+
parse_contents_chapters = _INGEST.parse_contents_chapters
|
| 17 |
+
stable_hash = _INGEST.stable_hash
|
| 18 |
|
| 19 |
|
| 20 |
def test_contents_parser_preserves_book_page_numbers() -> None:
|