Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use chronicler base class to consolidate implementations where supported #138

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions blaseball_mike/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import functools
import re

from dateutil.parser import parse

from blaseball_mike import chronicler, utils


class _LazyLoadDecorator:

Expand Down Expand Up @@ -130,3 +134,32 @@ def json(self):
if "timestamp" in data and not isinstance(data["timestamp"], str):
data["timestamp"] = data["timestamp"].strftime("%Y-%m-%dT%H:%M:%S.%fZ")
return data


class BaseChronicler(Base):
_entity_type = NotImplemented

@classmethod
def load_all(cls, time=None, season=None, day=None):
return cls.load(None, time=time, season=season, day=day)

@classmethod
def load_one(cls, id_, time=None, season=None, day=None):
return cls.load(id_, time=time, season=season, day=day).get(id_)

@classmethod
def load(cls, ids, time=None, season=None, day=None):
if season and day:
timestamp = utils.get_gameday_start_time(season, day)
else:
if isinstance(time, str):
timestamp = parse(time)
else:
timestamp = time
entities = chronicler.get_entities(cls._entity_type, id_=ids, at=timestamp)
return {e["entityId"]: cls(dict(e["data"], timestamp=e['validFrom'])) for e in entities}

@classmethod
def load_history(cls, id_, order='desc', count=None):
entities = chronicler.get_versions(cls._entity_type, id_=id_, order=order, count=count)
return [cls(dict(e['data'], timestamp=e['validFrom'])) for e in entities]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if you use () instead of [] this will produce a generator, which is probably what we actually want

81 changes: 27 additions & 54 deletions blaseball_mike/models/league.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
from collections import OrderedDict

from .base import Base
from .base import Base, BaseChronicler
from .team import Team
from .. import database


class League(Base):
class League(BaseChronicler):
_entity_type = "league"
ILB = 'd8545021-e9fc-48a3-af74-48685950a183'

"""
Represents the entire league
"""
@classmethod
def _get_fields(cls):
p = cls.load()
p = cls.load(cls.ILB)
return [cls._from_api_conversion(x) for x in p.fields]

def __init__(self, data):
super().__init__(data)
self._teams = {}

@classmethod
def load(cls):
return cls(database.get_league())

@classmethod
def load_by_id(cls, id_):
return cls(database.get_league(id_))
def load_ilb(cls, id_=ILB, *args, **kwargs):
return cls.load_one(id_, *args, **kwargs)

@Base.lazy_load("_subleague_ids", cache_name="_subleagues", default_value=dict())
def subleagues(self):
"""Returns dictionary keyed by subleague ID."""
return {id_: Subleague.load(id_) for id_ in self._subleague_ids}
timestamp = self.timestamp if hasattr(self, "timestamp") else None
return {id_: Subleague.load_one(id_, time=timestamp) for id_ in self._subleague_ids}
rgallo marked this conversation as resolved.
Show resolved Hide resolved

@property
def teams(self):
Expand All @@ -41,10 +40,13 @@ def teams(self):

@Base.lazy_load("_tiebreakers_id", cache_name="_tiebreaker")
def tiebreakers(self):
return Tiebreaker.load(self._tiebreakers_id)
timestamp = self.timestamp if hasattr(self, "timestamp") else None
return Tiebreaker.load_one(self._tiebreakers_id, time=timestamp)


class Subleague(Base):
class Subleague(BaseChronicler):
_entity_type = "subleague"

"""
Represents a subleague, ie Mild vs Wild
"""
Expand All @@ -57,17 +59,11 @@ def __init__(self, data):
super().__init__(data)
self._teams = {}

@classmethod
def load(cls, id_):
"""
Load by ID.
"""
return cls(database.get_subleague(id_))

@Base.lazy_load("_division_ids", cache_name="_divisions", default_value=dict())
def divisions(self):
"""Returns dictionary keyed by division ID."""
return {id_: Division.load(id_) for id_ in self._division_ids}
timestamp = self.timestamp if hasattr(self, "timestamp") else None
return {id_: Division.load_one(id_, time=timestamp) for id_ in self._division_ids}

@property
def teams(self):
Expand All @@ -78,7 +74,9 @@ def teams(self):
return self._teams


class Division(Base):
class Division(BaseChronicler):
_entity_type = "division"

"""
Represents a blaseball division ie Mild Low, Mild High, Wild Low, Wild High.
"""
Expand All @@ -87,24 +85,6 @@ def _get_fields(cls):
p = cls.load("f711d960-dc28-4ae2-9249-e1f320fec7d7")
return [cls._from_api_conversion(x) for x in p.fields]

@classmethod
def load(cls, id_):
"""
Load by ID
"""
return cls(database.get_division(id_))

@classmethod
def load_all(cls):
"""
Load all divisions, including historical divisions (Chaotic Good, Lawful Evil, etc.)

Returns dictionary keyed by division ID.
"""
return {
id_: cls(div) for id_, div in database.get_all_divisions().items()
}

@classmethod
def load_by_name(cls, name):
"""
Expand All @@ -121,30 +101,23 @@ def teams(self):
"""
Comes back as dictionary keyed by team ID
"""
return {id_: Team.load(id_) for id_ in self._team_ids}
timestamp = self.timestamp if hasattr(self, "timestamp") else None
return {id_: Team.load_one(id_, time=timestamp) for id_ in self._team_ids}


class Tiebreaker(Base):
class Tiebreaker(BaseChronicler):
_entity_type = "tiebreakers"

"""Represents a league's tiebreaker order"""
@classmethod
def _get_fields(cls):
p = cls.load_one("370c436f-79fa-418b-bc98-5db48442ba3f")
return [cls._from_api_conversion(x) for x in p.fields]

@classmethod
def load(cls, id_):
tiebreakers = database.get_tiebreakers(id_)
return {
id_: cls(tiebreaker) for (id_, tiebreaker) in tiebreakers.items()
}

@classmethod
def load_one(cls, id_):
return cls.load(id_).get(id_)

@Base.lazy_load("_order_ids", cache_name="_order", default_value=OrderedDict())
def order(self):
timestamp = self.timestamp if hasattr(self, "timestamp") else None
order = OrderedDict()
for id_ in self._order_ids:
order[id_] = Team.load(id_)
order[id_] = Team.load(id_, time=timestamp)
return order
66 changes: 14 additions & 52 deletions blaseball_mike/models/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
import uuid
import warnings

from dateutil.parser import parse

from .base import Base
from .base import Base, BaseChronicler
from .item import Item
from .modification import Modification
from .. import database, chronicler, reference
from .. import database, reference


class Player(BaseChronicler):
_entity_type = "player"

class Player(Base):
"""
Represents a blaseball player.
"""
Expand All @@ -20,70 +20,32 @@ def _get_fields(cls):
p = cls.load_one("766dfd1e-11c3-42b6-a167-9b2d568b5dc0")
return [cls._from_api_conversion(x) for x in p.fields]

@classmethod
def load(cls, *ids):
"""
Load one or more players by ID.

Returns a dictionary of players keyed by Player ID.
"""
players = database.get_player(list(ids))
return {
id_: cls(player) for (id_, player) in players.items()
}

@classmethod
def load_one(cls, id_):
"""
Load single player by ID.
"""
return cls.load(id_).get(id_)

@classmethod
def load_one_at_time(cls, id_, time):
"""
Load single player by ID with historical stats at the provided IRL datetime.
"""
if isinstance(time, str):
time = parse(time)

players = list(chronicler.get_entities("player", id_=id_, at=time))
if len(players) == 0:
return None
return cls(dict(players[0]["data"], timestamp=time))

@classmethod
def load_all(cls):
"""
Load all players
"""
players = chronicler.get_entities("player")
return {x["entityId"]: cls(x["data"]) for x in players}

@classmethod
def load_history(cls, id_, order='desc', count=None):
"""
Returns array of Player stat changes with most recent first.
"""
players = chronicler.get_versions("player", id_=id_, order=order, count=count)
return [cls(dict(p['data'], timestamp=p['validFrom'])) for p in players]
warnings.warn("instead of .load_one_at_time(id_, time), use .load_one(id_, time=time)",
DeprecationWarning, stacklevel=2)
return cls.load_one(id_, time=time)

@classmethod
def load_all_by_gameday(cls, season, day):
"""
Returns dict of all players and their fk stats on the given season/day. 1-indexed.
"""
players = reference.get_all_players_for_gameday(season, day)
return {
player['player_id']: cls(player) for player in players
}
warnings.warn("instead of .load_all_by_gameday(season, day), use .load_all(season=season, day=day)",
DeprecationWarning, stacklevel=2)
return cls.load_all(season=season, day=day)

@classmethod
def load_by_gameday(cls, id_, season, day):
"""
Returns one player and their fk stats on the given season/day. 1-indexed.
"""
return cls.load_all_by_gameday(season, day).get(id_)
warnings.warn("instead of .load_by_gameday(id_, season, day), use .load(id_, season=season, day=day)",
DeprecationWarning, stacklevel=2)
return cls.load(id_, season=season, day=day)

@classmethod
def find_by_name(cls, name):
Expand Down
2 changes: 1 addition & 1 deletion blaseball_mike/models/season.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def load(cls, season_number):

@Base.lazy_load("_league_id", cache_name="_league")
def league(self):
return League.load_by_id(self._league_id)
return League.load_one(self._league_id)

@Base.lazy_load("_standings_id", cache_name="_standings")
def standings(self):
Expand Down
Loading