Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ All custom exceptions inherit from `OpenRAGError` (`openrag/utils/exceptions/`):

Uses Loguru with structured logging:
```python
from utils.logger import get_logger
from core.utils.logging import get_logger
logger = get_logger()
logger.bind(file_id=file_id, partition=partition).info("Message")
```
Expand All @@ -323,7 +323,7 @@ Use absolute imports from the `openrag/` directory (which is the Python path roo
```python
# Correct - absolute imports
from components.ray_utils import call_ray_actor_with_timeout
from utils.logger import get_logger
from core.utils.logging import get_logger
from config import load_config

# Avoid relative imports across packages
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/documentation/milvus_migration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ Milvus migration: <description> (schema version N-1 → N)
"""

from pymilvus import DataType, MilvusClient
from utils.logger import get_logger
from core.utils.logging import get_logger

TARGET_VERSION = N # replace with the actual version number

Expand Down
2 changes: 1 addition & 1 deletion openrag/api/dependencies/auth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import os

from core.utils.exceptions import OpenRAGError
from core.utils.logging import get_logger
from di.providers import get_auth_service, get_config, get_job_service, get_partition_service
from fastapi import Depends, HTTPException, Request, status
from utils.logger import get_logger

logger = get_logger()

Expand Down
37 changes: 37 additions & 0 deletions openrag/api/dependencies/files.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from pathlib import Path
from typing import Any

import aiofiles
import consts
from core.indexing import validators as core_validators
from core.utils.exceptions import ValidationError
from core.utils.filename import make_unique_filename
from di.providers import get_config
from fastapi import Depends, Form, UploadFile

Expand Down Expand Up @@ -29,3 +34,35 @@ async def validate_file_format(
mimetype=metadata.get("mimetype"),
)
return file


async def save_file_to_disk(
file: UploadFile,
dest_dir: Path,
chunk_size: int = consts.FILE_READ_CHUNK_SIZE,
with_random_prefix: bool = False,
) -> Path:
"""Save an uploaded file to disk in chunks and return the saved path."""
dest_dir.mkdir(parents=True, exist_ok=True)
dest_dir = dest_dir.resolve()

raw_filename = file.filename or ""
safe_basename = raw_filename.replace("\\", "/").split("/")[-1].strip()
if not safe_basename:
raise ValidationError("Uploaded file must have a filename.", status_code=400)

filename = make_unique_filename(safe_basename) if with_random_prefix else safe_basename
file_path = (dest_dir / filename).resolve()
try:
file_path.relative_to(dest_dir)
except ValueError:
raise ValidationError("Uploaded filename resolves outside destination directory.", status_code=400)

async with aiofiles.open(file_path, "wb") as buffer:
while True:
chunk = await file.read(chunk_size)
if not chunk:
break
await buffer.write(chunk)

return file_path
2 changes: 1 addition & 1 deletion openrag/api/dependencies/llm.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import consts
import openai
from api.dependencies.auth import SUPER_ADMIN_MODE
from core.utils.logging import get_logger
from di.providers import get_config
from fastapi import Depends, HTTPException, status
from openai import AsyncOpenAI
from utils.logger import get_logger

logger = get_logger()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
from pathlib import Path

import pytest
from fastapi import HTTPException, UploadFile

from .files import extract_temporal_fields, sanitize_filename, save_file_to_disk
from api.dependencies.files import save_file_to_disk
from core.utils.exceptions import ValidationError
from core.utils.filename import extract_temporal_fields, sanitize_filename
from fastapi import UploadFile


@pytest.mark.asyncio
Expand Down Expand Up @@ -36,7 +37,7 @@ def fake_make_unique_filename(filename: str) -> str:
return "PREFIX_1234_test.txt"

monkeypatch.setattr(
"openrag.components.indexer.utils.files.make_unique_filename",
"api.dependencies.files.make_unique_filename",
fake_make_unique_filename,
)

Expand All @@ -59,6 +60,31 @@ def fake_make_unique_filename(filename: str) -> str:
assert saved_path.read_bytes() == file_content


@pytest.mark.asyncio
async def test_save_file_to_disk_strips_path_components(tmp_path):
upload = UploadFile(
filename="../../nested/evil.txt",
file=io.BytesIO(b"content"),
)

saved_path = await save_file_to_disk(file=upload, dest_dir=tmp_path)

assert saved_path.parent == tmp_path.resolve()
assert saved_path.name == "evil.txt"
assert saved_path.read_bytes() == b"content"


@pytest.mark.asyncio
async def test_save_file_to_disk_rejects_empty_filename(tmp_path):
upload = UploadFile(
filename="",
file=io.BytesIO(b"content"),
)

with pytest.raises(ValidationError):
await save_file_to_disk(file=upload, dest_dir=tmp_path)


@pytest.mark.parametrize(
"input_name,expected",
[
Expand Down Expand Up @@ -105,8 +131,8 @@ def test_extract_temporal_fields_with_timezone():


def test_extract_temporal_fields_invalid_datetime_raises_400():
with pytest.raises(HTTPException) as exc_info:
with pytest.raises(ValidationError) as exc_info:
extract_temporal_fields({"created_at": "not-a-date"}, ["created_at"])
assert exc_info.value.status_code == 400
assert "not-a-date" in exc_info.value.detail
assert "created_at" in exc_info.value.detail
assert "not-a-date" in str(exc_info.value)
assert "created_at" in str(exc_info.value)
2 changes: 1 addition & 1 deletion openrag/api/error_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
StorageError,
ValidationError,
)
from core.utils.logging import get_logger
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from utils.logger import get_logger

logger = get_logger()

Expand Down
4 changes: 2 additions & 2 deletions openrag/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
from api.routers.user.extract import router as extract_router
from api.routers.user.health import router as health_router
from api.routers.user.search import router as search_router
from config import load_config
from core.config import load_config
from core.utils.logging import get_logger
from di.container import ServiceContainer
from di.providers import set_container
from di.workers import ensure_worker_bootstrap
Expand All @@ -61,7 +62,6 @@
from fastapi.openapi.utils import get_openapi
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from utils.logger import get_logger

# pydub 0.25.1 ships invalid-escape regex literals; the warning is upstream.
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pydub")
Expand Down
4 changes: 2 additions & 2 deletions openrag/api/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@
reset_auth_context,
set_auth_context,
)
from config import load_config
from core.config import load_config
from core.utils.log_tail import app_log_file
from core.utils.logging import get_logger
from di.container import ServiceContainer
from di.workers import ensure_worker_bootstrap
from mcp.server.fastmcp import FastMCP
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse
from utils.logger import get_logger

logger = get_logger()

Expand Down
9 changes: 3 additions & 6 deletions openrag/api/middleware/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@
from typing import Any
from urllib.parse import quote

from components.auth.refresh import refresh_session_if_needed
from core.config.auth import AuthBypassConfig
from core.utils.logging import get_logger
from fastapi import Request
from fastapi.responses import JSONResponse, RedirectResponse
from starlette.middleware.base import BaseHTTPMiddleware
from utils.logger import get_logger

logger = get_logger()

Expand Down Expand Up @@ -164,10 +163,9 @@ async def dispatch(self, request: Request, call_next):
if cookie_token:
session = await auth_service.get_oidc_session_by_token_for_request(cookie_token)
if session is not None:
refreshed = await refresh_session_if_needed(
refreshed = await auth_service.refresh_session_if_needed(
session=session,
enc_key=enc_key,
auth_service=auth_service,
)
if refreshed is None:
# Refresh failed or session unusable → revoke and fall through.
Expand Down Expand Up @@ -199,10 +197,9 @@ async def dispatch(self, request: Request, call_next):
if auth_mode == "oidc":
session = await auth_service.get_oidc_session_by_token_for_request(token)
if session is not None:
refreshed = await refresh_session_if_needed(
refreshed = await auth_service.refresh_session_if_needed(
session=session,
enc_key=enc_key,
auth_service=auth_service,
)
if refreshed is None:
try:
Expand Down
2 changes: 1 addition & 1 deletion openrag/api/middleware/instrumentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

import time

from core.observability.monitoring import record_request
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
from utils.monitoring import record_request

# Paths to exclude from metric recording — avoids self-referential noise
# and inflated counters on probe traffic.
Expand Down
2 changes: 1 addition & 1 deletion openrag/api/routers/admin/cluster.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from api.dependencies.auth import require_admin
from core.utils.logging import get_logger
from di.workers import list_ray_actors as list_ray_actor_states
from di.workers import restart_ray_actor
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import JSONResponse
from utils.logger import get_logger

logger = get_logger()

Expand Down
5 changes: 3 additions & 2 deletions openrag/api/routers/admin/indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
require_task_owner,
)
from api.dependencies.files import (
save_file_to_disk,
validate_file_format,
validate_file_id,
validate_metadata,
)
from api.routers.admin.task_logs import collect_task_logs
from components.indexer.utils.files import sanitize_filename, save_file_to_disk
from core.utils.filename import sanitize_filename
from core.utils.log_tail import app_log_file
from core.utils.logging import get_logger
from di.providers import get_auth_service, get_config, get_indexing_service, get_partition_service
from fastapi import (
APIRouter,
Expand All @@ -42,7 +44,6 @@
status,
)
from fastapi.responses import JSONResponse
from utils.logger import get_logger

logger = get_logger()

Expand Down
2 changes: 1 addition & 1 deletion openrag/api/routers/admin/monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
import asyncio

from api.dependencies.auth import require_admin
from core.observability.monitoring import get_metrics
from fastapi import APIRouter, Depends
from fastapi.responses import Response
from utils.monitoring import get_metrics

router = APIRouter()

Expand Down
2 changes: 1 addition & 1 deletion openrag/api/routers/admin/partitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
require_partition_owner,
require_partition_viewer,
)
from core.utils.logging import get_logger
from di.providers import get_partition_service
from fastapi import APIRouter, Depends, Form, HTTPException, Request, Response, status
from fastapi.responses import JSONResponse
from utils.logger import get_logger

logger = get_logger()
router = APIRouter()
Expand Down
4 changes: 2 additions & 2 deletions openrag/api/routers/admin/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
from pathlib import Path

from api.dependencies.files import (
save_file_to_disk,
validate_file_format,
validate_metadata,
)
from api.schemas.admin.tools import ToolInfo
from components.indexer.utils.files import save_file_to_disk
from core.utils.logging import get_logger
from di.providers import get_config, get_conversion_service
from fastapi import APIRouter, Depends, Form, HTTPException, UploadFile, status
from fastapi.responses import JSONResponse
from utils.logger import get_logger

logger = get_logger()

Expand Down
2 changes: 1 addition & 1 deletion openrag/api/routers/admin/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@

from api.dependencies.auth import current_user, require_admin, require_admin_or_self
from api.schemas.admin.users import UserCreate, UserPublic, UserUpdate
from core.utils.logging import get_logger
from di.providers import get_user_service
from fastapi import APIRouter, Depends, HTTPException, Response, status
from fastapi.responses import JSONResponse
from utils.logger import get_logger

logger = get_logger()
router = APIRouter()
Expand Down
2 changes: 1 addition & 1 deletion openrag/api/routers/admin/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

from api.dependencies.auth import require_partition_editor, require_partition_owner, require_partition_viewer
from api.schemas.admin.workspaces import AddFilesRequest, CreateWorkspaceRequest
from core.utils.logging import get_logger
from di.providers import get_workspace_service
from fastapi import APIRouter, Depends, HTTPException, status
from utils.logger import get_logger

router = APIRouter()
logger = get_logger()
Expand Down
4 changes: 2 additions & 2 deletions openrag/api/routers/auth/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@

import os

from components.auth import StateCookieSerializer
from core.auth.state_cookie import StateCookieSerializer
from core.utils.exceptions import OpenRAGError
from core.utils.logging import get_logger
from di.providers import get_auth_service
from fastapi import APIRouter, Depends, Form, HTTPException, Request, Response, status
from fastapi.responses import JSONResponse, RedirectResponse
from utils.logger import get_logger

logger = get_logger()
router = APIRouter()
Expand Down
9 changes: 4 additions & 5 deletions openrag/api/routers/user/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@
)
from api.routers.user.source_links import build_document_source_link
from api.schemas.user.chat import OpenAIChatCompletionRequest, OpenAICompletionRequest
from components.indexer.utils.text_sanitizer import sanitize_text
from components.utils import get_num_tokens
from config import load_config
from core.config import load_config
from core.utils.exceptions import OpenRAGError
from core.utils.logging import get_logger
from core.utils.text import get_num_tokens, sanitize_text
from di.providers import get_config, get_partition_service, get_query_service
from fastapi import APIRouter, Body, Depends, HTTPException, Request, status
from fastapi.responses import JSONResponse, StreamingResponse
from utils.exceptions.base import OpenRAGError
from utils.logger import get_logger

logger = get_logger()
router = APIRouter()
Expand Down
Loading
Loading