Coverage for src / idx_api / main.py: 88%
69 statements
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-28 11:16 -0700
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-28 11:16 -0700
1"""FastAPI application entry point."""
3from contextlib import asynccontextmanager
4from importlib.metadata import version
6from fastapi import FastAPI
7from fastapi.middleware.cors import CORSMiddleware
8from fastapi.staticfiles import StaticFiles
9from pathlib import Path
11from idx_api.config import settings
12from idx_api.database import get_db_session
13from idx_api.embeddings import create_vector_tables, create_image_tables
14from idx_api.routers import admin, agents, auth, broker_contacts, brokerage_domains, brokerages, clients, content_sources, data_management, embeddings_admin, leads, newsletter, properties, public, search, suggestions, tours, uploads, vision
17def get_version() -> str:
18 """Get package version."""
19 try:
20 return version("idx-api")
21 except Exception:
22 return "0.0.0-dev"
25@asynccontextmanager
26async def lifespan(app: FastAPI):
27 """Application lifespan events."""
28 # Startup
29 print(f"🏠 IDX API v{get_version()} starting...")
30 print(f"📁 Database: {settings.sqlite_db_path}")
31 print(f"🔮 Ollama: {settings.ollama_base_url}")
33 # Initialize vector tables
34 try:
35 with get_db_session() as db:
36 create_vector_tables(db)
37 print("✅ Vector tables initialized")
38 except Exception as e:
39 print(f"⚠️ Failed to initialize vector tables: {e}")
41 # Initialize image tables for multi-modal search
42 try:
43 with get_db_session() as db:
44 create_image_tables(db)
45 print("✅ Image tables initialized (multi-modal search)")
46 except Exception as e:
47 print(f"⚠️ Failed to initialize image tables: {e}")
49 yield
50 # Shutdown
51 print("👋 IDX API shutting down...")
54app = FastAPI(
55 title="MLSGrid IDX API",
56 description="Property search and listing API powered by MLSGrid data",
57 version=get_version(),
58 lifespan=lifespan,
59 docs_url="/docs",
60 redoc_url="/redoc",
61)
63# CORS
64app.add_middleware(
65 CORSMiddleware,
66 allow_origins=settings.cors_origins,
67 allow_credentials=True,
68 allow_methods=["*"],
69 allow_headers=["*"],
70)
72# Routers
73app.include_router(public.router, prefix="/api", tags=["public"]) # Public endpoints (no auth)
74app.include_router(brokerage_domains.router, prefix="/api", tags=["domains"], include_in_schema=True) # Public domain lookup
75app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
76app.include_router(admin.router, prefix="/api/admin", tags=["admin"])
77app.include_router(agents.router, prefix="/api/admin", tags=["agents"])
78app.include_router(brokerages.router, prefix="/api/admin", tags=["brokerages"])
79app.include_router(broker_contacts.router, prefix="/api/admin", tags=["broker-contacts"])
80app.include_router(brokerage_domains.router, prefix="/api/admin", tags=["brokerage-domains"])
81app.include_router(suggestions.router, prefix="/api/admin", tags=["suggestions"])
82app.include_router(embeddings_admin.router, prefix="/api/admin", tags=["embeddings"])
83app.include_router(data_management.router, prefix="/api/admin", tags=["data-management"])
84app.include_router(content_sources.router, prefix="/api/admin", tags=["content-sources"])
85app.include_router(clients.router, prefix="/api/admin", tags=["clients"])
86app.include_router(uploads.router, prefix="/api", tags=["uploads"])
87app.include_router(properties.router, prefix="/api/properties", tags=["properties"])
88app.include_router(search.router, prefix="/api/search", tags=["search"])
89app.include_router(tours.router, prefix="/api/tours", tags=["tours"])
90app.include_router(newsletter.router, prefix="/api", tags=["newsletter"])
91app.include_router(leads.router, prefix="/api", tags=["leads"]) # Contact form → lead creation
92app.include_router(vision.router, prefix="/api/admin/vision", tags=["vision"])
94# Static files for uploads
95uploads_dir = Path("/data/uploads")
96uploads_dir.mkdir(parents=True, exist_ok=True)
97app.mount("/uploads", StaticFiles(directory=str(uploads_dir)), name="uploads")
98# Also mount as /media for backward compatibility
99app.mount("/media", StaticFiles(directory=str(uploads_dir)), name="media")
102@app.get("/health")
103async def health_check():
104 """Health check endpoint."""
105 return {"status": "healthy", "version": get_version()}
108@app.get("/")
109async def root():
110 """API root with links to documentation."""
111 return {
112 "name": "MLSGrid IDX API",
113 "version": get_version(),
114 "docs": "/docs",
115 "health": "/health",
116 }
119def run():
120 """Run the server (for script entry point)."""
121 import uvicorn
123 uvicorn.run(
124 "idx_api.main:app",
125 host=settings.api_host,
126 port=settings.api_port,
127 reload=settings.environment == "dev",
128 )
131if __name__ == "__main__":
132 run()