Skip to content

Commit

Permalink
Increase code coverage (#466)
Browse files Browse the repository at this point in the history
Remove unused code and add some tests
Also fix detection of running pytest
  • Loading branch information
evroon authored Feb 10, 2024
1 parent c2160f6 commit 9479c92
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 145 deletions.
2 changes: 1 addition & 1 deletion backend/bracket/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]:

yield

if environment != Environment.CI:
if environment is not Environment.CI:
await database.disconnect()

await AsyncioTasksManager.gather()
Expand Down
3 changes: 2 additions & 1 deletion backend/bracket/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import os
import sys
from enum import auto
from typing import Annotated

Expand Down Expand Up @@ -65,7 +66,7 @@ class DemoConfig(Config):


def currently_testing() -> bool:
return "PYTEST_CURRENT_TEST" in os.environ
return "pytest" in sys.modules


environment = Environment(
Expand Down
113 changes: 0 additions & 113 deletions backend/bracket/logic/planning/matches.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
import random
from collections import defaultdict
from typing import NamedTuple

from heliclockter import timedelta

from bracket.models.db.match import (
Match,
MatchCreateBody,
MatchRescheduleBody,
MatchWithDetails,
MatchWithDetailsDefinitive,
)
from bracket.models.db.stage_item_inputs import StageItemInputGeneric
from bracket.models.db.tournament import Tournament
from bracket.models.db.util import StageWithStageItems
from bracket.sql.courts import get_all_courts_in_tournament
from bracket.sql.matches import (
sql_create_match,
sql_reschedule_match_and_determine_duration_and_margin,
)
from bracket.sql.stages import get_full_tournament_details
from bracket.sql.tournaments import sql_get_tournament
from bracket.utils.types import assert_some


async def create_match_and_assign_free_court(
tournament_id: int,
match_body: MatchCreateBody,
) -> Match:
return await sql_create_match(match_body)


async def schedule_all_unscheduled_matches(tournament_id: int) -> None:
tournament = await sql_get_tournament(tournament_id)
stages = await get_full_tournament_details(tournament_id)
Expand Down Expand Up @@ -85,107 +73,6 @@ async def schedule_all_unscheduled_matches(tournament_id: int) -> None:
await update_start_times_of_matches(tournament_id)


def has_conflict(
match: MatchWithDetailsDefinitive | MatchWithDetails,
team_defs: set[int | None],
matches_per_team: dict[int | None, list[Match]],
) -> bool:
for team in team_defs:
for existing_match in matches_per_team[team]:
if existing_match.start_time == match.start_time:
return True

return False


async def todo_schedule_all_matches(tournament_id: int) -> None:
tournament = await sql_get_tournament(tournament_id)
stages = await get_full_tournament_details(tournament_id)
courts = await get_all_courts_in_tournament(tournament_id)

match_count_per_court: dict[int, int] = {assert_some(court.id): 0 for court in courts}
matches_per_court: dict[int, list[Match]] = {assert_some(court.id): [] for court in courts}
matches_per_team: dict[int | None, list[Match]] = defaultdict(list)

matches_to_schedule = [
match.model_copy(update={"court_id": None, "position_in_schedule": None})
for stage in stages
for stage_item in stage.stage_items
for round_ in stage_item.rounds
for match in round_.matches
]
await iterative_scheduling(
match_count_per_court,
matches_per_court,
matches_per_team,
matches_to_schedule,
tournament,
)


async def iterative_scheduling(
match_count_per_court: dict[int, int],
matches_per_court: dict[int, list[Match]],
matches_per_team: dict[int | None, list[Match]],
matches_to_schedule: list[MatchWithDetailsDefinitive | MatchWithDetails],
tournament: Tournament,
) -> None:
attempts_since_last_write = 0

while len(matches_to_schedule) > 0:
attempts_since_last_write += 1
match = matches_to_schedule[0]

StageItemInputGeneric(
team_id=match.team1_id,
winner_from_stage_item_id=match.team1_winner_from_stage_item_id,
winner_position=match.team1_winner_position,
winner_from_match_id=match.team1_winner_from_match_id,
)
StageItemInputGeneric(
team_id=match.team2_id,
winner_from_stage_item_id=match.team2_winner_from_stage_item_id,
winner_position=match.team2_winner_position,
winner_from_match_id=match.team2_winner_from_match_id,
)
team_defs = {match.team1_id, match.team2_id}
court_id = sorted(match_count_per_court.items(), key=lambda x: x[1])[0][0]

try:
position_in_schedule = len(matches_per_court[court_id])
last_match = matches_per_court[court_id][-1]
start_time = assert_some(last_match.start_time) + timedelta(
minutes=match.duration_minutes
)
except IndexError:
start_time = tournament.start_time
position_in_schedule = 0

updated_match = match.model_copy(
update={
"start_time": start_time,
"position_in_schedule": position_in_schedule,
"court_id": court_id,
}
)

match_has_conflict = has_conflict(updated_match, team_defs, matches_per_team)
if match_has_conflict and attempts_since_last_write < 100:
continue

match_count_per_court[court_id] += 1
matches_per_court[court_id].append(updated_match)
matches_per_team[match.team1_id].append(updated_match)
matches_per_team[match.team2_id].append(updated_match)
matches_to_schedule.remove(match)
attempts_since_last_write = 0
random.shuffle(matches_to_schedule)

await sql_reschedule_match_and_determine_duration_and_margin(
assert_some(match.id), court_id, start_time, position_in_schedule, match, tournament
)


class MatchPosition(NamedTuple):
match: MatchWithDetailsDefinitive | MatchWithDetails
position: float
Expand Down
16 changes: 4 additions & 12 deletions backend/bracket/logic/scheduling/builder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from fastapi import HTTPException
from heliclockter import datetime_utc

from bracket.database import database
from bracket.logic.scheduling.elimination import (
build_single_elimination_stage_item,
get_number_of_rounds_to_create_single_elimination,
Expand All @@ -18,7 +16,7 @@
)
from bracket.models.db.team import FullTeamWithPlayers
from bracket.models.db.util import StageWithStageItems
from bracket.sql.rounds import get_next_round_name
from bracket.sql.rounds import get_next_round_name, sql_create_round
from bracket.sql.stage_items import get_stage_item
from bracket.utils.types import assert_some

Expand All @@ -35,18 +33,12 @@ async def create_rounds_for_new_stage_item(tournament_id: int, stage_item: Stage
case other:
raise NotImplementedError(f"No round creation implementation for {other}")

now = datetime_utc.now()
for _ in range(rounds_count):
await database.execute(
query="""
INSERT INTO rounds (created, stage_item_id, name, is_draft, is_active)
VALUES (:created, :stage_item_id, :name, :is_draft, :is_active)
""",
values=RoundToInsert(
created=now,
await sql_create_round(
RoundToInsert(
stage_item_id=assert_some(stage_item.id),
name=await get_next_round_name(tournament_id, assert_some(stage_item.id)),
).model_dump(),
),
)


Expand Down
6 changes: 3 additions & 3 deletions backend/bracket/logic/scheduling/elimination.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from bracket.logic.planning.matches import create_match_and_assign_free_court
from bracket.models.db.match import Match, MatchCreateBody
from bracket.models.db.tournament import Tournament
from bracket.models.db.util import RoundWithMatches, StageItemWithRounds
from bracket.sql.matches import sql_create_match
from bracket.sql.rounds import get_rounds_for_stage_item
from bracket.sql.tournaments import sql_get_tournament
from bracket.utils.types import assert_some
Expand Down Expand Up @@ -79,13 +79,13 @@ async def build_single_elimination_stage_item(
first_round = rounds[0]

prev_matches = [
await create_match_and_assign_free_court(tournament_id, match)
await sql_create_match(match)
for match in determine_matches_first_round(first_round, stage_item, tournament)
]

for round_ in rounds[1:]:
prev_matches = [
await create_match_and_assign_free_court(tournament_id, match)
await sql_create_match(match)
for match in determine_matches_subsequent_round(prev_matches, round_, tournament)
]

Expand Down
4 changes: 2 additions & 2 deletions backend/bracket/logic/scheduling/round_robin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from bracket.logic.planning.matches import create_match_and_assign_free_court
from bracket.models.db.match import (
MatchCreateBody,
)
from bracket.models.db.util import StageItemWithRounds
from bracket.sql.matches import sql_create_match
from bracket.sql.tournaments import sql_get_tournament
from bracket.utils.types import assert_some

Expand Down Expand Up @@ -60,7 +60,7 @@ async def build_round_robin_stage_item(tournament_id: int, stage_item: StageItem
custom_duration_minutes=None,
custom_margin_minutes=None,
)
await create_match_and_assign_free_court(tournament_id, match)
await sql_create_match(match)


def get_number_of_rounds_to_create_round_robin(team_count: int) -> int:
Expand Down
2 changes: 1 addition & 1 deletion backend/bracket/logic/scheduling/upcoming_matches.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async def get_upcoming_matches_for_swiss_round(
[stage_item] = stage.stage_items

if stage_item.type is not StageType.SWISS:
raise HTTPException(400, "There is no draft round, so no matches can be scheduled.")
raise HTTPException(400, "Expected stage item to be of type SWISS.")

rounds = await get_rounds_for_stage_item(tournament_id, assert_some(stage_item.id))
teams = await get_teams_with_members(tournament_id, only_active_teams=True)
Expand Down
1 change: 0 additions & 1 deletion backend/bracket/models/db/round.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class RoundCreateBody(BaseModelORM):


class RoundToInsert(RoundUpdateBody):
created: datetime_utc
stage_item_id: int
is_draft: bool = False
is_active: bool = False
4 changes: 1 addition & 3 deletions backend/bracket/routes/matches.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException

from bracket.logic.planning.matches import (
create_match_and_assign_free_court,
handle_match_reschedule,
schedule_all_unscheduled_matches,
)
Expand Down Expand Up @@ -146,8 +145,7 @@ async def create_matches_automatically(
assert isinstance(match, SuggestedMatch)

assert round_.id and match.team1.id and match.team2.id
await create_match_and_assign_free_court(
tournament_id,
await sql_create_match(
MatchCreateBody(
round_id=round_.id,
team1_id=match.team1.id,
Expand Down
11 changes: 4 additions & 7 deletions backend/bracket/routes/rounds.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from fastapi import APIRouter, Depends, HTTPException
from heliclockter import datetime_utc
from starlette import status

from bracket.database import database
Expand All @@ -20,7 +19,7 @@
round_with_matches_dependency,
)
from bracket.schema import rounds
from bracket.sql.rounds import get_next_round_name, set_round_active_or_draft
from bracket.sql.rounds import get_next_round_name, set_round_active_or_draft, sql_create_round
from bracket.sql.stage_items import get_stage_item
from bracket.sql.stages import get_full_tournament_details

Expand Down Expand Up @@ -78,13 +77,11 @@ async def create_round(
detail=f"Stage type {stage_item.type} doesn't support manual creation of rounds",
)

round_id = await database.execute(
query=rounds.insert(),
values=RoundToInsert(
created=datetime_utc.now(),
round_id = await sql_create_round(
RoundToInsert(
stage_item_id=round_body.stage_item_id,
name=await get_next_round_name(tournament_id, round_body.stage_item_id),
).model_dump(),
),
)

await set_round_active_or_draft(round_id, tournament_id, is_active=False, is_draft=True)
Expand Down
2 changes: 1 addition & 1 deletion backend/bracket/routes/stage_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,6 @@ async def start_next_round(
) from exc

assert next_round.id is not None
await set_round_active_or_draft(next_round.id, tournament_id, is_active=False, is_draft=False)
await set_round_active_or_draft(next_round.id, tournament_id, is_active=True, is_draft=False)

return SuccessResponse()
19 changes: 19 additions & 0 deletions backend/bracket/sql/rounds.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
from bracket.database import database
from bracket.models.db.round import RoundToInsert
from bracket.models.db.util import RoundWithMatches
from bracket.sql.stage_items import get_stage_item
from bracket.sql.stages import get_full_tournament_details


async def sql_create_round(round_: RoundToInsert) -> int:
query = """
INSERT INTO rounds (created, is_draft, is_active, name, stage_item_id)
VALUES (NOW(), :is_draft, :is_active, :name, :stage_item_id)
RETURNING id
"""
result: int = await database.fetch_val(
query=query,
values={
"name": round_.name,
"is_draft": round_.is_draft,
"is_active": round_.is_active,
"stage_item_id": round_.stage_item_id,
},
)
return result


async def get_rounds_for_stage_item(
tournament_id: int, stage_item_id: int
) -> list[RoundWithMatches]:
Expand Down
Loading

0 comments on commit 9479c92

Please sign in to comment.