Skip to content
This repository was archived by the owner on Feb 18, 2026. It is now read-only.

Commit cbfb06b

Browse files
committed
Add response_model to FastAPI routes for OpenAPI schema generation
- Add response_model and response_model_exclude_none to all route endpoints - Simplify RequestAdapter by replacing openapi dict access with simple version key - Fix Address model with explicit tags field using Field(exclude=True) - Remove empty schemas directory - Fix list_block_txs to return list[Union[TxUtxo, TxAccount]] instead of Txs
1 parent 6a9711d commit cbfb06b

12 files changed

Lines changed: 116 additions & 29 deletions

File tree

gsrest/models/addresses.py

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

33
from typing import Any, Optional
44

5-
from pydantic import ConfigDict
5+
from pydantic import Field
66

77
from gsrest.models.base import APIModel
88
from gsrest.models.common import LabeledItemRef
@@ -13,13 +13,6 @@
1313
class Address(APIModel):
1414
"""Address model."""
1515

16-
# Allow extra fields for test fixtures that set .tags
17-
model_config = ConfigDict(
18-
populate_by_name=True,
19-
from_attributes=True,
20-
extra="allow",
21-
)
22-
2316
currency: str
2417
address: str
2518
entity: int
@@ -38,11 +31,12 @@ class Address(APIModel):
3831
actors: Optional[list[LabeledItemRef]] = None
3932
is_contract: Optional[bool] = None
4033
status: Optional[str] = None
34+
# tags field used by test fixtures only, excluded from serialization and schema
35+
tags: Optional[list[Any]] = Field(default=None, exclude=True)
4136

4237
def to_dict(self, shallow: bool = False) -> dict[str, Any]:
43-
"""Override to exclude extra fields (like 'tags') from serialization."""
38+
"""Override to exclude test-only fields from serialization."""
4439
result = super().to_dict(shallow=shallow)
45-
# Remove any extra fields that aren't part of the API response
4640
result.pop("tags", None)
4741
return result
4842

gsrest/routes/addresses.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55
from fastapi import APIRouter, Depends, Path, Query, Request
66

77
from gsrest.dependencies import ServiceContainer
8+
from gsrest.models import (
9+
Address,
10+
AddressTags,
11+
AddressTxs,
12+
Entity,
13+
Links,
14+
NeighborAddresses,
15+
RelatedAddresses,
16+
TagSummary,
17+
)
818
from gsrest.routes.base import (
919
RequestAdapter,
1020
apply_plugin_hooks,
@@ -30,6 +40,8 @@ def _normalize_page(page: Optional[str]) -> Optional[str]:
3040
"/addresses/{address}",
3141
summary="Get an address",
3242
operation_id="get_address",
43+
response_model=Address,
44+
response_model_exclude_none=True,
3345
)
3446
async def get_address(
3547
request: Request,
@@ -60,6 +72,8 @@ async def get_address(
6072
"/addresses/{address}/entity",
6173
summary="Get the entity of an address",
6274
operation_id="get_address_entity",
75+
response_model=Entity,
76+
response_model_exclude_none=True,
6377
)
6478
async def get_address_entity(
6579
request: Request,
@@ -90,6 +104,8 @@ async def get_address_entity(
90104
"/addresses/{address}/tag_summary",
91105
summary="Get attribution tag summary for a given address",
92106
operation_id="get_tag_summary_by_address",
107+
response_model=TagSummary,
108+
response_model_exclude_none=True,
93109
)
94110
async def get_tag_summary_by_address(
95111
request: Request,
@@ -121,6 +137,8 @@ async def get_tag_summary_by_address(
121137
"/addresses/{address}/tags",
122138
summary="Get attribution tags for a given address",
123139
operation_id="list_tags_by_address",
140+
response_model=AddressTags,
141+
response_model_exclude_none=True,
124142
)
125143
async def list_tags_by_address(
126144
request: Request,
@@ -160,6 +178,8 @@ async def list_tags_by_address(
160178
"/addresses/{address}/txs",
161179
summary="Get all transactions an address has been involved in",
162180
operation_id="list_address_txs",
181+
response_model=AddressTxs,
182+
response_model_exclude_none=True,
163183
)
164184
async def list_address_txs(
165185
request: Request,
@@ -219,6 +239,8 @@ async def list_address_txs(
219239
"/addresses/{address}/neighbors",
220240
summary="Get an address's neighbors in the address graph",
221241
operation_id="list_address_neighbors",
242+
response_model=NeighborAddresses,
243+
response_model_exclude_none=True,
222244
)
223245
async def list_address_neighbors(
224246
request: Request,
@@ -269,6 +291,8 @@ async def list_address_neighbors(
269291
"/addresses/{address}/links",
270292
summary="Get outgoing transactions between two addresses",
271293
operation_id="list_address_links",
294+
response_model=Links,
295+
response_model_exclude_none=True,
272296
)
273297
async def list_address_links(
274298
request: Request,
@@ -326,6 +350,8 @@ async def list_address_links(
326350
"/addresses/{address}/related_addresses",
327351
summary="Get related addresses to the input address",
328352
operation_id="list_related_addresses",
353+
response_model=RelatedAddresses,
354+
response_model_exclude_none=True,
329355
)
330356
async def list_related_addresses(
331357
request: Request,

gsrest/routes/base.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,8 @@ def __getitem__(self, key):
5454
return self._fastapi_request.app.state.config
5555
elif key == "request_config":
5656
return {"show_private_tags": self._show_private_tags}
57-
elif key == "openapi":
58-
# Used by general_service for version info
59-
# Try to get from generated OpenAPI schema, fall back to app version
60-
schema = getattr(self._fastapi_request.app.state, "openapi_schema", None)
61-
if schema:
62-
return schema
63-
# Fallback: construct minimal openapi info from app
64-
return {"info": {"version": self._fastapi_request.app.version}}
57+
elif key == "version":
58+
return self._fastapi_request.app.version
6559
raise KeyError(key)
6660

6761
@property

gsrest/routes/blocks.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
"""Block API routes"""
22

3+
from typing import Union
4+
35
from fastapi import APIRouter, Depends, Path, Request
46

57
from gsrest.dependencies import ServiceContainer
8+
from gsrest.models import Block, BlockAtDate, TxAccount, TxUtxo
69
from gsrest.routes.base import (
710
RequestAdapter,
811
apply_plugin_hooks,
@@ -19,6 +22,8 @@
1922
"/blocks/{height}",
2023
summary="Get a block by its height",
2124
operation_id="get_block",
25+
response_model=Block,
26+
response_model_exclude_none=True,
2227
)
2328
async def get_block(
2429
request: Request,
@@ -45,6 +50,8 @@ async def get_block(
4550
"/blocks/{height}/txs",
4651
summary="Get block transactions",
4752
operation_id="list_block_txs",
53+
response_model=list[Union[TxUtxo, TxAccount]],
54+
response_model_exclude_none=True,
4855
)
4956
async def list_block_txs(
5057
request: Request,
@@ -71,6 +78,8 @@ async def list_block_txs(
7178
"/block_by_date/{date}",
7279
summary="Get block by date",
7380
operation_id="get_block_by_date",
81+
response_model=BlockAtDate,
82+
response_model_exclude_none=True,
7483
)
7584
async def get_block_by_date(
7685
request: Request,

gsrest/routes/entities.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55
from fastapi import APIRouter, Depends, Path, Query, Request
66

77
from gsrest.dependencies import ServiceContainer
8+
from gsrest.models import (
9+
AddressTags,
10+
AddressTxs,
11+
Entity,
12+
EntityAddresses,
13+
Links,
14+
NeighborEntities,
15+
SearchResultLevel1,
16+
)
817
from gsrest.routes.base import (
918
RequestAdapter,
1019
apply_plugin_hooks,
@@ -31,6 +40,8 @@ def _normalize_page(page: Optional[str]) -> Optional[str]:
3140
"/entities/{entity}",
3241
summary="Get an entity",
3342
operation_id="get_entity",
43+
response_model=Entity,
44+
response_model_exclude_none=True,
3445
)
3546
async def get_entity(
3647
request: Request,
@@ -65,6 +76,8 @@ async def get_entity(
6576
"/entities/{entity}/addresses",
6677
summary="Get an entity's addresses",
6778
operation_id="list_entity_addresses",
79+
response_model=EntityAddresses,
80+
response_model_exclude_none=True,
6881
)
6982
async def list_entity_addresses(
7083
request: Request,
@@ -100,6 +113,8 @@ async def list_entity_addresses(
100113
summary="Get an entity's neighbors in the entity graph",
101114
operation_id="list_entity_neighbors",
102115
deprecated=True,
116+
response_model=NeighborEntities,
117+
response_model_exclude_none=True,
103118
)
104119
async def list_entity_neighbors(
105120
request: Request,
@@ -159,6 +174,8 @@ async def list_entity_neighbors(
159174
summary="Get transactions between two entities",
160175
operation_id="list_entity_links",
161176
deprecated=True,
177+
response_model=Links,
178+
response_model_exclude_none=True,
162179
)
163180
async def list_entity_links(
164181
request: Request,
@@ -217,6 +234,8 @@ async def list_entity_links(
217234
summary="Get address tags for a given entity",
218235
operation_id="list_address_tags_by_entity",
219236
deprecated=True,
237+
response_model=AddressTags,
238+
response_model_exclude_none=True,
220239
)
221240
async def list_address_tags_by_entity(
222241
request: Request,
@@ -252,6 +271,8 @@ async def list_address_tags_by_entity(
252271
summary="Get all transactions an entity has been involved in",
253272
operation_id="list_entity_txs",
254273
deprecated=True,
274+
response_model=AddressTxs,
275+
response_model_exclude_none=True,
255276
)
256277
async def list_entity_txs(
257278
request: Request,
@@ -311,6 +332,8 @@ async def list_entity_txs(
311332
"/entities/{entity}/search",
312333
summary="Search neighbors of an entity",
313334
operation_id="search_entity_neighbors",
335+
response_model=list[SearchResultLevel1],
336+
response_model_exclude_none=True,
314337
)
315338
async def search_entity_neighbors(
316339
request: Request,

gsrest/routes/general.py

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

55
from fastapi import APIRouter, Depends, Query, Request
66

7+
from gsrest.dependencies import ServiceContainer
8+
from gsrest.models import SearchResult, Stats
79
from gsrest.routes.base import (
810
RequestAdapter,
911
apply_plugin_hooks,
@@ -12,7 +14,6 @@
1214
get_tagstore_access_groups,
1315
to_json_response,
1416
)
15-
from gsrest.dependencies import ServiceContainer
1617
import gsrest.service.general_service as service
1718

1819
router = APIRouter()
@@ -22,6 +23,8 @@
2223
"/stats",
2324
summary="Get statistics of supported currencies",
2425
operation_id="get_statistics",
26+
response_model=Stats,
27+
response_model_exclude_none=True,
2528
)
2629
async def get_statistics(
2730
request: Request,
@@ -44,6 +47,8 @@ async def get_statistics(
4447
"/search",
4548
summary="Returns matching addresses, transactions and labels",
4649
operation_id="search",
50+
response_model=SearchResult,
51+
response_model_exclude_none=True,
4752
)
4853
async def search(
4954
request: Request,

gsrest/routes/rates.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from fastapi import APIRouter, Depends, Path, Request
44

55
from gsrest.dependencies import ServiceContainer
6+
from gsrest.models import Rates
67
from gsrest.routes.base import (
78
RequestAdapter,
89
apply_plugin_hooks,
@@ -19,6 +20,8 @@
1920
"/rates/{height}",
2021
summary="Get exchange rates for a given block height",
2122
operation_id="get_exchange_rates",
23+
response_model=Rates,
24+
response_model_exclude_none=True,
2225
)
2326
async def get_exchange_rates(
2427
request: Request,

gsrest/routes/tags.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
from pydantic import BaseModel
77

88
from gsrest.dependencies import ServiceContainer
9+
from gsrest.models import (
10+
Actor,
11+
AddressTags,
12+
Concept,
13+
Taxonomy,
14+
UserTagReportResponse,
15+
)
916
from gsrest.routes.base import (
1017
RequestAdapter,
1118
apply_plugin_hooks,
@@ -41,6 +48,8 @@ class UserReportedTag(BaseModel):
4148
"/tags",
4249
summary="Get address tags by label",
4350
operation_id="list_address_tags",
51+
response_model=AddressTags,
52+
response_model_exclude_none=True,
4453
)
4554
async def list_address_tags(
4655
request: Request,
@@ -75,6 +84,8 @@ async def list_address_tags(
7584
"/tags/actors/{actor}",
7685
summary="Get an actor by ID",
7786
operation_id="get_actor",
87+
response_model=Actor,
88+
response_model_exclude_none=True,
7889
)
7990
async def get_actor(
8091
request: Request,
@@ -101,6 +112,8 @@ async def get_actor(
101112
"/tags/actors/{actor}/tags",
102113
summary="Get tags associated with an actor",
103114
operation_id="get_actor_tags",
115+
response_model=AddressTags,
116+
response_model_exclude_none=True,
104117
)
105118
async def get_actor_tags(
106119
request: Request,
@@ -135,6 +148,8 @@ async def get_actor_tags(
135148
"/tags/taxonomies",
136149
summary="List all taxonomies",
137150
operation_id="list_taxonomies",
151+
response_model=list[Taxonomy],
152+
response_model_exclude_none=True,
138153
)
139154
async def list_taxonomies(
140155
request: Request,
@@ -157,6 +172,8 @@ async def list_taxonomies(
157172
"/tags/taxonomies/{taxonomy}/concepts",
158173
summary="List concepts for a taxonomy",
159174
operation_id="list_concepts",
175+
response_model=list[Concept],
176+
response_model_exclude_none=True,
160177
)
161178
async def list_concepts(
162179
request: Request,
@@ -183,6 +200,8 @@ async def list_concepts(
183200
"/tags/report-tag",
184201
summary="Report a new tag",
185202
operation_id="report_tag",
203+
response_model=UserTagReportResponse,
204+
response_model_exclude_none=True,
186205
)
187206
async def report_tag(
188207
request: Request,

gsrest/routes/tokens.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from fastapi import APIRouter, Depends, Path, Request
44

55
from gsrest.dependencies import ServiceContainer
6+
from gsrest.models import TokenConfigs
67
from gsrest.routes.base import (
78
RequestAdapter,
89
apply_plugin_hooks,
@@ -19,6 +20,8 @@
1920
"/supported_tokens/",
2021
summary="Get supported tokens for a currency",
2122
operation_id="list_supported_tokens",
23+
response_model=TokenConfigs,
24+
response_model_exclude_none=True,
2225
)
2326
async def list_supported_tokens(
2427
request: Request,

0 commit comments

Comments
 (0)