Files
QuanTAlib/python/tests/test_signatures.py

118 lines
4.0 KiB
Python

"""test_signatures.py — Verify function signatures have no reserved keywords or duplicates."""
from __future__ import annotations
import importlib
import inspect
import keyword
import pytest
# All category modules with public indicator functions
CATEGORY_MODULES = [
"quantalib.channels",
"quantalib.core",
"quantalib.cycles",
"quantalib.dynamics",
"quantalib.errors",
"quantalib.filters",
"quantalib.momentum",
"quantalib.numerics",
"quantalib.oscillators",
"quantalib.reversals",
"quantalib.statistics",
"quantalib.trends_fir",
"quantalib.trends_iir",
"quantalib.volatility",
"quantalib.volume",
]
def _get_public_functions():
"""Yield (module_name, func_name, func) for all public functions."""
for modname in CATEGORY_MODULES:
mod = importlib.import_module(modname)
all_names = getattr(mod, "__all__", [])
for name in all_names:
fn = getattr(mod, name, None)
if fn is not None and callable(fn):
yield modname, name, fn
@pytest.fixture(scope="module")
def all_functions():
return list(_get_public_functions())
class TestNoReservedKeywords:
"""No function parameter should use a Python reserved keyword."""
def test_no_reserved_keyword_params(self, all_functions) -> None:
violations = []
for modname, fname, fn in all_functions:
sig = inspect.signature(fn)
for pname in sig.parameters:
if keyword.iskeyword(pname):
violations.append(f"{modname}.{fname}(... {pname} ...)")
assert violations == [], (
f"Reserved keyword used as parameter name:\n"
+ "\n".join(f" - {v}" for v in violations)
)
class TestNoDuplicateParams:
"""No function should have duplicate parameter names (caught at parse time,
but this validates post-fix)."""
def test_no_duplicate_params(self, all_functions) -> None:
violations = []
for modname, fname, fn in all_functions:
sig = inspect.signature(fn)
params = list(sig.parameters.keys())
if len(params) != len(set(params)):
seen = set()
dupes = [p for p in params if p in seen or seen.add(p)] # type: ignore[func-returns-value]
violations.append(f"{modname}.{fname}: duplicates={dupes}")
assert violations == [], (
f"Duplicate parameter names found:\n"
+ "\n".join(f" - {v}" for v in violations)
)
class TestNoBuiltinShadowing:
"""Public function names should not shadow critical Python builtins."""
CRITICAL_BUILTINS = {"super", "type", "id", "input", "print", "open", "list", "dict", "set", "map", "filter"}
def test_no_builtin_function_names(self, all_functions) -> None:
violations = []
for modname, fname, fn in all_functions:
if fname in self.CRITICAL_BUILTINS:
violations.append(f"{modname}.{fname}")
assert violations == [], (
f"Function names shadow Python builtins:\n"
+ "\n".join(f" - {v}" for v in violations)
)
class TestVolumeIndicatorsHaveVolumeParam:
"""Volume indicators that use _ptr(volume) must have volume in their signature."""
VOLUME_REQUIRED = [
"adl", "adosc", "iii", "kvo", "va", "vwad", "vwap", "wad",
"obv", "pvt", "pvr", "vf", "nvi", "pvi", "tvi", "pvd",
"vwma", "evwma", "efi", "aobv", "mfi", "cmf", "eom", "pvo",
]
def test_volume_funcs_have_volume_param(self) -> None:
import quantalib.volume as vol
violations = []
for fname in self.VOLUME_REQUIRED:
fn = getattr(vol, fname, None)
if fn is None:
continue
sig = inspect.signature(fn)
if "volume" not in sig.parameters:
violations.append(fname)
assert violations == [], (
f"Volume functions missing 'volume' parameter: {violations}"
)