d582f15681
- Implemented tests for import polling functionality, ensuring correct behavior for active and terminal import statuses. - Created tests for the login page, validating user interactions and error handling. - Added unit tests for money utility functions, covering parsing, formatting, and summation of monetary values. - Developed tests for query keys, ensuring proper scoping and invalidation roots. chore: setup testing environment and configuration - Added setup file for testing with Vitest and Testing Library. - Configured TypeScript and Vite for the frontend project. - Created Docker and Nginx configurations for local development and production environments. - Established a PostgreSQL initialization script for setting up application roles. docs: add README and environment configuration for infra - Included README for the infra directory, detailing local development setup and service descriptions. - Created an example environment configuration file for local development with placeholders for sensitive information.
58 lines
1.7 KiB
Python
58 lines
1.7 KiB
Python
import io
|
|
import zipfile
|
|
from collections.abc import Iterator
|
|
from contextlib import contextmanager
|
|
|
|
import defusedxml # noqa: F401 (hardened XML stack must be importable for openpyxl)
|
|
import pdfplumber
|
|
from openpyxl import load_workbook
|
|
|
|
from app.config import get_settings
|
|
from app.errors import AppError
|
|
|
|
|
|
def _quota_error() -> AppError:
|
|
return AppError(
|
|
"Document exceeds parser resource quotas",
|
|
code="IMPORT_QUOTA_EXCEEDED",
|
|
status_code=413,
|
|
)
|
|
|
|
|
|
@contextmanager
|
|
def open_xlsx(raw: bytes) -> Iterator:
|
|
settings = get_settings()
|
|
try:
|
|
with zipfile.ZipFile(io.BytesIO(raw)) as zf:
|
|
total_uncompressed = 0
|
|
for info in zf.infolist():
|
|
if info.flag_bits & 0x1:
|
|
raise AppError(
|
|
"Encrypted archives are not supported",
|
|
code="IMPORT_ENCRYPTED_ARCHIVE",
|
|
status_code=422,
|
|
)
|
|
total_uncompressed += info.file_size
|
|
if total_uncompressed > settings.max_archive_uncompressed_bytes:
|
|
raise _quota_error()
|
|
except zipfile.BadZipFile as exc:
|
|
raise AppError(
|
|
"File is not a valid XLSX container",
|
|
code="IMPORT_FILE_CORRUPT",
|
|
status_code=422,
|
|
) from exc
|
|
workbook = load_workbook(io.BytesIO(raw), read_only=True, data_only=True, keep_links=False)
|
|
try:
|
|
yield workbook
|
|
finally:
|
|
workbook.close()
|
|
|
|
|
|
@contextmanager
|
|
def open_pdf(raw: bytes) -> Iterator:
|
|
settings = get_settings()
|
|
with pdfplumber.open(io.BytesIO(raw)) as pdf:
|
|
if len(pdf.pages) > settings.max_pdf_pages:
|
|
raise _quota_error()
|
|
yield pdf
|