py-understat 0.1.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.
@@ -0,0 +1,65 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions: {}
9
+
10
+ jobs:
11
+ build:
12
+ name: Test and build distributions
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ contents: read
16
+ steps:
17
+ - name: Check out source
18
+ uses: actions/checkout@v5
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
22
+
23
+ - name: Install locked dependencies
24
+ run: uv sync --locked --all-groups
25
+
26
+ - name: Check formatting
27
+ run: uv run ruff format --check .
28
+
29
+ - name: Lint
30
+ run: uv run ruff check .
31
+
32
+ - name: Type check
33
+ run: uv run ty check
34
+
35
+ - name: Test
36
+ run: uv run pytest
37
+
38
+ - name: Build distributions
39
+ run: uv build
40
+
41
+ - name: Upload distributions
42
+ uses: actions/upload-artifact@v4
43
+ with:
44
+ name: python-package-distributions
45
+ path: dist/
46
+ if-no-files-found: error
47
+
48
+ publish:
49
+ name: Publish distributions to PyPI
50
+ needs: build
51
+ runs-on: ubuntu-latest
52
+ environment:
53
+ name: pypi
54
+ url: https://pypi.org/p/py-understat
55
+ permissions:
56
+ id-token: write
57
+ steps:
58
+ - name: Download distributions
59
+ uses: actions/download-artifact@v4
60
+ with:
61
+ name: python-package-distributions
62
+ path: dist/
63
+
64
+ - name: Publish distributions
65
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,9 @@
1
+ .env
2
+ .venv
3
+ testing/**
4
+ __pycache__/
5
+ *.py[cod]
6
+ .pytest_cache/
7
+ build/
8
+ dist/
9
+ *.egg-info/
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,25 @@
1
+ # Understat Data
2
+
3
+ This context retrieves football statistics published by Understat. It presents Understat resources through a stable Python API without redefining the source data.
4
+
5
+ ## Language
6
+
7
+ **Understat resource**:
8
+ A top-level published data subject: a League, Team, Player, or Match.
9
+ _Avoid_: endpoint, entity
10
+
11
+ **Competition season**:
12
+ The campaign beginning in a given calendar year and ending in the next, represented publicly as `YYYY/YYYY+1`.
13
+ _Avoid_: year, calendar year
14
+
15
+ **Resource snapshot**:
16
+ The complete set of data Understat returns for one resource query.
17
+ _Avoid_: table, endpoint response
18
+
19
+ **Team handle**:
20
+ The exact URL-safe name Understat assigns to a Team, such as `Manchester_United`.
21
+ _Avoid_: team name, slug
22
+
23
+ **Understat ID**:
24
+ The positive integer that identifies a Player or Match in Understat data.
25
+ _Avoid_: identifier, string ID
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 buenavista62
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,96 @@
1
+ Metadata-Version: 2.5
2
+ Name: py-understat
3
+ Version: 0.1.0
4
+ Summary: An asynchronous client for Understat football statistics
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.14
7
+ Requires-Dist: httpx>=0.28.1
8
+ Requires-Dist: pydantic>=2.13.5
9
+ Description-Content-Type: text/markdown
10
+
11
+ # py-understat
12
+
13
+ An asynchronous, typed client for football statistics published by [Understat](https://understat.com/). This is an unofficial client and is not affiliated with Understat.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ uv add py-understat
19
+ ```
20
+
21
+ From a checkout, install the package and its development tools with:
22
+
23
+ ```bash
24
+ uv sync --all-groups
25
+ ```
26
+
27
+ ## Query data
28
+
29
+ `UnderstatClient` owns its HTTP resources. Use it as an async context manager.
30
+
31
+ ```python
32
+ import asyncio
33
+
34
+ from py_understat import League, UnderstatClient
35
+
36
+
37
+ async def main() -> None:
38
+ async with UnderstatClient() as client:
39
+ premier_league = await client.league(League.EPL).get("2025/2026")
40
+
41
+ top_scorer = max(premier_league.players, key=lambda player: player.goals)
42
+ print(top_scorer.player_name, top_scorer.goals)
43
+
44
+
45
+ asyncio.run(main())
46
+ ```
47
+
48
+ Each `get()` call returns the complete snapshot Understat supplies for that resource:
49
+
50
+ ```python
51
+ async with UnderstatClient() as client:
52
+ league = await client.league(League.BUNDESLIGA).get("2025/2026")
53
+ team = await client.team("Bayern_Munich").get("2025/2026")
54
+ player = await client.player(8260).get()
55
+ match = await client.match(28778).get()
56
+
57
+ league.players # PlayerStatistic records
58
+ league.matches # MatchRecord records
59
+ league.teams # TeamSeason records keyed by Understat team ID
60
+ team.statistics # Typed team statistic categories
61
+ player.matches # PlayerMatch records
62
+ player.shots # Shot records
63
+ match.rosters # RosterEntry records grouped by home/away side
64
+ match.shots # Shot records grouped by home/away side
65
+ ```
66
+
67
+ ## Identifiers
68
+
69
+ - `League` is an enum containing Understat's six supported competitions.
70
+ - Team queries take the exact Understat team handle, such as `"Manchester_United"`.
71
+ - Player and Match queries take a positive Understat integer ID.
72
+ - League and Team queries take a competition season written as `"YYYY/YYYY+1"`, for example `"2025/2026"`. `Season("2025/2026")` is available when a reusable value is useful.
73
+
74
+ Invalid identifiers fail locally with `InvalidIdentifierError` before making a request.
75
+
76
+ ## Data and failures
77
+
78
+ Known fields are normalized to native Python values: IDs and counts become `int`, expected-goal values become `float`, and source timestamps become `datetime`. Models are frozen, and source fields that the package does not yet name are available through each model's read-only `extra` mapping.
79
+
80
+ The client retries temporary transport failures, `429`, and `5xx` responses with bounded exponential backoff. It honors `Retry-After` up to the configured maximum delay. Catch package-level errors rather than HTTPX implementation errors:
81
+
82
+ ```python
83
+ from py_understat import RateLimitError, ResourceNotFoundError, UnderstatError
84
+
85
+ try:
86
+ async with UnderstatClient() as client:
87
+ snapshot = await client.player(8260).get()
88
+ except ResourceNotFoundError:
89
+ print("Unknown Understat player")
90
+ except RateLimitError:
91
+ print("Understat still rate-limited the request after retries")
92
+ except UnderstatError as error:
93
+ print(f"Understat is unavailable: {error}")
94
+ ```
95
+
96
+ The client sends an identifying user agent. It intentionally has no automatic cache or global rate limiter; callers needing either should apply them around immutable snapshots.
@@ -0,0 +1,86 @@
1
+ # py-understat
2
+
3
+ An asynchronous, typed client for football statistics published by [Understat](https://understat.com/). This is an unofficial client and is not affiliated with Understat.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ uv add py-understat
9
+ ```
10
+
11
+ From a checkout, install the package and its development tools with:
12
+
13
+ ```bash
14
+ uv sync --all-groups
15
+ ```
16
+
17
+ ## Query data
18
+
19
+ `UnderstatClient` owns its HTTP resources. Use it as an async context manager.
20
+
21
+ ```python
22
+ import asyncio
23
+
24
+ from py_understat import League, UnderstatClient
25
+
26
+
27
+ async def main() -> None:
28
+ async with UnderstatClient() as client:
29
+ premier_league = await client.league(League.EPL).get("2025/2026")
30
+
31
+ top_scorer = max(premier_league.players, key=lambda player: player.goals)
32
+ print(top_scorer.player_name, top_scorer.goals)
33
+
34
+
35
+ asyncio.run(main())
36
+ ```
37
+
38
+ Each `get()` call returns the complete snapshot Understat supplies for that resource:
39
+
40
+ ```python
41
+ async with UnderstatClient() as client:
42
+ league = await client.league(League.BUNDESLIGA).get("2025/2026")
43
+ team = await client.team("Bayern_Munich").get("2025/2026")
44
+ player = await client.player(8260).get()
45
+ match = await client.match(28778).get()
46
+
47
+ league.players # PlayerStatistic records
48
+ league.matches # MatchRecord records
49
+ league.teams # TeamSeason records keyed by Understat team ID
50
+ team.statistics # Typed team statistic categories
51
+ player.matches # PlayerMatch records
52
+ player.shots # Shot records
53
+ match.rosters # RosterEntry records grouped by home/away side
54
+ match.shots # Shot records grouped by home/away side
55
+ ```
56
+
57
+ ## Identifiers
58
+
59
+ - `League` is an enum containing Understat's six supported competitions.
60
+ - Team queries take the exact Understat team handle, such as `"Manchester_United"`.
61
+ - Player and Match queries take a positive Understat integer ID.
62
+ - League and Team queries take a competition season written as `"YYYY/YYYY+1"`, for example `"2025/2026"`. `Season("2025/2026")` is available when a reusable value is useful.
63
+
64
+ Invalid identifiers fail locally with `InvalidIdentifierError` before making a request.
65
+
66
+ ## Data and failures
67
+
68
+ Known fields are normalized to native Python values: IDs and counts become `int`, expected-goal values become `float`, and source timestamps become `datetime`. Models are frozen, and source fields that the package does not yet name are available through each model's read-only `extra` mapping.
69
+
70
+ The client retries temporary transport failures, `429`, and `5xx` responses with bounded exponential backoff. It honors `Retry-After` up to the configured maximum delay. Catch package-level errors rather than HTTPX implementation errors:
71
+
72
+ ```python
73
+ from py_understat import RateLimitError, ResourceNotFoundError, UnderstatError
74
+
75
+ try:
76
+ async with UnderstatClient() as client:
77
+ snapshot = await client.player(8260).get()
78
+ except ResourceNotFoundError:
79
+ print("Unknown Understat player")
80
+ except RateLimitError:
81
+ print("Understat still rate-limited the request after retries")
82
+ except UnderstatError as error:
83
+ print(f"Understat is unavailable: {error}")
84
+ ```
85
+
86
+ The client sends an identifying user agent. It intentionally has no automatic cache or global rate limiter; callers needing either should apply them around immutable snapshots.
@@ -0,0 +1,6 @@
1
+ def main():
2
+ print("Hello from py-understat!")
3
+
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "py-understat"
3
+ version = "0.1.0"
4
+ description = "An asynchronous client for Understat football statistics"
5
+ readme = "README.md"
6
+ requires-python = ">=3.14"
7
+ dependencies = [
8
+ "httpx>=0.28.1",
9
+ "pydantic>=2.13.5",
10
+ ]
11
+
12
+ [dependency-groups]
13
+ dev = [
14
+ "ipykernel>=7.3.0",
15
+ "pytest>=9.1.1",
16
+ "ruff>=0.16.5",
17
+ "ty>=0.0.75",
18
+ ]
19
+
20
+ [build-system]
21
+ requires = ["hatchling>=1.28.0"]
22
+ build-backend = "hatchling.build"
23
+
24
+ [tool.ruff]
25
+ target-version = "py314"
26
+
27
+ [tool.pytest.ini_options]
28
+ testpaths = ["tests"]
@@ -0,0 +1,36 @@
1
+ """An asynchronous, typed client for Understat football statistics."""
2
+
3
+ from .client import League, RetryPolicy, Season, UnderstatClient
4
+ from .exceptions import (
5
+ InvalidIdentifierError,
6
+ PayloadValidationError,
7
+ RateLimitError,
8
+ ResourceNotFoundError,
9
+ UnderstatError,
10
+ UpstreamError,
11
+ )
12
+ from .models import (
13
+ LeagueSnapshot,
14
+ MatchSnapshot,
15
+ PlayerSnapshot,
16
+ TeamSnapshot,
17
+ )
18
+
19
+ __all__ = [
20
+ "InvalidIdentifierError",
21
+ "League",
22
+ "LeagueSnapshot",
23
+ "MatchSnapshot",
24
+ "PayloadValidationError",
25
+ "PlayerSnapshot",
26
+ "RateLimitError",
27
+ "ResourceNotFoundError",
28
+ "RetryPolicy",
29
+ "Season",
30
+ "TeamSnapshot",
31
+ "UnderstatClient",
32
+ "UnderstatError",
33
+ "UpstreamError",
34
+ ]
35
+
36
+ __version__ = "0.1.0"