xce009 commited on
Commit
19bd230
·
1 Parent(s): 4789772

added return json

Browse files
Files changed (2) hide show
  1. app/api/v1/batch.py +72 -8
  2. app/models/schemas.py +3 -0
app/api/v1/batch.py CHANGED
@@ -2,13 +2,15 @@ from __future__ import annotations
2
 
3
  import asyncio
4
  import concurrent.futures
 
5
  import os
6
  import time
7
- from typing import Annotated, List
8
 
9
- from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
 
10
 
11
- from app.api.deps import get_converter_service, require_auth
12
  from app.config import get_settings
13
  from app.core.logger import get_logger
14
  from app.models.domain import ConversionError
@@ -19,6 +21,7 @@ from app.models.schemas import (
19
  ConversionMetadata,
20
  )
21
  from app.services.converter_service import ConverterService
 
22
 
23
  router = APIRouter()
24
  _logger = get_logger(__name__)
@@ -68,14 +71,24 @@ def _batch_result_from_ok(result) -> BatchFileResult:
68
  )
69
  async def batch_files(
70
  files: Annotated[List[UploadFile], File(description="Files to convert")],
 
 
71
  token: str = Depends(require_auth),
72
  converter_service: ConverterService = Depends(get_converter_service),
 
73
  ):
74
  if not files:
75
  raise HTTPException(status_code=400, detail={"success": False, "message": "No files provided."})
76
  if len(files) > _MAX_BATCH_FILES:
77
  raise HTTPException(status_code=400, detail={"success": False, "message": f"Maximum {_MAX_BATCH_FILES} files per batch."})
78
 
 
 
 
 
 
 
 
79
  batch_start = time.perf_counter()
80
 
81
  async def process_single_file(f: UploadFile) -> BatchFileResult:
@@ -92,11 +105,23 @@ async def batch_files(
92
  )
93
  loop = asyncio.get_running_loop()
94
  outcome = await loop.run_in_executor(_thread_pool, converter_service.convert_stream, raw, f.filename or "upload")
95
- return (
96
- _batch_result_from_error(f.filename or "unknown", outcome)
97
- if isinstance(outcome, ConversionError)
98
- else _batch_result_from_ok(outcome)
99
- )
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  tasks = [process_single_file(f) for f in files]
102
  results = await asyncio.gather(*tasks)
@@ -122,11 +147,50 @@ async def batch_urls(
122
  body: BatchUrlRequest,
123
  token: str = Depends(require_auth),
124
  converter_service: ConverterService = Depends(get_converter_service),
 
125
  ):
126
  batch_start = time.perf_counter()
127
 
128
  async def process_single_url(url: str) -> BatchFileResult:
129
  _logger.info("Batch processing URL: %s", url)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  loop = asyncio.get_running_loop()
131
  outcome = await loop.run_in_executor(_thread_pool, converter_service.convert_url, url)
132
  return (
 
2
 
3
  import asyncio
4
  import concurrent.futures
5
+ import json as json_mod
6
  import os
7
  import time
8
+ from typing import Annotated, List, Optional
9
 
10
+ import httpx
11
+ from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
12
 
13
+ from app.api.deps import get_converter_service, get_extraction_service, require_auth
14
  from app.config import get_settings
15
  from app.core.logger import get_logger
16
  from app.models.domain import ConversionError
 
21
  ConversionMetadata,
22
  )
23
  from app.services.converter_service import ConverterService
24
+ from app.services.extraction_service import ExtractionService
25
 
26
  router = APIRouter()
27
  _logger = get_logger(__name__)
 
71
  )
72
  async def batch_files(
73
  files: Annotated[List[UploadFile], File(description="Files to convert")],
74
+ return_json: bool = Form(False),
75
+ mappings: Optional[str] = Form(None, description="JSON string with field mappings"),
76
  token: str = Depends(require_auth),
77
  converter_service: ConverterService = Depends(get_converter_service),
78
+ extraction_service: ExtractionService = Depends(get_extraction_service),
79
  ):
80
  if not files:
81
  raise HTTPException(status_code=400, detail={"success": False, "message": "No files provided."})
82
  if len(files) > _MAX_BATCH_FILES:
83
  raise HTTPException(status_code=400, detail={"success": False, "message": f"Maximum {_MAX_BATCH_FILES} files per batch."})
84
 
85
+ parsed_mappings = None
86
+ if mappings:
87
+ try:
88
+ parsed_mappings = json_mod.loads(mappings)
89
+ except json_mod.JSONDecodeError:
90
+ raise HTTPException(status_code=400, detail={"success": False, "message": "Invalid JSON in mappings parameter."})
91
+
92
  batch_start = time.perf_counter()
93
 
94
  async def process_single_file(f: UploadFile) -> BatchFileResult:
 
105
  )
106
  loop = asyncio.get_running_loop()
107
  outcome = await loop.run_in_executor(_thread_pool, converter_service.convert_stream, raw, f.filename or "upload")
108
+ if isinstance(outcome, ConversionError):
109
+ return _batch_result_from_error(f.filename or "unknown", outcome)
110
+
111
+ result = _batch_result_from_ok(outcome)
112
+ if return_json and f.filename:
113
+ loop = asyncio.get_running_loop()
114
+ json_result = await loop.run_in_executor(
115
+ _thread_pool,
116
+ extraction_service.extract_structured,
117
+ f.filename,
118
+ outcome.markdown,
119
+ parsed_mappings,
120
+ raw,
121
+ )
122
+ result.json_content = json_result if "error" not in json_result else None
123
+ result.error = json_result.get("error") if "error" in json_result else None
124
+ return result
125
 
126
  tasks = [process_single_file(f) for f in files]
127
  results = await asyncio.gather(*tasks)
 
147
  body: BatchUrlRequest,
148
  token: str = Depends(require_auth),
149
  converter_service: ConverterService = Depends(get_converter_service),
150
+ extraction_service: ExtractionService = Depends(get_extraction_service),
151
  ):
152
  batch_start = time.perf_counter()
153
 
154
  async def process_single_url(url: str) -> BatchFileResult:
155
  _logger.info("Batch processing URL: %s", url)
156
+ from urllib.parse import urlparse
157
+ parsed = urlparse(url)
158
+ filename = parsed.path.split("/")[-1] or "url_content"
159
+
160
+ if body.return_json:
161
+ try:
162
+ async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
163
+ resp = await client.get(url)
164
+ resp.raise_for_status()
165
+ raw_data = resp.content
166
+ if len(raw_data) > _MAX_UPLOAD_BYTES:
167
+ return BatchFileResult(
168
+ filename=filename, success=False, time_ms=0,
169
+ error=f"File exceeds {_settings.max_upload_mb} MB limit.",
170
+ )
171
+ loop = asyncio.get_running_loop()
172
+ outcome = await loop.run_in_executor(_thread_pool, converter_service.convert_stream, raw_data, filename)
173
+ if isinstance(outcome, ConversionError):
174
+ return _batch_result_from_error(url, outcome)
175
+ result = _batch_result_from_ok(outcome)
176
+ loop = asyncio.get_running_loop()
177
+ json_result = await loop.run_in_executor(
178
+ _thread_pool,
179
+ extraction_service.extract_structured,
180
+ filename,
181
+ outcome.markdown,
182
+ body.mappings,
183
+ raw_data,
184
+ )
185
+ result.json_content = json_result if "error" not in json_result else None
186
+ result.error = json_result.get("error") if "error" in json_result else None
187
+ return result
188
+ except httpx.HTTPError as exc:
189
+ return BatchFileResult(
190
+ filename=filename, success=False, time_ms=0,
191
+ error=f"Failed to fetch URL: {exc}",
192
+ )
193
+
194
  loop = asyncio.get_running_loop()
195
  outcome = await loop.run_in_executor(_thread_pool, converter_service.convert_url, url)
196
  return (
app/models/schemas.py CHANGED
@@ -43,6 +43,8 @@ class UrlRequest(BaseModel):
43
 
44
  class BatchUrlRequest(BaseModel):
45
  urls: List[str]
 
 
46
 
47
  @field_validator("urls")
48
  @classmethod
@@ -60,6 +62,7 @@ class BatchFileResult(BaseModel):
60
  success: bool
61
  time_ms: float
62
  content: Optional[str] = None
 
63
  error: Optional[str] = None
64
  metadata: Optional[ConversionMetadata] = None
65
 
 
43
 
44
  class BatchUrlRequest(BaseModel):
45
  urls: List[str]
46
+ return_json: bool = False
47
+ mappings: Optional[Dict[str, Dict[str, Any]]] = None
48
 
49
  @field_validator("urls")
50
  @classmethod
 
62
  success: bool
63
  time_ms: float
64
  content: Optional[str] = None
65
+ json_content: Optional[Any] = None
66
  error: Optional[str] = None
67
  metadata: Optional[ConversionMetadata] = None
68