protoplasm6 6.1.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.
- protoplasm6/__init__.py +5 -0
- protoplasm6/bases/__init__.py +3 -0
- protoplasm6/bases/_client.py +83 -0
- protoplasm6/bases/_exwrap.py +113 -0
- protoplasm6/bases/_metadata.py +89 -0
- protoplasm6/bases/_methwrap.py +220 -0
- protoplasm6/bases/_remotewrap.py +268 -0
- protoplasm6/bases/_servicer.py +46 -0
- protoplasm6/bases/dataclass_bases.py +2 -0
- protoplasm6/bases/grpc_bases.py +4 -0
- protoplasm6/casting/__init__.py +5 -0
- protoplasm6/casting/casters.py +89 -0
- protoplasm6/casting/castutils.py +379 -0
- protoplasm6/casting/converters/__init__.py +128 -0
- protoplasm6/casting/converters/_base.py +39 -0
- protoplasm6/casting/converters/_dict.py +39 -0
- protoplasm6/casting/converters/_direct.py +461 -0
- protoplasm6/casting/dictator.py +85 -0
- protoplasm6/casting/dictators/__init__.py +375 -0
- protoplasm6/casting/objectifier.py +100 -0
- protoplasm6/decorators/__init__.py +1 -0
- protoplasm6/decorators/apihelpers.py +100 -0
- protoplasm6/errors/__init__.py +4 -0
- protoplasm6/errors/_api.py +238 -0
- protoplasm6/errors/_base.py +31 -0
- protoplasm6/grpcserver.py +53 -0
- protoplasm6/loader/__init__.py +17 -0
- protoplasm6/plasm/__init__.py +7 -0
- protoplasm6/structs/__init__.py +8 -0
- protoplasm6/structs/_base.py +4 -0
- protoplasm6/structs/_ctx/__init__.py +2 -0
- protoplasm6/structs/_ctx/_basectx.py +15 -0
- protoplasm6/structs/_ctx/_grpcctx.py +53 -0
- protoplasm6/structs/_methodtype.py +33 -0
- protoplasm6/structs/_oneofs.py +95 -0
- protoplasm6/structs/_reqestsiter.py +32 -0
- protoplasm6/structs/_responseiter.py +172 -0
- protoplasm6/structs/dataclassbase.py +228 -0
- protoplasm6-6.1.0.dist-info/METADATA +402 -0
- protoplasm6-6.1.0.dist-info/RECORD +43 -0
- protoplasm6-6.1.0.dist-info/WHEEL +5 -0
- protoplasm6-6.1.0.dist-info/licenses/LICENSE +22 -0
- protoplasm6-6.1.0.dist-info/top_level.txt +1 -0
protoplasm6/__init__.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'BaseGrpcClientImplementation',
|
|
3
|
+
]
|
|
4
|
+
import grpc # noqa
|
|
5
|
+
|
|
6
|
+
from protoplasm6.structs import *
|
|
7
|
+
from ._remotewrap import *
|
|
8
|
+
from ._metadata import *
|
|
9
|
+
from .dataclass_bases import *
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseGrpcClientImplementation:
|
|
16
|
+
def __init__(self, service_stub_cls: Type, grpc_host='localhost:50051',
|
|
17
|
+
credentials: Optional[Union[bool, grpc.ChannelCredentials]] = None,
|
|
18
|
+
options: Optional[Dict] = None, metadata: MetadataSpec = None, *args, **kwargs):
|
|
19
|
+
"""
|
|
20
|
+
credentials:
|
|
21
|
+
- None -> Automatic... will use `grpc.ssl_channel_credentials()` and secure_channel if port is 443
|
|
22
|
+
- True -> Will use `grpc.ssl_channel_credentials()` and secure_channel
|
|
23
|
+
- False -> Will use insecure_channel and no creds
|
|
24
|
+
- grpc.ChannelCredentials -> Will use given creds and secure_channel
|
|
25
|
+
|
|
26
|
+
metadata:
|
|
27
|
+
- Metadata (i.e. headers, e.g. for authorization) added to every call
|
|
28
|
+
made by this client, either as a dict, an iterable of (key, value)
|
|
29
|
+
pairs or a callable returning either of those (which gets called for
|
|
30
|
+
every call, e.g. to fetch a fresh access token). It can also be set
|
|
31
|
+
(or changed) later via the `metadata` attribute.
|
|
32
|
+
- See also `call_metadata()` for adding metadata to calls within a
|
|
33
|
+
`with` block.
|
|
34
|
+
"""
|
|
35
|
+
self.metadata: MetadataSpec = metadata
|
|
36
|
+
options = options or {}
|
|
37
|
+
if 'grpc.enable_http_proxy' not in options:
|
|
38
|
+
options['grpc.enable_http_proxy'] = 0
|
|
39
|
+
option_tuple = tuple(options.items())
|
|
40
|
+
|
|
41
|
+
# Backwards compatibility
|
|
42
|
+
if kwargs:
|
|
43
|
+
if not credentials and 'credential' in kwargs:
|
|
44
|
+
credentials = kwargs.pop('credential')
|
|
45
|
+
log.warning('Deprecated keyword "credential" used when initiating BaseGrpcClient.'
|
|
46
|
+
' Please use "credentials" instead!')
|
|
47
|
+
|
|
48
|
+
# This keeps happening...!
|
|
49
|
+
if ':' not in grpc_host:
|
|
50
|
+
if credentials:
|
|
51
|
+
log.warning('No port used in host... adding 443 by default (cause this is a secure connection)!')
|
|
52
|
+
grpc_host = f'{grpc_host}:443'
|
|
53
|
+
else:
|
|
54
|
+
log.warning('No port used in host... adding 50051 by default!')
|
|
55
|
+
grpc_host = f'{grpc_host}:50051'
|
|
56
|
+
|
|
57
|
+
if credentials is None:
|
|
58
|
+
if grpc_host.endswith(':443'):
|
|
59
|
+
credentials = grpc.ssl_channel_credentials()
|
|
60
|
+
elif credentials is True:
|
|
61
|
+
credentials = grpc.ssl_channel_credentials()
|
|
62
|
+
|
|
63
|
+
if credentials:
|
|
64
|
+
self.grpc_channel: grpc.Channel = grpc.secure_channel(grpc_host, credentials, options=option_tuple)
|
|
65
|
+
else:
|
|
66
|
+
self.grpc_channel: grpc.Channel = grpc.insecure_channel(grpc_host, options=option_tuple)
|
|
67
|
+
|
|
68
|
+
self.stub = service_stub_cls(self.grpc_channel)
|
|
69
|
+
|
|
70
|
+
def call_metadata(self, metadata: MetadataSpec = None, **kwargs: Union[str, bytes]):
|
|
71
|
+
"""Context manager that adds the given metadata to every call made by
|
|
72
|
+
this client (only) within its block (in the current thread/context).
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
with orders.call_metadata({'authorization': f'Bearer {token}'}):
|
|
76
|
+
orders.get_order('o1')
|
|
77
|
+
```
|
|
78
|
+
"""
|
|
79
|
+
return call_metadata(metadata, _client=self, **kwargs)
|
|
80
|
+
|
|
81
|
+
def _forward_to_grpc(self, request_cls: Type[T_DCB], stub_method: Callable,
|
|
82
|
+
*args, **kwargs) -> Optional[Union[Any, Tuple[Any], Iterable[T_DCB]]]:
|
|
83
|
+
return RemoteMethodWrapper(self, request_cls, stub_method, *args, **kwargs).call()
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'GrpcExceptionWrapper',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
import grpc
|
|
6
|
+
from typing import *
|
|
7
|
+
from protoplasm6 import errors
|
|
8
|
+
|
|
9
|
+
_STATUS_CODE_TO_EXCEPTION_MAP = {
|
|
10
|
+
grpc.StatusCode.UNKNOWN: errors.api.Unknown,
|
|
11
|
+
grpc.StatusCode.NOT_FOUND: errors.api.NotFound,
|
|
12
|
+
grpc.StatusCode.INVALID_ARGUMENT: errors.api.InvalidArgument,
|
|
13
|
+
grpc.StatusCode.UNIMPLEMENTED: errors.api.Unimplemented,
|
|
14
|
+
grpc.StatusCode.PERMISSION_DENIED: errors.api.PermissionDenied,
|
|
15
|
+
grpc.StatusCode.UNAUTHENTICATED: errors.api.Unauthenticated,
|
|
16
|
+
grpc.StatusCode.UNAVAILABLE: errors.api.Unavailable,
|
|
17
|
+
grpc.StatusCode.DEADLINE_EXCEEDED: errors.api.DeadlineExceeded,
|
|
18
|
+
grpc.StatusCode.ALREADY_EXISTS: errors.api.AlreadyExists,
|
|
19
|
+
grpc.StatusCode.FAILED_PRECONDITION: errors.api.FailedPrecondition,
|
|
20
|
+
grpc.StatusCode.DATA_LOSS: errors.api.DataLoss,
|
|
21
|
+
grpc.StatusCode.RESOURCE_EXHAUSTED: errors.api.ResourceExhausted,
|
|
22
|
+
grpc.StatusCode.OUT_OF_RANGE: errors.api.OutOfRange,
|
|
23
|
+
grpc.StatusCode.INTERNAL: errors.api.Internal,
|
|
24
|
+
grpc.StatusCode.CANCELLED: errors.api.Cancelled,
|
|
25
|
+
grpc.StatusCode.ABORTED: errors.api.Aborted,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class GrpcExceptionWrapper(object):
|
|
30
|
+
def __init__(self, ex: grpc.RpcError):
|
|
31
|
+
self.ex = ex
|
|
32
|
+
self._code = False
|
|
33
|
+
self._debug_error_string = False
|
|
34
|
+
self._details = False
|
|
35
|
+
self._initial_metadata = False
|
|
36
|
+
self._traceback = False
|
|
37
|
+
self._trailing_metadata = False
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def code(self) -> Optional[grpc.StatusCode]:
|
|
41
|
+
if self._code is False:
|
|
42
|
+
self._code = None
|
|
43
|
+
code_call = getattr(self.ex, 'code', None)
|
|
44
|
+
if code_call and hasattr(code_call, '__call__'):
|
|
45
|
+
self._code = code_call()
|
|
46
|
+
return self._code
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def debug_error_string(self) -> str:
|
|
50
|
+
if self._debug_error_string is False:
|
|
51
|
+
self._debug_error_string = ''
|
|
52
|
+
code_call = getattr(self.ex, 'debug_error_string', None)
|
|
53
|
+
if code_call and hasattr(code_call, '__call__'):
|
|
54
|
+
self._debug_error_string = code_call()
|
|
55
|
+
return self._debug_error_string
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def details(self) -> str:
|
|
59
|
+
if self._details is False:
|
|
60
|
+
self._details = ''
|
|
61
|
+
code_call = getattr(self.ex, 'details', None)
|
|
62
|
+
if code_call and hasattr(code_call, '__call__'):
|
|
63
|
+
self._details = code_call()
|
|
64
|
+
return self._details
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def initial_metadata(self) -> dict:
|
|
68
|
+
if self._initial_metadata is False:
|
|
69
|
+
self._initial_metadata = {}
|
|
70
|
+
code_call = getattr(self.ex, 'initial_metadata', None)
|
|
71
|
+
if code_call and hasattr(code_call, '__call__'):
|
|
72
|
+
for k, v in code_call():
|
|
73
|
+
k = k.lower()
|
|
74
|
+
if k not in self._initial_metadata:
|
|
75
|
+
self._initial_metadata[k] = v
|
|
76
|
+
else:
|
|
77
|
+
if not isinstance(self._initial_metadata[k], list):
|
|
78
|
+
self._initial_metadata[k] = [self._initial_metadata[k]]
|
|
79
|
+
self._initial_metadata[k].append(v)
|
|
80
|
+
return self._initial_metadata
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def traceback(self):
|
|
84
|
+
if self._traceback is False:
|
|
85
|
+
self._traceback = None
|
|
86
|
+
code_call = getattr(self.ex, 'traceback', None)
|
|
87
|
+
if code_call and hasattr(code_call, '__call__'):
|
|
88
|
+
self._traceback = code_call()
|
|
89
|
+
return self._traceback
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def trailing_metadata(self) -> dict:
|
|
93
|
+
if self._trailing_metadata is False:
|
|
94
|
+
self._trailing_metadata = {}
|
|
95
|
+
code_call = getattr(self.ex, 'trailing_metadata', None)
|
|
96
|
+
if code_call and hasattr(code_call, '__call__'):
|
|
97
|
+
for k, v in code_call():
|
|
98
|
+
k = k.lower()
|
|
99
|
+
if k not in self._trailing_metadata:
|
|
100
|
+
self._trailing_metadata[k] = v
|
|
101
|
+
else:
|
|
102
|
+
if not isinstance(self._trailing_metadata[k], list):
|
|
103
|
+
self._trailing_metadata[k] = [self._trailing_metadata[k]]
|
|
104
|
+
self._trailing_metadata[k].append(v)
|
|
105
|
+
return self._trailing_metadata
|
|
106
|
+
|
|
107
|
+
def get_reraise(self):
|
|
108
|
+
return _STATUS_CODE_TO_EXCEPTION_MAP.get(self.code, errors.api.Unknown)(self.details,
|
|
109
|
+
ref_code=self.trailing_metadata.get('ref_code', None))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Custom call metadata (i.e. HTTP/2 headers, e.g. for authorization) for gRPC
|
|
2
|
+
calls made by protoplasm6 clients.
|
|
3
|
+
"""
|
|
4
|
+
__all__ = [
|
|
5
|
+
'MetadataSpec',
|
|
6
|
+
'call_metadata',
|
|
7
|
+
'normalize_metadata',
|
|
8
|
+
'resolve_metadata',
|
|
9
|
+
]
|
|
10
|
+
import contextlib
|
|
11
|
+
import contextvars
|
|
12
|
+
|
|
13
|
+
from protoplasm6.structs import *
|
|
14
|
+
|
|
15
|
+
MetadataValue = Union[str, bytes]
|
|
16
|
+
MetadataPairs = Union[Mapping[str, MetadataValue], Iterable[Tuple[str, MetadataValue]]]
|
|
17
|
+
MetadataSpec = Union[MetadataPairs, Callable[[], Optional[MetadataPairs]], None]
|
|
18
|
+
"""Metadata can be given as a dict, an iterable of (key, value) pairs or a
|
|
19
|
+
callable (taking no arguments) returning either of those, which gets called
|
|
20
|
+
for every single call (e.g. to fetch a fresh access token)."""
|
|
21
|
+
|
|
22
|
+
# Stack of (client or None for "all clients", metadata spec)
|
|
23
|
+
_CONTEXT: contextvars.ContextVar[Tuple[Tuple[Any, MetadataSpec], ...]] = contextvars.ContextVar(
|
|
24
|
+
'protoplasm6_call_metadata', default=())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def normalize_metadata(metadata: MetadataSpec) -> List[Tuple[str, MetadataValue]]:
|
|
28
|
+
"""Turns a metadata spec into a list of (key, value) tuples with lower case
|
|
29
|
+
keys (as gRPC requires).
|
|
30
|
+
"""
|
|
31
|
+
if metadata is None:
|
|
32
|
+
return []
|
|
33
|
+
if callable(metadata):
|
|
34
|
+
metadata = metadata()
|
|
35
|
+
if metadata is None:
|
|
36
|
+
return []
|
|
37
|
+
if isinstance(metadata, Mapping):
|
|
38
|
+
metadata = metadata.items()
|
|
39
|
+
pairs = []
|
|
40
|
+
for key, value in metadata:
|
|
41
|
+
key = str(key).lower()
|
|
42
|
+
if not key.endswith('-bin') and not isinstance(value, str):
|
|
43
|
+
value = str(value)
|
|
44
|
+
pairs.append((key, value))
|
|
45
|
+
return pairs
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@contextlib.contextmanager
|
|
49
|
+
def call_metadata(metadata: MetadataSpec = None, *, _client: Any = None, **kwargs: MetadataValue):
|
|
50
|
+
"""Context manager that adds the given metadata (e.g. authorization
|
|
51
|
+
headers) to every gRPC call made by protoplasm6 clients within its block
|
|
52
|
+
(in the current thread/context only).
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from protoplasm6 import plasm
|
|
56
|
+
|
|
57
|
+
with plasm.call_metadata({'authorization': f'Bearer {token}'}):
|
|
58
|
+
orders.get_order('o1')
|
|
59
|
+
|
|
60
|
+
with plasm.call_metadata(x_request_id='abc123'): # Underscores become dashes in keywords
|
|
61
|
+
orders.get_order('o1')
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
These can be nested and combined with the client's own metadata (see
|
|
65
|
+
`BaseGrpcClientImplementation`). Use `client.call_metadata(...)` to only
|
|
66
|
+
add metadata to the calls of a single client.
|
|
67
|
+
"""
|
|
68
|
+
entries = []
|
|
69
|
+
if metadata is not None:
|
|
70
|
+
entries.append((_client, metadata))
|
|
71
|
+
if kwargs:
|
|
72
|
+
entries.append((_client, {k.replace('_', '-'): v for k, v in kwargs.items()}))
|
|
73
|
+
token = _CONTEXT.set(_CONTEXT.get() + tuple(entries))
|
|
74
|
+
try:
|
|
75
|
+
yield
|
|
76
|
+
finally:
|
|
77
|
+
_CONTEXT.reset(token)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def resolve_metadata(client: Any) -> Optional[List[Tuple[str, MetadataValue]]]:
|
|
81
|
+
"""Returns all the metadata for a call made by the given client: The
|
|
82
|
+
client's own metadata followed by any metadata added via `call_metadata()`
|
|
83
|
+
(for all clients or for this one) in the order it was added.
|
|
84
|
+
"""
|
|
85
|
+
pairs = normalize_metadata(getattr(client, 'metadata', None))
|
|
86
|
+
for target, spec in _CONTEXT.get():
|
|
87
|
+
if target is None or target is client:
|
|
88
|
+
pairs.extend(normalize_metadata(spec))
|
|
89
|
+
return pairs or None
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'PlasmMethodWrapper',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
import grpc
|
|
6
|
+
import json
|
|
7
|
+
import uuid
|
|
8
|
+
|
|
9
|
+
from batutils.tpu import casting as typeutils_casting
|
|
10
|
+
|
|
11
|
+
from protoplasm6 import casting
|
|
12
|
+
from protoplasm6 import errors
|
|
13
|
+
from protoplasm6.bases.dataclass_bases import *
|
|
14
|
+
from protoplasm6.structs import *
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from .grpc_bases import BaseGrpcServicer
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
log = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PlasmMethodWrapper:
|
|
24
|
+
JSON_SERIALIZER = typeutils_casting.JsonSafeSerializer()
|
|
25
|
+
|
|
26
|
+
__slots__ = ('servicer', 'data_cls', 'impl_method', 'request', 'request_iterator', 'context', 'request_dc',
|
|
27
|
+
'unpacked_args', 'raw_response_iterable', 'raw_response', 'response_dc', 'response_proto',
|
|
28
|
+
'_ref_code', 'method_io')
|
|
29
|
+
|
|
30
|
+
def __init__(self, servicer: 'BaseGrpcServicer', data_cls: Type[T_DCB], impl_method: Callable,
|
|
31
|
+
request_or_iterator: Union[GeneratedProtocolMessageType, Iterable[GeneratedProtocolMessageType]],
|
|
32
|
+
context: grpc.ServicerContext, method_io: MethodIoType):
|
|
33
|
+
"""Container and worker for processing Servicer (i.e. Server Side)
|
|
34
|
+
requests and responses, packing, unpacking, casting and so on as well as
|
|
35
|
+
handling errors and log-dumping and all that bulky noisy jass that just
|
|
36
|
+
clutters up the actual servicer code.
|
|
37
|
+
"""
|
|
38
|
+
self.servicer: 'BaseGrpcServicer' = servicer
|
|
39
|
+
self.data_cls: Type[DataclassBase] = data_cls
|
|
40
|
+
self.impl_method: Callable = impl_method
|
|
41
|
+
|
|
42
|
+
self.request: Optional[GeneratedProtocolMessageType] = None
|
|
43
|
+
self.request_iterator: Optional[Iterable[GeneratedProtocolMessageType]] = None
|
|
44
|
+
|
|
45
|
+
self.context: grpc.ServicerContext = context
|
|
46
|
+
self.method_io: MethodIoType = method_io
|
|
47
|
+
|
|
48
|
+
if self.method_io.is_input_stream():
|
|
49
|
+
self.request_iterator = request_or_iterator
|
|
50
|
+
else:
|
|
51
|
+
self.request = request_or_iterator
|
|
52
|
+
|
|
53
|
+
self.request_dc: Optional[DataclassBase] = None
|
|
54
|
+
self.unpacked_args: Optional[Dict] = None
|
|
55
|
+
self.raw_response_iterable: Optional[Iterable[Any]] = None
|
|
56
|
+
self.raw_response: Any = None
|
|
57
|
+
self.response_dc: Optional[DataclassBase] = None
|
|
58
|
+
self.response_proto: Optional[GeneratedProtocolMessageType] = None
|
|
59
|
+
self._ref_code: str = ''
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def request_dc_iterator(self) -> Iterable[T_DCB]:
|
|
63
|
+
for raw_req in self.request_iterator:
|
|
64
|
+
self.request = raw_req
|
|
65
|
+
self._p2d()
|
|
66
|
+
yield self.request_dc
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def ref_code(self) -> str:
|
|
70
|
+
if not self._ref_code:
|
|
71
|
+
self._ref_code = str(uuid.uuid4())
|
|
72
|
+
return self._ref_code
|
|
73
|
+
|
|
74
|
+
def _get_dump(self, **kwargs) -> dict:
|
|
75
|
+
d = {
|
|
76
|
+
'ref_code': self.ref_code,
|
|
77
|
+
'class': str(self.servicer.__class__),
|
|
78
|
+
'data_cls': str(self.data_cls),
|
|
79
|
+
'impl_method': str(self.impl_method),
|
|
80
|
+
'request': str(self.request),
|
|
81
|
+
'method_io': self.method_io,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if self.context:
|
|
85
|
+
try:
|
|
86
|
+
d['invocation_metadata'] = dict(self.context.invocation_metadata()) # noqa
|
|
87
|
+
except Exception:
|
|
88
|
+
d['invocation_metadata'] = 'FAILED TO FETCH METADATA!'
|
|
89
|
+
|
|
90
|
+
if self.request_dc:
|
|
91
|
+
d['request_dc'] = repr(self.request_dc)
|
|
92
|
+
if self.unpacked_args:
|
|
93
|
+
d['unpacked_args'] = self.unpacked_args
|
|
94
|
+
if self.raw_response:
|
|
95
|
+
d['raw_response'] = repr(self.raw_response)
|
|
96
|
+
if self.response_dc:
|
|
97
|
+
d['response_dc'] = repr(self.response_dc)
|
|
98
|
+
if self.response_proto:
|
|
99
|
+
d['response_proto'] = repr(self.response_proto)
|
|
100
|
+
|
|
101
|
+
if kwargs:
|
|
102
|
+
d.update(kwargs)
|
|
103
|
+
|
|
104
|
+
return d
|
|
105
|
+
|
|
106
|
+
def _get_json_dump(self, **kwargs) -> str:
|
|
107
|
+
return json.dumps(self.JSON_SERIALIZER.serialize(self._get_dump(**kwargs)), indent=4)
|
|
108
|
+
|
|
109
|
+
def _abort(self, message: str, error_code, ex):
|
|
110
|
+
self.context.set_trailing_metadata([('ref_code', self.ref_code)])
|
|
111
|
+
log.exception(f'{message}::{ex!r}\n---\n{self._get_json_dump(details=repr(ex))}')
|
|
112
|
+
return self.context.abort(error_code, f'{ex!r};\nref_code={self.ref_code}')
|
|
113
|
+
|
|
114
|
+
def _abort_internal(self, ex: errors.ServiceApiException):
|
|
115
|
+
details = [ex.details]
|
|
116
|
+
if ex.should_log:
|
|
117
|
+
details.append(f'ref_code={self.ref_code}')
|
|
118
|
+
self.context.set_trailing_metadata([('ref_code', self.ref_code)])
|
|
119
|
+
log.exception(f'Logging ServiceApiException::{ex!r}\n---\n'
|
|
120
|
+
f'{self._get_json_dump(status_code=ex.status_code, details=ex.details)}')
|
|
121
|
+
return self.context.abort(ex.status_code, ';\n'.join(details))
|
|
122
|
+
|
|
123
|
+
def _p2d(self):
|
|
124
|
+
try:
|
|
125
|
+
self.request_dc = casting.proto_to_dataclass(self.request)
|
|
126
|
+
except Exception as ex:
|
|
127
|
+
return self._abort('pre_request_error;Error while casting proto to dataclass',
|
|
128
|
+
grpc.StatusCode.FAILED_PRECONDITION, ex)
|
|
129
|
+
|
|
130
|
+
def _unpack(self):
|
|
131
|
+
try:
|
|
132
|
+
if self.method_io.is_input_stream():
|
|
133
|
+
self.unpacked_args = {'request_iterator': self.request_dc_iterator}
|
|
134
|
+
else:
|
|
135
|
+
self.unpacked_args = self.request_dc.unpack()
|
|
136
|
+
except Exception as ex:
|
|
137
|
+
return self._abort('pre_request_error;Error while unpacking arguments',
|
|
138
|
+
grpc.StatusCode.FAILED_PRECONDITION, ex)
|
|
139
|
+
|
|
140
|
+
def _add_ctx(self):
|
|
141
|
+
try:
|
|
142
|
+
ctx_name = getattr(self.impl_method, '__takes_context_as__', False)
|
|
143
|
+
if ctx_name:
|
|
144
|
+
self.unpacked_args[ctx_name] = GrpcContext(self.context, self.method_io)
|
|
145
|
+
except Exception as ex:
|
|
146
|
+
return self._abort('pre_request_error;Error while adding context to parameters',
|
|
147
|
+
grpc.StatusCode.FAILED_PRECONDITION, ex)
|
|
148
|
+
|
|
149
|
+
def prepare(self):
|
|
150
|
+
if not self.method_io.is_input_stream():
|
|
151
|
+
self._p2d()
|
|
152
|
+
self._unpack()
|
|
153
|
+
self._add_ctx()
|
|
154
|
+
|
|
155
|
+
def _pack(self):
|
|
156
|
+
self.response_dc = None
|
|
157
|
+
try:
|
|
158
|
+
self.response_dc = casting.pack_dataclass_response(self.data_cls, self.raw_response)
|
|
159
|
+
except Exception as ex:
|
|
160
|
+
return self._abort('post_request_error;Error while packing raw response to dataclass',
|
|
161
|
+
grpc.StatusCode.CANCELLED, ex)
|
|
162
|
+
|
|
163
|
+
def _d2p(self):
|
|
164
|
+
self.response_proto = None
|
|
165
|
+
try:
|
|
166
|
+
self.response_proto = casting.dataclass_to_proto(self.response_dc)
|
|
167
|
+
except Exception as ex:
|
|
168
|
+
return self._abort('post_request_error;Error while casting dataclass to proto',
|
|
169
|
+
grpc.StatusCode.CANCELLED, ex)
|
|
170
|
+
|
|
171
|
+
def call(self) -> Union[GeneratedProtocolMessageType,
|
|
172
|
+
Iterable[GeneratedProtocolMessageType]]:
|
|
173
|
+
# Prepare the call
|
|
174
|
+
self.prepare()
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
if self.method_io.is_output_stream():
|
|
178
|
+
self.raw_response_iterable = self.impl_method(**self.unpacked_args)
|
|
179
|
+
|
|
180
|
+
else:
|
|
181
|
+
self.raw_response = self.impl_method(**self.unpacked_args)
|
|
182
|
+
|
|
183
|
+
except errors.ServiceApiException as ex:
|
|
184
|
+
return self._abort_internal(ex)
|
|
185
|
+
|
|
186
|
+
except Exception as ex:
|
|
187
|
+
return self._abort('Unexpected exception in implementation method',
|
|
188
|
+
grpc.StatusCode.UNKNOWN, ex)
|
|
189
|
+
|
|
190
|
+
if not self.method_io.is_output_stream():
|
|
191
|
+
# Pack up results for unary results
|
|
192
|
+
self._pack()
|
|
193
|
+
self._d2p()
|
|
194
|
+
return self.response_proto
|
|
195
|
+
|
|
196
|
+
else:
|
|
197
|
+
return self.iterable_response_proto
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def wrapped_iterable_raw_response(self) -> Iterable[Any]:
|
|
201
|
+
try:
|
|
202
|
+
for res in self.raw_response_iterable:
|
|
203
|
+
yield res
|
|
204
|
+
except errors.ServiceApiException as ex:
|
|
205
|
+
return self._abort_internal(ex)
|
|
206
|
+
except Exception as ex:
|
|
207
|
+
return self._abort('Unexpected exception in implementation method',
|
|
208
|
+
grpc.StatusCode.UNKNOWN, ex)
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def iterable_response_proto(self) -> Iterable[GeneratedProtocolMessageType]:
|
|
212
|
+
for res in self.wrapped_iterable_raw_response:
|
|
213
|
+
self.raw_response = res
|
|
214
|
+
self._pack()
|
|
215
|
+
self._d2p()
|
|
216
|
+
yield self.response_proto
|
|
217
|
+
|
|
218
|
+
self.raw_response = None
|
|
219
|
+
self.response_dc = None
|
|
220
|
+
self.response_proto = None
|