DraftPick, Player, Team, and DraftData dataclasses added along with tests

This commit is contained in:
Cal Corum 2025-06-07 23:49:13 -05:00
parent fdf80fcdc1
commit 50d0f89c1d
11 changed files with 380 additions and 0 deletions

7
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}

0
api_calls/__init__.py Normal file
View File

35
api_calls/current.py Normal file
View File

@ -0,0 +1,35 @@
import logging
import pydantic
from pydantic import field_validator
from db_calls import db_get
from exceptions import log_exception, ApiException
logger = logging.getLogger('discord_app')
class Current(pydantic.BaseModel):
id: int = 0
week: int = 69
freeze: bool = True
season: int = 69
bet_week: str = 'sheets'
trade_deadline: int = 1
pick_trade_start: int = 69
pick_trade_end: int = 420
playoffs_begin: int = 420
@field_validator("bet_week", mode="before")
@classmethod
def cast_to_string(cls, v):
if isinstance(v, str):
return v
return str(v)
async def get_current() -> Current:
data = await db_get('current') # data = {'id': 420, 'transcount': 555}
if data is None:
log_exception(ApiException('Did not receive current metadata from the API'))
# return Current()
return Current(**data)

35
api_calls/draft_data.py Normal file
View File

@ -0,0 +1,35 @@
import datetime
import logging
from typing import Optional
import pydantic
from pydantic import field_validator
from db_calls import db_get
from exceptions import log_exception, ApiException
logger = logging.getLogger('discord_app')
class DraftData(pydantic.BaseModel):
id: int = 0
currentpick: int = 0
timer: bool = False
pick_deadline: Optional[datetime.datetime] = None
result_channel: str = 'unknown'
ping_channel: str = 'unknown'
pick_minutes: int = 1
@field_validator("result_channel", "ping_channel", mode="before")
@classmethod
def cast_to_string(cls, v):
if isinstance(v, str):
return v
return str(v)
async def get_draft_data() -> DraftData:
data = await db_get('draftdata')
if data is None:
log_exception(ApiException('Did not receive current metadata from the API'))
return_val = DraftData(**data)
logger.info(f'this draft_data: {return_val}')
return return_val

63
api_calls/draft_pick.py Normal file
View File

@ -0,0 +1,63 @@
import logging
from typing import Optional
import pydantic
from api_calls.player import Player
from api_calls.team import Team
from db_calls import db_get, db_patch
from exceptions import log_exception, ApiException
logger = logging.getLogger('discord_app')
class DraftPick(pydantic.BaseModel):
id: int = 0
season: int = 0
overall: int = 0
round: int = 0
origowner: Optional[Team] = None
owner: Optional[Team] = None
player: Optional[Player] = None
async def get_one_draftpick(pick_id: Optional[int] = None, season: Optional[int] = None, overall: Optional[int] = None) -> DraftPick:
if not pick_id and not season and not overall:
log_exception(KeyError('Either pick_id or season + overall must be provided to get_one_draftpick'))
elif (season is not None and overall is None) or (season is None and overall is not None):
log_exception(KeyError('Both season and overall must be provided to get_one_draftpick'))
if pick_id is not None:
data = await db_get(f'draftpicks/{pick_id}')
if data is None:
log_exception(ApiException(f'No pick found with ID {pick_id}'))
return DraftPick(**data)
data = await db_get('draftpicks', params=[('season', season), ('overall', overall)])
if not data or data.get('count', 0) != 1 or len(data.get('picks', [])) != 1:
log_exception(ApiException(f'No pick found in season {season} with overall {overall}'))
return DraftPick(**data['picks'][0])
async def patch_draftpick(updated_pick: DraftPick) -> DraftPick:
if updated_pick.origowner is None or updated_pick.owner is None:
log_exception(ValueError('To patch draftpicks, owner and origowner may not be None'))
logger.info(f'Patching pick id {updated_pick.id}')
pick_data = updated_pick.model_dump(exclude={'player', 'origowner', 'owner'})
pick_data['origowner_id'] = updated_pick.origowner.id
pick_data['owner_id'] = updated_pick.owner.id
if updated_pick.player:
pick_data['player_id'] = updated_pick.player.id
else:
pick_data['player_id'] = None
new_pick = await db_patch(
'draftpicks',
object_id=updated_pick.id,
params=[],
payload=pick_data
)
return DraftPick(**new_pick)

37
api_calls/player.py Normal file
View File

@ -0,0 +1,37 @@
import logging
import pydantic
from typing import Optional
from api_calls.team import Team
from db_calls import db_get
from exceptions import log_exception, ApiException
logger = logging.getLogger('discord_app')
class Player(pydantic.BaseModel):
id: Optional[int] = None
name: str
wara: float
team: Team
image: str
image2: Optional[str] = None
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
sbaplayer_id: Optional[int] = None

25
api_calls/team.py Normal file
View File

@ -0,0 +1,25 @@
import logging
from typing import Optional
import pydantic
from pydantic import field_validator
from db_calls import db_get
from exceptions import log_exception, ApiException
logger = logging.getLogger('discord_app')
class Team(pydantic.BaseModel):
id: int = 0
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

2
pytest.ini Normal file
View File

@ -0,0 +1,2 @@
[pytest]
pythonpath = .

View File

@ -0,0 +1,39 @@
import pytest
from unittest.mock import AsyncMock, patch
from api_calls.current import get_current, Current
from exceptions import ApiException
@pytest.mark.asyncio
async def test_get_current_success():
mock_data = {
"id": 420,
"week": 1,
"freeze": False,
"season": 2025,
"bet_week": 12, # should be cast to string
"trade_deadline": 10,
"pick_trade_start": 20,
"pick_trade_end": 30,
"playoffs_begin": 40
}
with patch("api_calls.current.db_get", new_callable=AsyncMock) as mock_db_get:
mock_db_get.return_value = mock_data
result = await get_current()
assert isinstance(result, Current)
assert result.id == 420
assert result.bet_week == "12" # validated and cast to string
assert result.season == 2025
@pytest.mark.asyncio
async def test_get_current_returns_default_on_none():
with patch("api_calls.current.db_get", new_callable=AsyncMock) as mock_db_get:
mock_db_get.return_value = None
with pytest.raises(ApiException):
await get_current()

View File

@ -0,0 +1,44 @@
import pytest
from unittest.mock import AsyncMock, patch
from api_calls.draft_data import get_draft_data, DraftData
from exceptions import ApiException
import datetime
@pytest.mark.asyncio
async def test_get_draft_data_success():
mock_data = {
"id": 42,
"currentpick": 3,
"timer": True,
"pick_deadline": datetime.datetime(2025, 6, 7, 12, 30).isoformat(),
"result_channel": 123456, # will be cast to string
"ping_channel": None, # will become 'None' as string
"pick_minutes": 5
}
with patch("api_calls.draft_data.db_get", new_callable=AsyncMock) as mock_db_get:
mock_db_get.return_value = mock_data
result = await get_draft_data()
assert isinstance(result, DraftData)
assert result.id == 42
assert result.currentpick == 3
assert result.timer is True
assert isinstance(result.pick_deadline, datetime.datetime)
assert result.result_channel == "123456"
assert result.ping_channel == "None"
assert result.pick_minutes == 5
@pytest.mark.asyncio
async def test_get_draft_data_none_response_raises():
with patch("api_calls.draft_data.db_get", new_callable=AsyncMock) as mock_db_get, \
patch("api_calls.draft_data.log_exception") as mock_log_exception:
mock_db_get.return_value = None
mock_log_exception.side_effect = ApiException("Mocked exception")
with pytest.raises(ApiException, match="Mocked exception"):
await get_draft_data()

View File

@ -0,0 +1,93 @@
import pytest
from unittest.mock import AsyncMock, patch
from api_calls.draft_pick import get_one_draftpick, patch_draftpick, DraftPick
from api_calls.team import Team
from api_calls.player import Player
from exceptions import ApiException
@pytest.mark.asyncio
async def test_get_one_draftpick_by_id_success():
mock_data = {
"id": 5,
"season": 2025,
"overall": 3,
"round": 1
}
with patch("api_calls.draft_pick.db_get", new_callable=AsyncMock) as mock_db_get:
mock_db_get.return_value = mock_data
result = await get_one_draftpick(pick_id=5)
assert isinstance(result, DraftPick)
assert result.id == 5
assert result.overall == 3
@pytest.mark.asyncio
async def test_get_one_draftpick_by_season_and_overall_success():
mock_data = {
"count": 1,
"picks": [{
"id": 10,
"season": 2025,
"overall": 1,
"round": 1
}]
}
with patch("api_calls.draft_pick.db_get", new_callable=AsyncMock) as mock_db_get:
mock_db_get.return_value = mock_data
result = await get_one_draftpick(season=2025, overall=1)
assert result.id == 10
assert result.season == 2025
@pytest.mark.asyncio
async def test_get_one_draftpick_invalid_params_raise():
with patch("api_calls.draft_pick.log_exception") as mock_log:
mock_log.side_effect = KeyError("Missing args")
with pytest.raises(KeyError, match="Missing args"):
await get_one_draftpick()
@pytest.mark.asyncio
async def test_patch_draftpick_success():
updated_pick = DraftPick(
id=5,
season=2025,
overall=2,
round=1,
origowner=Team(id=1, abbrev='AAA', sname='Alpha', lname='Alpha Squad', season=2025),
owner=Team(id=2, abbrev='BBB', sname='Beta', lname='Beta Crew', season=2025),
player=Player(id=99, name="Test Player", team=Team(id=2, abbrev='BBB', sname='Beta', lname='Beta Crew', season=2025))
)
mock_response = {
"id": 5,
"season": 2025,
"overall": 2,
"round": 1
}
with patch("api_calls.draft_pick.db_patch", new_callable=AsyncMock) as mock_patch:
mock_patch.return_value = mock_response
result = await patch_draftpick(updated_pick)
assert isinstance(result, DraftPick)
assert result.id == 5
mock_patch.assert_awaited_once()
@pytest.mark.asyncio
async def test_patch_draftpick_missing_fields_logs_and_returns_empty():
pick = DraftPick(id=5, season=2025, overall=2, round=1)
with patch("api_calls.draft_pick.log_exception") as mock_log_exception:
mock_log_exception.side_effect = ApiException("Mocked exception")
with pytest.raises(ApiException, match="Mocked exception"):
await patch_draftpick(pick)