diff --git a/.gitignore b/.gitignore index 87c0ea2e..aefaf701 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,12 @@ -auth/private.json -auth/token.json +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# JetBrains IDE project files +.idea/* + +# Distribution / packaging +.Python +build/ +dist/ +*.egg-info/ diff --git a/__init__.py b/__init__.py deleted file mode 100644 index a48a07d5..00000000 --- a/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -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/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 00000000..78ee21f7 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,10 @@ +certifi==2019.6.16 +chardet==3.0.4 +idna==2.8 +pyaml==19.4.1 +PyYAML==5.1.2 +rauth==0.7.3 +requests==2.22.0 +stringcase==1.2.0 +urllib3==1.25.3 +yahoo-oauth==0.1.9 diff --git a/examples/examples.py b/examples/examples.py new file mode 100644 index 00000000..e69de29b diff --git a/resources/EXAMPLE-private.json b/examples/private.json similarity index 98% rename from resources/EXAMPLE-private.json rename to examples/private.json index 929d6ec0..b5a3e551 100644 --- a/resources/EXAMPLE-private.json +++ b/examples/private.json @@ -1,4 +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/requirements.txt b/requirements.txt new file mode 100644 index 00000000..04c5b308 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +yahoo-oauth==0.1.9 +stringcase==1.2.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..b88034e4 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = README.md diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..3ccb0c2c --- /dev/null +++ b/setup.py @@ -0,0 +1,31 @@ +import setuptools + +with open("README.md", "r") as docs: + long_description = docs.read() + +with open("requirements.txt") as reqs: + required = reqs.read().splitlines() + +setuptools.setup( + name="yffpy", + version="1.0.0", + author="Wren J. R.", + author_email="wrenjr@yahoo.com", + description="Python API wrapper for the Yahoo Fantasy Football public API.", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/uberfastman/yffpy", + packages=setuptools.find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: OS Independent", + "Development Status :: 4 - Beta", + "Topic :: Software Development :: Libraries :: Python Modules", + "Environment :: Console", + "Intended Audience :: Developers" + ], + python_requires=">=3.5", + install_requires=required +) diff --git a/yffpy/__init__.py b/yffpy/__init__.py new file mode 100644 index 00000000..945ae2d1 --- /dev/null +++ b/yffpy/__init__.py @@ -0,0 +1,3 @@ +from yffpy.data import Data +from yffpy.models import User, Game, League, Team, Standings, Manager, RosterAdds, TeamLogo, TeamPoints, TeamStandings, OutcomeTotals, Streak, Settings, RosterPosition, StatCategories, StatModifiers, Stat, StatPositionType, Bonus, Matchup, MatchupGrade, Player, ByeWeeks, Headshot, Name, PlayerPoints, PlayerStats, SelectedPosition +from yffpy.query import YahooFantasyFootballQuery diff --git a/yffpy/data.py b/yffpy/data.py new file mode 100644 index 00000000..3003c1c7 --- /dev/null +++ b/yffpy/data.py @@ -0,0 +1,91 @@ +import json +import logging +import os + +from yffpy.models import YahooFantasyObject +from yffpy.utils import complex_json_handler, unpack_data + +logger = logging.getLogger(__name__) + + +class Data(object): + + def __init__(self, save_dir): + self.save_dir = save_dir + + @staticmethod + def get(yff_query, params=None): + # 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 "")) + return data + + def save(self, file_name, yff_query, params=None): + + if not os.path.exists(self.save_dir): + os.makedirs(self.save_dir) + + data = self.get(yff_query, params) + + saved_data_file_path = os.path.join(self.save_dir, file_name + ".json") + with open(saved_data_file_path, "w", encoding="utf-8") as data_file: + json.dump(data, data_file, ensure_ascii=False, indent=2, default=complex_json_handler) + logger.info("DATA SAVED LOCALLY TO: {}".format(saved_data_file_path)) + return data + + def load(self, file_name, data_type_class=None): + + saved_data_file_path = os.path.join(self.save_dir, file_name + ".json") + if os.path.exists(saved_data_file_path): + with open(saved_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 LOADED LOCALLY FROM: {}".format(saved_data_file_path)) + else: + raise FileNotFoundError( + "FILE {} DOES NOT EXIST. CANNOT LOAD DATA LOCALLY WITHOUT HAVING PREVIOUSLY SAVED DATA!".format( + saved_data_file_path)) + return data + + # def persist_and_retrieve_data(self, 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 diff --git a/dao.py b/yffpy/models.py similarity index 98% rename from dao.py rename to yffpy/models.py index cf914979..0deb4d7f 100644 --- a/dao.py +++ b/yffpy/models.py @@ -1,21 +1,11 @@ import json import logging -import inflect import stringcase -logger = logging.getLogger(__name__) -place = inflect.engine() - +from yffpy.utils import complex_json_handler -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))) +logger = logging.getLogger(__name__) class YahooFantasyObject(object): diff --git a/query.py b/yffpy/query.py similarity index 64% rename from query.py rename to yffpy/query.py index 64ac8c17..ba93de05 100644 --- a/query.py +++ b/yffpy/query.py @@ -2,8 +2,8 @@ from yahoo_oauth import OAuth2 -from yffpy.dao import * -from yffpy.util import unpack_data, reformat_json_list +from yffpy.models import * +from yffpy.utils import reformat_json_list, unpack_data logger = logging.getLogger(__name__) logging.getLogger("yahoo_oauth").setLevel(level=logging.INFO) @@ -18,10 +18,6 @@ def __init__(self, auth_dir, league_id, game_id=None, offline=False): 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) @@ -49,18 +45,22 @@ def query(self, url, data_key_list, data_type_class=None, run=True): 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)) + 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)) + 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)) + logger.debug( + "UNPACKED AND PARSED JSON (Yahoo fantasy football data wth parent type: {}): {}".format(data_type_class, + unpacked)) return { "data": clean_response_data, @@ -78,39 +78,53 @@ 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) + 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.") + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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/yffpy/utils.py similarity index 60% rename from util.py rename to yffpy/utils.py index 12f022c7..2e9beef4 100644 --- a/util.py +++ b/yffpy/utils.py @@ -1,12 +1,8 @@ -import json import logging -import os from collections import ChainMap import stringcase -from yffpy.dao import YahooFantasyObject, complex_json_handler - logger = logging.getLogger(__name__) @@ -77,6 +73,16 @@ def get_type(json_obj_dict, parent_class, subclasses): return json_obj_dict +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))) + + def reformat_json_list(json_obj): if isinstance(json_obj[0], list): if len(json_obj) > 1: @@ -86,41 +92,3 @@ def reformat_json_list(json_obj): 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