From fd0e9e6d9abead4884e88ac294966ee778c57b84 Mon Sep 17 00:00:00 2001 From: Wren <4575707+uberfastman@users.noreply.github.com> Date: Fri, 6 Sep 2019 03:44:13 -0400 Subject: [PATCH] initial upload --- EXAMPLE-private.json | 4 + README.md | 11 + __init__.py | 34 +++ dao.py | 511 +++++++++++++++++++++++++++++++++++++++++++ gitignore | 2 + query.py | 116 ++++++++++ util.py | 126 +++++++++++ 7 files changed, 804 insertions(+) create mode 100644 EXAMPLE-private.json create mode 100644 README.md create mode 100644 __init__.py create mode 100644 dao.py create mode 100644 gitignore create mode 100644 query.py create mode 100644 util.py diff --git a/EXAMPLE-private.json b/EXAMPLE-private.json new file mode 100644 index 00000000..b5a3e551 --- /dev/null +++ b/EXAMPLE-private.json @@ -0,0 +1,4 @@ +{ + "consumer_key": "YAHOO_DEVELOPER_APP_CONSUMER_KEY_STRING", + "consumer_secret": "YAHOO_DEVELOPER_APP_CONSUMER_SECRET_STRING" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..222ac6f0 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +``` + __ ________ ______ _______ __ + \ \ / / ____| ____| __ \ \ / / + \ \_/ /| |__ | |__ | |__) \ \_/ / + \ / | __| | __| | ___/ \ / + | | | | | | | | | | + |_| |_| |_| |_| |_| +``` + +### Python API wrapper for the Yahoo Fantasy Football public API +##### By Wren J. R. (uberfastman) diff --git a/__init__.py b/__init__.py new file mode 100644 index 00000000..a48a07d5 --- /dev/null +++ b/__init__.py @@ -0,0 +1,34 @@ +import logging +import os +from logging.handlers import RotatingFileHandler + +# configure root logger settings +logger = logging.getLogger() +logger.setLevel(logging.DEBUG) + +stderr_logger = logging.getLogger("STDERR") +stderr_logger.setLevel(logging.ERROR) + +log_formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + +# console handler +console_handler = logging.StreamHandler() # can put sys.stdout or sys.stderr as parameter of StreamHandler() +console_handler.setLevel(logging.INFO) +console_handler.setFormatter(log_formatter) +logger.addHandler(console_handler) + +log_dir = os.path.join(os.path.dirname(__file__), "..", "logs") +if not os.path.exists(log_dir): + os.makedirs(log_dir) + +# stdout log file handler +stdout_file_handler = RotatingFileHandler(os.path.join(log_dir, "out.log"), maxBytes=1000000, backupCount=5, mode="a") +stdout_file_handler.setLevel(logging.INFO) +stdout_file_handler.setFormatter(log_formatter) +logger.addHandler(stdout_file_handler) + +# stderr log file handler +stderr_file_handler = RotatingFileHandler(os.path.join(log_dir, "error.log"), maxBytes=100000, backupCount=5, mode="a") +stderr_file_handler.setLevel(logging.ERROR) +stderr_file_handler.setFormatter(log_formatter) +stderr_logger.addHandler(stderr_file_handler) diff --git a/dao.py b/dao.py new file mode 100644 index 00000000..cf914979 --- /dev/null +++ b/dao.py @@ -0,0 +1,511 @@ +import json +import logging + +import inflect +import stringcase + +logger = logging.getLogger(__name__) +place = inflect.engine() + + +def complex_json_handler(obj): + if hasattr(obj, "serialized"): + return obj.serialized() + else: + try: + return str(obj, "utf-8") + except TypeError: + raise TypeError('Object of type %s with value of %s is not JSON serializable' % (type(obj), repr(obj))) + + +class YahooFantasyObject(object): + + def __init__(self, extracted_data): + self.extracted_data = extracted_data + self._index = 0 + if isinstance(extracted_data, dict): + self._keys = list(self.extracted_data.keys()) + + def __str__(self): + # return json.load({self.__class__.__module__ + "." + self.__class__.__qualname__: self.to_json()}) + return self.to_json() + + def __repr__(self): + # return json.load({self.__class__.__module__ + "." + self.__class__.__qualname__: self.to_json()}) + return self.to_json() + + def __len__(self): + return len(self.extracted_data) + + def __iter__(self): + return self + + def __next__(self): + try: + if isinstance(self.extracted_data, dict): + result = self.extracted_data.get(self._keys[self._index]) + else: + result = self.extracted_data[self._index] + except IndexError: + raise StopIteration + self._index += 1 + return result + + def __reversed__(self): + return reversed(self._keys) + + def subclass_dict(self): + return {stringcase.snakecase(cls.__name__): cls for cls in self.__class__.__subclasses__()} + + def clean_data_dict(self): + clean_dict = {} + for k, v in self.__dict__.items(): + if k in self._keys: + clean_dict[k] = v.clean_data_dict() if type(v) in self.subclass_dict().values() else v + return clean_dict + + def serialized(self): + serializable_dict = dict() + for a, v in self.clean_data_dict().items(): + if hasattr(v, "serialized"): + serializable_dict[a] = v.serialized() + else: + serializable_dict[a] = v + return serializable_dict + + def to_json(self): + return json.dumps(self.serialized(), indent=2, default=complex_json_handler, ensure_ascii=False) + + @classmethod + def from_json(cls, json_data: dict): + # return cls(**json_data) + return cls(json_data) + + +# class YahooFantasyObjectParent(YahooFantasyObject): +# +# def __init__(self, extracted_data): +# YahooFantasyObject.__init__(self, extracted_data) +# self.copyright = self.extracted_data.get("copyright", "") +# self.league = self.extracted_data.get("league", "") # type: League +# self.refresh_rate = self.extracted_data.get("refresh_rate", "") +# self.time = self.extracted_data.get("time", "") +# self.xml_lang = self.extracted_data.get("xml:lang", "") +# self.yahoo_uri = self.extracted_data.get("yahoo:uri", "") + + +class User(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.games = self.extracted_data.get("games", "") + self.guid = self.extracted_data.get("guid", "") + + +class Game(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.code = self.extracted_data.get("code", "") + self.game_id = self.extracted_data.get("game_id", "") + self.game_key = self.extracted_data.get("game_key", "") + self.is_game_over = self.extracted_data.get("is_game_over", "") + self.is_live_draft_lobby_active = self.extracted_data.get("is_live_draft_lobby_active", "") + self.is_offseason = self.extracted_data.get("is_offseason", "") + self.is_registration_over = self.extracted_data.get("is_registration_over", "") + self.leagues = self.extracted_data.get("leagues", "") + self.name = self.extracted_data.get("name", "") + self.season = self.extracted_data.get("season", "") + self.type = self.extracted_data.get("type", "") + self.url = self.extracted_data.get("url", "") + + +class League(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.allow_add_to_dl_extra_pos = self.extracted_data.get("allow_add_to_dl_extra_pos", "") + self.current_week = self.extracted_data.get("current_week", "") + self.draft_status = self.extracted_data.get("draft_status", "") + self.edit_key = self.extracted_data.get("edit_key", "") + self.end_date = self.extracted_data.get("end_date", "") + self.end_week = self.extracted_data.get("end_week", "") + self.entry_fee = self.extracted_data.get("entry_fee", "") + self.game_code = self.extracted_data.get("game_code", "") + self.iris_group_chat_id = self.extracted_data.get("iris_group_chat_id", "") + self.is_cash_league = self.extracted_data.get("is_cash_league", "") + self.is_finished = self.extracted_data.get("is_finished", "") + self.is_pro_league = self.extracted_data.get("is_pro_league", "") + self.league_id = self.extracted_data.get("league_id", "") + self.league_key = self.extracted_data.get("league_key", "") + self.league_type = self.extracted_data.get("league_type", "") + self.league_update_timestamp = self.extracted_data.get("league_update_timestamp", "") + self.logo_url = self.extracted_data.get("logo_url", "") + self.name = self.extracted_data.get("name", "") + self.num_teams = self.extracted_data.get("num_teams", "") + self.password = self.extracted_data.get("password", "") + self.payment_deadline = self.extracted_data.get("payment_deadline", "") + self.renew = self.extracted_data.get("renew", "") + self.renewed = self.extracted_data.get("renewed", "") + self.scoring_type = self.extracted_data.get("scoring_type", "") + self.season = self.extracted_data.get("season", "") + self.settings = self.extracted_data.get("settings", "") # type: Settings + self.short_invitation_url = self.extracted_data.get("short_invitation_url", "") + self.standings = self.extracted_data.get("standings", "") # type: Standings + self.start_date = self.extracted_data.get("start_date", "") + self.start_week = self.extracted_data.get("start_week", "") + self.url = self.extracted_data.get("url", "") + self.weekly_deadline = self.extracted_data.get("weekly_deadline", "") + + +class Team(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.clinched_playoffs = self.extracted_data.get("clinched_playoffs", "") + self.draft_grade = self.extracted_data.get("draft_grade", "") + self.draft_recap_url = self.extracted_data.get("draft_recap_url", "") + self.has_draft_grade = self.extracted_data.get("has_draft_grade", "") + self.league_scoring_type = self.extracted_data.get("league_scoring_type", "") + self.managers = self.extracted_data.get("managers", "") + self.name = self.extracted_data.get("name", "").encode("utf-8") + self.number_of_moves = self.extracted_data.get("number_of_moves", "") + self.number_of_trades = self.extracted_data.get("number_of_trades", "") + self.roster_adds = self.extracted_data.get("roster_adds", "") # type: RosterAdds + self.team_id = self.extracted_data.get("team_id", "") + self.team_key = self.extracted_data.get("team_key", "") + self.team_logos = self.extracted_data.get("team_logos", "") + self.team_points = self.extracted_data.get("team_points", "") # type: TeamPoints + self.team_standings = self.extracted_data.get("team_standings", "") # type: TeamStandings + self.url = self.extracted_data.get("url", "") + self.waiver_priority = self.extracted_data.get("waiver_priority", "") + + +class Standings(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.teams = self.extracted_data.get("teams", "") + + +class Manager(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.email = self.extracted_data.get("email", "") + self.guid = self.extracted_data.get("guid", "") + self.image_url = self.extracted_data.get("image_url", "") + self.is_comanager = self.extracted_data.get("is_comanager", "") + self.manager_id = self.extracted_data.get("manager_id", "") + self.nickname = self.extracted_data.get("nickname", "") + + +class RosterAdds(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.coverage_type = self.extracted_data.get("coverage_type", "") + self.coverage_value = self.extracted_data.get("coverage_value", "") + self.value = self.extracted_data.get("value", 0) + + +class TeamLogo(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.size = self.extracted_data.get("size", "") + self.url = self.extracted_data.get("url", "") + + +class TeamPoints(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.coverage_type = self.extracted_data.get("coverage_type", "") + self.season = self.extracted_data.get("season", "") + self.total = self.extracted_data.get("total", 0) + + +class TeamStandings(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.outcome_totals = self.extracted_data.get("outcome_totals") # type: OutcomeTotals + self.playoff_seed = self.extracted_data.get("playoff_seed", 0) + self.points_against = self.extracted_data.get("points_against", 0) + self.points_for = self.extracted_data.get("points_for", 0) + self.rank = self.extracted_data.get("rank", 0) + self.streak = self.extracted_data.get("streak") # type: Streak + + +class OutcomeTotals(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.losses = int(self.extracted_data.get("losses", 0)) + self.percentage = float(self.extracted_data.get("percentage", 0)) + self.ties = int(self.extracted_data.get("ties", 0) or 0) + self.wins = int(self.extracted_data.get("wins", 0)) + + +class Streak(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.type = self.extracted_data.get("type", "") + self.value = self.extracted_data.get("value", "") + + +class Settings(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.cant_cut_list = self.extracted_data.get("cant_cut_list", "") + self.draft_pick_time = self.extracted_data.get("draft_pick_time", "") + self.draft_time = self.extracted_data.get("draft_time", "") + self.draft_type = self.extracted_data.get("draft_type", "") + self.has_multiweek_championship = self.extracted_data.get("has_multiweek_championship", "") + self.has_playoff_consolation_games = self.extracted_data.get("has_playoff_consolation_games", "") + self.is_auction_draft = self.extracted_data.get("is_auction_draft", "") + self.max_teams = self.extracted_data.get("max_teams", "") + self.num_playoff_consolation_teams = self.extracted_data.get("num_playoff_consolation_teams", "") + self.num_playoff_teams = self.extracted_data.get("num_playoff_teams", "") + self.pickem_enabled = self.extracted_data.get("pickem_enabled", "") + self.player_pool = self.extracted_data.get("player_pool", "") + self.playoff_start_week = self.extracted_data.get("playoff_start_week", "") + self.post_draft_players = self.extracted_data.get("post_draft_players", "") + self.roster_positions = self.extracted_data.get("roster_positions", "") + self.scoring_type = self.extracted_data.get("scoring_type", "") + self.stat_categories = self.extracted_data.get("stat_categories", "") # type: StatCategories + self.stat_modifiers = self.extracted_data.get("stat_modifiers", "") # type: StatModifiers + self.trade_end_date = self.extracted_data.get("trade_end_date", "") + self.trade_ratify_type = self.extracted_data.get("trade_ratify_type", "") + self.trade_reject_time = self.extracted_data.get("trade_reject_time", "") + self.uses_faab = self.extracted_data.get("uses_faab", "") + self.uses_fractional_points = self.extracted_data.get("uses_fractional_points", "") + self.uses_lock_eliminated_teams = self.extracted_data.get("uses_lock_eliminated_teams", "") + self.uses_negative_points = self.extracted_data.get("uses_negative_points", "") + self.uses_playoff = self.extracted_data.get("uses_playoff", "") + self.uses_playoff_reseeding = self.extracted_data.get("uses_playoff_reseeding", "") + self.waiver_rule = self.extracted_data.get("waiver_rule", "") + self.waiver_time = self.extracted_data.get("waiver_time", "") + self.waiver_type = self.extracted_data.get("waiver_type", "") + + +class RosterPosition(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.count = self.extracted_data.get("count", "") + self.position = self.extracted_data.get("position", "") + self.position_type = self.extracted_data.get("position_type", "") + + +class StatCategories(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.stats = self.extracted_data.get("stats", "") + + +class StatModifiers(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.stats = self.extracted_data.get("stats", "") + + +class Stat(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.bonuses = self.extracted_data.get("bonuses", {}) + self.display_name = self.extracted_data.get("display_name", "") + self.enabled = self.extracted_data.get("enabled", "") + self.name = self.extracted_data.get("name", "") + self.position_type = self.extracted_data.get("position_type", "") + self.sort_order = self.extracted_data.get("sort_order", "") + self.stat_id = self.extracted_data.get("stat_id", "") + self.stat_position_types = self.extracted_data.get("stat_position_types", []) + self.value = self.extracted_data.get("value", "") + + +class StatPositionType(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.is_only_display_stat = self.extracted_data.get("is_only_display_stat", "") + self.position_type = self.extracted_data.get("position_type", "") + + +class Bonus(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.points = self.extracted_data.get("points", "") + self.target = self.extracted_data.get("target", "") + + +class Matchup(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.matchup = self.extracted_data.get("matchup", "") + self.is_consolation = self.extracted_data.get("is_consolation", "") + self.is_matchup_recap_available = self.extracted_data.get("is_matchup_recap_available", "") + self.is_playoffs = self.extracted_data.get("is_playoffs", "") + self.is_tied = self.extracted_data.get("is_tied", "") + self.matchup_grades = self.extracted_data.get("matchup_grades", "") + self.matchup_recap_title = self.extracted_data.get("matchup_recap_title", "") + self.matchup_recap_url = self.extracted_data.get("matchup_recap_url", "") + self.status = self.extracted_data.get("status", "") + self.teams = self.extracted_data.get("teams") + self.week = self.extracted_data.get("week", "") + self.week_end = self.extracted_data.get("week_end", "") + self.week_start = self.extracted_data.get("week_start", "") + self.winner_team_key = self.extracted_data.get("winner_team_key", "") + + +class MatchupGrade(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.grade = self.extracted_data.get("grade", "") + self.team_key = self.extracted_data.get("team_key", "") + + +class Player(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.bye_weeks = self.extracted_data.get("bye_weeks") # type: ByeWeeks + self.display_position = self.extracted_data.get("display_position", "") + self.editorial_player_key = self.extracted_data.get("editorial_player_key", "") + self.editorial_team_abbr = self.extracted_data.get("editorial_team_abbr", "") + self.editorial_team_full_name = self.extracted_data.get("editorial_team_full_name", "") + self.editorial_team_key = self.extracted_data.get("editorial_team_key", "") + self.eligible_positions = self.extracted_data.get("eligible_positions", "") + self.has_player_notes = self.extracted_data.get("has_player_notes", "") + self.headshot = self.extracted_data.get("headshot", "") # type: Headshot + self.is_editable = self.extracted_data.get("is_editable", "") + self.is_undroppable = self.extracted_data.get("is_undroppable", "") + self.name = self.extracted_data.get("name", "") # type: Name + self.player_id = self.extracted_data.get("player_id", "") + self.player_key = self.extracted_data.get("player_key", "") + self.player_notes_last_timestamp = self.extracted_data.get("player_notes_last_timestamp", "") + self.player_points = self.extracted_data.get("player_points", "") # type: PlayerPoints + self.player_stats = self.extracted_data.get("player_stats", "") # type: PlayerStats + self.position_type = self.extracted_data.get("position_type", "") + self.primary_position = self.extracted_data.get("primary_position", "") + self.selected_position = self.extracted_data.get("selected_position", "") # type: SelectedPosition + self.status = self.extracted_data.get("status", "") + self.uniform_number = self.extracted_data.get("uniform_number", "") + + +class ByeWeeks(YahooFantasyObject): + """ + """ + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.week = self.extracted_data.get("week", "") + + +class Headshot(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.size = self.extracted_data.get("size", "") + self.url = self.extracted_data.get("url", "") + + +class Name(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.ascii_first = self.extracted_data.get("ascii_first", "") + self.ascii_last = self.extracted_data.get("ascii_last", "") + self.first = self.extracted_data.get("first", "") + self.full = self.extracted_data.get("full", "") + self.last = self.extracted_data.get("last", "") + + +class PlayerPoints(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.coverage_type = self.extracted_data.get("coverage_type", "") + self.week = self.extracted_data.get("week", "") + self.total = float(self.extracted_data.get("total", 0) or 0) + + +class PlayerStats(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.coverage_type = self.extracted_data.get("coverage_type", "") + self.week = self.extracted_data.get("week", "") + self.stats = self.extracted_data.get("stats", "") + + +class SelectedPosition(YahooFantasyObject): + """ + """ + + def __init__(self, extracted_data): + YahooFantasyObject.__init__(self, extracted_data) + self.coverage_type = self.extracted_data.get("coverage_type", "") + self.is_flex = self.extracted_data.get("is_flex", "") + self.position = self.extracted_data.get("position", "") + self.week = self.extracted_data.get("week", "") diff --git a/gitignore b/gitignore new file mode 100644 index 00000000..82b988d8 --- /dev/null +++ b/gitignore @@ -0,0 +1,2 @@ +auth/private.json +auth/token.json \ No newline at end of file diff --git a/query.py b/query.py new file mode 100644 index 00000000..64ac8c17 --- /dev/null +++ b/query.py @@ -0,0 +1,116 @@ +import os + +from yahoo_oauth import OAuth2 + +from yffpy.dao import * +from yffpy.util import unpack_data, reformat_json_list + +logger = logging.getLogger(__name__) +logging.getLogger("yahoo_oauth").setLevel(level=logging.INFO) + + +class YahooFantasyFootballQuery(object): + + def __init__(self, auth_dir, league_id, game_id=None, offline=False): + + self.league_id = league_id + self.game_id = game_id + self.league_key = None + self.league_name = None + + # yahoo_oauth_token_cache_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "auth") + # if not os.path.exists(yahoo_oauth_token_cache_dir): + # os.makedirs(yahoo_oauth_token_cache_dir) + + if not offline: + with open(os.path.join(auth_dir, "private.json")) as yahoo_app_credentials: + auth_info = json.load(yahoo_app_credentials) + self._yahoo_consumer_key = auth_info["consumer_key"] + self._yahoo_consumer_secret = auth_info["consumer_secret"] + + token_file_path = os.path.join(auth_dir, "token.json") + if os.path.isfile(token_file_path): + with open(token_file_path) as yahoo_oauth_token: + auth_info = json.load(yahoo_oauth_token) + else: + with open(token_file_path, "w") as yahoo_oauth_token: + json.dump(auth_info, yahoo_oauth_token) + + if "access_token" in auth_info.keys(): + self._yahoo_access_token = auth_info["access_token"] + + self.oauth = OAuth2(None, None, from_file=token_file_path) + if not self.oauth.token_is_valid(): + self.oauth.refresh_access_token() + + def query(self, url, data_key_list, data_type_class=None, run=True): + + if run: + response = self.oauth.session.get(url, params={"format": "json"}) + logger.debug("RESPONSE (RAW JSON): {}".format(response.json())) + raw_response_data = response.json().get("fantasy_content") + logger.debug("RESPONSE (Yahoo fantasy football data extracted from: \"fantasy_content\"): {}".format(raw_response_data)) + + for i in range(len(data_key_list)): + if type(raw_response_data) == list: + raw_response_data = reformat_json_list(raw_response_data)[data_key_list[i]] + else: + raw_response_data = raw_response_data.get(data_key_list[i]) + logger.debug("RESPONSE (Yahoo fantasy football data extracted from: {}): {}".format(data_key_list, raw_response_data)) + + unpacked = unpack_data(raw_response_data, YahooFantasyObject) + clean_response_data = data_type_class(unpacked) if data_type_class else unpacked + logger.debug("UNPACKED AND PARSED JSON (Yahoo fantasy football data wth parent type: {}): {}".format(data_type_class, unpacked)) + + return { + "data": clean_response_data, + "url": url, + "raw": raw_response_data + } + else: + return { + "data": None, + "url": url, + "raw": None + } + + def get_current_nfl_fantasy_game(self, run=True): + return self.query("https://fantasysports.yahooapis.com/fantasy/v2/game/nfl", ["game"], Game, run=run) + + def get_nfl_fantasy_game(self, game_id, run=True): + return self.query("https://fantasysports.yahooapis.com/fantasy/v2/game/" + str(game_id), ["game"], Game, run=run) + + def get_league_key(self): + if self.game_id: + return self.get_nfl_fantasy_game(self.game_id).get("data").game_key + ".l." + self.league_id + else: + logger.warning("No Yahoo Fantasy game id provided, defaulting to current NFL fantasy football season game id.") + return self.get_current_nfl_fantasy_game().get("data").game_key + ".l." + self.league_id + + def get_user_game_history(self, run=True): + return self.query("https://fantasysports.yahooapis.com/fantasy/v2/users;use_login=1/games;codes=nfl/", ["users", "0", "user"], User, run=run) + + def get_user_league_history(self, run=True): + return self.query("https://fantasysports.yahooapis.com/fantasy/v2/users;use_login=1/games;codes=nfl/leagues/", ["users", "0", "user"], User, run=run) + + def get_overview(self, run=True): + return self.query("https://fantasysports.yahooapis.com/fantasy/v2/league/" + self.league_key + "/", ["league"], League, run=run) + + def get_standings(self, run=True): + return self.query("https://fantasysports.yahooapis.com/fantasy/v2/league/" + self.league_key + "/standings", ["league", "standings"], Standings, run=run) + + def get_settings(self, run=True): + return self.query("https://fantasysports.yahooapis.com/fantasy/v2/league/" + self.league_key + "/settings", ["league", "settings"], Settings, run=run) + + def get_teams(self, run=True): + return self.query("https://fantasysports.yahooapis.com/fantasy/v2/league/" + self.league_key + "/teams", ["league", "teams"], run=run) + + def get_matchups(self, chosen_week, run=True): + return self.query("https://fantasysports.yahooapis.com/fantasy/v2/league/" + self.league_key + "/scoreboard;week=" + str(chosen_week), ["league", "scoreboard", "0", "matchups"], run=run) + + def get_team_roster(self, team_id, chosen_week, run=True): + team_key = self.league_key + ".t." + str(team_id) + return self.query("https://fantasysports.yahooapis.com/fantasy/v2/team/" + str(team_key) + "/roster;week=" + str(chosen_week) + "/players/stats", ["team", "roster", "0", "players"], run=run) + + def get_player_stats(self, player_key, chosen_week, run=True): + return self.query("https://fantasysports.yahooapis.com/fantasy/v2/league/" + self.league_key + "/players;player_keys=" + player_key + "/stats;type=week;week=" + str(chosen_week), ["league", "players", "0", "player"], Player, run=run) diff --git a/util.py b/util.py new file mode 100644 index 00000000..12f022c7 --- /dev/null +++ b/util.py @@ -0,0 +1,126 @@ +import json +import logging +import os +from collections import ChainMap + +import stringcase + +from yffpy.dao import YahooFantasyObject, complex_json_handler + +logger = logging.getLogger(__name__) + + +# noinspection PyTypeChecker +def unpack_data(json_obj, parent_class=None): + subclasses = {} + if parent_class: + subclasses = {stringcase.snakecase(cls.__name__): cls for cls in parent_class.__subclasses__()} + + if json_obj: + if isinstance(json_obj, list): + json_obj = [obj for obj in json_obj if obj] + + if len(json_obj) == 1: + return unpack_data(json_obj[0], parent_class) + else: + if any(isinstance(obj, dict) for obj in json_obj): + return flatten_dict_list(json_obj, parent_class) + + return [unpack_data(obj, parent_class) for obj in json_obj if obj] + + elif isinstance(json_obj, dict): + + if "0" in json_obj.keys() and "1" not in json_obj.keys(): + if len(json_obj.keys()) == 1: + return unpack_data(json_obj.get("0"), parent_class) + else: + if isinstance(json_obj.get("0"), dict): + json_obj.update(json_obj.get("0")) + + if "count" in json_obj.keys() and "position" in json_obj.keys(): + return get_type({k: unpack_data(v, parent_class) for k, v in json_obj.items()}, parent_class, + subclasses) + else: + return get_type(dict({k: unpack_data(v, parent_class) for k, v in json_obj.items() if k != "count"}), + parent_class, subclasses) + else: + return json_obj + + +def flatten_dict_list(json_obj_dict_list, parent_class): + json_obj_dict_list = [obj for obj in json_obj_dict_list if obj] + item_keys = [] + ndx = 0 + for item in json_obj_dict_list: + if isinstance(item, list): + flattened_item = flatten_dict_list(item, parent_class) + json_obj_dict_list[ndx] = flattened_item + item_keys.extend(list(flattened_item.keys())) + else: + item_keys.extend(list(item.keys())) + ndx += 1 + + if len(item_keys) == len(set(item_keys)): + agg_dict = {} + for dict_item in json_obj_dict_list: + agg_dict.update(dict_item) + + return unpack_data(agg_dict, parent_class) + else: + return [unpack_data(obj, parent_class) for obj in json_obj_dict_list if obj] + + +def get_type(json_obj_dict, parent_class, subclasses): + for k, v in json_obj_dict.items(): + if k in subclasses.keys() and isinstance(v, dict) and not isinstance(v, subclasses[k]): + json_obj_dict[k] = subclasses[k](unpack_data(v, parent_class)) + return json_obj_dict + + +def reformat_json_list(json_obj): + if isinstance(json_obj[0], list): + if len(json_obj) > 1: + return reformat_json_list( + [reformat_json_list(item) if isinstance(item, list) else item for item in json_obj]) + else: + return reformat_json_list(json_obj[0]) + else: + return ChainMap(*[value for value in json_obj if value]) + + +def persist_and_retrieve_data(yff_query, data_dir, data_file_name, data_type_class=None, params=None, + persist_data=False, refresh_data=True): + data_persistence_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), data_dir) + + if not os.path.exists(data_persistence_dir): + os.makedirs(data_persistence_dir) + + if refresh_data: + # run data query + if params: + query_output = yff_query(**params, run=True) + else: + query_output = yff_query(run=True) + data = query_output.get("data") + url = query_output.get("url") + logger.info( + "DATA FETCHED WITH QUERY URL: {}".format(url) + (" AND PARAMS: {}".format(params) if params else "")) + else: + persisted_data_file_path = os.path.join(data_persistence_dir, data_file_name + ".json") + if os.path.exists(persisted_data_file_path): + with open(persisted_data_file_path, "r", encoding="utf-8") as data_file: + unpacked = unpack_data(json.load(data_file), YahooFantasyObject) + data = data_type_class(unpacked) if data_type_class else unpacked + logger.info("DATA RETRIEVED LOCALLY: {}".format(persisted_data_file_path)) + else: + raise FileNotFoundError( + "FILE {} DOES NOT EXIST. CANNOT RUN LOCALLY WITHOUT HAVING PREVIOUSLY PERSISTED DATA!".format( + persisted_data_file_path)) + + if persist_data and refresh_data: + persisted_data_file_path = os.path.join(data_persistence_dir, data_file_name + ".json") + with open(persisted_data_file_path, "w", encoding="utf-8") as persisted_data_file: + json.dump(data, persisted_data_file, ensure_ascii=False, indent=2, default=complex_json_handler) + logger.info("DATA PERSISTED LOCALLY: {}".format(persisted_data_file_path)) + + return data