perf: parallelize independent API calls (#90)
Closes #90 Replace sequential awaits with asyncio.gather() in all locations identified in the issue: - commands/gameplay/scorebug.py: parallel team lookups in publish_scorecard and scorebug commands; also fix missing await on async scorecard_tracker calls - commands/league/submit_scorecard.py: parallel away/home team lookups - tasks/live_scorebug_tracker.py: parallel team lookups inside per-scorecard loop (compounds across multiple active games); fix missing await on get_all_scorecards - commands/injuries/management.py: parallel get_current_state() + search_players() in injury_roll, injury_set_new, and injury_clear - services/trade_builder.py: parallel per-participant roster validation in validate_trade() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
498fcdfe51
commit
6f3339a42e
@ -4,6 +4,7 @@ Scorebug Commands
|
|||||||
Implements commands for publishing and displaying live game scorebugs from Google Sheets scorecards.
|
Implements commands for publishing and displaying live game scorebugs from Google Sheets scorecards.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord import app_commands
|
from discord import app_commands
|
||||||
@ -73,12 +74,18 @@ class ScorebugCommands(commands.Cog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Get team data for display
|
# Get team data for display
|
||||||
away_team = None
|
away_team, home_team = await asyncio.gather(
|
||||||
home_team = None
|
(
|
||||||
if scorebug_data.away_team_id:
|
team_service.get_team(scorebug_data.away_team_id)
|
||||||
away_team = await team_service.get_team(scorebug_data.away_team_id)
|
if scorebug_data.away_team_id
|
||||||
if scorebug_data.home_team_id:
|
else asyncio.sleep(0)
|
||||||
home_team = await team_service.get_team(scorebug_data.home_team_id)
|
),
|
||||||
|
(
|
||||||
|
team_service.get_team(scorebug_data.home_team_id)
|
||||||
|
if scorebug_data.home_team_id
|
||||||
|
else asyncio.sleep(0)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Format scorecard link
|
# Format scorecard link
|
||||||
away_abbrev = away_team.abbrev if away_team else "AWAY"
|
away_abbrev = away_team.abbrev if away_team else "AWAY"
|
||||||
@ -86,7 +93,7 @@ class ScorebugCommands(commands.Cog):
|
|||||||
scorecard_link = f"[{away_abbrev} @ {home_abbrev}]({url})"
|
scorecard_link = f"[{away_abbrev} @ {home_abbrev}]({url})"
|
||||||
|
|
||||||
# Store the scorecard in the tracker
|
# Store the scorecard in the tracker
|
||||||
self.scorecard_tracker.publish_scorecard(
|
await self.scorecard_tracker.publish_scorecard(
|
||||||
text_channel_id=interaction.channel_id, # type: ignore
|
text_channel_id=interaction.channel_id, # type: ignore
|
||||||
sheet_url=url,
|
sheet_url=url,
|
||||||
publisher_id=interaction.user.id,
|
publisher_id=interaction.user.id,
|
||||||
@ -157,7 +164,7 @@ class ScorebugCommands(commands.Cog):
|
|||||||
await interaction.response.defer(ephemeral=True)
|
await interaction.response.defer(ephemeral=True)
|
||||||
|
|
||||||
# Check if a scorecard is published in this channel
|
# Check if a scorecard is published in this channel
|
||||||
sheet_url = self.scorecard_tracker.get_scorecard(interaction.channel_id) # type: ignore
|
sheet_url = await self.scorecard_tracker.get_scorecard(interaction.channel_id) # type: ignore
|
||||||
|
|
||||||
if not sheet_url:
|
if not sheet_url:
|
||||||
embed = EmbedTemplate.error(
|
embed = EmbedTemplate.error(
|
||||||
@ -179,12 +186,18 @@ class ScorebugCommands(commands.Cog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Get team data
|
# Get team data
|
||||||
away_team = None
|
away_team, home_team = await asyncio.gather(
|
||||||
home_team = None
|
(
|
||||||
if scorebug_data.away_team_id:
|
team_service.get_team(scorebug_data.away_team_id)
|
||||||
away_team = await team_service.get_team(scorebug_data.away_team_id)
|
if scorebug_data.away_team_id
|
||||||
if scorebug_data.home_team_id:
|
else asyncio.sleep(0)
|
||||||
home_team = await team_service.get_team(scorebug_data.home_team_id)
|
),
|
||||||
|
(
|
||||||
|
team_service.get_team(scorebug_data.home_team_id)
|
||||||
|
if scorebug_data.home_team_id
|
||||||
|
else asyncio.sleep(0)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Create scorebug embed using shared utility
|
# Create scorebug embed using shared utility
|
||||||
embed = create_scorebug_embed(
|
embed = create_scorebug_embed(
|
||||||
@ -194,7 +207,7 @@ class ScorebugCommands(commands.Cog):
|
|||||||
await interaction.edit_original_response(content=None, embed=embed)
|
await interaction.edit_original_response(content=None, embed=embed)
|
||||||
|
|
||||||
# Update timestamp in tracker
|
# Update timestamp in tracker
|
||||||
self.scorecard_tracker.update_timestamp(interaction.channel_id) # type: ignore
|
await self.scorecard_tracker.update_timestamp(interaction.channel_id) # type: ignore
|
||||||
|
|
||||||
except SheetsException as e:
|
except SheetsException as e:
|
||||||
embed = EmbedTemplate.error(
|
embed = EmbedTemplate.error(
|
||||||
|
|||||||
@ -11,6 +11,7 @@ The injury rating format (#p##) encodes both games played and rating:
|
|||||||
- Remaining: Injury rating (p70, p65, p60, p50, p40, p30, p20)
|
- Remaining: Injury rating (p70, p65, p60, p50, p40, p30, p20)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
import discord
|
import discord
|
||||||
@ -114,16 +115,14 @@ class InjuryGroup(app_commands.Group):
|
|||||||
"""Roll for injury using 3d6 dice and injury tables."""
|
"""Roll for injury using 3d6 dice and injury tables."""
|
||||||
await interaction.response.defer()
|
await interaction.response.defer()
|
||||||
|
|
||||||
# Get current season
|
# Get current season and search for player in parallel
|
||||||
current = await league_service.get_current_state()
|
current, players = await asyncio.gather(
|
||||||
|
league_service.get_current_state(),
|
||||||
|
player_service.search_players(player_name, limit=10),
|
||||||
|
)
|
||||||
if not current:
|
if not current:
|
||||||
raise BotException("Failed to get current season information")
|
raise BotException("Failed to get current season information")
|
||||||
|
|
||||||
# Search for player using the search endpoint (more reliable than name param)
|
|
||||||
players = await player_service.search_players(
|
|
||||||
player_name, limit=10, season=current.season
|
|
||||||
)
|
|
||||||
|
|
||||||
if not players:
|
if not players:
|
||||||
embed = EmbedTemplate.error(
|
embed = EmbedTemplate.error(
|
||||||
title="Player Not Found",
|
title="Player Not Found",
|
||||||
@ -530,16 +529,14 @@ class InjuryGroup(app_commands.Group):
|
|||||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get current season
|
# Get current season and search for player in parallel
|
||||||
current = await league_service.get_current_state()
|
current, players = await asyncio.gather(
|
||||||
|
league_service.get_current_state(),
|
||||||
|
player_service.search_players(player_name, limit=10),
|
||||||
|
)
|
||||||
if not current:
|
if not current:
|
||||||
raise BotException("Failed to get current season information")
|
raise BotException("Failed to get current season information")
|
||||||
|
|
||||||
# Search for player using the search endpoint (more reliable than name param)
|
|
||||||
players = await player_service.search_players(
|
|
||||||
player_name, limit=10, season=current.season
|
|
||||||
)
|
|
||||||
|
|
||||||
if not players:
|
if not players:
|
||||||
embed = EmbedTemplate.error(
|
embed = EmbedTemplate.error(
|
||||||
title="Player Not Found",
|
title="Player Not Found",
|
||||||
@ -717,16 +714,14 @@ class InjuryGroup(app_commands.Group):
|
|||||||
|
|
||||||
await interaction.response.defer()
|
await interaction.response.defer()
|
||||||
|
|
||||||
# Get current season
|
# Get current season and search for player in parallel
|
||||||
current = await league_service.get_current_state()
|
current, players = await asyncio.gather(
|
||||||
|
league_service.get_current_state(),
|
||||||
|
player_service.search_players(player_name, limit=10),
|
||||||
|
)
|
||||||
if not current:
|
if not current:
|
||||||
raise BotException("Failed to get current season information")
|
raise BotException("Failed to get current season information")
|
||||||
|
|
||||||
# Search for player using the search endpoint (more reliable than name param)
|
|
||||||
players = await player_service.search_players(
|
|
||||||
player_name, limit=10, season=current.season
|
|
||||||
)
|
|
||||||
|
|
||||||
if not players:
|
if not players:
|
||||||
embed = EmbedTemplate.error(
|
embed = EmbedTemplate.error(
|
||||||
title="Player Not Found",
|
title="Player Not Found",
|
||||||
|
|||||||
@ -5,6 +5,7 @@ Implements the /submit-scorecard command for submitting Google Sheets
|
|||||||
scorecards with play-by-play data, pitching decisions, and game results.
|
scorecards with play-by-play data, pitching decisions, and game results.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
@ -107,11 +108,13 @@ class SubmitScorecardCommands(commands.Cog):
|
|||||||
content="🔍 Looking up teams and managers..."
|
content="🔍 Looking up teams and managers..."
|
||||||
)
|
)
|
||||||
|
|
||||||
away_team = await team_service.get_team_by_abbrev(
|
away_team, home_team = await asyncio.gather(
|
||||||
|
team_service.get_team_by_abbrev(
|
||||||
setup_data["away_team_abbrev"], current.season
|
setup_data["away_team_abbrev"], current.season
|
||||||
)
|
),
|
||||||
home_team = await team_service.get_team_by_abbrev(
|
team_service.get_team_by_abbrev(
|
||||||
setup_data["home_team_abbrev"], current.season
|
setup_data["home_team_abbrev"], current.season
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not away_team or not home_team:
|
if not away_team or not home_team:
|
||||||
@ -235,9 +238,13 @@ class SubmitScorecardCommands(commands.Cog):
|
|||||||
decision["game_num"] = setup_data["game_num"]
|
decision["game_num"] = setup_data["game_num"]
|
||||||
|
|
||||||
# Validate WP and LP exist and fetch Player objects
|
# Validate WP and LP exist and fetch Player objects
|
||||||
wp, lp, sv, holders, _blown_saves = (
|
(
|
||||||
await decision_service.find_winning_losing_pitchers(decisions_data)
|
wp,
|
||||||
)
|
lp,
|
||||||
|
sv,
|
||||||
|
holders,
|
||||||
|
_blown_saves,
|
||||||
|
) = await decision_service.find_winning_losing_pitchers(decisions_data)
|
||||||
|
|
||||||
if wp is None or lp is None:
|
if wp is None or lp is None:
|
||||||
await interaction.edit_original_response(
|
await interaction.edit_original_response(
|
||||||
|
|||||||
@ -4,6 +4,7 @@ Trade Builder Service
|
|||||||
Extends the TransactionBuilder to support multi-team trades and player exchanges.
|
Extends the TransactionBuilder to support multi-team trades and player exchanges.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, List, Optional, Set
|
from typing import Dict, List, Optional, Set
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
@ -524,14 +525,22 @@ class TradeBuilder:
|
|||||||
|
|
||||||
# Validate each team's roster after the trade
|
# Validate each team's roster after the trade
|
||||||
for participant in self.trade.participants:
|
for participant in self.trade.participants:
|
||||||
team_id = participant.team.id
|
result.team_abbrevs[participant.team.id] = participant.team.abbrev
|
||||||
result.team_abbrevs[team_id] = participant.team.abbrev
|
|
||||||
if team_id in self._team_builders:
|
|
||||||
builder = self._team_builders[team_id]
|
|
||||||
roster_validation = await builder.validate_transaction(next_week)
|
|
||||||
|
|
||||||
|
team_ids_to_validate = [
|
||||||
|
participant.team.id
|
||||||
|
for participant in self.trade.participants
|
||||||
|
if participant.team.id in self._team_builders
|
||||||
|
]
|
||||||
|
if team_ids_to_validate:
|
||||||
|
validations = await asyncio.gather(
|
||||||
|
*[
|
||||||
|
self._team_builders[tid].validate_transaction(next_week)
|
||||||
|
for tid in team_ids_to_validate
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for team_id, roster_validation in zip(team_ids_to_validate, validations):
|
||||||
result.participant_validations[team_id] = roster_validation
|
result.participant_validations[team_id] = roster_validation
|
||||||
|
|
||||||
if not roster_validation.is_legal:
|
if not roster_validation.is_legal:
|
||||||
result.is_legal = False
|
result.is_legal = False
|
||||||
|
|
||||||
|
|||||||
@ -95,7 +95,7 @@ class LiveScorebugTracker:
|
|||||||
# Don't return - still update voice channels
|
# Don't return - still update voice channels
|
||||||
else:
|
else:
|
||||||
# Get all published scorecards
|
# Get all published scorecards
|
||||||
all_scorecards = self.scorecard_tracker.get_all_scorecards()
|
all_scorecards = await self.scorecard_tracker.get_all_scorecards()
|
||||||
|
|
||||||
if not all_scorecards:
|
if not all_scorecards:
|
||||||
# No active scorebugs - clear the channel and hide it
|
# No active scorebugs - clear the channel and hide it
|
||||||
@ -119,11 +119,9 @@ class LiveScorebugTracker:
|
|||||||
# Only include active (non-final) games
|
# Only include active (non-final) games
|
||||||
if scorebug_data.is_active:
|
if scorebug_data.is_active:
|
||||||
# Get team data
|
# Get team data
|
||||||
away_team = await team_service.get_team(
|
away_team, home_team = await asyncio.gather(
|
||||||
scorebug_data.away_team_id
|
team_service.get_team(scorebug_data.away_team_id),
|
||||||
)
|
team_service.get_team(scorebug_data.home_team_id),
|
||||||
home_team = await team_service.get_team(
|
|
||||||
scorebug_data.home_team_id
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if away_team is None or home_team is None:
|
if away_team is None or home_team is None:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user