splight-lib 3.3.7__tar.gz → 4.0.0.dev0__tar.gz

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 (90) hide show
  1. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/PKG-INFO +1 -1
  2. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/setup.py +1 -1
  3. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/component/abstract.py +8 -0
  4. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/component/exceptions.py +4 -5
  5. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/execution.py +25 -2
  6. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib.egg-info/PKG-INFO +1 -1
  7. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib.egg-info/SOURCES.txt +0 -5
  8. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib.egg-info/requires.txt +0 -4
  9. splight-lib-3.3.7/splight_lib/client/grpc/client.py +0 -71
  10. splight-lib-3.3.7/splight_lib/client/grpc/decorators.py +0 -34
  11. splight-lib-3.3.7/splight_lib/client/grpc/reflector.py +0 -320
  12. splight-lib-3.3.7/splight_lib/component/log_streamer.py +0 -107
  13. splight-lib-3.3.7/splight_lib/utils/__init__.py +0 -0
  14. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/LICENSE.txt +0 -0
  15. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/README.md +0 -0
  16. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/pyproject.toml +0 -0
  17. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/setup.cfg +0 -0
  18. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/__init__.py +0 -0
  19. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/abstract/__init__.py +0 -0
  20. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/abstract/client.py +0 -0
  21. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/auth/__init__.py +0 -0
  22. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/auth/exceptions.py +0 -0
  23. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/auth/mac_auth.py +0 -0
  24. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/auth/token.py +0 -0
  25. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/__init__.py +0 -0
  26. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/__init__.py +0 -0
  27. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/abstract.py +0 -0
  28. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/classmap.py +0 -0
  29. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/exceptions.py +0 -0
  30. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/local_client.py +0 -0
  31. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/communication/remote_client.py +0 -0
  32. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/database/__init__.py +0 -0
  33. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/database/abstract.py +0 -0
  34. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/database/builder.py +0 -0
  35. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/database/classmap.py +0 -0
  36. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/database/local_client.py +0 -0
  37. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/database/remote_client.py +0 -0
  38. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/datalake/__init__.py +0 -0
  39. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/datalake/abstract.py +0 -0
  40. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/datalake/builder.py +0 -0
  41. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/datalake/local_client.py +0 -0
  42. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/datalake/remote_client.py +0 -0
  43. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/exceptions.py +0 -0
  44. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/file_handler.py +0 -0
  45. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/filter.py +0 -0
  46. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/hub/__init__.py +0 -0
  47. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/hub/abstract.py +0 -0
  48. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/client/hub/client.py +0 -0
  49. {splight-lib-3.3.7/splight_lib/client/grpc → splight-lib-4.0.0.dev0/splight_lib/communication}/__init__.py +0 -0
  50. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/communication/event_handler.py +0 -0
  51. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/component/__init__.py +0 -0
  52. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/component/spec.py +0 -0
  53. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/constants.py +0 -0
  54. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/encryption.py +0 -0
  55. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/logging/__init__.py +0 -0
  56. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/logging/_internal.py +0 -0
  57. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/logging/component.py +0 -0
  58. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/logging/constants.py +0 -0
  59. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/logging/logging.py +0 -0
  60. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/__init__.py +0 -0
  61. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/alert.py +0 -0
  62. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/asset.py +0 -0
  63. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/attribute.py +0 -0
  64. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/base.py +0 -0
  65. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/communication.py +0 -0
  66. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/component.py +0 -0
  67. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/dashboard.py +0 -0
  68. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/data_address.py +0 -0
  69. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/event.py +0 -0
  70. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/exceptions.py +0 -0
  71. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/file.py +0 -0
  72. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/hub.py +0 -0
  73. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/native.py +0 -0
  74. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/pipeline.py +0 -0
  75. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/query.py +0 -0
  76. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/secret.py +0 -0
  77. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/models/setpoint.py +0 -0
  78. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/restclient/__init__.py +0 -0
  79. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/restclient/client.py +0 -0
  80. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/restclient/exceptions.py +0 -0
  81. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/restclient/types.py +0 -0
  82. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/settings.py +0 -0
  83. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/testing/__init__.py +0 -0
  84. {splight-lib-3.3.7/splight_lib/communication → splight-lib-4.0.0.dev0/splight_lib/utils}/__init__.py +0 -0
  85. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/utils/custom_model.py +0 -0
  86. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/utils/hub.py +0 -0
  87. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib/webhook.py +0 -0
  88. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib.egg-info/dependency_links.txt +0 -0
  89. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib.egg-info/not-zip-safe +0 -0
  90. {splight-lib-3.3.7 → splight-lib-4.0.0.dev0}/splight_lib.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: splight-lib
3
- Version: 3.3.7
3
+ Version: 4.0.0.dev0
4
4
  Summary: Library for public use. Splight
5
5
  Home-page: UNKNOWN
6
6
  Author: Splight
@@ -14,7 +14,7 @@ test_requires = [
14
14
 
15
15
  setup(
16
16
  name="splight-lib",
17
- version="3.3.7",
17
+ version="4.0.0.dev0",
18
18
  author="Splight",
19
19
  author_email="factory@splight-ae.com",
20
20
  packages=find_packages(),
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import sys
3
+ import traceback
3
4
  from functools import partial
4
5
  from tempfile import NamedTemporaryFile
5
6
  from time import sleep
@@ -79,6 +80,8 @@ class HealthCheckProcessor:
79
80
  self._running = True
80
81
  while self._running:
81
82
  if not self._engine.healthcheck():
83
+ exc = self._engine.get_last_exception()
84
+ self._log_exception(exc)
82
85
  self._logger.error(
83
86
  "Healthcheck task failed.", tags=LogTags.RUNTIME
84
87
  )
@@ -94,6 +97,11 @@ class HealthCheckProcessor:
94
97
  def stop(self):
95
98
  self._running = False
96
99
 
100
+ def _log_exception(self, exc: Optional[Exception]):
101
+ if exc:
102
+ message = "".join(traceback.format_tb(exc.__traceback__))
103
+ self._logger.exception(message)
104
+
97
105
 
98
106
  class SplightBaseComponent:
99
107
  def __init__(
@@ -20,7 +20,10 @@ class MissingBindingCallback(Exception):
20
20
 
21
21
  class MissingRoutineCallback(Exception):
22
22
  def __init__(self, routine_name: str, action: str):
23
- msg = f"Missing method associated with a Routine: {routine_name} {action}_handler event"
23
+ msg = (
24
+ f"Missing method associated with a Routine: {routine_name} "
25
+ f"{action}_handler event"
26
+ )
24
27
  super().__init__(msg)
25
28
 
26
29
 
@@ -40,7 +43,3 @@ class MissingCommandCallback(Exception):
40
43
  def __init__(self, method_name: str):
41
44
  msg = f"Missing method {method_name} associated with a Command"
42
45
  super().__init__(msg)
43
-
44
-
45
- class LogsStreamerError(Exception):
46
- pass
@@ -6,7 +6,7 @@ from functools import wraps
6
6
  from subprocess import Popen as DefaultPopen
7
7
  from threading import Event, Lock
8
8
  from threading import Thread as DefaultThread
9
- from typing import Any, Callable, List, Tuple, Union
9
+ from typing import Any, Callable, List, Optional, Tuple, Union
10
10
 
11
11
  from splight_lib.abstract.client import AbstractClient
12
12
  from splight_lib.logging._internal import LogTags, get_splight_logger
@@ -240,8 +240,20 @@ class Thread(DefaultThread):
240
240
  def __init__(self, target: Callable, args: Tuple = (), **kwargs) -> None:
241
241
  target = self.store_result(target)
242
242
  self.result = Empty()
243
+ self._exc = None
243
244
  super().__init__(target=target, args=args, name=target, **kwargs)
244
245
 
246
+ @property
247
+ def exc(self):
248
+ return self._exc
249
+
250
+ def run(self):
251
+ try:
252
+ super().run()
253
+ except Exception as exc:
254
+ self._exc = exc
255
+ raise exc
256
+
245
257
  def store_result(self, func: Callable) -> Callable:
246
258
  @wraps(func)
247
259
  def wrapper(*args, **kwargs):
@@ -342,9 +354,20 @@ class ExecutionClient(AbstractClient):
342
354
  return self._scheduler.unschedule(job)
343
355
 
344
356
  def healthcheck(self):
345
- return all(
357
+ is_alive = all(
346
358
  [
347
359
  p.is_alive() or p.exit_ok()
348
360
  for p in self.processes + self.threads
349
361
  ]
350
362
  )
363
+ return is_alive
364
+
365
+ def get_last_exception(self) -> Optional[Exception]:
366
+ """Get the last exception thrown in one of the threads.
367
+ It assumes that there is only one thread that crashed
368
+ Also, only works for the thread not the processes.
369
+ """
370
+ broken_thread = [x.exc for x in self.threads if x.exc]
371
+ if broken_thread:
372
+ return broken_thread[0]
373
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: splight-lib
3
- Version: 3.3.7
3
+ Version: 4.0.0.dev0
4
4
  Summary: Library for public use. Splight
5
5
  Home-page: UNKNOWN
6
6
  Author: Splight
@@ -41,10 +41,6 @@ splight_lib/client/datalake/abstract.py
41
41
  splight_lib/client/datalake/builder.py
42
42
  splight_lib/client/datalake/local_client.py
43
43
  splight_lib/client/datalake/remote_client.py
44
- splight_lib/client/grpc/__init__.py
45
- splight_lib/client/grpc/client.py
46
- splight_lib/client/grpc/decorators.py
47
- splight_lib/client/grpc/reflector.py
48
44
  splight_lib/client/hub/__init__.py
49
45
  splight_lib/client/hub/abstract.py
50
46
  splight_lib/client/hub/client.py
@@ -53,7 +49,6 @@ splight_lib/communication/event_handler.py
53
49
  splight_lib/component/__init__.py
54
50
  splight_lib/component/abstract.py
55
51
  splight_lib/component/exceptions.py
56
- splight_lib/component/log_streamer.py
57
52
  splight_lib/component/spec.py
58
53
  splight_lib/logging/__init__.py
59
54
  splight_lib/logging/_internal.py
@@ -6,10 +6,6 @@ cryptography==35.0.0
6
6
  email-validator==1.3.1
7
7
  furl==2.1.3
8
8
  geojson-pydantic==0.5.0
9
- grpc-interceptor==0.15.0
10
- grpcio-reflection==1.54.2
11
- grpcio-tools==1.54.2
12
- grpcio==1.54.2
13
9
  hexbytes==0.2.2
14
10
  httpx==0.23.3
15
11
  mergedeep==1.3.4
@@ -1,71 +0,0 @@
1
- from typing import Callable, Optional, Tuple
2
-
3
- import grpc
4
-
5
- from splight_lib.client.grpc.decorators import retry_streaming
6
- from splight_lib.client.grpc.reflector import GrpcReflectionClient
7
-
8
-
9
- class LogsGRPCError(Exception):
10
- pass
11
-
12
-
13
- class MissingGRPCService(Exception):
14
- pass
15
-
16
-
17
- class SplightGRPCClient:
18
- AUTHORIZATION: str = "authorization"
19
- _SERVICE_NAME: str = None
20
-
21
- def __init__(self, grpc_host: str, secure_channel: bool = True):
22
- if not self._SERVICE_NAME:
23
- raise MissingGRPCService("Missing parameter service_name")
24
-
25
- if secure_channel:
26
- self._channel = grpc.secure_channel(
27
- grpc_host, grpc.ssl_channel_credentials()
28
- )
29
- else:
30
- self._channel = grpc.insecure_channel(grpc_host)
31
-
32
- self._reflector = GrpcReflectionClient()
33
- self._reflector.load_protocols(
34
- self._channel, symbols=[self._SERVICE_NAME]
35
- )
36
- self._stub = self._reflector.service_stub_class(self._SERVICE_NAME)(
37
- self._channel
38
- )
39
- self._auth_header: Optional[Tuple[str, str]] = None
40
-
41
- def set_authorization_header(self, access_id: str, secret_key: str):
42
- self._auth_header = (
43
- SplightGRPCClient.AUTHORIZATION,
44
- f"Splight {access_id} {secret_key}",
45
- )
46
-
47
-
48
- class LogsGRPCClient(SplightGRPCClient):
49
- _SERVICE_NAME: str = "LogsService"
50
- _LOG_ENTRY: str = "LogEntry"
51
-
52
- def __init__(self, grpc_host: str, secure_channel: bool = True):
53
- super().__init__(grpc_host, secure_channel=secure_channel)
54
- self._log_entry = self._reflector.message_class(self._LOG_ENTRY)
55
-
56
- @retry_streaming(times=5)
57
- def stream_logs(self, log_generator: Callable, component_id: str):
58
- try:
59
- self._stub.stash_log_entry(
60
- self._parse_log_message(log_generator(), component_id),
61
- metadata=[self._auth_header],
62
- )
63
- except grpc.RpcError as exc:
64
- raise LogsGRPCError("Unable to stream component logs") from exc
65
-
66
- def _parse_log_message(self, message_iterator: str, component_id: str):
67
- for message in message_iterator:
68
- yield self._log_entry(
69
- message=message,
70
- component_id=component_id,
71
- )
@@ -1,34 +0,0 @@
1
- from time import sleep
2
- from typing import Callable
3
-
4
- import grpc
5
-
6
- from splight_lib.logging._internal import get_splight_logger
7
-
8
- logger = get_splight_logger()
9
-
10
-
11
- class GRPCRetryError(Exception):
12
- pass
13
-
14
-
15
- def retry_streaming(
16
- times: int = 3, delay: float = 0.5, delay_factor: float = 2
17
- ):
18
- def decorator(func: Callable):
19
- def wrapper(*args, **kwargs):
20
- try:
21
- func(*args, **kwargs)
22
- except grpc.RpcError as e:
23
- logger.error(f"Error: {e}")
24
- if times == 0:
25
- raise GRPCRetryError("Max retries exceeded")
26
- sleep(delay)
27
- logger.warn(f"Retrying {func.__name__}, {times} retries left")
28
- retry_streaming(times - 1, delay * delay_factor)(func)(
29
- *args, **kwargs
30
- )
31
-
32
- return wrapper
33
-
34
- return decorator
@@ -1,320 +0,0 @@
1
- """A gRPC reflection client.
2
-
3
- This module provides a programmatic interface for getting grpc class types
4
- discovered via gRPC reflection service.
5
-
6
- See:
7
- https://grpc.io/ for more information on gRPC in general.
8
-
9
- https://github.com/grpc/grpc/blob/master/doc/server-reflection.md for more
10
- information on gRPC service reflection.
11
-
12
- Code taken from
13
- https://github.com/sparky8512/yagrc
14
- """
15
- from collections import deque
16
-
17
- from google.protobuf import descriptor_pb2, descriptor_pool, message_factory
18
- from google.protobuf.internal import enum_type_wrapper
19
- from grpc_reflection.v1alpha import reflection_pb2, reflection_pb2_grpc
20
-
21
- # This is somewhat arbitrary, and is just here to prevent hang in the case of
22
- # dead network connection that the client side still believes to be open.
23
- QUERY_TIMEOUT = 10
24
- """Max wait time for reflection query completion, in seconds."""
25
-
26
-
27
- class ServiceError(Exception):
28
- """Error reported by reflection service."""
29
-
30
-
31
- def __stub_init__(self, channel):
32
- """The __init__ method for service stub classes."""
33
- for (
34
- name,
35
- call,
36
- full_name,
37
- out_serializer,
38
- in_serializer,
39
- ) in self._methods: # pylint: disable=protected-access
40
- setattr(
41
- self,
42
- name,
43
- getattr(channel, call)(
44
- full_name,
45
- request_serializer=out_serializer,
46
- response_deserializer=in_serializer,
47
- ),
48
- )
49
-
50
-
51
- def _list_services(stub):
52
- responses = stub.ServerReflectionInfo(
53
- iter([reflection_pb2.ServerReflectionRequest(list_services="")]),
54
- timeout=QUERY_TIMEOUT,
55
- )
56
- for response in responses:
57
- if response.HasField("error_response"):
58
- raise ServiceError(response.error_response.error_message)
59
- for service in response.list_services_response.service:
60
- yield service.name
61
-
62
-
63
- def list_services(channel):
64
- """Get list of fully qualified service names via reflection.
65
-
66
- Args:
67
- channel (grpc.Channel): The RPC channel to use.
68
-
69
- Raises:
70
- ServiceError: Reflection service responded with an error.
71
- grpc.RpcError: Lower level RPC error.
72
- """
73
- stub = reflection_pb2_grpc.ServerReflectionStub(channel)
74
- return list(_list_services(stub))
75
-
76
-
77
- def enum_from_descr(proto):
78
- """Get enum class from enum descriptor.
79
-
80
- Args:
81
- proto (google.protobuf.descriptor.EnumDescriptor): Enum descriptor.
82
- """
83
- return enum_type_wrapper.EnumTypeWrapper(proto)
84
-
85
-
86
- class GrpcReflectionEngine:
87
- """Implementation class for gRPC reflection client.
88
-
89
- This class is not meant to be used directly. Instead, use
90
- `GrpcReflectionClient` or `yagrc.importer.GrpcImporter`
91
- """
92
-
93
- def __init__(self):
94
- self.methods_by_file = {}
95
- self.pool = descriptor_pool.DescriptorPool()
96
- self._factory = message_factory.MessageFactory(self.pool)
97
-
98
- def load_protocols(self, channel, filenames=None, symbols=None):
99
- """Implementation of `GrpcReflectionClient.load_protocols`"""
100
- stub = reflection_pb2_grpc.ServerReflectionStub(channel)
101
-
102
- requests = []
103
- if filenames:
104
- requests.extend(
105
- reflection_pb2.ServerReflectionRequest(file_by_filename=name)
106
- for name in filenames
107
- )
108
- if symbols:
109
- requests.extend(
110
- reflection_pb2.ServerReflectionRequest(
111
- file_containing_symbol=symbol
112
- )
113
- for symbol in symbols
114
- )
115
- if not requests:
116
- requests.extend(
117
- reflection_pb2.ServerReflectionRequest(
118
- file_containing_symbol=name
119
- )
120
- for name in _list_services(stub)
121
- if name != "grpc.reflection.v1alpha.ServerReflection"
122
- )
123
-
124
- protos = {}
125
- traversed = set()
126
- while requests:
127
- responses = stub.ServerReflectionInfo(
128
- iter(requests), timeout=QUERY_TIMEOUT
129
- )
130
- deps = set()
131
- for response in responses:
132
- if response.HasField("error_response"):
133
- raise ServiceError(response.error_response.error_message)
134
- for (
135
- desc_bytes
136
- ) in response.file_descriptor_response.file_descriptor_proto:
137
- proto = descriptor_pb2.FileDescriptorProto.FromString( # pylint: disable=no-member
138
- desc_bytes
139
- )
140
- traversed.add(proto.name)
141
- deps.update(proto.dependency)
142
- protos[proto.name] = proto
143
- self.methods_by_file[proto.name] = {
144
- service.name: service.method
145
- for service in proto.service
146
- }
147
- deps -= traversed
148
- requests = [
149
- reflection_pb2.ServerReflectionRequest(file_by_filename=dep)
150
- for dep in deps
151
- ]
152
- # prevent unsatisfied deps from looping forever
153
- traversed.update(deps)
154
-
155
- names = deque(protos.keys())
156
- traversed = set()
157
- while names:
158
- name = names[0]
159
- traversed.add(name)
160
- # raises KeyError if unsatisfied dep:
161
- proto = protos[name]
162
- deps = set(proto.dependency) - traversed
163
- if deps:
164
- names = deque(x for x in names if x not in deps)
165
- names.extendleft(deps)
166
- else:
167
- del names[0]
168
- self.pool.Add(proto)
169
-
170
- return protos.keys()
171
-
172
- def file_descriptor(self, name):
173
- """Get file descriptor for a proto file that has been loaded.
174
-
175
- Args:
176
- name (str): The file name of the .proto file, including path.
177
-
178
- Returns:
179
- google.protobuf.descriptor.FileDescriptor: The descriptor for the
180
- specified file.
181
-
182
- Raises:
183
- KeyError: File has not been loaded.
184
- """
185
- return self.pool.FindFileByName(name)
186
-
187
- def message_from_descr(self, proto):
188
- """Get message class from message descriptor.
189
-
190
- Args:
191
- proto (google.protobuf.descriptor.Descriptor): Message descriptor.
192
- """
193
- message = self._factory.GetPrototype(proto)
194
- return message
195
-
196
- def gen_stub_class(self, service, method_protos):
197
- """Get service stub class from service descriptor.
198
-
199
- Args:
200
- service (google.protobuf.descriptor.ServiceDescriptor): Service
201
- descriptor.
202
- method_protos (iterable): The
203
- `google.protobuf.descriptor_pb2.MethodDescriptorProto` objects
204
- for the methods in the service.
205
- """
206
- stub_methods = []
207
- dep_descrs = []
208
- for method_proto in method_protos:
209
- method = service.methods_by_name[method_proto.name]
210
- channel_call = "_".join(
211
- "stream" if x else "unary"
212
- for x in [
213
- method_proto.client_streaming,
214
- method_proto.server_streaming,
215
- ]
216
- )
217
- dep_descrs.extend([method.input_type, method.output_type])
218
- stub_methods.append(
219
- (
220
- method.name,
221
- channel_call,
222
- "/{}/{}".format(service.full_name, method.name),
223
- self.message_from_descr(
224
- method.input_type
225
- ).SerializeToString,
226
- self.message_from_descr(method.output_type).FromString,
227
- )
228
- )
229
- class_name = service.name + "Stub"
230
- return (
231
- type(
232
- class_name,
233
- (),
234
- {"_methods": stub_methods, "__init__": __stub_init__},
235
- ),
236
- dep_descrs,
237
- )
238
-
239
-
240
- class GrpcReflectionClient:
241
- """Client to discover protocol types via gRPC service reflection.
242
-
243
- Protocol files must be loaded via `load_protocols` prior to getting any of
244
- the class types.
245
-
246
- Note that for nested types (fields, oneofs, etc) other than nested message
247
- types, the top-level message type should be requested and the nested types
248
- can be accessed on it via attribute.
249
- """
250
-
251
- def __init__(self):
252
- self._engine = GrpcReflectionEngine()
253
-
254
- def load_protocols(self, channel, filenames=None, symbols=None):
255
- """Load a set of proto files to use later for protocol types.
256
-
257
- Load one or more specified files and/or files containing specified
258
- symbols, as well as the transitive dependency files of those files.
259
-
260
- If neither `filenames` nor `symbols` is specified, then files
261
- containing the symbols for all services advertised by the RPC server,
262
- other than the reflection service, are loaded.
263
-
264
- This method may be called multiple times to load additional proto
265
- files; however, it is not recommended to use channels that point to
266
- different servers unless it is known that the symbols either do not
267
- overlap or contain the same exact version across all servers.
268
-
269
- The caller-supplied channel is only used within the context of this
270
- method call, so the caller is free to close it afterwards.
271
-
272
- Args:
273
- channel (grpc.Channel): The RPC channel to use.
274
- filenames (iterable[str]): Optional. Proto file names, with path,
275
- to request.
276
- symbols (iterable[str]): Optional. Fully qualified symbol names
277
- for which to request proto files.
278
-
279
- Raises:
280
- ServiceError: Reflection service responded with an error.
281
- grpc.RpcError: Lower level RPC error.
282
- """
283
- return self._engine.load_protocols(channel, filenames, symbols)
284
-
285
- def service_stub_class(self, name):
286
- """Get the stub class for the specified service type.
287
-
288
- Args:
289
- name: Fully qualified name of the protocol service.
290
-
291
- Raises:
292
- KeyError: File defining service has not been loaded.
293
- """
294
- service = self._engine.pool.FindServiceByName(name)
295
- methods = self._engine.methods_by_file[service.file.name][service.name]
296
- return self._engine.gen_stub_class(service, methods)[0]
297
-
298
- def message_class(self, name):
299
- """Get the class for the specified message type.
300
-
301
- Args:
302
- name: Fully qualified name of the protocol message.
303
-
304
- Raises:
305
- KeyError: File defining message has not been loaded.
306
- """
307
- return self._engine.message_from_descr(
308
- self._engine.pool.FindMessageTypeByName(name)
309
- )
310
-
311
- def enum_class(self, name):
312
- """Get the class for the specified enum type.
313
-
314
- Args:
315
- name: Fully qualified name of the protocol enum.
316
-
317
- Raises:
318
- KeyError: File defining enum has not been loaded.
319
- """
320
- return enum_from_descr(self._engine.pool.FindEnumTypeByName(name))
@@ -1,107 +0,0 @@
1
- import re
2
- import sys
3
- from queue import Empty, Queue
4
- from subprocess import Popen
5
- from threading import Thread
6
- from typing import Generator, Optional
7
-
8
- from splight_lib.client.grpc.client import LogsGRPCClient, LogsGRPCError
9
- from splight_lib.component.exceptions import LogsStreamerError
10
- from splight_lib.settings import settings
11
-
12
- LOG_FORMAT = r"^.* \| .* \| .*:\d{2,} \| .* "
13
- LOG_PATTERN = re.compile(LOG_FORMAT)
14
-
15
-
16
- class ComponentLogsStreamer:
17
- def __init__(self, process: Popen, component_id: str):
18
- self._process = process
19
- self._component_id = component_id
20
-
21
- try:
22
- self._client = self._create_client()
23
- except Exception as exc:
24
- raise LogsStreamerError(
25
- "Unable to connect to gRPC server"
26
- ) from exc
27
- self._logs_entry = self._client._log_entry
28
- self._thread: Optional[Thread] = None
29
- self._queue: Optional[Queue] = None
30
- self._running: bool = False
31
-
32
- def _create_client(self):
33
- client = LogsGRPCClient(
34
- grpc_host=settings.SPLIGHT_GRPC_HOST, secure_channel=True
35
- )
36
- client.set_authorization_header(
37
- access_id=settings.SPLIGHT_ACCESS_ID,
38
- secret_key=settings.SPLIGHT_SECRET_KEY,
39
- )
40
- return client
41
-
42
- def start(self):
43
- self._thread = Thread(target=self._consume_logs, daemon=True)
44
- self._queue = Queue()
45
- self._running = True
46
- self._thread.start()
47
- self._run()
48
-
49
- def stop(self):
50
- self._running = False
51
- self._thread.join(timeout=10)
52
- self._queue = None
53
- self._thread = None
54
-
55
- def _run(self):
56
- while self._running:
57
- try:
58
- self._client.stream_logs(
59
- self.logs_iterator, self._component_id
60
- )
61
- except LogsGRPCError as exc:
62
- raise LogsStreamerError(
63
- "Component Logs stream stopped"
64
- ) from exc
65
-
66
- def _consume_logs(self):
67
- reader = iter(self._process.stdout.readline, "")
68
- for new_line in reader:
69
- if self._process.poll() is not None:
70
- output, error = self._process.communicate()
71
- output_msg = output.decode("utf-8")
72
- error_msg = error.decode("utf-8")
73
- self._queue.put(output_msg)
74
- self._queue.put(error_msg)
75
- self._running = False
76
- break
77
- self._queue.put(new_line.decode("utf-8"))
78
-
79
- def logs_iterator(self) -> Generator:
80
- self._message_buffer = []
81
- while True:
82
- try:
83
- message = self._queue.get(timeout=10)
84
- except Empty:
85
- msg = "".join(self._message_buffer)
86
- if msg:
87
- sys.stdout.write(msg)
88
- yield msg
89
- return
90
-
91
- full_msg = self._generate_message(message)
92
- if not full_msg:
93
- continue
94
- yield full_msg
95
- sys.stdout.write(full_msg)
96
-
97
- def _generate_message(self, raw_msg: str) -> Optional[str]:
98
- if self._is_log(raw_msg):
99
- msg = "".join(self._message_buffer)
100
- self._message_buffer = [raw_msg]
101
- return msg
102
- self._message_buffer.append(raw_msg)
103
- return None
104
-
105
- def _is_log(self, raw_msg: str) -> bool:
106
- match = LOG_PATTERN.match(raw_msg)
107
- return True if match else False
File without changes
File without changes
File without changes