Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 35 additions & 25 deletions src/features/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,40 @@

Provides comprehensive feature extraction for market regime detection
and position sizing algorithms.

Public names are imported lazily (PEP 562). A broken dependency in one
module (for example base.py's data-layer import) therefore no longer
prevents importing unrelated, self-contained modules in this package
(greeks, pnl, position_models, ...).
"""

from .base import FeatureEngineering
from .regime_features import (
PriceStructureFeatures,
TrendIndicators,
MomentumIndicators,
VolatilityFeatures,
VolumeFeatures,
SupportResistanceFeatures,
MarketContextFeatures,
EventFeatures,
RegimeStateVector
)

__all__ = [
"FeatureEngineering",
"PriceStructureFeatures",
"TrendIndicators",
"MomentumIndicators",
"VolatilityFeatures",
"VolumeFeatures",
"SupportResistanceFeatures",
"MarketContextFeatures",
"EventFeatures",
"RegimeStateVector",
]
import importlib

# Map each public name to the submodule that defines it.
_EXPORTS = {
"FeatureEngineering": ".base",
"PriceStructureFeatures": ".regime_features",
"TrendIndicators": ".regime_features",
"MomentumIndicators": ".regime_features",
"VolatilityFeatures": ".regime_features",
"VolumeFeatures": ".regime_features",
"SupportResistanceFeatures": ".regime_features",
"MarketContextFeatures": ".regime_features",
"EventFeatures": ".regime_features",
"RegimeStateVector": ".regime_features",
}

__all__ = list(_EXPORTS)


def __getattr__(name: str):
"""Lazily import and return a public attribute (PEP 562)."""
module_name = _EXPORTS.get(name)
if module_name is None:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
module = importlib.import_module(module_name, __name__)
return getattr(module, name)


def __dir__():
return sorted(__all__)
40 changes: 40 additions & 0 deletions tests/features/test_lazy_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""
Regression tests for lazy feature-package imports (issue #10).

The src.features package must not eagerly import its submodules, so a broken
dependency in one module (e.g. base.py's data-layer import) cannot take down
unrelated, self-contained modules in the package.
"""

import importlib
import sys

import pytest


def _purge_features():
for name in list(sys.modules):
if name == "src.features" or name.startswith("src.features."):
del sys.modules[name]


def test_importing_package_does_not_eagerly_load_base():
"""Importing the package must not pull in base (its broken dependency)."""
_purge_features()
importlib.import_module("src.features")
assert "src.features.base" not in sys.modules


def test_clean_submodule_imports_without_base():
"""A self-contained submodule imports without requiring base."""
_purge_features()
importlib.import_module("src.features.position_models")
assert "src.features.base" not in sys.modules


def test_unknown_attribute_raises():
"""Accessing an undefined public name still raises AttributeError."""
_purge_features()
features = importlib.import_module("src.features")
with pytest.raises(AttributeError):
features.DefinitelyNotExported