Skip to main content

Football Tournament Engine (football-tournament)

A production-ready, reusable Python library for managing football tournaments.

football-tournament is designed as a standalone domain engine. It encapsulates all core tournament management logic (team registration, stadium management, match generation, scheduling, conflict detection, result recording, league standings, and knockout bracket progression) without coupling to any web framework, ORM, database, or UI layer.

It can be seamlessly integrated into REST APIs (FastAPI, Django), university systems, mobile applications, or CLI tools.


Features

  • Multiple Tournament Formats:
    • League / Table: Round-robin (single and double round-robin).
    • Knockout: Single-elimination brackets with automatic bye handling for non-power-of-two team counts.
    • Group + Knockout: Group stage round-robins followed by automatic qualification and knockout bracket generation.
  • Robust Scheduling Engine:
    • Manual and automatic scheduling based on configurable time windows (SchedulingConfig).
    • Strict conflict detection: prevents stadium double-booking, team overlapping matches, self-play, and invalid time slots.
  • Standings Calculator:
    • Computes Played, Won, Drawn, Lost, Goals For, Goals Against, Goal Difference, and Points.
    • Configurable points system and tie-breaking rules (points, goal_difference, goals_for, goals_against, wins, played, head_to_head, name).
  • Results & Knockout Progression:
    • Records scores and optional penalty shoot-outs; penalty results decide the winner of a drawn knockout tie.
    • Winners are auto-advanced into the next round and the bracket is exposed as ordered rounds via get_bracket().
    • Matches can be cancelled; cancelled matches cannot be re-scored.
  • Tournament Lifecycle & Validation:
    • State management (DRAFT, REGISTRATION, SCHEDULED, ONGOING, COMPLETED, CANCELLED) with an enforced state machine via set_status(); tournaments auto-complete when every match is done.
    • Comprehensive Pydantic validation, config validation, and custom domain exceptions.
  • Persistence:
    • save_tournament() / load_tournament() (or the engine.save() / engine.load() convenience wrappers) serialize a full tournament to a JSON file that survives process restarts.
  • Framework & Database Independent:
    • Works entirely in memory with pure Python objects.
    • Easily adaptable to SQLAlchemy, SQLModel, PostgreSQL, SQLite, or Supabase via persistence adapters.

Installation

pip install football-tournament

For development (testing and linting):

pip install -e ".[dev]"

Quick Start

from datetime import date, time
from football_tournament import (
    TournamentEngine,
    TournamentFormat,
    Team,
    Stadium,
    SchedulingConfig,
)

# 1. Initialize engine
engine = TournamentEngine()

# 2. Create tournament
tournament = engine.create_tournament(
    name="University Football Tournament",
    format=TournamentFormat.LEAGUE,
)

# 3. Register teams
teams = [
    Team(name="Software Engineering"),
    Team(name="Computer Science"),
    Team(name="Electrical Engineering"),
    Team(name="Civil Engineering"),
]
for team in teams:
    engine.add_team(tournament, team)

# 4. Add stadium
stadium = Stadium(name="Main Stadium", location="Adama", capacity=5000)
engine.add_stadium(tournament, stadium)

# 5. Generate matches
matches = engine.generate_matches(tournament)

# 6. Automatically schedule matches
config = SchedulingConfig(
    start_date=date(2026, 10, 1),
    end_date=date(2026, 10, 5),
    daily_start=time(14, 0),
    daily_end=time(18, 0),
)
engine.auto_schedule(tournament, config)

# 7. Record match result
engine.set_match_result(tournament, matches[0].id, home_score=2, away_score=1)

# 8. View standings
standings = engine.get_standings(tournament)
for rec in standings:
    print(f"{rec.team.name}: {rec.points} pts (GD: {rec.goal_difference})")

Knockout with penalties, bracket and persistence

from football_tournament import TournamentEngine, TournamentFormat, Team

engine = TournamentEngine()

tournament = engine.create_tournament(name="Champions Cup", format=TournamentFormat.KNOCKOUT)
for name in ["Real Madrid", "Bayern", "Liverpool", "PSG"]:
    engine.add_team(tournament, Team(name=name))

engine.generate_matches(tournament)

# First semifinal ends level; penalties decide the winner.
sf = tournament.matches[0]
engine.set_match_result(
    tournament, sf.id, home_score=1, away_score=1,
    home_penalties=4, away_penalties=3,   # home team advances
)

# The winner of that semifinal is now slotted into the Final.
import json
bracket = engine.get_bracket(tournament)          # list[Round]
print([round.name for round in bracket])          # e.g. ["Semifinal", "Final"]

# Persist the whole tournament to disk and reload it later.
path = engine.save(tournament, "champions_cup.json")
reloaded = engine.load(path)
assert reloaded.name == "Champions Cup"

See engine.get_standings(tournament, tiebreakers=["points", "head_to_head"]) for custom tie-breaking, and engine.cancel_match(...) / tournament.set_status(...) for cancellation and lifecycle control.


Architecture Overview

football_tournament/
├── models/         # Domain entities (Tournament, Team, Stadium, Match, Group, Round)
├── enums/          # Controlled vocabularies (TournamentFormat, MatchStatus, TournamentStatus)
├── formats/        # Format strategies (League, Knockout, Group + Knockout)
├── scheduling/     # Scheduler, conflict detectors, time slots, scheduling config
├── standings/      # Standings calculator and ranking rules
├── exceptions/     # Custom domain errors
├── persistence.py  # JSON save/load helpers
└── engine.py       # High-level TournamentEngine facade

Separation of Responsibilities

  1. Match Generation: Creates fixture pairings without date/time/stadium bindings.
  2. Scheduling: Assigns time slots and stadiums while enforcing strict conflict rules.
  3. Results: Records scores and updates match status.
  4. Standings: Computes table statistics from completed matches.

Integrating with FastAPI

Because the package is framework-independent, integrating it into FastAPI is straightforward:

from fastapi import FastAPI, HTTPException
from football_tournament import TournamentEngine, TournamentFormat, Team, Stadium

app = FastAPI()
engine = TournamentEngine()

# In-memory store for demonstration (replace with database adapter in production)
tournaments = {}

@app.post("/tournaments")
def create_tournament(name: str, format: TournamentFormat):
    tournament = engine.create_tournament(name=name, format=format)
    tournaments[tournament.id] = tournament
    return {"id": tournament.id, "name": tournament.name, "format": tournament.format}

@app.post("/tournaments/{tournament_id}/teams")
def add_team(tournament_id: str, name: str):
    if tournament_id not in tournaments:
        raise HTTPException(status_code=404, detail="Tournament not found")
    t = tournaments[tournament_id]
    team = Team(name=name)
    engine.add_team(t, team)
    return {"team_id": team.id, "name": team.name}

Running Tests

Run the test suite with pytest:

PYTHONPATH=src pytest

Building & Publishing

Build Wheel and Source Distribution

pip install build
python -m build

Publish to PyPI

pip install twine
python -m twine upload dist/*

License

MIT License. See LICENSE for details.

Release files for football-tournament 0.2.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 football-tournament 0.2.0
File Size Uploaded
football_tournament-0.2.0.tar.gz 34.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for football-tournament 0.2.0
File Interpreter ABI Platform
football_tournament-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 66.9 kB

Release files / football_tournament-0.2.0.tar.gz

Download URL football_tournament-0.2.0.tar.gz
Size 34.2 kB
Tags Source
SHA-256 checksum
How to use checksums
9d0f024c6b11b4acf9ae55cb799cf5743cfc33b50d253968e681ccb21d6bfe84
BLAKE2b-256 checksum
How to use checksums
b9c6aa45cc7a9ebba17310a89bdcbe8766abe925f9e3c29983b2347d7c4212b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / football_tournament-0.2.0-py3-none-any.whl

Download URL football_tournament-0.2.0-py3-none-any.whl
Size 32.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
df3e866754e1a1238eea12ba3c01e1f1ea65bcf36eb4c2d522537f00ebc38a77
BLAKE2b-256 checksum
How to use checksums
8c5c45d844c32955366252a07b817859902bcc5b406edcf79b71d1ce35ae6b18
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

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