Skip to content

Commit eb77c1e

Browse files
Copilotkraktus
andcommitted
Add proper TypedDict definitions for puzzle data and create puzzle tests
Co-authored-by: kraktus <56031107+kraktus@users.noreply.github.com>
1 parent 020e2a9 commit eb77c1e

File tree

6 files changed

+137
-14
lines changed

6 files changed

+137
-14
lines changed

berserk/__init__.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22

33
from importlib import metadata
44

5-
berserk_metadata = metadata.metadata(__package__) # type: ignore
6-
7-
__author__ = "Lichess" # FIXME how to retrieve it from berserk_metadata?
8-
__email__ = berserk_metadata["Author-email"]
9-
__version__ = berserk_metadata["Version"]
5+
try:
6+
berserk_metadata = metadata.metadata(__package__) # type: ignore
7+
__author__ = "Lichess" # FIXME how to retrieve it from berserk_metadata?
8+
__email__ = berserk_metadata["Author-email"]
9+
__version__ = berserk_metadata["Version"]
10+
except Exception:
11+
# Fallback for development environment
12+
__author__ = "Lichess"
13+
__email__ = "contact@lichess.org"
14+
__version__ = "0.0.0dev"
1015

1116

1217
from .clients import Client
@@ -19,6 +24,7 @@
1924
OnlineLightUser,
2025
OpeningStatistic,
2126
PaginatedTeams,
27+
Puzzle,
2228
PuzzleRace,
2329
SwissInfo,
2430
SwissResult,
@@ -48,6 +54,7 @@
4854
"OpeningStatistic",
4955
"PaginatedTeams",
5056
"PGN",
57+
"Puzzle",
5158
"PuzzleRace",
5259
"Requestor",
5360
"SwissInfo",

berserk/__init__.py.backup

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""Top-level package for berserk."""
2+
3+
from importlib import metadata
4+
5+
berserk_metadata = metadata.metadata(__package__) # type: ignore
6+
7+
__author__ = "Lichess" # FIXME how to retrieve it from berserk_metadata?
8+
__email__ = berserk_metadata["Author-email"]
9+
__version__ = berserk_metadata["Version"]
10+
11+
12+
from .clients import Client
13+
from .types import (
14+
ArenaResult,
15+
BroadcastPlayer,
16+
Team,
17+
LightUser,
18+
ChapterIdName,
19+
OnlineLightUser,
20+
OpeningStatistic,
21+
PaginatedTeams,
22+
PuzzleRace,
23+
SwissInfo,
24+
SwissResult,
25+
TVFeed,
26+
)
27+
from .session import TokenSession
28+
from .session import Requestor
29+
from .formats import JSON
30+
from .formats import JSON_LIST
31+
from .formats import LIJSON
32+
from .formats import NDJSON
33+
from .formats import NDJSON_LIST
34+
from .formats import PGN
35+
36+
__all__ = [
37+
"ArenaResult",
38+
"BroadcastPlayer",
39+
"ChapterIdName",
40+
"Client",
41+
"JSON",
42+
"JSON_LIST",
43+
"LightUser",
44+
"LIJSON",
45+
"NDJSON",
46+
"NDJSON_LIST",
47+
"OnlineLightUser",
48+
"OpeningStatistic",
49+
"PaginatedTeams",
50+
"PGN",
51+
"PuzzleRace",
52+
"Requestor",
53+
"SwissInfo",
54+
"SwissResult",
55+
"Team",
56+
"TokenSession",
57+
"TVFeed",
58+
]

berserk/clients/puzzles.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,36 @@
55
from .. import models
66
from ..formats import NDJSON
77
from .base import BaseClient
8-
from ..types import PuzzleRace
8+
from ..types import Puzzle, PuzzleRace
99

1010

1111
class Puzzles(BaseClient):
1212
"""Client for puzzle-related endpoints."""
1313

14-
def get_daily(self) -> Dict[str, Any]:
14+
def get_daily(self) -> Puzzle:
1515
"""Get the current daily Lichess puzzle.
1616
1717
:return: current daily puzzle
1818
"""
1919
path = "/api/puzzle/daily"
20-
return self._r.get(path)
20+
return cast(Puzzle, self._r.get(path))
2121

22-
def get_next(self) -> Dict[str, Any]:
22+
def get_next(self) -> Puzzle:
2323
"""Get the next puzzle for the authenticated user.
2424
2525
:return: next puzzle
2626
"""
2727
path = "/api/puzzle/next"
28-
return self._r.get(path)
28+
return cast(Puzzle, self._r.get(path))
2929

30-
def get(self, id: str) -> Dict[str, Any]:
30+
def get(self, id: str) -> Puzzle:
3131
"""Get a puzzle by its id.
3232
3333
:param id: the id of the puzzle to retrieve
3434
:return: the puzzle
3535
"""
3636
path = f"/api/puzzle/{id}"
37-
return self._r.get(path)
37+
return cast(Puzzle, self._r.get(path))
3838

3939
def get_puzzle_activity(
4040
self, max: int | None = None, before: int | None = None

berserk/types/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .challenges import ChallengeJson
77
from .common import ClockConfig, ExternalEngine, LightUser, OnlineLightUser, VariantKey
88
from .fide import FidePlayer
9-
from .puzzles import PuzzleRace
9+
from .puzzles import Puzzle, PuzzleGame, PuzzleRace
1010
from .opening_explorer import (
1111
OpeningExplorerRating,
1212
OpeningStatistic,
@@ -37,6 +37,8 @@
3737
"Perf",
3838
"Preferences",
3939
"Profile",
40+
"Puzzle",
41+
"PuzzleGame",
4042
"PuzzleRace",
4143
"Speed",
4244
"StreamerInfo",

berserk/types/puzzles.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,39 @@
11
from __future__ import annotations
22

3-
from typing_extensions import TypedDict
3+
from typing import List
4+
from typing_extensions import TypedDict, NotRequired
5+
6+
from .common import LightUser
7+
8+
9+
class PuzzleGame(TypedDict):
10+
# Game ID from which the puzzle was extracted
11+
id: str
12+
# Full PGN of the game
13+
pgn: str
14+
# Time control used in the game
15+
clock: str
16+
17+
18+
class Puzzle(TypedDict):
19+
# Puzzle unique identifier
20+
id: str
21+
# FEN position for the puzzle
22+
fen: str
23+
# Array of moves (the solution)
24+
moves: List[str]
25+
# Puzzle difficulty rating
26+
rating: int
27+
# Number of times the puzzle has been attempted
28+
plays: int
29+
# Puzzle themes/tags
30+
themes: List[str]
31+
# Information about the source game
32+
game: PuzzleGame
33+
# Initial move that sets up the puzzle position (optional)
34+
initialMove: NotRequired[str]
35+
# Solution moves (optional, might be same as moves)
36+
solution: NotRequired[List[str]]
437

538

639
class PuzzleRace(TypedDict):

tests/clients/test_puzzles.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import pytest
2+
3+
from berserk import Client, Puzzle
4+
from utils import validate, skip_if_older_3_dot_10
5+
6+
7+
class TestLichessPuzzles:
8+
@skip_if_older_3_dot_10
9+
@pytest.mark.vcr
10+
def test_get_daily(self):
11+
"""Test getting the daily puzzle (no authentication required)."""
12+
res = Client().puzzles.get_daily()
13+
validate(Puzzle, res)
14+
15+
@skip_if_older_3_dot_10
16+
@pytest.mark.vcr
17+
def test_get_puzzle_by_id(self):
18+
"""Test getting a puzzle by its ID (no authentication required)."""
19+
# Using a known puzzle ID for testing
20+
puzzle_id = "ponnQ" # This is a common example puzzle ID from Lichess
21+
res = Client().puzzles.get(puzzle_id)
22+
validate(Puzzle, res)
23+
assert res["id"] == puzzle_id

0 commit comments

Comments
 (0)