azure-functions-runtime 1.0.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.
Files changed (30) hide show
  1. azure_functions_runtime/__init__.py +21 -0
  2. azure_functions_runtime/bindings/context.py +66 -0
  3. azure_functions_runtime/bindings/datumdef.py +232 -0
  4. azure_functions_runtime/bindings/generic.py +75 -0
  5. azure_functions_runtime/bindings/meta.py +288 -0
  6. azure_functions_runtime/bindings/nullable_converters.py +126 -0
  7. azure_functions_runtime/bindings/out.py +15 -0
  8. azure_functions_runtime/bindings/retrycontext.py +47 -0
  9. azure_functions_runtime/bindings/tracecontext.py +43 -0
  10. azure_functions_runtime/functions.py +435 -0
  11. azure_functions_runtime/handle_event.py +448 -0
  12. azure_functions_runtime/http_v2.py +291 -0
  13. azure_functions_runtime/loader.py +219 -0
  14. azure_functions_runtime/logging.py +16 -0
  15. azure_functions_runtime/otel.py +116 -0
  16. azure_functions_runtime/utils/__init__.py +2 -0
  17. azure_functions_runtime/utils/app_setting_manager.py +102 -0
  18. azure_functions_runtime/utils/constants.py +63 -0
  19. azure_functions_runtime/utils/executor.py +34 -0
  20. azure_functions_runtime/utils/helpers.py +40 -0
  21. azure_functions_runtime/utils/threadpool.py +88 -0
  22. azure_functions_runtime/utils/tracing.py +70 -0
  23. azure_functions_runtime/utils/typing_inspect.py +295 -0
  24. azure_functions_runtime/utils/validators.py +19 -0
  25. azure_functions_runtime/utils/wrappers.py +52 -0
  26. azure_functions_runtime/version.py +4 -0
  27. azure_functions_runtime-1.0.0.dist-info/METADATA +120 -0
  28. azure_functions_runtime-1.0.0.dist-info/RECORD +30 -0
  29. azure_functions_runtime-1.0.0.dist-info/WHEEL +5 -0
  30. azure_functions_runtime-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ from .handle_event import (worker_init_request,
4
+ functions_metadata_request,
5
+ function_environment_reload_request,
6
+ invocation_request,
7
+ function_load_request)
8
+ from .utils.threadpool import (
9
+ start_threadpool_executor,
10
+ stop_threadpool_executor,
11
+ get_threadpool_executor,
12
+ )
13
+
14
+ __all__ = ('worker_init_request',
15
+ 'functions_metadata_request',
16
+ 'function_environment_reload_request',
17
+ 'invocation_request',
18
+ 'function_load_request',
19
+ 'start_threadpool_executor',
20
+ 'stop_threadpool_executor',
21
+ 'get_threadpool_executor')
@@ -0,0 +1,66 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ import threading
4
+
5
+ from .retrycontext import RetryContext
6
+ from .tracecontext import TraceContext
7
+
8
+
9
+ class Context:
10
+ def __init__(self,
11
+ func_name: str,
12
+ func_dir: str,
13
+ invocation_id: str,
14
+ thread_local_storage: threading.local,
15
+ trace_context: TraceContext,
16
+ retry_context: RetryContext) -> None:
17
+ self.__func_name = func_name
18
+ self.__func_dir = func_dir
19
+ self.__invocation_id = invocation_id
20
+ self.__thread_local_storage = thread_local_storage
21
+ self.__trace_context = trace_context
22
+ self.__retry_context = retry_context
23
+
24
+ @property
25
+ def invocation_id(self) -> str:
26
+ return self.__invocation_id
27
+
28
+ @property
29
+ def thread_local_storage(self) -> threading.local:
30
+ return self.__thread_local_storage
31
+
32
+ @property
33
+ def function_name(self) -> str:
34
+ return self.__func_name
35
+
36
+ @property
37
+ def function_directory(self) -> str:
38
+ return self.__func_dir
39
+
40
+ @property
41
+ def trace_context(self) -> TraceContext:
42
+ return self.__trace_context
43
+
44
+ @property
45
+ def retry_context(self) -> RetryContext:
46
+ return self.__retry_context
47
+
48
+
49
+ def get_context(invoc_request, name: str,
50
+ directory: str) -> Context:
51
+ """ For more information refer:
52
+ https://aka.ms/azfunc-invocation-context
53
+ """
54
+ trace_context = TraceContext(
55
+ invoc_request.trace_context.trace_parent,
56
+ invoc_request.trace_context.trace_state,
57
+ invoc_request.trace_context.attributes)
58
+
59
+ retry_context = RetryContext(
60
+ invoc_request.retry_context.retry_count,
61
+ invoc_request.retry_context.max_retry_count,
62
+ invoc_request.retry_context.exception)
63
+
64
+ return Context(
65
+ name, directory, invoc_request.invocation_id,
66
+ threading.local(), trace_context, retry_context)
@@ -0,0 +1,232 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ import json
4
+ import logging
5
+
6
+ from datetime import datetime
7
+ from typing import Any, List, Optional
8
+
9
+ from .nullable_converters import (
10
+ to_nullable_bool,
11
+ to_nullable_double,
12
+ to_nullable_string,
13
+ to_nullable_timestamp,
14
+ )
15
+
16
+ try:
17
+ from http.cookies import SimpleCookie
18
+ except ImportError:
19
+ from Cookie import SimpleCookie # type: ignore
20
+
21
+
22
+ class Datum:
23
+ def __init__(self, value, type):
24
+ self.value = value
25
+ self.type = type
26
+
27
+ @property
28
+ def python_value(self) -> Any:
29
+ if self.value is None or self.type is None:
30
+ return None
31
+ elif self.type in ('bytes', 'string', 'int', 'double'):
32
+ return self.value
33
+ elif self.type == 'json':
34
+ return json.loads(self.value)
35
+ elif self.type == 'collection_string':
36
+ return [v for v in self.value.string]
37
+ elif self.type == 'collection_bytes':
38
+ return [v for v in self.value.bytes]
39
+ elif self.type == 'collection_double':
40
+ return [v for v in self.value.double]
41
+ elif self.type == 'collection_sint64':
42
+ return [v for v in self.value.sint64]
43
+ else:
44
+ return self.value
45
+
46
+ @property
47
+ def python_type(self) -> type:
48
+ return type(self.python_value)
49
+
50
+ def __eq__(self, other):
51
+ if not isinstance(other, type(self)):
52
+ return False
53
+
54
+ return self.value == other.value and self.type == other.type
55
+
56
+ def __hash__(self):
57
+ return hash((type(self), (self.value, self.type)))
58
+
59
+ def __repr__(self):
60
+ val_repr = repr(self.value)
61
+ if len(val_repr) > 10:
62
+ val_repr = val_repr[:10] + '...'
63
+ return '<Datum ' + str(self.type) + val_repr + '>'
64
+
65
+ @classmethod
66
+ def from_typed_data(cls, protos):
67
+ try:
68
+ td = protos.TypedData
69
+ except Exception:
70
+ td = protos
71
+ tt = td.WhichOneof('data')
72
+ if tt == 'http':
73
+ http = td.http
74
+ val = dict(
75
+ method=Datum(http.method, 'string'),
76
+ url=Datum(http.url, 'string'),
77
+ headers={
78
+ k: Datum(v, 'string') for k, v in http.headers.items()
79
+ },
80
+ body=(
81
+ Datum.from_typed_data(http.body)
82
+ or Datum(type='bytes', value=b'')
83
+ ),
84
+ params={
85
+ k: Datum(v, 'string') for k, v in http.params.items()
86
+ },
87
+ query={
88
+ k: Datum(v, 'string') for k, v in http.query.items()
89
+ },
90
+ )
91
+ elif tt == 'string':
92
+ val = td.string
93
+ elif tt == 'bytes':
94
+ val = td.bytes
95
+ elif tt == 'json':
96
+ val = td.json
97
+ elif tt == 'collection_bytes':
98
+ val = td.collection_bytes
99
+ elif tt == 'collection_string':
100
+ val = td.collection_string
101
+ elif tt == 'collection_sint64':
102
+ val = td.collection_sint64
103
+ elif tt == 'model_binding_data':
104
+ val = td.model_binding_data
105
+ elif tt == 'collection_model_binding_data':
106
+ val = td.collection_model_binding_data
107
+ elif tt is None:
108
+ return None
109
+ else:
110
+ raise NotImplementedError(
111
+ 'unsupported TypeData kind: %s' % tt
112
+ )
113
+
114
+ return cls(val, tt)
115
+
116
+
117
+ def datum_as_proto(datum: Datum, protos):
118
+ if datum.type == 'string':
119
+ return protos.TypedData(string=datum.value)
120
+ elif datum.type == 'bytes':
121
+ return protos.TypedData(bytes=datum.value)
122
+ elif datum.type == 'json':
123
+ return protos.TypedData(json=datum.value)
124
+ elif datum.type == 'http':
125
+ return protos.TypedData(http=protos.RpcHttp(
126
+ status_code=datum.value['status_code'].value,
127
+ headers={
128
+ k: v.value
129
+ for k, v in datum.value['headers'].items()
130
+ },
131
+ cookies=parse_to_rpc_http_cookie_list(datum.value.get('cookies'), protos),
132
+ enable_content_negotiation=False,
133
+ body=datum_as_proto(datum.value['body'], protos),
134
+ ))
135
+ elif datum.type is None:
136
+ return None
137
+ elif datum.type == 'dict':
138
+ # TypedData doesn't support dict, so we return it as json
139
+ return protos.TypedData(json=json.dumps(datum.value))
140
+ elif datum.type == 'list':
141
+ # TypedData doesn't support list, so we return it as json
142
+ return protos.TypedData(json=json.dumps(datum.value))
143
+ elif datum.type == 'int':
144
+ return protos.TypedData(int=datum.value)
145
+ elif datum.type == 'double':
146
+ return protos.TypedData(double=datum.value)
147
+ elif datum.type == 'bool':
148
+ # TypedData doesn't support bool, so we return it as an int
149
+ return protos.TypedData(int=int(datum.value))
150
+ else:
151
+ raise NotImplementedError(
152
+ 'unexpected Datum type: %s' % datum.type
153
+ )
154
+
155
+
156
+ def parse_to_rpc_http_cookie_list(cookies: Optional[List[SimpleCookie]], protos):
157
+ if cookies is None:
158
+ return cookies
159
+
160
+ rpc_http_cookies = []
161
+
162
+ for cookie in cookies:
163
+ for name, cookie_entity in cookie.items():
164
+ rpc_http_cookies.append(
165
+ protos.RpcHttpCookie(name=name,
166
+ value=cookie_entity.value,
167
+ domain=to_nullable_string(
168
+ cookie_entity['domain'],
169
+ 'cookie.domain',
170
+ protos),
171
+ path=to_nullable_string(
172
+ cookie_entity['path'],
173
+ 'cookie.path',
174
+ protos),
175
+ expires=to_nullable_timestamp(
176
+ parse_cookie_attr_expires(
177
+ cookie_entity), 'cookie.expires',
178
+ protos),
179
+ secure=to_nullable_bool(
180
+ bool(cookie_entity['secure']),
181
+ 'cookie.secure',
182
+ protos),
183
+ http_only=to_nullable_bool(
184
+ bool(cookie_entity['httponly']),
185
+ 'cookie.httpOnly',
186
+ protos),
187
+ same_site=parse_cookie_attr_same_site(
188
+ cookie_entity, protos),
189
+ max_age=to_nullable_double(
190
+ cookie_entity['max-age'],
191
+ 'cookie.maxAge',
192
+ protos)))
193
+
194
+ return rpc_http_cookies
195
+
196
+
197
+ def parse_cookie_attr_expires(cookie_entity):
198
+ expires = cookie_entity['expires']
199
+
200
+ if expires is not None and len(expires) != 0:
201
+ try:
202
+ return datetime.strptime(expires, "%a, %d %b %Y %H:%M:%S GMT")
203
+ except ValueError:
204
+ logging.error(
205
+ "Can not parse value %s of expires in the cookie "
206
+ "due to invalid format.", expires)
207
+ raise
208
+ except OverflowError:
209
+ logging.error(
210
+ "Can not parse value %s of expires in the cookie "
211
+ "because the parsed date exceeds the largest valid C "
212
+ "integer on your system.", expires)
213
+ raise
214
+
215
+ return None
216
+
217
+
218
+ def parse_cookie_attr_same_site(cookie_entity, protos):
219
+ same_site = getattr(protos.RpcHttpCookie.SameSite, "None")
220
+ try:
221
+ raw_same_site_str = cookie_entity['samesite'].lower()
222
+
223
+ if raw_same_site_str == 'lax':
224
+ same_site = protos.RpcHttpCookie.SameSite.Lax
225
+ elif raw_same_site_str == 'strict':
226
+ same_site = protos.RpcHttpCookie.SameSite.Strict
227
+ elif raw_same_site_str == 'none':
228
+ same_site = protos.RpcHttpCookie.SameSite.ExplicitNone
229
+ except Exception:
230
+ return same_site
231
+
232
+ return same_site
@@ -0,0 +1,75 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ import typing
4
+
5
+ from typing import Any, Optional
6
+
7
+ from .datumdef import Datum
8
+
9
+
10
+ class GenericBinding:
11
+
12
+ @classmethod
13
+ def has_trigger_support(cls) -> bool:
14
+ return False
15
+
16
+ @classmethod
17
+ def check_input_type_annotation(cls, pytype: type) -> bool:
18
+ return issubclass(pytype, (str, bytes))
19
+
20
+ @classmethod
21
+ def check_output_type_annotation(cls, pytype: type) -> bool:
22
+ return issubclass(pytype, (str, bytes, bytearray))
23
+
24
+ @classmethod
25
+ def encode(cls, obj: Any, *,
26
+ expected_type: Optional[type]) -> Datum:
27
+ if isinstance(obj, str):
28
+ return Datum(type='string', value=obj)
29
+
30
+ elif isinstance(obj, (bytes, bytearray)):
31
+ return Datum(type='bytes', value=bytes(obj))
32
+ elif obj is None:
33
+ return Datum(type=None, value=obj)
34
+ elif isinstance(obj, dict):
35
+ return Datum(type='dict', value=obj)
36
+ elif isinstance(obj, list):
37
+ return Datum(type='list', value=obj)
38
+ elif isinstance(obj, int):
39
+ return Datum(type='int', value=obj)
40
+ elif isinstance(obj, float):
41
+ return Datum(type='double', value=obj)
42
+ elif isinstance(obj, bool):
43
+ return Datum(type='bool', value=obj)
44
+ else:
45
+ raise NotImplementedError
46
+
47
+ @classmethod
48
+ def decode(cls, data: Datum, *, trigger_metadata) -> typing.Any:
49
+ # Enabling support for Dapr bindings
50
+ # https://github.com/Azure/azure-functions-python-worker/issues/1316
51
+ if data is None:
52
+ return None
53
+ data_type = data.type
54
+
55
+ if data_type == 'string':
56
+ result = data.value
57
+ elif data_type == 'bytes':
58
+ result = data.value
59
+ elif data_type == 'json':
60
+ result = data.value
61
+ elif data_type is None:
62
+ result = None
63
+ else:
64
+ raise ValueError(
65
+ 'unexpected type of data received for the "generic" binding ',
66
+ repr(data_type)
67
+ )
68
+
69
+ return result
70
+
71
+ @classmethod
72
+ def has_implicit_output(cls, bind_name: Optional[str]) -> bool:
73
+ if bind_name == 'durableClient':
74
+ return False
75
+ return True
@@ -0,0 +1,288 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ # mypy: disable-error-code="attr-defined"
4
+ import os
5
+ import sys
6
+
7
+ from typing import Any, Dict, Optional, Union
8
+
9
+ from .datumdef import Datum, datum_as_proto
10
+ from .generic import GenericBinding
11
+
12
+ from ..http_v2 import HttpV2Registry
13
+ from ..logging import logger
14
+ from ..utils.constants import (
15
+ CUSTOMER_PACKAGES_PATH,
16
+ HTTP,
17
+ HTTP_TRIGGER,
18
+ )
19
+ from ..utils.helpers import set_sdk_version
20
+
21
+
22
+ PB_TYPE = 'rpc_data'
23
+ PB_TYPE_DATA = 'data'
24
+ PB_TYPE_RPC_SHARED_MEMORY = 'rpc_shared_memory'
25
+
26
+ BINDING_REGISTRY = None
27
+ DEFERRED_BINDING_REGISTRY = None
28
+
29
+
30
+ def _check_http_input_type_annotation(bind_name: str, pytype: type,
31
+ is_deferred_binding: bool) -> bool:
32
+ if HttpV2Registry.http_v2_enabled():
33
+ return HttpV2Registry.ext_base().RequestTrackerMeta \
34
+ .check_type(pytype)
35
+
36
+ binding = get_binding(bind_name, is_deferred_binding)
37
+ return binding.check_input_type_annotation(pytype)
38
+
39
+
40
+ def _check_http_output_type_annotation(bind_name: str, pytype: type) -> bool:
41
+ if HttpV2Registry.http_v2_enabled():
42
+ return HttpV2Registry.ext_base().ResponseTrackerMeta.check_type(pytype)
43
+
44
+ binding = get_binding(bind_name)
45
+ return binding.check_output_type_annotation(pytype)
46
+
47
+
48
+ INPUT_TYPE_CHECK_OVERRIDE_MAP = {
49
+ HTTP_TRIGGER: _check_http_input_type_annotation
50
+ }
51
+
52
+ OUTPUT_TYPE_CHECK_OVERRIDE_MAP = {
53
+ HTTP: _check_http_output_type_annotation
54
+ }
55
+
56
+
57
+ def load_binding_registry() -> None:
58
+ """
59
+ Tries to load azure-functions from the customer's BYO. If it's
60
+ not found, it loads the builtin. If the BINDING_REGISTRY is None,
61
+ azure-functions hasn't been loaded in properly.
62
+
63
+ Tries to load the base extension.
64
+ """
65
+
66
+ func = sys.modules.get('azure.functions')
67
+
68
+ if func is None:
69
+ import azure.functions as func
70
+
71
+ set_sdk_version(func.__version__) # type: ignore
72
+
73
+ global BINDING_REGISTRY
74
+ BINDING_REGISTRY = func.get_binding_registry() # type: ignore
75
+
76
+ if BINDING_REGISTRY is None:
77
+ raise AttributeError('BINDING_REGISTRY is None. azure-functions '
78
+ 'library not found. Sys Path: %s. '
79
+ 'Sys Modules: %s. '
80
+ 'python-packages Path exists: %s.',
81
+ sys.path, sys.modules,
82
+ os.path.exists(CUSTOMER_PACKAGES_PATH))
83
+
84
+ try:
85
+ import azurefunctions.extensions.base as clients
86
+ global DEFERRED_BINDING_REGISTRY
87
+ DEFERRED_BINDING_REGISTRY = clients.get_binding_registry()
88
+ except ImportError:
89
+ logger.debug('Base extension not found. '
90
+ 'Python version: 3.%s, Sys path: %s, '
91
+ 'Sys Module: %s, python-packages Path exists: %s.',
92
+ sys.version_info.minor, sys.path,
93
+ sys.modules, os.path.exists(CUSTOMER_PACKAGES_PATH))
94
+
95
+
96
+ def get_binding(bind_name: str,
97
+ is_deferred_binding: Optional[bool] = False)\
98
+ -> object:
99
+ """
100
+ First checks if the binding is a non-deferred binding. This is
101
+ the most common case.
102
+ Second checks if the binding is a deferred binding.
103
+ If the binding is neither, it's a generic type.
104
+ """
105
+ binding = None
106
+ if binding is None and not is_deferred_binding:
107
+ binding = BINDING_REGISTRY.get(bind_name) # type: ignore
108
+ if binding is None and is_deferred_binding:
109
+ binding = DEFERRED_BINDING_REGISTRY.get(bind_name) # type: ignore
110
+ if binding is None:
111
+ binding = GenericBinding
112
+ return binding
113
+
114
+
115
+ def is_trigger_binding(bind_name: str) -> bool:
116
+ binding = get_binding(bind_name)
117
+ return binding.has_trigger_support()
118
+
119
+
120
+ def check_input_type_annotation(bind_name: str,
121
+ pytype: type,
122
+ is_deferred_binding: bool) -> bool:
123
+ global INPUT_TYPE_CHECK_OVERRIDE_MAP
124
+ if bind_name in INPUT_TYPE_CHECK_OVERRIDE_MAP:
125
+ return INPUT_TYPE_CHECK_OVERRIDE_MAP[bind_name](bind_name, pytype,
126
+ is_deferred_binding)
127
+
128
+ binding = get_binding(bind_name, is_deferred_binding)
129
+
130
+ return binding.check_input_type_annotation(pytype)
131
+
132
+
133
+ def check_output_type_annotation(bind_name: str, pytype: type) -> bool:
134
+ global OUTPUT_TYPE_CHECK_OVERRIDE_MAP
135
+ if bind_name in OUTPUT_TYPE_CHECK_OVERRIDE_MAP:
136
+ return OUTPUT_TYPE_CHECK_OVERRIDE_MAP[bind_name](bind_name, pytype)
137
+
138
+ binding = get_binding(bind_name)
139
+ return binding.check_output_type_annotation(pytype)
140
+
141
+
142
+ def has_implicit_output(bind_name: str) -> bool:
143
+ binding = get_binding(bind_name)
144
+
145
+ # Need to pass in bind_name to exempt Durable Functions
146
+ if binding is GenericBinding:
147
+ return (getattr(binding, 'has_implicit_output', lambda: False)
148
+ (bind_name)) # type: ignore
149
+
150
+ else:
151
+ # If the binding does not have metaclass of meta.InConverter
152
+ # The implicit_output does not exist
153
+ return getattr(binding, 'has_implicit_output', lambda: False)()
154
+
155
+
156
+ def from_incoming_proto(
157
+ binding: str,
158
+ pb, *,
159
+ pytype: Optional[type],
160
+ trigger_metadata: Optional[Dict[str, Any]],
161
+ function_name: str,
162
+ is_deferred_binding: Optional[bool] = False) -> Any:
163
+ binding_obj = get_binding(binding, is_deferred_binding)
164
+ if trigger_metadata:
165
+ metadata = {
166
+ k: Datum.from_typed_data(v)
167
+ for k, v in trigger_metadata.items()
168
+ }
169
+ else:
170
+ metadata = {}
171
+
172
+ pb_type = pb.WhichOneof(PB_TYPE)
173
+ if pb_type == PB_TYPE_DATA:
174
+ val = pb.data
175
+ datum = Datum.from_typed_data(val)
176
+ else:
177
+ raise TypeError('Unknown ParameterBindingType: %s' % pb_type)
178
+
179
+ try:
180
+ # if the binding is an sdk type binding
181
+ if is_deferred_binding:
182
+ return deferred_bindings_decode(binding=binding_obj,
183
+ pb=pb,
184
+ pytype=pytype,
185
+ datum=datum,
186
+ metadata=metadata,
187
+ function_name=function_name)
188
+ return binding_obj.decode(datum, trigger_metadata=metadata)
189
+ except NotImplementedError:
190
+ # Binding does not support the data.
191
+ dt = val.WhichOneof('data')
192
+ raise TypeError(
193
+ 'unable to decode incoming TypedData: '
194
+ 'unsupported combination of TypedData field %s '
195
+ 'and expected binding type %s' % (repr(dt), binding_obj))
196
+
197
+
198
+ def get_datum(binding: str, obj: Any,
199
+ pytype: Optional[type]) -> Union[Datum, None]:
200
+ """
201
+ Convert an object to a datum with the specified type.
202
+ """
203
+ binding_obj = get_binding(binding)
204
+ try:
205
+ datum = binding_obj.encode(obj, expected_type=pytype)
206
+ except NotImplementedError:
207
+ # Binding does not support the data.
208
+ raise TypeError(
209
+ 'unable to encode outgoing TypedData: '
210
+ 'unsupported type "%s" for '
211
+ 'Python type "%s"' % (binding, type(obj).__name__))
212
+ return datum
213
+
214
+
215
+ def _does_datatype_support_caching(datum: Datum):
216
+ supported_datatypes = ('bytes', 'string')
217
+ return datum.type in supported_datatypes
218
+
219
+
220
+ def to_outgoing_proto(binding: str, obj: Any, *,
221
+ pytype: Optional[type],
222
+ protos):
223
+ datum = get_datum(binding, obj, pytype)
224
+ return datum_as_proto(datum, protos) # type: ignore
225
+
226
+
227
+ def to_outgoing_param_binding(binding: str, obj: Any, *,
228
+ pytype: Optional[type],
229
+ out_name: str,
230
+ protos):
231
+ datum = get_datum(binding, obj, pytype)
232
+ # If not, send it as part of the response message over RPC
233
+ # rpc_val can be None here as we now support a None return type
234
+ rpc_val = datum_as_proto(datum, protos) # type: ignore
235
+ return protos.ParameterBinding(
236
+ name=out_name,
237
+ data=rpc_val)
238
+
239
+
240
+ def deferred_bindings_decode(binding: Any,
241
+ pb: Any, *,
242
+ pytype: Optional[type],
243
+ datum: Any,
244
+ metadata: Any,
245
+ function_name: str):
246
+ """
247
+ The extension manages a cache for clients (ie. BlobClient, ContainerClient)
248
+ That have already been created, so that the worker can reuse the
249
+ previously created type without creating a new one.
250
+
251
+ For async types, the function_name is needed as a key to differentiate.
252
+ This prevents a known SDK issue where reusing a client across functions
253
+ can lose the session context and cause an error.
254
+
255
+ """
256
+
257
+ deferred_binding_type = binding.decode(datum,
258
+ trigger_metadata=metadata,
259
+ pytype=pytype)
260
+
261
+ return deferred_binding_type
262
+
263
+
264
+ def check_deferred_bindings_enabled(param_anno: Union[type, None],
265
+ deferred_bindings_enabled: bool) -> Any:
266
+ """
267
+ Checks if deferred bindings is enabled at fx and single binding level
268
+
269
+ The first bool represents if deferred bindings is enabled at a fx level
270
+ The second represents if the current binding is deferred binding
271
+ """
272
+ if (DEFERRED_BINDING_REGISTRY is not None
273
+ and DEFERRED_BINDING_REGISTRY.check_supported_type(param_anno)):
274
+ return True, True
275
+ else:
276
+ return deferred_bindings_enabled, False
277
+
278
+
279
+ def get_deferred_raw_bindings(indexed_function, input_types):
280
+ """
281
+ Calls a method from the base extension that generates the raw bindings
282
+ for a given function. It also returns logs for that function including
283
+ the defined binding type and if deferred bindings is enabled for that
284
+ binding.
285
+ """
286
+ raw_bindings, bindings_logs = DEFERRED_BINDING_REGISTRY.get_raw_bindings(
287
+ indexed_function, input_types)
288
+ return raw_bindings, bindings_logs