main.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. #!/usr/bin/env python3
  2. """
  3. Main FastAPI application entry point.
  4. """
  5. from contextlib import asynccontextmanager
  6. from pathlib import Path
  7. import threading
  8. import os
  9. import argparse
  10. from fastapi import FastAPI
  11. from fastapi.staticfiles import StaticFiles
  12. from slowapi.errors import RateLimitExceeded
  13. from slowapi.middleware import SlowAPIMiddleware
  14. from web.api.v1 import router as v1_router
  15. from web.ui import router as ui_router
  16. from web.core.config import get_settings
  17. from web.core.startup import initialize_application
  18. from web.core.logging_config import setup_logging
  19. from web.core.limiter import limiter, rate_limit_exceeded_handler
  20. import ap_git
  21. import metadata_manager
  22. import build_manager
  23. setup_logging()
  24. @asynccontextmanager
  25. async def lifespan(app: FastAPI):
  26. """
  27. Lifespan context manager for startup and shutdown events.
  28. """
  29. # Startup
  30. settings = get_settings()
  31. initialize_application(settings.base_dir)
  32. repo = ap_git.GitRepo.clone_if_needed(
  33. source=settings.ap_git_url,
  34. dest=settings.source_dir,
  35. recurse_submodules=True,
  36. )
  37. vehicles_manager = metadata_manager.VehiclesManager()
  38. ap_src_metadata_fetcher = metadata_manager.APSourceMetadataFetcher(
  39. ap_repo=repo,
  40. caching_enabled=True,
  41. redis_host=settings.redis_host,
  42. redis_port=settings.redis_port,
  43. )
  44. versions_fetcher = metadata_manager.VersionsFetcher(
  45. remotes_json_path=settings.remotes_json_path,
  46. ap_repo=repo
  47. )
  48. versions_fetcher.reload_remotes_json()
  49. build_mgr = build_manager.BuildManager(
  50. outdir=settings.outdir_parent,
  51. redis_host=settings.redis_host,
  52. redis_port=settings.redis_port
  53. )
  54. cleaner = build_manager.BuildArtifactsCleaner()
  55. progress_updater = build_manager.BuildProgressUpdater()
  56. inbuilt_builder = None
  57. inbuilt_builder_thread = None
  58. if settings.enable_inbuilt_builder:
  59. from builder.builder import Builder # noqa: E402
  60. inbuilt_builder = Builder(
  61. workdir=settings.workdir_parent,
  62. source_repo=repo
  63. )
  64. inbuilt_builder_thread = threading.Thread(
  65. target=inbuilt_builder.run,
  66. daemon=True
  67. )
  68. inbuilt_builder_thread.start()
  69. versions_fetcher.start()
  70. cleaner.start()
  71. progress_updater.start()
  72. app.state.repo = repo
  73. app.state.ap_src_metadata_fetcher = ap_src_metadata_fetcher
  74. app.state.versions_fetcher = versions_fetcher
  75. app.state.vehicles_manager = vehicles_manager
  76. app.state.build_manager = build_mgr
  77. app.state.inbuilt_builder = inbuilt_builder
  78. app.state.inbuilt_builder_thread = inbuilt_builder_thread
  79. app.state.limiter = limiter
  80. yield
  81. # Shutdown
  82. versions_fetcher.stop()
  83. cleaner.stop()
  84. progress_updater.stop()
  85. if inbuilt_builder is not None:
  86. inbuilt_builder.shutdown()
  87. if (inbuilt_builder_thread is not None and
  88. inbuilt_builder_thread.is_alive()):
  89. inbuilt_builder_thread.join()
  90. # Create FastAPI application
  91. app = FastAPI(
  92. title="CustomBuild API",
  93. description="API for ArduPilot Custom Firmware Builder",
  94. version="1.0.0",
  95. docs_url="/api/docs",
  96. redoc_url="/api/redoc",
  97. lifespan=lifespan,
  98. )
  99. # SlowAPIMiddleware is used for rate limiting
  100. app.add_middleware(SlowAPIMiddleware)
  101. app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)
  102. # Mount static files
  103. WEB_ROOT = Path(__file__).resolve().parent
  104. app.mount(
  105. "/static",
  106. StaticFiles(directory=str(WEB_ROOT / "static")),
  107. name="static"
  108. )
  109. # Include API v1 router
  110. app.include_router(v1_router, prefix="/api")
  111. # Include Web UI router
  112. app.include_router(ui_router)
  113. @app.get("/health")
  114. async def health_check():
  115. """Health check endpoint."""
  116. return {"status": "healthy"}
  117. if __name__ == "__main__":
  118. parser = argparse.ArgumentParser(description="CustomBuild API Server")
  119. parser.add_argument(
  120. "--port",
  121. type=int,
  122. default=int(os.getenv("WEB_PORT", 8080)),
  123. help="Port to run the server on (default: 8080 or WEB_PORT env var)"
  124. )
  125. args = parser.parse_args()
  126. import uvicorn
  127. uvicorn.run(
  128. "main:app",
  129. host="0.0.0.0",
  130. port=args.port,
  131. reload=True
  132. )