Soumik Bose commited on
Commit
c0fce26
·
1 Parent(s): 65fe3da
controller.py CHANGED
@@ -37,12 +37,14 @@ import uvicorn
37
  from csv_analysis_service import execute_analysis_logic
38
  from csv_chart_service import execute_python_code
39
  from csv_metadata_service import CsvDataRequest, CsvInfoRequest, CsvInfoResponse, PythonExecutionRequest, PythonExecutionResponse, execute_python_logic, get_csv_basic_info, get_robust_csv_rows
 
40
  from mongo_service import convert_oid, execute_mongo_operation, extract_database_name, sanitize_json_input
41
  from pydantic_csv_analysis_model import AnalysisRequest, AnalysisResponse
42
  from pydantic_csv_charts_model import ChartExecutionPayload, ChartExecutionResponse
43
  from pydantic_mongo_executor_model import ExecutorPayload, ExecutorResponse
44
  from report_service import FileBoxProps, ReportRequest, execute_report_generation
45
  from supabase_service import upload_bytes_to_supabase
 
46
 
47
  # --- Configuration & Setup ---
48
  load_dotenv()
@@ -631,6 +633,15 @@ async def root():
631
  async def ping():
632
  return {"message": "I am alive!"}
633
 
 
 
 
 
 
 
 
 
 
634
  if __name__ == "__main__":
635
  host = os.getenv("HOST", "0.0.0.0")
636
  port = int(os.getenv("PORT", 7860))
 
37
  from csv_analysis_service import execute_analysis_logic
38
  from csv_chart_service import execute_python_code
39
  from csv_metadata_service import CsvDataRequest, CsvInfoRequest, CsvInfoResponse, PythonExecutionRequest, PythonExecutionResponse, execute_python_logic, get_csv_basic_info, get_robust_csv_rows
40
+ from extract_csv_metadata_service import extract_csv_metadata_logic
41
  from mongo_service import convert_oid, execute_mongo_operation, extract_database_name, sanitize_json_input
42
  from pydantic_csv_analysis_model import AnalysisRequest, AnalysisResponse
43
  from pydantic_csv_charts_model import ChartExecutionPayload, ChartExecutionResponse
44
  from pydantic_mongo_executor_model import ExecutorPayload, ExecutorResponse
45
  from report_service import FileBoxProps, ReportRequest, execute_report_generation
46
  from supabase_service import upload_bytes_to_supabase
47
+ from table_info_model import CsvFieldsRequest, CsvFieldsResponse
48
 
49
  # --- Configuration & Setup ---
50
  load_dotenv()
 
633
  async def ping():
634
  return {"message": "I am alive!"}
635
 
636
+ @app.post("/api/get_csv_fields", response_model=CsvFieldsResponse)
637
+ async def get_csv_fields_endpoint(payload: CsvFieldsRequest, token: str = Depends(validate_token)):
638
+ request_id = str(uuid.uuid4())[:8]
639
+ try:
640
+ fields = await run_in_threadpool(extract_csv_metadata_logic, csv_url=payload.csv_url)
641
+ return CsvFieldsResponse(success=True, fields=fields, request_id=request_id)
642
+ except Exception as e:
643
+ raise HTTPException(status_code=500, detail={"success": False, "error": str(e), "request_id": request_id})
644
+
645
  if __name__ == "__main__":
646
  host = os.getenv("HOST", "0.0.0.0")
647
  port = int(os.getenv("PORT", 7860))
extract_csv_metadata_service.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Dict, Any, Optional
3
+ import pandas as pd
4
+ import numpy as np
5
+
6
+ # Configure Logging
7
+ logging.basicConfig(
8
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
9
+ level=logging.INFO
10
+ )
11
+ logger = logging.getLogger("csv_metadata_service")
12
+
13
+ def extract_csv_metadata_logic(csv_url: str) -> List[Dict[str, Any]]:
14
+ """
15
+ Analyzes a CSV file from a URL to infer column metadata.
16
+
17
+ Args:
18
+ csv_url (str): The public URL of the CSV file.
19
+
20
+ Returns:
21
+ List[Dict[str, Any]]: A list of dictionaries containing field metadata.
22
+ """
23
+ try:
24
+ logger.info(f"Extracting metadata from: {csv_url}")
25
+
26
+ # Load a sample of the CSV to infer types (first 50 rows is usually enough)
27
+ # Using on_bad_lines='skip' to be robust against malformed rows
28
+ # storage_options={'User-Agent': ...} can be added if requests are blocked
29
+ df = pd.read_csv(csv_url, nrows=50, on_bad_lines='skip')
30
+
31
+ fields = []
32
+
33
+ for col in df.columns:
34
+ dtype = df[col].dtype
35
+
36
+ # 1. Default type
37
+ generic_type = 'string'
38
+
39
+ # 2. Map Pandas/Numpy types to generic keys
40
+ if pd.api.types.is_integer_dtype(dtype):
41
+ generic_type = 'integer'
42
+ elif pd.api.types.is_float_dtype(dtype):
43
+ generic_type = 'decimal'
44
+ elif pd.api.types.is_bool_dtype(dtype):
45
+ generic_type = 'boolean'
46
+ elif pd.api.types.is_datetime64_any_dtype(dtype):
47
+ generic_type = 'date'
48
+ else:
49
+ # 3. Attempt to detect dates in object/string columns
50
+ # We check the first non-null value to see if it parses as a date
51
+ if len(df) > 0:
52
+ try:
53
+ first_valid = df[col].dropna().iloc[0] if not df[col].dropna().empty else ""
54
+ val_str = str(first_valid).strip()
55
+
56
+ # Simple check to avoid treating plain numbers as dates
57
+ if val_str and not val_str.isdigit():
58
+ pd.to_datetime(val_str)
59
+ generic_type = 'date'
60
+ except (ValueError, TypeError):
61
+ # Not a date, keep as string
62
+ pass
63
+
64
+ # 4. Determine Nullability
65
+ is_nullable = bool(df[col].isnull().any())
66
+
67
+ # 5. Heuristic for Primary Key
68
+ lower_name = col.lower()
69
+ potential_names = ['id', 'uuid', '_id', 'pk']
70
+
71
+ # It's likely a PK if it has a common ID name OR ends in _id
72
+ is_pk_name = lower_name in potential_names or lower_name.endswith('_id')
73
+
74
+ # It must also be unique in our sample
75
+ is_unique = df[col].is_unique if not df.empty else False
76
+
77
+ is_primary_key = is_pk_name and is_unique
78
+
79
+ fields.append({
80
+ "name": col,
81
+ "type": generic_type,
82
+ "nullable": is_nullable,
83
+ "isPrimaryKey": is_primary_key
84
+ })
85
+
86
+ logger.info(f"Successfully extracted {len(fields)} fields.")
87
+ return fields
88
+
89
+ except Exception as e:
90
+ logger.error(f"CSV Analysis Error for {csv_url}: {str(e)}")
91
+ # Re-raise to be handled by the controller's exception handler
92
+ raise e
table_info_model.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+ from pydantic import BaseModel
3
+
4
+
5
+ class CsvFieldsRequest(BaseModel):
6
+ csv_url: str
7
+
8
+ class FieldMetadata(BaseModel):
9
+ name: str
10
+ type: str
11
+ nullable: bool
12
+ isPrimaryKey: bool
13
+
14
+ class CsvFieldsResponse(BaseModel):
15
+ success: bool
16
+ fields: List[FieldMetadata]
17
+ error: Optional[str] = None
18
+ request_id: str