Skip to content

Commit c8fdd19

Browse files
authored
refactor: implement pydantic schemas (#80)
* build: update project requirements * refactor: implement pydantic schemas * refactor: update services to schemas * refactor: update utils to schemas * refactor: implement schemas in endpoints * test: update unit tests
1 parent 96a457e commit c8fdd19

38 files changed

+2092
-1506
lines changed

app/api/endpoints/clubs.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,29 @@
22

33
from fastapi import APIRouter
44

5+
from app.schemas import clubs as schemas
56
from app.services.clubs.players import TransfermarktClubPlayers
67
from app.services.clubs.profile import TransfermarktClubProfile
78
from app.services.clubs.search import TransfermarktClubSearch
89

910
router = APIRouter()
1011

1112

12-
@router.get("/search/{club_name}")
13+
@router.get("/search/{club_name}", response_model=schemas.ClubSearch, response_model_exclude_none=True)
1314
def search_clubs(club_name: str, page_number: Optional[int] = 1) -> dict:
1415
tfmkt = TransfermarktClubSearch(query=club_name, page_number=page_number)
1516
found_clubs = tfmkt.search_clubs()
1617
return found_clubs
1718

1819

19-
@router.get("/{club_id}/profile")
20+
@router.get("/{club_id}/profile", response_model=schemas.ClubProfile, response_model_exclude_defaults=True)
2021
def get_club_profile(club_id: str) -> dict:
2122
tfmkt = TransfermarktClubProfile(club_id=club_id)
2223
club_profile = tfmkt.get_club_profile()
2324
return club_profile
2425

2526

26-
@router.get("/{club_id}/players")
27+
@router.get("/{club_id}/players", response_model=schemas.ClubPlayers, response_model_exclude_defaults=True)
2728
def get_club_players(club_id: str, season_id: Optional[str] = None) -> dict:
2829
tfmkt = TransfermarktClubPlayers(club_id=club_id, season_id=season_id)
2930
club_players = tfmkt.get_club_players()

app/api/endpoints/competitions.py

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

33
from fastapi import APIRouter
44

5+
from app.schemas import competitions as schemas
56
from app.services.competitions.clubs import TransfermarktCompetitionClubs
67
from app.services.competitions.search import TransfermarktCompetitionSearch
78

89
router = APIRouter()
910

1011

11-
@router.get("/search/{competition_name}")
12-
def search_competitions(competition_name: str, page_number: Optional[int] = 1) -> dict:
12+
@router.get("/search/{competition_name}", response_model=schemas.CompetitionSearch)
13+
def search_competitions(competition_name: str, page_number: Optional[int] = 1):
1314
tfmkt = TransfermarktCompetitionSearch(query=competition_name, page_number=page_number)
1415
competitions = tfmkt.search_competitions()
1516
return competitions
1617

1718

18-
@router.get("/{competition_id}/clubs")
19-
def get_competition_clubs(competition_id: str, season_id: Optional[str] = None) -> dict:
19+
@router.get("/{competition_id}/clubs", response_model=schemas.CompetitionClubs)
20+
def get_competition_clubs(competition_id: str, season_id: Optional[str] = None):
2021
tfmkt = TransfermarktCompetitionClubs(competition_id=competition_id, season_id=season_id)
2122
competition_clubs = tfmkt.get_competition_clubs()
2223
return competition_clubs

app/api/endpoints/players.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from fastapi import APIRouter
44

5+
from app.schemas import players as schemas
56
from app.services.players.achievements import TransfermarktPlayerAchievements
67
from app.services.players.injuries import TransfermarktPlayerInjuries
78
from app.services.players.jersey_numbers import TransfermarktPlayerJerseyNumbers
@@ -14,56 +15,56 @@
1415
router = APIRouter()
1516

1617

17-
@router.get("/search/{player_name}")
18+
@router.get("/search/{player_name}", response_model=schemas.PlayerSearch, response_model_exclude_none=True)
1819
def search_players(player_name: str, page_number: Optional[int] = 1):
1920
tfmkt = TransfermarktPlayerSearch(query=player_name, page_number=page_number)
2021
found_players = tfmkt.search_players()
2122
return found_players
2223

2324

24-
@router.get("/{player_id}/profile")
25+
@router.get("/{player_id}/profile", response_model=schemas.PlayerProfile, response_model_exclude_none=True)
2526
def get_player_profile(player_id: str):
2627
tfmkt = TransfermarktPlayerProfile(player_id=player_id)
2728
player_info = tfmkt.get_player_profile()
2829
return player_info
2930

3031

31-
@router.get("/{player_id}/market_value")
32+
@router.get("/{player_id}/market_value", response_model=schemas.PlayerMarketValue, response_model_exclude_none=True)
3233
def get_player_market_value(player_id: str):
3334
tfmkt = TransfermarktPlayerMarketValue(player_id=player_id)
3435
player_market_value = tfmkt.get_player_market_value()
3536
return player_market_value
3637

3738

38-
@router.get("/{player_id}/transfers")
39+
@router.get("/{player_id}/transfers", response_model=schemas.PlayerTransfers, response_model_exclude_none=True)
3940
def get_player_transfers(player_id: str):
4041
tfmkt = TransfermarktPlayerTransfers(player_id=player_id)
4142
player_market_value = tfmkt.get_player_transfers()
4243
return player_market_value
4344

4445

45-
@router.get("/{player_id}/jersey_numbers")
46+
@router.get("/{player_id}/jersey_numbers", response_model=schemas.PlayerJerseyNumbers, response_model_exclude_none=True)
4647
def get_player_jersey_numbers(player_id: str):
4748
tfmkt = TransfermarktPlayerJerseyNumbers(player_id=player_id)
4849
player_jerseynumbers = tfmkt.get_player_jersey_numbers()
4950
return player_jerseynumbers
5051

5152

52-
@router.get("/{player_id}/stats")
53+
@router.get("/{player_id}/stats", response_model=schemas.PlayerStats, response_model_exclude_none=True)
5354
def get_player_stats(player_id: str):
5455
tfmkt = TransfermarktPlayerStats(player_id=player_id)
5556
player_stats = tfmkt.get_player_stats()
5657
return player_stats
5758

5859

59-
@router.get("/{player_id}/injuries")
60+
@router.get("/{player_id}/injuries", response_model=schemas.PlayerInjuries, response_model_exclude_none=True)
6061
def get_player_injuries(player_id: str, page_number: Optional[int] = 1):
6162
tfmkt = TransfermarktPlayerInjuries(player_id=player_id, page_number=page_number)
6263
players_injuries = tfmkt.get_player_injuries()
6364
return players_injuries
6465

6566

66-
@router.get("/{player_id}/achievements")
67+
@router.get("/{player_id}/achievements", response_model=schemas.PlayerAchievements, response_model_exclude_none=True)
6768
def get_player_achievements(player_id: str):
6869
tfmkt = TransfermarktPlayerAchievements(player_id=player_id)
6970
player_achievements = tfmkt.get_player_achievements()

app/schemas/__init__.py

Whitespace-only changes.

app/schemas/base.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from datetime import datetime
2+
3+
from dateutil import parser
4+
from pydantic import BaseModel, ConfigDict, Field, field_validator
5+
from pydantic.alias_generators import to_camel
6+
7+
8+
class AuditMixin(BaseModel):
9+
updated_at: datetime = Field(default_factory=datetime.now)
10+
11+
12+
class TransfermarktBaseModel(BaseModel):
13+
model_config = ConfigDict(alias_generator=to_camel)
14+
15+
@field_validator(
16+
"date_of_birth",
17+
"joined_on",
18+
"contract",
19+
"founded_on",
20+
"members_date",
21+
"from_date",
22+
"until_date",
23+
"date",
24+
"contract_expires",
25+
"joined",
26+
"retired_since",
27+
mode="before",
28+
check_fields=False,
29+
)
30+
def parse_str_to_date(cls, v: str):
31+
try:
32+
return parser.parse(v).date() if v else None
33+
except parser.ParserError:
34+
return None
35+
36+
@field_validator(
37+
"current_market_value",
38+
"current_transfer_record",
39+
"market_value",
40+
"mean_market_value",
41+
"members",
42+
"total_market_value",
43+
"age",
44+
"goals",
45+
"assists",
46+
"yellow_cards",
47+
"red_cards",
48+
"minutes_played",
49+
"fee",
50+
"appearances",
51+
"games_missed",
52+
mode="before",
53+
check_fields=False,
54+
)
55+
def parse_str_to_int(cls, v: str):
56+
parsed_value = (
57+
(
58+
v.replace("bn", "000000000")
59+
.replace("m", "000000")
60+
.replace("k", "000")
61+
.replace("€", "")
62+
.replace(".", "")
63+
.replace("+", "")
64+
.replace(".", "")
65+
.replace("'", "")
66+
)
67+
if v and any(char.isdigit() for char in v)
68+
else None
69+
)
70+
return int(parsed_value) if parsed_value else None
71+
72+
@field_validator("height", mode="before", check_fields=False)
73+
def parse_height(cls, v: str):
74+
if not any(char.isdigit() for char in v):
75+
return None
76+
return int(v.replace(",", "").replace("m", ""))
77+
78+
@field_validator("days", mode="before", check_fields=False)
79+
def parse_days(cls, v: str):
80+
days = "".join(filter(str.isdigit, v))
81+
return int(days) if days else None

app/schemas/clubs/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from app.schemas.clubs.players import ClubPlayers as ClubPlayers
2+
from app.schemas.clubs.profile import ClubProfile as ClubProfile
3+
from app.schemas.clubs.search import ClubSearch as ClubSearch

app/schemas/clubs/players.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from datetime import date
2+
from typing import Optional
3+
4+
from app.schemas.base import AuditMixin, TransfermarktBaseModel
5+
6+
7+
class ClubPlayer(TransfermarktBaseModel):
8+
id: str
9+
name: str
10+
position: str
11+
date_of_birth: date
12+
age: int
13+
nationality: list[str]
14+
current_club: Optional[str] = None
15+
height: Optional[int] = None
16+
foot: Optional[str] = None
17+
joined_on: Optional[date] = None
18+
joined: Optional[str] = None
19+
signed_from: Optional[str] = None
20+
contract: Optional[date] = None
21+
market_value: Optional[int] = None
22+
status: Optional[str] = ""
23+
24+
25+
class ClubPlayers(TransfermarktBaseModel, AuditMixin):
26+
id: str
27+
players: list[ClubPlayer]

app/schemas/clubs/profile.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from datetime import date
2+
from typing import Optional
3+
4+
from app.schemas.base import TransfermarktBaseModel
5+
6+
7+
class ClubSquad(TransfermarktBaseModel):
8+
size: int
9+
average_age: float
10+
foreigners: int
11+
national_team_players: int
12+
13+
14+
class ClubLeague(TransfermarktBaseModel):
15+
id: str
16+
name: str
17+
country_id: str
18+
country_name: str
19+
tier: str
20+
21+
22+
class ClubProfile(TransfermarktBaseModel):
23+
id: str
24+
url: str
25+
name: str
26+
official_name: str
27+
image: str
28+
legal_form: Optional[str] = None
29+
address_line_1: str
30+
address_line_2: str
31+
address_line_3: str
32+
tel: str
33+
fax: str
34+
website: str
35+
founded_on: date
36+
members: Optional[int] = None
37+
members_date: Optional[date] = None
38+
other_sports: Optional[list[str]] = None
39+
colors: Optional[list[str]] = []
40+
stadium_name: str
41+
stadium_seats: int
42+
current_transfer_record: int
43+
current_market_value: int
44+
confederation: Optional[str] = None
45+
fifa_world_ranking: Optional[str] = None
46+
squad: ClubSquad
47+
league: ClubLeague
48+
historical_crests: Optional[list[str]] = []

app/schemas/clubs/search.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from typing import Optional
2+
3+
from app.schemas.base import AuditMixin, TransfermarktBaseModel
4+
5+
6+
class ClubSearchResult(TransfermarktBaseModel):
7+
id: str
8+
url: str
9+
name: str
10+
country: str
11+
squad: int
12+
market_value: Optional[int] = None
13+
14+
15+
class ClubSearch(TransfermarktBaseModel, AuditMixin):
16+
query: str
17+
page_number: int
18+
last_page_number: int
19+
results: list[ClubSearchResult]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from app.schemas.competitions.clubs import CompetitionClubs as CompetitionClubs
2+
from app.schemas.competitions.search import CompetitionSearch as CompetitionSearch

0 commit comments

Comments
 (0)