Initial curated RAGcore source from c16667f0c9397e2bfc8dd0255b114fba23c8d05e
This commit is contained in:
commit
7ca726caf2
659 files changed
+98171
No files matched your search
@@ -0,0 +1,105 @@
|
||||
"""Tests for the documented modular-monolith dependency boundaries."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
|
||||
SOURCE_ROOT = Path(__file__).resolve().parents[2] / "src" / "ragcore"
|
||||
|
||||
FRAMEWORK_IMPORTS = (
|
||||
"alembic",
|
||||
"docling",
|
||||
"fastapi",
|
||||
"httpx",
|
||||
"pydantic",
|
||||
"pydantic_settings",
|
||||
"qdrant_client",
|
||||
"sqlalchemy",
|
||||
"starlette",
|
||||
"uvicorn",
|
||||
)
|
||||
|
||||
FORBIDDEN_RAGCORE_IMPORTS = {
|
||||
"domain": {
|
||||
"ragcore.api",
|
||||
"ragcore.application",
|
||||
"ragcore.evaluation",
|
||||
"ragcore.generation",
|
||||
"ragcore.ingestion",
|
||||
"ragcore.infrastructure",
|
||||
"ragcore.platform",
|
||||
"ragcore.retrieval",
|
||||
"ragcore.web",
|
||||
},
|
||||
"application": {
|
||||
"ragcore.api",
|
||||
"ragcore.infrastructure",
|
||||
"ragcore.platform",
|
||||
"ragcore.web",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _module_name(path: Path) -> str:
|
||||
relative = path.relative_to(SOURCE_ROOT).with_suffix("")
|
||||
parts = relative.parts
|
||||
if parts[-1] == "__init__":
|
||||
parts = parts[:-1]
|
||||
return ".".join(("ragcore", *parts))
|
||||
|
||||
|
||||
def _resolve_relative_import(module_name: str, node: ast.ImportFrom) -> str:
|
||||
if node.level == 0:
|
||||
return node.module or ""
|
||||
|
||||
package_parts = module_name.split(".")[:-1]
|
||||
base_parts = package_parts[: len(package_parts) - node.level + 1]
|
||||
if node.module:
|
||||
base_parts.extend(node.module.split("."))
|
||||
return ".".join(base_parts)
|
||||
|
||||
|
||||
def _imports(path: Path) -> Iterator[tuple[int, str]]:
|
||||
module_name = _module_name(path)
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
yield node.lineno, alias.name
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
yield node.lineno, _resolve_relative_import(module_name, node)
|
||||
|
||||
|
||||
def _matches(import_name: str, prefix: str) -> bool:
|
||||
return import_name == prefix or import_name.startswith(f"{prefix}.")
|
||||
|
||||
|
||||
def test_domain_and_application_respect_dependency_boundaries() -> None:
|
||||
violations: list[str] = []
|
||||
|
||||
for layer, forbidden_modules in FORBIDDEN_RAGCORE_IMPORTS.items():
|
||||
layer_root = SOURCE_ROOT / layer
|
||||
for path in sorted(layer_root.rglob("*.py")):
|
||||
for line, imported in _imports(path):
|
||||
framework = next(
|
||||
(prefix for prefix in FRAMEWORK_IMPORTS if _matches(imported, prefix)),
|
||||
None,
|
||||
)
|
||||
forbidden = next(
|
||||
(prefix for prefix in forbidden_modules if _matches(imported, prefix)),
|
||||
None,
|
||||
)
|
||||
if framework:
|
||||
violations.append(
|
||||
f"{path}:{line}: {layer} imports framework {imported}; "
|
||||
"keep framework adapters at the API/infrastructure boundary"
|
||||
)
|
||||
elif forbidden:
|
||||
violations.append(
|
||||
f"{path}:{line}: {layer} imports {imported}; "
|
||||
"depend on domain rules and application ports instead"
|
||||
)
|
||||
|
||||
assert not violations, "Architecture boundary violations:\n" + "\n".join(violations)
|
||||
Reference in new issue
Block a user