Spaces:
Sleeping
Download docs/backend.md from Sid-WC121/visual-notebook: direct link, hf CLI and curl.
- Browser
- Download file 11.3 kB
-
https://huggingface.co/spaces/Sid-WC121/visual-notebook/resolve/main/docs/backend.md
- Command line
-
hf download hf://spaces/Sid-WC121/visual-notebook/docs/backend.md
-
curl -L -o backend.md https://huggingface.co/spaces/Sid-WC121/visual-notebook/resolve/main/docs/backend.md
Backend Deep Dive
This document covers the FastAPI backend architecture in detail, including application lifecycle, middleware, dependency injection, request validation, session management, and error handling.
Application Structure
The backend code lives under backend/visual_notebook/ and follows a layered architecture:
backend/visual_notebook/
main.py # FastAPI app, lifespan, middleware, static serving
config.py # Settings dataclass from environment variables
session.py # Session and SessionStore classes
api/
routes.py # All API endpoint definitions
schemas.py # Pydantic request/response models
deps.py # FastAPI dependency injection functions
domain/
dataframe.py # DataFrameRef unified wrapper
history.py # History tree (Memento pattern)
operations.py # Operation registry and implementations
checkpoints.py # Checkpoint persistence
validators.py # Column validation helpers
data/
csv_loader.py # CSV loading, sampling, and export
schema.py # Schema inference and statistics
types.py # ColumnType enum and classification
viz/
_common.py # Shared chart utilities
histogram.py # Histogram computation
scatter.py # Scatter plot computation
bar_topn.py # Top-N bar chart computation
timeline.py # Timeline/line chart computation
heatmap.py # Heatmap computation
map.py # Map/geo computation
tests/
test_history.py # History module tests
test_operations.py # Operations module tests
FastAPI Application Entry Point (main.py)
Lifespan Handler
The application uses FastAPI's lifespan context manager to manage startup and shutdown resources. On startup, it:
- Creates the
DATA_DIRdirectory if it does not exist, ensuring uploaded CSV files have a storage location. - Creates the
CHECKPOINT_DIRdirectory for session checkpoint persistence. - Instantiates a
SessionStoreand attaches it toapp.state.session_store. - Creates a DuckDB in-memory connection and attaches it to
app.state.duckdb_con. This connection serves as the default for new sessions.
On shutdown, the DuckDB connection is closed. Because the connection is in-memory (using :memory:), closing it releases all associated resources.
@asynccontextmanager
async def lifespan(app: FastAPI):
Path(settings.data_dir).mkdir(parents=True, exist_ok=True)
Path(settings.checkpoint_dir).mkdir(parents=True, exist_ok=True)
app.state.session_store = SessionStore()
app.state.duckdb_con = duckdb.connect(":memory:", read_only=False)
yield
app.state.duckdb_con.close()
CORS Middleware
Cross-Origin Resource Sharing is configured via the CORSMiddleware. The allowed origins are read from the CORS_ORIGINS environment variable (comma-separated). Credentials are enabled so that the session cookie is sent with cross-origin requests. All HTTP methods and headers are permitted.
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
In production, the CORS_ORIGINS variable should be restricted to the actual frontend domain. The default development values allow http://localhost:5173 and http://localhost:3000.
API Router
All API endpoints are defined in api/routes.py and mounted under the /api prefix:
app.include_router(api_router, prefix="/api")
Health Endpoint
A health check endpoint is defined directly on the application (not under /api) for infrastructure monitoring:
@app.get("/health")
async def health():
return {"status": "ok", "version": "0.1.0"}
API Routes Layer
All endpoints are organized in a single APIRouter instance within routes.py. The router handles the following endpoint groups:
| Group | Endpoints | Purpose |
|---|---|---|
| Data loading | POST /upload/csv |
Upload and register CSV files |
| Schema | GET /schema |
Retrieve column schema and statistics |
| Rows | GET /rows |
Paginated row retrieval |
| Operations | GET /ops, POST /ops/{op_key} |
List and apply data transformations |
| History | POST /history/undo, POST /history/redo, POST /history/branch, GET /history |
Undo/redo/branch navigation |
| Visualization | POST /viz/chart |
Chart computation |
| Export | GET /export/csv |
Export current data as CSV |
| Checkpoints | POST /checkpoint, GET /checkpoints |
Save and list session checkpoints |
Each endpoint uses FastAPI's dependency injection to obtain the current session, DuckDB connection, and DataFrameRef as needed.
Dependency Injection (deps.py)
The deps.py module provides four dependency injection functions that are reused across all API endpoints:
get_session_store
Retrieves the SessionStore from the application state. This is a simple accessor that reads request.app.state.session_store.
get_duckdb_con
Retrieves the shared DuckDB connection from request.app.state.duckdb_con. This connection is used for CSV registration and as the default connection for new sessions.
get_session
The primary session resolution dependency. It reads the session_id cookie from the request and calls SessionStore.get_or_create(). If the cookie is absent or the session ID is not found, a new session is created automatically. This ensures that every request has a valid session.
def get_session(request: Request, store: SessionStore = Depends(get_session_store), con=Depends(get_duckdb_con)) -> Session:
session_id = request.cookies.get("session_id")
session = store.get_or_create(session_id, con)
return session
get_current_ref
Returns the current DataFrameRef from the session. Raises HTTPException(400) if no dataset has been loaded. This dependency is used by endpoints that require an active dataset (schema, rows, operations, visualization).
get_history
Returns the History tree from the current session. A convenience dependency that chains get_session and accesses session.history.
Pydantic Schemas
All request and response payloads are validated through Pydantic models defined in api/schemas.py. Key models include:
| Model | Purpose | Key Fields |
|---|---|---|
ColumnInfo |
Column metadata and statistics | name, dtype, column_type, null_count, unique_count, min, max, mean, std, q25, q50, q75, count |
UploadResponse |
CSV upload result | dataset_id, table_name, row_count, columns, session_id |
SchemaResponse |
Schema query result | table_name, row_count, columns |
ApplyOpRequest |
Operation application request | op_key, params |
ApplyOpResponse |
Operation application result | state_id, columns_schema, row_count, sample_rows |
ChartRequest |
Chart computation request | chart_type, params |
ChartResponse |
Chart computation result | chart_type, spec |
HistoryNode |
History tree node | id, operation, params, parent_id, children, is_current |
HistoryResponse |
Full history tree | nodes, current_id, order |
UndoRedoResponse |
Undo/redo result | state_id, operation, columns_schema, row_count |
OperationInfo |
Operation descriptor | key, label, description, param_schema |
CheckpointResponse |
Checkpoint save result | path, timestamp, state_id |
Pydantic v2 is used throughout, providing automatic serialization, validation, and OpenAPI schema generation. The model_dump() method is used to serialize responses to dictionaries.
Session Management
Session Dataclass
Each Session object holds:
id: A 12-character hex string identifying the session.duckdb_con: The DuckDB connection for this session.current_table: The name of the currently active DuckDB table.current_ref: TheDataFrameRefwrapping the current data state.history: TheHistorytree tracking all operations.metadata: An extensible dictionary for future session-level data.
SessionStore
The SessionStore is an in-memory dictionary that maps session IDs to Session objects. It provides:
create(duckdb_con): Generates a new session ID, creates aSessionwith a freshHistory, and stores it.get(session_id): Returns the session orNone.get_or_create(session_id, duckdb_con): Returns an existing session or creates a new one.remove(session_id): Removes a session from the store.
Cookie-Based Session Binding
After a CSV upload, the response sets an httponly cookie named session_id with a 7-day expiration:
resp.set_cookie("session_id", session.id, httponly=True, max_age=86400 * 7)
The httponly flag prevents JavaScript access, improving security against XSS attacks. Subsequent requests include this cookie automatically, allowing the backend to resolve the session.
CORS Configuration
The CORS policy is configured through the CORS_ORIGINS environment variable. In development, this defaults to http://localhost:5173,http://localhost:3000. In production Docker deployments, it is typically set to * or the specific frontend domain. Credentials are enabled to support cookie-based session management across origins.
Static File Serving for Production
In production, the backend serves the compiled React frontend. The _try_mount_static() function attempts to mount the static directory (defaulting to /app/static):
- Mounts
/assetsas a static file directory for JS, CSS, and other build artifacts. - Mounts
/staticas a catch-all for other static resources. - Registers a catch-all
/{full_path:path}route that servesindex.htmlfor client-side routing (SPA fallback).
The function gracefully handles missing directories and permission errors, making it safe to run in development (where the frontend is served by Vite) without the static build present.
Error Handling Patterns
The backend uses a layered error handling approach:
HTTPException in Routes
Endpoints raise HTTPException with appropriate status codes:
| Status | Condition |
|---|---|
| 400 | No dataset loaded, invalid operation state |
| 404 | Operation key not found, history state not found |
| 413 | File exceeds MAX_UPLOAD_SIZE |
| 422 | Validation error (from Pydantic or custom validators) |
| 500 | Operation execution failure, chart computation failure |
Validation Errors
Operation validators raise ValueError for invalid parameters. The route handler catches these and converts them to HTTPException(422) with the validation message as the detail.
Graceful Degradation
Non-critical operations such as checkpoint Parquet export are wrapped in try-except blocks that silently ignore failures. This ensures that a checkpoint save still succeeds even if the Parquet export fails.
Frontend Error Display
The response error interceptor in the Axios client extracts the detail field from error responses and surfaces it through the ErrorToast component, providing user-visible feedback for all backend errors.