Skip to content

Commit 56036af

Browse files
YassinNouh21claude
andcommitted
Extract shared tagging helpers to reduce duplication across routers
- Add database/tagging.py with generic insert/select/remove/select_tags helpers - Add core/tagging.py with require_user, tag_entity, untag_entity helpers - Refactor flows/tasks/runs DB modules to delegate to shared helpers - Refactor flows/tasks/runs routers to use shared tag/untag logic - Rename connection -> expdb in DB tag functions for consistency - Add get() lookup function to database/runs.py Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent 18e713f commit 56036af

8 files changed

Lines changed: 265 additions & 261 deletions

File tree

src/core/tagging.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from collections.abc import Callable
2+
from http import HTTPStatus
3+
from typing import Any
4+
5+
from fastapi import HTTPException
6+
from sqlalchemy import Connection, Row
7+
8+
from database.users import User, UserGroup
9+
10+
11+
def require_user(user: User | None) -> User:
12+
if user is None:
13+
raise HTTPException(
14+
status_code=HTTPStatus.PRECONDITION_FAILED,
15+
detail={"code": "103", "message": "Authentication failed"},
16+
)
17+
return user
18+
19+
20+
def tag_entity(
21+
entity_id: int,
22+
tag: str,
23+
user: User,
24+
expdb: Connection,
25+
*,
26+
get_tags_fn: Callable[[int, Connection], list[str]],
27+
tag_fn: Callable[..., None],
28+
response_key: str,
29+
) -> dict[str, dict[str, Any]]:
30+
tags = get_tags_fn(entity_id, expdb)
31+
if tag.casefold() in (t.casefold() for t in tags):
32+
raise HTTPException(
33+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
34+
detail={
35+
"code": "473",
36+
"message": "Entity already tagged by this tag.",
37+
"additional_information": f"id={entity_id}; tag={tag}",
38+
},
39+
)
40+
tag_fn(entity_id, tag, user_id=user.user_id, expdb=expdb)
41+
tags = get_tags_fn(entity_id, expdb)
42+
return {response_key: {"id": str(entity_id), "tag": tags}}
43+
44+
45+
def untag_entity(
46+
entity_id: int,
47+
tag: str,
48+
user: User,
49+
expdb: Connection,
50+
*,
51+
get_tag_fn: Callable[[int, str, Connection], Row | None],
52+
delete_tag_fn: Callable[[int, str, Connection], None],
53+
get_tags_fn: Callable[[int, Connection], list[str]],
54+
response_key: str,
55+
) -> dict[str, dict[str, Any]]:
56+
existing = get_tag_fn(entity_id, tag, expdb)
57+
if existing is None:
58+
raise HTTPException(
59+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
60+
detail={
61+
"code": "477",
62+
"message": "Tag not found.",
63+
"additional_information": f"id={entity_id}; tag={tag}",
64+
},
65+
)
66+
if existing.uploader != user.user_id and UserGroup.ADMIN not in user.groups:
67+
raise HTTPException(
68+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
69+
detail={
70+
"code": "478",
71+
"message": "Tag is not owned by you.",
72+
"additional_information": f"id={entity_id}; tag={tag}",
73+
},
74+
)
75+
delete_tag_fn(entity_id, tag, expdb)
76+
tags = get_tags_fn(entity_id, expdb)
77+
return {response_key: {"id": str(entity_id), "tag": tags}}

src/database/flows.py

Lines changed: 12 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33

44
from sqlalchemy import Connection, Row, text
55

6+
from database.tagging import insert_tag, remove_tag, select_tag, select_tags
7+
8+
_TABLE = "implementation_tag"
9+
_ID_COLUMN = "id"
10+
611

712
def get_subflows(for_flow: int, expdb: Connection) -> Sequence[Row]:
813
return cast(
@@ -21,17 +26,7 @@ def get_subflows(for_flow: int, expdb: Connection) -> Sequence[Row]:
2126

2227

2328
def get_tags(flow_id: int, expdb: Connection) -> list[str]:
24-
tag_rows = expdb.execute(
25-
text(
26-
"""
27-
SELECT tag
28-
FROM implementation_tag
29-
WHERE id = :flow_id
30-
""",
31-
),
32-
parameters={"flow_id": flow_id},
33-
)
34-
return [tag.tag for tag in tag_rows]
29+
return select_tags(table=_TABLE, id_column=_ID_COLUMN, id_=flow_id, expdb=expdb)
3530

3631

3732
def get_parameters(flow_id: int, expdb: Connection) -> Sequence[Row]:
@@ -50,41 +45,16 @@ def get_parameters(flow_id: int, expdb: Connection) -> Sequence[Row]:
5045
)
5146

5247

53-
def tag(id_: int, tag_: str, *, user_id: int, connection: Connection) -> None:
54-
connection.execute(
55-
text(
56-
"""
57-
INSERT INTO implementation_tag(`id`, `tag`, `uploader`)
58-
VALUES (:flow_id, :tag, :user_id)
59-
""",
60-
),
61-
parameters={"flow_id": id_, "tag": tag_, "user_id": user_id},
62-
)
48+
def tag(id_: int, tag_: str, *, user_id: int, expdb: Connection) -> None:
49+
insert_tag(table=_TABLE, id_column=_ID_COLUMN, id_=id_, tag_=tag_, user_id=user_id, expdb=expdb)
6350

6451

65-
def get_tag(id_: int, tag_: str, connection: Connection) -> Row | None:
66-
return connection.execute(
67-
text(
68-
"""
69-
SELECT `id`, `tag`, `uploader`
70-
FROM implementation_tag
71-
WHERE `id` = :flow_id AND `tag` = :tag
72-
""",
73-
),
74-
parameters={"flow_id": id_, "tag": tag_},
75-
).one_or_none()
52+
def get_tag(id_: int, tag_: str, expdb: Connection) -> Row | None:
53+
return select_tag(table=_TABLE, id_column=_ID_COLUMN, id_=id_, tag_=tag_, expdb=expdb)
7654

7755

78-
def delete_tag(id_: int, tag_: str, connection: Connection) -> None:
79-
connection.execute(
80-
text(
81-
"""
82-
DELETE FROM implementation_tag
83-
WHERE `id` = :flow_id AND `tag` = :tag
84-
""",
85-
),
86-
parameters={"flow_id": id_, "tag": tag_},
87-
)
56+
def delete_tag(id_: int, tag_: str, expdb: Connection) -> None:
57+
remove_tag(table=_TABLE, id_column=_ID_COLUMN, id_=id_, tag_=tag_, expdb=expdb)
8858

8959

9060
def get_by_name(name: str, external_version: str, expdb: Connection) -> Row | None:

src/database/runs.py

Lines changed: 20 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,35 @@
11
from sqlalchemy import Connection, Row, text
22

3+
from database.tagging import insert_tag, remove_tag, select_tag, select_tags
34

4-
def get_tags(id_: int, expdb: Connection) -> list[str]:
5-
tag_rows = expdb.execute(
5+
_TABLE = "run_tag"
6+
_ID_COLUMN = "id"
7+
8+
9+
def get(id_: int, expdb: Connection) -> Row | None:
10+
return expdb.execute(
611
text(
712
"""
8-
SELECT `tag`
9-
FROM run_tag
13+
SELECT *
14+
FROM run
1015
WHERE `id` = :run_id
1116
""",
1217
),
1318
parameters={"run_id": id_},
14-
)
15-
return [row.tag for row in tag_rows]
19+
).one_or_none()
1620

1721

18-
def tag(id_: int, tag_: str, *, user_id: int, connection: Connection) -> None:
19-
connection.execute(
20-
text(
21-
"""
22-
INSERT INTO run_tag(`id`, `tag`, `uploader`)
23-
VALUES (:run_id, :tag, :user_id)
24-
""",
25-
),
26-
parameters={"run_id": id_, "tag": tag_, "user_id": user_id},
27-
)
22+
def get_tags(id_: int, expdb: Connection) -> list[str]:
23+
return select_tags(table=_TABLE, id_column=_ID_COLUMN, id_=id_, expdb=expdb)
2824

2925

30-
def get_tag(id_: int, tag_: str, connection: Connection) -> Row | None:
31-
return connection.execute(
32-
text(
33-
"""
34-
SELECT `id`, `tag`, `uploader`
35-
FROM run_tag
36-
WHERE `id` = :run_id AND `tag` = :tag
37-
""",
38-
),
39-
parameters={"run_id": id_, "tag": tag_},
40-
).one_or_none()
26+
def tag(id_: int, tag_: str, *, user_id: int, expdb: Connection) -> None:
27+
insert_tag(table=_TABLE, id_column=_ID_COLUMN, id_=id_, tag_=tag_, user_id=user_id, expdb=expdb)
4128

4229

43-
def delete_tag(id_: int, tag_: str, connection: Connection) -> None:
44-
connection.execute(
45-
text(
46-
"""
47-
DELETE FROM run_tag
48-
WHERE `id` = :run_id AND `tag` = :tag
49-
""",
50-
),
51-
parameters={"run_id": id_, "tag": tag_},
52-
)
30+
def get_tag(id_: int, tag_: str, expdb: Connection) -> Row | None:
31+
return select_tag(table=_TABLE, id_column=_ID_COLUMN, id_=id_, tag_=tag_, expdb=expdb)
32+
33+
34+
def delete_tag(id_: int, tag_: str, expdb: Connection) -> None:
35+
remove_tag(table=_TABLE, id_column=_ID_COLUMN, id_=id_, tag_=tag_, expdb=expdb)

src/database/tagging.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from sqlalchemy import Connection, Row, text
2+
3+
4+
def insert_tag(
5+
*,
6+
table: str,
7+
id_column: str,
8+
id_: int,
9+
tag_: str,
10+
user_id: int,
11+
expdb: Connection,
12+
) -> None:
13+
expdb.execute(
14+
text(
15+
f"""
16+
INSERT INTO {table}(`{id_column}`, `tag`, `uploader`)
17+
VALUES (:id, :tag, :user_id)
18+
""",
19+
),
20+
parameters={"id": id_, "tag": tag_, "user_id": user_id},
21+
)
22+
23+
24+
def select_tag(
25+
*,
26+
table: str,
27+
id_column: str,
28+
id_: int,
29+
tag_: str,
30+
expdb: Connection,
31+
) -> Row | None:
32+
return expdb.execute(
33+
text(
34+
f"""
35+
SELECT `{id_column}` as id, `tag`, `uploader`
36+
FROM {table}
37+
WHERE `{id_column}` = :id AND `tag` = :tag
38+
""",
39+
),
40+
parameters={"id": id_, "tag": tag_},
41+
).one_or_none()
42+
43+
44+
def remove_tag(
45+
*,
46+
table: str,
47+
id_column: str,
48+
id_: int,
49+
tag_: str,
50+
expdb: Connection,
51+
) -> None:
52+
expdb.execute(
53+
text(
54+
f"""
55+
DELETE FROM {table}
56+
WHERE `{id_column}` = :id AND `tag` = :tag
57+
""",
58+
),
59+
parameters={"id": id_, "tag": tag_},
60+
)
61+
62+
63+
def select_tags(
64+
*,
65+
table: str,
66+
id_column: str,
67+
id_: int,
68+
expdb: Connection,
69+
) -> list[str]:
70+
tag_rows = expdb.execute(
71+
text(
72+
f"""
73+
SELECT `tag`
74+
FROM {table}
75+
WHERE `{id_column}` = :id
76+
""",
77+
),
78+
parameters={"id": id_},
79+
)
80+
return [row.tag for row in tag_rows]

src/database/tasks.py

Lines changed: 12 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33

44
from sqlalchemy import Connection, Row, text
55

6+
from database.tagging import insert_tag, remove_tag, select_tag, select_tags
7+
8+
_TABLE = "task_tag"
9+
_ID_COLUMN = "id"
10+
611

712
def get(id_: int, expdb: Connection) -> Row | None:
813
return expdb.execute(
@@ -93,51 +98,16 @@ def get_task_type_inout_with_template(task_type: int, expdb: Connection) -> Sequ
9398

9499

95100
def get_tags(id_: int, expdb: Connection) -> list[str]:
96-
tag_rows = expdb.execute(
97-
text(
98-
"""
99-
SELECT `tag`
100-
FROM task_tag
101-
WHERE `id` = :task_id
102-
""",
103-
),
104-
parameters={"task_id": id_},
105-
)
106-
return [row.tag for row in tag_rows]
101+
return select_tags(table=_TABLE, id_column=_ID_COLUMN, id_=id_, expdb=expdb)
107102

108103

109-
def tag(id_: int, tag_: str, *, user_id: int, connection: Connection) -> None:
110-
connection.execute(
111-
text(
112-
"""
113-
INSERT INTO task_tag(`id`, `tag`, `uploader`)
114-
VALUES (:task_id, :tag, :user_id)
115-
""",
116-
),
117-
parameters={"task_id": id_, "tag": tag_, "user_id": user_id},
118-
)
104+
def tag(id_: int, tag_: str, *, user_id: int, expdb: Connection) -> None:
105+
insert_tag(table=_TABLE, id_column=_ID_COLUMN, id_=id_, tag_=tag_, user_id=user_id, expdb=expdb)
119106

120107

121-
def get_tag(id_: int, tag_: str, connection: Connection) -> Row | None:
122-
return connection.execute(
123-
text(
124-
"""
125-
SELECT `id`, `tag`, `uploader`
126-
FROM task_tag
127-
WHERE `id` = :task_id AND `tag` = :tag
128-
""",
129-
),
130-
parameters={"task_id": id_, "tag": tag_},
131-
).one_or_none()
108+
def get_tag(id_: int, tag_: str, expdb: Connection) -> Row | None:
109+
return select_tag(table=_TABLE, id_column=_ID_COLUMN, id_=id_, tag_=tag_, expdb=expdb)
132110

133111

134-
def delete_tag(id_: int, tag_: str, connection: Connection) -> None:
135-
connection.execute(
136-
text(
137-
"""
138-
DELETE FROM task_tag
139-
WHERE `id` = :task_id AND `tag` = :tag
140-
""",
141-
),
142-
parameters={"task_id": id_, "tag": tag_},
143-
)
112+
def delete_tag(id_: int, tag_: str, expdb: Connection) -> None:
113+
remove_tag(table=_TABLE, id_column=_ID_COLUMN, id_=id_, tag_=tag_, expdb=expdb)

0 commit comments

Comments
 (0)