Repository: Azure/azure-functions-python-worker (language: python)

Issue:
partially initialized module 'azure.functions' has no attribute 'get_binding_registry' 
**Announcement**

We have identified an issue with python 3.9 functions apps using the V2 programming model. 
Some of the invocations are failing with the following error:

```python
Failure Exception: 
AttributeError: partially initialized module 'azure.functions' has no 
attribute 'get_binding_registry' (most likely due to a circular import) 
Stack: 
  File '/azure_functions_worker/dispatcher.py', line 434, 
       in _handle__invocation_request if bindings.is_trigger_binding(pb_type_info.binding_name):
  File '/azure_functions_worker/bindings/meta.py', line 39, 
       in is_trigger_binding binding = get_binding(bind_name) 
  File '/azure_functions_worker/bindings/meta.py', line 29, 
       in get_binding registry = get_binding_registry() 
  File '/azure_functions_worker/bindings/meta.py', line 24, 
       in get_binding_registry return func.get_binding_registry()
```

**Mitigation**
Add the app setting `PYTHON_ENABLE_WORKER_EXTENSIONS` to 0 to the function app.

**Fix**
We are working on a fix and will send out an update shortly.


Discussion / hints:

Fix patch:
diff --git a/azure_functions_worker/bindings/__init__.py b/azure_functions_worker/bindings/__init__.py
index 5e81d4e8..3d326b5d 100644
--- a/azure_functions_worker/bindings/__init__.py
+++ b/azure_functions_worker/bindings/__init__.py
@@ -6,7 +6,7 @@
 from .meta import check_input_type_annotation
 from .meta import check_output_type_annotation
 from .meta import has_implicit_output
-from .meta import is_trigger_binding
+from .meta import is_trigger_binding, load_binding_registry
 from .meta import from_incoming_proto, to_outgoing_proto, \
     to_outgoing_param_binding
 from .out import Out
@@ -15,6 +15,7 @@
 __all__ = (
     'Out', 'Context',
     'is_trigger_binding',
+    'load_binding_registry',
     'check_input_type_annotation', 'check_output_type_annotation',
     'has_implicit_output',
     'from_incoming_proto', 'to_outgoing_proto', 'TraceContext', 'RetryContext',
diff --git a/azure_functions_worker/bindings/meta.py b/azure_functions_worker/bindings/meta.py
index 52867a00..3f52f8d0 100644
--- a/azure_functions_worker/bindings/meta.py
+++ b/azure_functions_worker/bindings/meta.py
@@ -12,21 +12,23 @@
 PB_TYPE = 'rpc_data'
 PB_TYPE_DATA = 'data'
 PB_TYPE_RPC_SHARED_MEMORY = 'rpc_shared_memory'
+BINDING_REGISTRY = None
 
 
-def get_binding_registry():
+def load_binding_registry() -> None:
     func = sys.modules.get('azure.functions')
 
     # If fails to acquire customer's BYO azure-functions, load the builtin
     if func is None:
         import azure.functions as func
 
-    return func.get_binding_registry()
+    global BINDING_REGISTRY
+    BINDING_REGISTRY = func.get_binding_registry()
 
 
 def get_binding(bind_name: str) -> object:
     binding = None
-    registry = get_binding_registry()
+    registry = BINDING_REGISTRY
     if registry is not None:
         binding = registry.get(bind_name)
     if binding is None:
diff --git a/azure_functions_worker/dispatcher.py b/azure_functions_worker/dispatcher.py
index b57aa432..d05d1839 100644
--- a/azure_functions_worker/dispatcher.py
+++ b/azure_functions_worker/dispatcher.py
@@ -292,6 +292,10 @@ async def _handle__worker_init_request(self, request):
                 and is_envvar_true(PYTHON_LOAD_FUNCTIONS_INIT):
             import azure.functions  # NoQA
 
+        # loading bindings registry and saving results to a static
+        # dictionary which will be later used in the invocation request
+        bindings.load_binding_registry()
+
         return protos.StreamingMessage(
             request_id=self.request_id,
             worker_init_response=protos.WorkerInitResponse(
@@ -567,6 +571,10 @@ async def _handle__function_environment_reload_request(self, request):
                 func_env_reload_request.function_app_directory
             )
 
+            # calling load_binding_registry again since the
+            # reload_customer_libraries call clears the registry
+            bindings.load_binding_registry()
+
             # Change function app directory
             if getattr(func_env_reload_request,
                        'function_app_directory', None):


Test patch:
diff --git a/tests/unittests/test_mock_blob_shared_memory_functions.py b/tests/unittests/test_mock_blob_shared_memory_functions.py
index 0fdd1faf..f3cee34a 100644
--- a/tests/unittests/test_mock_blob_shared_memory_functions.py
+++ b/tests/unittests/test_mock_blob_shared_memory_functions.py
@@ -54,6 +54,7 @@ async def test_binary_blob_write_function(self):
         func_name = 'put_blob_as_bytes_return_http_response'
         async with testutils.start_mockhost(script_root=self.blob_funcs_dir) \
                 as host:
+            await host.init_worker("4.17.1")
             await host.load_function(func_name)
 
             content_size = consts.MIN_BYTES_FOR_SHARED_MEM_TRANSFER + 10
@@ -133,6 +134,7 @@ async def test_str_blob_read_function(self):
         func_name = 'get_blob_as_str_return_http_response'
         async with testutils.start_mockhost(script_root=self.blob_funcs_dir) \
                 as host:
+            await host.init_worker("4.17.1")
             await host.load_function(func_name)
 
             # Write binary content into shared memory
@@ -199,6 +201,7 @@ async def test_str_blob_write_function(self):
         func_name = 'put_blob_as_str_return_http_response'
         async with testutils.start_mockhost(script_root=self.blob_funcs_dir) \
                 as host:
+            await host.init_worker("4.17.1")
             await host.load_function(func_name)
 
             content_size = consts.MIN_BYTES_FOR_SHARED_MEM_TRANSFER + 10
@@ -279,6 +282,7 @@ async def test_close_shared_memory_maps(self):
         func_name = 'put_blob_as_bytes_return_http_response'
         async with testutils.start_mockhost(script_root=self.blob_funcs_dir) \
                 as host:
+            await host.init_worker("4.17.1")
             await host.load_function(func_name)
 
             content_size = consts.MIN_BYTES_FOR_SHARED_MEM_TRANSFER + 10
@@ -334,6 +338,7 @@ async def test_shared_memory_not_used_with_small_output(self):
         func_name = 'put_blob_as_bytes_return_http_response'
         async with testutils.start_mockhost(script_root=self.blob_funcs_dir) \
                 as host:
+            await host.init_worker("4.17.1")
             await host.load_function(func_name)
 
             content_size = consts.MIN_BYTES_FOR_SHARED_MEM_TRANSFER - 10
@@ -372,6 +377,7 @@ async def test_multiple_input_output_blobs(self):
         func_name = 'put_get_multiple_blobs_as_bytes_return_http_response'
         async with testutils.start_mockhost(script_root=self.blob_funcs_dir) \
                 as host:
+            await host.init_worker("4.17.1")
             await host.load_function(func_name)
 
             # Input 1
@@ -514,6 +520,7 @@ async def _test_binary_blob_read_function(self, func_name):
         """
         async with testutils.start_mockhost(script_root=self.blob_funcs_dir) \
                 as host:
+            await host.init_worker("4.17.1")
             await host.load_function(func_name)
 
             # Write binary content into shared memory
diff --git a/tests/unittests/test_mock_durable_functions.py b/tests/unittests/test_mock_durable_functions.py
index d362d805..daeef65a 100644
--- a/tests/unittests/test_mock_durable_functions.py
+++ b/tests/unittests/test_mock_durable_functions.py
@@ -11,6 +11,7 @@ async def test_mock_activity_trigger(self):
         async with testutils.start_mockhost(
                 script_root=self.durable_functions_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('activity_trigger')
 
             self.assertEqual(r.response.function_id, func_id)
@@ -40,6 +41,7 @@ async def test_mock_activity_trigger_no_anno(self):
         async with testutils.start_mockhost(
                 script_root=self.durable_functions_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('activity_trigger_no_anno')
 
             self.assertEqual(r.response.function_id, func_id)
@@ -69,6 +71,7 @@ async def test_mock_activity_trigger_dict(self):
         async with testutils.start_mockhost(
                 script_root=self.durable_functions_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('activity_trigger_dict')
 
             self.assertEqual(r.response.function_id, func_id)
@@ -98,6 +101,7 @@ async def test_mock_activity_trigger_int_to_float(self):
         async with testutils.start_mockhost(
                 script_root=self.durable_functions_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function(
                 'activity_trigger_int_to_float')
 
@@ -128,6 +132,7 @@ async def test_mock_orchestration_trigger(self):
         async with testutils.start_mockhost(
                 script_root=self.durable_functions_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('orchestration_trigger')
 
             self.assertEqual(r.response.function_id, func_id)
diff --git a/tests/unittests/test_mock_eventhub_functions.py b/tests/unittests/test_mock_eventhub_functions.py
index 3c4e22ba..8fa544e8 100644
--- a/tests/unittests/test_mock_eventhub_functions.py
+++ b/tests/unittests/test_mock_eventhub_functions.py
@@ -13,6 +13,7 @@ async def test_mock_eventhub_trigger_iot(self):
         async with testutils.start_mockhost(
                 script_root=self.mock_funcs_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('eventhub_trigger_iot')
 
             self.assertEqual(r.response.function_id, func_id)
@@ -56,6 +57,7 @@ async def test_mock_eventhub_cardinality_one(self):
         async with testutils.start_mockhost(
                 script_root=self.mock_funcs_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('eventhub_cardinality_one')
             self.assertEqual(r.response.function_id, func_id)
             self.assertEqual(r.response.result.status,
@@ -90,6 +92,7 @@ async def test_mock_eventhub_cardinality_one_bad_annotation(self):
         async with testutils.start_mockhost(
                 script_root=self.mock_funcs_dir) as host:
 
+            await host.init_worker("4.17.1")
             # This suppose to fail since the event should not be int
             func_id, r = await host.load_function(
                 'eventhub_cardinality_one_bad_anno'
@@ -102,6 +105,8 @@ async def test_mock_eventhub_cardinality_many(self):
         async with testutils.start_mockhost(
                 script_root=self.mock_funcs_dir) as host:
 
+            await host.init_worker("4.17.1")
+
             func_id, r = await host.load_function('eventhub_cardinality_many')
             self.assertEqual(r.response.function_id, func_id)
             self.assertEqual(r.response.result.status,
@@ -139,6 +144,8 @@ async def test_mock_eventhub_cardinality_many_bad_annotation(self):
                 script_root=self.mock_funcs_dir) as host:
 
             # This suppose to fail since the event should not be List[str]
+            await host.init_worker("4.17.1")
+
             func_id, r = await host.load_function(
                 'eventhub_cardinality_many_bad_anno'
             )
diff --git a/tests/unittests/test_mock_generic_functions.py b/tests/unittests/test_mock_generic_functions.py
index 6a72a238..32004850 100644
--- a/tests/unittests/test_mock_generic_functions.py
+++ b/tests/unittests/test_mock_generic_functions.py
@@ -11,6 +11,7 @@ async def test_mock_generic_as_str(self):
         async with testutils.start_mockhost(
                 script_root=self.generic_funcs_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('foobar_as_str')
 
             self.assertEqual(r.response.function_id, func_id)
@@ -38,6 +39,7 @@ async def test_mock_generic_as_bytes(self):
         async with testutils.start_mockhost(
                 script_root=self.generic_funcs_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('foobar_as_bytes')
 
             self.assertEqual(r.response.function_id, func_id)
@@ -65,6 +67,7 @@ async def test_mock_generic_as_str_no_anno(self):
         async with testutils.start_mockhost(
                 script_root=self.generic_funcs_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('foobar_as_str_no_anno')
 
             self.assertEqual(r.response.function_id, func_id)
@@ -92,6 +95,7 @@ async def test_mock_generic_as_bytes_no_anno(self):
         async with testutils.start_mockhost(
                 script_root=self.generic_funcs_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('foobar_as_bytes_no_anno')
 
             self.assertEqual(r.response.function_id, func_id)
@@ -119,6 +123,7 @@ async def test_mock_generic_should_not_support_implicit_output(self):
         async with testutils.start_mockhost(
                 script_root=self.generic_funcs_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('foobar_implicit_output')
 
             self.assertEqual(r.response.function_id, func_id)
@@ -144,6 +149,7 @@ async def test_mock_generic_should_support_without_datatype(self):
         async with testutils.start_mockhost(
                 script_root=self.generic_funcs_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('foobar_with_no_datatype')
 
             self.assertEqual(r.response.function_id, func_id)
diff --git a/tests/unittests/test_mock_http_functions.py b/tests/unittests/test_mock_http_functions.py
index 01bf2e76..f7538065 100644
--- a/tests/unittests/test_mock_http_functions.py
+++ b/tests/unittests/test_mock_http_functions.py
@@ -8,6 +8,8 @@ class TestMockHost(testutils.AsyncTestCase):
 
     async def test_call_sync_function_check_logs(self):
         async with testutils.start_mockhost() as host:
+
+            await host.init_worker("4.17.1")
             await host.load_function('sync_logging')
 
             invoke_id, r = await host.invoke_function(
@@ -36,6 +38,8 @@ async def test_call_sync_function_check_logs(self):
 
     async def test_call_async_function_check_logs(self):
         async with testutils.start_mockhost() as host:
+
+            await host.init_worker("4.17.1")
             await host.load_function('async_logging')
 
             invoke_id, r = await host.invoke_function(
@@ -77,6 +81,7 @@ async def test_handles_unsupported_messages_gracefully(self):
                 protos.StreamingMessage(
                     worker_heartbeat=protos.WorkerHeartbeat()))
 
+            await host.init_worker("4.17.1")
             _, r = await host.load_function('return_out')
             self.assertEqual(r.response.result.status,
                              protos.StatusResult.Success)
diff --git a/tests/unittests/test_mock_log_filtering_functions.py b/tests/unittests/test_mock_log_filtering_functions.py
index ada84e63..81e676c2 100644
--- a/tests/unittests/test_mock_log_filtering_functions.py
+++ b/tests/unittests/test_mock_log_filtering_functions.py
@@ -18,6 +18,7 @@ async def test_root_logger_should_be_customer_log(self):
             'azure_functions_worker.dispatcher.is_system_log_category'
         ) as islc_mock:
             async with testutils.start_mockhost(script_root=self.dir) as host:
+                await host.init_worker("4.17.1")
                 await host.load_function('debug_logging')
                 await self._invoke_function(host, 'debug_logging')
 
@@ -33,6 +34,7 @@ async def test_customer_logging_should_not_be_system_log(self):
             'azure_functions_worker.dispatcher.is_system_log_category'
         ) as islc_mock:
             async with testutils.start_mockhost(script_root=self.dir) as host:
+                await host.init_worker("4.17.1")
                 await host.load_function('debug_user_logging')
                 await self._invoke_function(host, 'debug_user_logging')
 
@@ -48,6 +50,7 @@ async def test_sdk_logger_should_be_system_log(self):
             'azure_functions_worker.dispatcher.is_system_log_category'
         ) as islc_mock:
             async with testutils.start_mockhost(script_root=self.dir) as host:
+                await host.init_worker("4.17.1")
                 await host.load_function('sdk_logging')
                 await self._invoke_function(host, 'sdk_logging')
 
@@ -65,6 +68,7 @@ async def test_sdk_submodule_logger_should_be_system_log(self):
             'azure_functions_worker.dispatcher.is_system_log_category'
         ) as islc_mock:
             async with testutils.start_mockhost(script_root=self.dir) as host:
+                await host.init_worker("4.17.1")
                 await host.load_function('sdk_submodule_logging')
                 await self._invoke_function(host, 'sdk_submodule_logging')
 
diff --git a/tests/unittests/test_mock_timer_functions.py b/tests/unittests/test_mock_timer_functions.py
index c3203df7..7bdda8ee 100644
--- a/tests/unittests/test_mock_timer_functions.py
+++ b/tests/unittests/test_mock_timer_functions.py
@@ -13,6 +13,7 @@ async def test_mock_timer__return_pastdue(self):
         async with testutils.start_mockhost(
                 script_root=self.timer_funcs_dir) as host:
 
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('return_pastdue')
 
             self.assertEqual(r.response.function_id, func_id)
@@ -44,7 +45,7 @@ async def call_and_check(due: bool):
     async def test_mock_timer__user_event_loop(self):
         async with testutils.start_mockhost(
                 script_root=self.timer_funcs_dir) as host:
-
+            await host.init_worker("4.17.1")
             func_id, r = await host.load_function('user_event_loop_timer')
 
             self.assertEqual(r.response.function_id, func_id)