commit
6faec225d3
@ -1,8 +1,8 @@
|
|||||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8
|
FROM tiangolo/uvicorn-gunicorn-fastapi:latest
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
COPY requirements.txt ./
|
COPY requirements.txt ./
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
COPY . .
|
COPY ./app /app/app
|
||||||
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
1940
app/db_engine.py
Normal file
1940
app/db_engine.py
Normal file
File diff suppressed because it is too large
Load Diff
34
app/dependencies.py
Normal file
34
app/dependencies.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
|
|
||||||
|
date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}'
|
||||||
|
LOG_DATA = {
|
||||||
|
'filename': f'logs/database/{date}.log',
|
||||||
|
'format': '%(asctime)s - database - %(levelname)s - %(message)s',
|
||||||
|
'log_level': logging.INFO if os.environ.get('LOG_LEVEL') == 'INFO' else 'WARN'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
filename=LOG_DATA['filename'],
|
||||||
|
format=LOG_DATA['format'],
|
||||||
|
level=LOG_DATA['log_level']
|
||||||
|
)
|
||||||
|
|
||||||
|
# date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}'
|
||||||
|
# log_level = logging.INFO if os.environ.get('LOG_LEVEL') == 'INFO' else 'WARN'
|
||||||
|
# logging.basicConfig(
|
||||||
|
# filename=f'logs/database/{date}.log',
|
||||||
|
# format='%(asctime)s - sba-database - %(levelname)s - %(message)s',
|
||||||
|
# level=log_level
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
||||||
|
|
||||||
|
|
||||||
|
def valid_token(token):
|
||||||
|
return token == os.environ.get('API_TOKEN')
|
||||||
56
app/main.py
Normal file
56
app/main.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fastapi import Depends, FastAPI, Request
|
||||||
|
# from fastapi.openapi.docs import get_swagger_ui_html
|
||||||
|
# from fastapi.openapi.utils import get_openapi
|
||||||
|
|
||||||
|
from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats, \
|
||||||
|
pitchingstats, fieldingstats, draftpicks, draftlist, managers, awards, draftdata, keepers
|
||||||
|
|
||||||
|
date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}'
|
||||||
|
log_level = logging.INFO if os.environ.get('LOG_LEVEL') == 'INFO' else 'WARN'
|
||||||
|
logging.basicConfig(
|
||||||
|
filename=f'logs/database/{date}.log',
|
||||||
|
format='%(asctime)s - sba-database - %(levelname)s - %(message)s',
|
||||||
|
level=log_level
|
||||||
|
)
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
responses={404: {'description': 'Not found'}}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
app.include_router(current.router)
|
||||||
|
app.include_router(players.router)
|
||||||
|
app.include_router(results.router)
|
||||||
|
app.include_router(schedules.router)
|
||||||
|
app.include_router(teams.router)
|
||||||
|
app.include_router(transactions.router)
|
||||||
|
app.include_router(standings.router)
|
||||||
|
app.include_router(battingstats.router)
|
||||||
|
app.include_router(pitchingstats.router)
|
||||||
|
app.include_router(fieldingstats.router)
|
||||||
|
app.include_router(draftpicks.router)
|
||||||
|
app.include_router(draftlist.router)
|
||||||
|
app.include_router(managers.router)
|
||||||
|
app.include_router(awards.router)
|
||||||
|
app.include_router(draftdata.router)
|
||||||
|
app.include_router(keepers.router)
|
||||||
|
|
||||||
|
|
||||||
|
# @app.get("/docs", include_in_schema=False)
|
||||||
|
# async def get_docs(req: Request):
|
||||||
|
# print(req.scope)
|
||||||
|
# return get_swagger_ui_html(openapi_url=req.scope.get('root_path')+'/openapi.json', title='Swagger')
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# @app.get("/openapi.json", include_in_schema=False)
|
||||||
|
# async def openapi():
|
||||||
|
# return get_openapi(title='SBa Dev API', version=f'0.1.1', routes=app.routes)
|
||||||
|
|
||||||
|
|
||||||
|
# @app.get("/api")
|
||||||
|
# async def root():
|
||||||
|
# return {"message": "Hello Bigger Applications!"}
|
||||||
0
app/routers_v3/__init__.py
Normal file
0
app/routers_v3/__init__.py
Normal file
162
app/routers_v3/awards.py
Normal file
162
app/routers_v3/awards.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from typing import List, Optional
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, Award, Team, Player, Manager, model_to_dict, chunked, fn
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/awards',
|
||||||
|
tags=['awards']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AwardModel(pydantic.BaseModel):
|
||||||
|
name: str
|
||||||
|
season: int
|
||||||
|
timing: Optional[str] = "In-Season"
|
||||||
|
manager1_id: Optional[int] = None
|
||||||
|
manager2_id: Optional[int] = None
|
||||||
|
player_id: Optional[int] = None
|
||||||
|
team_id: Optional[int] = None
|
||||||
|
image: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AwardList(pydantic.BaseModel):
|
||||||
|
count: int
|
||||||
|
awards: List[AwardModel]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_awards(
|
||||||
|
name: list = Query(default=None), season: Optional[int] = None, timing: Optional[str] = None,
|
||||||
|
manager_id: list = Query(default=None), player_id: list = Query(default=None),
|
||||||
|
team_id: list = Query(default=None), short_output: Optional[bool] = False):
|
||||||
|
all_awards = Award.select()
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
name_list = [x.lower() for x in name]
|
||||||
|
all_awards = all_awards.where(fn.Lower(Award.name) == name_list)
|
||||||
|
if season is not None:
|
||||||
|
all_awards = all_awards.where(Award.season == season)
|
||||||
|
if timing is not None:
|
||||||
|
all_awards = all_awards.where(fn.Lower(Award.timing) == timing.lower())
|
||||||
|
if manager_id is not None:
|
||||||
|
managers = Manager.select().where(Manager.id << manager_id)
|
||||||
|
all_awards = all_awards.where(
|
||||||
|
(Award.manager1 << managers) | (Award.manager2 << managers)
|
||||||
|
)
|
||||||
|
if player_id is not None:
|
||||||
|
all_awards = all_awards.where(Award.player_id << player_id)
|
||||||
|
if team_id is not None:
|
||||||
|
all_awards = all_awards.where(Award.team_id << team_id)
|
||||||
|
|
||||||
|
return_awards = {
|
||||||
|
'count': all_awards.count(),
|
||||||
|
'awards': [model_to_dict(x, recurse=not short_output) for x in all_awards]
|
||||||
|
}
|
||||||
|
db.close()
|
||||||
|
return return_awards
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{award_id}')
|
||||||
|
async def get_one_award(award_id: int, short_output: Optional[bool] = False):
|
||||||
|
this_award = Award.get_or_none(Award.id == award_id)
|
||||||
|
if this_award is None:
|
||||||
|
db.close()
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{award_id}')
|
||||||
|
async def patch_award(
|
||||||
|
award_id: int, name: Optional[str] = None, season: Optional[int] = None, timing: Optional[str] = None,
|
||||||
|
image: Optional[str] = None, manager1_id: Optional[int] = None, manager2_id: Optional[int] = None,
|
||||||
|
player_id: Optional[int] = None, team_id: Optional[int] = None, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_player - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_award = Award.get_or_none(Award.id == award_id)
|
||||||
|
if this_award is None:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Award ID {award_id} not found')
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
this_award.name = name
|
||||||
|
if season is not None:
|
||||||
|
this_award.season = season
|
||||||
|
if timing is not None:
|
||||||
|
this_award.timing = timing
|
||||||
|
if image is not None:
|
||||||
|
this_award.image = image
|
||||||
|
if manager1_id is not None:
|
||||||
|
this_award.manager1_id = manager1_id
|
||||||
|
if manager2_id is not None:
|
||||||
|
this_award.manager2_id = manager2_id
|
||||||
|
if player_id is not None:
|
||||||
|
this_award.player_id = player_id
|
||||||
|
if team_id is not None:
|
||||||
|
this_award.team_id = team_id
|
||||||
|
|
||||||
|
if this_award.save() == 1:
|
||||||
|
r_award = model_to_dict(this_award)
|
||||||
|
db.close()
|
||||||
|
return r_award
|
||||||
|
else:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=500, detail=f'Unable to patch award {award_id}')
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_player - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
new_awards = []
|
||||||
|
for x in award_list.awards:
|
||||||
|
if x.manager1_id is not None and Manager.get_or_none(Manager.id == x.manager1_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Manager ID {x.manager1_id} not found')
|
||||||
|
if x.manager2_id is not None and Manager.get_or_none(Manager.id == x.manager2_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Manager ID {x.manager2_id} not found')
|
||||||
|
if x.player_id is not None and Player.get_or_none(Player.id == x.player_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found')
|
||||||
|
if x.team_id is not None and Team.get_or_none(Team.id == x.team_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {x.team_id} not found')
|
||||||
|
|
||||||
|
new_awards.append(x.dict())
|
||||||
|
|
||||||
|
with db.atomic():
|
||||||
|
for batch in chunked(new_awards, 15):
|
||||||
|
Award.insert_many(batch).on_conflict_replace().execute()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return f'Inserted {len(new_awards)} awards'
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete('/{award_id}')
|
||||||
|
async def delete_award(award_id: int, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_player - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_award = Award.get_or_none(Award.id == award_id)
|
||||||
|
if this_award is None:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Award ID {award_id} not found')
|
||||||
|
|
||||||
|
count = this_award.delete_instance()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if count == 1:
|
||||||
|
return f'Award {award_id} has been deleted'
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=500, detail=f'Award {award_id} could not be deleted')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
306
app/routers_v3/battingstats.py
Normal file
306
app/routers_v3/battingstats.py
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from typing import List, Optional, Literal
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, BattingStat, Team, Player, Current, model_to_dict, chunked, fn, per_season_weeks
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
|
||||||
|
|
||||||
|
logging.basicConfig(filename=LOG_DATA['filename'], format=LOG_DATA['format'], level=LOG_DATA['log_level'])
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/battingstats',
|
||||||
|
tags=['battingstats']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BatStatModel(pydantic.BaseModel):
|
||||||
|
player_id: int
|
||||||
|
team_id: int
|
||||||
|
pos: str
|
||||||
|
pa: Optional[int] = 0
|
||||||
|
ab: Optional[int] = 0
|
||||||
|
run: Optional[int] = 0
|
||||||
|
hit: Optional[int] = 0
|
||||||
|
rbi: Optional[int] = 0
|
||||||
|
double: Optional[int] = 0
|
||||||
|
triple: Optional[int] = 0
|
||||||
|
hr: Optional[int] = 0
|
||||||
|
bb: Optional[int] = 0
|
||||||
|
so: Optional[int] = 0
|
||||||
|
hbp: Optional[int] = 0
|
||||||
|
sac: Optional[int] = 0
|
||||||
|
ibb: Optional[int] = 0
|
||||||
|
gidp: Optional[int] = 0
|
||||||
|
sb: Optional[int] = 0
|
||||||
|
cs: Optional[int] = 0
|
||||||
|
bphr: Optional[int] = 0
|
||||||
|
bpfo: Optional[int] = 0
|
||||||
|
bp1b: Optional[int] = 0
|
||||||
|
bplo: Optional[int] = 0
|
||||||
|
xba: Optional[int] = 0
|
||||||
|
xbt: Optional[int] = 0
|
||||||
|
xch: Optional[int] = 0
|
||||||
|
xhit: Optional[int] = 0
|
||||||
|
error: Optional[int] = 0
|
||||||
|
pb: Optional[int] = 0
|
||||||
|
sbc: Optional[int] = 0
|
||||||
|
csc: Optional[int] = 0
|
||||||
|
roba: Optional[int] = 0
|
||||||
|
robs: Optional[int] = 0
|
||||||
|
raa: Optional[int] = 0
|
||||||
|
rto: Optional[int] = 0
|
||||||
|
week: int
|
||||||
|
game: int
|
||||||
|
season: int
|
||||||
|
|
||||||
|
|
||||||
|
class BatStatList(pydantic.BaseModel):
|
||||||
|
count: int
|
||||||
|
stats: List[BatStatModel]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_batstats(
|
||||||
|
season: int, s_type: Optional[str] = 'regular', team_abbrev: list = Query(default=None),
|
||||||
|
player_name: list = Query(default=None), player_id: list = Query(default=None),
|
||||||
|
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None),
|
||||||
|
position: list = Query(default=None), limit: Optional[int] = None, sort: Optional[str] = None,
|
||||||
|
short_output: Optional[bool] = True):
|
||||||
|
if 'post' in s_type.lower():
|
||||||
|
all_stats = BattingStat.post_season(season)
|
||||||
|
if all_stats.count() == 0:
|
||||||
|
db.close()
|
||||||
|
return {'count': 0, 'stats': []}
|
||||||
|
elif s_type.lower() in ['combined', 'total', 'all']:
|
||||||
|
all_stats = BattingStat.combined_season(season)
|
||||||
|
if all_stats.count() == 0:
|
||||||
|
db.close()
|
||||||
|
return {'count': 0, 'stats': []}
|
||||||
|
else:
|
||||||
|
all_stats = BattingStat.regular_season(season)
|
||||||
|
if all_stats.count() == 0:
|
||||||
|
db.close()
|
||||||
|
return {'count': 0, 'stats': []}
|
||||||
|
|
||||||
|
if position is not None:
|
||||||
|
all_stats = all_stats.where(BattingStat.pos << [x.upper() for x in position])
|
||||||
|
if team_abbrev is not None:
|
||||||
|
t_query = Team.select().where(Team.abbrev << [x.upper() for x in team_abbrev])
|
||||||
|
all_stats = all_stats.where(BattingStat.team << t_query)
|
||||||
|
if player_name is not None or player_id is not None:
|
||||||
|
if player_id:
|
||||||
|
all_stats = all_stats.where(BattingStat.player_id << player_id)
|
||||||
|
else:
|
||||||
|
p_query = Player.select_season(season).where(fn.Lower(Player.name) << [x.lower() for x in player_name])
|
||||||
|
all_stats = all_stats.where(BattingStat.player << p_query)
|
||||||
|
if game_num:
|
||||||
|
all_stats = all_stats.where(BattingStat.game == game_num)
|
||||||
|
|
||||||
|
start = 1
|
||||||
|
end = Current.get(Current.season == season).week
|
||||||
|
if week_start is not None:
|
||||||
|
start = week_start
|
||||||
|
if week_end is not None:
|
||||||
|
end = min(week_end, end)
|
||||||
|
if start > end:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f'Start week {start} is after end week {end} - cannot pull stats'
|
||||||
|
)
|
||||||
|
all_stats = all_stats.where(
|
||||||
|
(BattingStat.week >= start) & (BattingStat.week <= end)
|
||||||
|
)
|
||||||
|
|
||||||
|
if limit:
|
||||||
|
all_stats = all_stats.limit(limit)
|
||||||
|
if sort:
|
||||||
|
if sort == 'newest':
|
||||||
|
all_stats = all_stats.order_by(-BattingStat.week, -BattingStat.game)
|
||||||
|
|
||||||
|
return_stats = {
|
||||||
|
'count': all_stats.count(),
|
||||||
|
'stats': [model_to_dict(x, recurse=not short_output) for x in all_stats],
|
||||||
|
# 'stats': [{'id': x.id} for x in all_stats]
|
||||||
|
}
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return return_stats
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/totals')
|
||||||
|
async def get_totalstats(
|
||||||
|
season: int, s_type: Literal['regular', 'post', 'total', None] = None, team_abbrev: list = Query(default=None),
|
||||||
|
team_id: list = Query(default=None), player_name: list = Query(default=None),
|
||||||
|
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None),
|
||||||
|
position: list = Query(default=None), sort: Optional[str] = None, player_id: list = Query(default=None),
|
||||||
|
group_by: Literal['team', 'player', 'playerteam'] = 'player', short_output: Optional[bool] = False,
|
||||||
|
min_pa: Optional[int] = 1, week: list = Query(default=None)):
|
||||||
|
if sum(1 for x in [s_type, (week_start or week_end), week] if x is not None) > 1:
|
||||||
|
raise HTTPException(status_code=400, detail=f'Only one of s_type, week_start/week_end, or week may be used.')
|
||||||
|
|
||||||
|
all_stats = (
|
||||||
|
BattingStat
|
||||||
|
.select(BattingStat.player, fn.SUM(BattingStat.pa).alias('sum_pa'), fn.SUM(BattingStat.ab).alias('sum_ab'),
|
||||||
|
fn.SUM(BattingStat.run).alias('sum_run'), fn.SUM(BattingStat.hit).alias('sum_hit'),
|
||||||
|
fn.SUM(BattingStat.rbi).alias('sum_rbi'), fn.SUM(BattingStat.double).alias('sum_double'),
|
||||||
|
fn.SUM(BattingStat.triple).alias('sum_triple'), fn.SUM(BattingStat.hr).alias('sum_hr'),
|
||||||
|
fn.SUM(BattingStat.bb).alias('sum_bb'), fn.SUM(BattingStat.so).alias('sum_so'),
|
||||||
|
fn.SUM(BattingStat.hbp).alias('sum_hbp'), fn.SUM(BattingStat.sac).alias('sum_sac'),
|
||||||
|
fn.SUM(BattingStat.ibb).alias('sum_ibb'), fn.SUM(BattingStat.gidp).alias('sum_gidp'),
|
||||||
|
fn.SUM(BattingStat.sb).alias('sum_sb'), fn.SUM(BattingStat.cs).alias('sum_cs'),
|
||||||
|
fn.SUM(BattingStat.bphr).alias('sum_bphr'), fn.SUM(BattingStat.bpfo).alias('sum_bpfo'),
|
||||||
|
fn.SUM(BattingStat.bp1b).alias('sum_bp1b'), fn.SUM(BattingStat.bplo).alias('sum_bplo'),
|
||||||
|
fn.SUM(BattingStat.xba).alias('sum_xba'), fn.SUM(BattingStat.xbt).alias('sum_xbt'),
|
||||||
|
fn.SUM(BattingStat.xch).alias('sum_xch'), fn.SUM(BattingStat.xhit).alias('sum_xhit'),
|
||||||
|
fn.SUM(BattingStat.error).alias('sum_error'), fn.SUM(BattingStat.pb).alias('sum_pb'),
|
||||||
|
fn.SUM(BattingStat.sbc).alias('sum_sbc'), fn.SUM(BattingStat.csc).alias('sum_csc'),
|
||||||
|
fn.SUM(BattingStat.roba).alias('sum_roba'), fn.SUM(BattingStat.robs).alias('sum_robs'),
|
||||||
|
fn.SUM(BattingStat.raa).alias('sum_raa'), fn.SUM(BattingStat.rto).alias('sum_rto'),
|
||||||
|
BattingStat.team)
|
||||||
|
.where(BattingStat.season == season)
|
||||||
|
.having(fn.SUM(BattingStat.pa) >= min_pa)
|
||||||
|
)
|
||||||
|
|
||||||
|
if True in [s_type is not None, week_start is not None, week_end is not None]:
|
||||||
|
weeks = {}
|
||||||
|
if s_type is not None:
|
||||||
|
weeks = per_season_weeks(season, s_type)
|
||||||
|
elif week_start is not None or week_end is not None:
|
||||||
|
if week_start is None or week_end is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail='Both week_start and week_end must be included if either is used.'
|
||||||
|
)
|
||||||
|
weeks['start'] = week_start
|
||||||
|
if week_end < weeks['start']:
|
||||||
|
raise HTTPException(status_code=400, detail='week_end must be greater than or equal to week_start')
|
||||||
|
else:
|
||||||
|
weeks['end'] = week_end
|
||||||
|
|
||||||
|
all_stats = all_stats.where(
|
||||||
|
(BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end'])
|
||||||
|
)
|
||||||
|
elif week is not None:
|
||||||
|
all_stats = all_stats.where(BattingStat.week << week)
|
||||||
|
|
||||||
|
if game_num is not None:
|
||||||
|
all_stats = all_stats.where(BattingStat.game << game_num)
|
||||||
|
if position is not None:
|
||||||
|
p_list = [x.upper() for x in position]
|
||||||
|
all_players = Player.select().where(
|
||||||
|
(Player.pos_1 << p_list) | (Player.pos_2 << p_list) | (Player.pos_3 << p_list) | ( Player.pos_4 << p_list) |
|
||||||
|
(Player.pos_5 << p_list) | (Player.pos_6 << p_list) | (Player.pos_7 << p_list) | ( Player.pos_8 << p_list)
|
||||||
|
)
|
||||||
|
all_stats = all_stats.where(BattingStat.player << all_players)
|
||||||
|
if sort is not None:
|
||||||
|
if sort == 'player':
|
||||||
|
all_stats = all_stats.order_by(BattingStat.player)
|
||||||
|
elif sort == 'team':
|
||||||
|
all_stats = all_stats.order_by(BattingStat.team)
|
||||||
|
if group_by is not None:
|
||||||
|
if group_by == 'team':
|
||||||
|
all_stats = all_stats.group_by(BattingStat.team)
|
||||||
|
elif group_by == 'player':
|
||||||
|
all_stats = all_stats.group_by(BattingStat.player)
|
||||||
|
elif group_by == 'playerteam':
|
||||||
|
all_stats = all_stats.group_by(BattingStat.team, BattingStat.player)
|
||||||
|
|
||||||
|
# if team_abbrev is None and team_id is None and player_name is None and player_id is None:
|
||||||
|
# raise HTTPException(
|
||||||
|
# status_code=400,
|
||||||
|
# detail=f'Must include team_id/team_abbrev and/or player_name/player_id'
|
||||||
|
# )
|
||||||
|
|
||||||
|
if team_id is not None:
|
||||||
|
all_teams = Team.select().where(Team.id << team_id)
|
||||||
|
all_stats = all_stats.where(BattingStat.team << all_teams)
|
||||||
|
elif team_abbrev is not None:
|
||||||
|
all_teams = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev])
|
||||||
|
all_stats = all_stats.where(BattingStat.team << all_teams)
|
||||||
|
|
||||||
|
if player_name is not None:
|
||||||
|
all_players = Player.select().where(fn.Lower(Player.name) << [x.lower() for x in player_name])
|
||||||
|
all_stats = all_stats.where(BattingStat.player << all_players)
|
||||||
|
elif player_id is not None:
|
||||||
|
all_players = Player.select().where(Player.id << player_id)
|
||||||
|
all_stats = all_stats.where(BattingStat.player << all_players)
|
||||||
|
|
||||||
|
return_stats = {
|
||||||
|
'count': all_stats.count(),
|
||||||
|
'stats': [{
|
||||||
|
'player': x.player_id if short_output else model_to_dict(x.player, recurse=False),
|
||||||
|
'team': x.team_id if short_output else model_to_dict(x.team, recurse=False),
|
||||||
|
'pa': x.sum_pa,
|
||||||
|
'ab': x.sum_ab,
|
||||||
|
'run': x.sum_run,
|
||||||
|
'hit': x.sum_hit,
|
||||||
|
'rbi': x.sum_rbi,
|
||||||
|
'double': x.sum_double,
|
||||||
|
'triple': x.sum_triple,
|
||||||
|
'hr': x.sum_hr,
|
||||||
|
'bb': x.sum_bb,
|
||||||
|
'so': x.sum_so,
|
||||||
|
'hbp': x.sum_hbp,
|
||||||
|
'sac': x.sum_sac,
|
||||||
|
'ibb': x.sum_ibb,
|
||||||
|
'gidp': x.sum_gidp,
|
||||||
|
'sb': x.sum_sb,
|
||||||
|
'cs': x.sum_cs,
|
||||||
|
'bphr': x.sum_bphr,
|
||||||
|
'bpfo': x.sum_bpfo,
|
||||||
|
'bp1b': x.sum_bp1b,
|
||||||
|
'bplo': x.sum_bplo
|
||||||
|
} for x in all_stats]
|
||||||
|
}
|
||||||
|
db.close()
|
||||||
|
return return_stats
|
||||||
|
|
||||||
|
|
||||||
|
# @router.get('/career/{player_name}')
|
||||||
|
# async def get_careerstats(
|
||||||
|
# s_type: Literal['regular', 'post', 'total'] = 'regular', player_name: list = Query(default=None)):
|
||||||
|
# pass # Keep Career Stats table and recalculate after posting stats
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{stat_id}')
|
||||||
|
async def patch_batstats(stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_batstats - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
if BattingStat.get_or_none(BattingStat.id == stat_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Stat ID {stat_id} not found')
|
||||||
|
|
||||||
|
BattingStat.update(**new_stats.dict()).where(BattingStat.id == stat_id).execute()
|
||||||
|
r_stat = model_to_dict(BattingStat.get_by_id(stat_id))
|
||||||
|
db.close()
|
||||||
|
return r_stat
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'post_batstats - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
all_stats = []
|
||||||
|
|
||||||
|
for x in s_list.stats:
|
||||||
|
team = Team.get_or_none(Team.id == x.team_id)
|
||||||
|
this_player = Player.get_or_none(Player.id == x.player_id)
|
||||||
|
if team is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {x.team_id} not found')
|
||||||
|
if this_player is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found')
|
||||||
|
|
||||||
|
all_stats.append(BattingStat(**x.dict()))
|
||||||
|
|
||||||
|
with db.atomic():
|
||||||
|
for batch in chunked(all_stats, 15):
|
||||||
|
BattingStat.insert_many(batch).on_conflict_replace().execute()
|
||||||
|
|
||||||
|
# Update career stats
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return f'Added {len(all_stats)} batting lines'
|
||||||
118
app/routers_v3/current.py
Normal file
118
app/routers_v3/current.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from typing import Optional
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, Current, model_to_dict
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/current',
|
||||||
|
tags=['current']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CurrentModel(pydantic.BaseModel):
|
||||||
|
week: Optional[int] = 0
|
||||||
|
freeze: Optional[bool] = False
|
||||||
|
season: int
|
||||||
|
transcount: Optional[int] = 0
|
||||||
|
bstatcount: Optional[int] = 0
|
||||||
|
pstatcount: Optional[int] = 0
|
||||||
|
bet_week: Optional[int] = 0
|
||||||
|
trade_deadline: int
|
||||||
|
pick_trade_start: int = 69
|
||||||
|
pick_trade_end: int = 420
|
||||||
|
playoffs_begin: int
|
||||||
|
injury_count: Optional[int] = 0
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_current(season: Optional[int] = None):
|
||||||
|
if season is not None:
|
||||||
|
current = Current.get_or_none(season=season)
|
||||||
|
else:
|
||||||
|
current = Current.latest()
|
||||||
|
|
||||||
|
if current is not None:
|
||||||
|
r_curr = model_to_dict(current)
|
||||||
|
db.close()
|
||||||
|
return r_curr
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{current_id}')
|
||||||
|
async def patch_current(
|
||||||
|
current_id: int, season: Optional[int] = None, week: Optional[int] = None, freeze: Optional[bool] = None,
|
||||||
|
transcount: Optional[int] = None, bstatcount: Optional[int] = None, pstatcount: Optional[int] = None,
|
||||||
|
bet_week: Optional[int] = None, trade_deadline: Optional[int] = None, pick_trade_start: Optional[int] = None,
|
||||||
|
pick_trade_end: Optional[int] = None, injury_count: Optional[int] = None, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_current - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
try:
|
||||||
|
current = Current.get_by_id(current_id)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Current id {current_id} not found')
|
||||||
|
|
||||||
|
if week is not None:
|
||||||
|
current.week = week
|
||||||
|
if season is not None:
|
||||||
|
current.season = season
|
||||||
|
if freeze is not None:
|
||||||
|
current.freeze = freeze
|
||||||
|
if transcount is not None:
|
||||||
|
current.transcount = transcount
|
||||||
|
if bstatcount is not None:
|
||||||
|
current.bstatcount = bstatcount
|
||||||
|
if pstatcount is not None:
|
||||||
|
current.pstatcount = pstatcount
|
||||||
|
if bet_week is not None:
|
||||||
|
current.bet_week = bet_week
|
||||||
|
if trade_deadline is not None:
|
||||||
|
current.trade_deadline = trade_deadline
|
||||||
|
if pick_trade_start is not None:
|
||||||
|
current.pick_trade_start = pick_trade_start
|
||||||
|
if pick_trade_end is not None:
|
||||||
|
current.pick_trade_end = pick_trade_end
|
||||||
|
if injury_count is not None:
|
||||||
|
current.injury_count = injury_count
|
||||||
|
|
||||||
|
if current.save():
|
||||||
|
r_curr = model_to_dict(current)
|
||||||
|
db.close()
|
||||||
|
return r_curr
|
||||||
|
else:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=500, detail=f'Unable to patch current {current_id}')
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_current - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_current = Current(**new_current.dict())
|
||||||
|
|
||||||
|
if this_current.save():
|
||||||
|
r_curr = model_to_dict(this_current)
|
||||||
|
db.close()
|
||||||
|
return r_curr
|
||||||
|
else:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=500, detail=f'Unable to post season {new_current.season} current')
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete('/{current_id}')
|
||||||
|
async def delete_current(current_id: int, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_current - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
if Current.delete_by_id(current_id) == 1:
|
||||||
|
return f'Deleted current ID {current_id}'
|
||||||
|
|
||||||
|
raise HTTPException(status_code=500, detail=f'Unable to delete current {current_id}')
|
||||||
73
app/routers_v3/draftdata.py
Normal file
73
app/routers_v3/draftdata.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from typing import Optional
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, DraftData, model_to_dict
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/draftdata',
|
||||||
|
tags=['draftdata']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DraftDataModel(pydantic.BaseModel):
|
||||||
|
currentpick: int
|
||||||
|
timer: bool
|
||||||
|
pick_deadline: datetime.datetime
|
||||||
|
result_channel_id = int
|
||||||
|
ping_channel_id = int
|
||||||
|
pick_minutes = int
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_draftdata():
|
||||||
|
draft_data = DraftData.get_or_none()
|
||||||
|
|
||||||
|
if draft_data is not None:
|
||||||
|
r_data = model_to_dict(draft_data)
|
||||||
|
db.close()
|
||||||
|
return r_data
|
||||||
|
|
||||||
|
raise HTTPException(status_code=404, detail=f'No draft data found')
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{data_id}')
|
||||||
|
async def patch_draftdata(
|
||||||
|
data_id: int, currentpick: Optional[int] = None, timer: Optional[bool] = 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)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_draftdata - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
draft_data = DraftData.get_or_none(DraftData.id == data_id)
|
||||||
|
if draft_data is None:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'No draft data found')
|
||||||
|
|
||||||
|
if currentpick is not None:
|
||||||
|
draft_data.currentpick = currentpick
|
||||||
|
if timer is not None:
|
||||||
|
draft_data.timer = timer
|
||||||
|
if pick_deadline is not None:
|
||||||
|
draft_data.pick_deadline = pick_deadline
|
||||||
|
if result_channel is not None:
|
||||||
|
draft_data.result_channel = result_channel
|
||||||
|
if ping_channel is not None:
|
||||||
|
draft_data.ping_channel = ping_channel
|
||||||
|
if pick_minutes is not None:
|
||||||
|
draft_data.pick_minutes = pick_minutes
|
||||||
|
|
||||||
|
saved = draft_data.save()
|
||||||
|
r_data = model_to_dict(draft_data)
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if saved == 1:
|
||||||
|
return r_data
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=500, detail='Updating draft data failed')
|
||||||
|
|
||||||
91
app/routers_v3/draftlist.py
Normal file
91
app/routers_v3/draftlist.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from typing import List, Optional
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, DraftList, Team, model_to_dict, chunked
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/draftlist',
|
||||||
|
tags=['draftlist']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DraftListModel(pydantic.BaseModel):
|
||||||
|
season: int
|
||||||
|
team_id: int
|
||||||
|
rank: int
|
||||||
|
player_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class DraftListList(pydantic.BaseModel):
|
||||||
|
count: int
|
||||||
|
draft_list: List[DraftListModel]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_draftlist(
|
||||||
|
season: Optional[int], team_id: list = Query(default=None), token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'get_draftlist - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
all_list = DraftList.select()
|
||||||
|
|
||||||
|
if season is not None:
|
||||||
|
all_list = all_list.where(DraftList.season == season)
|
||||||
|
if team_id is not None:
|
||||||
|
all_list = all_list.where(DraftList.team_id << team_id)
|
||||||
|
|
||||||
|
r_list = {
|
||||||
|
'count': all_list.count(),
|
||||||
|
'picks': [model_to_dict(x) for x in all_list]
|
||||||
|
}
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return r_list
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/team/{team_id}')
|
||||||
|
async def get_team_draftlist(team_id: int, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'post_draftlist - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_team = Team.get_or_none(Team.id == team_id)
|
||||||
|
if this_team is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {team_id} not found')
|
||||||
|
|
||||||
|
this_list = DraftList.select().where(DraftList.team == this_team)
|
||||||
|
r_list = {
|
||||||
|
'count': this_list.count(),
|
||||||
|
'picks': [model_to_dict(x) for x in this_list]
|
||||||
|
}
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return r_list
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_draftlist(draft_list: DraftListList, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'post_draftlist - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
new_list = []
|
||||||
|
this_team = Team.get_or_none(Team.id == draft_list.draft_list[0].team_id)
|
||||||
|
if this_team is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {draft_list.draft_list[0].team_id} not found')
|
||||||
|
|
||||||
|
DraftList.delete().where(DraftList.team == this_team).execute()
|
||||||
|
|
||||||
|
for x in draft_list.draft_list:
|
||||||
|
new_list.append(x.dict())
|
||||||
|
|
||||||
|
with db.atomic():
|
||||||
|
for batch in chunked(new_list, 15):
|
||||||
|
DraftList.insert_many(batch).on_conflict_replace().execute()
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return f'Inserted {len(new_list)} list values'
|
||||||
169
app/routers_v3/draftpicks.py
Normal file
169
app/routers_v3/draftpicks.py
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from typing import List, Optional
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, DraftPick, Team, model_to_dict, chunked
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/draftpicks',
|
||||||
|
tags=['draftpicks']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DraftPickModel(pydantic.BaseModel):
|
||||||
|
overall: Optional[int] = None
|
||||||
|
round: int
|
||||||
|
origowner_id: int
|
||||||
|
owner_id: Optional[int] = None
|
||||||
|
season: int
|
||||||
|
player_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
class DraftPickList(pydantic.BaseModel):
|
||||||
|
picks: List[DraftPickModel]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_picks(
|
||||||
|
season: int, owner_team_abbrev: list = Query(default=None), orig_team_abbrev: list = Query(default=None),
|
||||||
|
owner_team_id: list = Query(default=None), orig_team_id: list = Query(default=None),
|
||||||
|
pick_round_start: Optional[int] = None, pick_round_end: Optional[int] = None, traded: Optional[bool] = None,
|
||||||
|
overall: Optional[int] = None, overall_start: Optional[int] = None, overall_end: Optional[int] = None,
|
||||||
|
short_output: Optional[bool] = False, sort: Optional[str] = None, limit: Optional[int] = None,
|
||||||
|
player_id: list = Query(default=None), player_taken: Optional[bool] = None):
|
||||||
|
all_picks = DraftPick.select().where(DraftPick.season == season)
|
||||||
|
|
||||||
|
if owner_team_abbrev is not None:
|
||||||
|
team_list = []
|
||||||
|
for x in owner_team_abbrev:
|
||||||
|
team_list.append(Team.get_season(x, season))
|
||||||
|
all_picks = all_picks.where(
|
||||||
|
(DraftPick.owner << team_list) | (DraftPick.owner << team_list)
|
||||||
|
)
|
||||||
|
|
||||||
|
if orig_team_abbrev is not None:
|
||||||
|
team_list = []
|
||||||
|
for x in orig_team_abbrev:
|
||||||
|
team_list.append(Team.get_season(x, season))
|
||||||
|
all_picks = all_picks.where(
|
||||||
|
(DraftPick.origowner << team_list) | (DraftPick.origowner << team_list)
|
||||||
|
)
|
||||||
|
|
||||||
|
if owner_team_id is not None:
|
||||||
|
all_picks = all_picks.where(
|
||||||
|
(DraftPick.owner_id << owner_team_id) | (DraftPick.owner_id << owner_team_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if orig_team_id is not None:
|
||||||
|
all_picks = all_picks.where(
|
||||||
|
(DraftPick.origowner_id << orig_team_id) | (DraftPick.origowner_id << orig_team_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if pick_round_start is not None and pick_round_end is not None and pick_round_end < pick_round_start:
|
||||||
|
raise HTTPException(status_code=400, detail=f'pick_round_end must be greater than or equal to pick_round_start')
|
||||||
|
|
||||||
|
if player_id is not None:
|
||||||
|
all_picks = all_picks.where(DraftPick.player_id << player_id)
|
||||||
|
if pick_round_start is not None:
|
||||||
|
all_picks = all_picks.where(DraftPick.round >= pick_round_start)
|
||||||
|
if pick_round_end is not None:
|
||||||
|
all_picks = all_picks.where(DraftPick.round <= pick_round_end)
|
||||||
|
if traded is not None:
|
||||||
|
all_picks = all_picks.where(DraftPick.origowner != DraftPick.owner)
|
||||||
|
if overall is not None:
|
||||||
|
all_picks = all_picks.where(DraftPick.overall == overall)
|
||||||
|
if overall_start is not None:
|
||||||
|
all_picks = all_picks.where(DraftPick.overall >= overall_start)
|
||||||
|
if overall_end is not None:
|
||||||
|
all_picks = all_picks.where(DraftPick.overall <= overall_end)
|
||||||
|
if player_taken is not None:
|
||||||
|
all_picks = all_picks.where(DraftPick.player.is_null(not player_taken))
|
||||||
|
if limit is not None:
|
||||||
|
all_picks = all_picks.limit(limit)
|
||||||
|
|
||||||
|
if sort is not None:
|
||||||
|
if sort == 'order-asc':
|
||||||
|
all_picks = all_picks.order_by(DraftPick.overall)
|
||||||
|
elif sort == 'order-desc':
|
||||||
|
all_picks = all_picks.order_by(-DraftPick.overall)
|
||||||
|
|
||||||
|
return_picks = {'count': all_picks.count(), 'picks': []}
|
||||||
|
for line in all_picks:
|
||||||
|
return_picks['picks'].append(model_to_dict(line, recurse=not short_output))
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return return_picks
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{pick_id}')
|
||||||
|
async def get_one_pick(pick_id: int, short_output: Optional[bool] = False):
|
||||||
|
this_pick = DraftPick.get_or_none(DraftPick.id == pick_id)
|
||||||
|
if this_pick is not None:
|
||||||
|
r_pick = model_to_dict(this_pick, recurse=not short_output)
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Pick ID {pick_id} not found')
|
||||||
|
db.close()
|
||||||
|
return r_pick
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{pick_id}')
|
||||||
|
async def patch_pick(pick_id: int, new_pick: DraftPickModel, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_pick - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
if DraftPick.get_or_none(DraftPick.id == pick_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Pick ID {pick_id} not found')
|
||||||
|
|
||||||
|
DraftPick.update(**new_pick.dict()).where(DraftPick.id == pick_id).execute()
|
||||||
|
r_pick = model_to_dict(DraftPick.get_by_id(pick_id))
|
||||||
|
db.close()
|
||||||
|
return r_pick
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'post_picks - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
new_picks = []
|
||||||
|
for pick in p_list.picks:
|
||||||
|
dupe = DraftPick.get_or_none(DraftPick.season == pick.season, DraftPick.overall == pick.overall)
|
||||||
|
if dupe:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f'Pick # {pick.overall} already exists for season {pick.season}'
|
||||||
|
)
|
||||||
|
|
||||||
|
new_picks.append(pick.dict())
|
||||||
|
|
||||||
|
with db.atomic():
|
||||||
|
for batch in chunked(new_picks, 15):
|
||||||
|
DraftPick.insert_many(batch).on_conflict_replace().execute()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return f'Inserted {len(new_picks)} picks'
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete('/{pick_id}')
|
||||||
|
async def delete_pick(pick_id: int, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'delete_pick - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_pick = DraftPick.get_or_none(DraftPick.id == pick_id)
|
||||||
|
if this_pick is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Pick ID {pick_id} not found')
|
||||||
|
|
||||||
|
count = this_pick.delete_instance()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if count == 1:
|
||||||
|
return f'Draft pick {pick_id} has been deleted'
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=500, detail=f'Draft pick {pick_id} could not be deleted')
|
||||||
|
|
||||||
193
app/routers_v3/fieldingstats.py
Normal file
193
app/routers_v3/fieldingstats.py
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from typing import List, Optional, Literal
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, BattingStat, Team, Player, Current, model_to_dict, chunked, fn, per_season_weeks
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
|
||||||
|
|
||||||
|
logging.basicConfig(filename=LOG_DATA['filename'], format=LOG_DATA['format'], level=LOG_DATA['log_level'])
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/fieldingstats',
|
||||||
|
tags=['fieldingstats']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_fieldingstats(
|
||||||
|
season: int, s_type: Optional[str] = 'regular', team_abbrev: list = Query(default=None),
|
||||||
|
player_name: list = Query(default=None), player_id: list = Query(default=None),
|
||||||
|
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None),
|
||||||
|
position: list = Query(default=None), limit: Optional[int] = None, sort: Optional[str] = None,
|
||||||
|
short_output: Optional[bool] = True):
|
||||||
|
if 'post' in s_type.lower():
|
||||||
|
all_stats = BattingStat.post_season(season)
|
||||||
|
if all_stats.count() == 0:
|
||||||
|
db.close()
|
||||||
|
return {'count': 0, 'stats': []}
|
||||||
|
elif s_type.lower() in ['combined', 'total', 'all']:
|
||||||
|
all_stats = BattingStat.combined_season(season)
|
||||||
|
if all_stats.count() == 0:
|
||||||
|
db.close()
|
||||||
|
return {'count': 0, 'stats': []}
|
||||||
|
else:
|
||||||
|
all_stats = BattingStat.regular_season(season)
|
||||||
|
if all_stats.count() == 0:
|
||||||
|
db.close()
|
||||||
|
return {'count': 0, 'stats': []}
|
||||||
|
|
||||||
|
all_stats = all_stats.where(
|
||||||
|
(BattingStat.xch > 0) | (BattingStat.pb > 0) | (BattingStat.sbc > 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
if position is not None:
|
||||||
|
all_stats = all_stats.where(BattingStat.pos << [x.upper() for x in position])
|
||||||
|
if team_abbrev is not None:
|
||||||
|
t_query = Team.select().where(Team.abbrev << [x.upper() for x in team_abbrev])
|
||||||
|
all_stats = all_stats.where(BattingStat.team << t_query)
|
||||||
|
if player_name is not None or player_id is not None:
|
||||||
|
if player_id:
|
||||||
|
all_stats = all_stats.where(BattingStat.player_id << player_id)
|
||||||
|
else:
|
||||||
|
p_query = Player.select_season(season).where(fn.Lower(Player.name) << [x.lower() for x in player_name])
|
||||||
|
all_stats = all_stats.where(BattingStat.player << p_query)
|
||||||
|
if game_num:
|
||||||
|
all_stats = all_stats.where(BattingStat.game == game_num)
|
||||||
|
|
||||||
|
start = 1
|
||||||
|
end = Current.get(Current.season == season).week
|
||||||
|
if week_start is not None:
|
||||||
|
start = week_start
|
||||||
|
if week_end is not None:
|
||||||
|
end = min(week_end, end)
|
||||||
|
if start > end:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f'Start week {start} is after end week {end} - cannot pull stats'
|
||||||
|
)
|
||||||
|
all_stats = all_stats.where(
|
||||||
|
(BattingStat.week >= start) & (BattingStat.week <= end)
|
||||||
|
)
|
||||||
|
|
||||||
|
if limit:
|
||||||
|
all_stats = all_stats.limit(limit)
|
||||||
|
if sort:
|
||||||
|
if sort == 'newest':
|
||||||
|
all_stats = all_stats.order_by(-BattingStat.week, -BattingStat.game)
|
||||||
|
|
||||||
|
return_stats = {
|
||||||
|
'count': all_stats.count(),
|
||||||
|
'stats': [{
|
||||||
|
'player': x.player_id if short_output else model_to_dict(x.player, recurse=False),
|
||||||
|
'team': x.team_id if short_output else model_to_dict(x.team, recurse=False),
|
||||||
|
'pos': x.pos,
|
||||||
|
'xch': x.xch,
|
||||||
|
'xhit': x.xhit,
|
||||||
|
'error': x.error,
|
||||||
|
'pb': x.pb,
|
||||||
|
'sbc': x.sbc,
|
||||||
|
'csc': x.csc,
|
||||||
|
'week': x.week,
|
||||||
|
'game': x.game,
|
||||||
|
'season': x.season
|
||||||
|
} for x in all_stats]
|
||||||
|
}
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return return_stats
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/totals')
|
||||||
|
async def get_totalstats(
|
||||||
|
season: int, s_type: Literal['regular', 'post', 'total', None] = None, team_abbrev: list = Query(default=None),
|
||||||
|
team_id: list = Query(default=None), player_name: list = Query(default=None),
|
||||||
|
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None),
|
||||||
|
position: list = Query(default=None), sort: Optional[str] = None, player_id: list = Query(default=None),
|
||||||
|
group_by: Literal['team', 'player', 'playerteam'] = 'player', short_output: Optional[bool] = False,
|
||||||
|
min_ch: Optional[int] = 1, week: list = Query(default=None)):
|
||||||
|
|
||||||
|
all_stats = (
|
||||||
|
BattingStat
|
||||||
|
.select(BattingStat.player, BattingStat.pos, fn.SUM(BattingStat.xch).alias('sum_xch'),
|
||||||
|
fn.SUM(BattingStat.xhit).alias('sum_xhit'), fn.SUM(BattingStat.error).alias('sum_error'),
|
||||||
|
fn.SUM(BattingStat.pb).alias('sum_pb'), fn.SUM(BattingStat.sbc).alias('sum_sbc'),
|
||||||
|
fn.SUM(BattingStat.csc).alias('sum_csc'), BattingStat.team)
|
||||||
|
.where(BattingStat.season == season)
|
||||||
|
.having(fn.SUM(BattingStat.xch) >= min_ch)
|
||||||
|
)
|
||||||
|
|
||||||
|
if True in [s_type is not None, week_start is not None, week_end is not None]:
|
||||||
|
weeks = {}
|
||||||
|
if s_type is not None:
|
||||||
|
weeks = per_season_weeks(season, s_type)
|
||||||
|
elif week_start is not None or week_end is not None:
|
||||||
|
if week_start is None or week_end is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail='Both week_start and week_end must be included if either is used.'
|
||||||
|
)
|
||||||
|
weeks['start'] = week_start
|
||||||
|
if week_end < weeks['start']:
|
||||||
|
raise HTTPException(status_code=400, detail='week_end must be greater than or equal to week_start')
|
||||||
|
else:
|
||||||
|
weeks['end'] = week_end
|
||||||
|
|
||||||
|
all_stats = all_stats.where(
|
||||||
|
(BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end'])
|
||||||
|
)
|
||||||
|
|
||||||
|
elif week is not None:
|
||||||
|
all_stats = all_stats.where(BattingStat.week << week)
|
||||||
|
|
||||||
|
if game_num is not None:
|
||||||
|
all_stats = all_stats.where(BattingStat.game << game_num)
|
||||||
|
if position is not None:
|
||||||
|
p_list = [x.upper() for x in position]
|
||||||
|
all_players = Player.select().where(
|
||||||
|
(Player.pos_1 << p_list) | (Player.pos_2 << p_list) | (Player.pos_3 << p_list) | (Player.pos_4 << p_list) |
|
||||||
|
(Player.pos_5 << p_list) | (Player.pos_6 << p_list) | (Player.pos_7 << p_list) | (Player.pos_8 << p_list)
|
||||||
|
)
|
||||||
|
all_stats = all_stats.where(BattingStat.player << all_players)
|
||||||
|
if sort is not None:
|
||||||
|
if sort == 'player':
|
||||||
|
all_stats = all_stats.order_by(BattingStat.player)
|
||||||
|
elif sort == 'team':
|
||||||
|
all_stats = all_stats.order_by(BattingStat.team)
|
||||||
|
if group_by is not None:
|
||||||
|
if group_by == 'team':
|
||||||
|
all_stats = all_stats.group_by(BattingStat.pos, BattingStat.team)
|
||||||
|
elif group_by == 'player':
|
||||||
|
all_stats = all_stats.group_by(BattingStat.pos, BattingStat.player)
|
||||||
|
elif group_by == 'playerteam':
|
||||||
|
all_stats = all_stats.group_by(BattingStat.pos, BattingStat.team, BattingStat.player)
|
||||||
|
if team_id is not None:
|
||||||
|
all_teams = Team.select().where(Team.id << team_id)
|
||||||
|
all_stats = all_stats.where(BattingStat.team << all_teams)
|
||||||
|
elif team_abbrev is not None:
|
||||||
|
all_teams = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev])
|
||||||
|
all_stats = all_stats.where(BattingStat.team << all_teams)
|
||||||
|
|
||||||
|
if player_name is not None:
|
||||||
|
all_players = Player.select().where(fn.Lower(Player.name) << [x.lower() for x in player_name])
|
||||||
|
all_stats = all_stats.where(BattingStat.player << all_players)
|
||||||
|
elif player_id is not None:
|
||||||
|
all_players = Player.select().where(Player.id << player_id)
|
||||||
|
all_stats = all_stats.where(BattingStat.player << all_players)
|
||||||
|
|
||||||
|
return_stats = {
|
||||||
|
'count': sum(1 for i in all_stats if i.sum_xch + i.sum_sbc > 0),
|
||||||
|
'stats': [{
|
||||||
|
'player': x.player_id if short_output else model_to_dict(x.player, recurse=False),
|
||||||
|
'team': x.team_id if short_output else model_to_dict(x.team, recurse=False),
|
||||||
|
'pos': x.pos,
|
||||||
|
'xch': x.sum_xch,
|
||||||
|
'xhit': x.sum_xhit,
|
||||||
|
'error': x.sum_error,
|
||||||
|
'pb': x.sum_pb,
|
||||||
|
'sbc': x.sum_sbc,
|
||||||
|
'csc': x.sum_csc
|
||||||
|
} for x in all_stats if x.sum_xch + x.sum_sbc > 0]
|
||||||
|
}
|
||||||
|
db.close()
|
||||||
|
return return_stats
|
||||||
113
app/routers_v3/keepers.py
Normal file
113
app/routers_v3/keepers.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query, Response
|
||||||
|
from typing import List, Optional
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, Keeper, Player, model_to_dict, chunked, fn
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
|
||||||
|
|
||||||
|
logging.basicConfig(filename=LOG_DATA['filename'], format=LOG_DATA['format'], level=LOG_DATA['log_level'])
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/keepers',
|
||||||
|
tags=['keepers']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class KeeperModel(pydantic.BaseModel):
|
||||||
|
season: int
|
||||||
|
team_id: int
|
||||||
|
player_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class KeeperList(pydantic.BaseModel):
|
||||||
|
count: Optional[int] = None
|
||||||
|
keepers: List[KeeperModel]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_keepers(
|
||||||
|
season: list = Query(default=None), team_id: list = Query(default=None), player_id: list = Query(default=None),
|
||||||
|
short_output: bool = False):
|
||||||
|
all_keepers = Keeper.select()
|
||||||
|
|
||||||
|
if season is not None:
|
||||||
|
all_keepers = all_keepers.where(Keeper.season << season)
|
||||||
|
if team_id is not None:
|
||||||
|
all_keepers = all_keepers.where(Keeper.team_id << team_id)
|
||||||
|
if player_id is not None:
|
||||||
|
all_keepers = all_keepers.where(Keeper.player_id << player_id)
|
||||||
|
|
||||||
|
return_keepers = {
|
||||||
|
'count': all_keepers.count(),
|
||||||
|
'keepers': [model_to_dict(x, recurse=not short_output) for x in all_keepers]
|
||||||
|
}
|
||||||
|
db.close()
|
||||||
|
return return_keepers
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{keeper_id}')
|
||||||
|
async def patch_keeper(
|
||||||
|
keeper_id: int, season: Optional[int] = None, team_id: Optional[int] = None, player_id: Optional[int] = None,
|
||||||
|
token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_keeper - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_keeper = Keeper.get_or_none(Keeper.id == keeper_id)
|
||||||
|
if not this_keeper:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Keeper ID {keeper_id} not found')
|
||||||
|
|
||||||
|
if season is not None:
|
||||||
|
this_keeper.season = season
|
||||||
|
if player_id is not None:
|
||||||
|
this_keeper.player_id = player_id
|
||||||
|
if team_id is not None:
|
||||||
|
this_keeper.team_id = team_id
|
||||||
|
|
||||||
|
if this_keeper.save():
|
||||||
|
r_keeper = model_to_dict(this_keeper)
|
||||||
|
db.close()
|
||||||
|
return r_keeper
|
||||||
|
else:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=500, detail=f'Unable to patch keeper {keeper_id}')
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_keepers(k_list: KeeperList, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'post_keepers - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
new_keepers = []
|
||||||
|
for keeper in k_list.keepers:
|
||||||
|
new_keepers.append(keeper.dict())
|
||||||
|
|
||||||
|
with db.atomic():
|
||||||
|
for batch in chunked(new_keepers, 14):
|
||||||
|
Keeper.insert_many(batch).on_conflict_replace().execute()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return f'Inserted {len(new_keepers)} keepers'
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete('/{keeper_id}')
|
||||||
|
async def delete_keeper(keeper_id: int, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'delete_keeper - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_keeper = Keeper.get_or_none(Keeper.id == keeper_id)
|
||||||
|
if not this_keeper:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Keeper ID {keeper_id} not found')
|
||||||
|
|
||||||
|
count = this_keeper.delete_instance()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if count == 1:
|
||||||
|
return f'Keeper ID {keeper_id} has been deleted'
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=500, detail=f'Keeper ID {keeper_id} could not be deleted')
|
||||||
|
|
||||||
|
|
||||||
152
app/routers_v3/managers.py
Normal file
152
app/routers_v3/managers.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from typing import List, Optional
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, Manager, Team, Current, model_to_dict, fn
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/managers',
|
||||||
|
tags=['managers']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerModel(pydantic.BaseModel):
|
||||||
|
name: str
|
||||||
|
image: Optional[str] = None
|
||||||
|
headline: Optional[str] = None
|
||||||
|
bio: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_managers(
|
||||||
|
name: list = Query(default=None), active: Optional[bool] = None, short_output: Optional[bool] = False):
|
||||||
|
if active is not None:
|
||||||
|
current = Current.latest()
|
||||||
|
t_query = Team.select_season(current.season)
|
||||||
|
t_query = t_query.where(
|
||||||
|
~(Team.abbrev.endswith('IL')) & ~(Team.abbrev.endswith('MiL'))
|
||||||
|
)
|
||||||
|
logging.info(f'tquery: {t_query}')
|
||||||
|
a_mgr = []
|
||||||
|
i_mgr = []
|
||||||
|
|
||||||
|
for x in t_query:
|
||||||
|
logging.info(f'Team: {x.abbrev} / mgr1: {x.manager1} / mgr2: {x.manager2}')
|
||||||
|
if x.manager1 is not None:
|
||||||
|
a_mgr.append(x.manager1)
|
||||||
|
logging.info(f'appending {x.manager1.name}')
|
||||||
|
if x.manager2 is not None:
|
||||||
|
a_mgr.append(x.manager2)
|
||||||
|
logging.info(f'appending {x.manager2.name}')
|
||||||
|
|
||||||
|
logging.info(f'a_mgr: {a_mgr}')
|
||||||
|
if active:
|
||||||
|
final_mgrs = [model_to_dict(y, recurse=not short_output) for y in a_mgr]
|
||||||
|
else:
|
||||||
|
logging.info(f'checking inactive')
|
||||||
|
for z in Manager.select():
|
||||||
|
logging.info(f'checking: {z.name}')
|
||||||
|
if z not in a_mgr:
|
||||||
|
logging.info(f'+inactive: {z.name}')
|
||||||
|
i_mgr.append(z)
|
||||||
|
final_mgrs = [model_to_dict(y, recurse=not short_output) for y in i_mgr]
|
||||||
|
|
||||||
|
return_managers = {
|
||||||
|
'count': len(final_mgrs),
|
||||||
|
'managers': final_mgrs
|
||||||
|
}
|
||||||
|
|
||||||
|
else:
|
||||||
|
all_managers = Manager.select()
|
||||||
|
if name is not None:
|
||||||
|
name_list = [x.lower() for x in name]
|
||||||
|
all_managers = all_managers.where(fn.Lower(Manager.name) << name_list)
|
||||||
|
|
||||||
|
return_managers = {
|
||||||
|
'count': all_managers.count(),
|
||||||
|
'managers': [model_to_dict(x, recurse=not short_output) for x in all_managers]
|
||||||
|
}
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return return_managers
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{manager_id}')
|
||||||
|
async def get_one_manager(manager_id: int, short_output: Optional[bool] = False):
|
||||||
|
this_manager = Manager.get_or_none(Manager.id == manager_id)
|
||||||
|
if this_manager is not None:
|
||||||
|
r_manager = model_to_dict(this_manager, recurse=not short_output)
|
||||||
|
db.close()
|
||||||
|
return r_manager
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Manager {manager_id} not found')
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{manager_id}')
|
||||||
|
async def patch_manager(
|
||||||
|
manager_id: int, name: Optional[str] = None, image: Optional[str] = None, headline: Optional[str] = None,
|
||||||
|
bio: Optional[str] = None, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_manager - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_manager = Manager.get_or_none(Manager.id == manager_id)
|
||||||
|
if this_manager is None:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Manager ID {manager_id} not found')
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
this_manager.name = name
|
||||||
|
if image is not None:
|
||||||
|
this_manager.image = image
|
||||||
|
if headline is not None:
|
||||||
|
this_manager.headline = headline
|
||||||
|
if bio is not None:
|
||||||
|
this_manager.bio = bio
|
||||||
|
|
||||||
|
if this_manager.save() == 1:
|
||||||
|
r_manager = model_to_dict(this_manager)
|
||||||
|
db.close()
|
||||||
|
return r_manager
|
||||||
|
else:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=500, detail=f'Unable to patch manager {this_manager}')
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_manager(new_manager: ManagerModel, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'post_manager - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_manager = Manager(**new_manager.dict())
|
||||||
|
|
||||||
|
if this_manager.save():
|
||||||
|
r_manager = model_to_dict(this_manager)
|
||||||
|
db.close()
|
||||||
|
return r_manager
|
||||||
|
else:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=500, detail=f'Unable to post manager {this_manager.name}')
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete('/{manager_id}')
|
||||||
|
async def delete_manager(manager_id: int, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'delete_manager - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_manager = Manager.get_or_none(Manager.id == manager_id)
|
||||||
|
if this_manager is None:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Manager ID {manager_id} not found')
|
||||||
|
|
||||||
|
count = this_manager.delete_instance()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if count == 1:
|
||||||
|
return f'Manager {manager_id} has been deleted'
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=500, detail=f'Manager {manager_id} could not be deleted')
|
||||||
267
app/routers_v3/pitchingstats.py
Normal file
267
app/routers_v3/pitchingstats.py
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from typing import List, Optional, Literal
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, PitchingStat, Team, Player, Current, model_to_dict, chunked, fn, per_season_weeks
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
|
||||||
|
|
||||||
|
logging.basicConfig(filename=LOG_DATA['filename'], format=LOG_DATA['format'], level=LOG_DATA['log_level'])
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/pitchingstats',
|
||||||
|
tags=['pitchingstats']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PitStatModel(pydantic.BaseModel):
|
||||||
|
player_id: int
|
||||||
|
team_id: int
|
||||||
|
ip: Optional[float] = 0.0
|
||||||
|
hit: Optional[int] = 0
|
||||||
|
run: Optional[int] = 0
|
||||||
|
erun: Optional[int] = 0
|
||||||
|
so: Optional[int] = 0
|
||||||
|
bb: Optional[int] = 0
|
||||||
|
hbp: Optional[int] = 0
|
||||||
|
wp: Optional[int] = 0
|
||||||
|
balk: Optional[int] = 0
|
||||||
|
hr: Optional[int] = 0
|
||||||
|
gs: Optional[int] = 0
|
||||||
|
win: Optional[int] = 0
|
||||||
|
loss: Optional[int] = 0
|
||||||
|
hold: Optional[int] = 0
|
||||||
|
sv: Optional[int] = 0
|
||||||
|
bsv: Optional[int] = 0
|
||||||
|
ir: Optional[int] = 0
|
||||||
|
irs: Optional[int] = 0
|
||||||
|
week: int
|
||||||
|
game: int
|
||||||
|
season: int
|
||||||
|
|
||||||
|
|
||||||
|
class PitStatList(pydantic.BaseModel):
|
||||||
|
count: int
|
||||||
|
stats: List[PitStatModel]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_pitstats(
|
||||||
|
season: int, s_type: Optional[str] = 'regular', team_abbrev: list = Query(default=None),
|
||||||
|
player_name: list = Query(default=None), player_id: list = Query(default=None),
|
||||||
|
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None),
|
||||||
|
limit: Optional[int] = None, ip_min: Optional[float] = None, sort: Optional[str] = None,
|
||||||
|
short_output: Optional[bool] = True):
|
||||||
|
if 'post' in s_type.lower():
|
||||||
|
all_stats = PitchingStat.post_season(season)
|
||||||
|
if all_stats.count() == 0:
|
||||||
|
db.close()
|
||||||
|
return {'count': 0, 'stats': []}
|
||||||
|
elif s_type.lower() in ['combined', 'total', 'all']:
|
||||||
|
all_stats = PitchingStat.combined_season(season)
|
||||||
|
if all_stats.count() == 0:
|
||||||
|
db.close()
|
||||||
|
return {'count': 0, 'stats': []}
|
||||||
|
else:
|
||||||
|
all_stats = PitchingStat.regular_season(season)
|
||||||
|
if all_stats.count() == 0:
|
||||||
|
db.close()
|
||||||
|
return {'count': 0, 'stats': []}
|
||||||
|
|
||||||
|
if team_abbrev is not None:
|
||||||
|
t_query = Team.select().where(Team.abbrev << [x.upper() for x in team_abbrev])
|
||||||
|
all_stats = all_stats.where(PitchingStat.team << t_query)
|
||||||
|
if player_name is not None or player_id is not None:
|
||||||
|
if player_id:
|
||||||
|
all_stats = all_stats.where(PitchingStat.player_id << player_id)
|
||||||
|
else:
|
||||||
|
p_query = Player.select_season(season).where(fn.Lower(Player.name) << [x.lower() for x in player_name])
|
||||||
|
all_stats = all_stats.where(PitchingStat.player << p_query)
|
||||||
|
if game_num:
|
||||||
|
all_stats = all_stats.where(PitchingStat.game == game_num)
|
||||||
|
if ip_min is not None:
|
||||||
|
all_stats = all_stats.where(PitchingStat.ip >= ip_min)
|
||||||
|
|
||||||
|
start = 1
|
||||||
|
end = Current.get(Current.season == season).week
|
||||||
|
if week_start is not None:
|
||||||
|
start = week_start
|
||||||
|
if week_end is not None:
|
||||||
|
end = min(week_end, end)
|
||||||
|
if start > end:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f'Start week {start} is after end week {end} - cannot pull stats'
|
||||||
|
)
|
||||||
|
all_stats = all_stats.where(
|
||||||
|
(PitchingStat.week >= start) & (PitchingStat.week <= end)
|
||||||
|
)
|
||||||
|
|
||||||
|
if limit:
|
||||||
|
all_stats = all_stats.limit(limit)
|
||||||
|
if sort:
|
||||||
|
if sort == 'newest':
|
||||||
|
all_stats = all_stats.order_by(-PitchingStat.week, -PitchingStat.game)
|
||||||
|
|
||||||
|
return_stats = {
|
||||||
|
'count': all_stats.count(),
|
||||||
|
'stats': [model_to_dict(x, recurse=not short_output) for x in all_stats]
|
||||||
|
}
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return return_stats
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/totals')
|
||||||
|
async def get_totalstats(
|
||||||
|
season: int, s_type: Literal['regular', 'post', 'total', None] = None, team_abbrev: list = Query(default=None),
|
||||||
|
team_id: list = Query(default=None), player_name: list = Query(default=None),
|
||||||
|
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None),
|
||||||
|
is_sp: Optional[bool] = None, ip_min: Optional[float] = 0.25, sort: Optional[str] = None,
|
||||||
|
player_id: list = Query(default=None), short_output: Optional[bool] = False,
|
||||||
|
group_by: Literal['team', 'player', 'playerteam'] = 'player', week: list = Query(default=None)):
|
||||||
|
if sum(1 for x in [s_type, (week_start or week_end), week] if x is not None) > 1:
|
||||||
|
raise HTTPException(status_code=400, detail=f'Only one of s_type, week_start/week_end, or week may be used.')
|
||||||
|
|
||||||
|
all_stats = (
|
||||||
|
PitchingStat
|
||||||
|
.select(PitchingStat.player, fn.SUM(PitchingStat.ip).alias('sum_ip'),
|
||||||
|
fn.SUM(PitchingStat.hit).alias('sum_hit'), fn.SUM(PitchingStat.run).alias('sum_run'),
|
||||||
|
fn.SUM(PitchingStat.erun).alias('sum_erun'), fn.SUM(PitchingStat.so).alias('sum_so'),
|
||||||
|
fn.SUM(PitchingStat.bb).alias('sum_bb'), fn.SUM(PitchingStat.hbp).alias('sum_hbp'),
|
||||||
|
fn.SUM(PitchingStat.wp).alias('sum_wp'), fn.SUM(PitchingStat.balk).alias('sum_balk'),
|
||||||
|
fn.SUM(PitchingStat.hr).alias('sum_hr'), fn.SUM(PitchingStat.ir).alias('sum_ir'),
|
||||||
|
fn.SUM(PitchingStat.win).alias('sum_win'), fn.SUM(PitchingStat.loss).alias('sum_loss'),
|
||||||
|
fn.SUM(PitchingStat.hold).alias('sum_hold'), fn.SUM(PitchingStat.sv).alias('sum_sv'),
|
||||||
|
fn.SUM(PitchingStat.bsv).alias('sum_bsv'), fn.SUM(PitchingStat.irs).alias('sum_irs'),
|
||||||
|
fn.SUM(PitchingStat.gs).alias('sum_gs'), PitchingStat.team)
|
||||||
|
.where(PitchingStat.season == season)
|
||||||
|
.having(fn.SUM(PitchingStat.ip) >= ip_min)
|
||||||
|
)
|
||||||
|
|
||||||
|
if True in [s_type is not None, week_start is not None, week_end is not None]:
|
||||||
|
weeks = {}
|
||||||
|
if s_type is not None:
|
||||||
|
weeks = per_season_weeks(season, s_type)
|
||||||
|
elif week_start is not None or week_end is not None:
|
||||||
|
if week_start is None or week_end is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail='Both week_start and week_end must be included if either is used.'
|
||||||
|
)
|
||||||
|
weeks['start'] = week_start
|
||||||
|
if week_end < weeks['start']:
|
||||||
|
raise HTTPException(status_code=400, detail='week_end must be greater than or equal to week_start')
|
||||||
|
else:
|
||||||
|
weeks['end'] = week_end
|
||||||
|
|
||||||
|
all_stats = all_stats.where(
|
||||||
|
(PitchingStat.week >= weeks['start']) & (PitchingStat.week <= weeks['end'])
|
||||||
|
)
|
||||||
|
|
||||||
|
elif week is not None:
|
||||||
|
all_stats = all_stats.where(PitchingStat.week << week)
|
||||||
|
|
||||||
|
if game_num is not None:
|
||||||
|
all_stats = all_stats.where(PitchingStat.game << game_num)
|
||||||
|
if is_sp is not None:
|
||||||
|
if is_sp:
|
||||||
|
all_stats = all_stats.where(PitchingStat.gs == 1)
|
||||||
|
if not is_sp:
|
||||||
|
all_stats = all_stats.where(PitchingStat.gs == 0)
|
||||||
|
if sort is not None:
|
||||||
|
if sort == 'player':
|
||||||
|
all_stats = all_stats.order_by(PitchingStat.player)
|
||||||
|
elif sort == 'team':
|
||||||
|
all_stats = all_stats.order_by(PitchingStat.team)
|
||||||
|
if group_by is not None:
|
||||||
|
if group_by == 'team':
|
||||||
|
all_stats = all_stats.group_by(PitchingStat.team)
|
||||||
|
elif group_by == 'player':
|
||||||
|
all_stats = all_stats.group_by(PitchingStat.player)
|
||||||
|
elif group_by == 'playerteam':
|
||||||
|
all_stats = all_stats.group_by(PitchingStat.team, PitchingStat.player)
|
||||||
|
if team_id is not None:
|
||||||
|
all_teams = Team.select().where(Team.id << team_id)
|
||||||
|
all_stats = all_stats.where(PitchingStat.team << all_teams)
|
||||||
|
elif team_abbrev is not None:
|
||||||
|
all_teams = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev])
|
||||||
|
all_stats = all_stats.where(PitchingStat.team << all_teams)
|
||||||
|
if player_name is not None:
|
||||||
|
all_players = Player.select().where(fn.Lower(Player.name) << [x.lower() for x in player_name])
|
||||||
|
all_stats = all_stats.where(PitchingStat.player << all_players)
|
||||||
|
elif player_id is not None:
|
||||||
|
all_players = Player.select().where(Player.id << player_id)
|
||||||
|
all_stats = all_stats.where(PitchingStat.player << all_players)
|
||||||
|
|
||||||
|
return_stats = {
|
||||||
|
'count': all_stats.count(),
|
||||||
|
'stats': [{
|
||||||
|
'player': x.player_id if short_output else model_to_dict(x.player, recurse=False),
|
||||||
|
'team': x.team_id if short_output else model_to_dict(x.team, recurse=False),
|
||||||
|
'ip': x.sum_ip,
|
||||||
|
'hit': x.sum_hit,
|
||||||
|
'run': x.sum_run,
|
||||||
|
'erun': x.sum_erun,
|
||||||
|
'so': x.sum_so,
|
||||||
|
'bb': x.sum_bb,
|
||||||
|
'hbp': x.sum_hbp,
|
||||||
|
'wp': x.sum_wp,
|
||||||
|
'balk': x.sum_balk,
|
||||||
|
'hr': x.sum_hr,
|
||||||
|
'ir': x.sum_ir,
|
||||||
|
'irs': x.sum_irs,
|
||||||
|
'gs': x.sum_gs,
|
||||||
|
'win': x.sum_win,
|
||||||
|
'loss': x.sum_loss,
|
||||||
|
'hold': x.sum_hold,
|
||||||
|
'sv': x.sum_sv,
|
||||||
|
'bsv': x.sum_bsv
|
||||||
|
} for x in all_stats]
|
||||||
|
}
|
||||||
|
db.close()
|
||||||
|
return return_stats
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{stat_id}')
|
||||||
|
async def patch_pitstats(stat_id: int, new_stats: PitStatModel, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_pitstats - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
if PitchingStat.get_or_none(PitchingStat.id == stat_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Stat ID {stat_id} not found')
|
||||||
|
|
||||||
|
PitchingStat.update(**new_stats.dict()).where(PitchingStat.id == stat_id).execute()
|
||||||
|
r_stat = model_to_dict(PitchingStat.get_by_id(stat_id))
|
||||||
|
db.close()
|
||||||
|
return r_stat
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'post_pitstats - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
all_stats = []
|
||||||
|
|
||||||
|
for x in s_list.stats:
|
||||||
|
team = Team.get_or_none(Team.id == x.team_id)
|
||||||
|
this_player = Player.get_or_none(Player.id == x.player_id)
|
||||||
|
if team is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {x.team_id} not found')
|
||||||
|
if this_player is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found')
|
||||||
|
|
||||||
|
all_stats.append(PitchingStat(**x.dict()))
|
||||||
|
|
||||||
|
with db.atomic():
|
||||||
|
for batch in chunked(all_stats, 15):
|
||||||
|
PitchingStat.insert_many(batch).on_conflict_replace().execute()
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return f'Added {len(all_stats)} batting lines'
|
||||||
190
app/routers_v3/players.py
Normal file
190
app/routers_v3/players.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query, Response
|
||||||
|
from typing import List, Optional
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
from ..db_engine import db, Player, model_to_dict, chunked, fn
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
|
||||||
|
|
||||||
|
logging.basicConfig(filename=LOG_DATA['filename'], format=LOG_DATA['format'], level=LOG_DATA['log_level'])
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/players',
|
||||||
|
tags=['players']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerModel(pydantic.BaseModel):
|
||||||
|
name: str
|
||||||
|
wara: float
|
||||||
|
image: str
|
||||||
|
image2: Optional[str] = None
|
||||||
|
team_id: int
|
||||||
|
season: int
|
||||||
|
pitcher_injury: Optional[int] = None
|
||||||
|
pos_1: str
|
||||||
|
pos_2: Optional[str] = None
|
||||||
|
pos_3: Optional[str] = None
|
||||||
|
pos_4: Optional[str] = None
|
||||||
|
pos_5: Optional[str] = None
|
||||||
|
pos_6: Optional[str] = None
|
||||||
|
pos_7: Optional[str] = None
|
||||||
|
pos_8: Optional[str] = None
|
||||||
|
vanity_card: Optional[str] = None
|
||||||
|
headshot: Optional[str] = None
|
||||||
|
last_game: Optional[str] = None
|
||||||
|
last_game2: Optional[str] = None
|
||||||
|
il_return: Optional[str] = None
|
||||||
|
demotion_week: Optional[int] = None
|
||||||
|
strat_code: Optional[str] = None
|
||||||
|
bbref_id: Optional[str] = None
|
||||||
|
injury_rating: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerList(pydantic.BaseModel):
|
||||||
|
players: List[PlayerModel]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_players(
|
||||||
|
season: Optional[int], name: Optional[str] = None, team_id: list = Query(default=None),
|
||||||
|
pos: list = Query(default=None), strat_code: list = Query(default=None), is_injured: Optional[bool] = None,
|
||||||
|
sort: Optional[str] = None, short_output: Optional[bool] = False, csv: Optional[bool] = False):
|
||||||
|
all_players = Player.select_season(season)
|
||||||
|
|
||||||
|
if team_id is not None:
|
||||||
|
all_players = all_players.where(Player.team_id << team_id)
|
||||||
|
|
||||||
|
if strat_code is not None:
|
||||||
|
code_list = [x.lower() for x in strat_code]
|
||||||
|
all_players = all_players.where(fn.Lower(Player.strat_code) << code_list)
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
all_players = all_players.where(fn.lower(Player.name) == name.lower())
|
||||||
|
|
||||||
|
if pos is not None:
|
||||||
|
p_list = [x.upper() for x in pos]
|
||||||
|
all_players = all_players.where(
|
||||||
|
(Player.pos_1 << p_list) | (Player.pos_2 << p_list) | (Player.pos_3 << p_list) | (Player.pos_4 << p_list) |
|
||||||
|
(Player.pos_5 << p_list) | (Player.pos_6 << p_list) | (Player.pos_7 << p_list) | (Player.pos_8 << p_list)
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_injured is not None:
|
||||||
|
all_players = all_players.where(Player.il_return.is_null(False))
|
||||||
|
|
||||||
|
if sort is not None:
|
||||||
|
if sort == 'cost-asc':
|
||||||
|
all_players = all_players.order_by(Player.wara)
|
||||||
|
elif sort == 'cost-desc':
|
||||||
|
all_players = all_players.order_by(-Player.wara)
|
||||||
|
elif sort == 'name-asc':
|
||||||
|
all_players = all_players.order_by(Player.name)
|
||||||
|
elif sort == 'name-desc':
|
||||||
|
all_players = all_players.order_by(-Player.name)
|
||||||
|
|
||||||
|
print(f'csv: {csv}')
|
||||||
|
if csv:
|
||||||
|
player_list = [
|
||||||
|
['name', 'wara', 'image', 'image2', 'team', 'season', 'pitcher_injury', 'pos_1', 'pos_2', 'pos_3',
|
||||||
|
'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8', 'last_game', 'last_game2', 'il_return', 'demotion_week',
|
||||||
|
'headshot', 'vanity_card', 'strat_code', 'bbref_id', 'injury_rating', 'player_id']
|
||||||
|
]
|
||||||
|
for line in all_players:
|
||||||
|
player_list.append(
|
||||||
|
[
|
||||||
|
line.name, line.wara, line.image, line.image2, line.team.abbrev, line.season, line.pitcher_injury,
|
||||||
|
line.pos_1, line.pos_2, line.pos_3, line.pos_4, line.pos_5, line.pos_6, line.pos_7, line.pos_8,
|
||||||
|
line.last_game, line.last_game2, line.il_return, line.demotion_week, line.headshot,
|
||||||
|
line.vanity_card, line.strat_code.replace(",", "-_-"), line.bbref_id, line.injury_rating, line.id
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return_players = {
|
||||||
|
'count': all_players.count(),
|
||||||
|
'players': DataFrame(player_list).to_csv(header=False, index=False),
|
||||||
|
'csv': True
|
||||||
|
}
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return Response(content=return_players['players'], media_type='text/csv')
|
||||||
|
|
||||||
|
else:
|
||||||
|
return_players = {
|
||||||
|
'count': all_players.count(),
|
||||||
|
'players': [model_to_dict(x, recurse=not short_output) for x in all_players]
|
||||||
|
}
|
||||||
|
db.close()
|
||||||
|
return return_players
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{player_id}')
|
||||||
|
async def get_one_player(player_id: int, short_output: Optional[bool] = False):
|
||||||
|
this_player = Player.get_or_none(Player.id == player_id)
|
||||||
|
if this_player:
|
||||||
|
r_player = model_to_dict(this_player, recurse=not short_output)
|
||||||
|
else:
|
||||||
|
r_player = None
|
||||||
|
db.close()
|
||||||
|
return r_player
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{player_id}')
|
||||||
|
async def patch_player(
|
||||||
|
player_id: int, new_player: PlayerModel, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_player - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
if Player.get_or_none(Player.id == player_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Player ID {player_id} not found')
|
||||||
|
|
||||||
|
Player.update(**new_player.dict()).where(Player.id == player_id).execute()
|
||||||
|
r_player = model_to_dict(Player.get_by_id(player_id))
|
||||||
|
db.close()
|
||||||
|
return r_player
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_players(p_list: PlayerList, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'post_players - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
new_players = []
|
||||||
|
for player in p_list.players:
|
||||||
|
dupe = Player.get_or_none(Player.season == player.season, Player.name == player.name)
|
||||||
|
if dupe:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f'Player name {player.name} already in use in Season {player.season}'
|
||||||
|
)
|
||||||
|
|
||||||
|
new_players.append(player.dict())
|
||||||
|
|
||||||
|
with db.atomic():
|
||||||
|
for batch in chunked(new_players, 15):
|
||||||
|
Player.insert_many(batch).on_conflict_replace().execute()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return f'Inserted {len(new_players)} players'
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete('/{player_id}')
|
||||||
|
async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'delete_player - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_player = Player.get_or_none(Player.id == player_id)
|
||||||
|
if not this_player:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Player ID {player_id} not found')
|
||||||
|
|
||||||
|
count = this_player.delete_instance()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if count == 1:
|
||||||
|
return f'Player {player_id} has been deleted'
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=500, detail=f'Player {player_id} could not be deleted')
|
||||||
175
app/routers_v3/results.py
Normal file
175
app/routers_v3/results.py
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from typing import List, Optional
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, Result, Team, model_to_dict, chunked
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/results',
|
||||||
|
tags=['results']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ResultModel(pydantic.BaseModel):
|
||||||
|
week: int
|
||||||
|
game: int
|
||||||
|
awayteam_id: int
|
||||||
|
hometeam_id: int
|
||||||
|
awayscore: int
|
||||||
|
homescore: int
|
||||||
|
season: int
|
||||||
|
scorecard_url: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ResultList(pydantic.BaseModel):
|
||||||
|
results: List[ResultModel]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_results(
|
||||||
|
season: int, team_abbrev: list = Query(default=None), week_start: Optional[int] = None,
|
||||||
|
week_end: Optional[int] = None, game_num: list = Query(default=None),
|
||||||
|
away_abbrev: list = Query(default=None), home_abbrev: list = Query(default=None),
|
||||||
|
short_output: Optional[bool] = False):
|
||||||
|
all_results = Result.select_season(season)
|
||||||
|
|
||||||
|
if team_abbrev is not None:
|
||||||
|
team_list = []
|
||||||
|
for x in team_abbrev:
|
||||||
|
team_list.append(Team.get_season(x, season))
|
||||||
|
all_results = all_results.where(
|
||||||
|
(Result.awayteam << team_list) | (Result.hometeam << team_list)
|
||||||
|
)
|
||||||
|
|
||||||
|
if away_abbrev is not None:
|
||||||
|
team_list = []
|
||||||
|
for x in away_abbrev:
|
||||||
|
team_list.append(Team.get_season(x, season))
|
||||||
|
all_results = all_results.where(Result.awayteam << team_list)
|
||||||
|
|
||||||
|
if home_abbrev is not None:
|
||||||
|
team_list = []
|
||||||
|
for x in home_abbrev:
|
||||||
|
team_list.append(Team.get_season(x, season))
|
||||||
|
all_results = all_results.where(Result.hometeam << team_list)
|
||||||
|
|
||||||
|
if game_num is not None:
|
||||||
|
all_results = all_results.where(Result.game << game_num)
|
||||||
|
|
||||||
|
if week_start is not None:
|
||||||
|
all_results = all_results.where(Result.week >= week_start)
|
||||||
|
|
||||||
|
if week_end is not None:
|
||||||
|
all_results = all_results.where(Result.week <= week_end)
|
||||||
|
|
||||||
|
return_results = {
|
||||||
|
'count': all_results.count(),
|
||||||
|
'results': [model_to_dict(x, recurse=not short_output) for x in all_results]
|
||||||
|
}
|
||||||
|
db.close()
|
||||||
|
return return_results
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{result_id}')
|
||||||
|
async def get_one_result(result_id: int, short_output: Optional[bool] = False):
|
||||||
|
this_result = Result.get_or_none(Result.id == result_id)
|
||||||
|
if this_result is not None:
|
||||||
|
r_result = model_to_dict(this_result, recurse=not short_output)
|
||||||
|
else:
|
||||||
|
r_result = None
|
||||||
|
db.close()
|
||||||
|
return r_result
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{result_id}')
|
||||||
|
async def patch_result(
|
||||||
|
result_id: int, week_num: Optional[int] = None, game_num: Optional[int] = None,
|
||||||
|
away_team_id: Optional[int] = None, home_team_id: Optional[int] = None, away_score: Optional[int] = None,
|
||||||
|
home_score: Optional[int] = None, season: Optional[int] = None, scorecard_url: Optional[str] = None,
|
||||||
|
token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_player - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_result = Result.get_or_none(Result.id == result_id)
|
||||||
|
if this_result is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Result ID {result_id} not found')
|
||||||
|
|
||||||
|
if week_num is not None:
|
||||||
|
this_result.week = week_num
|
||||||
|
|
||||||
|
if game_num is not None:
|
||||||
|
this_result.game = game_num
|
||||||
|
|
||||||
|
if away_team_id is not None:
|
||||||
|
this_result.awayteam_id = away_team_id
|
||||||
|
|
||||||
|
if home_team_id is not None:
|
||||||
|
this_result.hometeam_id = home_team_id
|
||||||
|
|
||||||
|
if away_score is not None:
|
||||||
|
this_result.awayscore = away_score
|
||||||
|
|
||||||
|
if home_score is not None:
|
||||||
|
this_result.homescore = home_score
|
||||||
|
|
||||||
|
if season is not None:
|
||||||
|
this_result.season = season
|
||||||
|
|
||||||
|
if scorecard_url is not None:
|
||||||
|
this_result.scorecard_url = scorecard_url
|
||||||
|
|
||||||
|
if this_result.save() == 1:
|
||||||
|
r_result = model_to_dict(this_result)
|
||||||
|
db.close()
|
||||||
|
return r_result
|
||||||
|
else:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=500, detail=f'Unable to patch result {result_id}')
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_results(result_list: ResultList, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_player - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
new_results = []
|
||||||
|
for x in result_list.results:
|
||||||
|
if Team.get_or_none(Team.id == x.awayteam_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {x.awayteam_id} not found')
|
||||||
|
if Team.get_or_none(Team.id == x.hometeam_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {x.hometeam_id} not found')
|
||||||
|
|
||||||
|
new_results.append(x.dict())
|
||||||
|
|
||||||
|
with db.atomic():
|
||||||
|
for batch in chunked(new_results, 15):
|
||||||
|
Result.insert_many(batch).on_conflict_replace().execute()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return f'Inserted {len(new_results)} results'
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete('/{result_id}')
|
||||||
|
async def delete_result(result_id: int, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'delete_result - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_result = Result.get_or_none(Result.id == result_id)
|
||||||
|
if not this_result:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Result ID {result_id} not found')
|
||||||
|
|
||||||
|
count = this_result.delete_instance()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if count == 1:
|
||||||
|
return f'Result {result_id} has been deleted'
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=500, detail=f'Result {result_id} could not be deleted')
|
||||||
|
|
||||||
|
|
||||||
157
app/routers_v3/schedules.py
Normal file
157
app/routers_v3/schedules.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from typing import List, Optional
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, Schedule, Team, model_to_dict, chunked
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/schedules',
|
||||||
|
tags=['schedules']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleModel(pydantic.BaseModel):
|
||||||
|
week: int
|
||||||
|
awayteam_id: int
|
||||||
|
hometeam_id: int
|
||||||
|
gamecount: int
|
||||||
|
season: int
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleList(pydantic.BaseModel):
|
||||||
|
schedules: List[ScheduleModel]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_schedules(
|
||||||
|
season: int, team_abbrev: list = Query(default=None), away_abbrev: list = Query(default=None),
|
||||||
|
home_abbrev: list = Query(default=None), week_start: Optional[int] = None, week_end: Optional[int] = None,
|
||||||
|
short_output: Optional[bool] = True):
|
||||||
|
all_sched = Schedule.select_season(season)
|
||||||
|
|
||||||
|
if team_abbrev is not None:
|
||||||
|
team_list = []
|
||||||
|
for x in team_abbrev:
|
||||||
|
team_list.append(Team.get_season(x, season))
|
||||||
|
all_sched = all_sched.where(
|
||||||
|
(Schedule.awayteam << team_list) | (Schedule.hometeam << team_list)
|
||||||
|
)
|
||||||
|
|
||||||
|
if away_abbrev is not None:
|
||||||
|
team_list = []
|
||||||
|
for x in away_abbrev:
|
||||||
|
team_list.append(Team.get_season(x, season))
|
||||||
|
all_sched = all_sched.where(Schedule.awayteam << team_list)
|
||||||
|
|
||||||
|
if home_abbrev is not None:
|
||||||
|
team_list = []
|
||||||
|
for x in home_abbrev:
|
||||||
|
team_list.append(Team.get_season(x, season))
|
||||||
|
all_sched = all_sched.where(Schedule.hometeam << team_list)
|
||||||
|
|
||||||
|
if week_start is not None:
|
||||||
|
all_sched = all_sched.where(Schedule.week >= week_start)
|
||||||
|
|
||||||
|
if week_end is not None:
|
||||||
|
all_sched = all_sched.where(Schedule.week <= week_end)
|
||||||
|
|
||||||
|
all_sched = all_sched.order_by(Schedule.id)
|
||||||
|
|
||||||
|
return_sched = {
|
||||||
|
'count': all_sched.count(),
|
||||||
|
'schedules': [model_to_dict(x, recurse=not short_output) for x in all_sched]
|
||||||
|
}
|
||||||
|
db.close()
|
||||||
|
return return_sched
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{schedule_id}')
|
||||||
|
async def get_one_schedule(schedule_id: int):
|
||||||
|
this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
|
||||||
|
if this_sched is not None:
|
||||||
|
r_sched = model_to_dict(this_sched)
|
||||||
|
else:
|
||||||
|
r_sched = None
|
||||||
|
db.close()
|
||||||
|
return r_sched
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{schedule_id}')
|
||||||
|
async def patch_schedule(
|
||||||
|
schedule_id: int, week: list = Query(default=None), awayteam_id: Optional[int] = None,
|
||||||
|
hometeam_id: Optional[int] = None, gamecount: Optional[int] = None, season: Optional[int] = None,
|
||||||
|
token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_schedule - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
|
||||||
|
if this_sched is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Schedule ID {schedule_id} not found')
|
||||||
|
|
||||||
|
if week is not None:
|
||||||
|
this_sched.week = week
|
||||||
|
|
||||||
|
if awayteam_id is not None:
|
||||||
|
this_sched.awayteam_id = awayteam_id
|
||||||
|
|
||||||
|
if hometeam_id is not None:
|
||||||
|
this_sched.hometeam_id = hometeam_id
|
||||||
|
|
||||||
|
if gamecount is not None:
|
||||||
|
this_sched.gamecount = gamecount
|
||||||
|
|
||||||
|
if season is not None:
|
||||||
|
this_sched.season = season
|
||||||
|
|
||||||
|
if this_sched.save() == 1:
|
||||||
|
r_sched = model_to_dict(this_sched)
|
||||||
|
db.close()
|
||||||
|
return r_sched
|
||||||
|
else:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=500, detail=f'Unable to patch schedule {schedule_id}')
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'post_schedules - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
new_sched = []
|
||||||
|
for x in sched_list.schedules:
|
||||||
|
if Team.get_or_none(Team.id == x.awayteam_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {x.awayteam_id} not found')
|
||||||
|
if Team.get_or_none(Team.id == x.hometeam_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {x.hometeam_id} not found')
|
||||||
|
|
||||||
|
new_sched.append(x.dict())
|
||||||
|
|
||||||
|
with db.atomic():
|
||||||
|
for batch in chunked(new_sched, 15):
|
||||||
|
Schedule.insert_many(batch).on_conflict_replace().execute()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return f'Inserted {len(new_sched)} schedules'
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete('/{schedule_id}')
|
||||||
|
async def delete_schedule(schedule_id: int, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'delete_schedule - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
|
||||||
|
if this_sched is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Schedule ID {schedule_id} not found')
|
||||||
|
|
||||||
|
count = this_sched.delete_instance()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if count == 1:
|
||||||
|
return f'Schedule {this_sched} has been deleted'
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=500, detail=f'Schedule {this_sched} could not be deleted')
|
||||||
90
app/routers_v3/standings.py
Normal file
90
app/routers_v3/standings.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from typing import List, Optional
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, Standings, Team, Division, model_to_dict, chunked, fn
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/standings',
|
||||||
|
tags=['standings']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_standings(
|
||||||
|
season: int, team_abbrev: list = Query(default=None), league_abbrev: Optional[str] = None,
|
||||||
|
division_abbrev: Optional[str] = None, short_output: Optional[bool] = False):
|
||||||
|
standings = Standings.select_season(season)
|
||||||
|
|
||||||
|
# if standings.count() == 0:
|
||||||
|
# db.close()
|
||||||
|
# raise HTTPException(status_code=404, detail=f'No output for season {season}')
|
||||||
|
|
||||||
|
if team_abbrev is not None:
|
||||||
|
t_query = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev])
|
||||||
|
standings = standings.where(Standings.team << t_query)
|
||||||
|
|
||||||
|
if league_abbrev is not None:
|
||||||
|
l_query = Division.select().where(fn.Lower(Division.league_abbrev) == league_abbrev.lower())
|
||||||
|
standings = standings.where(Standings.team.division << l_query)
|
||||||
|
|
||||||
|
if division_abbrev is not None:
|
||||||
|
d_query = Division.select().where(fn.Lower(Division.division_abbrev) == division_abbrev.lower())
|
||||||
|
standings = standings.where(Standings.team.division << d_query)
|
||||||
|
|
||||||
|
def win_pct(this_team_stan):
|
||||||
|
if this_team_stan.wins + this_team_stan.losses == 0:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return (this_team_stan.wins / (this_team_stan.wins + this_team_stan.losses)) + \
|
||||||
|
(this_team_stan.run_diff * .000001)
|
||||||
|
|
||||||
|
div_teams = [x for x in standings]
|
||||||
|
div_teams.sort(key=lambda team: win_pct(team), reverse=True)
|
||||||
|
|
||||||
|
return_standings = {
|
||||||
|
'count': len(div_teams),
|
||||||
|
'standings': [model_to_dict(x, recurse=not short_output) for x in div_teams]
|
||||||
|
}
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return return_standings
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{stan_id}')
|
||||||
|
async def patch_standings(
|
||||||
|
stan_id, wins: Optional[int] = None, losses: Optional[int] = None, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_standings - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
try:
|
||||||
|
this_stan = Standings.get_by_id(stan_id)
|
||||||
|
except Exception as e:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'No team found with id {stan_id}')
|
||||||
|
|
||||||
|
if wins:
|
||||||
|
this_stan.wins = wins
|
||||||
|
if losses:
|
||||||
|
this_stan.losses = losses
|
||||||
|
|
||||||
|
this_stan.save()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return model_to_dict(this_stan)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('/s{season}/recalculate')
|
||||||
|
async def recalculate_standings(season: int, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'recalculate_standings - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
code = Standings.recalculate(season)
|
||||||
|
db.close()
|
||||||
|
if code == 69:
|
||||||
|
HTTPException(status_code=500, detail=f'Error recreating Standings rows')
|
||||||
|
raise HTTPException(status_code=200, detail=f'Just recalculated standings for season {season}')
|
||||||
249
app/routers_v3/teams.py
Normal file
249
app/routers_v3/teams.py
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from typing import List, Optional, Literal
|
||||||
|
import copy
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, Team, Manager, Division, model_to_dict, chunked, fn
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/teams',
|
||||||
|
tags=['teams']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TeamModel(pydantic.BaseModel):
|
||||||
|
abbrev: str
|
||||||
|
sname: str
|
||||||
|
lname: str
|
||||||
|
gmid: Optional[int] = None
|
||||||
|
gmid2: Optional[int] = None
|
||||||
|
manager1_id: Optional[int] = None
|
||||||
|
manager2_id: Optional[int] = None
|
||||||
|
division_id: Optional[int] = None
|
||||||
|
stadium: Optional[str] = None
|
||||||
|
thumbnail: Optional[str] = None
|
||||||
|
color: Optional[str] = None
|
||||||
|
dice_color: Optional[str] = None
|
||||||
|
season: int
|
||||||
|
|
||||||
|
|
||||||
|
class TeamList(pydantic.BaseModel):
|
||||||
|
teams: List[TeamModel]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_teams(
|
||||||
|
season: Optional[int] = None, owner_id: list = Query(default=None), manager_id: list = Query(default=None),
|
||||||
|
team_abbrev: list = Query(default=None), active_only: Optional[bool] = False,
|
||||||
|
short_output: Optional[bool] = False):
|
||||||
|
if season is not None:
|
||||||
|
all_teams = Team.select_season(season)
|
||||||
|
else:
|
||||||
|
all_teams = Team.select()
|
||||||
|
|
||||||
|
if manager_id is not None:
|
||||||
|
managers = Manager.select().where(Manager.id << manager_id)
|
||||||
|
all_teams = all_teams.where(
|
||||||
|
(Team.manager1_id << managers) | (Team.manager2_id << managers)
|
||||||
|
)
|
||||||
|
if owner_id:
|
||||||
|
all_teams = all_teams.where((Team.gmid << owner_id) | (Team.gmid2 << owner_id))
|
||||||
|
if team_abbrev is not None:
|
||||||
|
team_list = [x.lower() for x in team_abbrev]
|
||||||
|
all_teams = all_teams.where(fn.lower(Team.abbrev) << team_list)
|
||||||
|
if active_only:
|
||||||
|
all_teams = all_teams.where(
|
||||||
|
~(Team.abbrev.endswith('IL')) & ~(Team.abbrev.endswith('MiL'))
|
||||||
|
)
|
||||||
|
|
||||||
|
return_teams = {
|
||||||
|
'count': all_teams.count(),
|
||||||
|
'teams': [model_to_dict(x, recurse=not short_output) for x in all_teams]
|
||||||
|
}
|
||||||
|
db.close()
|
||||||
|
return return_teams
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{team_id}')
|
||||||
|
async def get_one_team(team_id: int):
|
||||||
|
this_team = Team.get_or_none(Team.id == team_id)
|
||||||
|
if this_team:
|
||||||
|
r_team = model_to_dict(this_team)
|
||||||
|
else:
|
||||||
|
r_team = None
|
||||||
|
db.close()
|
||||||
|
return r_team
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{team_id}/roster/{which}')
|
||||||
|
async def get_team_roster(team_id: int, which: Literal['current', 'next'], sort: Optional[str] = None):
|
||||||
|
try:
|
||||||
|
this_team = Team.get_by_id(team_id)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {team_id} not found')
|
||||||
|
|
||||||
|
if which == 'current':
|
||||||
|
full_roster = this_team.get_this_week()
|
||||||
|
else:
|
||||||
|
full_roster = this_team.get_next_week()
|
||||||
|
|
||||||
|
active_players = copy.deepcopy(full_roster['active']['players'])
|
||||||
|
sil_players = copy.deepcopy(full_roster['shortil']['players'])
|
||||||
|
lil_players = copy.deepcopy(full_roster['longil']['players'])
|
||||||
|
full_roster['active']['players'] = []
|
||||||
|
full_roster['shortil']['players'] = []
|
||||||
|
full_roster['longil']['players'] = []
|
||||||
|
|
||||||
|
for player in active_players:
|
||||||
|
full_roster['active']['players'].append(model_to_dict(player))
|
||||||
|
for player in sil_players:
|
||||||
|
full_roster['shortil']['players'].append(model_to_dict(player))
|
||||||
|
for player in lil_players:
|
||||||
|
full_roster['longil']['players'].append(model_to_dict(player))
|
||||||
|
|
||||||
|
if sort:
|
||||||
|
if sort == 'wara-desc':
|
||||||
|
full_roster['active']['players'].sort(key=lambda p: p["wara"], reverse=True)
|
||||||
|
full_roster['active']['players'].sort(key=lambda p: p["wara"], reverse=True)
|
||||||
|
full_roster['active']['players'].sort(key=lambda p: p["wara"], reverse=True)
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return full_roster
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{team_id}')
|
||||||
|
async def patch_team(
|
||||||
|
team_id: int, manager1_id: Optional[int] = None, manager2_id: Optional[int] = None, gmid: Optional[int] = None,
|
||||||
|
gmid2: Optional[int] = None, mascot: Optional[str] = None, stadium: Optional[str] = None,
|
||||||
|
thumbnail: Optional[str] = None, color: Optional[str] = None, abbrev: Optional[str] = None,
|
||||||
|
sname: Optional[str] = None, lname: Optional[str] = None, dice_color: Optional[str] = None,
|
||||||
|
division_id: Optional[int] = None, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_team - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_team = Team.get_or_none(Team.id == team_id)
|
||||||
|
if not this_team:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if abbrev is not None:
|
||||||
|
this_team.abbrev = abbrev
|
||||||
|
if manager1_id is not None:
|
||||||
|
if manager1_id == 0:
|
||||||
|
this_team.manager1 = None
|
||||||
|
else:
|
||||||
|
this_manager = Manager.get_or_none(Manager.id == manager1_id)
|
||||||
|
if not this_manager:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Manager ID {manager1_id} not found')
|
||||||
|
this_team.manager1 = this_manager
|
||||||
|
if manager2_id is not None:
|
||||||
|
if manager2_id == 0:
|
||||||
|
this_team.manager2 = None
|
||||||
|
else:
|
||||||
|
this_manager = Manager.get_or_none(Manager.id == manager2_id)
|
||||||
|
if not this_manager:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Manager ID {manager2_id} not found')
|
||||||
|
this_team.manager2 = this_manager
|
||||||
|
if gmid is not None:
|
||||||
|
this_team.gmid = gmid
|
||||||
|
if gmid2 is not None:
|
||||||
|
if gmid2 == 0:
|
||||||
|
this_team.gmid2 = None
|
||||||
|
else:
|
||||||
|
this_team.gmid2 = gmid2
|
||||||
|
if mascot is not None:
|
||||||
|
if mascot == 'False':
|
||||||
|
this_team.mascot = None
|
||||||
|
else:
|
||||||
|
this_team.mascot = mascot
|
||||||
|
if stadium is not None:
|
||||||
|
this_team.stadium = stadium
|
||||||
|
if thumbnail is not None:
|
||||||
|
this_team.thumbnail = thumbnail
|
||||||
|
if color is not None:
|
||||||
|
this_team.color = color
|
||||||
|
if dice_color is not None:
|
||||||
|
this_team.dice_color = dice_color
|
||||||
|
if sname is not None:
|
||||||
|
this_team.sname = sname
|
||||||
|
if lname is not None:
|
||||||
|
this_team.lname = lname
|
||||||
|
if division_id is not None:
|
||||||
|
if division_id == 0:
|
||||||
|
this_team.division = None
|
||||||
|
else:
|
||||||
|
this_division = Division.get_or_none(Division.id == division_id)
|
||||||
|
if not this_division:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Division ID {division_id} not found')
|
||||||
|
this_team.division = this_division
|
||||||
|
|
||||||
|
if this_team.save():
|
||||||
|
r_team = model_to_dict(this_team)
|
||||||
|
db.close()
|
||||||
|
return r_team
|
||||||
|
else:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=500, detail=f'Unable to patch team {team_id}')
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_team(team_list: TeamList, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'post_team - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
new_teams = []
|
||||||
|
for team in team_list.teams:
|
||||||
|
dupe_team = Team.get_or_none(Team.season == team.season, Team.abbrev == team.abbrev)
|
||||||
|
if dupe_team:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500, detail=f'Team Abbrev {team.abbrev} already in use in Season {team.season}'
|
||||||
|
)
|
||||||
|
|
||||||
|
if team.manager1_id and not Manager.get_or_none(Manager.id == team.manager1_id):
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Manager ID {team.manager1_id} not found')
|
||||||
|
|
||||||
|
if team.manager2_id and not Manager.get_or_none(Manager.id == team.manager2_id):
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Manager ID {team.manager2_id} not found')
|
||||||
|
|
||||||
|
if team.division_id and not Division.get_or_none(Division.id == team.division_id):
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Division ID {team.division_id} not found')
|
||||||
|
|
||||||
|
new_teams.append(team.dict())
|
||||||
|
|
||||||
|
with db.atomic():
|
||||||
|
for batch in chunked(new_teams, 15):
|
||||||
|
Team.insert_many(batch).on_conflict_replace().execute()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return f'Inserted {len(new_teams)} teams'
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{team_id}')
|
||||||
|
async def delete_team(team_id: int, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'delete_team - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
this_team = Team.get_or_none(Team.id == team_id)
|
||||||
|
if not this_team:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {team_id} not found')
|
||||||
|
|
||||||
|
count = this_team.delete_instance()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if count == 1:
|
||||||
|
return f'Team {team_id} has been deleted'
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=500, detail=f'Team {team_id} could not be deleted')
|
||||||
|
|
||||||
153
app/routers_v3/transactions.py
Normal file
153
app/routers_v3/transactions.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query, Response
|
||||||
|
from typing import List, Optional
|
||||||
|
from pandas import DataFrame
|
||||||
|
import logging
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from ..db_engine import db, Transaction, Team, Player, model_to_dict, chunked, fn
|
||||||
|
from ..dependencies import oauth2_scheme, valid_token
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix='/api/v3/transactions',
|
||||||
|
tags=['transactions']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionModel(pydantic.BaseModel):
|
||||||
|
week: int
|
||||||
|
player_id: int
|
||||||
|
oldteam_id: int
|
||||||
|
newteam_id: int
|
||||||
|
season: int
|
||||||
|
moveid: str
|
||||||
|
cancelled: Optional[bool] = False
|
||||||
|
frozen: Optional[bool] = False
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionList(pydantic.BaseModel):
|
||||||
|
count: int
|
||||||
|
moves: List[TransactionModel]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('')
|
||||||
|
async def get_transactions(
|
||||||
|
season, team_abbrev: list = Query(default=None), week_start: Optional[int] = 0,
|
||||||
|
week_end: Optional[int] = None, cancelled: Optional[bool] = None, frozen: Optional[bool] = None,
|
||||||
|
player_name: list = Query(default=None), player_id: list = Query(default=None), move_id: Optional[str] = None,
|
||||||
|
is_trade: Optional[bool] = None, short_output: Optional[bool] = False):
|
||||||
|
if season:
|
||||||
|
transactions = Transaction.select_season(season)
|
||||||
|
else:
|
||||||
|
transactions = Transaction.select()
|
||||||
|
|
||||||
|
if team_abbrev is not None:
|
||||||
|
t_list = [x.upper() for x in team_abbrev]
|
||||||
|
these_teams = Team.select().where((Team.abbrev << t_list))
|
||||||
|
transactions = transactions.where(
|
||||||
|
(Transaction.newteam << these_teams) | (Transaction.oldteam << these_teams)
|
||||||
|
)
|
||||||
|
if week_start is not None:
|
||||||
|
transactions = transactions.where(Transaction.week >= week_start)
|
||||||
|
if week_end is not None:
|
||||||
|
transactions = transactions.where(Transaction.week <= week_end)
|
||||||
|
if move_id:
|
||||||
|
transactions = transactions.where(Transaction.moveid == move_id)
|
||||||
|
if player_id or player_name:
|
||||||
|
if player_id:
|
||||||
|
p_list = Player.select().where(Player.id << player_id)
|
||||||
|
transactions = transactions.where(Transaction.player << p_list)
|
||||||
|
else:
|
||||||
|
p_list = [x.lower() for x in player_name]
|
||||||
|
these_players = Player.select().where(fn.Lower(Player.name) << p_list)
|
||||||
|
transactions = transactions.where(Transaction.player << these_players)
|
||||||
|
|
||||||
|
if cancelled:
|
||||||
|
transactions = transactions.where(Transaction.cancelled == 1)
|
||||||
|
else:
|
||||||
|
transactions = transactions.where(Transaction.cancelled == 0)
|
||||||
|
|
||||||
|
if frozen:
|
||||||
|
transactions = transactions.where(Transaction.frozen == 1)
|
||||||
|
else:
|
||||||
|
transactions = transactions.where(Transaction.frozen == 0)
|
||||||
|
|
||||||
|
if is_trade is not None:
|
||||||
|
raise HTTPException(status_code=501, detail='The is_trade parameter is not implemented, yet')
|
||||||
|
|
||||||
|
transactions = transactions.order_by(-Transaction.week, Transaction.moveid)
|
||||||
|
|
||||||
|
return_trans = {
|
||||||
|
'count': transactions.count(),
|
||||||
|
'transactions': [model_to_dict(x, recurse=not short_output) for x in transactions]
|
||||||
|
}
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
return return_trans
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch('/{move_id}')
|
||||||
|
async def patch_transactions(
|
||||||
|
move_id, token: str = Depends(oauth2_scheme), frozen: Optional[bool] = None, cancelled: Optional[bool] = None):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'patch_transactions - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
these_moves = Transaction.select().where(Transaction.moveid == move_id)
|
||||||
|
if these_moves.count() == 0:
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=404, detail=f'Move ID {move_id} not found')
|
||||||
|
|
||||||
|
if frozen is not None:
|
||||||
|
for x in these_moves:
|
||||||
|
x.frozen = frozen
|
||||||
|
x.save()
|
||||||
|
if cancelled is not None:
|
||||||
|
for x in these_moves:
|
||||||
|
x.cancelled = cancelled
|
||||||
|
x.save()
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=200, detail=f'Updated {these_moves.count()} transactions')
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('')
|
||||||
|
async def post_transactions(moves: TransactionList, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'post_transactions - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
all_moves = []
|
||||||
|
|
||||||
|
for x in moves.moves:
|
||||||
|
if Team.get_or_none(Team.id == x.oldteam_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {x.oldteam_id} not found')
|
||||||
|
if Team.get_or_none(Team.id == x.newteam_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Team ID {x.newteam_id} not found')
|
||||||
|
if Player.get_or_none(Player.id == x.player_id) is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found')
|
||||||
|
|
||||||
|
all_moves.append(x.dict())
|
||||||
|
|
||||||
|
with db.atomic():
|
||||||
|
for batch in chunked(all_moves, 15):
|
||||||
|
Transaction.insert_many(batch).on_conflict_replace().execute()
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
raise HTTPException(status_code=200, detail=f'{len(all_moves)} transactions have been added')
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete('/{move_id}')
|
||||||
|
async def delete_transactions(move_id, token: str = Depends(oauth2_scheme)):
|
||||||
|
if not valid_token(token):
|
||||||
|
logging.warning(f'delete_transactions - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
delete_query = Transaction.delete().where(Transaction.moveid == move_id)
|
||||||
|
|
||||||
|
count = delete_query.execute()
|
||||||
|
db.close()
|
||||||
|
if count > 0:
|
||||||
|
raise HTTPException(status_code=200, detail=f'Removed {count} transactions')
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=418, detail=f'Well slap my ass and call me a teapot; '
|
||||||
|
f'I did not delete any records')
|
||||||
2
main.py
2
main.py
@ -1539,7 +1539,7 @@ async def v1_players_patch(
|
|||||||
raise HTTPException(status_code=404, detail=f'Team id {team_id} not found')
|
raise HTTPException(status_code=404, detail=f'Team id {team_id} not found')
|
||||||
if name:
|
if name:
|
||||||
this_player.name = name
|
this_player.name = name
|
||||||
if wara:
|
if wara is not None:
|
||||||
this_player.wara = wara
|
this_player.wara = wara
|
||||||
if image:
|
if image:
|
||||||
this_player.image = image
|
this_player.image = image
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
fastapi==0.61.1
|
fastapi
|
||||||
uvicorn==0.12.2
|
uvicorn
|
||||||
peewee==3.13.3
|
peewee==3.13.3
|
||||||
python-multipart
|
python-multipart
|
||||||
pandas
|
pandas
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user