DraftPick, Player, Team, and DraftData dataclasses added along with tests
This commit is contained in:
parent
fdf80fcdc1
commit
50d0f89c1d
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"python.testing.pytestArgs": [
|
||||||
|
"tests"
|
||||||
|
],
|
||||||
|
"python.testing.unittestEnabled": false,
|
||||||
|
"python.testing.pytestEnabled": true
|
||||||
|
}
|
||||||
0
api_calls/__init__.py
Normal file
0
api_calls/__init__.py
Normal file
35
api_calls/current.py
Normal file
35
api_calls/current.py
Normal 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
35
api_calls/draft_data.py
Normal 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
63
api_calls/draft_pick.py
Normal 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
37
api_calls/player.py
Normal 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
25
api_calls/team.py
Normal 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
2
pytest.ini
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[pytest]
|
||||||
|
pythonpath = .
|
||||||
39
tests/api_calls/test_current.py
Normal file
39
tests/api_calls/test_current.py
Normal 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()
|
||||||
|
|
||||||
44
tests/api_calls/test_draft_data.py
Normal file
44
tests/api_calls/test_draft_data.py
Normal 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()
|
||||||
93
tests/api_calls/test_draft_picks.py
Normal file
93
tests/api_calls/test_draft_picks.py
Normal 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)
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user