Coverage for src/backend/InvenTree/machine/api.py: 80%

87 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 17:47 +0000

1"""JSON API for the machine app.""" 

2 

3from django.urls import include, path, re_path 

4 

5from drf_spectacular.utils import extend_schema 

6from rest_framework import permissions 

7from rest_framework.exceptions import NotFound 

8from rest_framework.response import Response 

9from rest_framework.views import APIView 

10 

11import InvenTree.permissions 

12import machine.serializers as MachineSerializers 

13from InvenTree.filters import SEARCH_ORDER_FILTER 

14from InvenTree.mixins import ListCreateAPI, RetrieveUpdateAPI, RetrieveUpdateDestroyAPI 

15from machine import registry 

16from machine.models import MachineConfig, MachineSetting 

17 

18 

19class MachineList(ListCreateAPI): 

20 """API endpoint for list of Machine objects. 

21 

22 - GET: Return a list of all Machine objects 

23 - POST: create a MachineConfig 

24 """ 

25 

26 queryset = MachineConfig.objects.all() 

27 serializer_class = MachineSerializers.MachineConfigSerializer 

28 

29 def get_serializer_class(self): 

30 """Allow driver, machine_type fields on creation.""" 

31 if self.request.method == 'POST': 

32 return MachineSerializers.MachineConfigCreateSerializer 

33 return super().get_serializer_class() 

34 

35 filter_backends = SEARCH_ORDER_FILTER 

36 

37 filterset_fields = ['machine_type', 'driver', 'active'] 

38 

39 ordering_fields = ['name', 'machine_type', 'driver', 'active'] 

40 

41 ordering = ['-active', 'machine_type'] 

42 

43 search_fields = ['name'] 

44 

45 

46class MachineDetail(RetrieveUpdateDestroyAPI): 

47 """API detail endpoint for MachineConfig object. 

48 

49 - GET: return a single MachineConfig 

50 - PUT: update a MachineConfig 

51 - PATCH: partial update a MachineConfig 

52 - DELETE: delete a MachineConfig 

53 """ 

54 

55 queryset = MachineConfig.objects.all() 

56 serializer_class = MachineSerializers.MachineConfigSerializer 

57 

58 

59def get_machine(machine_pk): 

60 """Get machine by pk. 

61 

62 Returns: 

63 BaseMachineType: The machine instance in the registry 

64 

65 Raises: 

66 NotFound: If machine is not found 

67 """ 

68 machine = registry.get_machine(machine_pk) 

69 

70 if machine is None: 70 ↛ 73line 70 didn't jump to line 73 because the condition on line 70 was always true

71 raise NotFound(detail=f"Machine '{machine_pk}' not found") 

72 

73 return machine 

74 

75 

76class MachineSettingList(APIView): 

77 """List endpoint for all machine related settings. 

78 

79 - GET: return all settings for a machine config 

80 """ 

81 

82 permission_classes = [InvenTree.permissions.IsAuthenticatedOrReadScope] 

83 

84 @extend_schema( 

85 responses={200: MachineSerializers.MachineSettingSerializer(many=True)} 

86 ) 

87 def get(self, request, pk): 

88 """Return all settings for a machine config.""" 

89 machine = get_machine(pk) 

90 

91 all_settings = [] 

92 

93 for settings, config_type in machine.setting_types: 

94 settings_dict = MachineSetting.all_settings( 

95 settings_definition=settings, 

96 machine_config=machine.machine_config, 

97 config_type=config_type, 

98 ) 

99 all_settings.extend(list(settings_dict.values())) 

100 

101 results = MachineSerializers.MachineSettingSerializer( 

102 list(all_settings), many=True 

103 ).data 

104 return Response(results) 

105 

106 

107class MachineSettingDetail(RetrieveUpdateAPI): 

108 """Detail endpoint for a machine-specific setting. 

109 

110 - GET: Get machine setting detail 

111 - PUT: Update machine setting 

112 - PATCH: Update machine setting 

113 

114 (Note that these cannot be created or deleted via API) 

115 """ 

116 

117 lookup_field = 'key' 

118 queryset = MachineSetting.objects.all() 

119 serializer_class = MachineSerializers.MachineSettingSerializer 

120 

121 def get_object(self): 

122 """Lookup machine setting object, based on the URL.""" 

123 pk = self.kwargs['pk'] 

124 key = self.kwargs['key'] 

125 config_type = MachineSetting.get_config_type(self.kwargs['config_type']) 

126 

127 machine = get_machine(pk) 

128 

129 setting_map = {d: s for s, d in machine.setting_types} 

130 if key.upper() not in setting_map[config_type]: 

131 raise NotFound( 

132 detail=f"Machine '{machine.name}' has no {config_type.name} setting matching '{key.upper()}'" 

133 ) 

134 

135 return MachineSetting.get_setting_object( 

136 key, machine_config=machine.machine_config, config_type=config_type 

137 ) 

138 

139 

140class MachineRestart(APIView): 

141 """Endpoint for performing a machine restart. 

142 

143 - POST: restart machine by pk 

144 """ 

145 

146 permission_classes = [ 

147 permissions.IsAuthenticated, 

148 InvenTree.permissions.IsStaffOrReadOnlyScope, 

149 ] 

150 

151 @extend_schema( 

152 request=None, responses={200: MachineSerializers.MachineRestartSerializer()} 

153 ) 

154 def post(self, request, pk): 

155 """Restart machine by pk.""" 

156 machine = get_machine(pk) 

157 registry.restart_machine(machine) 

158 

159 result = MachineSerializers.MachineRestartSerializer({'ok': True}).data 

160 return Response(result) 

161 

162 

163class MachineTypesList(APIView): 

164 """List API Endpoint for all discovered machine types. 

165 

166 - GET: List all machine types 

167 """ 

168 

169 permission_classes = [InvenTree.permissions.IsAuthenticatedOrReadScope] 

170 

171 @extend_schema(responses={200: MachineSerializers.MachineTypeSerializer(many=True)}) 

172 def get(self, request): 

173 """List of all machine types.""" 

174 machine_types = list(registry.get_machine_types()) 

175 results = MachineSerializers.MachineTypeSerializer( 

176 machine_types, many=True 

177 ).data 

178 return Response(results) 

179 

180 

181class MachineDriverList(APIView): 

182 """List API Endpoint for all discovered machine driver types.""" 

183 

184 permission_classes = [InvenTree.permissions.IsAuthenticatedOrReadScope] 

185 

186 @extend_schema( 

187 responses={200: MachineSerializers.MachineDriverSerializer(many=True)} 

188 ) 

189 def get(self, request): 

190 """List all machine drivers.""" 

191 machine_type = request.query_params.get('machine_type', None) 

192 

193 drivers = registry.get_driver_types(machine_type) 

194 

195 results = MachineSerializers.MachineDriverSerializer( 

196 list(drivers), many=True 

197 ).data 

198 return Response(results) 

199 

200 

201class RegistryStatusView(APIView): 

202 """Status API endpoint for the machine registry. 

203 

204 - GET: Provide status data for the machine registry 

205 """ 

206 

207 permission_classes = [InvenTree.permissions.IsAuthenticatedOrReadScope] 

208 

209 serializer_class = MachineSerializers.MachineRegistryStatusSerializer 

210 

211 @extend_schema( 

212 responses={200: MachineSerializers.MachineRegistryStatusSerializer()} 

213 ) 

214 def get(self, request): 

215 """Provide status data for the machine registry.""" 

216 result = MachineSerializers.MachineRegistryStatusSerializer({ 

217 'registry_errors': [{'message': str(error)} for error in registry.errors] 

218 }).data 

219 

220 return Response(result) 

221 

222 

223machine_api_urls = [ 

224 # machine types 

225 path('types/', MachineTypesList.as_view(), name='api-machine-types'), 

226 # machine drivers 

227 path('drivers/', MachineDriverList.as_view(), name='api-machine-drivers'), 

228 # registry status 

229 path('status/', RegistryStatusView.as_view(), name='api-machine-registry-status'), 

230 # detail views for a single Machine 

231 path( 

232 '<uuid:pk>/', 

233 include([ 

234 # settings 

235 path( 

236 'settings/', 

237 include([ 

238 re_path( 

239 r'^(?P<config_type>M|D)/(?P<key>\w+)/', 

240 MachineSettingDetail.as_view(), 

241 name='api-machine-settings-detail', 

242 ), 

243 path('', MachineSettingList.as_view(), name='api-machine-settings'), 

244 ]), 

245 ), 

246 # restart 

247 path('restart/', MachineRestart.as_view(), name='api-machine-restart'), 

248 # detail 

249 path('', MachineDetail.as_view(), name='api-machine-detail'), 

250 ]), 

251 ), 

252 # machine list and create 

253 path('', MachineList.as_view(), name='api-machine-list'), 

254]