summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2026-08-30 13:40:39 +0200
committerChristian Breunig <christian@breunig.cc>2026-08-30 13:40:39 +0200
commit6c80440b0711b8f1e0a713df4f097ab1902163cd (patch)
treecb6bb5f1afd35769a7f60dcbbfec060ea4366439 /python
parentb9fd27b511d22554f294e87e9349456b3418bb2b (diff)
downloadvyos-1x-6c80440b0711b8f1e0a713df4f097ab1902163cd.tar.gz
vyos-1x-6c80440b0711b8f1e0a713df4f097ab1902163cd.zip
vyos: T9261: defer heavy imports out of the Config/ConfigSession path
Importing vyos.config dragged in the whole HTTP and crypto stack. vyos.version imported requests and urllib3 for get_remote_version(), plus vyos.system.image for a single call in get_full_version_data() - and that reaches vyos.system.grub and vyos.template, so every consumer paid for jinja2 as well. vyconf_session imported vyos.remote, and therefore paramiko and cryptography, for a get_config_file() used in two methods. All of these move to their call sites. vyos/utils/__init__.py eagerly imported all 18 submodules, so 'from vyos.utils.dict import get_sub_dict' also loaded auth, network and process. It now resolves them on first attribute access via a module-level __getattr__. No caller relied on the eager import; the common 'from vyos.utils.<sub> import <name>' form is unaffected. import vyos.config 129.6 -> 47.7 ms (-63%, 2.7x), 575 -> 194 modules import vyos.ifconfig 205.0 -> 149.7 ms (-27%, 1.4x) This is per process, so it is paid again by every conf-mode script not handled in-daemon by vyos-configd, every op-mode script, and every smoketest.
Diffstat (limited to 'python')
-rw-r--r--python/vyos/utils/__init__.py58
-rw-r--r--python/vyos/version.py11
-rw-r--r--python/vyos/vyconf_session.py11
3 files changed, 58 insertions, 22 deletions
diff --git a/python/vyos/utils/__init__.py b/python/vyos/utils/__init__.py
index 280cde17f..2d934b75f 100644
--- a/python/vyos/utils/__init__.py
+++ b/python/vyos/utils/__init__.py
@@ -13,21 +13,43 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
-from vyos.utils import assertion
-from vyos.utils import auth
-from vyos.utils import boot
-from vyos.utils import commit
-from vyos.utils import configfs
-from vyos.utils import convert
-from vyos.utils import cpu
-from vyos.utils import dict
-from vyos.utils import file
-from vyos.utils import io
-from vyos.utils import kernel
-from vyos.utils import list
-from vyos.utils import locking
-from vyos.utils import misc
-from vyos.utils import network
-from vyos.utils import permission
-from vyos.utils import process
-from vyos.utils import system
+import importlib
+
+__all__ = [
+ 'assertion',
+ 'auth',
+ 'boot',
+ 'commit',
+ 'configfs',
+ 'convert',
+ 'cpu',
+ 'dict',
+ 'file',
+ 'io',
+ 'kernel',
+ 'list',
+ 'locking',
+ 'misc',
+ 'network',
+ 'permission',
+ 'process',
+ 'system',
+]
+
+
+def __getattr__(name):
+ """Import submodules on first access (PEP 562).
+
+ Importing them eagerly pulled the whole set into every consumer of
+ 'from vyos.utils.<sub> import <name>', which is a measurable cost on the
+ conf-mode script path where each script is a fresh interpreter.
+ """
+ if name not in __all__:
+ raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
+ mod = importlib.import_module(f'{__name__}.{name}')
+ globals()[name] = mod
+ return mod
+
+
+def __dir__():
+ return sorted(list(globals()) + __all__)
diff --git a/python/vyos/version.py b/python/vyos/version.py
index 4ba44f166..4288a26ff 100644
--- a/python/vyos/version.py
+++ b/python/vyos/version.py
@@ -31,10 +31,7 @@ Example of the version data dict::
import os
-import requests
import vyos.defaults
-from vyos.system.image import is_live_boot
-from urllib3.util import retry
from vyos.utils.file import read_file
from vyos.utils.file import read_json
@@ -70,6 +67,11 @@ def get_version(fname=version_file):
def get_full_version_data(fname=version_file):
+ # Imported here rather than at module scope: vyos.system.image pulls in
+ # vyos.system.grub -> vyos.template -> jinja2, which every consumer of
+ # vyos.version would otherwise pay for on import.
+ from vyos.system.image import is_live_boot
+
version_data = get_version_data(fname)
# Get system architecture (well, kernel architecture rather)
@@ -126,6 +128,9 @@ def get_remote_version(url):
]
"""
+ import requests
+ from urllib3.util import retry
+
headers = {}
session = requests.Session()
diff --git a/python/vyos/vyconf_session.py b/python/vyos/vyconf_session.py
index 91e51338c..932c87964 100644
--- a/python/vyos/vyconf_session.py
+++ b/python/vyos/vyconf_session.py
@@ -31,7 +31,6 @@ from vyos.proto.vyconf_proto import Errnum
from vyos.utils.commit import acquire_commit_lock_file
from vyos.utils.commit import release_commit_lock_file
from vyos.utils.commit import call_commit_hooks
-from vyos.remote import get_config_file
class VyconfSessionError(Exception):
@@ -246,6 +245,11 @@ class VyconfSession:
self, file_name: str, migrate: bool = False, cached: bool = False
) -> tuple[str, int]:
# pylint: disable=consider-using-with
+ # Imported here rather than at module scope: vyos.remote pulls in
+ # paramiko and requests, which every consumer of vyos.config would
+ # otherwise pay for on import.
+ from vyos.remote import get_config_file
+
file_path = tempfile.NamedTemporaryFile(delete=False).name
err = get_config_file(file_name, file_path)
if err:
@@ -275,6 +279,11 @@ class VyconfSession:
self, file_name: str, migrate: bool = False, destructive: bool = False
) -> tuple[str, int]:
# pylint: disable=consider-using-with
+ # Imported here rather than at module scope: vyos.remote pulls in
+ # paramiko and requests, which every consumer of vyos.config would
+ # otherwise pay for on import.
+ from vyos.remote import get_config_file
+
file_path = tempfile.NamedTemporaryFile(delete=False).name
err = get_config_file(file_name, file_path)
if err: