mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-14 08:38:04 +00:00
fix(python): critical bug fixes across Python wrapper
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""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}"
|
||||
)
|
||||
Reference in New Issue
Block a user