vehicles.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. """
  2. Vehicles service for handling vehicle-related business logic.
  3. """
  4. import logging
  5. from typing import List, Optional
  6. from fastapi import Request
  7. from schemas import (
  8. VehicleBase,
  9. RemoteInfo,
  10. VersionOut,
  11. BoardOut,
  12. FeatureOut,
  13. CategoryBase,
  14. FeatureDefault,
  15. )
  16. logger = logging.getLogger(__name__)
  17. class VehiclesService:
  18. """Service for managing vehicles, versions, boards, and features."""
  19. def __init__(self, vehicle_manager=None,
  20. versions_fetcher=None,
  21. ap_src_metadata_fetcher=None,
  22. repo=None):
  23. self.vehicles_manager = vehicle_manager
  24. self.versions_fetcher = versions_fetcher
  25. self.ap_src_metadata_fetcher = ap_src_metadata_fetcher
  26. self.repo = repo
  27. def get_all_vehicles(self) -> List[VehicleBase]:
  28. """Get list of all available vehicles."""
  29. logger.info('Fetching all vehicles')
  30. vehicles = self.vehicles_manager.get_all_vehicles()
  31. # Sort by name for consistent ordering
  32. sorted_vehicles = sorted(vehicles, key=lambda v: v.name)
  33. logger.info(f'Found vehicles: {[v.name for v in sorted_vehicles]}')
  34. return [
  35. VehicleBase(id=vehicle.id, name=vehicle.name)
  36. for vehicle in sorted_vehicles
  37. ]
  38. def get_vehicle(self, vehicle_id: str) -> Optional[VehicleBase]:
  39. """Get a specific vehicle by ID."""
  40. vehicle = self.vehicles_manager.get_vehicle_by_id(vehicle_id)
  41. if vehicle:
  42. return VehicleBase(id=vehicle.id, name=vehicle.name)
  43. return None
  44. def get_versions(
  45. self,
  46. vehicle_id: str,
  47. type_filter: Optional[str] = None
  48. ) -> List[VersionOut]:
  49. """Get all versions available for a specific vehicle."""
  50. versions = []
  51. for version_info in self.versions_fetcher.get_versions_for_vehicle(
  52. vehicle_id=vehicle_id
  53. ):
  54. # Apply type filter if provided
  55. if type_filter and version_info.release_type != type_filter:
  56. continue
  57. if version_info.release_type == "latest":
  58. title = f"Latest ({version_info.remote_info.name})"
  59. else:
  60. rel_type = version_info.release_type
  61. ver_num = version_info.version_number
  62. remote = version_info.remote_info.name
  63. title = f"{rel_type} {ver_num} ({remote})"
  64. versions.append(VersionOut(
  65. id=version_info.version_id,
  66. name=title,
  67. type=version_info.release_type,
  68. remote=RemoteInfo(
  69. name=version_info.remote_info.name,
  70. url=version_info.remote_info.url,
  71. ),
  72. commit_ref=version_info.commit_ref,
  73. vehicle_id=vehicle_id,
  74. ))
  75. # Sort by name
  76. return sorted(versions, key=lambda x: x.name)
  77. def get_version(
  78. self,
  79. vehicle_id: str,
  80. version_id: str
  81. ) -> Optional[VersionOut]:
  82. """Get details of a specific version for a vehicle."""
  83. versions = self.get_versions(vehicle_id)
  84. for version in versions:
  85. if version.id == version_id:
  86. return version
  87. return None
  88. def get_boards(
  89. self,
  90. vehicle_id: str,
  91. version_id: str
  92. ) -> List[BoardOut]:
  93. """Get all boards available for a specific vehicle version."""
  94. # Get version info
  95. version_info = self.versions_fetcher.get_version_info(
  96. vehicle_id=vehicle_id,
  97. version_id=version_id
  98. )
  99. if not version_info:
  100. return []
  101. logger.info(
  102. f'Board list requested for {vehicle_id} '
  103. f'{version_info.remote_info.name} {version_info.commit_ref}'
  104. )
  105. # Get boards list
  106. with self.repo.get_checkout_lock():
  107. boards = self.ap_src_metadata_fetcher.get_boards(
  108. remote=version_info.remote_info.name,
  109. commit_ref=version_info.commit_ref,
  110. vehicle_id=vehicle_id,
  111. )
  112. return [
  113. BoardOut(
  114. id=board,
  115. name=board,
  116. vehicle_id=vehicle_id,
  117. version_id=version_id
  118. )
  119. for board in boards
  120. ]
  121. def get_board(
  122. self,
  123. vehicle_id: str,
  124. version_id: str,
  125. board_id: str
  126. ) -> Optional[BoardOut]:
  127. """Get details of a specific board for a vehicle version."""
  128. boards = self.get_boards(vehicle_id, version_id)
  129. for board in boards:
  130. if board.id == board_id:
  131. return board
  132. return None
  133. def get_features(
  134. self,
  135. vehicle_id: str,
  136. version_id: str,
  137. board_id: str,
  138. category_id: Optional[str] = None
  139. ) -> List[FeatureOut]:
  140. """
  141. Get all features with defaults for a specific
  142. vehicle version/board.
  143. """
  144. # Get version info
  145. version_info = self.versions_fetcher.get_version_info(
  146. vehicle_id=vehicle_id,
  147. version_id=version_id
  148. )
  149. if not version_info:
  150. return []
  151. logger.info(
  152. f'Features requested for {vehicle_id} '
  153. f'{version_info.remote_info.name} {version_info.commit_ref}'
  154. )
  155. # Get build options from source
  156. with self.repo.get_checkout_lock():
  157. options = self.ap_src_metadata_fetcher.get_build_options_at_commit(
  158. remote=version_info.remote_info.name,
  159. commit_ref=version_info.commit_ref
  160. )
  161. # Try to fetch board-specific defaults from firmware-server
  162. board_defaults = None
  163. artifacts_dir = version_info.ap_build_artifacts_url
  164. if artifacts_dir is not None:
  165. board_defaults = (
  166. self.ap_src_metadata_fetcher.get_board_defaults_from_fw_server(
  167. artifacts_url=artifacts_dir,
  168. board_id=board_id,
  169. vehicle_id=vehicle_id,
  170. )
  171. )
  172. # Build feature list
  173. features = []
  174. for option in options:
  175. # Apply category filter if provided
  176. if category_id and option.category != category_id:
  177. continue
  178. # Determine default state and source
  179. if board_defaults and option.define in board_defaults:
  180. # Override with firmware server data
  181. default_enabled = (board_defaults[option.define] != 0)
  182. default_source = 'firmware-server'
  183. else:
  184. # Use build-options-py fallback
  185. default_enabled = (option.default != 0)
  186. default_source = 'build-options-py'
  187. # Parse dependencies (comma-separated labels)
  188. dependencies = []
  189. if option.dependency:
  190. dependencies = [
  191. label.strip()
  192. for label in option.dependency.split(',')
  193. ]
  194. features.append(FeatureOut(
  195. id=option.label,
  196. name=option.label,
  197. category=CategoryBase(
  198. id=option.category,
  199. name=option.category,
  200. description=None
  201. ),
  202. description=option.description,
  203. vehicle_id=vehicle_id,
  204. version_id=version_id,
  205. board_id=board_id,
  206. default=FeatureDefault(
  207. enabled=default_enabled,
  208. source=default_source
  209. ),
  210. dependencies=dependencies
  211. ))
  212. # Sort by name
  213. return sorted(features, key=lambda x: x.category.name)
  214. def get_feature(
  215. self,
  216. vehicle_id: str,
  217. version_id: str,
  218. board_id: str,
  219. feature_id: str
  220. ) -> Optional[FeatureOut]:
  221. """Get details of a specific feature for a vehicle version/board."""
  222. features = self.get_features(vehicle_id, version_id, board_id)
  223. for feature in features:
  224. if feature.id == feature_id:
  225. return feature
  226. return None
  227. def get_vehicles_service(request: Request) -> VehiclesService:
  228. """
  229. Get VehiclesService instance with dependencies from app state.
  230. Args:
  231. request: FastAPI Request object
  232. Returns:
  233. VehiclesService instance initialized with app state dependencies
  234. """
  235. return VehiclesService(
  236. vehicle_manager=request.app.state.vehicles_manager,
  237. versions_fetcher=request.app.state.versions_fetcher,
  238. ap_src_metadata_fetcher=request.app.state.ap_src_metadata_fetcher,
  239. repo=request.app.state.repo,
  240. )