codespeak-cli 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,676 @@
1
+ from collections.abc import Iterator
2
+ from typing import Any
3
+
4
+ import api_stubs.os_environment_pb2 as os_env_pb2
5
+ from api_stubs.operation_logging import OsEnvRequestLogging
6
+ from api_stubs.os_environment_pb2 import (
7
+ AppendToFileRequest,
8
+ AppendToFileResponse,
9
+ ChildProcessOutcome,
10
+ CopyRequest,
11
+ CopyResponse,
12
+ DeleteFileRequest,
13
+ DeleteFileResponse,
14
+ DeleteRecursivelyRequest,
15
+ DeleteRecursivelyResponse,
16
+ ErrorInfo,
17
+ ErrorType,
18
+ ExistsRequest,
19
+ ExistsResponse,
20
+ GetAttributesRequest,
21
+ GetAttributesResponse,
22
+ GlobRequest,
23
+ GlobResponse,
24
+ GrepRequest,
25
+ GrepResponse,
26
+ IsDirectoryRequest,
27
+ IsDirectoryResponse,
28
+ IsFileRequest,
29
+ IsFileResponse,
30
+ ListDirectoryRequest,
31
+ ListDirectoryResponse,
32
+ ListTreeRequest,
33
+ ListTreeResponse,
34
+ MkdirsRequest,
35
+ MkdirsResponse,
36
+ OperationRequest,
37
+ OperationResponse,
38
+ ReadFileRequest,
39
+ ReadFileResponse,
40
+ ResolvePathRequest,
41
+ ResolvePathResponse,
42
+ ResolveUserNameRequest,
43
+ ResolveUserNameResponse,
44
+ RunChildProcessRequest,
45
+ RunChildProcessResponse,
46
+ RunDjangoDevServerRequest,
47
+ RunDjangoDevServerResponse,
48
+ TraverseRequest,
49
+ TraverseResponse,
50
+ WriteFileRequest,
51
+ WriteFileResponse,
52
+ )
53
+ from api_stubs.os_environment_pb2 import (
54
+ ChildProcessResult as ChildProcessResultProto,
55
+ )
56
+ from api_stubs.os_environment_pb2 import (
57
+ DjangoServerRunMode as DjangoServerRunModeProto,
58
+ )
59
+ from api_stubs.os_environment_pb2 import (
60
+ EntryAttributes as EntryAttributesProto,
61
+ )
62
+ from api_stubs.os_environment_pb2 import GrepFileCount as GrepFileCountProto
63
+ from api_stubs.os_environment_pb2 import GrepMatch as GrepMatchProto
64
+ from api_stubs.os_environment_pb2 import (
65
+ GrepOutputMode as GrepOutputModeProto,
66
+ )
67
+ from api_stubs.os_environment_pb2 import TreeEntry as TreeEntryProto
68
+ from api_stubs.os_environment_pb2_grpc import OsEnvironmentServiceServicer
69
+ from codespeak_shared.logging import LoggingUtil
70
+ from codespeak_shared.os_environment.os_environment import (
71
+ ChildProcessFailedToStartException,
72
+ ChildProcessTimedOutException,
73
+ DjangoServerRunMode,
74
+ EntryAttributes,
75
+ ExistsWithLastModifiedTimestamp,
76
+ FileNotFoundException,
77
+ FileState,
78
+ FileStateExpectationNotMet,
79
+ GrepOutputMode,
80
+ MaxFileSizeExceededException,
81
+ Missing,
82
+ OsEnvironment,
83
+ OsEnvironmentException,
84
+ )
85
+ from codespeak_shared.project_path import ProjectPath
86
+ from codespeak_shared.utils.process_util import OutputType
87
+ from rich.console import Console
88
+
89
+
90
+ class OsEnvironmentServicer(OsEnvironmentServiceServicer):
91
+ """
92
+ gRPC server implementation that delegates all operations to OsEnvironment using bidirectional streaming.
93
+ """
94
+
95
+ def __init__(self, os_env: OsEnvironment, console: Console | None = None):
96
+ self._os_env = os_env
97
+ self._console = console
98
+
99
+ @staticmethod
100
+ def create_error_info_from_exception(exception: OsEnvironmentException) -> ErrorInfo:
101
+ message = str(exception)
102
+ if isinstance(exception, FileNotFoundException):
103
+ return ErrorInfo(message=message, type=ErrorType.FILE_NOT_FOUND)
104
+ if isinstance(exception, MaxFileSizeExceededException):
105
+ return ErrorInfo(message=message, type=ErrorType.MAX_FILE_SIZE_EXCEEDED)
106
+ if isinstance(exception, FileStateExpectationNotMet):
107
+ return ErrorInfo(message=message, type=ErrorType.FILE_STATE_EXPECTATION_NOT_MET)
108
+ return ErrorInfo(message=message)
109
+
110
+ @staticmethod
111
+ def _to_proto_attributes(attributes: EntryAttributes) -> EntryAttributesProto:
112
+ return EntryAttributesProto(
113
+ last_modified=attributes.last_modified,
114
+ size=attributes.size,
115
+ mode=attributes.mode,
116
+ nlink=attributes.nlink,
117
+ user_name=attributes.user_name,
118
+ group_name=attributes.group_name,
119
+ )
120
+
121
+ def StreamOperations(
122
+ self, request_iterator: Iterator[OperationRequest], context: Any
123
+ ) -> Iterator[OperationResponse]:
124
+ """Handle bidirectional streaming of operations."""
125
+ for request in request_iterator:
126
+ request_id = request.request_id
127
+
128
+ # Format operation string once for logging
129
+ request_description = OsEnvRequestLogging.format(request)
130
+
131
+ # Determine which operation was requested
132
+ which = request.WhichOneof("operation")
133
+
134
+ if which == "get_attributes":
135
+ yield self._handle_get_attributes(request_id, request.get_attributes, request_description)
136
+ elif which == "read_file":
137
+ yield self._handle_read_file(request_id, request.read_file, request_description)
138
+ elif which == "write_file":
139
+ yield self._handle_write_file(request_id, request.write_file, request_description)
140
+ elif which == "append_to_file":
141
+ yield self._handle_append_to_file(request_id, request.append_to_file, request_description)
142
+ elif which == "mkdirs":
143
+ yield self._handle_mkdirs(request_id, request.mkdirs, request_description)
144
+ elif which == "is_file":
145
+ yield self._handle_is_file(request_id, request.is_file, request_description)
146
+ elif which == "is_directory":
147
+ yield self._handle_is_directory(request_id, request.is_directory, request_description)
148
+ elif which == "exists":
149
+ yield self._handle_exists(request_id, request.exists, request_description)
150
+ elif which == "copy":
151
+ yield self._handle_copy(request_id, request.copy, request_description)
152
+ elif which == "delete_recursively":
153
+ yield self._handle_delete_recursively(request_id, request.delete_recursively, request_description)
154
+ elif which == "delete_file":
155
+ yield self._handle_delete_file(request_id, request.delete_file, request_description)
156
+ elif which == "list_directory":
157
+ yield self._handle_list_directory(request_id, request.list_directory, request_description)
158
+ elif which == "glob":
159
+ yield self._handle_glob(request_id, request.glob, request_description)
160
+ elif which == "resolve_user_name":
161
+ yield self._handle_resolve_user_name(request_id, request.resolve_user_name, request_description)
162
+ elif which == "run_child_process":
163
+ yield self._handle_run_child_process(request_id, request.run_child_process, request_description)
164
+ elif which == "resolve_path":
165
+ yield self._handle_resolve_path(request_id, request.resolve_path, request_description)
166
+ elif which == "traverse":
167
+ yield self._handle_traverse(request_id, request.traverse, request_description)
168
+ elif which == "run_django_dev_server":
169
+ yield self._handle_run_django_dev_server(request_id, request.run_django_dev_server, request_description)
170
+ elif which == "grep":
171
+ yield self._handle_grep(request_id, request.grep, request_description)
172
+ elif which == "list_tree":
173
+ yield self._handle_list_tree(request_id, request.list_tree, request_description)
174
+
175
+ def _handle_get_attributes(
176
+ self, request_id: str, request: GetAttributesRequest, operation_details_to_log: str
177
+ ) -> OperationResponse:
178
+ with LoggingUtil.Span(operation_details_to_log):
179
+ try:
180
+ attributes = self._os_env.get_attributes(ProjectPath.from_string(request.path))
181
+ proto_attributes = self._to_proto_attributes(attributes)
182
+ return OperationResponse(
183
+ request_id=request_id,
184
+ get_attributes=GetAttributesResponse(attributes=proto_attributes),
185
+ )
186
+ except OsEnvironmentException as e:
187
+ return OperationResponse(
188
+ request_id=request_id,
189
+ get_attributes=GetAttributesResponse(error=self.create_error_info_from_exception(e)),
190
+ )
191
+
192
+ def _handle_read_file(
193
+ self, request_id: str, request: ReadFileRequest, operation_details_to_log: str
194
+ ) -> OperationResponse:
195
+ with LoggingUtil.Span(operation_details_to_log):
196
+ try:
197
+ max_allowed = (
198
+ request.max_allowed_file_size_bytes if request.max_allowed_file_size_bytes > 0 else 20 * 1024 * 1024
199
+ )
200
+ offset_line = request.offset_line if request.HasField("offset_line") else None
201
+ limit_lines = request.limit_lines if request.HasField("limit_lines") else None
202
+ content, attributes, truncated = self._os_env.read_file_ex(
203
+ ProjectPath.from_string(request.path),
204
+ max_allowed_file_size_bytes=max_allowed,
205
+ offset_line=offset_line,
206
+ limit_lines=limit_lines,
207
+ )
208
+ proto_attributes = self._to_proto_attributes(attributes)
209
+ return OperationResponse(
210
+ request_id=request_id,
211
+ read_file=ReadFileResponse(content=content, attributes=proto_attributes, truncated=truncated),
212
+ )
213
+ except MaxFileSizeExceededException as e:
214
+ # Include file size in attributes so client can construct proper exception
215
+ attrs_with_size = EntryAttributesProto(
216
+ size=e.file_size,
217
+ last_modified=0,
218
+ mode=0,
219
+ nlink=0,
220
+ user_name="",
221
+ group_name="",
222
+ )
223
+ return OperationResponse(
224
+ request_id=request_id,
225
+ read_file=ReadFileResponse(
226
+ error=self.create_error_info_from_exception(e),
227
+ attributes=attrs_with_size,
228
+ content="",
229
+ ),
230
+ )
231
+ except OsEnvironmentException as e:
232
+ return OperationResponse(
233
+ request_id=request_id,
234
+ read_file=ReadFileResponse(
235
+ error=self.create_error_info_from_exception(e),
236
+ attributes=EntryAttributesProto(),
237
+ content="",
238
+ ),
239
+ )
240
+
241
+ def _handle_write_file(
242
+ self, request_id: str, request: WriteFileRequest, operation_details_to_log: str
243
+ ) -> OperationResponse:
244
+ with LoggingUtil.Span(operation_details_to_log):
245
+ try:
246
+ path = ProjectPath.from_string(request.path)
247
+
248
+ expected_state = (
249
+ self._proto_to_file_state(request.expected_state) if request.HasField("expected_state") else None
250
+ )
251
+
252
+ new_last_modified = self._os_env.write_file(path, request.content, expected_state=expected_state)
253
+
254
+ return OperationResponse(
255
+ request_id=request_id,
256
+ write_file=WriteFileResponse(new_last_modified=new_last_modified),
257
+ )
258
+ except OsEnvironmentException as e:
259
+ return OperationResponse(
260
+ request_id=request_id,
261
+ write_file=WriteFileResponse(error=self.create_error_info_from_exception(e)),
262
+ )
263
+
264
+ def _handle_append_to_file(
265
+ self, request_id: str, request: AppendToFileRequest, operation_details_to_log: str
266
+ ) -> OperationResponse:
267
+ with LoggingUtil.Span(operation_details_to_log):
268
+ try:
269
+ path = ProjectPath.from_string(request.path)
270
+
271
+ expected_state = (
272
+ self._proto_to_file_state(request.expected_state) if request.HasField("expected_state") else None
273
+ )
274
+
275
+ new_last_modified = self._os_env.append_to_file(path, request.content, expected_state=expected_state)
276
+
277
+ return OperationResponse(
278
+ request_id=request_id,
279
+ append_to_file=AppendToFileResponse(new_last_modified=new_last_modified),
280
+ )
281
+ except OsEnvironmentException as e:
282
+ return OperationResponse(
283
+ request_id=request_id,
284
+ append_to_file=AppendToFileResponse(error=self.create_error_info_from_exception(e)),
285
+ )
286
+
287
+ def _handle_mkdirs(
288
+ self, request_id: str, request: MkdirsRequest, operation_details_to_log: str
289
+ ) -> OperationResponse:
290
+ with LoggingUtil.Span(operation_details_to_log):
291
+ try:
292
+ self._os_env.mkdirs(ProjectPath.from_string(request.path))
293
+ return OperationResponse(
294
+ request_id=request_id,
295
+ mkdirs=MkdirsResponse(),
296
+ )
297
+ except OsEnvironmentException as e:
298
+ return OperationResponse(
299
+ request_id=request_id,
300
+ mkdirs=MkdirsResponse(error=self.create_error_info_from_exception(e)),
301
+ )
302
+
303
+ def _handle_is_file(
304
+ self, request_id: str, request: IsFileRequest, operation_details_to_log: str
305
+ ) -> OperationResponse:
306
+ with LoggingUtil.Span(operation_details_to_log):
307
+ try:
308
+ result = self._os_env.is_file(ProjectPath.from_string(request.path))
309
+ return OperationResponse(
310
+ request_id=request_id,
311
+ is_file=IsFileResponse(result=result),
312
+ )
313
+ except OsEnvironmentException as e:
314
+ return OperationResponse(
315
+ request_id=request_id,
316
+ is_file=IsFileResponse(result=False, error=self.create_error_info_from_exception(e)),
317
+ )
318
+
319
+ def _handle_is_directory(
320
+ self, request_id: str, request: IsDirectoryRequest, operation_details_to_log: str
321
+ ) -> OperationResponse:
322
+ with LoggingUtil.Span(operation_details_to_log):
323
+ try:
324
+ result = self._os_env.is_directory(ProjectPath.from_string(request.path))
325
+ return OperationResponse(
326
+ request_id=request_id,
327
+ is_directory=IsDirectoryResponse(result=result),
328
+ )
329
+ except OsEnvironmentException as e:
330
+ return OperationResponse(
331
+ request_id=request_id,
332
+ is_directory=IsDirectoryResponse(result=False, error=self.create_error_info_from_exception(e)),
333
+ )
334
+
335
+ def _handle_exists(
336
+ self, request_id: str, request: ExistsRequest, operation_details_to_log: str
337
+ ) -> OperationResponse:
338
+ with LoggingUtil.Span(operation_details_to_log):
339
+ try:
340
+ result = self._os_env.exists(ProjectPath.from_string(request.path))
341
+ return OperationResponse(
342
+ request_id=request_id,
343
+ exists=ExistsResponse(result=result),
344
+ )
345
+ except OsEnvironmentException as e:
346
+ return OperationResponse(
347
+ request_id=request_id,
348
+ exists=ExistsResponse(result=False, error=self.create_error_info_from_exception(e)),
349
+ )
350
+
351
+ def _handle_copy(self, request_id: str, request: CopyRequest, operation_details_to_log: str) -> OperationResponse:
352
+ with LoggingUtil.Span(operation_details_to_log):
353
+ try:
354
+ self._os_env.copy(
355
+ ProjectPath.from_string(request.source_path), ProjectPath.from_string(request.dest_path)
356
+ )
357
+ return OperationResponse(
358
+ request_id=request_id,
359
+ copy=CopyResponse(),
360
+ )
361
+ except OsEnvironmentException as e:
362
+ return OperationResponse(
363
+ request_id=request_id,
364
+ copy=CopyResponse(error=self.create_error_info_from_exception(e)),
365
+ )
366
+
367
+ def _handle_delete_recursively(
368
+ self, request_id: str, request: DeleteRecursivelyRequest, operation_details_to_log: str
369
+ ) -> OperationResponse:
370
+ with LoggingUtil.Span(operation_details_to_log):
371
+ try:
372
+ self._os_env.delete_recursively(ProjectPath.from_string(request.path))
373
+ return OperationResponse(
374
+ request_id=request_id,
375
+ delete_recursively=DeleteRecursivelyResponse(),
376
+ )
377
+ except OsEnvironmentException as e:
378
+ return OperationResponse(
379
+ request_id=request_id,
380
+ delete_recursively=DeleteRecursivelyResponse(error=self.create_error_info_from_exception(e)),
381
+ )
382
+
383
+ def _handle_delete_file(
384
+ self, request_id: str, request: DeleteFileRequest, operation_details_to_log: str
385
+ ) -> OperationResponse:
386
+ with LoggingUtil.Span(operation_details_to_log):
387
+ try:
388
+ self._os_env.delete_file(ProjectPath.from_string(request.path))
389
+ return OperationResponse(
390
+ request_id=request_id,
391
+ delete_file=DeleteFileResponse(),
392
+ )
393
+ except OsEnvironmentException as e:
394
+ return OperationResponse(
395
+ request_id=request_id,
396
+ delete_file=DeleteFileResponse(error=self.create_error_info_from_exception(e)),
397
+ )
398
+
399
+ def _handle_list_directory(
400
+ self, request_id: str, request: ListDirectoryRequest, operation_details_to_log: str
401
+ ) -> OperationResponse:
402
+ with LoggingUtil.Span(operation_details_to_log):
403
+ try:
404
+ entries = self._os_env.list_directory(ProjectPath.from_string(request.path))
405
+ return OperationResponse(
406
+ request_id=request_id,
407
+ list_directory=ListDirectoryResponse(entries=entries),
408
+ )
409
+ except OsEnvironmentException as e:
410
+ return OperationResponse(
411
+ request_id=request_id,
412
+ list_directory=ListDirectoryResponse(error=self.create_error_info_from_exception(e)),
413
+ )
414
+
415
+ def _handle_glob(self, request_id: str, request: GlobRequest, operation_details_to_log: str) -> OperationResponse:
416
+ with LoggingUtil.Span(operation_details_to_log):
417
+ try:
418
+ path = ProjectPath.from_string(request.path) if request.path else None
419
+ paths = self._os_env.glob(request.pattern, path)
420
+ return OperationResponse(
421
+ request_id=request_id,
422
+ glob=GlobResponse(paths=[str(p) for p in paths]),
423
+ )
424
+ except OsEnvironmentException as e:
425
+ return OperationResponse(
426
+ request_id=request_id,
427
+ glob=GlobResponse(error=self.create_error_info_from_exception(e)),
428
+ )
429
+
430
+ def _handle_grep(self, request_id: str, request: GrepRequest, operation_details_to_log: str) -> OperationResponse:
431
+ with LoggingUtil.Span(operation_details_to_log):
432
+ try:
433
+ # Convert proto output mode to enum
434
+ output_mode_map = {
435
+ GrepOutputModeProto.GREP_OUTPUT_MODE_FILES_WITH_MATCHES: GrepOutputMode.FILES_WITH_MATCHES,
436
+ GrepOutputModeProto.GREP_OUTPUT_MODE_CONTENT: GrepOutputMode.CONTENT,
437
+ GrepOutputModeProto.GREP_OUTPUT_MODE_COUNT: GrepOutputMode.COUNT,
438
+ }
439
+ output_mode = output_mode_map.get(request.output_mode, GrepOutputMode.FILES_WITH_MATCHES)
440
+
441
+ path = ProjectPath.from_string(request.path) if request.path else None
442
+
443
+ result = self._os_env.grep(
444
+ pattern=request.pattern,
445
+ path=path,
446
+ glob_filter=request.glob if request.glob else None,
447
+ output_mode=output_mode,
448
+ context_before=request.context_before if request.HasField("context_before") else None,
449
+ context_after=request.context_after if request.HasField("context_after") else None,
450
+ context_both=request.context_both if request.HasField("context_both") else None,
451
+ show_line_numbers=request.show_line_numbers if request.HasField("show_line_numbers") else True,
452
+ case_insensitive=request.case_insensitive if request.HasField("case_insensitive") else False,
453
+ file_type=request.file_type if request.file_type else None,
454
+ head_limit=request.head_limit if request.HasField("head_limit") else None,
455
+ offset=request.offset if request.HasField("offset") else None,
456
+ multiline=request.multiline if request.HasField("multiline") else False,
457
+ )
458
+
459
+ return OperationResponse(
460
+ request_id=request_id,
461
+ grep=GrepResponse(
462
+ matching_files=result.matching_files,
463
+ matches=[
464
+ GrepMatchProto(
465
+ file_path=m.file_path,
466
+ line_number=m.line_number,
467
+ line_content=m.line_content,
468
+ )
469
+ for m in result.matches
470
+ ],
471
+ file_counts=[
472
+ GrepFileCountProto(
473
+ file_path=fc.file_path,
474
+ match_count=fc.match_count,
475
+ )
476
+ for fc in result.file_counts
477
+ ],
478
+ ),
479
+ )
480
+ except OsEnvironmentException as e:
481
+ return OperationResponse(
482
+ request_id=request_id,
483
+ grep=GrepResponse(error=self.create_error_info_from_exception(e)),
484
+ )
485
+
486
+ def _handle_resolve_user_name(
487
+ self, request_id: str, request: ResolveUserNameRequest, operation_details_to_log: str
488
+ ) -> OperationResponse:
489
+ with LoggingUtil.Span(operation_details_to_log):
490
+ try:
491
+ username = self._os_env.resolve_user_name()
492
+ return OperationResponse(
493
+ request_id=request_id,
494
+ resolve_user_name=ResolveUserNameResponse(username=username),
495
+ )
496
+ except OsEnvironmentException as e:
497
+ return OperationResponse(
498
+ request_id=request_id,
499
+ resolve_user_name=ResolveUserNameResponse(
500
+ username="", error=self.create_error_info_from_exception(e)
501
+ ),
502
+ )
503
+
504
+ def _handle_run_child_process(
505
+ self, request_id: str, request: RunChildProcessRequest, operation_details_to_log: str
506
+ ) -> OperationResponse:
507
+ with LoggingUtil.Span(operation_details_to_log):
508
+ try:
509
+ redirected_output = (
510
+ ProjectPath.from_string(request.redirected_output_path) if request.redirected_output_path else None
511
+ )
512
+ output_limit_chars = request.output_limit_chars if request.HasField("output_limit_chars") else None
513
+ result = self._os_env.run_child_process(
514
+ args=list(request.args),
515
+ cwd=ProjectPath.from_string(request.cwd_relative),
516
+ timeout=request.timeout,
517
+ check=request.check,
518
+ redirected_output_path=redirected_output,
519
+ shell=request.shell,
520
+ output_limit_chars=output_limit_chars,
521
+ interactive_mode=request.interactive_mode,
522
+ )
523
+ proto_result = ChildProcessResultProto(
524
+ exit_code=result.exit_code,
525
+ stdout=result.stdout,
526
+ stderr=result.stderr,
527
+ )
528
+ return OperationResponse(
529
+ request_id=request_id,
530
+ run_child_process=RunChildProcessResponse(
531
+ result=proto_result, outcome=ChildProcessOutcome.PROCESS_FINISHED
532
+ ),
533
+ )
534
+ except ChildProcessFailedToStartException as e:
535
+ return OperationResponse(
536
+ request_id=request_id,
537
+ run_child_process=RunChildProcessResponse(
538
+ error=self.create_error_info_from_exception(e),
539
+ outcome=ChildProcessOutcome.FAILED_TO_START_PROCESS,
540
+ ),
541
+ )
542
+ except ChildProcessTimedOutException as e:
543
+ return OperationResponse(
544
+ request_id=request_id,
545
+ run_child_process=RunChildProcessResponse(
546
+ result=ChildProcessResultProto(exit_code=-1, stdout=e.stdout, stderr=e.stderr),
547
+ error=self.create_error_info_from_exception(e),
548
+ outcome=ChildProcessOutcome.PROCESS_TIMED_OUT,
549
+ ),
550
+ )
551
+ except OsEnvironmentException as e:
552
+ # This catches other generic OsEnvironmentException errors that are not specifically
553
+ # ChildProcessFailedToStartException or ChildProcessTimedOutException.
554
+ # Since we don't know what happened, we use UNKNOWN outcome.
555
+ return OperationResponse(
556
+ request_id=request_id,
557
+ run_child_process=RunChildProcessResponse(
558
+ error=self.create_error_info_from_exception(e),
559
+ outcome=ChildProcessOutcome.UNKNOWN,
560
+ ),
561
+ )
562
+
563
+ def _handle_resolve_path(
564
+ self, request_id: str, request: ResolvePathRequest, operation_details_to_log: str
565
+ ) -> OperationResponse:
566
+ with LoggingUtil.Span(operation_details_to_log):
567
+ try:
568
+ resolved = self._os_env.resolve_path(ProjectPath.from_string(request.path))
569
+ return OperationResponse(
570
+ request_id=request_id,
571
+ resolve_path=ResolvePathResponse(resolved_path=str(resolved)),
572
+ )
573
+ except OsEnvironmentException as e:
574
+ return OperationResponse(
575
+ request_id=request_id,
576
+ resolve_path=ResolvePathResponse(resolved_path="", error=self.create_error_info_from_exception(e)),
577
+ )
578
+
579
+ def _handle_traverse(
580
+ self, request_id: str, request: TraverseRequest, operation_details_to_log: str
581
+ ) -> OperationResponse:
582
+ with LoggingUtil.Span(operation_details_to_log):
583
+ try:
584
+ paths = self._os_env.traverse(
585
+ prune_dirnames=set(request.prune_dirnames),
586
+ skip_file_basenames=set(request.skip_file_basenames),
587
+ required_extension=request.required_extension,
588
+ )
589
+ return OperationResponse(
590
+ request_id=request_id,
591
+ traverse=TraverseResponse(paths=[str(p) for p in paths]),
592
+ )
593
+ except OsEnvironmentException as e:
594
+ return OperationResponse(
595
+ request_id=request_id,
596
+ traverse=TraverseResponse(error=self.create_error_info_from_exception(e)),
597
+ )
598
+
599
+ def _handle_list_tree(
600
+ self, request_id: str, request: ListTreeRequest, operation_details_to_log: str
601
+ ) -> OperationResponse:
602
+ with LoggingUtil.Span(operation_details_to_log):
603
+ try:
604
+ path = ProjectPath.from_string(request.path) if request.path else None
605
+ max_depth = request.max_depth if request.HasField("max_depth") else None
606
+ max_entries = request.max_entries if request.HasField("max_entries") else None
607
+ entries, truncated = self._os_env.list_tree(path=path, max_depth=max_depth, max_entries=max_entries)
608
+ return OperationResponse(
609
+ request_id=request_id,
610
+ list_tree=ListTreeResponse(
611
+ entries=[TreeEntryProto(path=e.path, is_directory=e.is_directory) for e in entries],
612
+ truncated=truncated,
613
+ ),
614
+ )
615
+ except OsEnvironmentException as e:
616
+ return OperationResponse(
617
+ request_id=request_id,
618
+ list_tree=ListTreeResponse(error=self.create_error_info_from_exception(e)),
619
+ )
620
+
621
+ def _handle_run_django_dev_server(
622
+ self, request_id: str, request: RunDjangoDevServerRequest, operation_details_to_log: str
623
+ ) -> OperationResponse:
624
+ with LoggingUtil.Span(operation_details_to_log):
625
+ try:
626
+ # Convert proto enum to Python enum
627
+ run_mode = (
628
+ DjangoServerRunMode.SHUTDOWN_WHEN_READY
629
+ if request.run_mode == DjangoServerRunModeProto.DJANGO_SERVER_RUN_MODE_SHUTDOWN_WHEN_READY
630
+ else DjangoServerRunMode.INTERACTIVE
631
+ )
632
+
633
+ local_console_printer = None
634
+ if self._console and run_mode == DjangoServerRunMode.INTERACTIVE:
635
+ console = self._console
636
+
637
+ def _print(line: str, _output_type: OutputType) -> None:
638
+ console.print(line, markup=False, highlight=False)
639
+
640
+ local_console_printer = _print
641
+
642
+ success, error_output = self._os_env.run_django_dev_server(
643
+ server_readiness_timeout=request.server_readiness_timeout_sec,
644
+ run_mode=run_mode,
645
+ output_printer=local_console_printer,
646
+ )
647
+ return OperationResponse(
648
+ request_id=request_id,
649
+ run_django_dev_server=RunDjangoDevServerResponse(
650
+ success=success,
651
+ error_output=error_output if error_output else None,
652
+ ),
653
+ )
654
+ except OsEnvironmentException as e:
655
+ return OperationResponse(
656
+ request_id=request_id,
657
+ run_django_dev_server=RunDjangoDevServerResponse(
658
+ success=False, error=self.create_error_info_from_exception(e)
659
+ ),
660
+ )
661
+
662
+ @staticmethod
663
+ def _proto_to_file_state(proto_state: "os_env_pb2.FileState | None") -> FileState | None:
664
+ if proto_state is None:
665
+ return None
666
+
667
+ state_type = proto_state.WhichOneof("state")
668
+ match state_type:
669
+ case "exists_with_last_modified_timestamp":
670
+ return ExistsWithLastModifiedTimestamp(
671
+ timestamp=proto_state.exists_with_last_modified_timestamp.timestamp
672
+ )
673
+ case "missing":
674
+ return Missing()
675
+ case _:
676
+ return None