Skip to content

Commit 3014e44

Browse files
authored
Dataset untag (#323)
Adds the dataset untag functionality. Introduces two endpoints: - `POST /datasets/untag` which mimicks the I/O of the PHP API (modulo error handling) - `DEL /datasets/{id}/tag?tag=...` which is more semantically correct.
1 parent 3e7c4fc commit 3014e44

53 files changed

Lines changed: 617 additions & 179 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ authors = [
66
]
77
description = "The Python-based REST API for OpenML."
88
readme = "README.md"
9-
requires-python = ">=3.12"
9+
requires-python = ">=3.14"
1010
classifiers = [
1111
"Programming Language :: Python :: 3",
1212
"License :: OSI Approved :: MIT License",

src/core/access.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
from typing import Any
2-
3-
from sqlalchemy.engine import Row
1+
from typing import TYPE_CHECKING, Any
42

53
from database.users import User
64
from schemas.datasets.openml import Visibility
75

6+
if TYPE_CHECKING:
7+
from sqlalchemy.engine import Row
8+
89

910
async def _user_has_access(
1011
dataset: Row[Any],

src/core/errors.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
"""
66

77
from http import HTTPStatus
8+
from typing import TYPE_CHECKING
89

9-
from fastapi import Request
10-
from fastapi.exceptions import RequestValidationError
1110
from fastapi.responses import JSONResponse
1211

12+
if TYPE_CHECKING:
13+
from fastapi import Request
14+
from fastapi.exceptions import RequestValidationError
15+
1316
# =============================================================================
1417
# Base Exception
1518
# =============================================================================

src/core/formatting.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import html
2-
3-
from sqlalchemy.engine import Row
2+
from typing import TYPE_CHECKING
43

54
from config import load_routing_configuration
65
from schemas.datasets.openml import DatasetFileFormat
76

7+
if TYPE_CHECKING:
8+
from sqlalchemy.engine import Row
9+
810

911
def _str_to_bool(string: str) -> bool:
1012
if string.casefold() in ["true", "1", "yes", "y"]:

src/core/logging.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
import uuid
66
from collections.abc import Awaitable, Callable
77
from pathlib import Path
8+
from typing import TYPE_CHECKING
89

910
from loguru import logger
10-
from starlette.requests import Request
11-
from starlette.responses import Response
1211

1312
from config import load_configuration
1413

14+
if TYPE_CHECKING:
15+
from starlette.requests import Request
16+
from starlette.responses import Response
17+
1518

1619
def setup_log_sinks(configuration_file: Path | None = None) -> None:
1720
"""Configure loguru based on app configuration."""

src/database/datasets.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
import datetime
44
from collections import defaultdict
5+
from typing import TYPE_CHECKING
56

67
from sqlalchemy import text
7-
from sqlalchemy.engine import Row
88
from sqlalchemy.exc import IntegrityError
9-
from sqlalchemy.ext.asyncio import AsyncConnection
109

1110
from database.exceptions import (
1211
_DUPLICATE_ENTRY,
@@ -16,6 +15,10 @@
1615
)
1716
from schemas.datasets.openml import Feature
1817

18+
if TYPE_CHECKING:
19+
from sqlalchemy.engine import Row
20+
from sqlalchemy.ext.asyncio import AsyncConnection
21+
1922

2023
async def get(id_: int, connection: AsyncConnection) -> Row | None:
2124
row = await connection.execute(
@@ -45,6 +48,33 @@ async def get_file(*, file_id: int, connection: AsyncConnection) -> Row | None:
4548
return row.one_or_none()
4649

4750

51+
async def get_tag(dataset_id: int, tag: str, connection: AsyncConnection) -> Row | None:
52+
return (
53+
await connection.execute(
54+
text(
55+
"""
56+
SELECT *
57+
FROM dataset_tag
58+
WHERE id = :dataset_id AND tag = :tag
59+
""",
60+
),
61+
parameters={"dataset_id": dataset_id, "tag": tag},
62+
)
63+
).first()
64+
65+
66+
async def delete_tag(dataset_id: int, tag: str, connection: AsyncConnection) -> None:
67+
await connection.execute(
68+
text(
69+
"""
70+
DELETE FROM dataset_tag
71+
WHERE id = :dataset_id AND tag = :tag
72+
""",
73+
),
74+
parameters={"dataset_id": dataset_id, "tag": tag},
75+
)
76+
77+
4878
async def get_tags_for(id_: int, connection: AsyncConnection) -> list[str]:
4979
row = await connection.execute(
5080
text(

src/database/evaluations.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
from collections.abc import Sequence
2-
from typing import cast
2+
from typing import TYPE_CHECKING, cast
33

44
from sqlalchemy import Row, text
5-
from sqlalchemy.ext.asyncio import AsyncConnection
65

76
from core.formatting import _str_to_bool
87
from schemas.datasets.openml import EstimationProcedure
98

9+
if TYPE_CHECKING:
10+
from sqlalchemy.ext.asyncio import AsyncConnection
11+
1012

1113
async def get_math_functions(function_type: str, connection: AsyncConnection) -> Sequence[Row]:
1214
rows = await connection.execute(

src/database/flows.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from collections.abc import Sequence
2-
from typing import cast
2+
from typing import TYPE_CHECKING, cast
33

44
from sqlalchemy import Row, text
5-
from sqlalchemy.ext.asyncio import AsyncConnection
5+
6+
if TYPE_CHECKING:
7+
from sqlalchemy.ext.asyncio import AsyncConnection
68

79

810
async def get_subflows(for_flow: int, expdb: AsyncConnection) -> Sequence[Row]:

src/database/qualities.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from collections import defaultdict
22
from collections.abc import Iterable
3+
from typing import TYPE_CHECKING
34

45
from sqlalchemy import text
5-
from sqlalchemy.ext.asyncio import AsyncConnection
66

77
from schemas.datasets.openml import Quality
88

9+
if TYPE_CHECKING:
10+
from sqlalchemy.ext.asyncio import AsyncConnection
11+
912

1013
async def get_for_dataset(dataset_id: int, connection: AsyncConnection) -> list[Quality]:
1114
row = await connection.execute(

src/database/runs.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""Database queries for run-related data."""
22

33
from collections.abc import Sequence
4-
from typing import cast
4+
from typing import TYPE_CHECKING, cast
55

66
from sqlalchemy import Row, text
7-
from sqlalchemy.ext.asyncio import AsyncConnection
7+
8+
if TYPE_CHECKING:
9+
from sqlalchemy.ext.asyncio import AsyncConnection
810

911

1012
async def exist(id_: int, expdb: AsyncConnection) -> bool:

0 commit comments

Comments
 (0)