Skip to content

Commit 36889f3

Browse files
authored
Merge pull request #177 from lsst-sqre:tickets/DM-50016
DM-50016: Employ pagination in the Ook Links API
2 parents ef0adaf + 41788ba commit 36889f3

File tree

9 files changed

+1829
-352
lines changed

9 files changed

+1829
-352
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
<!-- scriv-insert-here -->
44

5+
<a id='changelog-0.12.0'></a>
6+
## 0.12.0 (2025-04-16)
7+
8+
### New features
9+
10+
- The Links API collection endpoints now use pagination for improved performance and usability. Ook uses keyset pagination, so look for a Links header with `next`, `prev`, and `first` links. Use these URLs to advance to the next page. The `X-Total-Count` header indicates the total number of items in the collection. Pagination applies to the following endpoints:
11+
12+
- `GET /ook/links/domains/sdm/schemas`
13+
- `GET /ook/links/domains/sdm/schemas/:schema/tables`
14+
- `GET /ook/links/domains/sdm/schemas/:schema/tables/:table/columns`
15+
516
<a id='changelog-0.11.0'></a>
617

718
## 0.11.0 (2025-04-04)

src/ook/dependencies/context.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from dataclasses import dataclass
1010
from typing import Annotated, Any
1111

12-
from fastapi import Depends, Request
12+
from fastapi import Depends, Request, Response
1313
from safir.dependencies.db_session import db_session_dependency
1414
from safir.dependencies.logger import logger_dependency
1515
from sqlalchemy.ext.asyncio import async_scoped_session
@@ -37,6 +37,9 @@ class RequestContext:
3737
request: Request
3838
"""The incoming request."""
3939

40+
response: Response
41+
"""The response to the request."""
42+
4043
logger: BoundLogger
4144
"""The request logger, rebound with discovered context."""
4245

@@ -73,6 +76,7 @@ def __init__(self) -> None:
7376
async def __call__(
7477
self,
7578
request: Request,
79+
response: Response,
7680
session: Annotated[
7781
async_scoped_session, Depends(db_session_dependency)
7882
],
@@ -81,6 +85,7 @@ async def __call__(
8185
"""Create a per-request context and return it."""
8286
return RequestContext(
8387
request=request,
88+
response=response,
8489
logger=logger,
8590
session=session,
8691
factory=Factory(

src/ook/domain/links.py

Lines changed: 87 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,106 +2,146 @@
22

33
from __future__ import annotations
44

5-
from dataclasses import dataclass
5+
from typing import Annotated, Literal
6+
7+
from pydantic import BaseModel, Field, RootModel
68

79
__all__ = [
810
"Link",
911
"LinksCollection",
1012
"SdmColumnLink",
1113
"SdmColumnLinksCollection",
14+
"SdmLinksCollection",
1215
"SdmSchemaLink",
1316
"SdmSchemaLinksCollection",
1417
"SdmTableLink",
1518
"SdmTableLinksCollection",
1619
]
1720

1821

19-
@dataclass(slots=True, kw_only=True)
20-
class Link:
21-
"""A link to documentation."""
22+
class Link(BaseModel):
23+
"""A link to documentation.
24+
25+
This is a Pydantic model to facilitate the parsing of SQLAlchemy queries
26+
directly into the domain.
27+
"""
2228

23-
html_url: str
24-
"""The URL to the documentation page."""
29+
html_url: str = Field(description="The URL to the documentation page.")
2530

26-
title: str
27-
"""The title of the documentation."""
31+
title: str = Field(description="The title of the documentation.")
2832

29-
type: str
30-
"""The type of documentation."""
33+
type: str = Field(description="The type of documentation.")
3134

32-
collection_title: str | None = None
33-
"""The title of the collection of documentation this link refers to."""
35+
collection_title: str | None = Field(
36+
None,
37+
description=(
38+
"The title of the collection of documentation this link refers to."
39+
),
40+
)
3441

3542

36-
@dataclass(slots=True, kw_only=True)
3743
class SdmSchemaLink(Link):
3844
"""A link to an SDM schema's documentation."""
3945

40-
name: str
41-
"""The name of the schema."""
46+
schema_name: str = Field(description="The name of the schema.")
4247

4348

44-
@dataclass(slots=True, kw_only=True)
4549
class SdmTableLink(Link):
4650
"""A link to an SDM table's documentation."""
4751

48-
schema_name: str
49-
"""The name of the schema."""
52+
schema_name: str = Field(description="The name of the schema.")
5053

51-
name: str
52-
"""The name of the table."""
54+
table_name: str = Field(description="The name of the table.")
5355

5456

55-
@dataclass(slots=True, kw_only=True)
5657
class SdmColumnLink(Link):
5758
"""A link to an SDM column's documentation."""
5859

59-
schema_name: str
60-
"""The name of the schema."""
60+
schema_name: str = Field(description="The name of the schema.")
6161

62-
table_name: str
63-
"""The name of the table."""
62+
table_name: str = Field(description="The name of the table.")
6463

65-
name: str
66-
"""The name of the column."""
64+
column_name: str = Field(description="The name of the column.")
6765

6866

69-
@dataclass(slots=True, kw_only=True)
70-
class LinksCollection[T: Link]:
71-
"""A collection of links to documentation of a specific entity."""
67+
class LinksCollection[T: Link](BaseModel):
68+
"""A collection of links to documentation of a specific entity.
7269
73-
links: list[T]
74-
"""The documentation links."""
70+
This is a Pydantic model to facilitate the parsing of SQLAlchemy queries
71+
directly into the domain.
72+
"""
73+
74+
links: list[T] = Field(description="The documentation links.")
7575

7676

77-
@dataclass(slots=True, kw_only=True)
7877
class SdmSchemaLinksCollection(LinksCollection[SdmSchemaLink]):
7978
"""A collection of links to an SDM schema."""
8079

81-
schema_name: str
82-
"""The name of the schema."""
80+
domain: Literal["sdm"] = Field(
81+
description="The links domain of the entity."
82+
)
83+
84+
entity_type: Literal["schema"] = Field(
85+
description="The type of the entity in the domain."
86+
)
87+
88+
schema_name: str = Field(description="The name of the schema.")
8389

8490

85-
@dataclass(slots=True, kw_only=True)
8691
class SdmTableLinksCollection(LinksCollection[SdmTableLink]):
8792
"""A collection of links to an SDM table."""
8893

89-
schema_name: str
90-
"""The name of the schema."""
94+
domain: Literal["sdm"] = Field(
95+
description="The links domain of the entity."
96+
)
97+
98+
entity_type: Literal["table"] = Field(
99+
description="The type of the entity in the domain."
100+
)
101+
102+
schema_name: str = Field(description="The name of the schema.")
103+
104+
table_name: str = Field(description="The name of the table.")
91105

92-
table_name: str
93-
"""The name of the table."""
106+
tap_table_index: int | None = Field(
107+
description="The sorting index of the table in the TAP schema."
108+
)
94109

95110

96-
@dataclass(slots=True, kw_only=True)
97111
class SdmColumnLinksCollection(LinksCollection[SdmColumnLink]):
98112
"""A collection of links to SDM columns."""
99113

100-
schema_name: str
101-
"""The name of the schema."""
114+
domain: Literal["sdm"] = Field(
115+
description="The links domain of the entity."
116+
)
117+
118+
entity_type: Literal["column"] = Field(
119+
description="The type of the entity in the domain."
120+
)
121+
122+
schema_name: str = Field(description="The name of the schema.")
123+
124+
table_name: str = Field(description="The name of the table.")
125+
126+
column_name: str = Field(description="The name of the column.")
127+
128+
tap_column_index: int | None = Field(
129+
description="The sorting index of the column in the TAP schema."
130+
)
131+
132+
133+
SdmLinksCollection = RootModel[
134+
Annotated[
135+
SdmSchemaLinksCollection
136+
| SdmTableLinksCollection
137+
| SdmColumnLinksCollection,
138+
Field(discriminator="entity_type"),
139+
]
140+
]
141+
"""A generic collection of SDM links for any SDM entity type.
102142
103-
table_name: str
104-
"""The name of the table."""
143+
Use this root model to parse SQLAlchemy query results when the domain type
144+
can be any of the three SDM entity types: schema, table, or column.
105145
106-
column_name: str
107-
"""The name of the column."""
146+
Access the underlying collection using the ``root`` attribute.
147+
"""

0 commit comments

Comments
 (0)