Skip to content

Commit 8e33411

Browse files
committed
Add aiohttp-swagger3 openapi.json creation
1 parent c1d8687 commit 8e33411

6 files changed

Lines changed: 188 additions & 247 deletions

File tree

docs/api/export_user_pgn.yaml

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
summary: Export user games as PGN
2+
description: >
3+
Streams the authenticated user's games in PGN format.
4+
5+
The requester must be logged in as the same user as `profileId`, unless the
6+
requester is an administrator. The response is streamed as PGN text and may
7+
contain multiple games.
8+
9+
tags:
10+
- Games
11+
12+
operationId: exportUserGamesPgn
13+
14+
parameters:
15+
- name: profileId
16+
in: path
17+
required: true
18+
description: Username/profile id whose games should be exported.
19+
schema:
20+
type: string
21+
example: gbtami
22+
23+
- name: max
24+
in: query
25+
required: false
26+
description: >
27+
Maximum number of latest games to export. Must be a positive integer.
28+
If omitted, all matching games are exported.
29+
schema:
30+
type: integer
31+
minimum: 1
32+
example: 100
33+
34+
- name: filter
35+
in: query
36+
required: false
37+
description: >
38+
Optional game filter. If omitted, all games are exported.
39+
schema:
40+
type: string
41+
enum:
42+
- all
43+
- win
44+
- loss
45+
- rated
46+
- playing
47+
- import
48+
- me
49+
- perf
50+
default: all
51+
example: rated
52+
53+
- name: variant
54+
in: query
55+
required: false
56+
description: >
57+
Variant filter, mainly useful together with filter=perf.
58+
Use pychess variant names, for example chess, crazyhouse, shogi, xiangqi,
59+
capablanca, or chess960-style names where supported.
60+
schema:
61+
type: string
62+
example: chess
63+
64+
- name: x
65+
in: query
66+
required: false
67+
description: >
68+
Engine level filter used by some loss/performance views.
69+
This is passed through to the server's existing game filtering logic.
70+
schema:
71+
type: integer
72+
minimum: 1
73+
example: 3
74+
75+
responses:
76+
"200":
77+
description: >
78+
PGN export stream. The body may be empty if the profile does not exist,
79+
the requester is anonymous, or no games match the filters.
80+
content:
81+
text/pgn:
82+
schema:
83+
type: string
84+
description: One or more PGN games concatenated into a text stream.
85+
example: |
86+
[Event "Rated chess game"]
87+
[Site "https://www.pychess.org"]
88+
[White "Alice"]
89+
[Black "Bob"]
90+
[Result "1-0"]
91+
92+
1. e4 e5 2. Nf3 Nc6 1-0
93+
94+
"400":
95+
description: Invalid query parameter, for example max is not a positive integer.
96+
content:
97+
text/plain:
98+
schema:
99+
type: string
100+
example: Query parameter 'max' must be a positive integer.
101+
102+
"403":
103+
description: The authenticated user is not allowed to export this user's games.
104+
content:
105+
text/plain:
106+
schema:
107+
type: string
108+
example: Users can only export their own games.
109+

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies = [
1010
"aiohttp-jinja2==1.6",
1111
"aiohttp-session==2.12.1",
1212
"aiohttp-sse==2.2.0",
13+
"aiohttp-swagger3==0.10.0",
1314
"brotli==1.2.0",
1415
"cryptography==48.0.0",
1516
"discord-py==2.7.1",

server/game_api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from aiohttp_sse import sse_response
1111
import pymongo
1212
from pymongo.errors import BulkWriteError
13+
from aiohttp_swagger3 import swagger_doc
1314

1415
from compress import C2R, decode_move_standard
1516
from const import DARK_FEN, STARTED, MATE, INVALIDMOVE, VARIANTEND, CLAIM, SSE_GET_TIMEOUT, SWISS
@@ -651,6 +652,7 @@ async def get_games(request: web.Request) -> web.StreamResponse:
651652
)
652653

653654

655+
@swagger_doc("docs/api/export_user_pgn.yaml")
654656
async def export(request: web.Request) -> web.StreamResponse:
655657
app_state = get_app_state(request.app)
656658
profileId = request.match_info.get("profileId")

server/server.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import jinja2
1919

2020
from pymongo import AsyncMongoClient
21+
from aiohttp_swagger3 import SwaggerDocs, SwaggerInfo
2122

2223
from db_wrapper import AsyncDBWrapper
2324
from middlewares import (
@@ -58,7 +59,15 @@ def make_app(
5859
simple_cookie_storage: bool = False,
5960
anon_as_test_users: bool = False,
6061
) -> Application:
62+
6163
app = web.Application()
64+
65+
swagger = SwaggerDocs(
66+
app,
67+
validate=False,
68+
info=SwaggerInfo(title="Pychess API", version="1.0.0"),
69+
)
70+
6271
app.middlewares.append(redirect_to_https)
6372
app[request_protection_state_key] = RequestProtectionState()
6473
app.middlewares.append(request_protection_middleware)
@@ -104,11 +113,27 @@ def make_app(
104113
app.on_shutdown.append(shutdown)
105114
app.on_cleanup.append(close_mongodb_client)
106115

116+
async def openapi_json(request):
117+
return web.json_response(swagger.spec)
118+
119+
app.router.add_get("/openapi.json", openapi_json)
120+
107121
# Setup routes.
108122
for route in get_routes:
109-
app.router.add_get(route[0], route[1], allow_head=False)
123+
path, handler = route
124+
if path == "/api/games/user/{profileId}/pgn":
125+
swagger.add_get(
126+
path,
127+
handler,
128+
allow_head=False,
129+
)
130+
else:
131+
app.router.add_get(path, handler, allow_head=False)
132+
110133
for route in post_routes:
111-
app.router.add_post(route[0], route[1])
134+
path, handler = route
135+
app.router.add_post(path, handler)
136+
112137
app.router.add_static("/static", "static", append_version=True)
113138
app.middlewares.append(handle_404)
114139

0 commit comments

Comments
 (0)