mirror of
https://github.com/microsoft/qlib.git
synced 2026-11-13 06:20:56 +08:00
* security: constrain config-driven code execution * fix: address security test and lint failures * ci: restore dependency and title lint compatibility * ci: handle current MLflow and Windows OSQP changes * ci: use lxml release with cross-platform wheels * fix: defer optional CVXPY solver imports * style: document intentional lazy import * ci: skip broken released OSQP on Windows Python 3.8 * test: make MLflow client timing check robust * ci: retry nondeterministic benchmark smoke test * fix: cap filelock before fork safety regression * fix: use filelock release compatible with multiprocessing * fix(security): restrict operator lookup and validate trusted roots * fix(security): preserve numeric expressions and legacy model imports * fix: preserve TRA initialization and document config migration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: preserve safe expression syntax and cache semantics Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use explicit scoped trust for file modules Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: satisfy CI lint without weakening trust checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: handle escaped file paths in module refusal errors Match the repr-formatted module path in refusal messages and cover Windows, UNC, quoted, and newline-containing paths on every platform. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: preserve expression parser error logging Log ExpressionSyntaxError with its field and traceback before re-raising, while preserving custom operator NameError and SyntaxError handling. Add five regression cases covering logging, exception identity, and failed cache entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: isolate expression logger capture Capture parser logs directly on qlib.data without propagating to parent and root capture handlers. Restore logger state with monkeypatch and retain exact single-record, traceback, and exception-identity assertions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(security): preserve malformed feature boundaries and reject unsupported literals * test(security): cover malformed dollar-prefixed expressions --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Yang Liu <DG-git-dev@users.noreply.github.com>
83 lines
3.1 KiB
Python
83 lines
3.1 KiB
Python
import ast
|
|
from pathlib import Path
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
|
|
def _module_tree(relative_path):
|
|
repository_root = Path(__file__).resolve().parents[2]
|
|
return ast.parse((repository_root / relative_path).read_text(encoding="utf-8"))
|
|
|
|
|
|
def test_model_and_graph_dispatch_do_not_call_python_eval():
|
|
for relative_path in [
|
|
"qlib/contrib/model/pytorch_tra.py",
|
|
"qlib/contrib/report/analysis_model/analysis_model_performance.py",
|
|
]:
|
|
tree = _module_tree(relative_path)
|
|
eval_calls = [
|
|
node
|
|
for node in ast.walk(tree)
|
|
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == "eval"
|
|
]
|
|
assert not eval_calls
|
|
|
|
|
|
def test_tra_rejects_code_as_model_name(tmp_path):
|
|
pytest.importorskip("torch")
|
|
from qlib.contrib.model.pytorch_tra import TRAModel
|
|
|
|
marker = tmp_path / "executed.txt"
|
|
model = object.__new__(TRAModel)
|
|
model.logger = Mock()
|
|
model.model_type = f"__import__('pathlib').Path({str(marker)!r}).touch()"
|
|
with pytest.raises(ValueError, match="Unsupported model_type") as error:
|
|
model._init_model()
|
|
assert "MODEL_TYPES" in str(error.value) and "config_migration.rst" in str(error.value)
|
|
assert not marker.exists()
|
|
|
|
|
|
@pytest.mark.parametrize("model_type", ["RNN", "Transformer"])
|
|
def test_tra_initializes_supported_models(model_type):
|
|
pytest.importorskip("torch")
|
|
from qlib.contrib.model.pytorch_tra import MODEL_TYPES, TRAModel
|
|
|
|
model = object.__new__(TRAModel)
|
|
model.logger = Mock()
|
|
model.model_type = model_type
|
|
model.model_config = {"input_size": 6, "hidden_size": 8, "num_layers": 1}
|
|
model.tra_config = {"num_states": 1}
|
|
model.init_state = None
|
|
model.reset_router = model.freeze_model = model.freeze_predictors = False
|
|
model.lr = 0.001
|
|
model._init_model()
|
|
assert isinstance(model.model, MODEL_TYPES[model_type])
|
|
|
|
|
|
def test_graph_rejects_code_as_graph_name(tmp_path):
|
|
pytest.importorskip("plotly")
|
|
import pandas as pd
|
|
from qlib.contrib.report.analysis_model.analysis_model_performance import model_performance_graph
|
|
|
|
marker = tmp_path / "executed.txt"
|
|
name = f"_import__('pathlib').Path({str(marker)!r}).touch()"
|
|
with pytest.raises(ValueError, match="Unsupported graph name") as error:
|
|
model_performance_graph(pd.DataFrame(), graph_names=[name], show_notebook=False)
|
|
assert "GRAPH_FUNCTIONS" in str(error.value) and "config_migration.rst" in str(error.value)
|
|
assert not marker.exists()
|
|
|
|
|
|
@pytest.mark.parametrize("name", ["group_return", "pred_ic", "pred_autocorr", "pred_turnover"])
|
|
def test_graph_dispatch_preserves_supported_names(monkeypatch, name):
|
|
pytest.importorskip("plotly")
|
|
import pandas as pd
|
|
from qlib.contrib.report.analysis_model import analysis_model_performance as module
|
|
|
|
figure = object()
|
|
graph = Mock(return_value=(figure,))
|
|
monkeypatch.setitem(module.GRAPH_FUNCTIONS, name, graph)
|
|
result = module.model_performance_graph(pd.DataFrame(), graph_names=[name], show_notebook=False)
|
|
assert result == [figure]
|
|
graph.assert_called_once()
|