refactor(chain): remove dead data port indirection

This commit is contained in:
jxxghp
2026-08-28 05:53:53 +08:00
parent df92d9e267
commit ac7a201329
16 changed files with 130 additions and 128 deletions
-1
View File
@@ -304,7 +304,6 @@ def configure_plugin_system_services():
configure_chain_data_ports(
site=site_repository,
subscribe=lambda: SubscribeOper(),
workflow=lambda: workflow_execution,
download_history=lambda: DownloadHistoryOper(),
transfer_history=lambda: TransferHistoryOper(),
transfer_pending=lambda: TransactionalTransferAdmissionRepository(
+2 -2
View File
@@ -1,8 +1,8 @@
{
"application": {
"covered_lines": 10001,
"covered_lines": 9978,
"percent": 78.79,
"statements": 12694
"statements": 12664
},
"domain": {
"covered_lines": 3392,
+2 -7
View File
@@ -1441,8 +1441,8 @@
"runtime_only": true
}
},
"edge_count": 6907,
"edge_sha256": "8b3bb489cfc9573e377d0ee21337633b842b9969fc69fe99b86f547613b86a2a",
"edge_count": 6902,
"edge_sha256": "e2adb079d1df7415b81cbfa8358536e29e06276c2cb5af3a6f6c634e6f58fb20",
"edges": [
"app -> app.runtime",
"app -> app.runtime.compat",
@@ -3979,7 +3979,6 @@
"app.application.backup -> app.runtime.log",
"app.application.chain.context -> app.application",
"app.application.chain.context -> app.application.chain",
"app.application.chain.context -> app.application.chain.data",
"app.application.chain.context -> app.application.chain.events",
"app.application.chain.context -> app.application.configuration",
"app.application.chain.context -> app.runtime",
@@ -3988,7 +3987,6 @@
"app.application.chain.data -> app.application.transfer",
"app.application.chain.data -> app.application.transfer.execution",
"app.application.chain.data -> app.application.transfer.workflow",
"app.application.chain.data -> app.application.workflow",
"app.application.chain.events -> app.application",
"app.application.chain.events -> app.application.history",
"app.application.chain.events -> app.application.transfer",
@@ -4480,7 +4478,6 @@
"app.chain -> app.application",
"app.chain -> app.application.chain",
"app.chain -> app.application.chain.context",
"app.chain -> app.application.chain.data",
"app.chain -> app.application.configuration",
"app.chain -> app.chain._messaging",
"app.chain -> app.chain._recognition",
@@ -5065,8 +5062,6 @@
"app.chain.webhook -> app.schemas",
"app.chain.webhook -> app.schemas.types",
"app.chain.workflow -> app.application",
"app.chain.workflow -> app.application.chain",
"app.chain.workflow -> app.application.chain.data",
"app.chain.workflow -> app.application.workflow",
"app.chain.workflow -> app.chain",
"app.chain.workflow -> app.runtime",
-3
View File
@@ -1213,9 +1213,6 @@
"E402": 4,
"I001": 1
},
"tests/test_chain_runtime_context.py": {
"I001": 1
},
"tests/test_cli_auto_update.py": {
"I001": 1
},
+51 -16
View File
@@ -347,8 +347,8 @@ def test_workflow_query_adapter_owns_projection_sessions():
assert "_project_workflow(record)" in source
def test_workflow_execution_chain_uses_typed_transaction_port():
"""工作流 Chain 写端不得再经过 raw Oper 或重复获取全局端口。"""
def test_workflow_execution_chain_uses_single_application_owned_port():
"""工作流 Chain 写端必须只使用 Application owner 的唯一配置入口。"""
contract_path = APP_ROOT / "application" / "workflow.py"
contract_tree = ast.parse(
contract_path.read_text(encoding="utf-8"),
@@ -384,29 +384,64 @@ def test_workflow_execution_chain_uses_typed_transaction_port():
for node in data_tree.body
if isinstance(node, ast.ClassDef) and node.name == "ChainDataPorts"
)
workflow_field = next(
node
data_fields = {
node.target.id
for node in data_class.body
if isinstance(node, ast.AnnAssign)
and isinstance(node.target, ast.Name)
and node.target.id == "workflow"
)
workflow_getter = next(
node
}
data_functions = {
node.name
for node in data_tree.body
if isinstance(node, ast.FunctionDef)
and node.name == "get_chain_workflow_port"
)
assert ast.unparse(workflow_field.annotation) == "WorkflowExecutionPortFactory"
assert ast.unparse(workflow_getter.returns) == "WorkflowExecutionPort"
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
}
assert "workflow" not in data_fields
assert "get_chain_workflow_port" not in data_functions
chain_source = (APP_ROOT / "chain" / "workflow.py").read_text(encoding="utf-8")
startup_source = (
APP_ROOT / "startup" / "initializers" / "modules.py"
).read_text(encoding="utf-8")
assert chain_source.count("get_chain_workflow_port()") == 1
assert "workflow=lambda: workflow_execution" in startup_source
assert "workflow=lambda: WorkflowOper()" not in startup_source
assert chain_source.count("get_configured_workflow_execution()") == 1
assert "get_chain_workflow_port" not in chain_source
assert "configure_workflow_execution(workflow_execution)" in startup_source
assert "workflow=lambda:" not in startup_source
def test_chain_registry_has_no_dynamic_proxies_or_dead_context_injection():
"""Chain registry 不得恢复零消费者动态代理或失效 data_ports 伪注入。"""
data_path = APP_ROOT / "application" / "chain" / "data.py"
data_tree = ast.parse(
data_path.read_text(encoding="utf-8"),
filename=str(data_path),
)
proxy_classes = {
node.name
for node in data_tree.body
if isinstance(node, ast.ClassDef)
and (node.name.endswith("PortProxy") or node.name == "_PortProxyMeta")
}
dynamic_getters = {
node.name
for node in ast.walk(data_tree)
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
and node.name == "__getattr__"
}
assert proxy_classes == set()
assert dynamic_getters == set()
context_source = (
APP_ROOT / "application" / "chain" / "context.py"
).read_text(encoding="utf-8")
chain_base_source = (APP_ROOT / "chain" / "__init__.py").read_text(
encoding="utf-8"
)
startup_source = (
APP_ROOT / "startup" / "initializers" / "modules.py"
).read_text(encoding="utf-8")
assert "data_ports" not in context_source
assert "self.data_ports" not in chain_base_source
assert "data_ports=" not in startup_source
def test_canonical_workflow_oper_has_no_legacy_writer_or_duplicate_exports():
+43 -1
View File
@@ -2,9 +2,12 @@
from unittest.mock import Mock
import pytest
from app.application.chain import context as chain_context
from app.application.chain import data as chain_data
from app.application.chain.context import ChainRuntimeContext
from app.application.configuration import ChainRuntimeConfig
from app.application.chain import context as chain_context
from app.chain import ChainBase
from app.runtime.extensions.module.dispatcher import ModuleInvocationDispatcher
@@ -51,3 +54,42 @@ def test_no_arg_chain_uses_compatibility_context_provider(monkeypatch) -> None:
provider.assert_called_once_with()
assert chain.modulemanager is context.module_manager
assert chain.pluginmanager is context.plugin_manager
def test_chain_runtime_context_rejects_unconfigured_provider(monkeypatch) -> None:
"""未由组合根配置运行上下文时必须显式拒绝无参 Chain。"""
monkeypatch.setattr(
chain_context,
"_context_provider",
chain_context._unconfigured_chain_runtime_context,
)
with pytest.raises(RuntimeError, match="Chain 运行上下文尚未由启动组合根配置"):
chain_context.get_chain_runtime_context()
def test_chain_data_registry_rejects_unconfigured_and_returns_factories(
monkeypatch,
) -> None:
"""数据 registry 未配置时拒绝访问,配置后按字段返回工厂实例。"""
monkeypatch.setattr(chain_data, "_ports", None)
with pytest.raises(RuntimeError, match="Chain 数据端口尚未配置"):
chain_data.get_chain_data_ports()
media_server = Mock()
user = Mock()
chain_data.configure_chain_data_ports(
site=Mock,
subscribe=Mock,
download_history=Mock,
transfer_history=Mock,
transfer_pending=Mock,
transfer_execution=Mock,
media_server=lambda: media_server,
download_failure=Mock,
user=lambda: user,
)
assert chain_data.get_chain_media_server_port() is media_server
assert chain_data.get_chain_user_port() is user
+15 -3
View File
@@ -643,7 +643,11 @@ def test_workflow_chain_process_serializes_circular_context(monkeypatch):
return fake_oper
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_chain_workflow_port", get_execution_port)
monkeypatch.setattr(
workflow_module,
"get_configured_workflow_execution",
get_execution_port,
)
monkeypatch.setattr(
workflow_module,
"get_configured_workflow_query",
@@ -952,7 +956,11 @@ def test_workflow_chain_rejects_execution_before_persisting_running_state(monkey
workflowoper = _FakeWorkflowOper(workflow)
manager = RejectingWorkflowManager([])
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: manager)
monkeypatch.setattr(workflow_module, "get_chain_workflow_port", lambda: workflowoper)
monkeypatch.setattr(
workflow_module,
"get_configured_workflow_execution",
lambda: workflowoper,
)
monkeypatch.setattr(
workflow_module,
"get_configured_workflow_query",
@@ -995,7 +1003,11 @@ def test_workflow_chain_releases_admitted_owner_when_start_fails(monkeypatch):
manager._executions = {}
workflowoper = FailingWorkflowOper(_build_workflow())
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: manager)
monkeypatch.setattr(workflow_module, "get_chain_workflow_port", lambda: workflowoper)
monkeypatch.setattr(
workflow_module,
"get_configured_workflow_execution",
lambda: workflowoper,
)
monkeypatch.setattr(
workflow_module,
"get_configured_workflow_query",