touchdesigner-mcp-server 0.4.0-alpha.0 → 0.4.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.ja.md +4 -4
  2. package/README.md +4 -3
  3. package/dist/cli.js +0 -0
  4. package/dist/gen/endpoints/TouchDesignerAPI.js +1 -1
  5. package/dist/gen/mcp/touchDesignerAPI.zod.js +1 -1
  6. package/dist/index.js +0 -0
  7. package/dist/server/touchDesignerServer.js +1 -1
  8. package/package.json +14 -12
  9. package/td/genHandlers.js +0 -47
  10. package/td/import_modules.py +0 -52
  11. package/td/mcp_webserver_base.tox +0 -0
  12. package/td/modules/mcp/controllers/__init__.py +0 -9
  13. package/td/modules/mcp/controllers/api_controller.py +0 -637
  14. package/td/modules/mcp/controllers/generated_handlers.py +0 -365
  15. package/td/modules/mcp/controllers/openapi_router.py +0 -265
  16. package/td/modules/mcp/services/__init__.py +0 -8
  17. package/td/modules/mcp/services/api_service.py +0 -555
  18. package/td/modules/mcp_webserver_script.py +0 -134
  19. package/td/modules/td_server/.dockerignore +0 -72
  20. package/td/modules/td_server/.openapi-generator/FILES +0 -55
  21. package/td/modules/td_server/.openapi-generator/VERSION +0 -1
  22. package/td/modules/td_server/.openapi-generator-ignore +0 -23
  23. package/td/modules/td_server/.travis.yml +0 -14
  24. package/td/modules/td_server/Dockerfile +0 -16
  25. package/td/modules/td_server/README.md +0 -49
  26. package/td/modules/td_server/git_push.sh +0 -57
  27. package/td/modules/td_server/openapi_server/__init__.py +0 -0
  28. package/td/modules/td_server/openapi_server/__main__.py +0 -19
  29. package/td/modules/td_server/openapi_server/controllers/__init__.py +0 -0
  30. package/td/modules/td_server/openapi_server/controllers/default_controller.py +0 -162
  31. package/td/modules/td_server/openapi_server/controllers/security_controller.py +0 -2
  32. package/td/modules/td_server/openapi_server/encoder.py +0 -19
  33. package/td/modules/td_server/openapi_server/models/__init__.py +0 -33
  34. package/td/modules/td_server/openapi_server/models/base_model.py +0 -68
  35. package/td/modules/td_server/openapi_server/models/create_node200_response.py +0 -125
  36. package/td/modules/td_server/openapi_server/models/create_node200_response_data.py +0 -63
  37. package/td/modules/td_server/openapi_server/models/create_node_request.py +0 -123
  38. package/td/modules/td_server/openapi_server/models/delete_node200_response.py +0 -125
  39. package/td/modules/td_server/openapi_server/models/delete_node200_response_data.py +0 -91
  40. package/td/modules/td_server/openapi_server/models/exec_node_method200_response.py +0 -125
  41. package/td/modules/td_server/openapi_server/models/exec_node_method200_response_data.py +0 -65
  42. package/td/modules/td_server/openapi_server/models/exec_node_method_request.py +0 -153
  43. package/td/modules/td_server/openapi_server/models/exec_node_method_request_args_inner.py +0 -34
  44. package/td/modules/td_server/openapi_server/models/exec_python_script200_response.py +0 -125
  45. package/td/modules/td_server/openapi_server/models/exec_python_script200_response_data.py +0 -65
  46. package/td/modules/td_server/openapi_server/models/exec_python_script200_response_data_result.py +0 -63
  47. package/td/modules/td_server/openapi_server/models/exec_python_script_request.py +0 -65
  48. package/td/modules/td_server/openapi_server/models/get_node_detail200_response.py +0 -125
  49. package/td/modules/td_server/openapi_server/models/get_nodes200_response.py +0 -125
  50. package/td/modules/td_server/openapi_server/models/get_nodes200_response_data.py +0 -65
  51. package/td/modules/td_server/openapi_server/models/get_td_info200_response.py +0 -125
  52. package/td/modules/td_server/openapi_server/models/get_td_info200_response_data.py +0 -155
  53. package/td/modules/td_server/openapi_server/models/get_td_python_class_details200_response.py +0 -125
  54. package/td/modules/td_server/openapi_server/models/get_td_python_classes200_response.py +0 -125
  55. package/td/modules/td_server/openapi_server/models/get_td_python_classes200_response_data.py +0 -63
  56. package/td/modules/td_server/openapi_server/models/td_node.py +0 -175
  57. package/td/modules/td_server/openapi_server/models/td_node_family_type.py +0 -44
  58. package/td/modules/td_server/openapi_server/models/td_python_class_details.py +0 -191
  59. package/td/modules/td_server/openapi_server/models/td_python_class_info.py +0 -127
  60. package/td/modules/td_server/openapi_server/models/td_python_method_info.py +0 -121
  61. package/td/modules/td_server/openapi_server/models/td_python_property_info.py +0 -123
  62. package/td/modules/td_server/openapi_server/models/update_node200_response.py +0 -125
  63. package/td/modules/td_server/openapi_server/models/update_node200_response_data.py +0 -149
  64. package/td/modules/td_server/openapi_server/models/update_node200_response_data_failed_inner.py +0 -91
  65. package/td/modules/td_server/openapi_server/models/update_node_request.py +0 -93
  66. package/td/modules/td_server/openapi_server/openapi/openapi.yaml +0 -975
  67. package/td/modules/td_server/openapi_server/test/__init__.py +0 -16
  68. package/td/modules/td_server/openapi_server/test/test_default_controller.py +0 -201
  69. package/td/modules/td_server/openapi_server/typing_utils.py +0 -30
  70. package/td/modules/td_server/openapi_server/util.py +0 -147
  71. package/td/modules/td_server/requirements.txt +0 -13
  72. package/td/modules/td_server/setup.py +0 -37
  73. package/td/modules/td_server/test-requirements.txt +0 -4
  74. package/td/modules/td_server/tox.ini +0 -11
  75. package/td/modules/utils/config.py +0 -7
  76. package/td/modules/utils/error_handling.py +0 -104
  77. package/td/modules/utils/logging.py +0 -23
  78. package/td/modules/utils/result.py +0 -40
  79. package/td/modules/utils/serialization.py +0 -57
  80. package/td/modules/utils/types.py +0 -33
  81. package/td/modules/utils/utils_logging.py +0 -60
  82. package/td/templates/mcp/api_controller_handlers.mustache +0 -63
@@ -1,637 +0,0 @@
1
- """
2
- OpenAPI schema based API controller for TouchDesigner MCP Web Server
3
-
4
- This controller uses the OpenAPIRouter to route requests based on the OpenAPI schema,
5
- and converts between API models and internal data structures.
6
- """
7
-
8
- import json
9
- import traceback
10
- from typing import Any, Dict, List, Optional, Protocol, Tuple
11
-
12
- from mcp.controllers.generated_handlers import *
13
- from mcp.controllers.openapi_router import OpenAPIRouter
14
- from utils.error_handling import ErrorCategory
15
- from utils.logging import log_message
16
- from utils.result import error_result
17
- from utils.serialization import safe_serialize
18
- from utils.types import LogLevel, Result
19
-
20
- try:
21
- from td_server.openapi_server.models.create_node200_response import (
22
- CreateNode200Response,
23
- )
24
- from td_server.openapi_server.models.delete_node200_response import (
25
- DeleteNode200Response,
26
- )
27
- from td_server.openapi_server.models.exec_node_method200_response import (
28
- ExecNodeMethod200Response,
29
- )
30
- from td_server.openapi_server.models.exec_python_script200_response import (
31
- ExecPythonScript200Response,
32
- )
33
- from td_server.openapi_server.models.get_node_detail200_response import (
34
- GetNodeDetail200Response,
35
- )
36
- from td_server.openapi_server.models.get_nodes200_response import (
37
- GetNodes200Response,
38
- )
39
- from td_server.openapi_server.models.get_td_info200_response import (
40
- GetTdInfo200Response,
41
- )
42
- from td_server.openapi_server.models.update_node200_response import (
43
- UpdateNode200Response,
44
- )
45
-
46
- log_message("OpenAPI response models imported successfully", LogLevel.DEBUG)
47
- except ImportError as e:
48
- log_message(
49
- f"OpenAPI models import failed, using raw dictionaries: {e}", LogLevel.WARNING
50
- )
51
-
52
-
53
- class ApiServiceProtocol(Protocol):
54
- """Protocol defining the API service interface"""
55
-
56
- def get_td_info(self) -> Result: ...
57
-
58
- def get_nodes(self, parent_path: str, pattern: Optional[str] = None, include_properties: bool = False) -> Result: ...
59
-
60
- def create_node(
61
- self,
62
- parent_path: str,
63
- node_type: str,
64
- node_name: Optional[str] = None,
65
- parameters: Optional[Dict[str, Any]] = None,
66
- ) -> Result: ...
67
-
68
- def delete_node(self, node_path: str) -> Result: ...
69
-
70
- def get_node_detail(self, node_path: str) -> Result: ...
71
-
72
- def update_node(self, node_path: str, properties: Dict[str, Any]) -> Result: ...
73
-
74
- def exec_script(self, script: str) -> Result: ...
75
-
76
- def get_python_classes(self) -> Result: ...
77
-
78
- def get_python_class_details(self, class_name: str) -> Result: ...
79
-
80
- def call_node_method(
81
- self,
82
- node_path: str,
83
- method_name: str,
84
- args: List[Any] = None,
85
- kwargs: Dict[str, Any] = None,
86
- ) -> Result: ...
87
-
88
-
89
- class RequestProcessor:
90
- """
91
- Responsible for processing and normalizing HTTP requests from different sources
92
-
93
- This class helps achieve separation of concerns by isolating request processing logic
94
- from the controller class, improving maintainability and testability.
95
- """
96
-
97
- @staticmethod
98
- def normalize_request(
99
- request: Dict[str, Any],
100
- ) -> Tuple[str, str, Dict[str, Any], str]:
101
- """
102
- Normalize request object to handle different request formats
103
-
104
- Args:
105
- request: Request object that might be in different formats
106
-
107
- Returns:
108
- Tuple containing (method, path, query_params, body)
109
- """
110
- method = ""
111
- path = ""
112
- query_params = {}
113
- body = ""
114
-
115
- try:
116
- method = RequestProcessor._extract_method(request)
117
-
118
- path, uri_query_params = RequestProcessor._extract_path_and_query(request)
119
- query_params.update(uri_query_params)
120
-
121
- if "query" in request and isinstance(request["query"], dict):
122
- query_params.update(request["query"])
123
-
124
- if "pars" in request and isinstance(request["pars"], dict):
125
- log_message(
126
- f"Found 'pars' in request: {request['pars']}", LogLevel.DEBUG
127
- )
128
- query_params.update(request["pars"])
129
-
130
- body = RequestProcessor._extract_body(request)
131
-
132
- except Exception as e:
133
- log_message(f"Error during request normalization: {str(e)}", LogLevel.ERROR)
134
- log_message(traceback.format_exc(), LogLevel.DEBUG)
135
-
136
- return method, path, query_params, body
137
-
138
- @staticmethod
139
- def _extract_method(request: Dict[str, Any]) -> str:
140
- """Extract HTTP method from request"""
141
- if "method" in request:
142
- if isinstance(request["method"], str):
143
- return request["method"].upper()
144
- return ""
145
-
146
- @staticmethod
147
- def _extract_path_and_query(request: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]:
148
- """Extract path and query parameters from request"""
149
- path = ""
150
- query_params = {}
151
-
152
- uri = request.get("uri", {})
153
-
154
- if isinstance(uri, dict):
155
- path = uri.get("path", "")
156
- uri_query = uri.get("query", {})
157
- if isinstance(uri_query, dict):
158
- query_params.update(uri_query)
159
- elif isinstance(uri, str):
160
- path = uri
161
-
162
- return path, query_params
163
-
164
- @staticmethod
165
- def _extract_body(request: Dict[str, Any]) -> str:
166
- """Extract body content from request"""
167
- body = ""
168
-
169
- body_content = request.get("body", "")
170
-
171
- if isinstance(body_content, (str, bytes)):
172
- body = (
173
- body_content
174
- if isinstance(body_content, str)
175
- else body_content.decode("utf-8", errors="replace")
176
- )
177
- elif isinstance(body_content, dict):
178
- body = json.dumps(body_content)
179
-
180
- if not body and "data" in request:
181
- data = request.get("data", "")
182
- if isinstance(data, bytes):
183
- body = data.decode("utf-8", errors="replace") if data else ""
184
- elif isinstance(data, str):
185
- body = data
186
- elif isinstance(data, dict):
187
- body = json.dumps(data)
188
-
189
- return body
190
-
191
-
192
- class IController(Protocol):
193
- """
194
- Controller interface for handling HTTP requests
195
-
196
- All controllers should implement this interface to ensure consistency across
197
- different controller implementations. This enforces a unified approach to
198
- request handling throughout the application.
199
- """
200
-
201
- def onHTTPRequest(
202
- self, webServerDAT: Any, request: Dict[str, Any], response: Dict[str, Any]
203
- ) -> Dict[str, Any]:
204
- """
205
- Process an HTTP request from TouchDesigner WebServerDAT
206
-
207
- Args:
208
- webServerDAT: Reference to the WebServerDAT object
209
- request: Dictionary containing request information
210
- response: Dictionary for storing response information
211
-
212
- Returns:
213
- Updated response dictionary
214
- """
215
- ...
216
-
217
-
218
- class APIControllerOpenAPI(IController):
219
- """
220
- API controller that uses OpenAPI schema for routing and model conversion
221
-
222
- Implements the IController interface for consistency with other controllers.
223
- """
224
-
225
- def __init__(self, service: Optional[ApiServiceProtocol] = None):
226
- """
227
- Initialize the controller with a service implementation
228
-
229
- Args:
230
- service: Service implementation (uses default if None)
231
- """
232
- if service is None:
233
- from mcp.services.api_service import api_service
234
-
235
- self._service = api_service
236
- else:
237
- self._service = service
238
-
239
- self.router = OpenAPIRouter()
240
- self.register_handlers()
241
-
242
- def _normalize_request(
243
- self, request: Dict[str, Any]
244
- ) -> Tuple[str, str, Dict[str, Any], str]:
245
- """
246
- Normalize request object to handle different request formats
247
-
248
- Args:
249
- request: Request object that might be in different formats
250
-
251
- Returns:
252
- Tuple containing (method, path, query_params, body)
253
- """
254
- return RequestProcessor.normalize_request(request)
255
-
256
- def onHTTPRequest(
257
- self, webServerDAT: Any, request: Dict[str, Any], response: Dict[str, Any]
258
- ) -> Dict[str, Any]:
259
- """
260
- Handle HTTP request from TouchDesigner WebServer DAT
261
-
262
- Implements IController interface for consistent handling across controllers.
263
-
264
- Args:
265
- webServerDAT: Reference to the WebServerDAT object
266
- request: Dictionary containing request information
267
- response: Dictionary for storing response information
268
-
269
- Returns:
270
- Updated response dictionary
271
- """
272
-
273
- if "headers" not in response:
274
- response["headers"] = {}
275
-
276
- response["headers"]["Access-Control-Allow-Origin"] = "*"
277
- response["headers"][
278
- "Access-Control-Allow-Methods"
279
- ] = "GET, POST, PUT, DELETE, PATCH, OPTIONS"
280
- response["headers"][
281
- "Access-Control-Allow-Headers"
282
- ] = "Content-Type, Authorization"
283
- response["headers"]["Content-Type"] = "application/json"
284
-
285
- try:
286
- method, path, query_params, body = self._normalize_request(request)
287
- except Exception as e:
288
-
289
- response["statusCode"] = 500
290
- response["statusReason"] = "Internal Server Error"
291
- response["data"] = json.dumps(
292
- {
293
- "success": False,
294
- "error": f"Request normalization error: {str(e)}",
295
- "errorCategory": str(ErrorCategory.INTERNAL),
296
- }
297
- )
298
- return response
299
-
300
- try:
301
- if method == "OPTIONS":
302
- response["statusCode"] = 200
303
- response["statusReason"] = "OK"
304
- response["data"] = "{}"
305
- return response
306
-
307
- result = self.router.route_request(method, path, query_params, body)
308
-
309
- if result["success"]:
310
- response["statusCode"] = 200
311
- response["statusReason"] = "OK"
312
- response["data"] = json.dumps(safe_serialize(result))
313
- else:
314
- error_category = result.get("errorCategory", ErrorCategory.VALIDATION)
315
- response["statusCode"] = 200
316
- response["statusReason"] = self._get_status_reason_for_error(
317
- error_category
318
- )
319
- response["data"] = json.dumps(
320
- {
321
- "success": False,
322
- "data" : None,
323
- "error": result["error"],
324
- "errorCategory": (
325
- str(error_category)
326
- if hasattr(error_category, "__str__")
327
- else None
328
- ),
329
- }
330
- )
331
-
332
- except Exception as e:
333
- log_message(f"Error handling request: {e}", LogLevel.ERROR)
334
- log_message(traceback.format_exc(), LogLevel.DEBUG)
335
-
336
- response["statusCode"] = 500
337
- response["statusReason"] = "Internal Server Error"
338
- response["data"] = json.dumps(
339
- {
340
- "success": False,
341
- "error": f"Internal server error: {str(e)}",
342
- "errorCategory": str(ErrorCategory.INTERNAL),
343
- }
344
- )
345
-
346
- log_message(
347
- f"Response status: {response['statusCode']}, {response['data']}",
348
- LogLevel.DEBUG,
349
- )
350
- return response
351
-
352
- def _get_status_code_for_error(self, error_category) -> int:
353
- """
354
- Map error category to HTTP status code
355
-
356
- Args:
357
- error_category: The error category
358
-
359
- Returns:
360
- Appropriate HTTP status code
361
- """
362
- if error_category == ErrorCategory.NOT_FOUND:
363
- return 404
364
- elif error_category == ErrorCategory.PERMISSION:
365
- return 403
366
- elif error_category == ErrorCategory.VALIDATION:
367
- return 400
368
- elif error_category == ErrorCategory.EXTERNAL:
369
- return 502
370
- else:
371
- return 500
372
-
373
- def _get_status_reason_for_error(self, error_category) -> str:
374
- """
375
- Map error category to HTTP status reason
376
-
377
- Args:
378
- error_category: The error category
379
-
380
- Returns:
381
- Status reason text
382
- """
383
- if error_category == ErrorCategory.NOT_FOUND:
384
- return "Not Found"
385
- elif error_category == ErrorCategory.PERMISSION:
386
- return "Forbidden"
387
- elif error_category == ErrorCategory.VALIDATION:
388
- return "Bad Request"
389
- elif error_category == ErrorCategory.EXTERNAL:
390
- return "Bad Gateway"
391
- else:
392
- return "Internal Server Error"
393
-
394
- def register_handlers(self) -> None:
395
- """Register all generated handlers automatically"""
396
- import mcp.controllers.generated_handlers as handlers
397
-
398
- for operation_id in handlers.__all__:
399
- handler = getattr(handlers, operation_id, None)
400
- if callable(handler):
401
- self.router.register_handler(operation_id, handler)
402
- else:
403
- log_message(f"Handler for {operation_id} not found.", LogLevel.WARNING)
404
-
405
- def _handle_get_td_info(self, body: Optional[str] = None, **kwargs) -> Result:
406
- """
407
- Handle get_td_info operation
408
-
409
- Returns server information such as version and platform.
410
- """
411
- service_result = self._service.get_td_info()
412
-
413
- response_data = GetTdInfo200Response().from_dict(service_result)
414
- return response_data.to_dict()
415
-
416
- def _handle_get_nodes(
417
- self,
418
- parentPath: str,
419
- pattern: Optional[str] = None,
420
- includeProperties: Optional[bool] = None,
421
- body: Optional[str] = None,
422
- **kwargs,
423
- ) -> Result:
424
- """
425
- Handle get_nodes operation
426
-
427
- Args:
428
- parentPath: Path of the parent node to get children from
429
- pattern: Optional pattern to filter nodes by
430
- includeProperties: Whether to include full node properties (default: False)
431
-
432
- Returns:
433
- List of nodes under the specified parent path
434
- """
435
- # Convert camelCase to snake_case and provide default value
436
- include_properties = includeProperties if includeProperties is not None else False
437
-
438
- service_result = self._service.get_nodes(parentPath, pattern, include_properties)
439
- response_data = GetNodes200Response().from_dict(service_result)
440
- return response_data.to_dict()
441
-
442
- def _handle_create_node(self, body: str, **kwargs) -> Result:
443
- """
444
- Handle create_node operation
445
-
446
- Args:
447
- body: Request body containing node creation parameters
448
-
449
- Returns:
450
- Information about the created node
451
- """
452
- if not body:
453
- return error_result("Request body is required")
454
-
455
- try:
456
- request_data = json.loads(body)
457
- except json.JSONDecodeError as e:
458
- return error_result(f"Invalid JSON in request body: {str(e)}")
459
-
460
- parent_path = request_data.get("parentPath")
461
- node_type = request_data.get("nodeType")
462
- node_name = request_data.get("nodeName")
463
- parameters = request_data.get("parameters", {})
464
-
465
- if not parent_path:
466
- return error_result("parentPath is required")
467
-
468
- if not node_type:
469
- return error_result("nodeType is required")
470
-
471
- service_result = self._service.create_node(
472
- parent_path, node_type, node_name, parameters
473
- )
474
-
475
- response_data = CreateNode200Response().from_dict(service_result)
476
- return response_data.to_dict()
477
-
478
- def _handle_delete_node(
479
- self, nodePath: str, body: Optional[str] = None, **kwargs
480
- ) -> Result:
481
- """
482
- Handle delete_node operation
483
-
484
- Args:
485
- nodePath: Path of the node to delete
486
-
487
- Returns:
488
- Result of the deletion operation
489
- """
490
- service_result = self._service.delete_node(nodePath)
491
-
492
- response_data = DeleteNode200Response().from_dict(service_result)
493
- return response_data.to_dict()
494
-
495
- def _handle_get_node_detail(
496
- self, nodePath: str, body: Optional[str] = None, **kwargs
497
- ) -> Result:
498
- """
499
- Handle get_node_detail operation
500
-
501
- Args:
502
- nodePath: Path of the node to get properties for
503
-
504
- Returns:
505
- Node properties
506
- """
507
- service_result = self._service.get_node_detail(nodePath)
508
- response_data = GetNodeDetail200Response().from_dict(service_result)
509
- return response_data.to_dict()
510
-
511
- def _handle_update_node(self, body: str, **kwargs) -> Result:
512
- """
513
- Handle update_node operation
514
-
515
- Args:
516
- nodePath: Path of the node to update
517
- body: Request body containing properties to update
518
-
519
- Returns:
520
- Result of the update operation
521
- """
522
- if not body:
523
- return error_result("Request body is required")
524
-
525
- try:
526
- request_data = json.loads(body)
527
- except json.JSONDecodeError as e:
528
- return error_result(f"Invalid JSON in request body: {str(e)}")
529
-
530
- nodePath = request_data.get("nodePath", "")
531
- if not nodePath:
532
- return error_result("nodePath is required")
533
-
534
- properties = request_data.get("properties", {})
535
-
536
- if not properties or not isinstance(properties, dict):
537
- return error_result("properties object is required")
538
-
539
- service_result = self._service.update_node(nodePath, properties)
540
- response_data = UpdateNode200Response().from_dict(service_result)
541
- return response_data.to_dict()
542
-
543
- def _handle_exec_node_method(self, body: str, **kwargs) -> Result:
544
- """
545
- Handle exec_node_method operation
546
-
547
- Args:
548
- body: Request body containing node path, method name, and arguments
549
-
550
- Returns:
551
- Result of the method execution
552
- """
553
- if not body:
554
- return error_result("Request body is required")
555
-
556
- try:
557
- request_data = json.loads(body)
558
- except json.JSONDecodeError as e:
559
- return error_result(f"Invalid JSON in request body: {str(e)}")
560
-
561
- node_path = request_data.get("nodePath")
562
- method = request_data.get("method")
563
- args = request_data.get("args", [])
564
- kwargs = request_data.get("kwargs", {})
565
-
566
- if not node_path:
567
- return error_result("nodePath is required")
568
-
569
- if not method:
570
- return error_result("method is required")
571
-
572
- service_result = self._service.call_node_method(node_path, method, args, kwargs)
573
-
574
- if not service_result["success"]:
575
- return service_result
576
-
577
- response_data = ExecNodeMethod200Response().from_dict(service_result)
578
- return response_data.to_dict()
579
-
580
- def _handle_exec_python_script(self, body: str, **kwargs) -> Result:
581
- """
582
- Handle exec_python_script operation
583
-
584
- Args:
585
- body: Request body containing Python script to execute
586
-
587
- Returns:
588
- Result of the script execution
589
- """
590
- if not body:
591
- return error_result("Request body is required")
592
-
593
- try:
594
- request_data = json.loads(body)
595
- except json.JSONDecodeError as e:
596
- return error_result(f"Invalid JSON in request body: {str(e)}")
597
-
598
- script = request_data.get("script")
599
-
600
- if not script:
601
- return error_result("script is required")
602
-
603
- service_result = self._service.exec_script(script)
604
-
605
- if not service_result["success"]:
606
- return service_result
607
-
608
- response_data = ExecPythonScript200Response().from_dict(service_result)
609
- return response_data.to_dict()
610
-
611
- def _handle_get_td_python_classes(
612
- self, body: Optional[str] = None, **kwargs
613
- ) -> Result:
614
- """
615
- Handle get_td_python_classes operation
616
-
617
- Returns:
618
- List of Python classes available in TouchDesigner
619
- """
620
- return self._service.get_python_classes()
621
-
622
- def _handle_get_td_python_class_details(
623
- self, className: str, body: Optional[str] = None, **kwargs
624
- ) -> Result:
625
- """
626
- Handle get_td_python_class_details operation
627
-
628
- Args:
629
- className: Name of the Python class to get details for
630
-
631
- Returns:
632
- Details of the specified Python class
633
- """
634
- return self._service.get_python_class_details(className)
635
-
636
-
637
- api_controller_openapi = APIControllerOpenAPI()