-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsvc.py
More file actions
152 lines (122 loc) · 5.46 KB
/
Copy pathsvc.py
File metadata and controls
152 lines (122 loc) · 5.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import logging
import os
import uuid
from contextlib import asynccontextmanager
from typing import AsyncGenerator
from uuid import UUID
import uvicorn
from fastapi import FastAPI, UploadFile, HTTPException, Query, Path, Depends, Request
from fastapi.responses import StreamingResponse
from content.local_storage import LocalContentStorage
from content.storage import FileUploadWrapper
from db.connections import DBConnManager
from metadata.db_storage import DBMetadataStorage
from storagemanager.storage_manager import (
StorageManager,
MetadataExtractionException,
FileSaveException,
MetadataRetrievalException,
FileDeletionException,
ContentRetrievalException
)
from validation import PaginatedContentMetadata, FileUploadResponse, ContentMetadata
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(f_app: FastAPI) -> AsyncGenerator[None, None]:
database_url = os.getenv('DB_URL')
if not database_url:
raise RuntimeError("DB_URL env var is required")
delimiter_chars = os.getenv('CSV_DELIMITERS')
estimate_rows = os.getenv('ESTIMATE_ROW_COUNT', 'False').lower() == 'true'
db_conn_mgr = DBConnManager(conn_str=database_url)
try:
db_conn_mgr.init()
storage_mgr = StorageManager(
content_storage=LocalContentStorage('./'),
metadata_storage=DBMetadataStorage(db_conn_mgr),
csv_delimiters=delimiter_chars,
estimate_rows=estimate_rows
)
f_app.state.storage_manager = storage_mgr
f_app.state.db_conn_mgr = db_conn_mgr
yield
except Exception as e:
logger.critical("Startup failed", exc_info=e)
raise e
finally:
logger.info("Shutdown signal received")
try:
db_conn_mgr.close()
except Exception as e:
logger.error("Error closing DB connection", exc_info=e)
def get_storage_manager(request: Request) -> StorageManager:
if not hasattr(request.app.state, "storage_manager"):
raise RuntimeError("Storage Manager is not initialized. Check application lifespan.")
return request.app.state.storage_manager
def create_app() -> FastAPI:
app = FastAPI(title="CSV Upload Service", lifespan=lifespan)
@app.post("/files", response_model=FileUploadResponse, status_code=201)
async def upload_csv(
file: UploadFile,
storage_manager: StorageManager = Depends(get_storage_manager)
):
try:
file_id = await storage_manager.save(file.filename, FileUploadWrapper(file.file))
return FileUploadResponse(id=uuid.UUID(file_id))
except MetadataExtractionException:
raise HTTPException(status_code=400, detail="Invalid CSV file or encoding")
except FileSaveException:
raise HTTPException(status_code=500, detail="Failed to persist the CSV file")
finally:
await file.close()
@app.get("/files", response_model=PaginatedContentMetadata, status_code=200)
async def get_all_metadata(
page: int = Query(1, ge=1, description="Page number"),
page_size: int = Query(10, ge=1, le=100, description="Items per page"),
storage_manager: StorageManager = Depends(get_storage_manager)
):
try:
return await storage_manager.list_metadata(page, page_size)
except MetadataRetrievalException:
raise HTTPException(status_code=500, detail="Failed to retrieve metadata")
@app.get("/files/{id}/metadata", response_model=ContentMetadata, status_code=200)
async def get_metadata(
id: UUID = Path(..., title="File UUID", description="The UUID of the file"),
storage_manager: StorageManager = Depends(get_storage_manager)
):
try:
metadata = await storage_manager.get_metadata(str(id))
except MetadataRetrievalException:
raise HTTPException(status_code=500, detail="Error retrieving metadata")
if metadata:
return metadata
raise HTTPException(status_code=404, detail="Not found")
@app.delete("/files/{id}", status_code=204)
async def delete_file(
id: UUID = Path(..., title="File UUID", description="The UUID of the file"),
storage_manager: StorageManager = Depends(get_storage_manager)
):
try:
deleted = await storage_manager.delete(str(id))
except FileDeletionException:
raise HTTPException(status_code=500, detail="Failed to delete file")
if not deleted:
raise HTTPException(status_code=404, detail="Not found")
@app.get("/files/{id}/data", status_code=200)
async def get_content(
id: UUID = Path(..., title="File UUID", description="The UUID of the file"),
storage_manager: StorageManager = Depends(get_storage_manager)
):
try:
content_stream = await storage_manager.get_content_stream(str(id))
except (MetadataRetrievalException, ContentRetrievalException):
raise HTTPException(status_code=500, detail="Failed to retrieve content")
if content_stream:
return StreamingResponse(content_stream(), media_type="application/json")
raise HTTPException(status_code=404, detail="Not found")
return app
app = create_app()
if __name__ == '__main__':
# uvicorn main:app --host 0.0.0.0 --port 8080
uvicorn.run(app, host="0.0.0.0", port=8080)