Skip to content

Commit 667557e

Browse files
refactor(backend): move database helpers to infra
1 parent 96661ab commit 667557e

9 files changed

Lines changed: 33 additions & 27 deletions

File tree

packages/backend/app/api/v1/datasources.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def list_datasource_tables(
214214
raise HTTPException(status_code=400, detail="Datasource has no connection_string")
215215
try:
216216
from sqlalchemy import create_engine, inspect
217-
from app.node.core.db_utils import normalize_connection_string
217+
from app.infra.db import normalize_connection_string
218218
engine = create_engine(normalize_connection_string(ds.connection_string))
219219
inspector = inspect(engine)
220220
tables = inspector.get_table_names()

packages/backend/app/datasource/services/connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
from sqlalchemy import inspect, text
66

7-
from app.node.core.db_utils import create_engine, normalize_connection_string
87
from app.datasource.services.specs import normalize_datasource_type, validate_database_datasource_type
8+
from app.infra.db import create_engine, normalize_connection_string
99

1010

1111
def validate_database_connection(

packages/backend/app/datasource/services/preview.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
from sqlalchemy import MetaData, Table, func, inspect, select
1616

1717
from app.core.config import settings
18-
from app.node.core.db_utils import create_engine, normalize_connection_string
1918
from app.datasource.services.specs import normalize_datasource_type, validate_database_datasource_type, validate_file_type
19+
from app.infra.db import create_engine, normalize_connection_string
2020
from app.infra.services.minio import download_bytes
2121

2222
DEFAULT_PREVIEW_PAGE_SIZE = 25

packages/backend/app/node/core/db_utils.py renamed to packages/backend/app/infra/db.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
from sqlalchemy import text
88

9-
from app.datasource.services.specs import validate_database_datasource_type
10-
119

1210
def normalize_connection_string(connection_string: str) -> str:
1311
"""Use PyMySQL driver for mysql:// URLs so MySQL works without mysqlclient (MySQLdb)."""
@@ -24,10 +22,6 @@ def create_engine(connection_string: str):
2422
return create_engine(url)
2523

2624

27-
def validate_datasource_type(datasource_type: str | None) -> None:
28-
validate_database_datasource_type(datasource_type)
29-
30-
3125
def validate_table_name(table: str) -> None:
3226
if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", table):
3327
raise ValueError("Invalid table name")
Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,7 @@
11
"""Core abstractions and shared helpers for workflow nodes."""
22

33
from app.node.core.base import BaseNode
4-
from app.node.core.db_utils import (
5-
create_engine,
6-
fetch_rows,
7-
json_safe_row,
8-
normalize_connection_string,
9-
validate_datasource_type,
10-
validate_table_name,
11-
)
124

135
__all__ = [
146
"BaseNode",
15-
"create_engine",
16-
"fetch_rows",
17-
"json_safe_row",
18-
"normalize_connection_string",
19-
"validate_datasource_type",
20-
"validate_table_name",
217
]

packages/backend/app/node/data/sql_execute.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55
from sqlalchemy.orm import Session
66

7+
from app.datasource.services.specs import validate_database_datasource_type
8+
from app.infra.db import create_engine, fetch_rows
79
from app.repositories import DataSourceRepository
810
from app.node.core.base import BaseNode
9-
from app.node.core.db_utils import create_engine, fetch_rows, validate_datasource_type
1011
from app.workflow.services.datasets import build_dataset_ref, materialize_sql_query_to_sandbox_result
1112
from deepeye.workflows.models import Node, Port
1213
from deepeye.workflows.registry import NodeSpec
@@ -36,7 +37,7 @@ def execute(self, node: Node, inputs: dict[str, Any], context: object) -> dict[s
3637

3738
connection_string = ds.connection_string
3839
datasource_type = getattr(ds, "type", None)
39-
validate_datasource_type(datasource_type)
40+
validate_database_datasource_type(datasource_type)
4041
if not connection_string:
4142
raise ValueError("database datasource is missing connection_string")
4243

packages/backend/app/tasks/agent_datasources.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def _get_single_datasource_schema(
144144
if not connection_string:
145145
return []
146146
try:
147-
from app.node.core.db_utils import json_safe_row, normalize_connection_string
147+
from app.infra.db import json_safe_row, normalize_connection_string
148148

149149
data_engine = create_engine(normalize_connection_string(connection_string))
150150
try:

packages/backend/app/test/test_architecture_boundaries.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
"app.services.docker_control_client",
4040
"app.services.minio_service",
4141
}
42+
LEGACY_NODE_DB_UTILITY_MODULES = {
43+
"app.node.core.db_utils",
44+
}
4245
LEGACY_WORKFLOW_SERVICE_MODULES = {
4346
"app.services.workflow_agent_drafts",
4447
"app.services.workflow_agent_response",
@@ -119,6 +122,18 @@ def test_datasource_domain_does_not_depend_on_tools_layer() -> None:
119122
assert violations == []
120123

121124

125+
def test_datasource_domain_does_not_depend_on_node_layer() -> None:
126+
violations: list[str] = []
127+
for path in sorted(DATASOURCE_DIR.rglob("*.py")):
128+
if path.name == "__init__.py":
129+
continue
130+
for module in sorted(_imported_modules(path)):
131+
if module == "app.node" or module.startswith("app.node."):
132+
violations.append(f"{path.relative_to(APP_DIR)} imports {module}")
133+
134+
assert violations == []
135+
136+
122137
def test_runtime_domain_does_not_depend_on_tools_layer() -> None:
123138
violations: list[str] = []
124139
for path in sorted(RUNTIME_DIR.rglob("*.py")):
@@ -222,6 +237,16 @@ def test_moved_deploy_and_infra_services_use_domain_import_paths() -> None:
222237
assert violations == []
223238

224239

240+
def test_moved_database_helpers_use_infra_import_paths() -> None:
241+
violations: list[str] = []
242+
for path in sorted(APP_DIR.rglob("*.py")):
243+
legacy_imports = sorted(_imported_modules(path) & LEGACY_NODE_DB_UTILITY_MODULES)
244+
for module in legacy_imports:
245+
violations.append(f"{path.relative_to(APP_DIR)} imports {module}")
246+
247+
assert violations == []
248+
249+
225250
def test_moved_datasource_services_use_domain_import_paths() -> None:
226251
violations: list[str] = []
227252
for path in sorted(APP_DIR.rglob("*.py")):

packages/backend/app/workflow/services/datasets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from sqlalchemy import text
1111

12-
from app.node.core.db_utils import create_engine, json_safe_row
12+
from app.infra.db import create_engine, json_safe_row
1313
from app.repositories import DataSourceRepository
1414
from app.datasource.services.specs import (
1515
get_datasource_filename,

0 commit comments

Comments
 (0)