azext_iot/deviceupdate/providers/loaders.py (78 lines of code) (raw):
# coding=utf-8
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
import os
import sys
import importlib
from azext_iot.common.utility import ensure_azure_namespace_path
from azext_iot.constants import INTERNAL_AZURE_CORE_NAMESPACE
from knack.log import get_logger
from typing import List
logger = get_logger(__name__)
def reload_modules() -> None:
"""
Incremental fix for azure namespace package usage.
Use to ensure shared azure namespace packages are being loaded from the
azure-iot extension directory vs base Azure CLI. This is particularly important
when both Azure CLI and the azure-iot extension have the same namespace package
dependency but different version requirements.
This needs to be executed before azure.* modules are imported.
"""
from azure.cli.core.extension import get_extension_path
from azext_iot.constants import EXTENSION_NAME
ext_path = get_extension_path(EXTENSION_NAME)
if not ext_path:
return
ext_azure_dir = os.path.join(ext_path, "azure")
if not os.path.isdir(ext_azure_dir):
return
ensure_azure_namespace_path()
def needs_reload(module_name: str) -> bool:
if module_name in sys.modules:
target_module = sys.modules.get(module_name)
_reload = True
if hasattr(target_module, "__path__"):
for path in target_module.__path__:
if path.startswith(ext_azure_dir):
_reload = False
break
return _reload
return False
def reload_module_state(module_name: str, remove_prereq: List[str] = None):
if remove_prereq:
for prereq in remove_prereq:
if prereq in sys.modules:
del sys.modules[prereq]
if module_name in sys.modules:
importlib.reload(sys.modules[module_name])
# This structure defines the target module for reload, and any prereq removals for a succesful reload.
mods_for_reload = {
"msrest": [],
"azure.core": [],
"azure.core.utils": ["azure.core.utils._utils"],
"azure.mgmt.core": [],
}
# Import modules with best attempt
for mod in mods_for_reload:
try:
if needs_reload(mod):
reload_module_state(mod, mods_for_reload[mod])
except Exception as e:
logger.warning("Failed to reload module: %s, error: %s", mod, str(e))
try:
init_internal_azure_core(azure_path=ext_azure_dir)
except Exception as e:
logger.warning("Failed to build internal module cache, error: %s", str(e))
def init_internal_azure_core(azure_path: str, namespace: str = INTERNAL_AZURE_CORE_NAMESPACE):
root_spec = importlib.machinery.PathFinder.find_spec("azure.core", [azure_path])
root_module = importlib.util.module_from_spec(root_spec)
root_spec.loader.exec_module(root_module)
sys.modules[namespace] = root_module
# Child-parent mapping. As of Py 3.7 insertion order for dictionaries are guaranteed.
todo_submodule_map = {
"azure.core.exceptions": {
"path": os.path.join(azure_path, "core"),
"parent": root_module,
"namespace": f"{INTERNAL_AZURE_CORE_NAMESPACE}.exceptions",
# "module": will be set at runtime.
},
}
for todo_submodule in todo_submodule_map:
todo_submodule_spec = importlib.machinery.PathFinder.find_spec(
todo_submodule, path=[todo_submodule_map[todo_submodule]["path"]], target=root_module
)
todo_submodule_module = importlib.util.module_from_spec(todo_submodule_spec)
todo_submodule_spec.loader.exec_module(todo_submodule_module)
_, _, child_seg = todo_submodule.rpartition(".")
parent = todo_submodule_map[todo_submodule].get("parent")
if parent:
# If the parent is str, then look up the module set at runtime.
if isinstance(parent, str):
parent = todo_submodule_map[parent]["module"]
setattr(parent, child_seg, todo_submodule_module)
todo_submodule_map[todo_submodule]["module"] = todo_submodule_module
sys.modules[todo_submodule_map[todo_submodule]["namespace"]] = todo_submodule_module