transfermarkt-api 5.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transfermarkt_api/__init__.py +23 -0
- transfermarkt_api/_version.py +3 -0
- transfermarkt_api/cache.py +20 -0
- transfermarkt_api/client.py +226 -0
- transfermarkt_api/exceptions.py +31 -0
- transfermarkt_api/py.typed +0 -0
- transfermarkt_api/schemas/__init__.py +24 -0
- transfermarkt_api/schemas/achievements.py +25 -0
- transfermarkt_api/schemas/base.py +17 -0
- transfermarkt_api/schemas/clubs/__init__.py +3 -0
- transfermarkt_api/schemas/clubs/listing.py +14 -0
- transfermarkt_api/schemas/clubs/players.py +30 -0
- transfermarkt_api/schemas/clubs/profile.py +82 -0
- transfermarkt_api/schemas/clubs/search.py +19 -0
- transfermarkt_api/schemas/coaches/__init__.py +31 -0
- transfermarkt_api/schemas/competitions/__init__.py +4 -0
- transfermarkt_api/schemas/competitions/clubs.py +15 -0
- transfermarkt_api/schemas/competitions/search.py +21 -0
- transfermarkt_api/schemas/competitions/seasons.py +15 -0
- transfermarkt_api/schemas/competitions/table.py +31 -0
- transfermarkt_api/schemas/countries.py +16 -0
- transfermarkt_api/schemas/games/__init__.py +83 -0
- transfermarkt_api/schemas/players/__init__.py +9 -0
- transfermarkt_api/schemas/players/absences.py +22 -0
- transfermarkt_api/schemas/players/injuries.py +20 -0
- transfermarkt_api/schemas/players/market_value.py +19 -0
- transfermarkt_api/schemas/players/matches.py +56 -0
- transfermarkt_api/schemas/players/national_career.py +21 -0
- transfermarkt_api/schemas/players/profile.py +81 -0
- transfermarkt_api/schemas/players/search.py +25 -0
- transfermarkt_api/schemas/players/stats.py +24 -0
- transfermarkt_api/schemas/players/transfers.py +38 -0
- transfermarkt_api/services/__init__.py +0 -0
- transfermarkt_api/services/achievements.py +50 -0
- transfermarkt_api/services/clubs/__init__.py +0 -0
- transfermarkt_api/services/clubs/achievements.py +10 -0
- transfermarkt_api/services/clubs/listing.py +22 -0
- transfermarkt_api/services/clubs/players.py +88 -0
- transfermarkt_api/services/clubs/profile.py +114 -0
- transfermarkt_api/services/clubs/search.py +33 -0
- transfermarkt_api/services/coaches/__init__.py +0 -0
- transfermarkt_api/services/coaches/profile.py +29 -0
- transfermarkt_api/services/coaches/search.py +18 -0
- transfermarkt_api/services/competitions/__init__.py +0 -0
- transfermarkt_api/services/competitions/clubs.py +30 -0
- transfermarkt_api/services/competitions/search.py +61 -0
- transfermarkt_api/services/competitions/seasons.py +28 -0
- transfermarkt_api/services/competitions/table.py +61 -0
- transfermarkt_api/services/countries.py +12 -0
- transfermarkt_api/services/games/__init__.py +0 -0
- transfermarkt_api/services/games/game.py +136 -0
- transfermarkt_api/services/players/__init__.py +0 -0
- transfermarkt_api/services/players/absences.py +33 -0
- transfermarkt_api/services/players/achievements.py +10 -0
- transfermarkt_api/services/players/injuries.py +42 -0
- transfermarkt_api/services/players/market_value.py +35 -0
- transfermarkt_api/services/players/matches.py +107 -0
- transfermarkt_api/services/players/national_career.py +31 -0
- transfermarkt_api/services/players/profile.py +122 -0
- transfermarkt_api/services/players/search.py +44 -0
- transfermarkt_api/services/players/stats.py +74 -0
- transfermarkt_api/services/players/transfers.py +89 -0
- transfermarkt_api/tfmkt/__init__.py +1 -0
- transfermarkt_api/tfmkt/client.py +249 -0
- transfermarkt_api/tfmkt/concurrency.py +17 -0
- transfermarkt_api/tfmkt/freshness.py +38 -0
- transfermarkt_api/tfmkt/reference.py +116 -0
- transfermarkt_api-5.0.0.dist-info/METADATA +234 -0
- transfermarkt_api-5.0.0.dist-info/RECORD +71 -0
- transfermarkt_api-5.0.0.dist-info/WHEEL +4 -0
- transfermarkt_api-5.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from transfermarkt_api.schemas.base import AuditMixin, TransfermarktBaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Country(TransfermarktBaseModel):
|
|
7
|
+
id: int
|
|
8
|
+
name: str
|
|
9
|
+
fifa_code: Optional[str] = None
|
|
10
|
+
confederation_id: Optional[int] = None
|
|
11
|
+
flag_url: Optional[str] = None
|
|
12
|
+
is_historical: bool
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Countries(TransfermarktBaseModel, AuditMixin):
|
|
16
|
+
countries: list[Country]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Literal, Optional
|
|
3
|
+
|
|
4
|
+
from transfermarkt_api.schemas.base import AuditMixin, TransfermarktBaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GameEntity(TransfermarktBaseModel):
|
|
8
|
+
id: str
|
|
9
|
+
name: Optional[str] = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GamePlayer(TransfermarktBaseModel):
|
|
13
|
+
id: str
|
|
14
|
+
name: Optional[str] = None
|
|
15
|
+
shirt_number: Optional[int] = None
|
|
16
|
+
position: Optional[str] = None
|
|
17
|
+
is_captain: bool = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GameTeamStats(TransfermarktBaseModel):
|
|
21
|
+
possession: Optional[float] = None
|
|
22
|
+
shots: Optional[int] = None
|
|
23
|
+
shots_on_target: Optional[int] = None
|
|
24
|
+
shots_off_target: Optional[int] = None
|
|
25
|
+
shots_blocked: Optional[int] = None
|
|
26
|
+
passes: Optional[int] = None
|
|
27
|
+
accurate_passes: Optional[int] = None
|
|
28
|
+
tackles: Optional[int] = None
|
|
29
|
+
tackles_won: Optional[int] = None
|
|
30
|
+
clearances: Optional[int] = None
|
|
31
|
+
saves: Optional[int] = None
|
|
32
|
+
offsides: Optional[int] = None
|
|
33
|
+
corners: Optional[int] = None
|
|
34
|
+
fouls_committed: Optional[int] = None
|
|
35
|
+
fouls_suffered: Optional[int] = None
|
|
36
|
+
yellow_cards: Optional[int] = None
|
|
37
|
+
second_yellow_cards: Optional[int] = None
|
|
38
|
+
red_cards: Optional[int] = None
|
|
39
|
+
penalties_won: Optional[int] = None
|
|
40
|
+
penalties_saved: Optional[int] = None
|
|
41
|
+
own_goals: Optional[int] = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class GameSide(TransfermarktBaseModel):
|
|
45
|
+
id: str
|
|
46
|
+
name: Optional[str] = None
|
|
47
|
+
score: Optional[int] = None
|
|
48
|
+
formation: Optional[str] = None
|
|
49
|
+
coach: Optional[GameEntity] = None
|
|
50
|
+
stats: Optional[GameTeamStats] = None
|
|
51
|
+
starting_lineup: list[GamePlayer]
|
|
52
|
+
substitutes: list[GamePlayer]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class GameScore(TransfermarktBaseModel):
|
|
56
|
+
home: int
|
|
57
|
+
away: int
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class GameEvent(TransfermarktBaseModel):
|
|
61
|
+
type: Literal["goal", "card", "substitution"]
|
|
62
|
+
minute: Optional[int] = None
|
|
63
|
+
added_time: Optional[int] = None
|
|
64
|
+
club_id: Optional[str] = None
|
|
65
|
+
player: Optional[GameEntity] = None
|
|
66
|
+
related_player: Optional[GameEntity] = None
|
|
67
|
+
card: Optional[Literal["yellow", "secondYellow", "red"]] = None
|
|
68
|
+
score: Optional[GameScore] = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Game(TransfermarktBaseModel, AuditMixin):
|
|
72
|
+
id: str
|
|
73
|
+
url: Optional[str] = None
|
|
74
|
+
competition: GameEntity
|
|
75
|
+
season_id: Optional[str] = None
|
|
76
|
+
season: Optional[str] = None
|
|
77
|
+
match_day: Optional[int] = None
|
|
78
|
+
date: Optional[datetime] = None
|
|
79
|
+
stadium: Optional[GameEntity] = None
|
|
80
|
+
attendance: Optional[int] = None
|
|
81
|
+
home: GameSide
|
|
82
|
+
away: GameSide
|
|
83
|
+
events: list[GameEvent]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from transfermarkt_api.schemas.players.absences import PlayerAbsences as PlayerAbsences
|
|
2
|
+
from transfermarkt_api.schemas.players.injuries import PlayerInjuries as PlayerInjuries
|
|
3
|
+
from transfermarkt_api.schemas.players.market_value import PlayerMarketValue as PlayerMarketValue
|
|
4
|
+
from transfermarkt_api.schemas.players.matches import PlayerMatches as PlayerMatches
|
|
5
|
+
from transfermarkt_api.schemas.players.national_career import PlayerNationalCareer as PlayerNationalCareer
|
|
6
|
+
from transfermarkt_api.schemas.players.profile import PlayerProfile as PlayerProfile
|
|
7
|
+
from transfermarkt_api.schemas.players.search import PlayerSearch as PlayerSearch
|
|
8
|
+
from transfermarkt_api.schemas.players.stats import PlayerStats as PlayerStats
|
|
9
|
+
from transfermarkt_api.schemas.players.transfers import PlayerTransfers as PlayerTransfers
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from transfermarkt_api.schemas.base import AuditMixin, TransfermarktBaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Absence(TransfermarktBaseModel):
|
|
8
|
+
season: Optional[str] = None
|
|
9
|
+
reason: Optional[str] = None
|
|
10
|
+
competition_id: Optional[str] = None
|
|
11
|
+
competition_name: Optional[str] = None
|
|
12
|
+
from_date: Optional[date] = None
|
|
13
|
+
until_date: Optional[date] = None
|
|
14
|
+
days: Optional[int] = None
|
|
15
|
+
games_missed: Optional[int] = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PlayerAbsences(TransfermarktBaseModel, AuditMixin):
|
|
19
|
+
id: str
|
|
20
|
+
page_number: int
|
|
21
|
+
last_page_number: int
|
|
22
|
+
absences: list[Absence]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from transfermarkt_api.schemas.base import AuditMixin, TransfermarktBaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Injury(TransfermarktBaseModel):
|
|
8
|
+
season: Optional[str] = None
|
|
9
|
+
injury: Optional[str] = None
|
|
10
|
+
from_date: Optional[date] = None
|
|
11
|
+
until_date: Optional[date] = None
|
|
12
|
+
days: Optional[int] = None
|
|
13
|
+
games_missed: Optional[int] = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PlayerInjuries(TransfermarktBaseModel, AuditMixin):
|
|
17
|
+
id: str
|
|
18
|
+
page_number: int
|
|
19
|
+
last_page_number: int
|
|
20
|
+
injuries: list[Injury]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from transfermarkt_api.schemas.base import AuditMixin, TransfermarktBaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MarketValueHistory(TransfermarktBaseModel):
|
|
8
|
+
age: Optional[int] = None
|
|
9
|
+
date: date
|
|
10
|
+
club_id: Optional[str] = None
|
|
11
|
+
club_name: Optional[str] = None
|
|
12
|
+
market_value: Optional[int] = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PlayerMarketValue(TransfermarktBaseModel, AuditMixin):
|
|
16
|
+
id: str
|
|
17
|
+
market_value: Optional[int] = None
|
|
18
|
+
market_value_history: list[MarketValueHistory]
|
|
19
|
+
ranking: Optional[dict[str, int]] = None
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Literal, Optional
|
|
3
|
+
|
|
4
|
+
from transfermarkt_api.schemas.base import AuditMixin, TransfermarktBaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PlayerMatchEntity(TransfermarktBaseModel):
|
|
8
|
+
id: str
|
|
9
|
+
name: Optional[str] = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PlayerMatch(TransfermarktBaseModel):
|
|
13
|
+
game_id: str
|
|
14
|
+
date: Optional[datetime] = None
|
|
15
|
+
season: Optional[str] = None
|
|
16
|
+
competition: PlayerMatchEntity
|
|
17
|
+
match_day: Optional[int] = None
|
|
18
|
+
club: PlayerMatchEntity
|
|
19
|
+
opponent: PlayerMatchEntity
|
|
20
|
+
venue: Optional[Literal["home", "away", "neutral"]] = None
|
|
21
|
+
club_goals: Optional[int] = None
|
|
22
|
+
opponent_goals: Optional[int] = None
|
|
23
|
+
participation: Optional[str] = None
|
|
24
|
+
shirt_number: Optional[int] = None
|
|
25
|
+
is_captain: bool = False
|
|
26
|
+
position: Optional[str] = None
|
|
27
|
+
is_starting: bool = False
|
|
28
|
+
substituted_in_minute: Optional[int] = None
|
|
29
|
+
substituted_out_minute: Optional[int] = None
|
|
30
|
+
minutes_played: int = 0
|
|
31
|
+
team_points: Optional[int] = None
|
|
32
|
+
goals: int = 0
|
|
33
|
+
assists: int = 0
|
|
34
|
+
yellow_card: bool = False
|
|
35
|
+
second_yellow_card: bool = False
|
|
36
|
+
red_card: bool = False
|
|
37
|
+
own_goals: Optional[int] = None
|
|
38
|
+
penalty_goals: Optional[int] = None
|
|
39
|
+
penalties_missed: Optional[int] = None
|
|
40
|
+
penalties_saved: Optional[int] = None
|
|
41
|
+
shots: Optional[int] = None
|
|
42
|
+
shots_on_target: Optional[int] = None
|
|
43
|
+
passes: Optional[int] = None
|
|
44
|
+
accurate_passes: Optional[int] = None
|
|
45
|
+
tackles: Optional[int] = None
|
|
46
|
+
tackles_won: Optional[int] = None
|
|
47
|
+
fouls_committed: Optional[int] = None
|
|
48
|
+
fouls_suffered: Optional[int] = None
|
|
49
|
+
offsides: Optional[int] = None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class PlayerMatches(TransfermarktBaseModel, AuditMixin):
|
|
53
|
+
id: str
|
|
54
|
+
page_number: int
|
|
55
|
+
last_page_number: int
|
|
56
|
+
matches: list[PlayerMatch]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from transfermarkt_api.schemas.base import AuditMixin, TransfermarktBaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class NationalTeam(TransfermarktBaseModel):
|
|
8
|
+
id: str
|
|
9
|
+
name: Optional[str] = None
|
|
10
|
+
appearances: Optional[int] = None
|
|
11
|
+
goals: Optional[int] = None
|
|
12
|
+
shirt_number: Optional[int] = None
|
|
13
|
+
is_captain: Optional[bool] = None
|
|
14
|
+
debut: Optional[date] = None
|
|
15
|
+
last_match: Optional[date] = None
|
|
16
|
+
status: Optional[str] = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PlayerNationalCareer(TransfermarktBaseModel, AuditMixin):
|
|
20
|
+
id: str
|
|
21
|
+
national_teams: list[NationalTeam]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from datetime import date
|
|
3
|
+
from typing import Literal, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import HttpUrl
|
|
6
|
+
|
|
7
|
+
from transfermarkt_api.schemas.base import AuditMixin, TransfermarktBaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PlayerPlaceOfBirth(TransfermarktBaseModel):
|
|
11
|
+
city: Optional[str] = None
|
|
12
|
+
country: Optional[str] = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PlayerPosition(TransfermarktBaseModel):
|
|
16
|
+
main: Optional[str] = None
|
|
17
|
+
group: Optional[str] = None
|
|
18
|
+
other: list[str] = []
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PlayerClub(TransfermarktBaseModel):
|
|
22
|
+
id: Optional[str] = None
|
|
23
|
+
name: Optional[str] = None
|
|
24
|
+
joined: Optional[date] = None
|
|
25
|
+
is_captain: Optional[bool] = None
|
|
26
|
+
contract_expires: Optional[date] = None
|
|
27
|
+
last_contract_renewal: Optional[date] = None
|
|
28
|
+
contract_option: Optional[str] = None
|
|
29
|
+
last_club_id: Optional[str] = None
|
|
30
|
+
last_club_name: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PlayerNationalTeam(TransfermarktBaseModel):
|
|
34
|
+
id: str
|
|
35
|
+
name: Optional[str] = None
|
|
36
|
+
shirt_number: Optional[str] = None
|
|
37
|
+
is_captain: Optional[bool] = None
|
|
38
|
+
debut: Optional[date] = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PlayerDatedValue(TransfermarktBaseModel):
|
|
42
|
+
value: int
|
|
43
|
+
date: Optional[datetime.date] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class PlayerMarketValueDetails(TransfermarktBaseModel):
|
|
47
|
+
last_updated: Optional[date] = None
|
|
48
|
+
trend: Optional[Literal["increased", "decreased", "unchanged"]] = None
|
|
49
|
+
previous: Optional[PlayerDatedValue] = None
|
|
50
|
+
highest: Optional[PlayerDatedValue] = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class PlayerAgent(TransfermarktBaseModel):
|
|
54
|
+
name: Optional[str] = None
|
|
55
|
+
url: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class PlayerProfile(TransfermarktBaseModel, AuditMixin):
|
|
59
|
+
id: str
|
|
60
|
+
url: Optional[HttpUrl] = None
|
|
61
|
+
name: str
|
|
62
|
+
full_name: Optional[str] = None
|
|
63
|
+
name_in_home_country: Optional[str] = None
|
|
64
|
+
image_url: Optional[HttpUrl] = None
|
|
65
|
+
date_of_birth: Optional[date] = None
|
|
66
|
+
date_of_death: Optional[date] = None
|
|
67
|
+
place_of_birth: PlayerPlaceOfBirth
|
|
68
|
+
age: Optional[int] = None
|
|
69
|
+
height: Optional[int] = None
|
|
70
|
+
citizenship: list[str]
|
|
71
|
+
is_retired: bool
|
|
72
|
+
retired_since: Optional[date] = None
|
|
73
|
+
position: PlayerPosition
|
|
74
|
+
foot: Optional[str] = None
|
|
75
|
+
shirt_number: Optional[str] = None
|
|
76
|
+
club: PlayerClub
|
|
77
|
+
national_team: Optional[PlayerNationalTeam] = None
|
|
78
|
+
market_value: Optional[int] = None
|
|
79
|
+
market_value_details: Optional[PlayerMarketValueDetails] = None
|
|
80
|
+
agent: Optional[PlayerAgent] = None
|
|
81
|
+
outfitter: Optional[str] = None
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from transfermarkt_api.schemas.base import AuditMixin, TransfermarktBaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PlayerSearchClub(TransfermarktBaseModel):
|
|
7
|
+
id: str
|
|
8
|
+
name: Optional[str] = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PlayerSearchResult(TransfermarktBaseModel):
|
|
12
|
+
id: str
|
|
13
|
+
name: Optional[str] = None
|
|
14
|
+
position: Optional[str] = None
|
|
15
|
+
club: Optional[PlayerSearchClub] = None
|
|
16
|
+
age: Optional[int] = None
|
|
17
|
+
nationalities: list[str]
|
|
18
|
+
market_value: Optional[int] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PlayerSearch(TransfermarktBaseModel, AuditMixin):
|
|
22
|
+
query: str
|
|
23
|
+
page_number: int
|
|
24
|
+
last_page_number: int
|
|
25
|
+
results: list[PlayerSearchResult]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from transfermarkt_api.schemas.base import AuditMixin, TransfermarktBaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PlayerStat(TransfermarktBaseModel):
|
|
7
|
+
competition_id: str
|
|
8
|
+
competition_name: Optional[str] = None
|
|
9
|
+
season_id: str
|
|
10
|
+
club_id: str
|
|
11
|
+
appearances: int
|
|
12
|
+
goals: int
|
|
13
|
+
assists: int
|
|
14
|
+
yellow_cards: int
|
|
15
|
+
second_yellow_cards: int
|
|
16
|
+
red_cards: int
|
|
17
|
+
minutes_played: int
|
|
18
|
+
goals_conceded: int
|
|
19
|
+
clean_sheets: int
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PlayerStats(TransfermarktBaseModel, AuditMixin):
|
|
23
|
+
id: str
|
|
24
|
+
stats: list[PlayerStat]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from typing import Literal, Optional
|
|
3
|
+
|
|
4
|
+
from transfermarkt_api.schemas.base import AuditMixin, TransfermarktBaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PlayerTransferLeague(TransfermarktBaseModel):
|
|
8
|
+
id: str
|
|
9
|
+
name: Optional[str] = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PlayerTransferClub(TransfermarktBaseModel):
|
|
13
|
+
id: str
|
|
14
|
+
name: Optional[str] = None
|
|
15
|
+
country_id: Optional[str] = None
|
|
16
|
+
country_name: Optional[str] = None
|
|
17
|
+
league: Optional[PlayerTransferLeague] = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PlayerTransfer(TransfermarktBaseModel):
|
|
21
|
+
id: str
|
|
22
|
+
club_from: PlayerTransferClub
|
|
23
|
+
club_to: PlayerTransferClub
|
|
24
|
+
date: Optional[datetime.date] = None
|
|
25
|
+
upcoming: bool
|
|
26
|
+
transfer_type: Optional[Literal["transfer", "freeTransfer", "internal", "loan", "endOfLoan"]] = None
|
|
27
|
+
season: Optional[str] = None
|
|
28
|
+
market_value: Optional[int] = None
|
|
29
|
+
fee: Optional[int] = None
|
|
30
|
+
age: Optional[int] = None
|
|
31
|
+
contract_until: Optional[datetime.date] = None
|
|
32
|
+
remaining_contract_days: Optional[int] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PlayerTransfers(TransfermarktBaseModel, AuditMixin):
|
|
36
|
+
id: str
|
|
37
|
+
transfers: list[PlayerTransfer]
|
|
38
|
+
youth_clubs: list[str] = []
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from transfermarkt_api.tfmkt import TfmktClient
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
async def group_achievements(tfmkt: TfmktClient, data: dict) -> list[dict]:
|
|
5
|
+
"""
|
|
6
|
+
Group upstream achievement records (one per title won) into titles with a count and per-season details.
|
|
7
|
+
|
|
8
|
+
Titles follow upstream's display order; details are most recent season first. Player records carry the club and
|
|
9
|
+
competition of each title; club records carry only the season, so those fields are null.
|
|
10
|
+
"""
|
|
11
|
+
records_by_title: dict[int, list[dict]] = {}
|
|
12
|
+
for record in data.get("achievements") or []:
|
|
13
|
+
records_by_title.setdefault(record["id"], []).append(record)
|
|
14
|
+
|
|
15
|
+
aggregated = {entry["id"]: entry for entry in data.get("aggregated") or []}
|
|
16
|
+
titles = sorted(
|
|
17
|
+
records_by_title,
|
|
18
|
+
key=lambda title_id: ((aggregated.get(title_id) or {}).get("order", 10_000), title_id),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
details = [record.get("details") or {} for records in records_by_title.values() for record in records]
|
|
22
|
+
clubs = await tfmkt.clubs(d.get("clubId") for d in details if d.get("clubId") not in (None, "", "0"))
|
|
23
|
+
competitions = await tfmkt.competitions(d.get("competitionId") for d in details if d.get("competitionId"))
|
|
24
|
+
|
|
25
|
+
def entity(entities: dict, entity_id: str | int | None) -> dict | None:
|
|
26
|
+
"""Return `{id, name}` for a referenced club or competition, or None when there is no reference."""
|
|
27
|
+
if entity_id in (None, "", "0"):
|
|
28
|
+
return None
|
|
29
|
+
return {"id": str(entity_id), "name": entities.get(str(entity_id), {}).get("name")}
|
|
30
|
+
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
"title": (
|
|
34
|
+
(aggregated.get(title_id) or {}).get("name") or records_by_title[title_id][0].get("name") or ""
|
|
35
|
+
).strip(),
|
|
36
|
+
"count": len(records_by_title[title_id]),
|
|
37
|
+
"details": [
|
|
38
|
+
{
|
|
39
|
+
"season": {
|
|
40
|
+
"id": str(record["seasonId"]) if record.get("seasonId") is not None else None,
|
|
41
|
+
"name": (record.get("season") or {}).get("display"),
|
|
42
|
+
},
|
|
43
|
+
"club": entity(clubs, (record.get("details") or {}).get("clubId")),
|
|
44
|
+
"competition": entity(competitions, (record.get("details") or {}).get("competitionId")),
|
|
45
|
+
}
|
|
46
|
+
for record in sorted(records_by_title[title_id], key=lambda r: r.get("seasonId") or 0, reverse=True)
|
|
47
|
+
],
|
|
48
|
+
}
|
|
49
|
+
for title_id in titles
|
|
50
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from transfermarkt_api.services.achievements import group_achievements
|
|
2
|
+
from transfermarkt_api.tfmkt import TfmktClient
|
|
3
|
+
from transfermarkt_api.tfmkt.concurrency import gather
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def get_club_achievements(tfmkt: TfmktClient, club_id: str) -> dict:
|
|
7
|
+
"""Club titles, including participations, with the seasons they were won."""
|
|
8
|
+
# the achievement route answers 200 with no data for unknown clubs; the club lookup gives a proper 404
|
|
9
|
+
_, data = await gather(tfmkt.club(club_id), tfmkt.club_achievements(club_id))
|
|
10
|
+
return {"id": club_id, "achievements": await group_achievements(tfmkt, data)}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from transfermarkt_api.exceptions import NotFound, UpstreamError
|
|
2
|
+
from transfermarkt_api.tfmkt import TfmktClient
|
|
3
|
+
from transfermarkt_api.tfmkt.reference import get_reference
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def list_clubs(tfmkt: TfmktClient, country_id: int) -> dict:
|
|
7
|
+
"""List the clubs available in the upstream country directory, preserving its order."""
|
|
8
|
+
reference = await get_reference(tfmkt)
|
|
9
|
+
country_name = reference.country_name(country_id)
|
|
10
|
+
if country_name is None:
|
|
11
|
+
raise NotFound(f"Country not found: {country_id}")
|
|
12
|
+
|
|
13
|
+
directory = await tfmkt.country_clubs(country_id)
|
|
14
|
+
if not isinstance(directory, dict) or not isinstance(directory.get("clubIds"), list):
|
|
15
|
+
raise UpstreamError(f"Unexpected upstream payload for /country/{country_id}/club")
|
|
16
|
+
club_ids = list(dict.fromkeys(str(club_id) for club_id in directory["clubIds"]))
|
|
17
|
+
clubs = await tfmkt.clubs(club_ids)
|
|
18
|
+
return {
|
|
19
|
+
"countryId": country_id,
|
|
20
|
+
"countryName": country_name,
|
|
21
|
+
"clubs": [{"id": club_id, "name": clubs.get(club_id, {}).get("name")} for club_id in club_ids],
|
|
22
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from transfermarkt_api.tfmkt import TfmktClient
|
|
4
|
+
from transfermarkt_api.tfmkt.concurrency import gather
|
|
5
|
+
from transfermarkt_api.tfmkt.reference import (
|
|
6
|
+
current_assignment,
|
|
7
|
+
date_of_birth,
|
|
8
|
+
get_reference,
|
|
9
|
+
height_cm,
|
|
10
|
+
market_value_of,
|
|
11
|
+
nationality_ids,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def get_club_players(tfmkt: TfmktClient, club_id: str, season_id: Optional[str] = None) -> dict:
|
|
16
|
+
"""
|
|
17
|
+
Squad of a club for a season (current squad when `season_id` is omitted).
|
|
18
|
+
|
|
19
|
+
Player details come from today's player records. `signedFrom` (and `joinedOn` for past seasons) come from the
|
|
20
|
+
latest transfer into this club up to that season. For a past season, other time-dependent fields (age, contract,
|
|
21
|
+
market value) are null rather than today's values, and `currentClub` names where the player is now.
|
|
22
|
+
Every squad member is returned, even if its player record is missing upstream.
|
|
23
|
+
"""
|
|
24
|
+
squad, reference = await gather(tfmkt.club_squad(club_id, season_id), get_reference(tfmkt))
|
|
25
|
+
members = squad.get("squad") or []
|
|
26
|
+
player_ids = [str(m["playerId"]) for m in members]
|
|
27
|
+
is_current = all(m.get("type") == "current" for m in members)
|
|
28
|
+
|
|
29
|
+
players, histories = await gather(
|
|
30
|
+
tfmkt.players(player_ids),
|
|
31
|
+
gather(*(tfmkt.player_transfer_history(i) for i in player_ids)),
|
|
32
|
+
)
|
|
33
|
+
max_season = int(season_id) if season_id and not is_current else None
|
|
34
|
+
arrivals = {i: latest_arrival(history, club_id, max_season) for i, history in zip(player_ids, histories)}
|
|
35
|
+
assignments = {i: current_assignment(players.get(i, {})) for i in player_ids}
|
|
36
|
+
squad_members = {str(member["playerId"]): member for member in members}
|
|
37
|
+
clubs = await tfmkt.clubs(
|
|
38
|
+
[a["transferSource"]["clubId"] for a in arrivals.values() if a]
|
|
39
|
+
+ ([] if is_current else [a["clubId"] for a in assignments.values() if a]),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
result = []
|
|
43
|
+
for player_id in player_ids:
|
|
44
|
+
player = players.get(player_id, {})
|
|
45
|
+
attributes = player.get("attributes") or {}
|
|
46
|
+
assignment = assignments[player_id]
|
|
47
|
+
arrival = arrivals[player_id]
|
|
48
|
+
entry = {
|
|
49
|
+
"id": player_id,
|
|
50
|
+
"name": player.get("name"),
|
|
51
|
+
"imageUrl": player.get("portraitUrl"),
|
|
52
|
+
"shirtNumber": squad_members[player_id].get("shirtNumber"),
|
|
53
|
+
"isCaptain": squad_members[player_id].get("isCaptain"),
|
|
54
|
+
"position": (attributes.get("position") or {}).get("name"),
|
|
55
|
+
"dateOfBirth": date_of_birth(player),
|
|
56
|
+
"nationality": reference.country_names(*nationality_ids(player)),
|
|
57
|
+
"height": height_cm(player),
|
|
58
|
+
"foot": (attributes.get("preferredFoot") or {}).get("name"),
|
|
59
|
+
"signedFrom": clubs.get(str(arrival["transferSource"]["clubId"]), {}).get("name") if arrival else None,
|
|
60
|
+
}
|
|
61
|
+
if is_current:
|
|
62
|
+
in_this_club = assignment is not None and str(assignment.get("clubId")) == str(club_id)
|
|
63
|
+
entry |= {
|
|
64
|
+
"age": (player.get("lifeDates") or {}).get("age"),
|
|
65
|
+
"joinedOn": assignment.get("start") if assignment is not None and in_this_club else None,
|
|
66
|
+
"contract": attributes.get("contractUntil"),
|
|
67
|
+
"marketValue": market_value_of((player.get("marketValueDetails") or {}).get("current")),
|
|
68
|
+
}
|
|
69
|
+
else:
|
|
70
|
+
entry |= {
|
|
71
|
+
"joinedOn": (arrival["details"].get("date") or "")[:10] or None if arrival else None,
|
|
72
|
+
"currentClub": clubs.get(str(assignment["clubId"]), {}).get("name") if assignment else None,
|
|
73
|
+
}
|
|
74
|
+
result.append(entry)
|
|
75
|
+
|
|
76
|
+
return {"id": club_id, "players": result}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def latest_arrival(history: Optional[dict], club_id: str, max_season: Optional[int]) -> Optional[dict]:
|
|
80
|
+
"""The most recent completed transfer into `club_id`, optionally limited to seasons up to `max_season`."""
|
|
81
|
+
records = ((history or {}).get("history") or {}).get("terminated") or []
|
|
82
|
+
arrivals = [
|
|
83
|
+
record
|
|
84
|
+
for record in records
|
|
85
|
+
if str(record["transferDestination"]["clubId"]) == str(club_id)
|
|
86
|
+
and (max_season is None or (record["details"].get("seasonId") or 0) <= max_season)
|
|
87
|
+
]
|
|
88
|
+
return max(arrivals, key=lambda record: record["details"].get("date") or "", default=None)
|