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