light-infer-chat commited on
Commit
114194d
·
1 Parent(s): ebb9029
app/api/v1/convert.py CHANGED
@@ -1,10 +1,8 @@
1
  from __future__ import annotations
2
 
3
  import asyncio
4
- import concurrent.futures
5
  import hashlib
6
  import json as json_mod
7
- import os
8
  from typing import Annotated, Any, Dict, Optional
9
  from urllib.parse import urlparse
10
 
@@ -29,6 +27,7 @@ from app.api.deps import (
29
  )
30
  from app.config import get_settings
31
  from app.core.logger import get_logger
 
32
  from app.models.domain import ConversionError, count_tokens
33
  from app.models.schemas import ConversionMetadata, ConversionResponse, UrlRequest
34
  from app.services.converter_service import ConverterService
@@ -39,8 +38,6 @@ router = APIRouter()
39
  _logger = get_logger(__name__)
40
  _settings = get_settings()
41
  _MAX_UPLOAD_BYTES = _settings.max_upload_bytes
42
- _MAX_WORKERS = min(32, (os.cpu_count() or 1) + 4)
43
- _thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=_MAX_WORKERS)
44
 
45
 
46
  def _build_metadata(result) -> ConversionMetadata:
 
1
  from __future__ import annotations
2
 
3
  import asyncio
 
4
  import hashlib
5
  import json as json_mod
 
6
  from typing import Annotated, Any, Dict, Optional
7
  from urllib.parse import urlparse
8
 
 
27
  )
28
  from app.config import get_settings
29
  from app.core.logger import get_logger
30
+ from app.core.thread_pool import thread_pool as _thread_pool
31
  from app.models.domain import ConversionError, count_tokens
32
  from app.models.schemas import ConversionMetadata, ConversionResponse, UrlRequest
33
  from app.services.converter_service import ConverterService
 
38
  _logger = get_logger(__name__)
39
  _settings = get_settings()
40
  _MAX_UPLOAD_BYTES = _settings.max_upload_bytes
 
 
41
 
42
 
43
  def _build_metadata(result) -> ConversionMetadata:
app/api/v1/embeddings.py CHANGED
@@ -1,8 +1,6 @@
1
  from __future__ import annotations
2
 
3
  import asyncio
4
- import concurrent.futures
5
- import os
6
  import time
7
 
8
  from fastapi import APIRouter, Depends, HTTPException
@@ -10,14 +8,13 @@ from fastapi import APIRouter, Depends, HTTPException
10
  from app.api.deps import get_embeddings_service, require_auth
11
  from app.config import get_settings
12
  from app.core.logger import get_logger
 
13
  from app.models.schemas import EmbeddingItem, EmbeddingRequest, EmbeddingResponse
14
  from app.services.embeddings_service import EmbeddingService
15
 
16
  router = APIRouter()
17
  _logger = get_logger(__name__)
18
  _settings = get_settings()
19
- _MAX_WORKERS = min(32, (os.cpu_count() or 1) + 4)
20
- _thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=_MAX_WORKERS)
21
  @router.post(
22
  "/embeddings",
23
  response_model=EmbeddingResponse,
 
1
  from __future__ import annotations
2
 
3
  import asyncio
 
 
4
  import time
5
 
6
  from fastapi import APIRouter, Depends, HTTPException
 
8
  from app.api.deps import get_embeddings_service, require_auth
9
  from app.config import get_settings
10
  from app.core.logger import get_logger
11
+ from app.core.thread_pool import thread_pool as _thread_pool
12
  from app.models.schemas import EmbeddingItem, EmbeddingRequest, EmbeddingResponse
13
  from app.services.embeddings_service import EmbeddingService
14
 
15
  router = APIRouter()
16
  _logger = get_logger(__name__)
17
  _settings = get_settings()
 
 
18
  @router.post(
19
  "/embeddings",
20
  response_model=EmbeddingResponse,
app/core/thread_pool.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import concurrent.futures
4
+ import os
5
+
6
+ _MAX_WORKERS = min(32, (os.cpu_count() or 1) + 4)
7
+
8
+ thread_pool = concurrent.futures.ThreadPoolExecutor(
9
+ max_workers=_MAX_WORKERS, thread_name_prefix="shared"
10
+ )
app/services/dataset_metadata_service.py CHANGED
@@ -11,8 +11,6 @@ import asyncio
11
  import csv
12
  import io
13
  import logging
14
- import os
15
- from concurrent.futures import ThreadPoolExecutor
16
  from dataclasses import dataclass, field
17
  from enum import Enum
18
  from pathlib import Path
@@ -35,6 +33,8 @@ import openpyxl # noqa: F401 – needed as engine for .xlsx
35
  import pandas as pd
36
  import xlrd # noqa: F401 – needed as engine for .xls
37
 
 
 
38
  logger = logging.getLogger(__name__)
39
  logger.setLevel(logging.DEBUG)
40
 
@@ -47,9 +47,6 @@ _DEFAULT_SAMPLE_ROWS: int = 5
47
  _MAX_FILE_SIZE: int = 2 * 1024 * 1024 * 1024 # 2 GiB
48
  _DOWNLOAD_CHUNK_SIZE: int = 256 * 1024 # 256 KiB streaming chunks
49
  _DEFAULT_TIMEOUT_SECONDS: int = 120
50
- _THREAD_POOL = ThreadPoolExecutor(
51
- max_workers=min(32, (os.cpu_count() or 4) + 4),
52
- )
53
 
54
  # Magic bytes for binary file-type detection
55
  _XLSX_MAGIC: bytes = b"PK\x03\x04"
@@ -375,7 +372,7 @@ def _read_xls_metadata(data: bytes, config: ExtractionConfig) -> Dict[str, Any]:
375
  async def _read_from_file_like(file_obj: BinaryIO) -> Tuple[bytes, Optional[str]]:
376
  """Read all bytes from a synchronous file-like object off the event loop."""
377
  loop = asyncio.get_running_loop()
378
- data: bytes = await loop.run_in_executor(_THREAD_POOL, file_obj.read)
379
  filename: Optional[str] = getattr(file_obj, "name", None)
380
  return data, filename
381
 
@@ -387,7 +384,7 @@ async def _read_from_path(path: Path) -> Tuple[bytes, str]:
387
  if not path.is_file():
388
  raise IsADirectoryError(f"Path is not a regular file: {path}")
389
  loop = asyncio.get_running_loop()
390
- data: bytes = await loop.run_in_executor(_THREAD_POOL, path.read_bytes)
391
  return data, path.name
392
 
393
 
@@ -599,7 +596,7 @@ async def extract_metadata(
599
  reader = readers[file_type]
600
  loop = asyncio.get_running_loop()
601
  result: Dict[str, Any] = await loop.run_in_executor(
602
- _THREAD_POOL,
603
  reader,
604
  data,
605
  config,
 
11
  import csv
12
  import io
13
  import logging
 
 
14
  from dataclasses import dataclass, field
15
  from enum import Enum
16
  from pathlib import Path
 
33
  import pandas as pd
34
  import xlrd # noqa: F401 – needed as engine for .xls
35
 
36
+ from app.core.thread_pool import thread_pool
37
+
38
  logger = logging.getLogger(__name__)
39
  logger.setLevel(logging.DEBUG)
40
 
 
47
  _MAX_FILE_SIZE: int = 2 * 1024 * 1024 * 1024 # 2 GiB
48
  _DOWNLOAD_CHUNK_SIZE: int = 256 * 1024 # 256 KiB streaming chunks
49
  _DEFAULT_TIMEOUT_SECONDS: int = 120
 
 
 
50
 
51
  # Magic bytes for binary file-type detection
52
  _XLSX_MAGIC: bytes = b"PK\x03\x04"
 
372
  async def _read_from_file_like(file_obj: BinaryIO) -> Tuple[bytes, Optional[str]]:
373
  """Read all bytes from a synchronous file-like object off the event loop."""
374
  loop = asyncio.get_running_loop()
375
+ data: bytes = await loop.run_in_executor(thread_pool, file_obj.read)
376
  filename: Optional[str] = getattr(file_obj, "name", None)
377
  return data, filename
378
 
 
384
  if not path.is_file():
385
  raise IsADirectoryError(f"Path is not a regular file: {path}")
386
  loop = asyncio.get_running_loop()
387
+ data: bytes = await loop.run_in_executor(thread_pool, path.read_bytes)
388
  return data, path.name
389
 
390
 
 
596
  reader = readers[file_type]
597
  loop = asyncio.get_running_loop()
598
  result: Dict[str, Any] = await loop.run_in_executor(
599
+ thread_pool,
600
  reader,
601
  data,
602
  config,
app/services/qr_decoder_service.py CHANGED
@@ -1,5 +1,6 @@
1
  from __future__ import annotations
2
 
 
3
  import io
4
  import ipaddress
5
  import os
@@ -14,6 +15,7 @@ import numpy as np
14
  from PIL import Image, UnidentifiedImageError
15
 
16
  from app.core.logger import get_logger
 
17
 
18
  logger = get_logger(__name__)
19
 
@@ -211,14 +213,14 @@ class QRDecoderService:
211
  # ------------------------------------------------------------------
212
 
213
  async def _load_cv_image_async(self, raw_bytes: bytes, origin: str) -> np.ndarray:
214
- loop = asyncio_get_loop()
215
- return await loop.run_in_executor(None, self._load_cv_image, raw_bytes, origin)
216
 
217
  async def _decode_async(
218
  self, image: np.ndarray, source: str
219
  ) -> QRDecoderResult:
220
- loop = asyncio_get_loop()
221
- return await loop.run_in_executor(None, self._decode_sync, image, source)
222
 
223
  # ------------------------------------------------------------------
224
  # Synchronous CPU-bound implementations
@@ -424,9 +426,4 @@ class QRDecoderService:
424
  ) from exc
425
 
426
 
427
- def asyncio_get_loop():
428
- import asyncio
429
- try:
430
- return asyncio.get_running_loop()
431
- except RuntimeError:
432
- return asyncio.new_event_loop()
 
1
  from __future__ import annotations
2
 
3
+ import asyncio
4
  import io
5
  import ipaddress
6
  import os
 
15
  from PIL import Image, UnidentifiedImageError
16
 
17
  from app.core.logger import get_logger
18
+ from app.core.thread_pool import thread_pool
19
 
20
  logger = get_logger(__name__)
21
 
 
213
  # ------------------------------------------------------------------
214
 
215
  async def _load_cv_image_async(self, raw_bytes: bytes, origin: str) -> np.ndarray:
216
+ loop = asyncio.get_running_loop()
217
+ return await loop.run_in_executor(thread_pool, self._load_cv_image, raw_bytes, origin)
218
 
219
  async def _decode_async(
220
  self, image: np.ndarray, source: str
221
  ) -> QRDecoderResult:
222
+ loop = asyncio.get_running_loop()
223
+ return await loop.run_in_executor(thread_pool, self._decode_sync, image, source)
224
 
225
  # ------------------------------------------------------------------
226
  # Synchronous CPU-bound implementations
 
426
  ) from exc
427
 
428
 
429
+