transfermarkt-api 5.0.0__tar.gz
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-5.0.0/.gitignore +164 -0
- transfermarkt_api-5.0.0/LICENSE +21 -0
- transfermarkt_api-5.0.0/PKG-INFO +234 -0
- transfermarkt_api-5.0.0/README.md +216 -0
- transfermarkt_api-5.0.0/app/__init__.py +0 -0
- transfermarkt_api-5.0.0/app/api/__init__.py +0 -0
- transfermarkt_api-5.0.0/app/api/api.py +11 -0
- transfermarkt_api-5.0.0/app/api/dependencies.py +13 -0
- transfermarkt_api-5.0.0/app/api/endpoints/__init__.py +0 -0
- transfermarkt_api-5.0.0/app/api/endpoints/clubs.py +41 -0
- transfermarkt_api-5.0.0/app/api/endpoints/coaches.py +19 -0
- transfermarkt_api-5.0.0/app/api/endpoints/competitions.py +39 -0
- transfermarkt_api-5.0.0/app/api/endpoints/countries.py +12 -0
- transfermarkt_api-5.0.0/app/api/endpoints/games.py +12 -0
- transfermarkt_api-5.0.0/app/api/endpoints/players.py +83 -0
- transfermarkt_api-5.0.0/app/api/params.py +5 -0
- transfermarkt_api-5.0.0/app/main.py +122 -0
- transfermarkt_api-5.0.0/app/rate_limit.py +32 -0
- transfermarkt_api-5.0.0/app/settings.py +17 -0
- transfermarkt_api-5.0.0/pyproject.toml +70 -0
- transfermarkt_api-5.0.0/transfermarkt_api/__init__.py +23 -0
- transfermarkt_api-5.0.0/transfermarkt_api/_version.py +3 -0
- transfermarkt_api-5.0.0/transfermarkt_api/cache.py +20 -0
- transfermarkt_api-5.0.0/transfermarkt_api/client.py +226 -0
- transfermarkt_api-5.0.0/transfermarkt_api/exceptions.py +31 -0
- transfermarkt_api-5.0.0/transfermarkt_api/py.typed +0 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/__init__.py +24 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/achievements.py +25 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/base.py +17 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/clubs/__init__.py +3 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/clubs/listing.py +14 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/clubs/players.py +30 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/clubs/profile.py +82 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/clubs/search.py +19 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/coaches/__init__.py +31 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/competitions/__init__.py +4 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/competitions/clubs.py +15 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/competitions/search.py +21 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/competitions/seasons.py +15 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/competitions/table.py +31 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/countries.py +16 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/games/__init__.py +83 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/players/__init__.py +9 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/players/absences.py +22 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/players/injuries.py +20 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/players/market_value.py +19 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/players/matches.py +56 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/players/national_career.py +21 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/players/profile.py +81 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/players/search.py +25 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/players/stats.py +24 -0
- transfermarkt_api-5.0.0/transfermarkt_api/schemas/players/transfers.py +38 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/__init__.py +0 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/achievements.py +50 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/clubs/__init__.py +0 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/clubs/achievements.py +10 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/clubs/listing.py +22 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/clubs/players.py +88 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/clubs/profile.py +114 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/clubs/search.py +33 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/coaches/__init__.py +0 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/coaches/profile.py +29 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/coaches/search.py +18 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/competitions/__init__.py +0 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/competitions/clubs.py +30 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/competitions/search.py +61 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/competitions/seasons.py +28 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/competitions/table.py +61 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/countries.py +12 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/games/__init__.py +0 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/games/game.py +136 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/players/__init__.py +0 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/players/absences.py +33 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/players/achievements.py +10 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/players/injuries.py +42 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/players/market_value.py +35 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/players/matches.py +107 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/players/national_career.py +31 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/players/profile.py +122 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/players/search.py +44 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/players/stats.py +74 -0
- transfermarkt_api-5.0.0/transfermarkt_api/services/players/transfers.py +89 -0
- transfermarkt_api-5.0.0/transfermarkt_api/tfmkt/__init__.py +1 -0
- transfermarkt_api-5.0.0/transfermarkt_api/tfmkt/client.py +249 -0
- transfermarkt_api-5.0.0/transfermarkt_api/tfmkt/concurrency.py +17 -0
- transfermarkt_api-5.0.0/transfermarkt_api/tfmkt/freshness.py +38 -0
- transfermarkt_api-5.0.0/transfermarkt_api/tfmkt/reference.py +116 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
*.ipynb
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
88
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
89
|
+
.python-version
|
|
90
|
+
|
|
91
|
+
# pipenv
|
|
92
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
93
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
94
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
95
|
+
# install all needed dependencies.
|
|
96
|
+
#Pipfile.lock
|
|
97
|
+
|
|
98
|
+
# poetry
|
|
99
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
100
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
101
|
+
# commonly ignored for libraries.
|
|
102
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
103
|
+
#poetry.lock
|
|
104
|
+
|
|
105
|
+
# pdm
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
107
|
+
#pdm.lock
|
|
108
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
109
|
+
# in version control.
|
|
110
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
111
|
+
.pdm.toml
|
|
112
|
+
|
|
113
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
114
|
+
__pypackages__/
|
|
115
|
+
|
|
116
|
+
# Celery stuff
|
|
117
|
+
celerybeat-schedule
|
|
118
|
+
celerybeat.pid
|
|
119
|
+
|
|
120
|
+
# SageMath parsed files
|
|
121
|
+
*.sage.py
|
|
122
|
+
|
|
123
|
+
# Environments
|
|
124
|
+
.env
|
|
125
|
+
.venv
|
|
126
|
+
env/
|
|
127
|
+
venv/
|
|
128
|
+
ENV/
|
|
129
|
+
env.bak/
|
|
130
|
+
venv.bak/
|
|
131
|
+
|
|
132
|
+
# Spyder project settings
|
|
133
|
+
.spyderproject
|
|
134
|
+
.spyproject
|
|
135
|
+
|
|
136
|
+
# Rope project settings
|
|
137
|
+
.ropeproject
|
|
138
|
+
|
|
139
|
+
# mkdocs documentation
|
|
140
|
+
/site
|
|
141
|
+
|
|
142
|
+
# mypy
|
|
143
|
+
.mypy_cache/
|
|
144
|
+
.dmypy.json
|
|
145
|
+
dmypy.json
|
|
146
|
+
|
|
147
|
+
# Pyre type checker
|
|
148
|
+
.pyre/
|
|
149
|
+
|
|
150
|
+
# pytype static type analyzer
|
|
151
|
+
.pytype/
|
|
152
|
+
|
|
153
|
+
# Cython debug symbols
|
|
154
|
+
cython_debug/
|
|
155
|
+
|
|
156
|
+
# PyCharm
|
|
157
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
158
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
159
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
160
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
161
|
+
.idea/
|
|
162
|
+
|
|
163
|
+
# Vercel
|
|
164
|
+
.vercel/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Felipe Allegretti
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: transfermarkt-api
|
|
3
|
+
Version: 5.0.0
|
|
4
|
+
Summary: Async Python SDK and HTTP service for Transfermarkt data
|
|
5
|
+
Author-email: Felipe Allegretti <felipe@allegretti.me>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: <3.14,>=3.12
|
|
9
|
+
Requires-Dist: cachetools<8,>=7.2
|
|
10
|
+
Requires-Dist: httpx<1,>=0.28.1
|
|
11
|
+
Requires-Dist: pydantic<3,>=2.13
|
|
12
|
+
Provides-Extra: server
|
|
13
|
+
Requires-Dist: fastapi<1,>=0.142.2; extra == 'server'
|
|
14
|
+
Requires-Dist: limits<6,>=5.8; extra == 'server'
|
|
15
|
+
Requires-Dist: pydantic-settings<3,>=2.15; extra == 'server'
|
|
16
|
+
Requires-Dist: uvicorn[standard]<1,>=0.54; extra == 'server'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# transfermarkt-api
|
|
20
|
+
|
|
21
|
+
An async Python SDK and RESTful API (FastAPI) for football data from [Transfermarkt](https://www.transfermarkt.com/): players, clubs and competitions.
|
|
22
|
+
|
|
23
|
+
Since version 4.0.0, data comes from Transfermarkt's JSON API (`tmapi.transfermarkt.technology`), the same API the website uses, instead of scraping HTML pages. The website blocks requests from most cloud hosts, which broke v3 deployments; the JSON API does not. See [CHANGELOG.md](CHANGELOG.md) for what changed in the response format.
|
|
24
|
+
|
|
25
|
+
Please note that the deployed application is used only for testing purposes and is rate limited to 2 requests
|
|
26
|
+
every 3 seconds per client IP. If you'd like to customize it, consider hosting in your own cloud service.
|
|
27
|
+
|
|
28
|
+
The JSON API is internal to Transfermarkt and undocumented: it can change without notice. This project's MIT license covers its code, not the data; check [Transfermarkt's terms](https://www.transfermarkt.com/intern/) before using the data in your own services.
|
|
29
|
+
|
|
30
|
+
### Python SDK
|
|
31
|
+
|
|
32
|
+
Install the library without server dependencies:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install transfermarkt-api
|
|
36
|
+
# Until the SDK release is published, install from this repository:
|
|
37
|
+
# pip install .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Release setup and the publishing procedure are documented in [Python package releases](docs/releases.md).
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import asyncio
|
|
44
|
+
|
|
45
|
+
from transfermarkt_api import Transfermarkt
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def main() -> None:
|
|
49
|
+
async with Transfermarkt() as tm:
|
|
50
|
+
squad = await tm.club_players("985")
|
|
51
|
+
for player in squad.players:
|
|
52
|
+
print(player.shirt_number, player.name, player.position, player.market_value)
|
|
53
|
+
print(squad.model_dump(mode="json", by_alias=True))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
asyncio.run(main())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The facade has one method per data endpoint: `player_profile`, `player_transfers`, `club_players`,
|
|
60
|
+
`competition_clubs`, `competition_table`, `game`, and the remaining methods listed in the
|
|
61
|
+
[SDK method table](docs/plans/0001-python-sdk.md#public-contract). Methods return typed Pydantic models exported
|
|
62
|
+
from `transfermarkt_api.schemas`. Nested models remain available in their resource modules. Use string entity
|
|
63
|
+
and season IDs, integer country IDs, and 1-based page numbers. Calls return one page; callers control pagination.
|
|
64
|
+
|
|
65
|
+
Constructor settings are explicit: `base_url`, `timeout` (seconds, `httpx.Timeout`, or `None`),
|
|
66
|
+
`max_concurrency` (default `10`), `transport`, and `cache`. The SDK reads no `.env` or environment settings on import.
|
|
67
|
+
`CacheConfig(ttl=600, reference_ttl=86400, max_entries=2000)` configures per-client caching. Pass `cache=None` to
|
|
68
|
+
disable both response and reference caches so every request reaches the transport.
|
|
69
|
+
|
|
70
|
+
One client supports concurrent calls within one event loop. Its async context manager, or explicit `aclose()`,
|
|
71
|
+
closes the HTTP client and supplied transport. Do not share a supplied transport across client lifetimes unless
|
|
72
|
+
your wrapper owns and controls its closure. Cache hits remain local to each SDK instance.
|
|
73
|
+
|
|
74
|
+
The SDK raises `NotFound`, `UpstreamBlocked`, `UpstreamTimeout`, `UpstreamError`, or `EndpointUnavailable`, all
|
|
75
|
+
derived from `TransfermarktError` and exported at the package root. Available upstream URL/status metadata lives
|
|
76
|
+
on `error.url` and `error.status_code`; the status is the upstream status, not the server's mapped HTTP status.
|
|
77
|
+
The unsupported `player_jersey_numbers` method raises `EndpointUnavailable` without fetching data.
|
|
78
|
+
Invalid constructor/page/country arguments raise `ValueError`. Custom transport exceptions propagate unchanged.
|
|
79
|
+
|
|
80
|
+
### Custom transports and replay
|
|
81
|
+
|
|
82
|
+
Supply an `httpx.AsyncBaseTransport` to control every upstream request, including reference lookups, batches,
|
|
83
|
+
and per-player fan-out. The SDK bounds in-flight concurrency; the caller's transport owns rate limits, retries,
|
|
84
|
+
proxy selection, recording and circuit breakers. The SDK performs no automatic retries.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import httpx
|
|
88
|
+
|
|
89
|
+
from transfermarkt_api import Transfermarkt
|
|
90
|
+
|
|
91
|
+
# Run inside an async function. A custom browser-capable backend can implement the same transport interface.
|
|
92
|
+
async with Transfermarkt(
|
|
93
|
+
transport=httpx.AsyncHTTPTransport(proxy="http://127.0.0.1:8080"),
|
|
94
|
+
cache=None,
|
|
95
|
+
) as tm:
|
|
96
|
+
squad = await tm.club_players("985", season_id="2024")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
[Recording and replay examples](examples/transports.py) wrap a caller-selected backend and save the response
|
|
100
|
+
before SDK parsing. Persist the method, full URL with repeated query parameters, status, relevant headers (including
|
|
101
|
+
`x-amzn-waf-action`), decoded body, and original fetch timestamp. The example store is in memory; replace it with
|
|
102
|
+
durable storage in your application. When reconstructing decoded responses, drop content-encoding/content-length
|
|
103
|
+
headers so the body is not decoded twice.
|
|
104
|
+
|
|
105
|
+
A replay transport returns an `httpx.Response` with
|
|
106
|
+
`extensions={"transfermarkt_fetched_at": captured_datetime}`. This optional metadata accepts naive or timezone-aware
|
|
107
|
+
datetimes. Aware inputs are converted to UTC; naive inputs are interpreted as UTC. `updatedAt` retains its existing
|
|
108
|
+
JSON shape without a timezone suffix and always represents UTC. Without metadata, it reports the current UTC fetch
|
|
109
|
+
time. Each model reports the oldest fetch time used in that call, including cache hits. Exact replay
|
|
110
|
+
needs the same response bodies and timestamps. Missing recordings must raise a transport error rather than fetch
|
|
111
|
+
live data. Batch URLs are stable; concurrent request completion order is not part of the replay key.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from examples.transports import RecordingTransport, ReplayTransport
|
|
115
|
+
|
|
116
|
+
records = {}
|
|
117
|
+
async with Transfermarkt(transport=RecordingTransport(httpx.AsyncHTTPTransport(), records), cache=None) as tm:
|
|
118
|
+
captured = await tm.club_players("985")
|
|
119
|
+
async with Transfermarkt(transport=ReplayTransport(records), cache=None) as tm:
|
|
120
|
+
replayed = await tm.club_players("985")
|
|
121
|
+
assert captured == replayed
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The examples and HTTP server are repository files. The wheel installs only the `transfermarkt_api` package,
|
|
125
|
+
avoiding collisions with a consumer's own `app` package. The `server` extra installs the server dependencies;
|
|
126
|
+
run `uvicorn app.main:app` from a repository checkout, or use the Docker image.
|
|
127
|
+
|
|
128
|
+
### API Swagger
|
|
129
|
+
https://transfermarkt-api.fly.dev/
|
|
130
|
+
|
|
131
|
+
### Endpoints
|
|
132
|
+
|
|
133
|
+
| Endpoint | Notes |
|
|
134
|
+
| --- | --- |
|
|
135
|
+
| `GET /players/search/{player_name}?page_number=` | 10 results per page |
|
|
136
|
+
| `GET /players/{player_id}/profile` | |
|
|
137
|
+
| `GET /players/{player_id}/market_value` | |
|
|
138
|
+
| `GET /players/{player_id}/transfers` | Transfer type, age, contract dates, remaining contract days, and each club's league and country at the time |
|
|
139
|
+
| `GET /players/{player_id}/stats` | Per season, competition and club; club competitions only |
|
|
140
|
+
| `GET /players/{player_id}/matches?season_id=&page_number=` | Past matches of the player's teams, most recent first, 50 per page; no upcoming fixtures |
|
|
141
|
+
| `GET /players/{player_id}/injuries?page_number=` | 15 injuries per page |
|
|
142
|
+
| `GET /players/{player_id}/absences?page_number=` | Suspensions, call-ups, leave; 15 per page |
|
|
143
|
+
| `GET /players/{player_id}/achievements` | Titles and awards with season, club and competition |
|
|
144
|
+
| `GET /players/{player_id}/national_career` | Senior and youth national teams |
|
|
145
|
+
| `GET /players/{player_id}/jersey_numbers` | `501`: no data source |
|
|
146
|
+
| `GET /countries/` | Country IDs and metadata, including historical entries; cached for 24 hours |
|
|
147
|
+
| `GET /clubs/?country_id=` | Available club IDs and names for a country; limited upstream coverage, no pagination |
|
|
148
|
+
| `GET /clubs/search/{club_name}?page_number=` | 10 results per page |
|
|
149
|
+
| `GET /clubs/{club_id}/profile` | Squad value details, historical names and stadium details |
|
|
150
|
+
| `GET /clubs/{club_id}/players?season_id=` | Current squad by default; shirt number and captaincy for the selected season |
|
|
151
|
+
| `GET /clubs/{club_id}/achievements` | Club titles by season |
|
|
152
|
+
| `GET /coaches/search/{coach_name}?page_number=` | 10 results per page |
|
|
153
|
+
| `GET /coaches/{coach_id}/profile` | |
|
|
154
|
+
| `GET /competitions/search/{competition_name}?page_number=` | 10 results per page |
|
|
155
|
+
| `GET /competitions/{competition_id}/clubs?season_id=` | Current season by default |
|
|
156
|
+
| `GET /competitions/{competition_id}/table?season_id=` | League table; one table per group for group stages |
|
|
157
|
+
| `GET /competitions/{competition_id}/seasons` | Valid `season_id` values |
|
|
158
|
+
| `GET /games/{game_id}` | Score, lineups, coaches, stadium, team stats, goals, cards and substitutions |
|
|
159
|
+
|
|
160
|
+
Errors: `404` unknown ID, `501` endpoint without data source, `502` unexpected upstream response, `503` upstream blocked or refused the request, `504` upstream timeout.
|
|
161
|
+
|
|
162
|
+
Fields without an upstream value are returned as `null`. `updatedAt` is when the underlying data was fetched from Transfermarkt; responses are cached for 10 minutes by default.
|
|
163
|
+
|
|
164
|
+
### Running Locally
|
|
165
|
+
|
|
166
|
+
Requires [uv](https://docs.astral.sh/uv/).
|
|
167
|
+
|
|
168
|
+
````bash
|
|
169
|
+
# Clone the repository
|
|
170
|
+
$ git clone https://github.com/felipeall/transfermarkt-api.git
|
|
171
|
+
|
|
172
|
+
# Go to the project's root folder
|
|
173
|
+
$ cd transfermarkt-api
|
|
174
|
+
|
|
175
|
+
# Install Python 3.12 and the dependencies
|
|
176
|
+
$ uv sync --extra server
|
|
177
|
+
|
|
178
|
+
# Start the API server
|
|
179
|
+
$ uv run uvicorn app.main:app --reload
|
|
180
|
+
|
|
181
|
+
# Access the API local page
|
|
182
|
+
$ open http://localhost:8000/
|
|
183
|
+
````
|
|
184
|
+
|
|
185
|
+
### Running Tests
|
|
186
|
+
|
|
187
|
+
````bash
|
|
188
|
+
# Offline tests (recorded upstream responses, no network)
|
|
189
|
+
$ uv run --extra server pytest
|
|
190
|
+
|
|
191
|
+
# Live smoke tests against Transfermarkt's JSON API
|
|
192
|
+
$ uv run --extra server pytest -m live
|
|
193
|
+
|
|
194
|
+
# Re-record upstream fixtures and response snapshots
|
|
195
|
+
$ rm -rf tests/fixtures/tfmkt && uv run python scripts/capture_fixtures.py
|
|
196
|
+
````
|
|
197
|
+
|
|
198
|
+
### Running via Docker
|
|
199
|
+
|
|
200
|
+
````bash
|
|
201
|
+
# Clone the repository
|
|
202
|
+
$ git clone https://github.com/felipeall/transfermarkt-api.git
|
|
203
|
+
|
|
204
|
+
# Go to the project's root folder
|
|
205
|
+
$ cd transfermarkt-api
|
|
206
|
+
|
|
207
|
+
# Build the Docker image
|
|
208
|
+
$ docker build -t transfermarkt-api .
|
|
209
|
+
|
|
210
|
+
# Instantiate the Docker container
|
|
211
|
+
$ docker run -d -p 8000:8000 transfermarkt-api
|
|
212
|
+
|
|
213
|
+
# Access the API local page
|
|
214
|
+
$ open http://localhost:8000/
|
|
215
|
+
````
|
|
216
|
+
|
|
217
|
+
### Environment Variables
|
|
218
|
+
|
|
219
|
+
| Variable | Description | Default |
|
|
220
|
+
|-------------------------------|--------------------------------------------------------------------------------------------------------|------------------------------------------|
|
|
221
|
+
| `RATE_LIMITING_ENABLE` | Enable rate limiting feature for API calls | `false` |
|
|
222
|
+
| `RATE_LIMITING_FREQUENCY` | Requests allowed per client IP across all routes, in [limits notation](https://limits.readthedocs.io/en/stable/quickstart.html#rate-limit-string-notation) | `2/3seconds` |
|
|
223
|
+
| `TFMKT_BASE_URL` | Transfermarkt JSON API base URL | `https://tmapi.transfermarkt.technology` |
|
|
224
|
+
| `TFMKT_TIMEOUT_SECONDS` | Timeout for each upstream request | `15` |
|
|
225
|
+
| `TFMKT_MAX_CONCURRENCY` | Maximum concurrent upstream requests across the whole server | `10` |
|
|
226
|
+
| `CACHE_TTL_SECONDS` | How long successful upstream responses are cached | `600` |
|
|
227
|
+
| `CACHE_REFERENCE_TTL_SECONDS` | Cache duration for reference data (countries, positions, ...) | `86400` |
|
|
228
|
+
| `CACHE_MAX_ENTRIES` | Maximum number of cached upstream responses | `2000` |
|
|
229
|
+
|
|
230
|
+
Rate limits apply to the API routes only: `/health`, the docs (`/`, `/docs`, `/redoc`) and `/openapi.json` are not
|
|
231
|
+
counted. They are counted per client IP: the `Fly-Client-IP` header set by the Fly.io proxy, or the address of the
|
|
232
|
+
connection when that header is missing. `X-Forwarded-For` is not trusted. Behind another reverse proxy, all
|
|
233
|
+
requests share the proxy's address, and outside Fly.io clients can send `Fly-Client-IP` themselves, so adjust
|
|
234
|
+
`client_ip` in `app/main.py` before enabling rate limiting on another host.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# transfermarkt-api
|
|
2
|
+
|
|
3
|
+
An async Python SDK and RESTful API (FastAPI) for football data from [Transfermarkt](https://www.transfermarkt.com/): players, clubs and competitions.
|
|
4
|
+
|
|
5
|
+
Since version 4.0.0, data comes from Transfermarkt's JSON API (`tmapi.transfermarkt.technology`), the same API the website uses, instead of scraping HTML pages. The website blocks requests from most cloud hosts, which broke v3 deployments; the JSON API does not. See [CHANGELOG.md](CHANGELOG.md) for what changed in the response format.
|
|
6
|
+
|
|
7
|
+
Please note that the deployed application is used only for testing purposes and is rate limited to 2 requests
|
|
8
|
+
every 3 seconds per client IP. If you'd like to customize it, consider hosting in your own cloud service.
|
|
9
|
+
|
|
10
|
+
The JSON API is internal to Transfermarkt and undocumented: it can change without notice. This project's MIT license covers its code, not the data; check [Transfermarkt's terms](https://www.transfermarkt.com/intern/) before using the data in your own services.
|
|
11
|
+
|
|
12
|
+
### Python SDK
|
|
13
|
+
|
|
14
|
+
Install the library without server dependencies:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install transfermarkt-api
|
|
18
|
+
# Until the SDK release is published, install from this repository:
|
|
19
|
+
# pip install .
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Release setup and the publishing procedure are documented in [Python package releases](docs/releases.md).
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import asyncio
|
|
26
|
+
|
|
27
|
+
from transfermarkt_api import Transfermarkt
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def main() -> None:
|
|
31
|
+
async with Transfermarkt() as tm:
|
|
32
|
+
squad = await tm.club_players("985")
|
|
33
|
+
for player in squad.players:
|
|
34
|
+
print(player.shirt_number, player.name, player.position, player.market_value)
|
|
35
|
+
print(squad.model_dump(mode="json", by_alias=True))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
asyncio.run(main())
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The facade has one method per data endpoint: `player_profile`, `player_transfers`, `club_players`,
|
|
42
|
+
`competition_clubs`, `competition_table`, `game`, and the remaining methods listed in the
|
|
43
|
+
[SDK method table](docs/plans/0001-python-sdk.md#public-contract). Methods return typed Pydantic models exported
|
|
44
|
+
from `transfermarkt_api.schemas`. Nested models remain available in their resource modules. Use string entity
|
|
45
|
+
and season IDs, integer country IDs, and 1-based page numbers. Calls return one page; callers control pagination.
|
|
46
|
+
|
|
47
|
+
Constructor settings are explicit: `base_url`, `timeout` (seconds, `httpx.Timeout`, or `None`),
|
|
48
|
+
`max_concurrency` (default `10`), `transport`, and `cache`. The SDK reads no `.env` or environment settings on import.
|
|
49
|
+
`CacheConfig(ttl=600, reference_ttl=86400, max_entries=2000)` configures per-client caching. Pass `cache=None` to
|
|
50
|
+
disable both response and reference caches so every request reaches the transport.
|
|
51
|
+
|
|
52
|
+
One client supports concurrent calls within one event loop. Its async context manager, or explicit `aclose()`,
|
|
53
|
+
closes the HTTP client and supplied transport. Do not share a supplied transport across client lifetimes unless
|
|
54
|
+
your wrapper owns and controls its closure. Cache hits remain local to each SDK instance.
|
|
55
|
+
|
|
56
|
+
The SDK raises `NotFound`, `UpstreamBlocked`, `UpstreamTimeout`, `UpstreamError`, or `EndpointUnavailable`, all
|
|
57
|
+
derived from `TransfermarktError` and exported at the package root. Available upstream URL/status metadata lives
|
|
58
|
+
on `error.url` and `error.status_code`; the status is the upstream status, not the server's mapped HTTP status.
|
|
59
|
+
The unsupported `player_jersey_numbers` method raises `EndpointUnavailable` without fetching data.
|
|
60
|
+
Invalid constructor/page/country arguments raise `ValueError`. Custom transport exceptions propagate unchanged.
|
|
61
|
+
|
|
62
|
+
### Custom transports and replay
|
|
63
|
+
|
|
64
|
+
Supply an `httpx.AsyncBaseTransport` to control every upstream request, including reference lookups, batches,
|
|
65
|
+
and per-player fan-out. The SDK bounds in-flight concurrency; the caller's transport owns rate limits, retries,
|
|
66
|
+
proxy selection, recording and circuit breakers. The SDK performs no automatic retries.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import httpx
|
|
70
|
+
|
|
71
|
+
from transfermarkt_api import Transfermarkt
|
|
72
|
+
|
|
73
|
+
# Run inside an async function. A custom browser-capable backend can implement the same transport interface.
|
|
74
|
+
async with Transfermarkt(
|
|
75
|
+
transport=httpx.AsyncHTTPTransport(proxy="http://127.0.0.1:8080"),
|
|
76
|
+
cache=None,
|
|
77
|
+
) as tm:
|
|
78
|
+
squad = await tm.club_players("985", season_id="2024")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
[Recording and replay examples](examples/transports.py) wrap a caller-selected backend and save the response
|
|
82
|
+
before SDK parsing. Persist the method, full URL with repeated query parameters, status, relevant headers (including
|
|
83
|
+
`x-amzn-waf-action`), decoded body, and original fetch timestamp. The example store is in memory; replace it with
|
|
84
|
+
durable storage in your application. When reconstructing decoded responses, drop content-encoding/content-length
|
|
85
|
+
headers so the body is not decoded twice.
|
|
86
|
+
|
|
87
|
+
A replay transport returns an `httpx.Response` with
|
|
88
|
+
`extensions={"transfermarkt_fetched_at": captured_datetime}`. This optional metadata accepts naive or timezone-aware
|
|
89
|
+
datetimes. Aware inputs are converted to UTC; naive inputs are interpreted as UTC. `updatedAt` retains its existing
|
|
90
|
+
JSON shape without a timezone suffix and always represents UTC. Without metadata, it reports the current UTC fetch
|
|
91
|
+
time. Each model reports the oldest fetch time used in that call, including cache hits. Exact replay
|
|
92
|
+
needs the same response bodies and timestamps. Missing recordings must raise a transport error rather than fetch
|
|
93
|
+
live data. Batch URLs are stable; concurrent request completion order is not part of the replay key.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from examples.transports import RecordingTransport, ReplayTransport
|
|
97
|
+
|
|
98
|
+
records = {}
|
|
99
|
+
async with Transfermarkt(transport=RecordingTransport(httpx.AsyncHTTPTransport(), records), cache=None) as tm:
|
|
100
|
+
captured = await tm.club_players("985")
|
|
101
|
+
async with Transfermarkt(transport=ReplayTransport(records), cache=None) as tm:
|
|
102
|
+
replayed = await tm.club_players("985")
|
|
103
|
+
assert captured == replayed
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The examples and HTTP server are repository files. The wheel installs only the `transfermarkt_api` package,
|
|
107
|
+
avoiding collisions with a consumer's own `app` package. The `server` extra installs the server dependencies;
|
|
108
|
+
run `uvicorn app.main:app` from a repository checkout, or use the Docker image.
|
|
109
|
+
|
|
110
|
+
### API Swagger
|
|
111
|
+
https://transfermarkt-api.fly.dev/
|
|
112
|
+
|
|
113
|
+
### Endpoints
|
|
114
|
+
|
|
115
|
+
| Endpoint | Notes |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| `GET /players/search/{player_name}?page_number=` | 10 results per page |
|
|
118
|
+
| `GET /players/{player_id}/profile` | |
|
|
119
|
+
| `GET /players/{player_id}/market_value` | |
|
|
120
|
+
| `GET /players/{player_id}/transfers` | Transfer type, age, contract dates, remaining contract days, and each club's league and country at the time |
|
|
121
|
+
| `GET /players/{player_id}/stats` | Per season, competition and club; club competitions only |
|
|
122
|
+
| `GET /players/{player_id}/matches?season_id=&page_number=` | Past matches of the player's teams, most recent first, 50 per page; no upcoming fixtures |
|
|
123
|
+
| `GET /players/{player_id}/injuries?page_number=` | 15 injuries per page |
|
|
124
|
+
| `GET /players/{player_id}/absences?page_number=` | Suspensions, call-ups, leave; 15 per page |
|
|
125
|
+
| `GET /players/{player_id}/achievements` | Titles and awards with season, club and competition |
|
|
126
|
+
| `GET /players/{player_id}/national_career` | Senior and youth national teams |
|
|
127
|
+
| `GET /players/{player_id}/jersey_numbers` | `501`: no data source |
|
|
128
|
+
| `GET /countries/` | Country IDs and metadata, including historical entries; cached for 24 hours |
|
|
129
|
+
| `GET /clubs/?country_id=` | Available club IDs and names for a country; limited upstream coverage, no pagination |
|
|
130
|
+
| `GET /clubs/search/{club_name}?page_number=` | 10 results per page |
|
|
131
|
+
| `GET /clubs/{club_id}/profile` | Squad value details, historical names and stadium details |
|
|
132
|
+
| `GET /clubs/{club_id}/players?season_id=` | Current squad by default; shirt number and captaincy for the selected season |
|
|
133
|
+
| `GET /clubs/{club_id}/achievements` | Club titles by season |
|
|
134
|
+
| `GET /coaches/search/{coach_name}?page_number=` | 10 results per page |
|
|
135
|
+
| `GET /coaches/{coach_id}/profile` | |
|
|
136
|
+
| `GET /competitions/search/{competition_name}?page_number=` | 10 results per page |
|
|
137
|
+
| `GET /competitions/{competition_id}/clubs?season_id=` | Current season by default |
|
|
138
|
+
| `GET /competitions/{competition_id}/table?season_id=` | League table; one table per group for group stages |
|
|
139
|
+
| `GET /competitions/{competition_id}/seasons` | Valid `season_id` values |
|
|
140
|
+
| `GET /games/{game_id}` | Score, lineups, coaches, stadium, team stats, goals, cards and substitutions |
|
|
141
|
+
|
|
142
|
+
Errors: `404` unknown ID, `501` endpoint without data source, `502` unexpected upstream response, `503` upstream blocked or refused the request, `504` upstream timeout.
|
|
143
|
+
|
|
144
|
+
Fields without an upstream value are returned as `null`. `updatedAt` is when the underlying data was fetched from Transfermarkt; responses are cached for 10 minutes by default.
|
|
145
|
+
|
|
146
|
+
### Running Locally
|
|
147
|
+
|
|
148
|
+
Requires [uv](https://docs.astral.sh/uv/).
|
|
149
|
+
|
|
150
|
+
````bash
|
|
151
|
+
# Clone the repository
|
|
152
|
+
$ git clone https://github.com/felipeall/transfermarkt-api.git
|
|
153
|
+
|
|
154
|
+
# Go to the project's root folder
|
|
155
|
+
$ cd transfermarkt-api
|
|
156
|
+
|
|
157
|
+
# Install Python 3.12 and the dependencies
|
|
158
|
+
$ uv sync --extra server
|
|
159
|
+
|
|
160
|
+
# Start the API server
|
|
161
|
+
$ uv run uvicorn app.main:app --reload
|
|
162
|
+
|
|
163
|
+
# Access the API local page
|
|
164
|
+
$ open http://localhost:8000/
|
|
165
|
+
````
|
|
166
|
+
|
|
167
|
+
### Running Tests
|
|
168
|
+
|
|
169
|
+
````bash
|
|
170
|
+
# Offline tests (recorded upstream responses, no network)
|
|
171
|
+
$ uv run --extra server pytest
|
|
172
|
+
|
|
173
|
+
# Live smoke tests against Transfermarkt's JSON API
|
|
174
|
+
$ uv run --extra server pytest -m live
|
|
175
|
+
|
|
176
|
+
# Re-record upstream fixtures and response snapshots
|
|
177
|
+
$ rm -rf tests/fixtures/tfmkt && uv run python scripts/capture_fixtures.py
|
|
178
|
+
````
|
|
179
|
+
|
|
180
|
+
### Running via Docker
|
|
181
|
+
|
|
182
|
+
````bash
|
|
183
|
+
# Clone the repository
|
|
184
|
+
$ git clone https://github.com/felipeall/transfermarkt-api.git
|
|
185
|
+
|
|
186
|
+
# Go to the project's root folder
|
|
187
|
+
$ cd transfermarkt-api
|
|
188
|
+
|
|
189
|
+
# Build the Docker image
|
|
190
|
+
$ docker build -t transfermarkt-api .
|
|
191
|
+
|
|
192
|
+
# Instantiate the Docker container
|
|
193
|
+
$ docker run -d -p 8000:8000 transfermarkt-api
|
|
194
|
+
|
|
195
|
+
# Access the API local page
|
|
196
|
+
$ open http://localhost:8000/
|
|
197
|
+
````
|
|
198
|
+
|
|
199
|
+
### Environment Variables
|
|
200
|
+
|
|
201
|
+
| Variable | Description | Default |
|
|
202
|
+
|-------------------------------|--------------------------------------------------------------------------------------------------------|------------------------------------------|
|
|
203
|
+
| `RATE_LIMITING_ENABLE` | Enable rate limiting feature for API calls | `false` |
|
|
204
|
+
| `RATE_LIMITING_FREQUENCY` | Requests allowed per client IP across all routes, in [limits notation](https://limits.readthedocs.io/en/stable/quickstart.html#rate-limit-string-notation) | `2/3seconds` |
|
|
205
|
+
| `TFMKT_BASE_URL` | Transfermarkt JSON API base URL | `https://tmapi.transfermarkt.technology` |
|
|
206
|
+
| `TFMKT_TIMEOUT_SECONDS` | Timeout for each upstream request | `15` |
|
|
207
|
+
| `TFMKT_MAX_CONCURRENCY` | Maximum concurrent upstream requests across the whole server | `10` |
|
|
208
|
+
| `CACHE_TTL_SECONDS` | How long successful upstream responses are cached | `600` |
|
|
209
|
+
| `CACHE_REFERENCE_TTL_SECONDS` | Cache duration for reference data (countries, positions, ...) | `86400` |
|
|
210
|
+
| `CACHE_MAX_ENTRIES` | Maximum number of cached upstream responses | `2000` |
|
|
211
|
+
|
|
212
|
+
Rate limits apply to the API routes only: `/health`, the docs (`/`, `/docs`, `/redoc`) and `/openapi.json` are not
|
|
213
|
+
counted. They are counted per client IP: the `Fly-Client-IP` header set by the Fly.io proxy, or the address of the
|
|
214
|
+
connection when that header is missing. `X-Forwarded-For` is not trusted. Behind another reverse proxy, all
|
|
215
|
+
requests share the proxy's address, and outside Fly.io clients can send `Fly-Client-IP` themselves, so adjust
|
|
216
|
+
`client_ip` in `app/main.py` before enabling rate limiting on another host.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
|
|
3
|
+
from app.api.endpoints import clubs, coaches, competitions, countries, games, players
|
|
4
|
+
|
|
5
|
+
api_router = APIRouter()
|
|
6
|
+
api_router.include_router(competitions.router, prefix="/competitions", tags=["competitions"])
|
|
7
|
+
api_router.include_router(clubs.router, prefix="/clubs", tags=["clubs"])
|
|
8
|
+
api_router.include_router(players.router, prefix="/players", tags=["players"])
|
|
9
|
+
api_router.include_router(coaches.router, prefix="/coaches", tags=["coaches"])
|
|
10
|
+
api_router.include_router(countries.router, prefix="/countries", tags=["countries"])
|
|
11
|
+
api_router.include_router(games.router, prefix="/games", tags=["games"])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from fastapi import Depends, Request
|
|
4
|
+
|
|
5
|
+
from transfermarkt_api import Transfermarkt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_tfmkt(request: Request) -> Transfermarkt:
|
|
9
|
+
"""Return the shared upstream client created during server startup."""
|
|
10
|
+
return request.app.state.tfmkt
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Tfmkt = Annotated[Transfermarkt, Depends(get_tfmkt)]
|
|
File without changes
|