Compare commits

..

No commits in common. "main" and "2026.4.3" have entirely different histories.

41 changed files with 422 additions and 482 deletions

3
.env
View File

@ -6,9 +6,6 @@ SBA_DB_USER_PASSWORD=your_production_password
# SBa API # SBa API
API_TOKEN=Tp3aO3jhYve5NJF1IqOmJTmk API_TOKEN=Tp3aO3jhYve5NJF1IqOmJTmk
# Integrations
DISCORD_WEBHOOK_URL=
# Universal # Universal
TZ=America/Chicago TZ=America/Chicago
LOG_LEVEL=INFO LOG_LEVEL=INFO

View File

@ -1,18 +1,20 @@
# Gitea Actions: Docker Build, Push, and Notify # Gitea Actions: Docker Build, Push, and Notify
# #
# CI/CD pipeline for Major Domo Database API: # CI/CD pipeline for Major Domo Database API:
# - Triggered by pushing a CalVer tag (e.g., 2026.4.5) # - Builds Docker images on every push/PR
# - Builds Docker image and pushes to Docker Hub with version + latest tags # - Auto-generates CalVer version (YYYY.MM.BUILD) on main branch merges
# - Pushes to Docker Hub and creates git tag on main
# - Sends Discord notifications on success/failure # - Sends Discord notifications on success/failure
#
# To release: git tag -a 2026.4.5 -m "description" && git push origin 2026.4.5
name: Build Docker Image name: Build Docker Image
on: on:
push: push:
tags: branches:
- '20*' # matches CalVer tags like 2026.4.5 - main
pull_request:
branches:
- main
jobs: jobs:
build: build:
@ -22,16 +24,7 @@ jobs:
- name: Checkout code - name: Checkout code
uses: https://github.com/actions/checkout@v4 uses: https://github.com/actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0 # Full history for tag counting
- name: Extract version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/}
SHA_SHORT=$(git rev-parse --short HEAD)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "sha_short=$SHA_SHORT" >> $GITHUB_OUTPUT
echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: https://github.com/docker/setup-buildx-action@v3 uses: https://github.com/docker/setup-buildx-action@v3
@ -42,47 +35,80 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image - name: Generate CalVer version
id: calver
uses: cal/gitea-actions/calver@main
# Dev build: push with dev + dev-SHA tags (PR/feature branches)
- name: Build Docker image (dev)
if: github.ref != 'refs/heads/main'
uses: https://github.com/docker/build-push-action@v5 uses: https://github.com/docker/build-push-action@v5
with: with:
context: . context: .
push: true push: true
tags: | tags: |
manticorum67/major-domo-database:${{ steps.version.outputs.version }} manticorum67/major-domo-database:dev
manticorum67/major-domo-database:latest manticorum67/major-domo-database:dev-${{ steps.calver.outputs.sha_short }}
cache-from: type=registry,ref=manticorum67/major-domo-database:buildcache cache-from: type=registry,ref=manticorum67/major-domo-database:buildcache
cache-to: type=registry,ref=manticorum67/major-domo-database:buildcache,mode=max cache-to: type=registry,ref=manticorum67/major-domo-database:buildcache,mode=max
# Production build: push with latest + CalVer tags (main only)
- name: Build Docker image (production)
if: github.ref == 'refs/heads/main'
uses: https://github.com/docker/build-push-action@v5
with:
context: .
push: true
tags: |
manticorum67/major-domo-database:latest
manticorum67/major-domo-database:${{ steps.calver.outputs.version }}
manticorum67/major-domo-database:${{ steps.calver.outputs.version_sha }}
cache-from: type=registry,ref=manticorum67/major-domo-database:buildcache
cache-to: type=registry,ref=manticorum67/major-domo-database:buildcache,mode=max
- name: Tag release
if: success() && github.ref == 'refs/heads/main'
uses: cal/gitea-actions/gitea-tag@main
with:
version: ${{ steps.calver.outputs.version }}
token: ${{ github.token }}
- name: Build Summary - name: Build Summary
run: | run: |
echo "## Docker Build Successful" >> $GITHUB_STEP_SUMMARY echo "## Docker Build Successful" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version:** \`${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Image Tags:**" >> $GITHUB_STEP_SUMMARY echo "**Image Tags:**" >> $GITHUB_STEP_SUMMARY
echo "- \`manticorum67/major-domo-database:${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`manticorum67/major-domo-database:latest\`" >> $GITHUB_STEP_SUMMARY echo "- \`manticorum67/major-domo-database:latest\`" >> $GITHUB_STEP_SUMMARY
echo "- \`manticorum67/major-domo-database:${{ steps.calver.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`manticorum67/major-domo-database:${{ steps.calver.outputs.version_sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "**Build Details:**" >> $GITHUB_STEP_SUMMARY echo "**Build Details:**" >> $GITHUB_STEP_SUMMARY
echo "- Commit: \`${{ steps.version.outputs.sha_short }}\`" >> $GITHUB_STEP_SUMMARY echo "- Branch: \`${{ steps.calver.outputs.branch }}\`" >> $GITHUB_STEP_SUMMARY
echo "- Timestamp: \`${{ steps.version.outputs.timestamp }}\`" >> $GITHUB_STEP_SUMMARY echo "- Commit: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "- Timestamp: \`${{ steps.calver.outputs.timestamp }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "Pull with: \`docker pull manticorum67/major-domo-database:${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "Pushed to Docker Hub!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Pull with: \`docker pull manticorum67/major-domo-database:latest\`" >> $GITHUB_STEP_SUMMARY
else
echo "_PR build - image not pushed to Docker Hub_" >> $GITHUB_STEP_SUMMARY
fi
- name: Discord Notification - Success - name: Discord Notification - Success
if: success() if: success() && github.ref == 'refs/heads/main'
uses: cal/gitea-actions/discord-notify@main uses: cal/gitea-actions/discord-notify@main
with: with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK }} webhook_url: ${{ secrets.DISCORD_WEBHOOK }}
title: "Major Domo Database" title: "Major Domo Database"
status: success status: success
version: ${{ steps.version.outputs.version }} version: ${{ steps.calver.outputs.version }}
image_tag: ${{ steps.version.outputs.version }} image_tag: ${{ steps.calver.outputs.version_sha }}
commit_sha: ${{ steps.version.outputs.sha_short }} commit_sha: ${{ steps.calver.outputs.sha_short }}
timestamp: ${{ steps.version.outputs.timestamp }} timestamp: ${{ steps.calver.outputs.timestamp }}
- name: Discord Notification - Failure - name: Discord Notification - Failure
if: failure() if: failure() && github.ref == 'refs/heads/main'
uses: cal/gitea-actions/discord-notify@main uses: cal/gitea-actions/discord-notify@main
with: with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK }} webhook_url: ${{ secrets.DISCORD_WEBHOOK }}

1
.gitignore vendored
View File

@ -55,6 +55,7 @@ Include/
pyvenv.cfg pyvenv.cfg
db_engine.py db_engine.py
main.py main.py
migrations.py
db_engine.py db_engine.py
sba_master.db sba_master.db
db_engine.py db_engine.py

View File

@ -40,7 +40,7 @@ python migrations.py # Run migrations (SQL files in migrat
- **Bot container**: `dev_sba_postgres` (PostgreSQL) + `dev_sba_db_api` (API) — check with `docker ps` - **Bot container**: `dev_sba_postgres` (PostgreSQL) + `dev_sba_db_api` (API) — check with `docker ps`
- **Image**: `manticorum67/major-domo-database:dev` (Docker Hub) - **Image**: `manticorum67/major-domo-database:dev` (Docker Hub)
- **CI/CD**: Gitea Actions — tag-triggered Docker builds. Push a CalVer tag to release: `git tag -a 2026.4.5 -m "description" && git push origin 2026.4.5` - **CI/CD**: Gitea Actions on PR to `main` — builds Docker image, auto-generates CalVer version (`YYYY.MM.BUILD`) on merge
## Important ## Important

View File

@ -1,5 +1,5 @@
# Use specific version for reproducible builds # Use specific version for reproducible builds
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.12 FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11
# Set Python optimizations # Set Python optimizations
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1

View File

@ -8,17 +8,21 @@ from typing import Literal, List, Optional
from pandas import DataFrame from pandas import DataFrame
from peewee import * from peewee import *
from peewee import ModelSelect from peewee import ModelSelect
from playhouse.pool import PooledPostgresqlDatabase
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
_postgres_password = os.environ.get("POSTGRES_PASSWORD") # Database configuration - supports both SQLite and PostgreSQL
if _postgres_password is None: DATABASE_TYPE = os.environ.get("DATABASE_TYPE", "sqlite")
if DATABASE_TYPE.lower() == "postgresql":
from playhouse.pool import PooledPostgresqlDatabase
_postgres_password = os.environ.get("POSTGRES_PASSWORD")
if _postgres_password is None:
raise RuntimeError( raise RuntimeError(
"POSTGRES_PASSWORD environment variable is not set. " "POSTGRES_PASSWORD environment variable is not set. "
"This variable is required when DATABASE_TYPE=postgresql." "This variable is required when DATABASE_TYPE=postgresql."
) )
db = PooledPostgresqlDatabase(
db = PooledPostgresqlDatabase(
os.environ.get("POSTGRES_DB", "sba_master"), os.environ.get("POSTGRES_DB", "sba_master"),
user=os.environ.get("POSTGRES_USER", "sba_admin"), user=os.environ.get("POSTGRES_USER", "sba_admin"),
password=_postgres_password, password=_postgres_password,
@ -26,11 +30,16 @@ db = PooledPostgresqlDatabase(
port=int(os.environ.get("POSTGRES_PORT", "5432")), port=int(os.environ.get("POSTGRES_PORT", "5432")),
max_connections=20, max_connections=20,
stale_timeout=300, # 5 minutes stale_timeout=300, # 5 minutes
timeout=5, timeout=0,
autoconnect=False, autoconnect=True,
autorollback=True, # Automatically rollback failed transactions autorollback=True, # Automatically rollback failed transactions
) )
else:
# Default SQLite configuration
db = SqliteDatabase(
"storage/sba_master.db",
pragmas={"journal_mode": "wal", "cache_size": -1 * 64000, "synchronous": 0},
)
date = f"{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}" date = f"{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}"
logger = logging.getLogger("discord_app") logger = logging.getLogger("discord_app")
@ -514,12 +523,12 @@ class Team(BaseModel):
all_drops = Transaction.select_season(Current.latest().season).where( all_drops = Transaction.select_season(Current.latest().season).where(
(Transaction.oldteam == self) (Transaction.oldteam == self)
& (Transaction.week == current.week + 1) & (Transaction.week == current.week + 1)
& (Transaction.cancelled == False) & (Transaction.cancelled == 0)
) )
all_adds = Transaction.select_season(Current.latest().season).where( all_adds = Transaction.select_season(Current.latest().season).where(
(Transaction.newteam == self) (Transaction.newteam == self)
& (Transaction.week == current.week + 1) & (Transaction.week == current.week + 1)
& (Transaction.cancelled == False) & (Transaction.cancelled == 0)
) )
for move in all_drops: for move in all_drops:
@ -597,12 +606,12 @@ class Team(BaseModel):
all_drops = Transaction.select_season(Current.latest().season).where( all_drops = Transaction.select_season(Current.latest().season).where(
(Transaction.oldteam == sil_team) (Transaction.oldteam == sil_team)
& (Transaction.week == current.week + 1) & (Transaction.week == current.week + 1)
& (Transaction.cancelled == False) & (Transaction.cancelled == 0)
) )
all_adds = Transaction.select_season(Current.latest().season).where( all_adds = Transaction.select_season(Current.latest().season).where(
(Transaction.newteam == sil_team) (Transaction.newteam == sil_team)
& (Transaction.week == current.week + 1) & (Transaction.week == current.week + 1)
& (Transaction.cancelled == False) & (Transaction.cancelled == 0)
) )
for move in all_drops: for move in all_drops:
@ -680,12 +689,12 @@ class Team(BaseModel):
all_drops = Transaction.select_season(Current.latest().season).where( all_drops = Transaction.select_season(Current.latest().season).where(
(Transaction.oldteam == lil_team) (Transaction.oldteam == lil_team)
& (Transaction.week == current.week + 1) & (Transaction.week == current.week + 1)
& (Transaction.cancelled == False) & (Transaction.cancelled == 0)
) )
all_adds = Transaction.select_season(Current.latest().season).where( all_adds = Transaction.select_season(Current.latest().season).where(
(Transaction.newteam == lil_team) (Transaction.newteam == lil_team)
& (Transaction.week == current.week + 1) & (Transaction.week == current.week + 1)
& (Transaction.cancelled == False) & (Transaction.cancelled == 0)
) )
for move in all_drops: for move in all_drops:
@ -1356,10 +1365,6 @@ class PitchingStat(BaseModel):
def select_season(season): def select_season(season):
return PitchingStat.select().where(PitchingStat.season == season) return PitchingStat.select().where(PitchingStat.season == season)
@staticmethod
def combined_season(season):
return PitchingStat.select().where(PitchingStat.season == season)
@staticmethod @staticmethod
def regular_season(season): def regular_season(season):
if season == 1: if season == 1:
@ -1524,18 +1529,7 @@ class Standings(BaseModel):
with db.atomic(): with db.atomic():
Standings.bulk_create(create_teams) Standings.bulk_create(create_teams)
# Pre-fetch all data needed for in-memory processing (avoids N+1 queries) # Iterate through each individual result
standings_by_team_id = {
s.team_id: s
for s in Standings.select().where(Standings.team << s_teams)
}
teams_by_id = {t.id: t for t in Team.select().where(Team.season == season)}
divisions_by_id = {
d.id: d
for d in Division.select().where(Division.season == season)
}
# Iterate through each individual result, tallying wins/losses in memory
# for game in Result.select_season(season).where(Result.week <= 22): # for game in Result.select_season(season).where(Result.week <= 22):
for game in ( for game in (
StratGame.select() StratGame.select()
@ -1546,121 +1540,8 @@ class Standings(BaseModel):
) )
.order_by(StratGame.week, StratGame.game_num) .order_by(StratGame.week, StratGame.game_num)
): ):
away_stan = standings_by_team_id.get(game.away_team_id) # tally win and loss for each standings object
home_stan = standings_by_team_id.get(game.home_team_id) game.update_standings()
away_team_obj = teams_by_id.get(game.away_team_id)
home_team_obj = teams_by_id.get(game.home_team_id)
if None in (away_stan, home_stan, away_team_obj, home_team_obj):
continue
away_div = divisions_by_id.get(away_team_obj.division_id)
home_div = divisions_by_id.get(home_team_obj.division_id)
if away_div is None or home_div is None:
continue
# Home Team Won
if game.home_score > game.away_score:
home_stan.wins += 1
home_stan.home_wins += 1
away_stan.losses += 1
away_stan.away_losses += 1
if home_stan.streak_wl == 'w':
home_stan.streak_num += 1
else:
home_stan.streak_wl = 'w'
home_stan.streak_num = 1
if away_stan.streak_wl == 'l':
away_stan.streak_num += 1
else:
away_stan.streak_wl = 'l'
away_stan.streak_num = 1
if game.home_score == game.away_score + 1:
home_stan.one_run_wins += 1
away_stan.one_run_losses += 1
if away_div.division_abbrev == 'TC':
home_stan.div1_wins += 1
elif away_div.division_abbrev == 'ETSOS':
home_stan.div2_wins += 1
elif away_div.division_abbrev == 'APL':
home_stan.div3_wins += 1
elif away_div.division_abbrev == 'BBC':
home_stan.div4_wins += 1
if home_div.division_abbrev == 'TC':
away_stan.div1_losses += 1
elif home_div.division_abbrev == 'ETSOS':
away_stan.div2_losses += 1
elif home_div.division_abbrev == 'APL':
away_stan.div3_losses += 1
elif home_div.division_abbrev == 'BBC':
away_stan.div4_losses += 1
home_stan.run_diff += game.home_score - game.away_score
away_stan.run_diff -= game.home_score - game.away_score
# Away Team Won
else:
home_stan.losses += 1
home_stan.home_losses += 1
away_stan.wins += 1
away_stan.away_wins += 1
if home_stan.streak_wl == 'l':
home_stan.streak_num += 1
else:
home_stan.streak_wl = 'l'
home_stan.streak_num = 1
if away_stan.streak_wl == 'w':
away_stan.streak_num += 1
else:
away_stan.streak_wl = 'w'
away_stan.streak_num = 1
if game.away_score == game.home_score + 1:
home_stan.one_run_losses += 1
away_stan.one_run_wins += 1
if away_div.division_abbrev == 'TC':
home_stan.div1_losses += 1
elif away_div.division_abbrev == 'ETSOS':
home_stan.div2_losses += 1
elif away_div.division_abbrev == 'APL':
home_stan.div3_losses += 1
elif away_div.division_abbrev == 'BBC':
home_stan.div4_losses += 1
if home_div.division_abbrev == 'TC':
away_stan.div1_wins += 1
elif home_div.division_abbrev == 'ETSOS':
away_stan.div2_wins += 1
elif home_div.division_abbrev == 'APL':
away_stan.div3_wins += 1
elif home_div.division_abbrev == 'BBC':
away_stan.div4_wins += 1
home_stan.run_diff -= game.away_score - game.home_score
away_stan.run_diff += game.away_score - game.home_score
# Bulk save all modified standings
with db.atomic():
Standings.bulk_update(
list(standings_by_team_id.values()),
fields=[
Standings.wins, Standings.losses,
Standings.home_wins, Standings.home_losses,
Standings.away_wins, Standings.away_losses,
Standings.one_run_wins, Standings.one_run_losses,
Standings.streak_wl, Standings.streak_num,
Standings.run_diff,
Standings.div1_wins, Standings.div1_losses,
Standings.div2_wins, Standings.div2_losses,
Standings.div3_wins, Standings.div3_losses,
Standings.div4_wins, Standings.div4_losses,
]
)
# Set pythag record and iterate through last 8 games for last8 record # Set pythag record and iterate through last 8 games for last8 record
for team in all_teams: for team in all_teams:
@ -1709,7 +1590,9 @@ class BattingCareer(BaseModel):
@staticmethod @staticmethod
def recalculate(): def recalculate():
# Wipe existing data # Wipe existing data
BattingCareer.delete().execute() delete_lines = BattingCareer.select()
for line in delete_lines:
line.delete_instance()
# For each seasonstat, find career or create new and increment # For each seasonstat, find career or create new and increment
for this_season in BattingSeason.select().where( for this_season in BattingSeason.select().where(
@ -1772,7 +1655,9 @@ class PitchingCareer(BaseModel):
@staticmethod @staticmethod
def recalculate(): def recalculate():
# Wipe existing data # Wipe existing data
PitchingCareer.delete().execute() delete_lines = PitchingCareer.select()
for line in delete_lines:
line.delete_instance()
# For each seasonstat, find career or create new and increment # For each seasonstat, find career or create new and increment
for this_season in PitchingSeason.select().where( for this_season in PitchingSeason.select().where(
@ -1824,7 +1709,9 @@ class FieldingCareer(BaseModel):
@staticmethod @staticmethod
def recalculate(): def recalculate():
# Wipe existing data # Wipe existing data
FieldingCareer.delete().execute() delete_lines = FieldingCareer.select()
for line in delete_lines:
line.delete_instance()
# For each seasonstat, find career or create new and increment # For each seasonstat, find career or create new and increment
for this_season in FieldingSeason.select().where( for this_season in FieldingSeason.select().where(
@ -2482,12 +2369,6 @@ class StratGame(BaseModel):
home_stan.save() home_stan.save()
away_stan.save() away_stan.save()
class Meta:
indexes = (
(("season",), False),
(("season", "week", "game_num"), False),
)
class StratPlay(BaseModel): class StratPlay(BaseModel):
game = ForeignKeyField(StratGame) game = ForeignKeyField(StratGame)
@ -2562,14 +2443,6 @@ class StratPlay(BaseModel):
re24_primary = FloatField(null=True) re24_primary = FloatField(null=True)
re24_running = FloatField(null=True) re24_running = FloatField(null=True)
class Meta:
indexes = (
(("game",), False),
(("batter",), False),
(("pitcher",), False),
(("runner",), False),
)
class Decision(BaseModel): class Decision(BaseModel):
game = ForeignKeyField(StratGame) game = ForeignKeyField(StratGame)
@ -2592,7 +2465,8 @@ class Decision(BaseModel):
class CustomCommandCreator(BaseModel): class CustomCommandCreator(BaseModel):
"""Model for custom command creators.""" """Model for custom command creators."""
discord_id = BigIntegerField(unique=True)
discord_id = CharField(max_length=20, unique=True) # Discord snowflake ID as string
username = CharField(max_length=32) username = CharField(max_length=32)
display_name = CharField(max_length=32, null=True) display_name = CharField(max_length=32, null=True)
created_at = DateTimeField() created_at = DateTimeField()

View File

@ -22,9 +22,6 @@ logger = logging.getLogger("discord_app")
# level=log_level # level=log_level
# ) # )
# Discord integration
DISCORD_WEBHOOK_URL = os.environ.get("DISCORD_WEBHOOK_URL")
# Redis configuration # Redis configuration
REDIS_HOST = os.environ.get("REDIS_HOST", "localhost") REDIS_HOST = os.environ.get("REDIS_HOST", "localhost")
REDIS_PORT = int(os.environ.get("REDIS_PORT", "6379")) REDIS_PORT = int(os.environ.get("REDIS_PORT", "6379"))
@ -519,12 +516,7 @@ def send_webhook_message(message: str) -> bool:
Returns: Returns:
bool: True if successful, False otherwise bool: True if successful, False otherwise
""" """
webhook_url = DISCORD_WEBHOOK_URL webhook_url = "https://discord.com/api/webhooks/1408811717424840876/7RXG_D5IqovA3Jwa9YOobUjVcVMuLc6cQyezABcWuXaHo5Fvz1en10M7J43o3OJ3bzGW"
if not webhook_url:
logger.warning(
"DISCORD_WEBHOOK_URL env var is not set — skipping webhook message"
)
return False
try: try:
payload = {"content": message} payload = {"content": message}

View File

@ -8,8 +8,6 @@ from fastapi import Depends, FastAPI, Request
from fastapi.openapi.docs import get_swagger_ui_html from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi from fastapi.openapi.utils import get_openapi
from .db_engine import db
# from fastapi.openapi.docs import get_swagger_ui_html # from fastapi.openapi.docs import get_swagger_ui_html
# from fastapi.openapi.utils import get_openapi # from fastapi.openapi.utils import get_openapi
@ -70,33 +68,9 @@ app = FastAPI(
) )
@app.middleware("http")
async def db_connection_middleware(request: Request, call_next):
db.connect(reuse_if_open=True)
try:
response = await call_next(request)
finally:
if not db.is_closed():
db.close()
return response
logger.info(f"Starting up now...") logger.info(f"Starting up now...")
@app.middleware("http")
async def db_connection_middleware(request: Request, call_next):
from .db_engine import db
db.connect(reuse_if_open=True)
try:
response = await call_next(request)
return response
finally:
if not db.is_closed():
db.close()
@app.middleware("http") @app.middleware("http")
async def strip_empty_query_params(request: Request, call_next): async def strip_empty_query_params(request: Request, call_next):
qs = request.scope.get("query_string", b"") qs = request.scope.get("query_string", b"")

View File

@ -78,6 +78,7 @@ async def get_awards(
"count": total_count, "count": total_count,
"awards": [model_to_dict(x, recurse=not short_output) for x in all_awards], "awards": [model_to_dict(x, recurse=not short_output) for x in all_awards],
} }
db.close()
return return_awards return return_awards
@ -86,8 +87,10 @@ async def get_awards(
async def get_one_award(award_id: int, short_output: Optional[bool] = False): async def get_one_award(award_id: int, short_output: Optional[bool] = False):
this_award = Award.get_or_none(Award.id == award_id) this_award = Award.get_or_none(Award.id == award_id)
if this_award is None: if this_award is None:
db.close()
raise HTTPException(status_code=404, detail=f"Award ID {award_id} not found") raise HTTPException(status_code=404, detail=f"Award ID {award_id} not found")
db.close()
return model_to_dict(this_award, recurse=not short_output) return model_to_dict(this_award, recurse=not short_output)
@ -106,11 +109,12 @@ async def patch_award(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_player - Bad Token") logger.warning(f"patch_player - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_award = Award.get_or_none(Award.id == award_id) this_award = Award.get_or_none(Award.id == award_id)
if this_award is None: if this_award is None:
db.close()
raise HTTPException(status_code=404, detail=f"Award ID {award_id} not found") raise HTTPException(status_code=404, detail=f"Award ID {award_id} not found")
if name is not None: if name is not None:
@ -132,8 +136,10 @@ async def patch_award(
if this_award.save() == 1: if this_award.save() == 1:
r_award = model_to_dict(this_award) r_award = model_to_dict(this_award)
db.close()
return r_award return r_award
else: else:
db.close()
raise HTTPException(status_code=500, detail=f"Unable to patch award {award_id}") raise HTTPException(status_code=500, detail=f"Unable to patch award {award_id}")
@ -141,7 +147,7 @@ async def patch_award(
@handle_db_errors @handle_db_errors
async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme)): async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_player - Bad Token") logger.warning(f"patch_player - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_awards = [] new_awards = []
@ -172,11 +178,12 @@ async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme))
status_code=404, detail=f"Team ID {x.team_id} not found" status_code=404, detail=f"Team ID {x.team_id} not found"
) )
new_awards.append(x.model_dump()) new_awards.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_awards, 15): for batch in chunked(new_awards, 15):
Award.insert_many(batch).on_conflict_ignore().execute() Award.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Inserted {len(new_awards)} awards" return f"Inserted {len(new_awards)} awards"
@ -185,14 +192,16 @@ async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme))
@handle_db_errors @handle_db_errors
async def delete_award(award_id: int, token: str = Depends(oauth2_scheme)): async def delete_award(award_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_player - Bad Token") logger.warning(f"patch_player - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_award = Award.get_or_none(Award.id == award_id) this_award = Award.get_or_none(Award.id == award_id)
if this_award is None: if this_award is None:
db.close()
raise HTTPException(status_code=404, detail=f"Award ID {award_id} not found") raise HTTPException(status_code=404, detail=f"Award ID {award_id} not found")
count = this_award.delete_instance() count = this_award.delete_instance()
db.close()
if count == 1: if count == 1:
return f"Award {award_id} has been deleted" return f"Award {award_id} has been deleted"

View File

@ -93,14 +93,17 @@ async def get_batstats(
if "post" in s_type.lower(): if "post" in s_type.lower():
all_stats = BattingStat.post_season(season) all_stats = BattingStat.post_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close()
return {"count": 0, "stats": []} return {"count": 0, "stats": []}
elif s_type.lower() in ["combined", "total", "all"]: elif s_type.lower() in ["combined", "total", "all"]:
all_stats = BattingStat.combined_season(season) all_stats = BattingStat.combined_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close()
return {"count": 0, "stats": []} return {"count": 0, "stats": []}
else: else:
all_stats = BattingStat.regular_season(season) all_stats = BattingStat.regular_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close()
return {"count": 0, "stats": []} return {"count": 0, "stats": []}
if position is not None: if position is not None:
@ -126,6 +129,7 @@ async def get_batstats(
if week_end is not None: if week_end is not None:
end = min(week_end, end) end = min(week_end, end)
if start > end: if start > end:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, status_code=404,
detail=f"Start week {start} is after end week {end} - cannot pull stats", detail=f"Start week {start} is after end week {end} - cannot pull stats",
@ -143,6 +147,7 @@ async def get_batstats(
# 'stats': [{'id': x.id} for x in all_stats] # 'stats': [{'id': x.id} for x in all_stats]
} }
db.close()
return return_stats return return_stats
@ -345,6 +350,7 @@ async def get_totalstats(
"bplo": x.sum_bplo, "bplo": x.sum_bplo,
} }
) )
db.close()
return return_stats return return_stats
@ -360,16 +366,15 @@ async def patch_batstats(
stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme) stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_batstats - Bad Token") logger.warning(f"patch_batstats - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
if BattingStat.get_or_none(BattingStat.id == stat_id) is None: if BattingStat.get_or_none(BattingStat.id == stat_id) is None:
raise HTTPException(status_code=404, detail=f"Stat ID {stat_id} not found") raise HTTPException(status_code=404, detail=f"Stat ID {stat_id} not found")
BattingStat.update(**new_stats.model_dump()).where( BattingStat.update(**new_stats.dict()).where(BattingStat.id == stat_id).execute()
BattingStat.id == stat_id
).execute()
r_stat = model_to_dict(BattingStat.get_by_id(stat_id)) r_stat = model_to_dict(BattingStat.get_by_id(stat_id))
db.close()
return r_stat return r_stat
@ -377,7 +382,7 @@ async def patch_batstats(
@handle_db_errors @handle_db_errors
async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)): async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_batstats - Bad Token") logger.warning(f"post_batstats - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
all_stats = [] all_stats = []
@ -405,7 +410,7 @@ async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)
status_code=404, detail=f"Player ID {x.player_id} not found" status_code=404, detail=f"Player ID {x.player_id} not found"
) )
all_stats.append(BattingStat(**x.model_dump())) all_stats.append(BattingStat(**x.dict()))
with db.atomic(): with db.atomic():
for batch in chunked(all_stats, 15): for batch in chunked(all_stats, 15):
@ -413,4 +418,5 @@ async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)
# Update career stats # Update career stats
db.close()
return f"Added {len(all_stats)} batting lines" return f"Added {len(all_stats)} batting lines"

View File

@ -41,6 +41,7 @@ async def get_current(season: Optional[int] = None):
if current is not None: if current is not None:
r_curr = model_to_dict(current) r_curr = model_to_dict(current)
db.close()
return r_curr return r_curr
else: else:
return None return None
@ -64,7 +65,7 @@ async def patch_current(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_current - Bad Token") logger.warning(f"patch_current - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -99,8 +100,10 @@ async def patch_current(
if current.save(): if current.save():
r_curr = model_to_dict(current) r_curr = model_to_dict(current)
db.close()
return r_curr return r_curr
else: else:
db.close()
raise HTTPException( raise HTTPException(
status_code=500, detail=f"Unable to patch current {current_id}" status_code=500, detail=f"Unable to patch current {current_id}"
) )
@ -110,15 +113,17 @@ async def patch_current(
@handle_db_errors @handle_db_errors
async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_scheme)): async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_current - Bad Token") logger.warning(f"patch_current - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_current = Current(**new_current.model_dump()) this_current = Current(**new_current.dict())
if this_current.save(): if this_current.save():
r_curr = model_to_dict(this_current) r_curr = model_to_dict(this_current)
db.close()
return r_curr return r_curr
else: else:
db.close()
raise HTTPException( raise HTTPException(
status_code=500, status_code=500,
detail=f"Unable to post season {new_current.season} current", detail=f"Unable to post season {new_current.season} current",
@ -129,7 +134,7 @@ async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_sc
@handle_db_errors @handle_db_errors
async def delete_current(current_id: int, token: str = Depends(oauth2_scheme)): async def delete_current(current_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_current - Bad Token") logger.warning(f"patch_current - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
if Current.delete_by_id(current_id) == 1: if Current.delete_by_id(current_id) == 1:

View File

@ -175,7 +175,7 @@ def delete_custom_command(command_id: int):
def get_creator_by_discord_id(discord_id: int): def get_creator_by_discord_id(discord_id: int):
"""Get a creator by Discord ID""" """Get a creator by Discord ID"""
creator = CustomCommandCreator.get_or_none( creator = CustomCommandCreator.get_or_none(
CustomCommandCreator.discord_id == discord_id CustomCommandCreator.discord_id == str(discord_id)
) )
if creator: if creator:
return model_to_dict(creator) return model_to_dict(creator)
@ -363,6 +363,8 @@ async def get_custom_commands(
except Exception as e: except Exception as e:
logger.error(f"Error getting custom commands: {e}") logger.error(f"Error getting custom commands: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
# Move this route to after the specific string routes # Move this route to after the specific string routes
@ -375,7 +377,7 @@ async def create_custom_command_endpoint(
): ):
"""Create a new custom command""" """Create a new custom command"""
if not valid_token(token): if not valid_token(token):
logger.warning("create_custom_command - Bad Token") logger.warning(f"create_custom_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -427,6 +429,8 @@ async def create_custom_command_endpoint(
except Exception as e: except Exception as e:
logger.error(f"Error creating custom command: {e}") logger.error(f"Error creating custom command: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.put("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA) @router.put("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@ -436,7 +440,7 @@ async def update_custom_command_endpoint(
): ):
"""Update an existing custom command""" """Update an existing custom command"""
if not valid_token(token): if not valid_token(token):
logger.warning("update_custom_command - Bad Token") logger.warning(f"update_custom_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -486,6 +490,8 @@ async def update_custom_command_endpoint(
except Exception as e: except Exception as e:
logger.error(f"Error updating custom command {command_id}: {e}") logger.error(f"Error updating custom command {command_id}: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.patch("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@ -502,7 +508,7 @@ async def patch_custom_command(
): ):
"""Partially update a custom command""" """Partially update a custom command"""
if not valid_token(token): if not valid_token(token):
logger.warning("patch_custom_command - Bad Token") logger.warning(f"patch_custom_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -569,6 +575,8 @@ async def patch_custom_command(
except Exception as e: except Exception as e:
logger.error(f"Error patching custom command {command_id}: {e}") logger.error(f"Error patching custom command {command_id}: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.delete("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@ -578,7 +586,7 @@ async def delete_custom_command_endpoint(
): ):
"""Delete a custom command""" """Delete a custom command"""
if not valid_token(token): if not valid_token(token):
logger.warning("delete_custom_command - Bad Token") logger.warning(f"delete_custom_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -604,6 +612,8 @@ async def delete_custom_command_endpoint(
except Exception as e: except Exception as e:
logger.error(f"Error deleting custom command {command_id}: {e}") logger.error(f"Error deleting custom command {command_id}: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
# Creator endpoints # Creator endpoints
@ -673,6 +683,8 @@ async def get_creators(
except Exception as e: except Exception as e:
logger.error(f"Error getting creators: {e}") logger.error(f"Error getting creators: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.post("/creators", include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/creators", include_in_schema=PRIVATE_IN_SCHEMA)
@ -682,7 +694,7 @@ async def create_creator_endpoint(
): ):
"""Create a new command creator""" """Create a new command creator"""
if not valid_token(token): if not valid_token(token):
logger.warning("create_creator - Bad Token") logger.warning(f"create_creator - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -716,6 +728,8 @@ async def create_creator_endpoint(
except Exception as e: except Exception as e:
logger.error(f"Error creating creator: {e}") logger.error(f"Error creating creator: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.get("/stats") @router.get("/stats")
@ -840,6 +854,8 @@ async def get_custom_command_stats():
except Exception as e: except Exception as e:
logger.error(f"Error getting custom command stats: {e}") logger.error(f"Error getting custom command stats: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
# Special endpoints for Discord bot integration # Special endpoints for Discord bot integration
@ -905,6 +921,8 @@ async def get_custom_command_by_name_endpoint(command_name: str):
except Exception as e: except Exception as e:
logger.error(f"Error getting custom command by name '{command_name}': {e}") logger.error(f"Error getting custom command by name '{command_name}': {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.patch("/by_name/{command_name}/execute", include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/by_name/{command_name}/execute", include_in_schema=PRIVATE_IN_SCHEMA)
@ -914,7 +932,7 @@ async def execute_custom_command(
): ):
"""Execute a custom command and update usage statistics""" """Execute a custom command and update usage statistics"""
if not valid_token(token): if not valid_token(token):
logger.warning("execute_custom_command - Bad Token") logger.warning(f"execute_custom_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -972,6 +990,8 @@ async def execute_custom_command(
except Exception as e: except Exception as e:
logger.error(f"Error executing custom command '{command_name}': {e}") logger.error(f"Error executing custom command '{command_name}': {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.get("/autocomplete") @router.get("/autocomplete")
@ -1007,6 +1027,8 @@ async def get_command_names_for_autocomplete(
except Exception as e: except Exception as e:
logger.error(f"Error getting command names for autocomplete: {e}") logger.error(f"Error getting command names for autocomplete: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.get("/{command_id}") @router.get("/{command_id}")
@ -1055,3 +1077,5 @@ async def get_custom_command(command_id: int):
except Exception as e: except Exception as e:
logger.error(f"Error getting custom command {command_id}: {e}") logger.error(f"Error getting custom command {command_id}: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()

View File

@ -143,6 +143,7 @@ async def get_decisions(
"count": all_dec.count(), "count": all_dec.count(),
"decisions": [model_to_dict(x, recurse=not short_output) for x in all_dec], "decisions": [model_to_dict(x, recurse=not short_output) for x in all_dec],
} }
db.close()
return return_dec return return_dec
@ -162,11 +163,12 @@ async def patch_decision(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_decision - Bad Token") logger.warning(f"patch_decision - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_dec = Decision.get_or_none(Decision.id == decision_id) this_dec = Decision.get_or_none(Decision.id == decision_id)
if this_dec is None: if this_dec is None:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, detail=f"Decision ID {decision_id} not found" status_code=404, detail=f"Decision ID {decision_id} not found"
) )
@ -192,8 +194,10 @@ async def patch_decision(
if this_dec.save() == 1: if this_dec.save() == 1:
d_result = model_to_dict(this_dec) d_result = model_to_dict(this_dec)
db.close()
return d_result return d_result
else: else:
db.close()
raise HTTPException( raise HTTPException(
status_code=500, detail=f"Unable to patch decision {decision_id}" status_code=500, detail=f"Unable to patch decision {decision_id}"
) )
@ -203,7 +207,7 @@ async def patch_decision(
@handle_db_errors @handle_db_errors
async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_scheme)): async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_decisions - Bad Token") logger.warning(f"post_decisions - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_dec = [] new_dec = []
@ -217,11 +221,12 @@ async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_sch
status_code=404, detail=f"Player ID {x.pitcher_id} not found" status_code=404, detail=f"Player ID {x.pitcher_id} not found"
) )
new_dec.append(x.model_dump()) new_dec.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_dec, 10): for batch in chunked(new_dec, 10):
Decision.insert_many(batch).on_conflict_ignore().execute() Decision.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Inserted {len(new_dec)} decisions" return f"Inserted {len(new_dec)} decisions"
@ -230,16 +235,18 @@ async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_sch
@handle_db_errors @handle_db_errors
async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme)): async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_decision - Bad Token") logger.warning(f"delete_decision - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_dec = Decision.get_or_none(Decision.id == decision_id) this_dec = Decision.get_or_none(Decision.id == decision_id)
if this_dec is None: if this_dec is None:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, detail=f"Decision ID {decision_id} not found" status_code=404, detail=f"Decision ID {decision_id} not found"
) )
count = this_dec.delete_instance() count = this_dec.delete_instance()
db.close()
if count == 1: if count == 1:
return f"Decision {decision_id} has been deleted" return f"Decision {decision_id} has been deleted"
@ -253,14 +260,16 @@ async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme))
@handle_db_errors @handle_db_errors
async def delete_decisions_game(game_id: int, token: str = Depends(oauth2_scheme)): async def delete_decisions_game(game_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_decisions_game - Bad Token") logger.warning(f"delete_decisions_game - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_game = StratGame.get_or_none(StratGame.id == game_id) this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game: if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f"Game ID {game_id} not found") raise HTTPException(status_code=404, detail=f"Game ID {game_id} not found")
count = Decision.delete().where(Decision.game == this_game).execute() count = Decision.delete().where(Decision.game == this_game).execute()
db.close()
if count > 0: if count > 0:
return f"Deleted {count} decisions matching Game ID {game_id}" return f"Deleted {count} decisions matching Game ID {game_id}"

View File

@ -55,6 +55,7 @@ async def get_divisions(
"count": total_count, "count": total_count,
"divisions": [model_to_dict(x) for x in all_divisions], "divisions": [model_to_dict(x) for x in all_divisions],
} }
db.close()
return return_div return return_div
@ -63,11 +64,13 @@ async def get_divisions(
async def get_one_division(division_id: int): async def get_one_division(division_id: int):
this_div = Division.get_or_none(Division.id == division_id) this_div = Division.get_or_none(Division.id == division_id)
if this_div is None: if this_div is None:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, detail=f"Division ID {division_id} not found" status_code=404, detail=f"Division ID {division_id} not found"
) )
r_div = model_to_dict(this_div) r_div = model_to_dict(this_div)
db.close()
return r_div return r_div
@ -82,11 +85,12 @@ async def patch_division(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_division - Bad Token") logger.warning(f"patch_division - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_div = Division.get_or_none(Division.id == division_id) this_div = Division.get_or_none(Division.id == division_id)
if this_div is None: if this_div is None:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, detail=f"Division ID {division_id} not found" status_code=404, detail=f"Division ID {division_id} not found"
) )
@ -102,8 +106,10 @@ async def patch_division(
if this_div.save() == 1: if this_div.save() == 1:
r_division = model_to_dict(this_div) r_division = model_to_dict(this_div)
db.close()
return r_division return r_division
else: else:
db.close()
raise HTTPException( raise HTTPException(
status_code=500, detail=f"Unable to patch division {division_id}" status_code=500, detail=f"Unable to patch division {division_id}"
) )
@ -115,15 +121,17 @@ async def post_division(
new_division: DivisionModel, token: str = Depends(oauth2_scheme) new_division: DivisionModel, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("post_division - Bad Token") logger.warning(f"post_division - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_division = Division(**new_division.model_dump()) this_division = Division(**new_division.dict())
if this_division.save() == 1: if this_division.save() == 1:
r_division = model_to_dict(this_division) r_division = model_to_dict(this_division)
db.close()
return r_division return r_division
else: else:
db.close()
raise HTTPException(status_code=500, detail=f"Unable to post division") raise HTTPException(status_code=500, detail=f"Unable to post division")
@ -131,16 +139,18 @@ async def post_division(
@handle_db_errors @handle_db_errors
async def delete_division(division_id: int, token: str = Depends(oauth2_scheme)): async def delete_division(division_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_division - Bad Token") logger.warning(f"delete_division - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_div = Division.get_or_none(Division.id == division_id) this_div = Division.get_or_none(Division.id == division_id)
if this_div is None: if this_div is None:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, detail=f"Division ID {division_id} not found" status_code=404, detail=f"Division ID {division_id} not found"
) )
count = this_div.delete_instance() count = this_div.delete_instance()
db.close()
if count == 1: if count == 1:
return f"Division {division_id} has been deleted" return f"Division {division_id} has been deleted"

View File

@ -32,6 +32,7 @@ async def get_draftdata():
if draft_data is not None: if draft_data is not None:
r_data = model_to_dict(draft_data) r_data = model_to_dict(draft_data)
db.close()
return r_data return r_data
raise HTTPException(status_code=404, detail=f'No draft data found') raise HTTPException(status_code=404, detail=f'No draft data found')
@ -44,11 +45,12 @@ async def patch_draftdata(
pick_deadline: Optional[datetime.datetime] = None, result_channel: Optional[int] = None, pick_deadline: Optional[datetime.datetime] = None, result_channel: Optional[int] = None,
ping_channel: Optional[int] = None, pick_minutes: Optional[int] = None, token: str = Depends(oauth2_scheme)): ping_channel: Optional[int] = None, pick_minutes: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning('patch_draftdata - Bad Token') logger.warning(f'patch_draftdata - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail='Unauthorized')
draft_data = DraftData.get_or_none(DraftData.id == data_id) draft_data = DraftData.get_or_none(DraftData.id == data_id)
if draft_data is None: if draft_data is None:
db.close()
raise HTTPException(status_code=404, detail=f'No draft data found') raise HTTPException(status_code=404, detail=f'No draft data found')
if currentpick is not None: if currentpick is not None:
@ -66,6 +68,7 @@ async def patch_draftdata(
saved = draft_data.save() saved = draft_data.save()
r_data = model_to_dict(draft_data) r_data = model_to_dict(draft_data)
db.close()
if saved == 1: if saved == 1:
return r_data return r_data

View File

@ -40,7 +40,7 @@ async def get_draftlist(
offset: int = Query(default=0, ge=0), offset: int = Query(default=0, ge=0),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("get_draftlist - Bad Token") logger.warning(f"get_draftlist - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
all_list = DraftList.select() all_list = DraftList.select()
@ -55,6 +55,7 @@ async def get_draftlist(
r_list = {"count": total_count, "picks": [model_to_dict(x) for x in all_list]} r_list = {"count": total_count, "picks": [model_to_dict(x) for x in all_list]}
db.close()
return r_list return r_list
@ -62,7 +63,7 @@ async def get_draftlist(
@handle_db_errors @handle_db_errors
async def get_team_draftlist(team_id: int, token: str = Depends(oauth2_scheme)): async def get_team_draftlist(team_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_draftlist - Bad Token") logger.warning(f"post_draftlist - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_team = Team.get_or_none(Team.id == team_id) this_team = Team.get_or_none(Team.id == team_id)
@ -75,6 +76,7 @@ async def get_team_draftlist(team_id: int, token: str = Depends(oauth2_scheme)):
"picks": [model_to_dict(x) for x in this_list], "picks": [model_to_dict(x) for x in this_list],
} }
db.close()
return r_list return r_list
@ -84,7 +86,7 @@ async def post_draftlist(
draft_list: DraftListList, token: str = Depends(oauth2_scheme) draft_list: DraftListList, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("post_draftlist - Bad Token") logger.warning(f"post_draftlist - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_list = [] new_list = []
@ -98,12 +100,13 @@ async def post_draftlist(
DraftList.delete().where(DraftList.team == this_team).execute() DraftList.delete().where(DraftList.team == this_team).execute()
for x in draft_list.draft_list: for x in draft_list.draft_list:
new_list.append(x.model_dump()) new_list.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_list, 15): for batch in chunked(new_list, 15):
DraftList.insert_many(batch).on_conflict_ignore().execute() DraftList.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Inserted {len(new_list)} list values" return f"Inserted {len(new_list)} list values"
@ -111,8 +114,9 @@ async def post_draftlist(
@handle_db_errors @handle_db_errors
async def delete_draftlist(team_id: int, token: str = Depends(oauth2_scheme)): async def delete_draftlist(team_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_draftlist - Bad Token") logger.warning(f"delete_draftlist - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
count = DraftList.delete().where(DraftList.team_id == team_id).execute() count = DraftList.delete().where(DraftList.team_id == team_id).execute()
db.close()
return f"Deleted {count} list values" return f"Deleted {count} list values"

View File

@ -124,6 +124,7 @@ async def get_picks(
for line in all_picks: for line in all_picks:
return_picks["picks"].append(model_to_dict(line, recurse=not short_output)) return_picks["picks"].append(model_to_dict(line, recurse=not short_output))
db.close()
return return_picks return return_picks
@ -135,6 +136,7 @@ async def get_one_pick(pick_id: int, short_output: Optional[bool] = False):
r_pick = model_to_dict(this_pick, recurse=not short_output) r_pick = model_to_dict(this_pick, recurse=not short_output)
else: else:
raise HTTPException(status_code=404, detail=f"Pick ID {pick_id} not found") raise HTTPException(status_code=404, detail=f"Pick ID {pick_id} not found")
db.close()
return r_pick return r_pick
@ -144,14 +146,15 @@ async def patch_pick(
pick_id: int, new_pick: DraftPickModel, token: str = Depends(oauth2_scheme) pick_id: int, new_pick: DraftPickModel, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_pick - Bad Token") logger.warning(f"patch_pick - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
if DraftPick.get_or_none(DraftPick.id == pick_id) is None: if DraftPick.get_or_none(DraftPick.id == pick_id) is None:
raise HTTPException(status_code=404, detail=f"Pick ID {pick_id} not found") raise HTTPException(status_code=404, detail=f"Pick ID {pick_id} not found")
DraftPick.update(**new_pick.model_dump()).where(DraftPick.id == pick_id).execute() DraftPick.update(**new_pick.dict()).where(DraftPick.id == pick_id).execute()
r_pick = model_to_dict(DraftPick.get_by_id(pick_id)) r_pick = model_to_dict(DraftPick.get_by_id(pick_id))
db.close()
return r_pick return r_pick
@ -159,7 +162,7 @@ async def patch_pick(
@handle_db_errors @handle_db_errors
async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme)): async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_picks - Bad Token") logger.warning(f"post_picks - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_picks = [] new_picks = []
@ -168,16 +171,18 @@ async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme))
DraftPick.season == pick.season, DraftPick.overall == pick.overall DraftPick.season == pick.season, DraftPick.overall == pick.overall
) )
if dupe: if dupe:
db.close()
raise HTTPException( raise HTTPException(
status_code=500, status_code=500,
detail=f"Pick # {pick.overall} already exists for season {pick.season}", detail=f"Pick # {pick.overall} already exists for season {pick.season}",
) )
new_picks.append(pick.model_dump()) new_picks.append(pick.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_picks, 15): for batch in chunked(new_picks, 15):
DraftPick.insert_many(batch).on_conflict_ignore().execute() DraftPick.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Inserted {len(new_picks)} picks" return f"Inserted {len(new_picks)} picks"
@ -186,7 +191,7 @@ async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme))
@handle_db_errors @handle_db_errors
async def delete_pick(pick_id: int, token: str = Depends(oauth2_scheme)): async def delete_pick(pick_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_pick - Bad Token") logger.warning(f"delete_pick - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_pick = DraftPick.get_or_none(DraftPick.id == pick_id) this_pick = DraftPick.get_or_none(DraftPick.id == pick_id)
@ -194,6 +199,7 @@ async def delete_pick(pick_id: int, token: str = Depends(oauth2_scheme)):
raise HTTPException(status_code=404, detail=f"Pick ID {pick_id} not found") raise HTTPException(status_code=404, detail=f"Pick ID {pick_id} not found")
count = this_pick.delete_instance() count = this_pick.delete_instance()
db.close()
if count == 1: if count == 1:
return f"Draft pick {pick_id} has been deleted" return f"Draft pick {pick_id} has been deleted"

View File

@ -46,14 +46,17 @@ async def get_fieldingstats(
if "post" in s_type.lower(): if "post" in s_type.lower():
all_stats = BattingStat.post_season(season) all_stats = BattingStat.post_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close()
return {"count": 0, "stats": []} return {"count": 0, "stats": []}
elif s_type.lower() in ["combined", "total", "all"]: elif s_type.lower() in ["combined", "total", "all"]:
all_stats = BattingStat.combined_season(season) all_stats = BattingStat.combined_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close()
return {"count": 0, "stats": []} return {"count": 0, "stats": []}
else: else:
all_stats = BattingStat.regular_season(season) all_stats = BattingStat.regular_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close()
return {"count": 0, "stats": []} return {"count": 0, "stats": []}
all_stats = all_stats.where( all_stats = all_stats.where(
@ -83,6 +86,7 @@ async def get_fieldingstats(
if week_end is not None: if week_end is not None:
end = min(week_end, end) end = min(week_end, end)
if start > end: if start > end:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, status_code=404,
detail=f"Start week {start} is after end week {end} - cannot pull stats", detail=f"Start week {start} is after end week {end} - cannot pull stats",
@ -120,6 +124,7 @@ async def get_fieldingstats(
], ],
} }
db.close()
return return_stats return return_stats
@ -276,4 +281,6 @@ async def get_totalstats(
} }
) )
return_stats["count"] = len(return_stats["stats"])
db.close()
return return_stats return return_stats

View File

@ -138,6 +138,8 @@ async def get_help_commands(
except Exception as e: except Exception as e:
logger.error(f"Error getting help commands: {e}") logger.error(f"Error getting help commands: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@ -147,7 +149,7 @@ async def create_help_command_endpoint(
): ):
"""Create a new help command""" """Create a new help command"""
if not valid_token(token): if not valid_token(token):
logger.warning("create_help_command - Bad Token") logger.warning(f"create_help_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -185,6 +187,8 @@ async def create_help_command_endpoint(
except Exception as e: except Exception as e:
logger.error(f"Error creating help command: {e}") logger.error(f"Error creating help command: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.put("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA) @router.put("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@ -194,7 +198,7 @@ async def update_help_command_endpoint(
): ):
"""Update an existing help command""" """Update an existing help command"""
if not valid_token(token): if not valid_token(token):
logger.warning("update_help_command - Bad Token") logger.warning(f"update_help_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -234,6 +238,8 @@ async def update_help_command_endpoint(
except Exception as e: except Exception as e:
logger.error(f"Error updating help command {command_id}: {e}") logger.error(f"Error updating help command {command_id}: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.patch("/{command_id}/restore", include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{command_id}/restore", include_in_schema=PRIVATE_IN_SCHEMA)
@ -243,7 +249,7 @@ async def restore_help_command_endpoint(
): ):
"""Restore a soft-deleted help command""" """Restore a soft-deleted help command"""
if not valid_token(token): if not valid_token(token):
logger.warning("restore_help_command - Bad Token") logger.warning(f"restore_help_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -271,6 +277,8 @@ async def restore_help_command_endpoint(
except Exception as e: except Exception as e:
logger.error(f"Error restoring help command {command_id}: {e}") logger.error(f"Error restoring help command {command_id}: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.delete("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@ -280,7 +288,7 @@ async def delete_help_command_endpoint(
): ):
"""Soft delete a help command""" """Soft delete a help command"""
if not valid_token(token): if not valid_token(token):
logger.warning("delete_help_command - Bad Token") logger.warning(f"delete_help_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -301,6 +309,8 @@ async def delete_help_command_endpoint(
except Exception as e: except Exception as e:
logger.error(f"Error deleting help command {command_id}: {e}") logger.error(f"Error deleting help command {command_id}: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.get("/stats") @router.get("/stats")
@ -358,6 +368,8 @@ async def get_help_command_stats():
except Exception as e: except Exception as e:
logger.error(f"Error getting help command stats: {e}") logger.error(f"Error getting help command stats: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
# Special endpoints for Discord bot integration # Special endpoints for Discord bot integration
@ -390,6 +402,8 @@ async def get_help_command_by_name_endpoint(
except Exception as e: except Exception as e:
logger.error(f"Error getting help command by name '{command_name}': {e}") logger.error(f"Error getting help command by name '{command_name}': {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.patch("/by_name/{command_name}/view", include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/by_name/{command_name}/view", include_in_schema=PRIVATE_IN_SCHEMA)
@ -397,7 +411,7 @@ async def get_help_command_by_name_endpoint(
async def increment_view_count(command_name: str, token: str = Depends(oauth2_scheme)): async def increment_view_count(command_name: str, token: str = Depends(oauth2_scheme)):
"""Increment view count for a help command""" """Increment view count for a help command"""
if not valid_token(token): if not valid_token(token):
logger.warning("increment_view_count - Bad Token") logger.warning(f"increment_view_count - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -425,6 +439,8 @@ async def increment_view_count(command_name: str, token: str = Depends(oauth2_sc
except Exception as e: except Exception as e:
logger.error(f"Error incrementing view count for '{command_name}': {e}") logger.error(f"Error incrementing view count for '{command_name}': {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.get("/autocomplete") @router.get("/autocomplete")
@ -454,6 +470,8 @@ async def get_help_names_for_autocomplete(
except Exception as e: except Exception as e:
logger.error(f"Error getting help names for autocomplete: {e}") logger.error(f"Error getting help names for autocomplete: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@router.get("/{command_id}") @router.get("/{command_id}")
@ -481,3 +499,5 @@ async def get_help_command(command_id: int):
except Exception as e: except Exception as e:
logger.error(f"Error getting help command {command_id}: {e}") logger.error(f"Error getting help command {command_id}: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()

View File

@ -75,6 +75,7 @@ async def get_injuries(
"count": total_count, "count": total_count,
"injuries": [model_to_dict(x, recurse=not short_output) for x in all_injuries], "injuries": [model_to_dict(x, recurse=not short_output) for x in all_injuries],
} }
db.close()
return return_injuries return return_injuries
@ -86,11 +87,12 @@ async def patch_injury(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_injury - Bad Token") logger.warning(f"patch_injury - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_injury = Injury.get_or_none(Injury.id == injury_id) this_injury = Injury.get_or_none(Injury.id == injury_id)
if this_injury is None: if this_injury is None:
db.close()
raise HTTPException(status_code=404, detail=f"Injury ID {injury_id} not found") raise HTTPException(status_code=404, detail=f"Injury ID {injury_id} not found")
if is_active is not None: if is_active is not None:
@ -98,8 +100,10 @@ async def patch_injury(
if this_injury.save() == 1: if this_injury.save() == 1:
r_injury = model_to_dict(this_injury) r_injury = model_to_dict(this_injury)
db.close()
return r_injury return r_injury
else: else:
db.close()
raise HTTPException( raise HTTPException(
status_code=500, detail=f"Unable to patch injury {injury_id}" status_code=500, detail=f"Unable to patch injury {injury_id}"
) )
@ -109,15 +113,17 @@ async def patch_injury(
@handle_db_errors @handle_db_errors
async def post_injury(new_injury: InjuryModel, token: str = Depends(oauth2_scheme)): async def post_injury(new_injury: InjuryModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_injury - Bad Token") logger.warning(f"post_injury - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_injury = Injury(**new_injury.model_dump()) this_injury = Injury(**new_injury.dict())
if this_injury.save(): if this_injury.save():
r_injury = model_to_dict(this_injury) r_injury = model_to_dict(this_injury)
db.close()
return r_injury return r_injury
else: else:
db.close()
raise HTTPException(status_code=500, detail=f"Unable to post injury") raise HTTPException(status_code=500, detail=f"Unable to post injury")
@ -125,14 +131,16 @@ async def post_injury(new_injury: InjuryModel, token: str = Depends(oauth2_schem
@handle_db_errors @handle_db_errors
async def delete_injury(injury_id: int, token: str = Depends(oauth2_scheme)): async def delete_injury(injury_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_injury - Bad Token") logger.warning(f"delete_injury - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_injury = Injury.get_or_none(Injury.id == injury_id) this_injury = Injury.get_or_none(Injury.id == injury_id)
if this_injury is None: if this_injury is None:
db.close()
raise HTTPException(status_code=404, detail=f"Injury ID {injury_id} not found") raise HTTPException(status_code=404, detail=f"Injury ID {injury_id} not found")
count = this_injury.delete_instance() count = this_injury.delete_instance()
db.close()
if count == 1: if count == 1:
return f"Injury {injury_id} has been deleted" return f"Injury {injury_id} has been deleted"

View File

@ -55,6 +55,7 @@ async def get_keepers(
"count": total_count, "count": total_count,
"keepers": [model_to_dict(x, recurse=not short_output) for x in all_keepers], "keepers": [model_to_dict(x, recurse=not short_output) for x in all_keepers],
} }
db.close()
return return_keepers return return_keepers
@ -68,7 +69,7 @@ async def patch_keeper(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_keeper - Bad Token") logger.warning(f"patch_keeper - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_keeper = Keeper.get_or_none(Keeper.id == keeper_id) this_keeper = Keeper.get_or_none(Keeper.id == keeper_id)
@ -84,8 +85,10 @@ async def patch_keeper(
if this_keeper.save(): if this_keeper.save():
r_keeper = model_to_dict(this_keeper) r_keeper = model_to_dict(this_keeper)
db.close()
return r_keeper return r_keeper
else: else:
db.close()
raise HTTPException( raise HTTPException(
status_code=500, detail=f"Unable to patch keeper {keeper_id}" status_code=500, detail=f"Unable to patch keeper {keeper_id}"
) )
@ -95,16 +98,17 @@ async def patch_keeper(
@handle_db_errors @handle_db_errors
async def post_keepers(k_list: KeeperList, token: str = Depends(oauth2_scheme)): async def post_keepers(k_list: KeeperList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_keepers - Bad Token") logger.warning(f"post_keepers - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_keepers = [] new_keepers = []
for keeper in k_list.keepers: for keeper in k_list.keepers:
new_keepers.append(keeper.model_dump()) new_keepers.append(keeper.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_keepers, 14): for batch in chunked(new_keepers, 14):
Keeper.insert_many(batch).on_conflict_ignore().execute() Keeper.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Inserted {len(new_keepers)} keepers" return f"Inserted {len(new_keepers)} keepers"
@ -113,7 +117,7 @@ async def post_keepers(k_list: KeeperList, token: str = Depends(oauth2_scheme)):
@handle_db_errors @handle_db_errors
async def delete_keeper(keeper_id: int, token: str = Depends(oauth2_scheme)): async def delete_keeper(keeper_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_keeper - Bad Token") logger.warning(f"delete_keeper - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_keeper = Keeper.get_or_none(Keeper.id == keeper_id) this_keeper = Keeper.get_or_none(Keeper.id == keeper_id)
@ -121,6 +125,7 @@ async def delete_keeper(keeper_id: int, token: str = Depends(oauth2_scheme)):
raise HTTPException(status_code=404, detail=f"Keeper ID {keeper_id} not found") raise HTTPException(status_code=404, detail=f"Keeper ID {keeper_id} not found")
count = this_keeper.delete_instance() count = this_keeper.delete_instance()
db.close()
if count == 1: if count == 1:
return f"Keeper ID {keeper_id} has been deleted" return f"Keeper ID {keeper_id} has been deleted"

View File

@ -84,6 +84,7 @@ async def get_managers(
], ],
} }
db.close()
return return_managers return return_managers
@ -93,6 +94,7 @@ async def get_one_manager(manager_id: int, short_output: Optional[bool] = False)
this_manager = Manager.get_or_none(Manager.id == manager_id) this_manager = Manager.get_or_none(Manager.id == manager_id)
if this_manager is not None: if this_manager is not None:
r_manager = model_to_dict(this_manager, recurse=not short_output) r_manager = model_to_dict(this_manager, recurse=not short_output)
db.close()
return r_manager return r_manager
else: else:
raise HTTPException(status_code=404, detail=f"Manager {manager_id} not found") raise HTTPException(status_code=404, detail=f"Manager {manager_id} not found")
@ -109,11 +111,12 @@ async def patch_manager(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_manager - Bad Token") logger.warning(f"patch_manager - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_manager = Manager.get_or_none(Manager.id == manager_id) this_manager = Manager.get_or_none(Manager.id == manager_id)
if this_manager is None: if this_manager is None:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, detail=f"Manager ID {manager_id} not found" status_code=404, detail=f"Manager ID {manager_id} not found"
) )
@ -129,8 +132,10 @@ async def patch_manager(
if this_manager.save() == 1: if this_manager.save() == 1:
r_manager = model_to_dict(this_manager) r_manager = model_to_dict(this_manager)
db.close()
return r_manager return r_manager
else: else:
db.close()
raise HTTPException( raise HTTPException(
status_code=500, detail=f"Unable to patch manager {this_manager}" status_code=500, detail=f"Unable to patch manager {this_manager}"
) )
@ -140,15 +145,17 @@ async def patch_manager(
@handle_db_errors @handle_db_errors
async def post_manager(new_manager: ManagerModel, token: str = Depends(oauth2_scheme)): async def post_manager(new_manager: ManagerModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_manager - Bad Token") logger.warning(f"post_manager - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_manager = Manager(**new_manager.model_dump()) this_manager = Manager(**new_manager.dict())
if this_manager.save(): if this_manager.save():
r_manager = model_to_dict(this_manager) r_manager = model_to_dict(this_manager)
db.close()
return r_manager return r_manager
else: else:
db.close()
raise HTTPException( raise HTTPException(
status_code=500, detail=f"Unable to post manager {this_manager.name}" status_code=500, detail=f"Unable to post manager {this_manager.name}"
) )
@ -158,16 +165,18 @@ async def post_manager(new_manager: ManagerModel, token: str = Depends(oauth2_sc
@handle_db_errors @handle_db_errors
async def delete_manager(manager_id: int, token: str = Depends(oauth2_scheme)): async def delete_manager(manager_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_manager - Bad Token") logger.warning(f"delete_manager - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_manager = Manager.get_or_none(Manager.id == manager_id) this_manager = Manager.get_or_none(Manager.id == manager_id)
if this_manager is None: if this_manager is None:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, detail=f"Manager ID {manager_id} not found" status_code=404, detail=f"Manager ID {manager_id} not found"
) )
count = this_manager.delete_instance() count = this_manager.delete_instance()
db.close()
if count == 1: if count == 1:
return f"Manager {manager_id} has been deleted" return f"Manager {manager_id} has been deleted"

View File

@ -78,14 +78,17 @@ async def get_pitstats(
if "post" in s_type.lower(): if "post" in s_type.lower():
all_stats = PitchingStat.post_season(season) all_stats = PitchingStat.post_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close()
return {"count": 0, "stats": []} return {"count": 0, "stats": []}
elif s_type.lower() in ["combined", "total", "all"]: elif s_type.lower() in ["combined", "total", "all"]:
all_stats = PitchingStat.combined_season(season) all_stats = PitchingStat.combined_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close()
return {"count": 0, "stats": []} return {"count": 0, "stats": []}
else: else:
all_stats = PitchingStat.regular_season(season) all_stats = PitchingStat.regular_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close()
return {"count": 0, "stats": []} return {"count": 0, "stats": []}
if team_abbrev is not None: if team_abbrev is not None:
@ -111,6 +114,7 @@ async def get_pitstats(
if week_end is not None: if week_end is not None:
end = min(week_end, end) end = min(week_end, end)
if start > end: if start > end:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, status_code=404,
detail=f"Start week {start} is after end week {end} - cannot pull stats", detail=f"Start week {start} is after end week {end} - cannot pull stats",
@ -129,6 +133,7 @@ async def get_pitstats(
"stats": [model_to_dict(x, recurse=not short_output) for x in all_stats], "stats": [model_to_dict(x, recurse=not short_output) for x in all_stats],
} }
db.close()
return return_stats return return_stats
@ -302,6 +307,7 @@ async def get_totalstats(
"bsv": x.sum_bsv, "bsv": x.sum_bsv,
} }
) )
db.close()
return return_stats return return_stats
@ -311,16 +317,15 @@ async def patch_pitstats(
stat_id: int, new_stats: PitStatModel, token: str = Depends(oauth2_scheme) stat_id: int, new_stats: PitStatModel, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_pitstats - Bad Token") logger.warning(f"patch_pitstats - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
if PitchingStat.get_or_none(PitchingStat.id == stat_id) is None: if PitchingStat.get_or_none(PitchingStat.id == stat_id) is None:
raise HTTPException(status_code=404, detail=f"Stat ID {stat_id} not found") raise HTTPException(status_code=404, detail=f"Stat ID {stat_id} not found")
PitchingStat.update(**new_stats.model_dump()).where( PitchingStat.update(**new_stats.dict()).where(PitchingStat.id == stat_id).execute()
PitchingStat.id == stat_id
).execute()
r_stat = model_to_dict(PitchingStat.get_by_id(stat_id)) r_stat = model_to_dict(PitchingStat.get_by_id(stat_id))
db.close()
return r_stat return r_stat
@ -328,7 +333,7 @@ async def patch_pitstats(
@handle_db_errors @handle_db_errors
async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme)): async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_pitstats - Bad Token") logger.warning(f"post_pitstats - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
all_stats = [] all_stats = []
@ -345,10 +350,11 @@ async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme)
status_code=404, detail=f"Player ID {x.player_id} not found" status_code=404, detail=f"Player ID {x.player_id} not found"
) )
all_stats.append(PitchingStat(**x.model_dump())) all_stats.append(PitchingStat(**x.dict()))
with db.atomic(): with db.atomic():
for batch in chunked(all_stats, 15): for batch in chunked(all_stats, 15):
PitchingStat.insert_many(batch).on_conflict_ignore().execute() PitchingStat.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Added {len(all_stats)} batting lines" return f"Added {len(all_stats)} batting lines"

View File

@ -4,7 +4,7 @@ Thin HTTP layer using PlayerService for business logic.
""" """
from fastapi import APIRouter, Query, Response, Depends from fastapi import APIRouter, Query, Response, Depends
from typing import Literal, Optional, List from typing import Optional, List
from ..dependencies import ( from ..dependencies import (
oauth2_scheme, oauth2_scheme,
@ -27,10 +27,8 @@ async def get_players(
pos: list = Query(default=None), pos: list = Query(default=None),
strat_code: list = Query(default=None), strat_code: list = Query(default=None),
is_injured: Optional[bool] = None, is_injured: Optional[bool] = None,
sort: Optional[Literal["cost-asc", "cost-desc", "name-asc", "name-desc"]] = None, sort: Optional[str] = None,
limit: Optional[int] = Query( limit: Optional[int] = Query(default=None, ge=1),
default=None, ge=1, description="Maximum number of results to return"
),
offset: Optional[int] = Query( offset: Optional[int] = Query(
default=None, ge=0, description="Number of results to skip for pagination" default=None, ge=0, description="Number of results to skip for pagination"
), ),

View File

@ -85,6 +85,7 @@ async def get_results(
"count": total_count, "count": total_count,
"results": [model_to_dict(x, recurse=not short_output) for x in all_results], "results": [model_to_dict(x, recurse=not short_output) for x in all_results],
} }
db.close()
return return_results return return_results
@ -96,6 +97,7 @@ async def get_one_result(result_id: int, short_output: Optional[bool] = False):
r_result = model_to_dict(this_result, recurse=not short_output) r_result = model_to_dict(this_result, recurse=not short_output)
else: else:
r_result = None r_result = None
db.close()
return r_result return r_result
@ -114,7 +116,7 @@ async def patch_result(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_player - Bad Token") logger.warning(f"patch_player - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_result = Result.get_or_none(Result.id == result_id) this_result = Result.get_or_none(Result.id == result_id)
@ -147,8 +149,10 @@ async def patch_result(
if this_result.save() == 1: if this_result.save() == 1:
r_result = model_to_dict(this_result) r_result = model_to_dict(this_result)
db.close()
return r_result return r_result
else: else:
db.close()
raise HTTPException( raise HTTPException(
status_code=500, detail=f"Unable to patch result {result_id}" status_code=500, detail=f"Unable to patch result {result_id}"
) )
@ -158,7 +162,7 @@ async def patch_result(
@handle_db_errors @handle_db_errors
async def post_results(result_list: ResultList, token: str = Depends(oauth2_scheme)): async def post_results(result_list: ResultList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_player - Bad Token") logger.warning(f"patch_player - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_results = [] new_results = []
@ -183,11 +187,12 @@ async def post_results(result_list: ResultList, token: str = Depends(oauth2_sche
status_code=404, detail=f"Team ID {x.hometeam_id} not found" status_code=404, detail=f"Team ID {x.hometeam_id} not found"
) )
new_results.append(x.model_dump()) new_results.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_results, 15): for batch in chunked(new_results, 15):
Result.insert_many(batch).on_conflict_ignore().execute() Result.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Inserted {len(new_results)} results" return f"Inserted {len(new_results)} results"
@ -196,14 +201,16 @@ async def post_results(result_list: ResultList, token: str = Depends(oauth2_sche
@handle_db_errors @handle_db_errors
async def delete_result(result_id: int, token: str = Depends(oauth2_scheme)): async def delete_result(result_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_result - Bad Token") logger.warning(f"delete_result - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_result = Result.get_or_none(Result.id == result_id) this_result = Result.get_or_none(Result.id == result_id)
if not this_result: if not this_result:
db.close()
raise HTTPException(status_code=404, detail=f"Result ID {result_id} not found") raise HTTPException(status_code=404, detail=f"Result ID {result_id} not found")
count = this_result.delete_instance() count = this_result.delete_instance()
db.close()
if count == 1: if count == 1:
return f"Result {result_id} has been deleted" return f"Result {result_id} has been deleted"

View File

@ -102,6 +102,7 @@ async def get_players(
if csv: if csv:
return_val = query_to_csv(all_players) return_val = query_to_csv(all_players)
db.close()
return Response(content=return_val, media_type="text/csv") return Response(content=return_val, media_type="text/csv")
total_count = all_players.count() total_count = all_players.count()
@ -111,6 +112,7 @@ async def get_players(
"count": total_count, "count": total_count,
"players": [model_to_dict(x) for x in all_players], "players": [model_to_dict(x) for x in all_players],
} }
db.close()
return return_val return return_val
@ -119,11 +121,13 @@ async def get_players(
async def get_one_player(player_id: int): async def get_one_player(player_id: int):
this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id) this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id)
if this_player is None: if this_player is None:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, detail=f"SbaPlayer id {player_id} not found" status_code=404, detail=f"SbaPlayer id {player_id} not found"
) )
r_data = model_to_dict(this_player) r_data = model_to_dict(this_player)
db.close()
return r_data return r_data
@ -140,7 +144,8 @@ async def patch_player(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logging.warning("Bad Token") logging.warning(f"Bad Token: {token}")
db.close()
raise HTTPException( raise HTTPException(
status_code=401, status_code=401,
detail="You are not authorized to patch mlb players. This event has been logged.", detail="You are not authorized to patch mlb players. This event has been logged.",
@ -148,6 +153,7 @@ async def patch_player(
this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id) this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id)
if this_player is None: if this_player is None:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, detail=f"SbaPlayer id {player_id} not found" status_code=404, detail=f"SbaPlayer id {player_id} not found"
) )
@ -167,8 +173,10 @@ async def patch_player(
if this_player.save() == 1: if this_player.save() == 1:
return_val = model_to_dict(this_player) return_val = model_to_dict(this_player)
db.close()
return return_val return return_val
else: else:
db.close()
raise HTTPException( raise HTTPException(
status_code=418, status_code=418,
detail="Well slap my ass and call me a teapot; I could not save that player", detail="Well slap my ass and call me a teapot; I could not save that player",
@ -179,7 +187,8 @@ async def patch_player(
@handle_db_errors @handle_db_errors
async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme)): async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logging.warning("Bad Token") logging.warning(f"Bad Token: {token}")
db.close()
raise HTTPException( raise HTTPException(
status_code=401, status_code=401,
detail="You are not authorized to post mlb players. This event has been logged.", detail="You are not authorized to post mlb players. This event has been logged.",
@ -198,6 +207,7 @@ async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme))
) )
if dupes.count() > 0: if dupes.count() > 0:
logger.error(f"Found a dupe for {x}") logger.error(f"Found a dupe for {x}")
db.close()
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail=f"{x.first_name} {x.last_name} has a key already in the database", detail=f"{x.first_name} {x.last_name} has a key already in the database",
@ -208,6 +218,7 @@ async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme))
with db.atomic(): with db.atomic():
for batch in chunked(new_players, 15): for batch in chunked(new_players, 15):
SbaPlayer.insert_many(batch).on_conflict_ignore().execute() SbaPlayer.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Inserted {len(new_players)} new MLB players" return f"Inserted {len(new_players)} new MLB players"
@ -216,7 +227,8 @@ async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme))
@handle_db_errors @handle_db_errors
async def post_one_player(player: SbaPlayerModel, token: str = Depends(oauth2_scheme)): async def post_one_player(player: SbaPlayerModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logging.warning("Bad Token") logging.warning(f"Bad Token: {token}")
db.close()
raise HTTPException( raise HTTPException(
status_code=401, status_code=401,
detail="You are not authorized to post mlb players. This event has been logged.", detail="You are not authorized to post mlb players. This event has been logged.",
@ -231,17 +243,20 @@ async def post_one_player(player: SbaPlayerModel, token: str = Depends(oauth2_sc
logging.info(f"POST /SbaPlayers/one - dupes found:") logging.info(f"POST /SbaPlayers/one - dupes found:")
for x in dupes: for x in dupes:
logging.info(f"{x}") logging.info(f"{x}")
db.close()
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail=f"{player.first_name} {player.last_name} has a key already in the database", detail=f"{player.first_name} {player.last_name} has a key already in the database",
) )
new_player = SbaPlayer(**player.model_dump()) new_player = SbaPlayer(**player.dict())
saved = new_player.save() saved = new_player.save()
if saved == 1: if saved == 1:
return_val = model_to_dict(new_player) return_val = model_to_dict(new_player)
db.close()
return return_val return return_val
else: else:
db.close()
raise HTTPException( raise HTTPException(
status_code=418, status_code=418,
detail="Well slap my ass and call me a teapot; I could not save that player", detail="Well slap my ass and call me a teapot; I could not save that player",
@ -252,7 +267,8 @@ async def post_one_player(player: SbaPlayerModel, token: str = Depends(oauth2_sc
@handle_db_errors @handle_db_errors
async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)): async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logging.warning("Bad Token") logging.warning(f"Bad Token: {token}")
db.close()
raise HTTPException( raise HTTPException(
status_code=401, status_code=401,
detail="You are not authorized to delete mlb players. This event has been logged.", detail="You are not authorized to delete mlb players. This event has been logged.",
@ -260,11 +276,13 @@ async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)):
this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id) this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id)
if this_player is None: if this_player is None:
db.close()
raise HTTPException( raise HTTPException(
status_code=404, detail=f"SbaPlayer id {player_id} not found" status_code=404, detail=f"SbaPlayer id {player_id} not found"
) )
count = this_player.delete_instance() count = this_player.delete_instance()
db.close()
if count == 1: if count == 1:
return f"Player {player_id} has been deleted" return f"Player {player_id} has been deleted"

View File

@ -80,6 +80,7 @@ async def get_schedules(
"count": total_count, "count": total_count,
"schedules": [model_to_dict(x, recurse=not short_output) for x in all_sched], "schedules": [model_to_dict(x, recurse=not short_output) for x in all_sched],
} }
db.close()
return return_sched return return_sched
@ -91,6 +92,7 @@ async def get_one_schedule(schedule_id: int):
r_sched = model_to_dict(this_sched) r_sched = model_to_dict(this_sched)
else: else:
r_sched = None r_sched = None
db.close()
return r_sched return r_sched
@ -106,7 +108,7 @@ async def patch_schedule(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_schedule - Bad Token") logger.warning(f"patch_schedule - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_sched = Schedule.get_or_none(Schedule.id == schedule_id) this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
@ -132,8 +134,10 @@ async def patch_schedule(
if this_sched.save() == 1: if this_sched.save() == 1:
r_sched = model_to_dict(this_sched) r_sched = model_to_dict(this_sched)
db.close()
return r_sched return r_sched
else: else:
db.close()
raise HTTPException( raise HTTPException(
status_code=500, detail=f"Unable to patch schedule {schedule_id}" status_code=500, detail=f"Unable to patch schedule {schedule_id}"
) )
@ -143,7 +147,7 @@ async def patch_schedule(
@handle_db_errors @handle_db_errors
async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_scheme)): async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_schedules - Bad Token") logger.warning(f"post_schedules - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_sched = [] new_sched = []
@ -168,11 +172,12 @@ async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_s
status_code=404, detail=f"Team ID {x.hometeam_id} not found" status_code=404, detail=f"Team ID {x.hometeam_id} not found"
) )
new_sched.append(x.model_dump()) new_sched.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_sched, 15): for batch in chunked(new_sched, 15):
Schedule.insert_many(batch).on_conflict_ignore().execute() Schedule.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Inserted {len(new_sched)} schedules" return f"Inserted {len(new_sched)} schedules"
@ -181,7 +186,7 @@ async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_s
@handle_db_errors @handle_db_errors
async def delete_schedule(schedule_id: int, token: str = Depends(oauth2_scheme)): async def delete_schedule(schedule_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_schedule - Bad Token") logger.warning(f"delete_schedule - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_sched = Schedule.get_or_none(Schedule.id == schedule_id) this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
@ -191,6 +196,7 @@ async def delete_schedule(schedule_id: int, token: str = Depends(oauth2_scheme))
) )
count = this_sched.delete_instance() count = this_sched.delete_instance()
db.close()
if count == 1: if count == 1:
return f"Schedule {this_sched} has been deleted" return f"Schedule {this_sched} has been deleted"

View File

@ -69,6 +69,7 @@ async def get_standings(
"standings": [model_to_dict(x, recurse=not short_output) for x in div_teams], "standings": [model_to_dict(x, recurse=not short_output) for x in div_teams],
} }
db.close()
return return_standings return return_standings
@ -87,18 +88,19 @@ async def get_team_standings(team_id: int):
@router.patch("/{stan_id}", include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{stan_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_standings( async def patch_standings(
stan_id: int, stan_id,
wins: Optional[int] = None, wins: Optional[int] = None,
losses: Optional[int] = None, losses: Optional[int] = None,
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_standings - Bad Token") logger.warning(f"patch_standings - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
this_stan = Standings.get_by_id(stan_id) this_stan = Standings.get_by_id(stan_id)
except Exception as e: except Exception as e:
db.close()
raise HTTPException(status_code=404, detail=f"No team found with id {stan_id}") raise HTTPException(status_code=404, detail=f"No team found with id {stan_id}")
if wins: if wins:
@ -107,6 +109,7 @@ async def patch_standings(
this_stan.losses = losses this_stan.losses = losses
this_stan.save() this_stan.save()
db.close()
return model_to_dict(this_stan) return model_to_dict(this_stan)
@ -115,7 +118,7 @@ async def patch_standings(
@handle_db_errors @handle_db_errors
async def post_standings(season: int, token: str = Depends(oauth2_scheme)): async def post_standings(season: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_standings - Bad Token") logger.warning(f"post_standings - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_teams = [] new_teams = []
@ -126,6 +129,7 @@ async def post_standings(season: int, token: str = Depends(oauth2_scheme)):
with db.atomic(): with db.atomic():
for batch in chunked(new_teams, 16): for batch in chunked(new_teams, 16):
Standings.insert_many(batch).on_conflict_ignore().execute() Standings.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Inserted {len(new_teams)} standings" return f"Inserted {len(new_teams)} standings"
@ -134,10 +138,11 @@ async def post_standings(season: int, token: str = Depends(oauth2_scheme)):
@handle_db_errors @handle_db_errors
async def recalculate_standings(season: int, token: str = Depends(oauth2_scheme)): async def recalculate_standings(season: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("recalculate_standings - Bad Token") logger.warning(f"recalculate_standings - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
code = Standings.recalculate(season) code = Standings.recalculate(season)
db.close()
if code == 69: if code == 69:
raise HTTPException(status_code=500, detail=f"Error recreating Standings rows") raise HTTPException(status_code=500, detail=f"Error recreating Standings rows")
return f"Just recalculated standings for season {season}" return f"Just recalculated standings for season {season}"

View File

@ -13,6 +13,7 @@ from ..dependencies import (
PRIVATE_IN_SCHEMA, PRIVATE_IN_SCHEMA,
handle_db_errors, handle_db_errors,
update_season_batting_stats, update_season_batting_stats,
MAX_LIMIT,
DEFAULT_LIMIT, DEFAULT_LIMIT,
) )
@ -60,7 +61,7 @@ async def get_games(
division_id: Optional[int] = None, division_id: Optional[int] = None,
short_output: Optional[bool] = False, short_output: Optional[bool] = False,
sort: Optional[str] = None, sort: Optional[str] = None,
limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=1000), limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=MAX_LIMIT),
offset: int = Query(default=0, ge=0), offset: int = Query(default=0, ge=0),
) -> Any: ) -> Any:
all_games = StratGame.select() all_games = StratGame.select()
@ -129,6 +130,7 @@ async def get_games(
"count": total_count, "count": total_count,
"games": [model_to_dict(x, recurse=not short_output) for x in all_games], "games": [model_to_dict(x, recurse=not short_output) for x in all_games],
} }
db.close()
return return_games return return_games
@ -137,9 +139,11 @@ async def get_games(
async def get_one_game(game_id: int) -> Any: async def get_one_game(game_id: int) -> Any:
this_game = StratGame.get_or_none(StratGame.id == game_id) this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game: if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f"StratGame ID {game_id} not found") raise HTTPException(status_code=404, detail=f"StratGame ID {game_id} not found")
g_result = model_to_dict(this_game) g_result = model_to_dict(this_game)
db.close()
return g_result return g_result
@ -156,11 +160,12 @@ async def patch_game(
scorecard_url: Optional[str] = None, scorecard_url: Optional[str] = None,
) -> Any: ) -> Any:
if not valid_token(token): if not valid_token(token):
logger.warning("patch_game - Bad Token") logger.warning(f"patch_game - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_game = StratGame.get_or_none(StratGame.id == game_id) this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game: if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f"StratGame ID {game_id} not found") raise HTTPException(status_code=404, detail=f"StratGame ID {game_id} not found")
if game_num is not None: if game_num is not None:
@ -236,7 +241,7 @@ async def patch_game(
@handle_db_errors @handle_db_errors
async def post_games(game_list: GameList, token: str = Depends(oauth2_scheme)) -> Any: async def post_games(game_list: GameList, token: str = Depends(oauth2_scheme)) -> Any:
if not valid_token(token): if not valid_token(token):
logger.warning("post_games - Bad Token") logger.warning(f"post_games - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_games = [] new_games = []
@ -250,11 +255,12 @@ async def post_games(game_list: GameList, token: str = Depends(oauth2_scheme)) -
status_code=404, detail=f"Team ID {x.home_team_id} not found" status_code=404, detail=f"Team ID {x.home_team_id} not found"
) )
new_games.append(x.model_dump()) new_games.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_games, 16): for batch in chunked(new_games, 16):
StratGame.insert_many(batch).on_conflict_ignore().execute() StratGame.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Inserted {len(new_games)} games" return f"Inserted {len(new_games)} games"
@ -263,11 +269,12 @@ async def post_games(game_list: GameList, token: str = Depends(oauth2_scheme)) -
@handle_db_errors @handle_db_errors
async def wipe_game(game_id: int, token: str = Depends(oauth2_scheme)) -> Any: async def wipe_game(game_id: int, token: str = Depends(oauth2_scheme)) -> Any:
if not valid_token(token): if not valid_token(token):
logger.warning("wipe_game - Bad Token") logger.warning(f"wipe_game - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_game = StratGame.get_or_none(StratGame.id == game_id) this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game: if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f"StratGame ID {game_id} not found") raise HTTPException(status_code=404, detail=f"StratGame ID {game_id} not found")
this_game.away_score = None this_game.away_score = None
@ -278,8 +285,10 @@ async def wipe_game(game_id: int, token: str = Depends(oauth2_scheme)) -> Any:
if this_game.save() == 1: if this_game.save() == 1:
g_result = model_to_dict(this_game) g_result = model_to_dict(this_game)
db.close()
return g_result return g_result
else: else:
db.close()
raise HTTPException(status_code=500, detail=f"Unable to wipe game {game_id}") raise HTTPException(status_code=500, detail=f"Unable to wipe game {game_id}")
@ -287,14 +296,16 @@ async def wipe_game(game_id: int, token: str = Depends(oauth2_scheme)) -> Any:
@handle_db_errors @handle_db_errors
async def delete_game(game_id: int, token: str = Depends(oauth2_scheme)) -> Any: async def delete_game(game_id: int, token: str = Depends(oauth2_scheme)) -> Any:
if not valid_token(token): if not valid_token(token):
logger.warning("delete_game - Bad Token") logger.warning(f"delete_game - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_game = StratGame.get_or_none(StratGame.id == game_id) this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game: if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f"StratGame ID {game_id} not found") raise HTTPException(status_code=404, detail=f"StratGame ID {game_id} not found")
count = this_game.delete_instance() count = this_game.delete_instance()
db.close()
if count == 1: if count == 1:
return f"StratGame {game_id} has been deleted" return f"StratGame {game_id} has been deleted"

View File

@ -17,6 +17,7 @@ from ...dependencies import (
add_cache_headers, add_cache_headers,
cache_result, cache_result,
handle_db_errors, handle_db_errors,
MAX_LIMIT,
DEFAULT_LIMIT, DEFAULT_LIMIT,
) )
from .common import build_season_games from .common import build_season_games
@ -57,7 +58,7 @@ async def get_batting_totals(
risp: Optional[bool] = None, risp: Optional[bool] = None,
inning: list = Query(default=None), inning: list = Query(default=None),
sort: Optional[str] = None, sort: Optional[str] = None,
limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=1000), limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=MAX_LIMIT),
short_output: Optional[bool] = False, short_output: Optional[bool] = False,
page_num: Optional[int] = 1, page_num: Optional[int] = 1,
week_start: Optional[int] = None, week_start: Optional[int] = None,
@ -597,4 +598,5 @@ async def get_batting_totals(
} }
) )
db.close()
return return_stats return return_stats

View File

@ -20,8 +20,10 @@ logger = logging.getLogger("discord_app")
@handle_db_errors @handle_db_errors
async def get_one_play(play_id: int): async def get_one_play(play_id: int):
if StratPlay.get_or_none(StratPlay.id == play_id) is None: if StratPlay.get_or_none(StratPlay.id == play_id) is None:
db.close()
raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found") raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found")
r_play = model_to_dict(StratPlay.get_by_id(play_id)) r_play = model_to_dict(StratPlay.get_by_id(play_id))
db.close()
return r_play return r_play
@ -31,14 +33,16 @@ async def patch_play(
play_id: int, new_play: PlayModel, token: str = Depends(oauth2_scheme) play_id: int, new_play: PlayModel, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_play - Bad Token") logger.warning(f"patch_play - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
if StratPlay.get_or_none(StratPlay.id == play_id) is None: if StratPlay.get_or_none(StratPlay.id == play_id) is None:
db.close()
raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found") raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found")
StratPlay.update(**new_play.model_dump()).where(StratPlay.id == play_id).execute() StratPlay.update(**new_play.dict()).where(StratPlay.id == play_id).execute()
r_play = model_to_dict(StratPlay.get_by_id(play_id)) r_play = model_to_dict(StratPlay.get_by_id(play_id))
db.close()
return r_play return r_play
@ -46,7 +50,7 @@ async def patch_play(
@handle_db_errors @handle_db_errors
async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)): async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_plays - Bad Token") logger.warning(f"post_plays - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_plays = [] new_plays = []
@ -84,11 +88,12 @@ async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)):
if this_play.pa == 0: if this_play.pa == 0:
this_play.batter_final = None this_play.batter_final = None
new_plays.append(this_play.model_dump()) new_plays.append(this_play.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_plays, 20): for batch in chunked(new_plays, 20):
StratPlay.insert_many(batch).on_conflict_ignore().execute() StratPlay.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"Inserted {len(new_plays)} plays" return f"Inserted {len(new_plays)} plays"
@ -97,14 +102,16 @@ async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)):
@handle_db_errors @handle_db_errors
async def delete_play(play_id: int, token: str = Depends(oauth2_scheme)): async def delete_play(play_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_play - Bad Token") logger.warning(f"delete_play - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_play = StratPlay.get_or_none(StratPlay.id == play_id) this_play = StratPlay.get_or_none(StratPlay.id == play_id)
if not this_play: if not this_play:
db.close()
raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found") raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found")
count = this_play.delete_instance() count = this_play.delete_instance()
db.close()
if count == 1: if count == 1:
return f"Play {play_id} has been deleted" return f"Play {play_id} has been deleted"
@ -118,14 +125,16 @@ async def delete_play(play_id: int, token: str = Depends(oauth2_scheme)):
@handle_db_errors @handle_db_errors
async def delete_plays_game(game_id: int, token: str = Depends(oauth2_scheme)): async def delete_plays_game(game_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_plays_game - Bad Token") logger.warning(f"delete_plays_game - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_game = StratGame.get_or_none(StratGame.id == game_id) this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game: if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f"Game ID {game_id} not found") raise HTTPException(status_code=404, detail=f"Game ID {game_id} not found")
count = StratPlay.delete().where(StratPlay.game == this_game).execute() count = StratPlay.delete().where(StratPlay.game == this_game).execute()
db.close()
if count > 0: if count > 0:
return f"Deleted {count} plays matching Game ID {game_id}" return f"Deleted {count} plays matching Game ID {game_id}"
@ -139,11 +148,12 @@ async def delete_plays_game(game_id: int, token: str = Depends(oauth2_scheme)):
@handle_db_errors @handle_db_errors
async def post_erun_check(token: str = Depends(oauth2_scheme)): async def post_erun_check(token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_erun_check - Bad Token") logger.warning(f"post_erun_check - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
all_plays = StratPlay.update(run=1).where( all_plays = StratPlay.update(run=1).where(
(StratPlay.e_run == 1) & (StratPlay.run == 0) (StratPlay.e_run == 1) & (StratPlay.run == 0)
) )
count = all_plays.execute() count = all_plays.execute()
db.close()
return count return count

View File

@ -17,6 +17,7 @@ from ...dependencies import (
handle_db_errors, handle_db_errors,
add_cache_headers, add_cache_headers,
cache_result, cache_result,
MAX_LIMIT,
DEFAULT_LIMIT, DEFAULT_LIMIT,
) )
from .common import build_season_games from .common import build_season_games
@ -56,7 +57,7 @@ async def get_fielding_totals(
team_id: list = Query(default=None), team_id: list = Query(default=None),
manager_id: list = Query(default=None), manager_id: list = Query(default=None),
sort: Optional[str] = None, sort: Optional[str] = None,
limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=1000), limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=MAX_LIMIT),
short_output: Optional[bool] = False, short_output: Optional[bool] = False,
page_num: Optional[int] = 1, page_num: Optional[int] = 1,
): ):
@ -364,4 +365,5 @@ async def get_fielding_totals(
"week": this_week, "week": this_week,
} }
) )
db.close()
return return_stats return return_stats

View File

@ -20,6 +20,7 @@ from ...dependencies import (
handle_db_errors, handle_db_errors,
add_cache_headers, add_cache_headers,
cache_result, cache_result,
MAX_LIMIT,
DEFAULT_LIMIT, DEFAULT_LIMIT,
) )
from .common import build_season_games from .common import build_season_games
@ -56,7 +57,7 @@ async def get_pitching_totals(
risp: Optional[bool] = None, risp: Optional[bool] = None,
inning: list = Query(default=None), inning: list = Query(default=None),
sort: Optional[str] = None, sort: Optional[str] = None,
limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=1000), limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=MAX_LIMIT),
short_output: Optional[bool] = False, short_output: Optional[bool] = False,
csv: Optional[bool] = False, csv: Optional[bool] = False,
page_num: Optional[int] = 1, page_num: Optional[int] = 1,
@ -351,6 +352,7 @@ async def get_pitching_totals(
) )
return_stats["count"] = len(return_stats["stats"]) return_stats["count"] = len(return_stats["stats"])
db.close()
if csv: if csv:
return Response( return Response(
content=complex_data_to_csv(return_stats["stats"]), media_type="text/csv" content=complex_data_to_csv(return_stats["stats"]), media_type="text/csv"

View File

@ -210,4 +210,5 @@ async def get_plays(
"count": all_plays.count(), "count": all_plays.count(),
"plays": [model_to_dict(x, recurse=not short_output) for x in all_plays], "plays": [model_to_dict(x, recurse=not short_output) for x in all_plays],
} }
db.close()
return return_plays return return_plays

View File

@ -78,14 +78,14 @@ async def get_transactions(
transactions = transactions.where(Transaction.player << these_players) transactions = transactions.where(Transaction.player << these_players)
if cancelled: if cancelled:
transactions = transactions.where(Transaction.cancelled == True) transactions = transactions.where(Transaction.cancelled == 1)
else: else:
transactions = transactions.where(Transaction.cancelled == False) transactions = transactions.where(Transaction.cancelled == 0)
if frozen: if frozen:
transactions = transactions.where(Transaction.frozen == True) transactions = transactions.where(Transaction.frozen == 1)
else: else:
transactions = transactions.where(Transaction.frozen == False) transactions = transactions.where(Transaction.frozen == 0)
transactions = transactions.order_by(-Transaction.week, Transaction.moveid) transactions = transactions.order_by(-Transaction.week, Transaction.moveid)
@ -101,6 +101,7 @@ async def get_transactions(
], ],
} }
db.close()
return return_trans return return_trans
@ -113,11 +114,12 @@ async def patch_transactions(
cancelled: Optional[bool] = None, cancelled: Optional[bool] = None,
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_transactions - Bad Token") logger.warning(f"patch_transactions - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
these_moves = Transaction.select().where(Transaction.moveid == move_id) these_moves = Transaction.select().where(Transaction.moveid == move_id)
if these_moves.count() == 0: if these_moves.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f"Move ID {move_id} not found") raise HTTPException(status_code=404, detail=f"Move ID {move_id} not found")
if frozen is not None: if frozen is not None:
@ -129,6 +131,7 @@ async def patch_transactions(
x.cancelled = cancelled x.cancelled = cancelled
x.save() x.save()
db.close()
return f"Updated {these_moves.count()} transactions" return f"Updated {these_moves.count()} transactions"
@ -138,7 +141,7 @@ async def post_transactions(
moves: TransactionList, token: str = Depends(oauth2_scheme) moves: TransactionList, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("post_transactions - Bad Token") logger.warning(f"post_transactions - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
all_moves = [] all_moves = []
@ -172,12 +175,13 @@ async def post_transactions(
status_code=404, detail=f"Player ID {x.player_id} not found" status_code=404, detail=f"Player ID {x.player_id} not found"
) )
all_moves.append(x.model_dump()) all_moves.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(all_moves, 15): for batch in chunked(all_moves, 15):
Transaction.insert_many(batch).on_conflict_ignore().execute() Transaction.insert_many(batch).on_conflict_ignore().execute()
db.close()
return f"{len(all_moves)} transactions have been added" return f"{len(all_moves)} transactions have been added"
@ -185,12 +189,13 @@ async def post_transactions(
@handle_db_errors @handle_db_errors
async def delete_transactions(move_id, token: str = Depends(oauth2_scheme)): async def delete_transactions(move_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_transactions - Bad Token") logger.warning(f"delete_transactions - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
delete_query = Transaction.delete().where(Transaction.moveid == move_id) delete_query = Transaction.delete().where(Transaction.moveid == move_id)
count = delete_query.execute() count = delete_query.execute()
db.close()
if count > 0: if count > 0:
return f"Removed {count} transactions" return f"Removed {count} transactions"
else: else:

View File

@ -124,7 +124,7 @@ async def refresh_season_batting_stats(
Useful for full season updates. Useful for full season updates.
""" """
if not valid_token(token): if not valid_token(token):
logger.warning("refresh_season_batting_stats - Bad Token") logger.warning(f"refresh_season_batting_stats - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
logger.info(f"Refreshing all batting stats for season {season}") logger.info(f"Refreshing all batting stats for season {season}")
@ -270,7 +270,7 @@ async def refresh_season_pitching_stats(
Private endpoint - not included in public API documentation. Private endpoint - not included in public API documentation.
""" """
if not valid_token(token): if not valid_token(token):
logger.warning("refresh_season_batting_stats - Bad Token") logger.warning(f"refresh_season_batting_stats - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
logger.info(f"Refreshing season {season} pitching stats") logger.info(f"Refreshing season {season} pitching stats")
@ -318,7 +318,7 @@ async def get_admin_cache_stats(token: str = Depends(oauth2_scheme)) -> dict:
Private endpoint - requires authentication. Private endpoint - requires authentication.
""" """
if not valid_token(token): if not valid_token(token):
logger.warning("get_admin_cache_stats - Bad Token") logger.warning(f"get_admin_cache_stats - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
logger.info("Getting cache statistics") logger.info("Getting cache statistics")

View File

@ -34,7 +34,6 @@ services:
- REDIS_HOST=sba_redis - REDIS_HOST=sba_redis
- REDIS_PORT=6379 - REDIS_PORT=6379
- REDIS_DB=0 - REDIS_DB=0
- DISCORD_WEBHOOK_URL=${DISCORD_WEBHOOK_URL}
depends_on: depends_on:
- postgres - postgres
- redis - redis

View File

@ -1,88 +0,0 @@
#!/usr/bin/env python3
"""Apply pending SQL migrations and record them in schema_versions.
Usage:
python migrations.py
Connects to PostgreSQL using the same environment variables as the API:
POSTGRES_DB (default: sba_master)
POSTGRES_USER (default: sba_admin)
POSTGRES_PASSWORD (required)
POSTGRES_HOST (default: sba_postgres)
POSTGRES_PORT (default: 5432)
On first run against an existing database, all migrations will be applied.
All migration files use IF NOT EXISTS guards so re-applying is safe.
"""
import os
import sys
from pathlib import Path
import psycopg2
MIGRATIONS_DIR = Path(__file__).parent / "migrations"
_CREATE_SCHEMA_VERSIONS = """
CREATE TABLE IF NOT EXISTS schema_versions (
filename VARCHAR(255) PRIMARY KEY,
applied_at TIMESTAMP NOT NULL DEFAULT NOW()
);
"""
def _get_connection():
password = os.environ.get("POSTGRES_PASSWORD")
if password is None:
raise RuntimeError("POSTGRES_PASSWORD environment variable is not set")
return psycopg2.connect(
dbname=os.environ.get("POSTGRES_DB", "sba_master"),
user=os.environ.get("POSTGRES_USER", "sba_admin"),
password=password,
host=os.environ.get("POSTGRES_HOST", "sba_postgres"),
port=int(os.environ.get("POSTGRES_PORT", "5432")),
)
def main():
conn = _get_connection()
try:
with conn:
with conn.cursor() as cur:
cur.execute(_CREATE_SCHEMA_VERSIONS)
with conn.cursor() as cur:
cur.execute("SELECT filename FROM schema_versions")
applied = {row[0] for row in cur.fetchall()}
migration_files = sorted(MIGRATIONS_DIR.glob("*.sql"))
pending = [f for f in migration_files if f.name not in applied]
if not pending:
print("No pending migrations.")
return
for migration_file in pending:
print(f"Applying {migration_file.name} ...", end=" ", flush=True)
sql = migration_file.read_text()
with conn:
with conn.cursor() as cur:
cur.execute(sql)
cur.execute(
"INSERT INTO schema_versions (filename) VALUES (%s)",
(migration_file.name,),
)
print("done")
print(f"\nApplied {len(pending)} migration(s).")
finally:
conn.close()
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)

View File

@ -1,9 +0,0 @@
-- Migration: Add schema_versions table for migration tracking
-- Date: 2026-03-27
-- Description: Creates a table to record which SQL migrations have been applied,
-- preventing double-application and missed migrations across environments.
CREATE TABLE IF NOT EXISTS schema_versions (
filename VARCHAR(255) PRIMARY KEY,
applied_at TIMESTAMP NOT NULL DEFAULT NOW()
);

View File

@ -1,24 +0,0 @@
-- Migration: Add missing indexes on foreign key columns in stratplay and stratgame
-- Created: 2026-03-27
--
-- PostgreSQL does not auto-index foreign key columns. These tables are the
-- highest-volume tables in the schema and are filtered/joined on these columns
-- in batting, pitching, and running stats aggregation and standings recalculation.
-- stratplay: FK join column
CREATE INDEX IF NOT EXISTS idx_stratplay_game_id ON stratplay(game_id);
-- stratplay: filtered in batting stats aggregation
CREATE INDEX IF NOT EXISTS idx_stratplay_batter_id ON stratplay(batter_id);
-- stratplay: filtered in pitching stats aggregation
CREATE INDEX IF NOT EXISTS idx_stratplay_pitcher_id ON stratplay(pitcher_id);
-- stratplay: filtered in running stats
CREATE INDEX IF NOT EXISTS idx_stratplay_runner_id ON stratplay(runner_id);
-- stratgame: heavily filtered by season
CREATE INDEX IF NOT EXISTS idx_stratgame_season ON stratgame(season);
-- stratgame: standings recalculation query ordering
CREATE INDEX IF NOT EXISTS idx_stratgame_season_week_game_num ON stratgame(season, week, game_num);

View File

@ -569,7 +569,7 @@ class TestGroupBySbaPlayer:
# Get per-season rows # Get per-season rows
r_seasons = requests.get( r_seasons = requests.get(
f"{api}/api/v3/plays/batting", f"{api}/api/v3/plays/batting",
params={"group_by": "player", "sbaplayer_id": 1, "limit": 500}, params={"group_by": "player", "sbaplayer_id": 1, "limit": 999},
timeout=15, timeout=15,
) )
assert r_seasons.status_code == 200 assert r_seasons.status_code == 200