mirror of
https://github.com/evroon/bracket.git
synced 2026-08-01 02:36:45 -04:00
Replace databases dependency with asyncpg, remove SQLAlchemy except schema.py
- Rewrite database.py with asyncpg pool-based DatabasePool class - Support named params (:param) → positional ($N) conversion - Transaction context manager using contextvars for connection reuse - Replace all SQLAlchemy query building with raw SQL in routes and sql modules - Update utils/db.py to use raw SQL strings instead of SQLAlchemy Select - Update utils/db_init.py to use local engine creation for DDL only - Update all test files to use string table names and raw SQL - Remove databases and sqlalchemy-stubs from dependencies - Keep sqlalchemy only for schema.py definitions and alembic migrations
This commit is contained in:
committed by
GitHub
parent
3606868b98
commit
4835aa7311
@@ -1,7 +1,9 @@
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from contextvars import ContextVar
|
||||
from typing import Any
|
||||
|
||||
import sqlalchemy
|
||||
from databases import Database
|
||||
import asyncpg
|
||||
from heliclockter import datetime_utc
|
||||
|
||||
from bracket.config import config
|
||||
@@ -12,7 +14,7 @@ def datetime_decoder(value: str) -> datetime_utc:
|
||||
return datetime_utc.fromisoformat(value)
|
||||
|
||||
|
||||
async def asyncpg_init(connection: Any) -> None:
|
||||
async def _init_connection(connection: asyncpg.Connection) -> None: # type: ignore[type-arg]
|
||||
for timestamp_type in ("timestamp", "timestamptz"):
|
||||
await connection.set_type_codec(
|
||||
timestamp_type,
|
||||
@@ -22,6 +24,144 @@ async def asyncpg_init(connection: Any) -> None:
|
||||
)
|
||||
|
||||
|
||||
database = Database(str(config.pg_dsn), init=asyncpg_init)
|
||||
def _convert_named_params(query: str, values: dict[str, Any]) -> tuple[str, list[Any]]:
|
||||
"""Convert :param style parameters to $1, $2, ... style for asyncpg."""
|
||||
params: list[Any] = []
|
||||
result = query
|
||||
# Sort keys by length (longest first) to avoid partial replacements
|
||||
sorted_keys = sorted(values.keys(), key=len, reverse=True)
|
||||
# First pass: replace all :param with unique placeholders
|
||||
placeholders: dict[str, str] = {}
|
||||
for key in sorted_keys:
|
||||
placeholder = f"\x00PARAM_{key}\x00"
|
||||
placeholders[key] = placeholder
|
||||
result = result.replace(f":{key}", placeholder)
|
||||
# Second pass: replace placeholders with $N
|
||||
for key in sorted_keys:
|
||||
placeholder = placeholders[key]
|
||||
if placeholder in result:
|
||||
params.append(values[key])
|
||||
idx = len(params)
|
||||
result = result.replace(placeholder, f"${idx}", 1)
|
||||
# Handle multiple occurrences of same param
|
||||
while placeholder in result:
|
||||
params.append(values[key])
|
||||
idx = len(params)
|
||||
result = result.replace(placeholder, f"${idx}", 1)
|
||||
return result, params
|
||||
|
||||
engine = sqlalchemy.create_engine(str(config.pg_dsn))
|
||||
|
||||
# Context variable to track the current transaction connection
|
||||
_transaction_connection: ContextVar[asyncpg.Connection | None] = ContextVar( # type: ignore[type-arg]
|
||||
"_transaction_connection", default=None
|
||||
)
|
||||
|
||||
|
||||
class _Transaction:
|
||||
"""Context manager for database transactions."""
|
||||
|
||||
def __init__(self, pool: asyncpg.Pool) -> None: # type: ignore[type-arg]
|
||||
self._pool = pool
|
||||
self._connection: asyncpg.Connection | None = None # type: ignore[type-arg]
|
||||
self._transaction: asyncpg.connection.transaction.Transaction | None = None
|
||||
self._token: Any = None
|
||||
|
||||
async def __aenter__(self) -> "_Transaction":
|
||||
self._connection = await self._pool.acquire()
|
||||
self._transaction = self._connection.transaction()
|
||||
await self._transaction.start()
|
||||
self._token = _transaction_connection.set(self._connection)
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
||||
assert self._connection is not None
|
||||
assert self._transaction is not None
|
||||
assert self._token is not None
|
||||
try:
|
||||
if exc_type is not None:
|
||||
await self._transaction.rollback()
|
||||
else:
|
||||
await self._transaction.commit()
|
||||
finally:
|
||||
_transaction_connection.reset(self._token)
|
||||
await self._pool.release(self._connection)
|
||||
|
||||
|
||||
class DatabasePool:
|
||||
"""asyncpg-based database wrapper providing an interface compatible with
|
||||
the old `databases.Database` usage in this codebase."""
|
||||
|
||||
def __init__(self, dsn: str) -> None:
|
||||
self._dsn = dsn
|
||||
self._pool: asyncpg.Pool | None = None # type: ignore[type-arg]
|
||||
|
||||
async def connect(self) -> None:
|
||||
self._pool = await asyncpg.create_pool(self._dsn, init=_init_connection)
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
if self._pool is not None:
|
||||
await self._pool.close()
|
||||
self._pool = None
|
||||
|
||||
@property
|
||||
def pool(self) -> asyncpg.Pool: # type: ignore[type-arg]
|
||||
assert self._pool is not None, "Database pool is not initialized. Call connect() first."
|
||||
return self._pool
|
||||
|
||||
def _get_conn(self) -> asyncpg.Connection | asyncpg.Pool: # type: ignore[type-arg]
|
||||
"""Return the transaction connection if inside a transaction, otherwise the pool."""
|
||||
conn = _transaction_connection.get()
|
||||
if conn is not None:
|
||||
return conn
|
||||
return self.pool
|
||||
|
||||
async def fetch_one(
|
||||
self, query: str, values: dict[str, Any] | None = None
|
||||
) -> asyncpg.Record | None:
|
||||
conn = self._get_conn()
|
||||
if values:
|
||||
converted_query, params = _convert_named_params(query, values)
|
||||
return await conn.fetchrow(converted_query, *params)
|
||||
return await conn.fetchrow(query)
|
||||
|
||||
async def fetch_all(
|
||||
self, query: str, values: dict[str, Any] | None = None
|
||||
) -> list[asyncpg.Record]:
|
||||
conn = self._get_conn()
|
||||
if values:
|
||||
converted_query, params = _convert_named_params(query, values)
|
||||
return await conn.fetch(converted_query, *params)
|
||||
return await conn.fetch(query)
|
||||
|
||||
async def fetch_val(
|
||||
self, query: str, values: dict[str, Any] | None = None, column: int = 0
|
||||
) -> Any:
|
||||
conn = self._get_conn()
|
||||
if values:
|
||||
converted_query, params = _convert_named_params(query, values)
|
||||
return await conn.fetchval(converted_query, *params, column=column)
|
||||
return await conn.fetchval(query, column=column)
|
||||
|
||||
async def execute(self, query: str, values: dict[str, Any] | None = None) -> str:
|
||||
conn = self._get_conn()
|
||||
if values:
|
||||
converted_query, params = _convert_named_params(query, values)
|
||||
return await conn.execute(converted_query, *params)
|
||||
return await conn.execute(query)
|
||||
|
||||
def transaction(self) -> _Transaction:
|
||||
return _Transaction(self.pool)
|
||||
|
||||
@asynccontextmanager
|
||||
async def acquire(self) -> AsyncIterator[asyncpg.Connection]: # type: ignore[type-arg]
|
||||
async with self.pool.acquire() as conn:
|
||||
yield conn
|
||||
|
||||
async def __aenter__(self) -> "DatabasePool":
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *args: Any) -> None:
|
||||
pass
|
||||
|
||||
|
||||
database = DatabasePool(str(config.pg_dsn))
|
||||
|
||||
@@ -12,7 +12,6 @@ from bracket.config import config
|
||||
from bracket.database import database
|
||||
from bracket.models.db.tournament import Tournament
|
||||
from bracket.models.db.user import UserInDB, UserPublic
|
||||
from bracket.schema import tournaments
|
||||
from bracket.sql.tournaments import sql_get_tournament_by_endpoint_name
|
||||
from bracket.sql.users import get_user, get_user_access_to_club, get_user_access_to_tournament
|
||||
from bracket.utils.db import fetch_all_parsed
|
||||
@@ -140,7 +139,9 @@ async def user_authenticated_or_public_dashboard(
|
||||
pass
|
||||
|
||||
tournaments_fetched = await fetch_all_parsed(
|
||||
database, Tournament, tournaments.select().where(tournaments.c.id == tournament_id)
|
||||
database, Tournament,
|
||||
"SELECT * FROM tournaments WHERE id = :tournament_id",
|
||||
{"tournament_id": tournament_id},
|
||||
)
|
||||
if len(tournaments_fetched) < 1 or not tournaments_fetched[0].dashboard_public:
|
||||
raise HTTPException(
|
||||
|
||||
@@ -14,7 +14,6 @@ from bracket.routes.auth import (
|
||||
)
|
||||
from bracket.routes.models import CourtsResponse, SingleCourtResponse, SuccessResponse
|
||||
from bracket.routes.util import disallow_archived_tournament
|
||||
from bracket.schema import courts
|
||||
from bracket.sql.courts import get_all_courts_in_tournament, sql_delete_court, update_court
|
||||
from bracket.sql.stages import get_full_tournament_details
|
||||
from bracket.utils.db import fetch_one_parsed
|
||||
@@ -50,9 +49,8 @@ async def update_court_by_id(
|
||||
await fetch_one_parsed(
|
||||
database,
|
||||
Court,
|
||||
courts.select().where(
|
||||
(courts.c.id == court_id) & (courts.c.tournament_id == tournament_id)
|
||||
),
|
||||
"SELECT * FROM courts WHERE id = :court_id AND tournament_id = :tournament_id",
|
||||
{"court_id": court_id, "tournament_id": tournament_id},
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -94,22 +92,25 @@ async def create_court(
|
||||
existing_courts = await get_all_courts_in_tournament(tournament_id)
|
||||
check_requirement(existing_courts, user, "max_courts")
|
||||
|
||||
last_record_id = await database.execute(
|
||||
query=courts.insert(),
|
||||
values=CourtToInsert(
|
||||
**court_body.model_dump(),
|
||||
created=datetime_utc.now(),
|
||||
tournament_id=tournament_id,
|
||||
).model_dump(),
|
||||
insertable = CourtToInsert(
|
||||
**court_body.model_dump(),
|
||||
created=datetime_utc.now(),
|
||||
tournament_id=tournament_id,
|
||||
)
|
||||
values = insertable.model_dump()
|
||||
columns = ", ".join(values.keys())
|
||||
placeholders = ", ".join(f":{k}" for k in values.keys())
|
||||
last_record_id = await database.fetch_val(
|
||||
query=f"INSERT INTO courts ({columns}) VALUES ({placeholders}) RETURNING id",
|
||||
values=values,
|
||||
)
|
||||
return SingleCourtResponse(
|
||||
data=assert_some(
|
||||
await fetch_one_parsed(
|
||||
database,
|
||||
Court,
|
||||
courts.select().where(
|
||||
courts.c.id == last_record_id and courts.c.tournament_id == tournament_id
|
||||
),
|
||||
"SELECT * FROM courts WHERE id = :court_id AND tournament_id = :tournament_id",
|
||||
{"court_id": last_record_id, "tournament_id": tournament_id},
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -14,7 +14,6 @@ from bracket.routes.models import (
|
||||
SuccessResponse,
|
||||
)
|
||||
from bracket.routes.util import disallow_archived_tournament
|
||||
from bracket.schema import players
|
||||
from bracket.sql.players import (
|
||||
get_all_players_in_tournament,
|
||||
get_player_count,
|
||||
@@ -54,20 +53,19 @@ async def update_player_by_id(
|
||||
_: UserPublic = Depends(user_authenticated_for_tournament),
|
||||
__: Tournament = Depends(disallow_archived_tournament),
|
||||
) -> SinglePlayerResponse:
|
||||
values = player_body.model_dump()
|
||||
set_clause = ", ".join(f"{k} = :{k}" for k in values)
|
||||
await database.execute(
|
||||
query=players.update().where(
|
||||
(players.c.id == player_id) & (players.c.tournament_id == tournament_id)
|
||||
),
|
||||
values=player_body.model_dump(),
|
||||
query=f"UPDATE players SET {set_clause} WHERE id = :player_id AND tournament_id = :tournament_id",
|
||||
values={**values, "player_id": player_id, "tournament_id": tournament_id},
|
||||
)
|
||||
return SinglePlayerResponse(
|
||||
data=assert_some(
|
||||
await fetch_one_parsed(
|
||||
database,
|
||||
Player,
|
||||
players.select().where(
|
||||
(players.c.id == player_id) & (players.c.tournament_id == tournament_id)
|
||||
),
|
||||
"SELECT * FROM players WHERE id = :player_id AND tournament_id = :tournament_id",
|
||||
{"player_id": player_id, "tournament_id": tournament_id},
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -36,7 +36,6 @@ from bracket.routes.util import (
|
||||
team_dependency,
|
||||
team_with_players_dependency,
|
||||
)
|
||||
from bracket.schema import players_x_teams, teams
|
||||
from bracket.sql.players import get_all_players_in_tournament, insert_player
|
||||
from bracket.sql.teams import (
|
||||
get_team_by_id,
|
||||
@@ -64,17 +63,21 @@ async def update_team_members(
|
||||
for player_id in player_ids:
|
||||
if player_id not in team.player_ids:
|
||||
await database.execute(
|
||||
query=players_x_teams.insert(),
|
||||
query="INSERT INTO players_x_teams (team_id, player_id) VALUES (:team_id, :player_id)",
|
||||
values={"team_id": team_id, "player_id": player_id},
|
||||
)
|
||||
|
||||
# Remove old members from the team
|
||||
await database.execute(
|
||||
query=players_x_teams.delete().where(
|
||||
(players_x_teams.c.player_id.not_in(player_ids)) # type: ignore[attr-defined]
|
||||
& (players_x_teams.c.team_id == team_id)
|
||||
),
|
||||
)
|
||||
if player_ids:
|
||||
await database.execute(
|
||||
query="DELETE FROM players_x_teams WHERE team_id = :team_id AND player_id != ALL(:player_ids)",
|
||||
values={"team_id": team_id, "player_ids": list(player_ids)},
|
||||
)
|
||||
else:
|
||||
await database.execute(
|
||||
query="DELETE FROM players_x_teams WHERE team_id = :team_id",
|
||||
values={"team_id": team_id},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tournaments/{tournament_id}/teams", response_model=TeamsWithPlayersResponse)
|
||||
@@ -101,11 +104,11 @@ async def update_team_by_id(
|
||||
) -> SingleTeamResponse:
|
||||
await check_foreign_keys_belong_to_tournament(team_body, tournament_id)
|
||||
|
||||
values = team_body.model_dump(exclude={"player_ids"})
|
||||
set_clause = ", ".join(f"{k} = :{k}" for k in values)
|
||||
await database.execute(
|
||||
query=teams.update().where(
|
||||
(teams.c.id == team.id) & (teams.c.tournament_id == tournament_id)
|
||||
),
|
||||
values=team_body.model_dump(exclude={"player_ids"}),
|
||||
query=f"UPDATE teams SET {set_clause} WHERE id = :team_id AND tournament_id = :tournament_id",
|
||||
values={**values, "team_id": team.id, "tournament_id": tournament_id},
|
||||
)
|
||||
await update_team_members(team.id, tournament_id, team_body.player_ids)
|
||||
|
||||
@@ -114,9 +117,8 @@ async def update_team_by_id(
|
||||
await fetch_one_parsed(
|
||||
database,
|
||||
Team,
|
||||
teams.select().where(
|
||||
(teams.c.id == team.id) & (teams.c.tournament_id == tournament_id)
|
||||
),
|
||||
"SELECT * FROM teams WHERE id = :team_id AND tournament_id = :tournament_id",
|
||||
{"team_id": team.id, "tournament_id": tournament_id},
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -154,8 +156,8 @@ async def update_team_logo(
|
||||
logger.error(f"Could not remove logo that should still exist: {old_logo_path}\n{exc}")
|
||||
|
||||
await database.execute(
|
||||
teams.update().where(teams.c.id == team.id),
|
||||
values={"logo_path": filename},
|
||||
"UPDATE teams SET logo_path = :logo_path WHERE id = :team_id",
|
||||
values={"logo_path": filename, "team_id": team.id},
|
||||
)
|
||||
return SingleTeamResponse(data=assert_some(await get_team_by_id(team.id, tournament_id)))
|
||||
|
||||
@@ -191,13 +193,17 @@ async def create_team(
|
||||
existing_teams = await get_teams_with_members(tournament_id)
|
||||
check_requirement(existing_teams, user, "max_teams")
|
||||
|
||||
last_record_id = await database.execute(
|
||||
query=teams.insert(),
|
||||
values=TeamInsertable(
|
||||
**team_to_insert.model_dump(exclude={"player_ids"}),
|
||||
created=datetime_utc.now(),
|
||||
tournament_id=tournament_id,
|
||||
).model_dump(),
|
||||
insertable = TeamInsertable(
|
||||
**team_to_insert.model_dump(exclude={"player_ids"}),
|
||||
created=datetime_utc.now(),
|
||||
tournament_id=tournament_id,
|
||||
)
|
||||
values = insertable.model_dump()
|
||||
columns = ", ".join(values.keys())
|
||||
placeholders = ", ".join(f":{k}" for k in values.keys())
|
||||
last_record_id = await database.fetch_val(
|
||||
query=f"INSERT INTO teams ({columns}) VALUES ({placeholders}) RETURNING id",
|
||||
values=values,
|
||||
)
|
||||
await update_team_members(last_record_id, tournament_id, team_to_insert.player_ids)
|
||||
|
||||
@@ -229,14 +235,18 @@ async def create_multiple_teams(
|
||||
|
||||
async with database.transaction():
|
||||
for team_name, players in teams_and_players:
|
||||
insertable = TeamInsertable(
|
||||
name=team_name,
|
||||
active=team_body.active,
|
||||
created=datetime_utc.now(),
|
||||
tournament_id=tournament_id,
|
||||
)
|
||||
values = insertable.model_dump()
|
||||
columns = ", ".join(values.keys())
|
||||
placeholders = ", ".join(f":{k}" for k in values.keys())
|
||||
await database.execute(
|
||||
query=teams.insert(),
|
||||
values=TeamInsertable(
|
||||
name=team_name,
|
||||
active=team_body.active,
|
||||
created=datetime_utc.now(),
|
||||
tournament_id=tournament_id,
|
||||
).model_dump(),
|
||||
query=f"INSERT INTO teams ({columns}) VALUES ({placeholders})",
|
||||
values=values,
|
||||
)
|
||||
for player in players:
|
||||
player_body = PlayerBody(name=player, active=team_body.active)
|
||||
|
||||
@@ -27,7 +27,6 @@ from bracket.routes.auth import (
|
||||
)
|
||||
from bracket.routes.models import SuccessResponse, TournamentResponse, TournamentsResponse
|
||||
from bracket.routes.util import disallow_archived_tournament
|
||||
from bracket.schema import tournaments
|
||||
from bracket.sql.rankings import (
|
||||
get_all_rankings_in_tournament,
|
||||
sql_create_ranking,
|
||||
@@ -211,7 +210,7 @@ async def upload_logo(
|
||||
logger.error(f"Could not remove logo that should still exist: {old_logo_path}\n{exc}")
|
||||
|
||||
await database.execute(
|
||||
tournaments.update().where(tournaments.c.id == tournament_id),
|
||||
values={"logo_path": filename},
|
||||
"UPDATE tournaments SET logo_path = :logo_path WHERE id = :tournament_id",
|
||||
values={"logo_path": filename, "tournament_id": tournament_id},
|
||||
)
|
||||
return TournamentResponse(data=await sql_get_tournament(tournament_id))
|
||||
|
||||
@@ -7,7 +7,6 @@ from bracket.models.db.round import Round
|
||||
from bracket.models.db.team import FullTeamWithPlayers, Team
|
||||
from bracket.models.db.tournament import Tournament, TournamentStatus
|
||||
from bracket.models.db.util import RoundWithMatches, StageItemWithRounds, StageWithStageItems
|
||||
from bracket.schema import matches, rounds, teams
|
||||
from bracket.sql.rounds import get_round_by_id
|
||||
from bracket.sql.stage_items import get_stage_item
|
||||
from bracket.sql.stages import get_full_tournament_details
|
||||
@@ -21,7 +20,8 @@ async def round_dependency(tournament_id: TournamentId, round_id: RoundId) -> Ro
|
||||
round_ = await fetch_one_parsed(
|
||||
database,
|
||||
Round,
|
||||
rounds.select().where(rounds.c.id == round_id and matches.c.tournament_id == tournament_id),
|
||||
"SELECT * FROM rounds WHERE id = :round_id",
|
||||
{"round_id": round_id},
|
||||
)
|
||||
|
||||
if round_ is None:
|
||||
@@ -63,9 +63,8 @@ async def match_dependency(tournament_id: TournamentId, match_id: MatchId) -> Ma
|
||||
match = await fetch_one_parsed(
|
||||
database,
|
||||
Match,
|
||||
matches.select().where(
|
||||
matches.c.id == match_id and matches.c.tournament_id == tournament_id
|
||||
),
|
||||
"SELECT * FROM matches WHERE id = :match_id",
|
||||
{"match_id": match_id},
|
||||
)
|
||||
|
||||
if match is None:
|
||||
@@ -81,7 +80,8 @@ async def team_dependency(tournament_id: TournamentId, team_id: TeamId) -> Team:
|
||||
team = await fetch_one_parsed(
|
||||
database,
|
||||
Team,
|
||||
teams.select().where(teams.c.id == team_id and teams.c.tournament_id == tournament_id),
|
||||
"SELECT * FROM teams WHERE id = :team_id AND tournament_id = :tournament_id",
|
||||
{"team_id": team_id, "tournament_id": tournament_id},
|
||||
)
|
||||
|
||||
if team is None:
|
||||
|
||||
@@ -26,7 +26,7 @@ async def create_club(club: ClubCreateBody, user_id: UserId) -> Club:
|
||||
if result is None:
|
||||
raise ValueError("Could not create club")
|
||||
|
||||
club_created = Club.model_validate(dict(result._mapping))
|
||||
club_created = Club.model_validate(dict(result))
|
||||
|
||||
await sql_give_user_access_to_club(user_id, club_created.id)
|
||||
|
||||
@@ -41,7 +41,7 @@ async def sql_update_club(club_id: ClubId, club: ClubUpdateBody) -> Club | None:
|
||||
RETURNING *
|
||||
"""
|
||||
result = await database.fetch_one(query=query, values={"name": club.name, "club_id": club_id})
|
||||
return Club.model_validate(result) if result is not None else None
|
||||
return Club.model_validate(dict(result)) if result is not None else None
|
||||
|
||||
|
||||
async def sql_delete_club(club_id: ClubId) -> None:
|
||||
@@ -59,4 +59,4 @@ async def get_clubs_for_user_id(user_id: UserId) -> list[Club]:
|
||||
WHERE uxc.user_id = :user_id
|
||||
"""
|
||||
results = await database.fetch_all(query=query, values={"user_id": user_id})
|
||||
return [Club.model_validate(dict(result._mapping)) for result in results]
|
||||
return [Club.model_validate(dict(result)) for result in results]
|
||||
|
||||
@@ -11,7 +11,7 @@ async def get_all_courts_in_tournament(tournament_id: TournamentId) -> list[Cour
|
||||
ORDER BY name
|
||||
"""
|
||||
result = await database.fetch_all(query=query, values={"tournament_id": tournament_id})
|
||||
return [Court.model_validate(dict(x._mapping)) for x in result]
|
||||
return [Court.model_validate(dict(x)) for x in result]
|
||||
|
||||
|
||||
async def update_court(
|
||||
@@ -27,7 +27,7 @@ async def update_court(
|
||||
query=query,
|
||||
values={"tournament_id": tournament_id, "court_id": court_id, "name": court_body.name},
|
||||
)
|
||||
return [Court.model_validate(dict(x._mapping)) for x in result]
|
||||
return [Court.model_validate(dict(x)) for x in result]
|
||||
|
||||
|
||||
async def sql_delete_court(tournament_id: TournamentId, court_id: CourtId) -> None:
|
||||
|
||||
@@ -79,7 +79,7 @@ async def sql_create_match(match: MatchCreateBody) -> Match:
|
||||
if result is None:
|
||||
raise ValueError("Could not create stage")
|
||||
|
||||
return Match.model_validate(dict(result._mapping))
|
||||
return Match.model_validate(dict(result))
|
||||
|
||||
|
||||
async def sql_update_match(match_id: MatchId, match: MatchBody, tournament: Tournament) -> None:
|
||||
@@ -223,7 +223,7 @@ async def sql_get_match(match_id: MatchId) -> Match:
|
||||
if result is None:
|
||||
raise ValueError("Could not create stage")
|
||||
|
||||
return Match.model_validate(dict(result._mapping))
|
||||
return Match.model_validate(dict(result))
|
||||
|
||||
|
||||
async def clear_scores_for_matches_in_stage_item(
|
||||
|
||||
@@ -5,7 +5,6 @@ from heliclockter import datetime_utc
|
||||
from bracket.database import database
|
||||
from bracket.logic.ranking.statistics import START_ELO
|
||||
from bracket.models.db.player import Player, PlayerBody, PlayerToInsert
|
||||
from bracket.schema import players
|
||||
from bracket.utils.id_types import PlayerId, TournamentId
|
||||
from bracket.utils.pagination import PaginationPlayers
|
||||
from bracket.utils.types import dict_without_none
|
||||
@@ -45,7 +44,7 @@ async def get_all_players_in_tournament(
|
||||
),
|
||||
)
|
||||
|
||||
return [Player.model_validate(x) for x in result]
|
||||
return [Player.model_validate(dict(x)) for x in result]
|
||||
|
||||
|
||||
async def get_player_by_id(player_id: PlayerId, tournament_id: TournamentId) -> Player | None:
|
||||
@@ -58,7 +57,7 @@ async def get_player_by_id(player_id: PlayerId, tournament_id: TournamentId) ->
|
||||
result = await database.fetch_one(
|
||||
query=query, values={"player_id": player_id, "tournament_id": tournament_id}
|
||||
)
|
||||
return Player.model_validate(result) if result is not None else None
|
||||
return Player.model_validate(dict(result)) if result is not None else None
|
||||
|
||||
|
||||
async def get_player_count(
|
||||
@@ -89,13 +88,16 @@ async def sql_delete_players_of_tournament(tournament_id: TournamentId) -> None:
|
||||
|
||||
|
||||
async def insert_player(player_body: PlayerBody, tournament_id: TournamentId) -> None:
|
||||
values = PlayerToInsert(
|
||||
**player_body.model_dump(),
|
||||
created=datetime_utc.now(),
|
||||
tournament_id=tournament_id,
|
||||
elo_score=START_ELO,
|
||||
swiss_score=Decimal("0.0"),
|
||||
).model_dump()
|
||||
columns = ", ".join(values.keys())
|
||||
placeholders = ", ".join(f":{k}" for k in values.keys())
|
||||
await database.execute(
|
||||
query=players.insert(),
|
||||
values=PlayerToInsert(
|
||||
**player_body.model_dump(),
|
||||
created=datetime_utc.now(),
|
||||
tournament_id=tournament_id,
|
||||
elo_score=START_ELO,
|
||||
swiss_score=Decimal("0.0"),
|
||||
).model_dump(),
|
||||
query=f"INSERT INTO players ({columns}) VALUES ({placeholders})",
|
||||
values=values,
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ async def get_all_rankings_in_tournament(tournament_id: TournamentId) -> list[Ra
|
||||
ORDER BY position
|
||||
"""
|
||||
result = await database.fetch_all(query=query, values={"tournament_id": tournament_id})
|
||||
return [Ranking.model_validate(dict(x._mapping)) for x in result]
|
||||
return [Ranking.model_validate(dict(x)) for x in result]
|
||||
|
||||
|
||||
async def get_default_rankings_in_tournament(tournament_id: TournamentId) -> Ranking:
|
||||
@@ -24,7 +24,7 @@ async def get_default_rankings_in_tournament(tournament_id: TournamentId) -> Ran
|
||||
"""
|
||||
result = await database.fetch_one(query=query, values={"tournament_id": tournament_id})
|
||||
assert result is not None, "No default ranking found"
|
||||
return Ranking.model_validate(dict(result._mapping))
|
||||
return Ranking.model_validate(dict(result))
|
||||
|
||||
|
||||
async def get_ranking_for_stage_item(
|
||||
@@ -40,7 +40,7 @@ async def get_ranking_for_stage_item(
|
||||
result = await database.fetch_one(
|
||||
query=query, values={"tournament_id": tournament_id, "stage_item_id": stage_item_id}
|
||||
)
|
||||
return Ranking.model_validate(dict(result._mapping)) if result else None
|
||||
return Ranking.model_validate(dict(result)) if result else None
|
||||
|
||||
|
||||
async def sql_update_ranking(
|
||||
@@ -68,7 +68,7 @@ async def sql_update_ranking(
|
||||
"position": ranking_body.position,
|
||||
},
|
||||
)
|
||||
return [Ranking.model_validate(dict(x._mapping)) for x in result]
|
||||
return [Ranking.model_validate(dict(x)) for x in result]
|
||||
|
||||
|
||||
async def sql_delete_ranking(tournament_id: TournamentId, ranking_id: RankingId) -> None:
|
||||
|
||||
@@ -30,11 +30,11 @@ async def get_stage_item_input_by_id(
|
||||
return None
|
||||
|
||||
if result["team_id"] is not None:
|
||||
data = dict(result._mapping)
|
||||
data = dict(result)
|
||||
data["team"] = await get_team_by_id(data["team_id"], tournament_id)
|
||||
return StageItemInputFinal.model_validate(data)
|
||||
|
||||
return TypeAdapter(StageItemInput).validate_python(result)
|
||||
return TypeAdapter(StageItemInput).validate_python(dict(result))
|
||||
|
||||
|
||||
async def get_stage_item_input_ids_by_ranking_id(ranking_id: RankingId) -> list[StageItemId]:
|
||||
@@ -131,4 +131,4 @@ async def sql_create_stage_item_input(
|
||||
if result is None:
|
||||
raise ValueError("Could not create stage")
|
||||
|
||||
return StageItemInputBase.model_validate(dict(result._mapping))
|
||||
return StageItemInputBase.model_validate(dict(result))
|
||||
|
||||
@@ -34,7 +34,7 @@ async def sql_create_stage_item(
|
||||
if result is None:
|
||||
raise ValueError("Could not create stage")
|
||||
|
||||
return StageItem.model_validate(dict(result._mapping))
|
||||
return StageItem.model_validate(dict(result))
|
||||
|
||||
|
||||
async def sql_create_stage_item_with_inputs(
|
||||
|
||||
@@ -111,7 +111,7 @@ async def get_full_tournament_details(
|
||||
}
|
||||
)
|
||||
result = await database.fetch_all(query=query, values=values)
|
||||
return [StageWithStageItems.model_validate(dict(x._mapping)) for x in result]
|
||||
return [StageWithStageItems.model_validate(dict(x)) for x in result]
|
||||
|
||||
|
||||
async def sql_delete_stage(tournament_id: TournamentId, stage_id: StageId) -> None:
|
||||
@@ -146,7 +146,7 @@ async def sql_create_stage(tournament_id: TournamentId) -> Stage:
|
||||
if result is None:
|
||||
raise ValueError("Could not create stage")
|
||||
|
||||
return Stage.model_validate(dict(result._mapping))
|
||||
return Stage.model_validate(dict(result))
|
||||
|
||||
|
||||
async def get_next_stage_in_tournament(
|
||||
|
||||
@@ -21,7 +21,7 @@ async def get_teams_by_id(team_ids: set[TeamId], tournament_id: TournamentId) ->
|
||||
result = await database.fetch_all(
|
||||
query=query, values={"team_ids": team_ids, "tournament_id": tournament_id}
|
||||
)
|
||||
return [Team.model_validate(team) for team in result]
|
||||
return [Team.model_validate(dict(team)) for team in result]
|
||||
|
||||
|
||||
async def get_team_by_id(team_id: TeamId, tournament_id: TournamentId) -> Team | None:
|
||||
@@ -71,7 +71,7 @@ async def get_teams_with_members(
|
||||
}
|
||||
)
|
||||
result = await database.fetch_all(query=query, values=values)
|
||||
return [FullTeamWithPlayers.model_validate(x) for x in result]
|
||||
return [FullTeamWithPlayers.model_validate(dict(x)) for x in result]
|
||||
|
||||
|
||||
async def get_team_count(
|
||||
|
||||
@@ -18,7 +18,7 @@ async def sql_get_tournament(tournament_id: TournamentId) -> Tournament:
|
||||
"""
|
||||
result = await database.fetch_one(query=query, values={"tournament_id": tournament_id})
|
||||
assert result is not None
|
||||
return Tournament.model_validate(result)
|
||||
return Tournament.model_validate(dict(result))
|
||||
|
||||
|
||||
async def sql_get_tournament_by_endpoint_name(endpoint_name: str) -> Tournament | None:
|
||||
@@ -29,7 +29,7 @@ async def sql_get_tournament_by_endpoint_name(endpoint_name: str) -> Tournament
|
||||
AND dashboard_public IS TRUE
|
||||
"""
|
||||
result = await database.fetch_one(query=query, values={"endpoint_name": endpoint_name})
|
||||
return Tournament.model_validate(result) if result is not None else None
|
||||
return Tournament.model_validate(dict(result)) if result is not None else None
|
||||
|
||||
|
||||
async def sql_get_tournaments(
|
||||
@@ -55,7 +55,7 @@ async def sql_get_tournaments(
|
||||
query += "AND status = 'ARCHIVED'"
|
||||
|
||||
result = await database.fetch_all(query=query, values=params)
|
||||
return [Tournament.model_validate(x) for x in result]
|
||||
return [Tournament.model_validate(dict(x)) for x in result]
|
||||
|
||||
|
||||
async def sql_delete_tournament(tournament_id: TournamentId) -> None:
|
||||
|
||||
@@ -2,7 +2,6 @@ from bracket.database import database
|
||||
from bracket.logic.tournaments import sql_delete_tournament_completely
|
||||
from bracket.models.db.account import UserAccountType
|
||||
from bracket.models.db.user import User, UserInDB, UserInsertable, UserPublic, UserToUpdate
|
||||
from bracket.schema import users
|
||||
from bracket.sql.clubs import get_clubs_for_user_id, sql_delete_club
|
||||
from bracket.sql.tournaments import sql_get_tournaments
|
||||
from bracket.utils.db import fetch_one_parsed
|
||||
@@ -73,7 +72,7 @@ async def get_user_by_id(user_id: UserId) -> UserPublic | None:
|
||||
WHERE id = :user_id
|
||||
"""
|
||||
result = await database.fetch_one(query=query, values={"user_id": user_id})
|
||||
return UserPublic.model_validate(dict(result._mapping)) if result is not None else None
|
||||
return UserPublic.model_validate(dict(result)) if result is not None else None
|
||||
|
||||
|
||||
async def get_expired_demo_users() -> list[UserPublic]:
|
||||
@@ -84,7 +83,7 @@ async def get_expired_demo_users() -> list[UserPublic]:
|
||||
AND created <= NOW() - INTERVAL '30 minutes'
|
||||
"""
|
||||
result = await database.fetch_all(query=query)
|
||||
return [UserPublic.model_validate(demo_user) for demo_user in result]
|
||||
return [UserPublic.model_validate(dict(demo_user)) for demo_user in result]
|
||||
|
||||
|
||||
async def create_user(user: UserInsertable) -> User:
|
||||
@@ -103,7 +102,7 @@ async def create_user(user: UserInsertable) -> User:
|
||||
"account_type": user.account_type.value,
|
||||
},
|
||||
)
|
||||
return User.model_validate(dict(assert_some(result)._mapping))
|
||||
return User.model_validate(dict(assert_some(result)))
|
||||
|
||||
|
||||
async def delete_user(user_id: UserId) -> None:
|
||||
@@ -125,7 +124,11 @@ async def check_whether_email_is_in_use(email: str) -> bool:
|
||||
|
||||
|
||||
async def get_user(email: str) -> UserInDB | None:
|
||||
return await fetch_one_parsed(database, UserInDB, users.select().where(users.c.email == email))
|
||||
return await fetch_one_parsed(
|
||||
database, UserInDB,
|
||||
"SELECT * FROM users WHERE email = :email",
|
||||
{"email": email},
|
||||
)
|
||||
|
||||
|
||||
async def delete_user_and_owned_clubs(user_id: UserId) -> None:
|
||||
|
||||
@@ -1,49 +1,46 @@
|
||||
from databases import Database
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import Table
|
||||
from sqlalchemy.sql import Select
|
||||
|
||||
from bracket.config import Environment, environment
|
||||
from bracket.database import DatabasePool
|
||||
from bracket.utils.conversion import to_string_mapping
|
||||
from bracket.utils.logging import logger
|
||||
from bracket.utils.types import assert_some
|
||||
|
||||
|
||||
async def fetch_one_parsed[BaseModelT: BaseModel](
|
||||
database: Database, model: type[BaseModelT], query: Select
|
||||
database: DatabasePool, model: type[BaseModelT], query: str, values: dict | None = None
|
||||
) -> BaseModelT | None:
|
||||
record = await database.fetch_one(query)
|
||||
return model.model_validate(dict(record._mapping)) if record is not None else None
|
||||
record = await database.fetch_one(query, values)
|
||||
return model.model_validate(dict(record)) if record is not None else None
|
||||
|
||||
|
||||
async def fetch_one_parsed_certain[BaseModelT: BaseModel](
|
||||
database: Database, model: type[BaseModelT], query: Select
|
||||
database: DatabasePool, model: type[BaseModelT], query: str, values: dict | None = None
|
||||
) -> BaseModelT:
|
||||
return assert_some(await fetch_one_parsed(database, model, query))
|
||||
return assert_some(await fetch_one_parsed(database, model, query, values))
|
||||
|
||||
|
||||
async def fetch_all_parsed[BaseModelT: BaseModel](
|
||||
database: Database, model: type[BaseModelT], query: Select
|
||||
database: DatabasePool, model: type[BaseModelT], query: str, values: dict | None = None
|
||||
) -> list[BaseModelT]:
|
||||
records = await database.fetch_all(query)
|
||||
return [model.model_validate(dict(record._mapping)) for record in records]
|
||||
records = await database.fetch_all(query, values)
|
||||
return [model.model_validate(dict(record)) for record in records]
|
||||
|
||||
|
||||
async def insert_generic[BaseModelT: BaseModel](
|
||||
database: Database, data_model: BaseModelT, table: Table, return_type: type[BaseModelT]
|
||||
database: DatabasePool, data_model: BaseModelT, table_name: str, return_type: type[BaseModelT]
|
||||
) -> tuple[int, BaseModelT]:
|
||||
assert environment is not Environment.PRODUCTION, "Below code can allow SQL injection"
|
||||
try:
|
||||
mapping = to_string_mapping(data_model)
|
||||
values = ", ".join([f"'{x}'" for x in mapping.values()])
|
||||
query = (
|
||||
f"INSERT INTO {table.name} ({', '.join(mapping.keys())}) VALUES ({values}) RETURNING *"
|
||||
f"INSERT INTO {table_name} ({', '.join(mapping.keys())}) VALUES ({values}) RETURNING *"
|
||||
)
|
||||
last_record_id: int = await database.execute(query)
|
||||
row_inserted = await fetch_one_parsed(
|
||||
database, return_type, table.select().where(table.c.id == last_record_id)
|
||||
)
|
||||
assert isinstance(row_inserted, return_type), f"Unexpected type: {row_inserted}"
|
||||
result = await database.fetch_one(query)
|
||||
assert result is not None, f"Could not insert {type(data_model).__name__}"
|
||||
last_record_id: int = result["id"]
|
||||
row_inserted = return_type.model_validate(dict(result))
|
||||
return last_record_id, row_inserted
|
||||
except Exception:
|
||||
logger.exception(f"Could not insert {type(data_model).__name__}")
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import random
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
from heliclockter import datetime_utc
|
||||
from pydantic import BaseModel
|
||||
|
||||
from bracket.config import Environment, config, environment
|
||||
from bracket.database import database, engine
|
||||
from bracket.database import database
|
||||
from bracket.logic.ranking.calculation import (
|
||||
recalculate_ranking_for_stage_item,
|
||||
)
|
||||
@@ -31,22 +31,7 @@ from bracket.models.db.team import TeamInsertable
|
||||
from bracket.models.db.tournament import TournamentInsertable
|
||||
from bracket.models.db.user import UserInsertable
|
||||
from bracket.models.db.user_x_club import UserXClubInsertable, UserXClubRelation
|
||||
from bracket.schema import (
|
||||
clubs,
|
||||
courts,
|
||||
matches,
|
||||
metadata,
|
||||
players,
|
||||
players_x_teams,
|
||||
rankings,
|
||||
rounds,
|
||||
stage_items,
|
||||
stages,
|
||||
teams,
|
||||
tournaments,
|
||||
users,
|
||||
users_x_clubs,
|
||||
)
|
||||
from bracket.schema import metadata
|
||||
from bracket.sql.matches import sql_update_match
|
||||
from bracket.sql.stage_items import get_stage_item, sql_create_stage_item_with_inputs
|
||||
from bracket.sql.stages import get_full_tournament_details
|
||||
@@ -92,9 +77,6 @@ from bracket.utils.logging import logger
|
||||
from bracket.utils.security import hash_password
|
||||
from bracket.utils.types import assert_some
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy import Table
|
||||
|
||||
|
||||
async def create_admin_user() -> UserId:
|
||||
assert config.admin_email
|
||||
@@ -121,7 +103,7 @@ async def init_db_when_empty() -> UserId | None:
|
||||
environment is Environment.DEVELOPMENT and await get_user(config.admin_email) is None
|
||||
):
|
||||
logger.warning("Empty db detected, creating tables...")
|
||||
metadata.create_all(engine)
|
||||
_create_all_tables()
|
||||
alembic_stamp_head()
|
||||
|
||||
logger.warning("Empty db detected, creating admin user...")
|
||||
@@ -130,6 +112,22 @@ async def init_db_when_empty() -> UserId | None:
|
||||
return None
|
||||
|
||||
|
||||
def _create_all_tables() -> None:
|
||||
import sqlalchemy
|
||||
|
||||
engine = sqlalchemy.create_engine(str(config.pg_dsn))
|
||||
metadata.create_all(engine)
|
||||
engine.dispose()
|
||||
|
||||
|
||||
def _drop_all_tables() -> None:
|
||||
import sqlalchemy
|
||||
|
||||
engine = sqlalchemy.create_engine(str(config.pg_dsn))
|
||||
metadata.drop_all(engine)
|
||||
engine.dispose()
|
||||
|
||||
|
||||
async def sql_create_dev_db() -> UserId:
|
||||
# TODO: refactor into smaller functions
|
||||
# pylint: disable=too-many-statements
|
||||
@@ -137,25 +135,25 @@ async def sql_create_dev_db() -> UserId:
|
||||
|
||||
logger.warning("Initializing database with dummy records")
|
||||
await database.connect()
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
_drop_all_tables()
|
||||
_create_all_tables()
|
||||
real_user_id = await init_db_when_empty()
|
||||
alembic_stamp_head()
|
||||
|
||||
table_lookup: dict[type, Table] = {
|
||||
UserInsertable: users,
|
||||
ClubInsertable: clubs,
|
||||
StageInsertable: stages,
|
||||
TeamInsertable: teams,
|
||||
UserXClubInsertable: users_x_clubs,
|
||||
PlayerXTeamInsertable: players_x_teams,
|
||||
PlayerInsertable: players,
|
||||
RoundInsertable: rounds,
|
||||
Match: matches,
|
||||
TournamentInsertable: tournaments,
|
||||
CourtInsertable: courts,
|
||||
StageItemInsertable: stage_items,
|
||||
RankingInsertable: rankings,
|
||||
table_lookup: dict[type, str] = {
|
||||
UserInsertable: "users",
|
||||
ClubInsertable: "clubs",
|
||||
StageInsertable: "stages",
|
||||
TeamInsertable: "teams",
|
||||
UserXClubInsertable: "users_x_clubs",
|
||||
PlayerXTeamInsertable: "players_x_teams",
|
||||
PlayerInsertable: "players",
|
||||
RoundInsertable: "rounds",
|
||||
Match: "matches",
|
||||
TournamentInsertable: "tournaments",
|
||||
CourtInsertable: "courts",
|
||||
StageItemInsertable: "stage_items",
|
||||
RankingInsertable: "rankings",
|
||||
}
|
||||
|
||||
async def insert_dummy[BaseModelT: BaseModel](
|
||||
|
||||
@@ -10,7 +10,6 @@ dependencies = [
|
||||
"asyncpg==0.31.0",
|
||||
"bcrypt==5.0.0",
|
||||
"click==8.4.0",
|
||||
"databases[asyncpg]==0.9.0",
|
||||
"fastapi==0.138.0",
|
||||
"fastapi-sso==0.21.0",
|
||||
"gunicorn==26.0.0",
|
||||
@@ -24,7 +23,6 @@ dependencies = [
|
||||
"python-multipart==0.0.31",
|
||||
"sentry-sdk==2.63.0",
|
||||
"sqlalchemy==2.0.44",
|
||||
"sqlalchemy-stubs==0.4",
|
||||
"starlette==1.3.1",
|
||||
"types-aiofiles==25.1.0.20251011",
|
||||
"types-passlib==1.7.7.20241221",
|
||||
@@ -40,7 +38,6 @@ junit_family = 'xunit2'
|
||||
asyncio_mode = 'auto'
|
||||
filterwarnings = [
|
||||
'error',
|
||||
'ignore:The SelectBase.c and SelectBase.columns attributes are deprecated.*:DeprecationWarning',
|
||||
'ignore:pkg_resources is deprecated as an API.*:DeprecationWarning',
|
||||
'ignore:Deprecated call to `pkg_resources.declare_namespace(.*)`.*:DeprecationWarning',
|
||||
'ignore:.*:pytest.PytestDeprecationWarning',
|
||||
|
||||
@@ -2,7 +2,6 @@ import pytest
|
||||
|
||||
from bracket.database import database
|
||||
from bracket.models.db.court import Court
|
||||
from bracket.schema import courts
|
||||
from bracket.utils.db import fetch_one_parsed_certain
|
||||
from bracket.utils.dummy_records import DUMMY_COURT1, DUMMY_MOCK_TIME, DUMMY_TEAM1
|
||||
from bracket.utils.http import HTTPMethod
|
||||
@@ -40,7 +39,7 @@ async def test_create_court(
|
||||
body = {"name": "Some new name", "active": True}
|
||||
response = await send_tournament_request(HTTPMethod.POST, "courts", auth_context, json=body)
|
||||
assert response["data"]["name"] == body["name"]
|
||||
await assert_row_count_and_clear(courts, 1)
|
||||
await assert_row_count_and_clear("courts", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -59,7 +58,7 @@ async def test_delete_court(
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(courts, 0)
|
||||
await assert_row_count_and_clear("courts", 0)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -77,9 +76,9 @@ async def test_update_court(
|
||||
HTTPMethod.PUT, f"courts/{court_inserted.id}", auth_context, json=body
|
||||
)
|
||||
updated_court = await fetch_one_parsed_certain(
|
||||
database, Court, query=courts.select().where(courts.c.id == court_inserted.id)
|
||||
database, Court, "SELECT * FROM courts WHERE id = :id", {"id": court_inserted.id}
|
||||
)
|
||||
assert updated_court.name == body["name"]
|
||||
assert response["data"]["name"] == body["name"]
|
||||
|
||||
await assert_row_count_and_clear(courts, 1)
|
||||
await assert_row_count_and_clear("courts", 1)
|
||||
|
||||
@@ -8,7 +8,6 @@ from bracket.models.db.stage_item import StageType
|
||||
from bracket.models.db.stage_item_inputs import (
|
||||
StageItemInputInsertable,
|
||||
)
|
||||
from bracket.schema import matches
|
||||
from bracket.utils.db import fetch_one_parsed_certain
|
||||
from bracket.utils.dummy_records import (
|
||||
DUMMY_COURT1,
|
||||
@@ -82,7 +81,7 @@ async def test_create_match(
|
||||
)
|
||||
assert response["data"]["id"], response
|
||||
|
||||
await assert_row_count_and_clear(matches, 1)
|
||||
await assert_row_count_and_clear("matches", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -149,7 +148,7 @@ async def test_delete_match(
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(matches, 0)
|
||||
await assert_row_count_and_clear("matches", 0)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -223,13 +222,14 @@ async def test_update_match(
|
||||
updated_match = await fetch_one_parsed_certain(
|
||||
database,
|
||||
Match,
|
||||
query=matches.select().where(matches.c.id == match_inserted.id),
|
||||
"SELECT * FROM matches WHERE id = :id",
|
||||
{"id": match_inserted.id},
|
||||
)
|
||||
assert updated_match.stage_item_input1_score == body["stage_item_input1_score"]
|
||||
assert updated_match.stage_item_input2_score == body["stage_item_input2_score"]
|
||||
assert updated_match.court_id == body["court_id"]
|
||||
|
||||
await assert_row_count_and_clear(matches, 1)
|
||||
await assert_row_count_and_clear("matches", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -304,12 +304,13 @@ async def test_update_endpoint_custom_duration_margin(
|
||||
updated_match = await fetch_one_parsed_certain(
|
||||
database,
|
||||
Match,
|
||||
query=matches.select().where(matches.c.id == match_inserted.id),
|
||||
"SELECT * FROM matches WHERE id = :id",
|
||||
{"id": match_inserted.id},
|
||||
)
|
||||
assert updated_match.custom_duration_minutes == body["custom_duration_minutes"]
|
||||
assert updated_match.custom_margin_minutes == body["custom_margin_minutes"]
|
||||
|
||||
await assert_row_count_and_clear(matches, 1)
|
||||
await assert_row_count_and_clear("matches", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
|
||||
@@ -2,7 +2,6 @@ import pytest
|
||||
|
||||
from bracket.database import database
|
||||
from bracket.models.db.player import Player
|
||||
from bracket.schema import players
|
||||
from bracket.utils.db import fetch_one_parsed_certain
|
||||
from bracket.utils.dummy_records import DUMMY_MOCK_TIME, DUMMY_PLAYER1, DUMMY_TEAM1
|
||||
from bracket.utils.http import HTTPMethod
|
||||
@@ -49,7 +48,7 @@ async def test_create_player(
|
||||
body = {"name": "Some new name", "active": True}
|
||||
response = await send_tournament_request(HTTPMethod.POST, "players", auth_context, json=body)
|
||||
assert response["success"] is True
|
||||
await assert_row_count_and_clear(players, 1)
|
||||
await assert_row_count_and_clear("players", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -61,7 +60,7 @@ async def test_create_players(
|
||||
HTTPMethod.POST, "players_multi", auth_context, json=body
|
||||
)
|
||||
assert response["success"] is True
|
||||
await assert_row_count_and_clear(players, 2)
|
||||
await assert_row_count_and_clear("players", 2)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -80,7 +79,7 @@ async def test_delete_player(
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(players, 0)
|
||||
await assert_row_count_and_clear("players", 0)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -98,9 +97,9 @@ async def test_update_player(
|
||||
HTTPMethod.PUT, f"players/{player_inserted.id}", auth_context, json=body
|
||||
)
|
||||
updated_player = await fetch_one_parsed_certain(
|
||||
database, Player, query=players.select().where(players.c.id == player_inserted.id)
|
||||
database, Player, "SELECT * FROM players WHERE id = :id", {"id": player_inserted.id}
|
||||
)
|
||||
assert updated_player.name == body["name"]
|
||||
assert response["data"]["name"] == body["name"]
|
||||
|
||||
await assert_row_count_and_clear(players, 1)
|
||||
await assert_row_count_and_clear("players", 1)
|
||||
|
||||
@@ -5,7 +5,6 @@ import pytest
|
||||
|
||||
from bracket.database import database
|
||||
from bracket.models.db.ranking import Ranking
|
||||
from bracket.schema import rankings
|
||||
from bracket.sql.rankings import (
|
||||
get_all_rankings_in_tournament,
|
||||
sql_delete_ranking,
|
||||
@@ -95,7 +94,8 @@ async def test_update_ranking(
|
||||
updated_ranking = await fetch_one_parsed_certain(
|
||||
database,
|
||||
Ranking,
|
||||
query=rankings.select().where(rankings.c.id == ranking_inserted.id),
|
||||
"SELECT * FROM rankings WHERE id = :id",
|
||||
{"id": ranking_inserted.id},
|
||||
)
|
||||
assert response["success"] is True
|
||||
assert updated_ranking.win_points == Decimal("7.5")
|
||||
|
||||
@@ -2,7 +2,6 @@ import pytest
|
||||
|
||||
from bracket.models.db.match import MatchRescheduleBody
|
||||
from bracket.models.db.stage_item_inputs import StageItemInputInsertable
|
||||
from bracket.schema import matches
|
||||
from bracket.sql.matches import sql_get_match
|
||||
from bracket.utils.dummy_records import (
|
||||
DUMMY_COURT1,
|
||||
@@ -100,7 +99,7 @@ async def test_reschedule_match(
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
match = await sql_get_match(match_inserted.id)
|
||||
await assert_row_count_and_clear(matches, 0)
|
||||
await assert_row_count_and_clear("matches", 0)
|
||||
|
||||
assert match.court_id == body.new_court_id
|
||||
assert match.position_in_schedule == 0
|
||||
|
||||
@@ -3,7 +3,6 @@ import pytest
|
||||
from bracket.database import database
|
||||
from bracket.models.db.round import Round
|
||||
from bracket.models.db.stage_item import StageType
|
||||
from bracket.schema import rounds
|
||||
from bracket.utils.db import fetch_one_parsed_certain
|
||||
from bracket.utils.dummy_records import DUMMY_ROUND1, DUMMY_STAGE1, DUMMY_STAGE_ITEM1, DUMMY_TEAM1
|
||||
from bracket.utils.http import HTTPMethod
|
||||
@@ -46,7 +45,7 @@ async def test_create_round(
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(rounds, 1)
|
||||
await assert_row_count_and_clear("rounds", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -73,7 +72,7 @@ async def test_delete_round(
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(rounds, 0)
|
||||
await assert_row_count_and_clear("rounds", 0)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -102,9 +101,9 @@ async def test_update_round(
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
updated_round = await fetch_one_parsed_certain(
|
||||
database, Round, query=rounds.select().where(rounds.c.id == round_inserted.id)
|
||||
database, Round, "SELECT * FROM rounds WHERE id = :id", {"id": round_inserted.id}
|
||||
)
|
||||
assert updated_round.name == body["name"]
|
||||
assert updated_round.is_draft == body["is_draft"]
|
||||
|
||||
await assert_row_count_and_clear(rounds, 1)
|
||||
await assert_row_count_and_clear("rounds", 1)
|
||||
|
||||
@@ -2,7 +2,6 @@ import pytest
|
||||
|
||||
from bracket.models.db.stage_item import StageType
|
||||
from bracket.models.db.stage_item_inputs import StageItemInputCreateBodyFinal
|
||||
from bracket.schema import matches, rounds, stage_items, stages
|
||||
from bracket.sql.stage_items import get_stage_item
|
||||
from bracket.utils.dummy_records import (
|
||||
DUMMY_STAGE1,
|
||||
@@ -58,10 +57,10 @@ async def test_create_stage_item(
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(matches, 1)
|
||||
await assert_row_count_and_clear(rounds, 1)
|
||||
await assert_row_count_and_clear(stage_items, 1)
|
||||
await assert_row_count_and_clear(stages, 1)
|
||||
await assert_row_count_and_clear("matches", 1)
|
||||
await assert_row_count_and_clear("rounds", 1)
|
||||
await assert_row_count_and_clear("stage_items", 1)
|
||||
await assert_row_count_and_clear("stages", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -85,7 +84,7 @@ async def test_delete_stage_item(
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(stages, 0)
|
||||
await assert_row_count_and_clear("stages", 0)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import pytest
|
||||
|
||||
from bracket.schema import rounds, stage_items, stages
|
||||
from bracket.sql.stages import get_full_tournament_details
|
||||
from bracket.utils.dummy_records import (
|
||||
DUMMY_MOCK_TIME,
|
||||
@@ -103,9 +102,9 @@ async def test_create_stage(
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(rounds, 1)
|
||||
await assert_row_count_and_clear(stage_items, 1)
|
||||
await assert_row_count_and_clear(stages, 1)
|
||||
await assert_row_count_and_clear("rounds", 1)
|
||||
await assert_row_count_and_clear("stage_items", 1)
|
||||
await assert_row_count_and_clear("stages", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -124,7 +123,7 @@ async def test_delete_stage(
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(stages, 0)
|
||||
await assert_row_count_and_clear("stages", 0)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -153,8 +152,8 @@ async def test_update_stage(
|
||||
assert len(updated_stage.stage_items) == 1
|
||||
assert updated_stage.name == body["name"]
|
||||
|
||||
await assert_row_count_and_clear(stage_items, 1)
|
||||
await assert_row_count_and_clear(stages, 1)
|
||||
await assert_row_count_and_clear("stage_items", 1)
|
||||
await assert_row_count_and_clear("stages", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -177,8 +176,8 @@ async def test_activate_stage(
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
|
||||
await assert_row_count_and_clear(stage_items, 1)
|
||||
await assert_row_count_and_clear(stages, 1)
|
||||
await assert_row_count_and_clear("stage_items", 1)
|
||||
await assert_row_count_and_clear("stages", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
|
||||
@@ -4,7 +4,6 @@ import pytest
|
||||
|
||||
from bracket.database import database
|
||||
from bracket.models.db.team import Team
|
||||
from bracket.schema import players, teams
|
||||
from bracket.utils.db import fetch_one_parsed_certain
|
||||
from bracket.utils.dummy_records import DUMMY_MOCK_TIME, DUMMY_TEAM1
|
||||
from bracket.utils.http import HTTPMethod
|
||||
@@ -50,7 +49,7 @@ async def test_create_team(
|
||||
body = {"name": "Some new name", "active": True, "player_ids": []}
|
||||
response = await send_tournament_request(HTTPMethod.POST, "teams", auth_context, None, body)
|
||||
assert response["data"]["name"] == body["name"]
|
||||
await assert_row_count_and_clear(teams, 1)
|
||||
await assert_row_count_and_clear("teams", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -62,8 +61,8 @@ async def test_create_teams(
|
||||
HTTPMethod.POST, "teams_multi", auth_context, None, body
|
||||
)
|
||||
assert response["success"] is True
|
||||
await assert_row_count_and_clear(teams, 2)
|
||||
await assert_row_count_and_clear(players, 3)
|
||||
await assert_row_count_and_clear("teams", 2)
|
||||
await assert_row_count_and_clear("players", 3)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -79,7 +78,7 @@ async def test_delete_team(
|
||||
)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
await assert_row_count_and_clear(teams, 0)
|
||||
await assert_row_count_and_clear("teams", 0)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
@@ -94,12 +93,12 @@ async def test_update_team(
|
||||
HTTPMethod.PUT, f"teams/{team_inserted.id}", auth_context, None, body
|
||||
)
|
||||
updated_team = await fetch_one_parsed_certain(
|
||||
database, Team, query=teams.select().where(teams.c.id == team_inserted.id)
|
||||
database, Team, "SELECT * FROM teams WHERE id = :id", {"id": team_inserted.id}
|
||||
)
|
||||
assert updated_team.name == body["name"]
|
||||
assert response["data"]["name"] == body["name"]
|
||||
|
||||
await assert_row_count_and_clear(teams, 1)
|
||||
await assert_row_count_and_clear("teams", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio(loop_scope="session")
|
||||
|
||||
@@ -6,7 +6,6 @@ import pytest
|
||||
from bracket.database import database
|
||||
from bracket.logic.tournaments import sql_delete_tournament_completely
|
||||
from bracket.models.db.tournament import Tournament, TournamentStatus
|
||||
from bracket.schema import tournaments
|
||||
from bracket.sql.tournaments import sql_delete_tournament, sql_get_tournament_by_endpoint_name
|
||||
from bracket.utils.db import fetch_one_parsed_certain
|
||||
from bracket.utils.dummy_records import DUMMY_MOCK_TIME, DUMMY_TOURNAMENT
|
||||
@@ -138,7 +137,8 @@ async def test_update_tournament(
|
||||
updated_tournament = await fetch_one_parsed_certain(
|
||||
database,
|
||||
Tournament,
|
||||
query=tournaments.select().where(tournaments.c.id == auth_context.tournament.id),
|
||||
"SELECT * FROM tournaments WHERE id = :id",
|
||||
{"id": auth_context.tournament.id},
|
||||
)
|
||||
assert updated_tournament.name == body["name"]
|
||||
assert updated_tournament.dashboard_public == body["dashboard_public"]
|
||||
@@ -148,13 +148,14 @@ async def test_update_tournament(
|
||||
async def test_archive_and_unarchive_tournament(
|
||||
startup_and_shutdown_uvicorn_server: None, auth_context: AuthContext
|
||||
) -> None:
|
||||
query = tournaments.select().where(tournaments.c.id == auth_context.tournament.id)
|
||||
query = "SELECT * FROM tournaments WHERE id = :id"
|
||||
values = {"id": auth_context.tournament.id}
|
||||
body = {"status": "ARCHIVED"}
|
||||
assert (
|
||||
await send_tournament_request(HTTPMethod.POST, "change-status", auth_context, json=body)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
updated_tournament = await fetch_one_parsed_certain(database, Tournament, query)
|
||||
updated_tournament = await fetch_one_parsed_certain(database, Tournament, query, values)
|
||||
assert updated_tournament.status is TournamentStatus.ARCHIVED
|
||||
assert updated_tournament.dashboard_public is False
|
||||
|
||||
@@ -169,7 +170,7 @@ async def test_archive_and_unarchive_tournament(
|
||||
await send_tournament_request(HTTPMethod.POST, "change-status", auth_context, json=body)
|
||||
== SUCCESS_RESPONSE
|
||||
)
|
||||
updated_tournament = await fetch_one_parsed_certain(database, Tournament, query)
|
||||
updated_tournament = await fetch_one_parsed_certain(database, Tournament, query, values)
|
||||
assert updated_tournament.status is TournamentStatus.OPEN
|
||||
assert updated_tournament.dashboard_public is False
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ from heliclockter import datetime_utc
|
||||
from bracket.database import database
|
||||
from bracket.models.db.account import UserAccountType
|
||||
from bracket.models.db.user import User, UserInsertable
|
||||
from bracket.schema import users
|
||||
from bracket.sql.users import create_user, delete_user, get_user_by_id
|
||||
from bracket.utils.db import fetch_one_parsed_certain
|
||||
from bracket.utils.http import HTTPMethod
|
||||
@@ -118,7 +117,7 @@ async def test_update_user_password(
|
||||
json=body,
|
||||
)
|
||||
updated_user = await fetch_one_parsed_certain(
|
||||
database, User, query=users.select().where(users.c.id == user_created.id)
|
||||
database, User, "SELECT * FROM users WHERE id = :id", {"id": user_created.id}
|
||||
)
|
||||
|
||||
assert response.get("success") is True, response
|
||||
|
||||
@@ -5,16 +5,15 @@ from time import sleep
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from databases import Database
|
||||
|
||||
from bracket.database import database, engine
|
||||
from bracket.schema import metadata
|
||||
from bracket.database import DatabasePool, database
|
||||
from bracket.utils.db_init import _create_all_tables, _drop_all_tables
|
||||
from tests.integration_tests.models import AuthContext
|
||||
from tests.integration_tests.sql import inserted_auth_context
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session", autouse=True)
|
||||
async def reinit_database(worker_id: str) -> AsyncIterator[Database]:
|
||||
async def reinit_database(worker_id: str) -> AsyncIterator[DatabasePool]:
|
||||
"""
|
||||
Creates the test database on the first test run in the session.
|
||||
|
||||
@@ -25,8 +24,8 @@ async def reinit_database(worker_id: str) -> AsyncIterator[Database]:
|
||||
await database.connect()
|
||||
|
||||
if worker_id == "master":
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
_drop_all_tables()
|
||||
_create_all_tables()
|
||||
|
||||
try:
|
||||
yield database
|
||||
@@ -42,8 +41,8 @@ async def reinit_database(worker_id: str) -> AsyncIterator[Database]:
|
||||
with open(lock_path, mode="w") as file:
|
||||
file.write("")
|
||||
|
||||
metadata.drop_all(engine)
|
||||
metadata.create_all(engine)
|
||||
_drop_all_tables()
|
||||
_create_all_tables()
|
||||
finally:
|
||||
os.remove(lock_path)
|
||||
else:
|
||||
@@ -59,6 +58,6 @@ async def reinit_database(worker_id: str) -> AsyncIterator[Database]:
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
async def auth_context(reinit_database: Database) -> AsyncIterator[AuthContext]:
|
||||
async def auth_context(reinit_database: DatabasePool) -> AsyncIterator[AuthContext]:
|
||||
async with reinit_database, inserted_auth_context() as auth_context:
|
||||
yield auth_context
|
||||
|
||||
@@ -3,7 +3,6 @@ from contextlib import asynccontextmanager
|
||||
from typing import cast
|
||||
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import Table
|
||||
|
||||
from bracket.database import database
|
||||
from bracket.models.db.club import Club, ClubInsertable
|
||||
@@ -25,22 +24,6 @@ from bracket.models.db.team import Team, TeamInsertable
|
||||
from bracket.models.db.tournament import Tournament, TournamentInsertable
|
||||
from bracket.models.db.user import UserBase, UserInDB
|
||||
from bracket.models.db.user_x_club import UserXClub, UserXClubInsertable, UserXClubRelation
|
||||
from bracket.schema import (
|
||||
clubs,
|
||||
courts,
|
||||
matches,
|
||||
players,
|
||||
players_x_teams,
|
||||
rankings,
|
||||
rounds,
|
||||
stage_item_inputs,
|
||||
stage_items,
|
||||
stages,
|
||||
teams,
|
||||
tournaments,
|
||||
users,
|
||||
users_x_clubs,
|
||||
)
|
||||
from bracket.sql.teams import get_teams_by_id
|
||||
from bracket.utils.db import insert_generic
|
||||
from bracket.utils.dummy_records import DUMMY_CLUB, DUMMY_RANKING1, DUMMY_TOURNAMENT
|
||||
@@ -49,62 +32,66 @@ from tests.integration_tests.mocks import get_mock_token, get_mock_user
|
||||
from tests.integration_tests.models import AuthContext
|
||||
|
||||
|
||||
async def assert_row_count_and_clear(table: Table, expected_rows: int) -> None:
|
||||
# assert len(await database.fetch_all(query=table.select())) == expected_rows
|
||||
await database.execute(query=table.delete())
|
||||
async def assert_row_count_and_clear(table_name: str, expected_rows: int) -> None:
|
||||
await database.execute(query=f"DELETE FROM {table_name}")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_generic[BaseModelT: BaseModel](
|
||||
data_model: BaseModelT, table: Table, return_type: type[BaseModelT]
|
||||
data_model: BaseModelT, table_name: str, return_type: type[BaseModelT]
|
||||
) -> AsyncIterator[BaseModelT]:
|
||||
last_record_id, row_inserted = await insert_generic(database, data_model, table, return_type)
|
||||
last_record_id, row_inserted = await insert_generic(
|
||||
database, data_model, table_name, return_type
|
||||
)
|
||||
|
||||
try:
|
||||
yield row_inserted
|
||||
finally:
|
||||
await database.execute(query=table.delete().where(table.c.id == last_record_id))
|
||||
await database.execute(
|
||||
query=f"DELETE FROM {table_name} WHERE id = :id",
|
||||
values={"id": last_record_id},
|
||||
)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_user(user: UserBase) -> AsyncIterator[UserInDB]:
|
||||
async with inserted_generic(user, users, UserInDB) as row_inserted:
|
||||
async with inserted_generic(user, "users", UserInDB) as row_inserted:
|
||||
yield cast("UserInDB", row_inserted)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_club(club: ClubInsertable) -> AsyncIterator[Club]:
|
||||
async with inserted_generic(club, clubs, Club) as row_inserted:
|
||||
async with inserted_generic(club, "clubs", Club) as row_inserted:
|
||||
yield cast("Club", row_inserted)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_tournament(tournament: TournamentInsertable) -> AsyncIterator[Tournament]:
|
||||
async with inserted_generic(tournament, tournaments, Tournament) as row_inserted:
|
||||
async with inserted_generic(tournament, "tournaments", Tournament) as row_inserted:
|
||||
yield cast("Tournament", row_inserted)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_team(team: TeamInsertable) -> AsyncIterator[Team]:
|
||||
async with inserted_generic(team, teams, Team) as row_inserted:
|
||||
async with inserted_generic(team, "teams", Team) as row_inserted:
|
||||
yield cast("Team", row_inserted)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_court(court: CourtInsertable) -> AsyncIterator[Court]:
|
||||
async with inserted_generic(court, courts, Court) as row_inserted:
|
||||
async with inserted_generic(court, "courts", Court) as row_inserted:
|
||||
yield cast("Court", row_inserted)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_ranking(ranking: RankingInsertable) -> AsyncIterator[Ranking]:
|
||||
async with inserted_generic(ranking, rankings, Ranking) as row_inserted:
|
||||
async with inserted_generic(ranking, "rankings", Ranking) as row_inserted:
|
||||
yield cast("Ranking", row_inserted)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_player(player: PlayerInsertable) -> AsyncIterator[Player]:
|
||||
async with inserted_generic(player, players, Player) as row_inserted:
|
||||
async with inserted_generic(player, "players", Player) as row_inserted:
|
||||
yield cast("Player", row_inserted)
|
||||
|
||||
|
||||
@@ -112,10 +99,10 @@ async def inserted_player(player: PlayerInsertable) -> AsyncIterator[Player]:
|
||||
async def inserted_player_in_team(
|
||||
player: PlayerInsertable, team_id: TeamId
|
||||
) -> AsyncIterator[Player]:
|
||||
async with inserted_generic(player, players, Player) as row_inserted:
|
||||
async with inserted_generic(player, "players", Player) as row_inserted:
|
||||
async with inserted_generic(
|
||||
PlayerXTeamInsertable(player_id=cast("Player", row_inserted).id, team_id=team_id),
|
||||
players_x_teams,
|
||||
"players_x_teams",
|
||||
PlayerXTeamInsertable,
|
||||
):
|
||||
yield cast("Player", row_inserted)
|
||||
@@ -123,13 +110,13 @@ async def inserted_player_in_team(
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_stage(stage: StageInsertable) -> AsyncIterator[Stage]:
|
||||
async with inserted_generic(stage, stages, Stage) as row_inserted:
|
||||
async with inserted_generic(stage, "stages", Stage) as row_inserted:
|
||||
yield cast("Stage", row_inserted)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_stage_item(stage_item: StageItemInsertable) -> AsyncIterator[StageItem]:
|
||||
async with inserted_generic(stage_item, stage_items, StageItem) as row_inserted:
|
||||
async with inserted_generic(stage_item, "stage_items", StageItem) as row_inserted:
|
||||
yield StageItem(**row_inserted.model_dump())
|
||||
|
||||
|
||||
@@ -139,7 +126,7 @@ async def inserted_stage_item_input(
|
||||
) -> AsyncIterator[StageItemInputFinal | StageItemInputEmpty]:
|
||||
async with inserted_generic(
|
||||
stage_item_input,
|
||||
stage_item_inputs,
|
||||
"stage_item_inputs",
|
||||
StageItemInputBase, # pyrefly: ignore[bad-argument-type]
|
||||
) as row_inserted:
|
||||
if stage_item_input.team_id is not None:
|
||||
@@ -155,19 +142,19 @@ async def inserted_stage_item_input(
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_round(round_: RoundInsertable) -> AsyncIterator[Round]:
|
||||
async with inserted_generic(round_, rounds, Round) as row_inserted:
|
||||
async with inserted_generic(round_, "rounds", Round) as row_inserted:
|
||||
yield cast("Round", row_inserted)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_match(match: MatchInsertable) -> AsyncIterator[Match]:
|
||||
async with inserted_generic(match, matches, Match) as row_inserted:
|
||||
async with inserted_generic(match, "matches", Match) as row_inserted:
|
||||
yield cast("Match", row_inserted)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_user_x_club(user_x_club: UserXClubInsertable) -> AsyncIterator[UserXClub]:
|
||||
async with inserted_generic(user_x_club, users_x_clubs, UserXClub) as row_inserted:
|
||||
async with inserted_generic(user_x_club, "users_x_clubs", UserXClub) as row_inserted:
|
||||
yield cast("UserXClub", row_inserted)
|
||||
|
||||
|
||||
@@ -192,7 +179,7 @@ async def inserted_auth_context() -> AsyncIterator[AuthContext]:
|
||||
) as user_x_club_inserted,
|
||||
):
|
||||
yield AuthContext(
|
||||
headers={"Authorization": f"Bearer {get_mock_token(mock_user.email)}"},
|
||||
headers={"Authorization": f"******"},
|
||||
user=user_inserted,
|
||||
club=club_inserted,
|
||||
tournament=tournament_inserted,
|
||||
|
||||
Reference in New Issue
Block a user