Performance¶
Optimization patterns used in kstlib to minimize startup time and memory footprint.
Lazy Loading¶
kstlib uses lazy loading to defer expensive imports until they’re actually needed. This significantly reduces startup time when only a subset of features is used.
PEP 562: Module-Level __getattr__¶
kstlib/__init__.py uses PEP 562 lazy loading:
# All symbols loaded on-demand via __getattr__
import kstlib # ~6ms (was ~280ms)
# Modules loaded only when accessed
kstlib.mail # Loads mail module
kstlib.ConfigLoader # Loads config module
Import time improvement: 280ms → 6ms (98% faster)¶
Rich traceback is now opt-in (saves ~100ms):
# Explicit activation
import kstlib
kstlib.install_rich_traceback()
# Or via environment variable
# KSTLIB_TRACEBACK=1 python script.py
The @lazy_factory Decorator¶
Located in kstlib.utils.lazy, this decorator wraps factory functions to defer
class imports until the factory is called:
from kstlib.utils import lazy_factory
@lazy_factory("kstlib.secrets.providers.sops", "SOPSProvider")
def _sops_factory(**kwargs) -> SecretProvider:
... # Body replaced by decorator
How it works:
At module load time, only the decorator is evaluated (no import)
When
_sops_factory()is called, the module is imported viaimportlibThe class is instantiated with the provided kwargs
Subsequent calls reuse the already-imported module (Python caches imports)
Metrics: Secret Providers¶
Before lazy loading, all 4 providers were imported at module load:
Module |
Before |
After |
Reduction |
|---|---|---|---|
|
109ms |
31ms |
-72% |
|
83ms |
0ms (lazy) |
-100% |
|
1ms |
0ms (lazy) |
-100% |
|
1ms |
0ms (lazy) |
-100% |
|
1ms |
0ms (lazy) |
-100% |
The keyring provider was the main culprit (83ms) due to its heavy dependency chain.
Current Import Profile¶
Measured with python -X importtime -c "import kstlib":
Module |
Cumulative (ms) |
Notes |
|---|---|---|
|
93 |
Builder imports config chain |
|
77 |
Typer + Rich dependencies |
|
62 |
YAML/TOML parsers |
|
32 |
Lazy-loaded providers |
|
5 |
Lightweight |
|
4 |
Lightweight |
How to Measure¶
# Full import profile (sorted by cumulative time)
python -X importtime -c "import kstlib" 2>&1 | sort -t'|' -k2 -rn | head -20
# Specific module
python -X importtime -c "from kstlib.secrets import resolve_secret" 2>&1 | grep kstlib
Lazy Loading Patterns¶
kstlib uses three distinct patterns for deferring imports. Each solves a different problem:
Pattern |
What it defers |
Use case |
|---|---|---|
PEP 562 |
Module loading at package level |
|
|
Class import + instantiation |
Plugin/provider factories |
Local imports |
Import inside function body |
Utility functions |
|
Type hints without runtime import |
Function signatures |
Pattern 1: PEP 562 (Package-level lazy modules)¶
Used in kstlib/__init__.py to defer submodule loading:
# In __init__.py
def __getattr__(name: str) -> Any:
if name == "mail":
return importlib.import_module("kstlib.mail") # Loaded only when accessed
raise AttributeError(...)
import kstlib # ~6ms - mail not loaded yet
kstlib.mail # NOW mail is loaded (~93ms)
Pattern 2: @lazy_factory (Class instantiation)¶
For factory functions that create instances of heavy classes:
from kstlib.utils import lazy_factory
@lazy_factory("kstlib.secrets.providers.sops", "SOPSProvider")
def get_sops_provider(**kwargs) -> SecretProvider:
... # Body is REPLACED by decorator - never executed
The decorator handles importlib.import_module() + getattr() + instantiation.
Pattern 3: Local imports (Function-level)¶
For functions that need a class internally:
from __future__ import annotations
def decrypt_file(path: Path) -> str:
from kstlib.secrets.providers.sops import SOPSProvider # Deferred import
return SOPSProvider().decrypt(path)
Pattern 4: TYPE_CHECKING (Type hints only)¶
For type annotations without runtime import:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from heavy_module import HeavyClass # Only imported by type checkers
def process(obj: HeavyClass) -> None: # Works without runtime import
...
Guidelines¶
When adding new modules:
Avoid top-level imports of heavy dependencies (keyring, cryptography, etc.)
Use PEP 562
__getattr__in__init__.pyfor lazy submodulesUse
@lazy_factoryfor plugin/provider patternsUse local imports for one-off heavy class usage in functions
Use
TYPE_CHECKINGwhen you only need the type for annotationsUse
from __future__ import annotationsto enable forward references without quotes (becomes default in Python 3.14+, see PEP 563)Measure before/after with
python -X importtime