Skip to main content

ilc-models

version Python Version from PEP 621 TOML coverage

ilc-models contains all data models for the ILC project.

It's a set of pure Pydantic v2 models with no I/O — a League of Match objects, each with its Lineups, Goal/Card/Substitution events, and the TableRow results computed from them, plus User and UserPublic models for accounts. Its only dependencies are pydantic (with the email extra) and argon2-cffi, which User uses to hash passwords. This package is the shared data contract used across the ilc-api, ilc-app and ilc-admin repositories, so a field change here is a breaking change for all three.

Installation

Requires Python 3.12 or later.

pip install ilc-models

Or with uv:

uv add ilc-models

Quick start

from ilc_models import League, Match, Teams, Score

league = League(
    league_id=1,
    name="Premiership",
    year=2025,
    start="2025-08-01",
    end="2026-05-01",
    current=True,
    coverage={"lineups": True},
    teams=["Team A", "Team B"],
    rounds={
        "1": [
            Match(
                match_id=1,
                kickoff="2025-08-09T15:00:00+01:00",
                round="1",
                teams=Teams(home="Team A", away="Team B"),
                status="FT",
                score=Score(home=2, away=1),
            )
        ]
    },
)

for row in league.table():
    print(row)

Model map

League
 rounds: dict[str, list[Match]]
    Match
       teams: Teams          # home / away team names
       score: Score          # home / away goals
       goals: list[Goal]
       cards: list[Card]
       substitutions: list[Substitution]
       lineups: Lineups
          home: Lineup
          away: Lineup
             starting: list[tuple[int, BasePlayer]]   # shirt_no, player
             subs: list[tuple[int, BasePlayer]]

Goal, Card, Substitution and LineupStatus all subclass the abstract BaseEvent and are aliased together as Event. League.players holds every Player who has featured in the league, keyed by player_id as a string.

Users are modelled separately: User (the stored shape, including password_hash) subclasses UserPublic (just email).

Users

from ilc_models import User

user = User.create("alice@example.com", "correct horse battery staple")

user.verify_password("correct horse battery staple")  # True
user.verify_password("wrong")  # False

# After a successful login, upgrade the hash if its parameters are outdated
attempt = "correct horse battery staple"
if user.verify_password(attempt) and user.needs_rehash():
    user.set_password(attempt)
    # ...then persist the user

User.create hashes the password with argon2 and never stores it. To load a user from the database, pass the stored hash instead: User(email=..., password_hash=...). Supplying both password and password_hash raises an error.

Warning: User.model_dump() includes password_hash. Anything that leaves the API should use UserPublic (e.g. FastAPI's response_model=UserPublic).

Conventions worth knowing

A few behaviours are deliberate and easy to trip over — see the full usage guide for detail:

  • Goal.team is the team credited with the goal, not the scorer's team. For an own goal the scorer belongs to the opposing team — call Match.event_team(event) to resolve the team a player in an event actually plays for.
  • Player equality compares player_id alone (falling back to name only when both IDs are 0), because the upstream API spells player names differently in event payloads versus player downloads.
  • League.table() models the Scottish-style split — once a team has played more than League.split matches, the table freezes the top/bottom halves by position at the split point and sorts each half independently. League.split is the number of matches each team plays before the split; the default of 0 means no split.
  • The team, teams, h2h and player filters on League.matches() are alternatives, not combinable. If you pass more than one, only the first applies, in that order. League.match_players() works the same way (without player), and Match.players() prefers teams over team.
  • Dates are validated ISO strings, not date objects. Match.kickoff is a full offset-aware timestamp; League.start/end and Deduction.date are yyyy-mm-dd. Use Match.date to get a datetime.date.
  • EventTime.plus (stoppage time) is only valid at the end of a half — i.e. when minutes is 45, 90, 105 or 120.

Documentation

Full API reference and usage guide: ilc-models.readthedocs.io

See CHANGELOG.md for release history.

Development

Task runner is just; Python tooling is uv.

just check   # ruff check --fix, ruff format, then ty check (type checking)
just test    # run tests with pytest
just cov     # pytest with coverage, then regenerate the coverage badge
just docs    # build the Sphinx docs locally
just tox     # run the tests across Python 3.12-3.14 and lowest/highest dependencies

License

MIT — see LICENSE.txt.

Metadata

Release files for ilc-models 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ilc-models 0.3.0
File Size Uploaded Provenance
ilc_models-0.3.0.tar.gz 102.4 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for ilc-models 0.3.0
File Interpreter ABI Platform
ilc_models-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 118.8 kB

Release files / ilc_models-0.3.0.tar.gz

Download URL ilc_models-0.3.0.tar.gz
Size 102.4 kB
Tags Source
SHA-256 checksum
How to use checksums
f0ec21aa382df752cc4dc6c760ea0e944bead001ba07031d3920064ed7b0913f
BLAKE2b-256 checksum
How to use checksums
6076b1dcfdd96b86c3ac405f2a33f9ebf3756bc4d8cb4ea99ceac1ff57a3d335
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / ilc_models-0.3.0-py3-none-any.whl

Download URL ilc_models-0.3.0-py3-none-any.whl
Size 16.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
21a5f3ecdcdfebb5c0895190751fb7093a93e8e035816e6e07c9c9b33c903d77
BLAKE2b-256 checksum
How to use checksums
cadbc474caac06cf804b81088958166d474fde6a6e2a529c5f593ed860a8b2f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for ilc-models 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page