Files
finance/backend/app/imports/blob.py
T
k1nq d582f15681 feat: add tests for import polling, login page, money utilities, and query keys
- 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.
2026-09-14 09:26:33 +05:00

78 lines
2.2 KiB
Python

import os
from pathlib import Path
from typing import Protocol
import boto3
from botocore.exceptions import ClientError
from app.config import get_settings
class BlobStore(Protocol):
def put(self, key: str, data: bytes) -> None: ...
def get(self, key: str) -> bytes: ...
def delete(self, key: str) -> None: ...
class LocalDirectoryBlobStore:
"""S3-neutral local store; production swaps in an S3-compatible client."""
def __init__(self, base_dir: str) -> None:
self._base = Path(base_dir)
def _path(self, key: str) -> Path:
if "/" in key or ".." in key or not key:
raise ValueError("invalid blob key")
return self._base / key
def put(self, key: str, data: bytes) -> None:
path = self._path(key)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
def get(self, key: str) -> bytes:
return self._path(key).read_bytes()
def delete(self, key: str) -> None:
path = self._path(key)
if path.exists():
path.unlink()
class S3BlobStore:
def __init__(self) -> None:
settings = get_settings()
self._bucket = settings.s3_bucket_statements
self._client = boto3.client(
"s3",
endpoint_url=settings.s3_endpoint_url,
region_name=settings.s3_region,
aws_access_key_id=settings.s3_access_key_id,
aws_secret_access_key=settings.s3_secret_access_key,
)
try:
self._client.head_bucket(Bucket=self._bucket)
except ClientError:
self._client.create_bucket(Bucket=self._bucket)
def put(self, key: str, data: bytes) -> None:
self._client.put_object(Bucket=self._bucket, Key=key, Body=data)
def get(self, key: str) -> bytes:
response = self._client.get_object(Bucket=self._bucket, Key=key)
return response["Body"].read()
def delete(self, key: str) -> None:
self._client.delete_object(Bucket=self._bucket, Key=key)
def get_blob_store() -> BlobStore:
settings = get_settings()
if settings.s3_endpoint_url:
return S3BlobStore()
return LocalDirectoryBlobStore(settings.blob_dir)
def new_object_key() -> str:
return os.urandom(16).hex()