gpustack-runtime 0.1.40.post1__py3-none-any.whl → 0.1.41__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.
Files changed (66) hide show
  1. gpustack_runtime/__init__.py +1 -1
  2. gpustack_runtime/__main__.py +5 -3
  3. gpustack_runtime/_version.py +2 -2
  4. gpustack_runtime/_version_appendix.py +1 -1
  5. gpustack_runtime/cmds/__init__.py +5 -3
  6. gpustack_runtime/cmds/__types__.py +1 -1
  7. gpustack_runtime/cmds/deployer.py +140 -18
  8. gpustack_runtime/cmds/detector.py +1 -1
  9. gpustack_runtime/cmds/images.py +1 -1
  10. gpustack_runtime/deployer/__init__.py +28 -2
  11. gpustack_runtime/deployer/__patches__.py +1 -1
  12. gpustack_runtime/deployer/__types__.py +2 -1
  13. gpustack_runtime/deployer/__utils__.py +2 -2
  14. gpustack_runtime/deployer/cdi/__init__.py +85 -5
  15. gpustack_runtime/deployer/cdi/__types__.py +92 -29
  16. gpustack_runtime/deployer/cdi/__utils__.py +178 -0
  17. gpustack_runtime/deployer/cdi/amd.py +146 -0
  18. gpustack_runtime/deployer/cdi/ascend.py +164 -0
  19. gpustack_runtime/deployer/cdi/hygon.py +147 -0
  20. gpustack_runtime/deployer/cdi/iluvatar.py +136 -0
  21. gpustack_runtime/deployer/cdi/metax.py +148 -0
  22. gpustack_runtime/deployer/cdi/thead.py +57 -23
  23. gpustack_runtime/deployer/docker.py +9 -8
  24. gpustack_runtime/deployer/k8s/deviceplugin/__init__.py +240 -0
  25. gpustack_runtime/deployer/k8s/deviceplugin/__types__.py +131 -0
  26. gpustack_runtime/deployer/k8s/deviceplugin/plugin.py +586 -0
  27. gpustack_runtime/deployer/k8s/types/kubelet/deviceplugin/v1beta1/__init__.py +3 -0
  28. gpustack_runtime/deployer/k8s/types/kubelet/deviceplugin/v1beta1/api.proto +212 -0
  29. gpustack_runtime/deployer/k8s/types/kubelet/deviceplugin/v1beta1/api_pb2.py +86 -0
  30. gpustack_runtime/deployer/k8s/types/kubelet/deviceplugin/v1beta1/api_pb2.pyi +168 -0
  31. gpustack_runtime/deployer/k8s/types/kubelet/deviceplugin/v1beta1/api_pb2_grpc.py +358 -0
  32. gpustack_runtime/deployer/k8s/types/kubelet/deviceplugin/v1beta1/constants.py +34 -0
  33. gpustack_runtime/deployer/kuberentes.py +37 -4
  34. gpustack_runtime/deployer/podman.py +9 -8
  35. gpustack_runtime/detector/__init__.py +42 -5
  36. gpustack_runtime/detector/__types__.py +8 -24
  37. gpustack_runtime/detector/__utils__.py +46 -39
  38. gpustack_runtime/detector/amd.py +55 -66
  39. gpustack_runtime/detector/ascend.py +29 -41
  40. gpustack_runtime/detector/cambricon.py +3 -3
  41. gpustack_runtime/detector/hygon.py +21 -49
  42. gpustack_runtime/detector/iluvatar.py +44 -60
  43. gpustack_runtime/detector/metax.py +54 -37
  44. gpustack_runtime/detector/mthreads.py +74 -36
  45. gpustack_runtime/detector/nvidia.py +130 -93
  46. gpustack_runtime/detector/pyacl/__init__.py +1 -1
  47. gpustack_runtime/detector/pyamdgpu/__init__.py +1 -1
  48. gpustack_runtime/detector/pyamdsmi/__init__.py +1 -1
  49. gpustack_runtime/detector/pycuda/__init__.py +1 -1
  50. gpustack_runtime/detector/pydcmi/__init__.py +1 -1
  51. gpustack_runtime/detector/pyhsa/__init__.py +1 -1
  52. gpustack_runtime/detector/pymxsml/__init__.py +1553 -1
  53. gpustack_runtime/detector/pyrocmcore/__init__.py +1 -1
  54. gpustack_runtime/detector/pyrocmsmi/__init__.py +1 -1
  55. gpustack_runtime/detector/thead.py +41 -60
  56. gpustack_runtime/envs.py +104 -12
  57. gpustack_runtime/logging.py +6 -2
  58. {gpustack_runtime-0.1.40.post1.dist-info → gpustack_runtime-0.1.41.dist-info}/METADATA +6 -1
  59. gpustack_runtime-0.1.41.dist-info/RECORD +67 -0
  60. gpustack_runtime/detector/pymxsml/mxsml.py +0 -1580
  61. gpustack_runtime/detector/pymxsml/mxsml_extension.py +0 -816
  62. gpustack_runtime/detector/pymxsml/mxsml_mcm.py +0 -476
  63. gpustack_runtime-0.1.40.post1.dist-info/RECORD +0 -55
  64. {gpustack_runtime-0.1.40.post1.dist-info → gpustack_runtime-0.1.41.dist-info}/WHEEL +0 -0
  65. {gpustack_runtime-0.1.40.post1.dist-info → gpustack_runtime-0.1.41.dist-info}/entry_points.txt +0 -0
  66. {gpustack_runtime-0.1.40.post1.dist-info → gpustack_runtime-0.1.41.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,586 @@
1
+ from __future__ import annotations as __future_annotations__
2
+
3
+ import asyncio
4
+ import contextlib
5
+ import logging
6
+ from functools import lru_cache
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Any, Literal
9
+
10
+ import grpc
11
+ from grpc_interceptor import AsyncServerInterceptor
12
+ from grpc_interceptor.exceptions import GrpcException
13
+
14
+ from .... import envs
15
+ from ....detector import Device, str_range_to_list
16
+ from ...cdi import (
17
+ generate_config,
18
+ manufacturer_to_cdi_kind,
19
+ manufacturer_to_runtime_env,
20
+ )
21
+ from ..types.kubelet.deviceplugin.v1beta1 import (
22
+ AllocateRequest,
23
+ AllocateResponse,
24
+ CDIDevice,
25
+ ContainerAllocateRequest,
26
+ ContainerAllocateResponse,
27
+ ContainerPreferredAllocationRequest,
28
+ ContainerPreferredAllocationResponse,
29
+ DevicePluginOptions,
30
+ DevicePluginServicer,
31
+ DeviceSpec,
32
+ Empty,
33
+ Healthy,
34
+ KubeletSocket,
35
+ ListAndWatchResponse,
36
+ Mount,
37
+ NUMANode,
38
+ PreferredAllocationRequest,
39
+ PreferredAllocationResponse,
40
+ PreStartContainerRequest,
41
+ PreStartContainerResponse,
42
+ RegisterRequest,
43
+ RegistrationStub,
44
+ TopologyInfo,
45
+ Version,
46
+ add_DevicePluginServicer_to_server,
47
+ )
48
+ from ..types.kubelet.deviceplugin.v1beta1 import (
49
+ Device as DevicePluginDevice,
50
+ )
51
+ from .__types__ import PluginServer
52
+
53
+ if TYPE_CHECKING:
54
+ from collections.abc import AsyncIterator, Callable
55
+
56
+ logger = logging.getLogger(__name__)
57
+
58
+ _ID_SPLIT = "::"
59
+
60
+
61
+ def _to_device_plugin_device_id(device_id: str, shard: int) -> str:
62
+ """
63
+ Converts a device ID and shard number to a Kubernetes Device Plugin device ID.
64
+
65
+ Args:
66
+ device_id:
67
+ The base device ID.
68
+ shard:
69
+ The shard number.
70
+
71
+ Returns:
72
+ The combined device plugin device ID.
73
+
74
+ """
75
+ return f"{device_id}{_ID_SPLIT}{shard}"
76
+
77
+
78
+ def _from_device_plugin_device_id(device_plugin_device_id: str) -> tuple[str, int]:
79
+ """
80
+ Converts a Kubernetes Device Plugin device ID to its base device ID and shard number.
81
+
82
+ Args:
83
+ device_plugin_device_id:
84
+ The combined device plugin device ID.
85
+
86
+ Returns:
87
+ A tuple containing the base device ID and shard number.
88
+
89
+ """
90
+ parts = device_plugin_device_id.split(_ID_SPLIT)
91
+ device_id = parts[0]
92
+ shard = int(parts[1])
93
+ return device_id, shard
94
+
95
+
96
+ class SharableDevicePlugin(PluginServer, DevicePluginServicer):
97
+ """
98
+ SharableDevicePlugin is a Kubernetes Device Plugin that supports device sharing.
99
+
100
+ It allows multiple containers to share the same underlying device by
101
+ creating multiple device IDs (shards) for each physical device.
102
+
103
+ """
104
+
105
+ _device: Device
106
+ """
107
+ The underlying device to be managed by this device plugin.
108
+ """
109
+ _id_by: Literal["uuid", "index"]
110
+ """
111
+ Controls how the device IDs of the Kubernetes Device Plugin are generated.
112
+ """
113
+ _max_allocations: int
114
+ """
115
+ Controls the maximum shards per underlying device.
116
+ """
117
+ _cdi_kind: str
118
+ """
119
+ The CDI kind associated with the `_device`.
120
+ """
121
+ _runtime_env: str
122
+ """
123
+ The runtime environment associated with the `_device`.
124
+ """
125
+ _kdp_resource: str
126
+ """
127
+ The device plugin resource name associated with the `_device`.
128
+ """
129
+
130
+ def __init__(
131
+ self,
132
+ device: Device,
133
+ id_by: Literal["uuid", "index"] = "uuid",
134
+ max_allocations: int | None = None,
135
+ ):
136
+ """
137
+ Initializes the SharableDevicePlugin.
138
+
139
+ Args:
140
+ device:
141
+ The underlying device to be managed by this device plugin.
142
+ id_by:
143
+ Controls how the device IDs of the Kubernetes Device Plugin are generated.
144
+ Either "uuid" or "index". Default is "uuid".
145
+ max_allocations:
146
+ Controls the maximum allocations per underlying device.
147
+ If None, uses the environment variable `GPUSTACK_RUNTIME_KUBERNETES_KDP_PER_DEVICE_MAX_ALLOCATIONS`.
148
+
149
+ """
150
+ self._device = device
151
+ self._id_by = id_by
152
+ self._max_allocations = max_allocations
153
+ if not self._max_allocations:
154
+ self._max_allocations = (
155
+ envs.GPUSTACK_RUNTIME_KUBERNETES_KDP_PER_DEVICE_MAX_ALLOCATIONS
156
+ )
157
+ self._max_allocations = max(self._max_allocations, 1)
158
+ self._cdi_kind = manufacturer_to_cdi_kind(device.manufacturer)
159
+ self._runtime_env = manufacturer_to_runtime_env(device.manufacturer)
160
+ self._kdp_resource = cdi_kind_to_kdp_resource(
161
+ cdi_kind=self._cdi_kind,
162
+ device_index=device.index,
163
+ )
164
+
165
+ super().__init__(self._kdp_resource)
166
+
167
+ @contextlib.asynccontextmanager
168
+ async def _serve_device_plugin(
169
+ self,
170
+ endpoint: Path,
171
+ timeout: int = 5,
172
+ ) -> AsyncIterator[None]:
173
+ """
174
+ Serve the device plugin asynchronously.
175
+
176
+ Args:
177
+ endpoint:
178
+ The path to the device plugin server endpoint.
179
+ timeout:
180
+ The timeout in seconds for starting the device plugin.
181
+
182
+ """
183
+ endpoint.unlink(missing_ok=True)
184
+
185
+ # Create the device plugin server.
186
+ server = grpc.aio.server(
187
+ interceptors=[
188
+ _LoggingInterceptor(
189
+ name=self._kdp_resource,
190
+ ),
191
+ ],
192
+ )
193
+ server.add_insecure_port(
194
+ address=f"unix://{endpoint}",
195
+ )
196
+
197
+ # Start the device plugin server.
198
+ add_DevicePluginServicer_to_server(self, server)
199
+ server_task = asyncio.create_task(server.start())
200
+
201
+ # Wait for the server to be ready,
202
+ # then yield for use within the context,
203
+ # and ensure proper shutdown afterward.
204
+ try:
205
+ loop = asyncio.get_event_loop()
206
+ start_time = loop.time()
207
+ while (loop.time() - start_time) < timeout:
208
+ if endpoint.exists():
209
+ with contextlib.suppress(ConnectionError, asyncio.TimeoutError):
210
+ _, writer = await asyncio.wait_for(
211
+ asyncio.open_unix_connection(endpoint),
212
+ timeout=0.1,
213
+ )
214
+ writer.close()
215
+ await writer.wait_closed()
216
+ break
217
+
218
+ await asyncio.sleep(0.05)
219
+ else:
220
+ msg = f"Failed to start device plugin server within {timeout} seconds"
221
+ raise TimeoutError(msg)
222
+
223
+ yield
224
+ finally:
225
+ await server.stop(grace=3.0)
226
+ await server_task
227
+ endpoint.unlink(missing_ok=True)
228
+
229
+ async def serve(
230
+ self,
231
+ stop_event: asyncio.Event,
232
+ kubelet_endpoint: Path | None = None,
233
+ start_timeout: int = 5,
234
+ register_timeout: int = 5,
235
+ ):
236
+ """
237
+ Serve the device plugin asynchronously.
238
+
239
+ Args:
240
+ stop_event:
241
+ An asyncio event to signal stopping the server.
242
+ kubelet_endpoint:
243
+ The path to the kubelet endpoint.
244
+ start_timeout:
245
+ The timeout in seconds for starting the device plugin.
246
+ register_timeout:
247
+ The timeout in seconds for registering the device plugin.
248
+
249
+ """
250
+ if not kubelet_endpoint:
251
+ kubelet_endpoint = KubeletSocket
252
+
253
+ resource_name = self._kdp_resource
254
+ endpoint = kubelet_endpoint.parent / f"{resource_name.replace('/', '.')}.sock"
255
+
256
+ async with self._serve_device_plugin(
257
+ endpoint=endpoint,
258
+ timeout=start_timeout,
259
+ ):
260
+ request = RegisterRequest(
261
+ version=Version,
262
+ endpoint=str(endpoint.name),
263
+ resource_name=resource_name,
264
+ options=self._get_device_plugin_options(),
265
+ )
266
+ async with grpc.aio.insecure_channel(
267
+ target=f"unix://{kubelet_endpoint}",
268
+ ) as channel:
269
+ stub = RegistrationStub(channel)
270
+ try:
271
+ await stub.Register(
272
+ request=request,
273
+ timeout=register_timeout,
274
+ )
275
+ except Exception:
276
+ logger.exception(
277
+ f"Failed to register device plugin for resource '{resource_name}' "
278
+ f"at endpoint '{endpoint}'",
279
+ )
280
+ raise
281
+
282
+ logger.info(
283
+ f"Serving device plugin for resource '{resource_name}' "
284
+ f"at endpoint '{endpoint}'",
285
+ )
286
+ await stop_event.wait()
287
+
288
+ @staticmethod
289
+ def _get_device_plugin_options() -> DevicePluginOptions:
290
+ """
291
+ Returns the device plugin options.
292
+
293
+ Returns:
294
+ The device plugin options.
295
+
296
+ """
297
+ return DevicePluginOptions(
298
+ pre_start_required=False,
299
+ get_preferred_allocation_available=True,
300
+ )
301
+
302
+ async def GetDevicePluginOptions( # noqa: N802
303
+ self,
304
+ req: Empty,
305
+ ctx: grpc.aio.ServicerContext,
306
+ ) -> DevicePluginOptions:
307
+ """
308
+ Returns the device plugin options.
309
+
310
+ Args:
311
+ req:
312
+ An empty request message.
313
+ ctx:
314
+ The request context.
315
+
316
+ Returns:
317
+ The device plugin options.
318
+
319
+ """
320
+ return self._get_device_plugin_options()
321
+
322
+ async def ListAndWatch( # noqa: N802
323
+ self,
324
+ req: Empty,
325
+ ctx: grpc.aio.ServicerContext,
326
+ ) -> AsyncIterator[ListAndWatchResponse]:
327
+ """
328
+ List and watch for device changes.
329
+
330
+ Args:
331
+ req:
332
+ An empty request message.
333
+ ctx:
334
+ The request context.
335
+
336
+ Yields:
337
+ The response containing the list of devices.
338
+
339
+ """
340
+ device_id = (
341
+ self._device.uuid if self._id_by == "uuid" else str(self._device.index)
342
+ )
343
+
344
+ dp_devices: list[DevicePluginDevice] = []
345
+ dp_device_health = Healthy
346
+ dp_device_topo = TopologyInfo(
347
+ nodes=[
348
+ NUMANode(
349
+ ID=node_id,
350
+ )
351
+ for node_id in str_range_to_list(
352
+ self._device.appendix.get("numa", "0"),
353
+ )
354
+ ],
355
+ )
356
+
357
+ for device_replica in range(1, self._max_allocations + 1):
358
+ dp_device_id = _to_device_plugin_device_id(device_id, device_replica)
359
+ dp_devices.append(
360
+ DevicePluginDevice(
361
+ ID=dp_device_id,
362
+ health=dp_device_health,
363
+ topology=dp_device_topo,
364
+ ),
365
+ )
366
+
367
+ yield ListAndWatchResponse(
368
+ devices=dp_devices,
369
+ )
370
+
371
+ # TODO(thxCode): implement health check
372
+
373
+ while not ctx.done(): # noqa: ASYNC110
374
+ await asyncio.sleep(15)
375
+
376
+ @staticmethod
377
+ def _get_preferred_allocation(
378
+ req: ContainerPreferredAllocationRequest,
379
+ ) -> ContainerPreferredAllocationResponse:
380
+ available_dp_device_ids = req.available_deviceIDs
381
+ required_dp_device_ids = req.must_include_deviceIDs
382
+ allocation_size = req.allocation_size
383
+
384
+ if len(available_dp_device_ids) == allocation_size:
385
+ return ContainerPreferredAllocationResponse(
386
+ deviceIDs=available_dp_device_ids,
387
+ )
388
+
389
+ if len(required_dp_device_ids) == allocation_size:
390
+ return ContainerPreferredAllocationResponse(
391
+ deviceIDs=required_dp_device_ids,
392
+ )
393
+
394
+ selected_dp_device_ids = list(required_dp_device_ids)
395
+ for dp_device_id in available_dp_device_ids:
396
+ if dp_device_id not in selected_dp_device_ids:
397
+ selected_dp_device_ids.append(dp_device_id)
398
+ if len(selected_dp_device_ids) == allocation_size:
399
+ break
400
+
401
+ return ContainerPreferredAllocationResponse(
402
+ deviceIDs=selected_dp_device_ids,
403
+ )
404
+
405
+ async def GetPreferredAllocation( # noqa: N802
406
+ self,
407
+ req: PreferredAllocationRequest,
408
+ ctx: grpc.aio.ServicerContext,
409
+ ) -> PreferredAllocationResponse:
410
+ allocation_responses = []
411
+ for request in req.container_requests:
412
+ allocation_responses.append(
413
+ self._get_preferred_allocation(request),
414
+ )
415
+
416
+ return PreferredAllocationResponse(
417
+ container_responses=allocation_responses,
418
+ )
419
+
420
+ def _allocate(
421
+ self,
422
+ req: ContainerAllocateRequest,
423
+ ) -> ContainerAllocateResponse:
424
+ policy = envs.GPUSTACK_RUNTIME_KUBERNETES_KDP_DEVICE_ALLOCATION_POLICY.lower()
425
+
426
+ request_dp_device_ids = req.devices_ids
427
+
428
+ # CDI device allocation.
429
+ if policy == "cdi":
430
+ cdi_devices: list[CDIDevice] = []
431
+ for dp_device_id in request_dp_device_ids:
432
+ device_id, _ = _from_device_plugin_device_id(dp_device_id)
433
+ cdi_devices.append(
434
+ CDIDevice(
435
+ name=f"{self._cdi_kind}={device_id}",
436
+ ),
437
+ )
438
+
439
+ return ContainerAllocateResponse(
440
+ cdi_devices=cdi_devices,
441
+ )
442
+
443
+ # Environment variable device allocation.
444
+ if policy == "env":
445
+ return ContainerAllocateResponse(
446
+ envs={
447
+ self._runtime_env: ",".join(request_dp_device_ids),
448
+ },
449
+ )
450
+
451
+ # Opaque device allocation.
452
+ if cdi_cfg := generate_config(self._device):
453
+ dev_envs: dict[str, str] = {}
454
+ dev_mounts: list[Mount] = []
455
+ dev_devices: list[DeviceSpec] = []
456
+
457
+ container_edits = cdi_cfg.container_edits or []
458
+ for cdi_dev in cdi_cfg.devices:
459
+ container_edits.append(cdi_dev.container_edits)
460
+
461
+ for edit in container_edits:
462
+ for e in edit.env or []:
463
+ k, v = e.split("=", 1)
464
+ dev_envs[k] = v
465
+ for m in edit.mounts or []:
466
+ dev_mounts.append(
467
+ Mount(
468
+ container_path=m.container_path,
469
+ host_path=m.host_path,
470
+ read_only="ro" in (m.options or []),
471
+ ),
472
+ )
473
+ for d in edit.device_nodes or []:
474
+ dev_devices.append(
475
+ DeviceSpec(
476
+ container_path=d.path,
477
+ host_path=d.host_path or d.path,
478
+ permissions=d.permissions,
479
+ ),
480
+ )
481
+
482
+ return ContainerAllocateResponse(
483
+ envs=dev_envs,
484
+ mounts=dev_mounts,
485
+ devices=dev_devices,
486
+ )
487
+
488
+ return ContainerAllocateResponse()
489
+
490
+ async def Allocate( # noqa: N802
491
+ self,
492
+ req: AllocateRequest,
493
+ ctx: grpc.aio.ServicerContext,
494
+ ) -> AllocateResponse:
495
+ allocation_response = []
496
+ for request in req.container_requests:
497
+ allocation_response.append(
498
+ self._allocate(request),
499
+ )
500
+
501
+ return AllocateResponse(
502
+ container_responses=allocation_response,
503
+ )
504
+
505
+ async def PreStartContainer( # noqa: N802
506
+ self,
507
+ req: PreStartContainerRequest,
508
+ ctx: grpc.aio.ServicerContext,
509
+ ) -> PreStartContainerResponse:
510
+ return PreStartContainerResponse()
511
+
512
+
513
+ @lru_cache
514
+ def cdi_kind_to_kdp_resource(
515
+ cdi_kind: str,
516
+ device_index: int,
517
+ ) -> str:
518
+ """
519
+ Map CDI kind and device index to a Kubernetes Device Plugin resource name.
520
+
521
+ Args:
522
+ cdi_kind:
523
+ The CDI kind.
524
+ device_index:
525
+ The index of the device.
526
+
527
+ Returns:
528
+ The resource name for the device plugin.
529
+
530
+ """
531
+ return f"{cdi_kind}{device_index}"
532
+
533
+
534
+ class _LoggingInterceptor(AsyncServerInterceptor):
535
+ _name: str
536
+ """
537
+ Name of the server.
538
+ """
539
+
540
+ def __init__(self, name: str):
541
+ self._name = name
542
+
543
+ def log_debug(self, msg, *args, **kwargs):
544
+ logger.debug(f"[{self._name}] {msg}", *args, **kwargs)
545
+
546
+ def log_exception(self, msg, *args, **kwargs):
547
+ logger.exception(f"[{self._name}] {msg}", *args, **kwargs)
548
+
549
+ @staticmethod
550
+ @lru_cache
551
+ def simplify_method_name(method_name: str) -> str:
552
+ return Path(method_name).name
553
+
554
+ async def intercept(
555
+ self,
556
+ method: Callable,
557
+ request: Any,
558
+ context: grpc.aio.ServicerContext,
559
+ method_name: str,
560
+ ) -> object:
561
+ method_name = self.simplify_method_name(method_name)
562
+
563
+ if hasattr(request, "__aiter__"):
564
+ self.log_debug(f"{method_name} received streaming request")
565
+ elif isinstance(request, Empty):
566
+ self.log_debug(f"{method_name} received empty request")
567
+ else:
568
+ self.log_debug(f"{method_name} received request:\n{request}")
569
+ try:
570
+ response = method(request, context)
571
+ if hasattr(response, "__aiter__"):
572
+ self.log_debug(f"{method_name} returning streaming response")
573
+ else:
574
+ response = await response
575
+ if isinstance(response, Empty):
576
+ self.log_debug(f"{method_name} returned empty response")
577
+ else:
578
+ self.log_debug(f"{method_name} returned response:\n{response}")
579
+ except GrpcException:
580
+ self.log_exception(f"{method_name} raised grpc exception")
581
+ raise
582
+ except Exception:
583
+ self.log_exception(f"{method_name} raised unexpected exception")
584
+ raise
585
+ else:
586
+ return response
@@ -0,0 +1,3 @@
1
+ from .api_pb2 import * # noqa: F403
2
+ from .api_pb2_grpc import * # noqa: F403
3
+ from .constants import * # noqa: F403