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
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
1"""JSON API for the machine app."""
3from django.urls import include, path, re_path
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
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
19class MachineList(ListCreateAPI):
20 """API endpoint for list of Machine objects.
22 - GET: Return a list of all Machine objects
23 - POST: create a MachineConfig
24 """
26 queryset = MachineConfig.objects.all()
27 serializer_class = MachineSerializers.MachineConfigSerializer
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()
35 filter_backends = SEARCH_ORDER_FILTER
37 filterset_fields = ['machine_type', 'driver', 'active']
39 ordering_fields = ['name', 'machine_type', 'driver', 'active']
41 ordering = ['-active', 'machine_type']
43 search_fields = ['name']
46class MachineDetail(RetrieveUpdateDestroyAPI):
47 """API detail endpoint for MachineConfig object.
49 - GET: return a single MachineConfig
50 - PUT: update a MachineConfig
51 - PATCH: partial update a MachineConfig
52 - DELETE: delete a MachineConfig
53 """
55 queryset = MachineConfig.objects.all()
56 serializer_class = MachineSerializers.MachineConfigSerializer
59def get_machine(machine_pk):
60 """Get machine by pk.
62 Returns:
63 BaseMachineType: The machine instance in the registry
65 Raises:
66 NotFound: If machine is not found
67 """
68 machine = registry.get_machine(machine_pk)
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")
73 return machine
76class MachineSettingList(APIView):
77 """List endpoint for all machine related settings.
79 - GET: return all settings for a machine config
80 """
82 permission_classes = [InvenTree.permissions.IsAuthenticatedOrReadScope]
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)
91 all_settings = []
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()))
101 results = MachineSerializers.MachineSettingSerializer(
102 list(all_settings), many=True
103 ).data
104 return Response(results)
107class MachineSettingDetail(RetrieveUpdateAPI):
108 """Detail endpoint for a machine-specific setting.
110 - GET: Get machine setting detail
111 - PUT: Update machine setting
112 - PATCH: Update machine setting
114 (Note that these cannot be created or deleted via API)
115 """
117 lookup_field = 'key'
118 queryset = MachineSetting.objects.all()
119 serializer_class = MachineSerializers.MachineSettingSerializer
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'])
127 machine = get_machine(pk)
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 )
135 return MachineSetting.get_setting_object(
136 key, machine_config=machine.machine_config, config_type=config_type
137 )
140class MachineRestart(APIView):
141 """Endpoint for performing a machine restart.
143 - POST: restart machine by pk
144 """
146 permission_classes = [
147 permissions.IsAuthenticated,
148 InvenTree.permissions.IsStaffOrReadOnlyScope,
149 ]
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)
159 result = MachineSerializers.MachineRestartSerializer({'ok': True}).data
160 return Response(result)
163class MachineTypesList(APIView):
164 """List API Endpoint for all discovered machine types.
166 - GET: List all machine types
167 """
169 permission_classes = [InvenTree.permissions.IsAuthenticatedOrReadScope]
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)
181class MachineDriverList(APIView):
182 """List API Endpoint for all discovered machine driver types."""
184 permission_classes = [InvenTree.permissions.IsAuthenticatedOrReadScope]
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)
193 drivers = registry.get_driver_types(machine_type)
195 results = MachineSerializers.MachineDriverSerializer(
196 list(drivers), many=True
197 ).data
198 return Response(results)
201class RegistryStatusView(APIView):
202 """Status API endpoint for the machine registry.
204 - GET: Provide status data for the machine registry
205 """
207 permission_classes = [InvenTree.permissions.IsAuthenticatedOrReadScope]
209 serializer_class = MachineSerializers.MachineRegistryStatusSerializer
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
220 return Response(result)
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]