Skip to content

Commit b33355a

Browse files
committed
Use lazy loading for packages
faster initialization (paramiko import is extremely expensive)
1 parent 7ad0322 commit b33355a

File tree

9 files changed

+157
-49
lines changed

9 files changed

+157
-49
lines changed

exec_helpers/__init__.py

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,27 @@
1616

1717
from __future__ import annotations
1818

19+
# Standard Library
20+
import importlib
21+
import typing
22+
import warnings
23+
1924
# Local Implementation
20-
from . import async_api
21-
from ._helpers import mask_command
22-
from ._ssh_helpers import HostsSSHConfigs
23-
from ._ssh_helpers import SSHConfig
24-
from .api import ExecHelper
2525
from .exceptions import CalledProcessError
2626
from .exceptions import ExecCalledProcessError
2727
from .exceptions import ExecHelperError
2828
from .exceptions import ExecHelperNoKillError
2929
from .exceptions import ExecHelperTimeoutError
3030
from .exceptions import ParallelCallExceptionsError
3131
from .exceptions import ParallelCallProcessError
32-
from .exec_result import ExecResult
33-
from .proc_enums import ExitCodes
34-
from .ssh import SSHClient
35-
from .ssh_auth import SSHAuth
36-
from .subprocess import Subprocess # nosec # Expected
3732

3833
try:
3934
# Local Implementation
4035
from ._version import version as __version__
4136
except ImportError:
4237
pass
4338

39+
# noinspection PyUnresolvedReferences
4440
__all__ = (
4541
"ExecHelperError",
4642
"ExecCalledProcessError",
@@ -49,18 +45,65 @@
4945
"ParallelCallProcessError",
5046
"ExecHelperNoKillError",
5147
"ExecHelperTimeoutError",
48+
# pylint: disable=undefined-all-variable
49+
# lazy load
50+
# API
51+
"async_api",
52+
"ExitCodes",
53+
"ExecResult",
5254
"ExecHelper",
53-
"SSHClient",
5455
"mask_command",
56+
# Expensive
57+
"Subprocess",
58+
"SSHClient",
5559
"SSHAuth",
5660
"SSHConfig",
5761
"HostsSSHConfigs",
58-
"Subprocess",
59-
"ExitCodes",
60-
"ExecResult",
61-
"async_api",
62+
# deprecated
63+
"ParallelCallExceptions",
6264
)
6365

66+
__locals: typing.Dict[str, typing.Any] = locals() # use mutable access for pure lazy loading
67+
68+
__lazy_load_modules: typing.Sequence[str] = ("async_api",)
69+
70+
__lazy_load_parent_modules: typing.Dict[str, str] = {
71+
"HostsSSHConfigs": "_ssh_helpers",
72+
"SSHConfig": "_ssh_helpers",
73+
"SSHClient": "ssh",
74+
"SSHAuth": "ssh_auth",
75+
"Subprocess": "subprocess",
76+
# API
77+
"ExitCodes": "proc_enums",
78+
"ExecResult": "exec_result",
79+
"ExecHelper": "api",
80+
"mask_command": "_helpers",
81+
"ParallelCallExceptions": "exceptions",
82+
}
83+
84+
_deprecated: typing.Dict[str, str] = {"ParallelCallExceptions": "ParallelCallExceptionsError"}
85+
86+
87+
def __getattr__(name: str) -> typing.Any:
88+
"""Get attributes lazy.
89+
90+
:return: attribute by name
91+
:raises AttributeError: attribute is not defined for lazy load
92+
"""
93+
if name in _deprecated:
94+
warnings.warn(f"{name} is deprecated in favor of {_deprecated[name]}", DeprecationWarning)
95+
if name in __lazy_load_modules:
96+
mod = importlib.import_module(f"{__package__}.{name}")
97+
__locals[name] = mod
98+
return mod
99+
if name in __lazy_load_parent_modules:
100+
mod = importlib.import_module(f"{__package__}.{__lazy_load_parent_modules[name]}")
101+
obj = getattr(mod, name)
102+
__locals[name] = obj
103+
return obj
104+
raise AttributeError(f"{name} not found in {__package__}")
105+
106+
64107
__author__ = "Alexey Stepanov"
65108
__author_email__ = "[email protected]"
66109
__maintainers__ = {

exec_helpers/_ssh_base.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,6 @@
4242
from exec_helpers import exec_result
4343
from exec_helpers import proc_enums
4444
from exec_helpers import ssh_auth
45-
from exec_helpers.api import CalledProcessErrorSubClassT
46-
from exec_helpers.api import CommandT
47-
from exec_helpers.api import ErrorInfoT
48-
from exec_helpers.api import ExpectedExitCodesT
49-
from exec_helpers.api import LogMaskReT
50-
from exec_helpers.api import OptionalStdinT
51-
from exec_helpers.api import OptionalTimeoutT
52-
from exec_helpers.proc_enums import ExitCodeT
5345

5446
# Local Implementation
5547
from . import _helpers
@@ -62,6 +54,16 @@
6254
import socket
6355
import types
6456

57+
# Package Implementation
58+
from exec_helpers.api import CalledProcessErrorSubClassT
59+
from exec_helpers.api import CommandT
60+
from exec_helpers.api import ErrorInfoT
61+
from exec_helpers.api import ExpectedExitCodesT
62+
from exec_helpers.api import LogMaskReT
63+
from exec_helpers.api import OptionalStdinT
64+
from exec_helpers.api import OptionalTimeoutT
65+
from exec_helpers.proc_enums import ExitCodeT
66+
6567
__all__ = ("SSHClientBase", "SshExecuteAsyncResult", "SupportPathT")
6668

6769
KeepAlivePeriodT = typing.Union[int, bool]

exec_helpers/async_api/__init__.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,51 @@
1717
.. versionadded:: 3.0.0
1818
"""
1919

20-
__all__ = ("ExecHelper", "ExecResult", "Subprocess")
20+
# noinspection PyUnresolvedReferences
21+
__all__ = (
22+
# pylint: disable=undefined-all-variable
23+
# lazy load
24+
# API
25+
"ExecHelper",
26+
"ExecResult",
27+
# Expensive
28+
"Subprocess",
29+
)
2130

22-
# Local Implementation
23-
from .api import ExecHelper
24-
from .exec_result import ExecResult
25-
from .subprocess import Subprocess # nosec # Expected
31+
# Standard Library
32+
import importlib
33+
import typing
34+
import warnings
35+
36+
__locals: typing.Dict[str, typing.Any] = locals() # use mutable access for pure lazy loading
37+
38+
__lazy_load_modules: typing.Sequence[str] = ()
39+
40+
__lazy_load_parent_modules: typing.Dict[str, str] = {
41+
"Subprocess": "subprocess",
42+
# API
43+
"ExecResult": "exec_result",
44+
"ExecHelper": "api",
45+
}
46+
47+
_deprecated: typing.Dict[str, str] = {}
48+
49+
50+
def __getattr__(name: str) -> typing.Any:
51+
"""Get attributes lazy.
52+
53+
:return: attribute by name
54+
:raises AttributeError: attribute is not defined for lazy load
55+
"""
56+
if name in _deprecated:
57+
warnings.warn(f"{name} is deprecated in favor of {_deprecated[name]}", DeprecationWarning)
58+
if name in __lazy_load_modules:
59+
mod = importlib.import_module(f"{__package__}.{name}")
60+
__locals[name] = mod
61+
return mod
62+
if name in __lazy_load_parent_modules:
63+
mod = importlib.import_module(f"{__package__}.{__lazy_load_parent_modules[name]}")
64+
obj = getattr(mod, name)
65+
__locals[name] = obj
66+
return obj
67+
raise AttributeError(f"{name} not found in {__package__}")

exec_helpers/async_api/api.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
from exec_helpers.api import OptionalStdinT
4141
from exec_helpers.api import OptionalTimeoutT
4242
from exec_helpers.async_api import exec_result
43-
from exec_helpers.proc_enums import ExitCodeT
4443

4544
# Local Implementation
4645
from .. import _helpers
@@ -49,7 +48,20 @@
4948
# Standard Library
5049
import types
5150

52-
__all__ = ("ExecHelper",)
51+
# Package Implementation
52+
from exec_helpers.proc_enums import ExitCodeT
53+
54+
__all__ = (
55+
"ExecHelper",
56+
"CalledProcessErrorSubClassT",
57+
"OptionalStdinT",
58+
"OptionalTimeoutT",
59+
"CommandT",
60+
"LogMaskReT",
61+
"ErrorInfoT",
62+
"ChRootPathSetT",
63+
"ExpectedExitCodesT",
64+
)
5365

5466

5567
class ExecuteContext(typing.AsyncContextManager[api.ExecuteAsyncResult], abc.ABC):

exec_helpers/async_api/subprocess.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,8 @@
3434
from exec_helpers import exceptions
3535
from exec_helpers import proc_enums
3636
from exec_helpers import subprocess
37-
from exec_helpers.api import CalledProcessErrorSubClassT
38-
from exec_helpers.api import CommandT
39-
from exec_helpers.api import ErrorInfoT
40-
from exec_helpers.api import ExpectedExitCodesT
41-
from exec_helpers.api import LogMaskReT
42-
from exec_helpers.api import OptionalTimeoutT
4337
from exec_helpers.async_api import api
4438
from exec_helpers.async_api import exec_result
45-
from exec_helpers.exec_result import OptionalStdinT
46-
from exec_helpers.subprocess import CwdT
47-
from exec_helpers.subprocess import EnvT
4839

4940
# Local Implementation
5041
from .. import _log_templates
@@ -54,6 +45,17 @@
5445
# Standard Library
5546
import types
5647

48+
# Package Implementation
49+
from exec_helpers.api import CalledProcessErrorSubClassT
50+
from exec_helpers.api import CommandT
51+
from exec_helpers.api import ErrorInfoT
52+
from exec_helpers.api import ExpectedExitCodesT
53+
from exec_helpers.api import LogMaskReT
54+
from exec_helpers.api import OptionalTimeoutT
55+
from exec_helpers.exec_result import OptionalStdinT
56+
from exec_helpers.subprocess import CwdT
57+
from exec_helpers.subprocess import EnvT
58+
5759
__all__ = ("Subprocess", "SubprocessExecuteAsyncResult")
5860

5961

exec_helpers/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@
2121

2222
# Package Implementation
2323
from exec_helpers import proc_enums
24-
from exec_helpers.proc_enums import ExitCodeT
2524

2625
# Local Implementation
2726
from . import _log_templates
2827

2928
if typing.TYPE_CHECKING:
3029
# Package Implementation
3130
from exec_helpers import exec_result # noqa: F401 # pylint: disable=cyclic-import
31+
from exec_helpers.proc_enums import ExitCodeT
3232

3333
__all__ = (
3434
"ExecHelperError",

exec_helpers/exec_result.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
# Package Implementation
3030
from exec_helpers import exceptions
3131
from exec_helpers import proc_enums
32-
from exec_helpers.proc_enums import ExitCodeT
3332

3433
try:
3534
# noinspection PyPackageRequirements
@@ -63,6 +62,9 @@
6362
# noinspection PyPackageRequirements
6463
import logwrap
6564

65+
# Package Implementation
66+
from exec_helpers.proc_enums import ExitCodeT
67+
6668
__all__ = ("ExecResult", "OptionalStdinT")
6769

6870
LOGGER: logging.Logger = logging.getLogger(__name__)

exec_helpers/ssh.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@
2222
import os
2323
import pathlib
2424
import posixpath
25+
import typing
2526

2627
# Local Implementation
2728
from . import _ssh_base
28-
from ._ssh_base import SupportPathT
29+
30+
if typing.TYPE_CHECKING:
31+
from ._ssh_base import SupportPathT
2932

3033
__all__ = ("SSHClient",)
3134

exec_helpers/subprocess.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,6 @@
3636
from exec_helpers import exceptions
3737
from exec_helpers import exec_result
3838
from exec_helpers import proc_enums
39-
from exec_helpers.api import CalledProcessErrorSubClassT
40-
from exec_helpers.api import CommandT
41-
from exec_helpers.api import ErrorInfoT
42-
from exec_helpers.api import ExpectedExitCodesT
43-
from exec_helpers.api import LogMaskReT
44-
from exec_helpers.api import OptionalStdinT
45-
from exec_helpers.api import OptionalTimeoutT
4639

4740
# Local Implementation
4841
from . import _log_templates
@@ -52,6 +45,15 @@
5245
# Standard Library
5346
import types
5447

48+
# Package Implementation
49+
from exec_helpers.api import CalledProcessErrorSubClassT
50+
from exec_helpers.api import CommandT
51+
from exec_helpers.api import ErrorInfoT
52+
from exec_helpers.api import ExpectedExitCodesT
53+
from exec_helpers.api import LogMaskReT
54+
from exec_helpers.api import OptionalStdinT
55+
from exec_helpers.api import OptionalTimeoutT
56+
5557
__all__ = ("Subprocess", "SubprocessExecuteAsyncResult", "EnvT", "CwdT")
5658

5759
EnvT = typing.Optional[

0 commit comments

Comments
 (0)