[sonic-py-common] Add 'general' module with load_module_from_source() function (#7167)

#### Why I did it

To eliminate the need to write duplicate code in order to import a Python module from a source file.

#### How I did it

Add `general` module to sonic-py-common, which contains a `load_module_from_source()` function which supports both Python 2 and 3.

Call this new function in:
- sonic-ctrmgrd/tests/container_test.py
- sonic-ctrmgrd/tests/ctrmgr_tools_test.py
- sonic-host-services/tests/determine-reboot-cause_test.py
- sonic-host-services/tests/hostcfgd/hostcfgd_test.py
- sonic-host-services/tests/procdockerstatsd_test.py
- sonic-py-common/sonic_py_common/daemon_base.py
This commit is contained in:
Joe LeVeque 2021-04-08 08:29:28 -07:00 committed by GitHub
parent 42d22f4953
commit ee1383791c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 44 additions and 62 deletions

View File

@ -24,6 +24,7 @@ setup(
tests_require=[
'pytest',
'pytest-cov',
'sonic-py-common',
],
install_requires=['netaddr', 'pyyaml'],
license="GNU General Public License v3",

View File

@ -1,6 +1,4 @@
import copy
import importlib.machinery
import importlib.util
import json
import os
import subprocess
@ -655,13 +653,3 @@ def create_remote_ctr_config_json():
s.write(str_conf)
return fname
def load_mod_from_file(modname, fpath):
spec = importlib.util.spec_from_loader(modname,
importlib.machinery.SourceFileLoader(modname, fpath))
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
sys.modules[modname] = mod
return mod

View File

@ -2,12 +2,14 @@ import os
from unittest.mock import MagicMock, patch
import pytest
from sonic_py_common.general import load_module_from_source
from . import common_test
common_test.load_mod_from_file("docker",
load_module_from_source("docker",
os.path.join(os.path.dirname(os.path.realpath(__file__)), "mock_docker.py"))
container = common_test.load_mod_from_file("container",
container = load_module_from_source("container",
os.path.join(os.path.dirname(os.path.realpath(__file__)), "../ctrmgr/container"))

View File

@ -3,10 +3,11 @@ import sys
from unittest.mock import MagicMock, patch
import pytest
from sonic_py_common.general import load_module_from_source
from . import common_test
common_test.load_mod_from_file("docker",
load_module_from_source("docker",
os.path.join(os.path.dirname(os.path.realpath(__file__)), "mock_docker.py"))
sys.path.append("ctrmgr")

View File

@ -35,6 +35,7 @@ setup(
],
tests_require = [
'pytest',
'sonic-py-common'
],
classifiers = [
'Development Status :: 3 - Alpha',

View File

@ -1,10 +1,9 @@
import importlib.machinery
import importlib.util
import sys
import os
import pytest
import swsssdk
from sonic_py_common.general import load_module_from_source
# TODO: Remove this if/else block once we no longer support Python 2
if sys.version_info.major == 3:
@ -31,11 +30,7 @@ sys.path.insert(0, modules_path)
# Load the file under test
determine_reboot_cause_path = os.path.join(scripts_path, 'determine-reboot-cause')
loader = importlib.machinery.SourceFileLoader('determine_reboot_cause', determine_reboot_cause_path)
spec = importlib.util.spec_from_loader(loader.name, loader)
determine_reboot_cause = importlib.util.module_from_spec(spec)
loader.exec_module(determine_reboot_cause)
sys.modules['determine_reboot_cause'] = determine_reboot_cause
determine_reboot_cause = load_module_from_source('determine_reboot_cause', determine_reboot_cause_path)
PROC_CMDLINE_CONTENTS = """\

View File

@ -1,13 +1,13 @@
import importlib.machinery
import importlib.util
import os
import sys
import swsssdk
from parameterized import parameterized
from sonic_py_common.general import load_module_from_source
from unittest import TestCase, mock
from tests.hostcfgd.test_vectors import HOSTCFGD_TEST_VECTOR
from tests.hostcfgd.mock_configdb import MockConfigDb
from .test_vectors import HOSTCFGD_TEST_VECTOR
from .mock_configdb import MockConfigDb
swsssdk.ConfigDBConnector = MockConfigDb
@ -18,11 +18,7 @@ sys.path.insert(0, modules_path)
# Load the file under test
hostcfgd_path = os.path.join(scripts_path, 'hostcfgd')
loader = importlib.machinery.SourceFileLoader('hostcfgd', hostcfgd_path)
spec = importlib.util.spec_from_loader(loader.name, loader)
hostcfgd = importlib.util.module_from_spec(spec)
loader.exec_module(hostcfgd)
sys.modules['hostcfgd'] = hostcfgd
hostcfgd = load_module_from_source('hostcfgd', hostcfgd_path)
class TestHostcfgd(TestCase):

View File

@ -1,10 +1,9 @@
import importlib.machinery
import importlib.util
import sys
import os
import pytest
import swsssdk
from sonic_py_common.general import load_module_from_source
from .mock_connector import MockConnector
@ -17,11 +16,7 @@ sys.path.insert(0, modules_path)
# Load the file under test
procdockerstatsd_path = os.path.join(scripts_path, 'procdockerstatsd')
loader = importlib.machinery.SourceFileLoader('procdockerstatsd', procdockerstatsd_path)
spec = importlib.util.spec_from_loader(loader.name, loader)
procdockerstatsd = importlib.util.module_from_spec(spec)
loader.exec_module(procdockerstatsd)
sys.modules['procdockerstatsd'] = procdockerstatsd
procdockerstatsd = load_module_from_source('procdockerstatsd', procdockerstatsd_path)
class TestProcDockerStatsDaemon(object):
def test_convert_to_bytes(self):

View File

@ -2,6 +2,7 @@ import signal
import sys
from . import device_info
from .general import load_module_from_source
from .logger import Logger
#
@ -25,29 +26,6 @@ def db_connect(db_name, namespace=EMPTY_NAMESPACE):
return swsscommon.DBConnector(db_name, REDIS_TIMEOUT_MSECS, True, namespace)
# TODO: Consider moving this logic out of daemon_base and into antoher file
# so that it can be used by non-daemons. We can simply call that function here
# to retain backward compatibility.
def _load_module_from_file(module_name, file_path):
module = None
# TODO: Remove this check once we no longer support Python 2
if sys.version_info.major == 3:
import importlib.machinery
import importlib.util
loader = importlib.machinery.SourceFileLoader(module_name, file_path)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
else:
import imp
module = imp.load_source(module_name, file_path)
sys.modules[module_name] = module
return module
#
# DaemonBase ===================================================================
#
@ -92,7 +70,7 @@ class DaemonBase(Logger):
try:
module_file = "/".join([platform_path, "plugins", module_name + ".py"])
module = _load_module_from_file(module_name, module_file)
module = load_module_from_source(module_name, module_file)
except IOError as e:
raise IOError("Failed to load platform module '%s': %s" % (module_name, str(e)))

View File

@ -0,0 +1,25 @@
import sys
def load_module_from_source(module_name, file_path):
"""
This function will load the Python source file specified by <file_path>
as a module named <module_name> and return an instance of the module
"""
module = None
# TODO: Remove this check once we no longer support Python 2
if sys.version_info.major == 3:
import importlib.machinery
import importlib.util
loader = importlib.machinery.SourceFileLoader(module_name, file_path)
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
else:
import imp
module = imp.load_source(module_name, file_path)
sys.modules[module_name] = module
return module