From c283d96507116416b5336dc0bec61992dad9d819 Mon Sep 17 00:00:00 2001 From: MattsFace Date: Mon, 9 Jan 2023 14:54:34 -0800 Subject: [PATCH] v0.5.2 main release --- .github/pull_request_template.md | 19 + .github/scripts/prslackbot.py | 103 + .github/scripts/pytest_report_issues.py | 139 + .../build-and-test-mlbstatsapi-prd.yml | 41 + .../build-and-test-mlbstatsapi-test.yml | 46 + .github/workflows/build-and-test.yml | 38 + .github/workflows/catch-and-report.yml | 32 + .github/workflows/post-pr-url.yml | 41 + .gitignore | 160 + CODE_OF_CONDUCT.md | 128 + CONTRIBUTING.md | 43 + LICENSE | 19 + README.md | 590 ++ mlbstatsapi/__init__.py | 8 + mlbstatsapi/exceptions.py | 2 + mlbstatsapi/mlb_api.py | 2265 ++++++ mlbstatsapi/mlb_dataadapter.py | 141 + mlbstatsapi/mlb_module.py | 162 + mlbstatsapi/models/__init__.py | 0 mlbstatsapi/models/attendances/__init__.py | 1 + mlbstatsapi/models/attendances/attendance.py | 23 + mlbstatsapi/models/attendances/attributes.py | 190 + mlbstatsapi/models/awards/__init__.py | 2 + mlbstatsapi/models/awards/attributes.py | 47 + mlbstatsapi/models/awards/awards.py | 17 + mlbstatsapi/models/data/__init__.py | 10 + mlbstatsapi/models/data/data.py | 340 + mlbstatsapi/models/divisions/__init__.py | 1 + mlbstatsapi/models/divisions/division.py | 59 + mlbstatsapi/models/drafts/__init__.py | 1 + mlbstatsapi/models/drafts/attributes.py | 44 + mlbstatsapi/models/drafts/rounds.py | 94 + mlbstatsapi/models/game/__init__.py | 5 + mlbstatsapi/models/game/attributes.py | 23 + mlbstatsapi/models/game/game.py | 48 + mlbstatsapi/models/game/gamedata/__init__.py | 2 + .../models/game/gamedata/attributes.py | 258 + mlbstatsapi/models/game/gamedata/gamedata.py | 92 + mlbstatsapi/models/game/livedata/__init__.py | 1 + .../models/game/livedata/attributes.py | 51 + .../models/game/livedata/boxscore/__init__.py | 1 + .../game/livedata/boxscore/attributes.py | 191 + .../models/game/livedata/boxscore/boxscore.py | 30 + .../game/livedata/linescore/__init__.py | 1 + .../game/livedata/linescore/attributes.py | 184 + .../game/livedata/linescore/linescore.py | 67 + mlbstatsapi/models/game/livedata/livedata.py | 45 + .../models/game/livedata/plays/__init__.py | 1 + .../game/livedata/plays/play/__init__.py | 1 + .../game/livedata/plays/play/attributes.py | 111 + .../livedata/plays/play/matchup/__init__.py | 1 + .../livedata/plays/play/matchup/attributes.py | 19 + .../livedata/plays/play/matchup/matchup.py | 64 + .../models/game/livedata/plays/play/play.py | 67 + .../livedata/plays/play/playevent/__init__.py | 1 + .../plays/play/playevent/playevent.py | 85 + .../plays/play/playrunner/__init__.py | 1 + .../plays/play/playrunner/attributes.py | 106 + .../plays/play/playrunner/playrunner.py | 28 + .../livedata/plays/playbyinning/__init__.py | 1 + .../livedata/plays/playbyinning/attributes.py | 75 + .../plays/playbyinning/playbyinning.py | 31 + .../models/game/livedata/plays/plays.py | 37 + mlbstatsapi/models/gamepace/__init__.py | 2 + mlbstatsapi/models/gamepace/attributes.py | 186 + mlbstatsapi/models/gamepace/gamepace.py | 28 + mlbstatsapi/models/homerunderby/__init__.py | 2 + mlbstatsapi/models/homerunderby/attributes.py | 326 + .../models/homerunderby/homerunderby.py | 42 + mlbstatsapi/models/leagues/__init__.py | 2 + mlbstatsapi/models/leagues/league.py | 105 + mlbstatsapi/models/people/__init__.py | 2 + mlbstatsapi/models/people/attributes.py | 113 + mlbstatsapi/models/people/people.py | 294 + mlbstatsapi/models/schedules/__init__.py | 2 + mlbstatsapi/models/schedules/attributes.py | 218 + mlbstatsapi/models/schedules/schedule.py | 38 + mlbstatsapi/models/seasons/__init__.py | 1 + mlbstatsapi/models/seasons/season.py | 80 + mlbstatsapi/models/sports/__init__.py | 1 + mlbstatsapi/models/sports/sport.py | 37 + mlbstatsapi/models/standings/__init__.py | 2 + mlbstatsapi/models/standings/attributes.py | 140 + mlbstatsapi/models/standings/standings.py | 47 + mlbstatsapi/models/stats/__init__.py | 129 + mlbstatsapi/models/stats/catching.py | 306 + mlbstatsapi/models/stats/fielding.py | 455 ++ mlbstatsapi/models/stats/game.py | 72 + mlbstatsapi/models/stats/hitting.py | 948 +++ mlbstatsapi/models/stats/pitching.py | 1051 +++ mlbstatsapi/models/stats/running.py | 29 + mlbstatsapi/models/stats/stats.py | 308 + mlbstatsapi/models/stats/streak.py | 4 + mlbstatsapi/models/stats/team.py | 1 + mlbstatsapi/models/teams/__init__.py | 2 + mlbstatsapi/models/teams/attributes.py | 185 + mlbstatsapi/models/teams/team.py | 105 + mlbstatsapi/models/venues/__init__.py | 1 + mlbstatsapi/models/venues/attributes.py | 121 + mlbstatsapi/models/venues/venue.py | 42 + pyproject.toml | 28 + .../attendance/test_attendance.py | 38 + tests/external_tests/awards/test_awards.py | 69 + .../external_tests/division/test_division.py | 38 + tests/external_tests/drafts/test_draft.py | 79 + tests/external_tests/game/test_game.py | 31 + .../external_tests/gamepace/test_gamepace.py | 67 + .../homerunderby/test_homerunderby.py | 45 + tests/external_tests/league/test_league.py | 46 + tests/external_tests/mlb/test_mlb.py | 317 + .../mlbdataadapter/test_mlbadapter.py | 151 + tests/external_tests/person/test_person.py | 59 + .../external_tests/schedule/test_schedule.py | 32 + tests/external_tests/seasons/test_seasons.py | 69 + tests/external_tests/sport/test_sport.py | 33 + .../standings/test_standings.py | 75 + tests/external_tests/stats/test_catching.py | 91 + tests/external_tests/stats/test_fielding.py | 107 + tests/external_tests/stats/test_hitting.py | 348 + tests/external_tests/stats/test_pitching.py | 266 + .../stats/test_player_game_stats.py | 125 + tests/external_tests/team/test_roster.py | 60 + tests/external_tests/team/test_team.py | 61 + tests/external_tests/venue/test_venue.py | 34 + tests/mock_tests/awards/test_awards_mock.py | 56 + tests/mock_tests/drafts/test_draft_mock.py | 61 + .../mock_tests/gamepace/test_gamepace_mock.py | 58 + .../homerunderby/test_homerunderby_mock.py | 49 + tests/mock_tests/mlb/test_mlb_mock.py | 453 ++ tests/mock_tests/mock_json/awards/awards.json | 137 + .../mock_json/divisions/division.json | 25 + .../mock_json/divisions/divisions.json | 105 + tests/mock_tests/mock_json/drafts/draft.json | 380 + .../mock_json/gamepace/gamepace.json | 594 ++ tests/mock_tests/mock_json/games/game.json | 3315 ++++++++ .../mock_json/homerunderby/homerunderby.json | 6844 +++++++++++++++++ .../mock_tests/mock_json/leagues/league.json | 50 + .../mock_tests/mock_json/leagues/leagues.json | 193 + tests/mock_tests/mock_json/people/person.json | 53 + .../mock_tests/mock_json/people/players.json | 573 ++ .../mock_json/response/error_500.json | 6 + .../mock_json/response/not_found_404.json | 6 + .../mock_json/schedule/schedule_date.json | 707 ++ .../schedule/schedule_start_end_date.json | 587 ++ tests/mock_tests/mock_json/sports/sport.json | 13 + tests/mock_tests/mock_json/sports/sports.json | 50 + .../mock_json/standings/standings.json | 1422 ++++ .../person/game_stats_player_archie.json | 219 + .../stats/person/game_stats_player_cal.json | 489 ++ .../game_stats_player_shoei_ohtani.json | 109 + .../person/game_stats_player_ty_france.json | 523 ++ .../stats/person/hitting_player_pitchlog.json | 519 ++ .../stats/person/hitting_player_playlog.json | 184 + .../stats/person/hitting_player_stats.json | 465 ++ .../mock_json/stats/person/hotcoldzone.json | 438 ++ .../person/pitching_player_pitchlog.json | 182 + .../stats/person/pitching_player_playlog.json | 175 + .../stats/person/pitching_player_stats.json | 363 + .../mock_json/stats/person/spraychart.json | 28 + .../stats/team/hitting_team_stats.json | 207 + .../stats/team/pitching_team_stats.json | 285 + tests/mock_tests/mock_json/teams/team.json | 51 + .../mock_json/teams/team_coaches.json | 151 + .../mock_json/teams/team_roster_coaches.json | 150 + .../mock_json/teams/team_roster_players.json | 710 ++ tests/mock_tests/mock_json/teams/teams.json | 215 + tests/mock_tests/mock_json/venues/venue.json | 10 + tests/mock_tests/mock_json/venues/venues.json | 46 + .../schedules/test_schedule_mock.py | 91 + .../standings/test_standings_mock.py | 56 + .../stats/test_game_player_stats_for_game.py | 165 + .../stats/test_hitting_stats_mock.py | 244 + .../stats/test_pitching_stats_mock.py | 249 + tests/mock_tests/teams/test_roster_mock.py | 66 + tests/mock_tests/teams/test_team_mock.py | 78 + .../mock_tests/teams/test_team_roster_mock.py | 63 + 176 files changed, 36398 insertions(+) create mode 100644 .github/pull_request_template.md create mode 100644 .github/scripts/prslackbot.py create mode 100644 .github/scripts/pytest_report_issues.py create mode 100644 .github/workflows/build-and-test-mlbstatsapi-prd.yml create mode 100644 .github/workflows/build-and-test-mlbstatsapi-test.yml create mode 100644 .github/workflows/build-and-test.yml create mode 100644 .github/workflows/catch-and-report.yml create mode 100644 .github/workflows/post-pr-url.yml create mode 100644 .gitignore create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 mlbstatsapi/__init__.py create mode 100644 mlbstatsapi/exceptions.py create mode 100644 mlbstatsapi/mlb_api.py create mode 100644 mlbstatsapi/mlb_dataadapter.py create mode 100644 mlbstatsapi/mlb_module.py create mode 100644 mlbstatsapi/models/__init__.py create mode 100644 mlbstatsapi/models/attendances/__init__.py create mode 100644 mlbstatsapi/models/attendances/attendance.py create mode 100644 mlbstatsapi/models/attendances/attributes.py create mode 100644 mlbstatsapi/models/awards/__init__.py create mode 100644 mlbstatsapi/models/awards/attributes.py create mode 100644 mlbstatsapi/models/awards/awards.py create mode 100644 mlbstatsapi/models/data/__init__.py create mode 100644 mlbstatsapi/models/data/data.py create mode 100644 mlbstatsapi/models/divisions/__init__.py create mode 100644 mlbstatsapi/models/divisions/division.py create mode 100644 mlbstatsapi/models/drafts/__init__.py create mode 100644 mlbstatsapi/models/drafts/attributes.py create mode 100644 mlbstatsapi/models/drafts/rounds.py create mode 100644 mlbstatsapi/models/game/__init__.py create mode 100644 mlbstatsapi/models/game/attributes.py create mode 100644 mlbstatsapi/models/game/game.py create mode 100644 mlbstatsapi/models/game/gamedata/__init__.py create mode 100644 mlbstatsapi/models/game/gamedata/attributes.py create mode 100644 mlbstatsapi/models/game/gamedata/gamedata.py create mode 100644 mlbstatsapi/models/game/livedata/__init__.py create mode 100644 mlbstatsapi/models/game/livedata/attributes.py create mode 100644 mlbstatsapi/models/game/livedata/boxscore/__init__.py create mode 100644 mlbstatsapi/models/game/livedata/boxscore/attributes.py create mode 100644 mlbstatsapi/models/game/livedata/boxscore/boxscore.py create mode 100644 mlbstatsapi/models/game/livedata/linescore/__init__.py create mode 100644 mlbstatsapi/models/game/livedata/linescore/attributes.py create mode 100644 mlbstatsapi/models/game/livedata/linescore/linescore.py create mode 100644 mlbstatsapi/models/game/livedata/livedata.py create mode 100644 mlbstatsapi/models/game/livedata/plays/__init__.py create mode 100644 mlbstatsapi/models/game/livedata/plays/play/__init__.py create mode 100644 mlbstatsapi/models/game/livedata/plays/play/attributes.py create mode 100644 mlbstatsapi/models/game/livedata/plays/play/matchup/__init__.py create mode 100644 mlbstatsapi/models/game/livedata/plays/play/matchup/attributes.py create mode 100644 mlbstatsapi/models/game/livedata/plays/play/matchup/matchup.py create mode 100644 mlbstatsapi/models/game/livedata/plays/play/play.py create mode 100644 mlbstatsapi/models/game/livedata/plays/play/playevent/__init__.py create mode 100644 mlbstatsapi/models/game/livedata/plays/play/playevent/playevent.py create mode 100644 mlbstatsapi/models/game/livedata/plays/play/playrunner/__init__.py create mode 100644 mlbstatsapi/models/game/livedata/plays/play/playrunner/attributes.py create mode 100644 mlbstatsapi/models/game/livedata/plays/play/playrunner/playrunner.py create mode 100644 mlbstatsapi/models/game/livedata/plays/playbyinning/__init__.py create mode 100644 mlbstatsapi/models/game/livedata/plays/playbyinning/attributes.py create mode 100644 mlbstatsapi/models/game/livedata/plays/playbyinning/playbyinning.py create mode 100644 mlbstatsapi/models/game/livedata/plays/plays.py create mode 100644 mlbstatsapi/models/gamepace/__init__.py create mode 100644 mlbstatsapi/models/gamepace/attributes.py create mode 100644 mlbstatsapi/models/gamepace/gamepace.py create mode 100644 mlbstatsapi/models/homerunderby/__init__.py create mode 100644 mlbstatsapi/models/homerunderby/attributes.py create mode 100644 mlbstatsapi/models/homerunderby/homerunderby.py create mode 100644 mlbstatsapi/models/leagues/__init__.py create mode 100644 mlbstatsapi/models/leagues/league.py create mode 100644 mlbstatsapi/models/people/__init__.py create mode 100644 mlbstatsapi/models/people/attributes.py create mode 100644 mlbstatsapi/models/people/people.py create mode 100644 mlbstatsapi/models/schedules/__init__.py create mode 100644 mlbstatsapi/models/schedules/attributes.py create mode 100644 mlbstatsapi/models/schedules/schedule.py create mode 100644 mlbstatsapi/models/seasons/__init__.py create mode 100644 mlbstatsapi/models/seasons/season.py create mode 100644 mlbstatsapi/models/sports/__init__.py create mode 100644 mlbstatsapi/models/sports/sport.py create mode 100644 mlbstatsapi/models/standings/__init__.py create mode 100644 mlbstatsapi/models/standings/attributes.py create mode 100644 mlbstatsapi/models/standings/standings.py create mode 100644 mlbstatsapi/models/stats/__init__.py create mode 100644 mlbstatsapi/models/stats/catching.py create mode 100644 mlbstatsapi/models/stats/fielding.py create mode 100644 mlbstatsapi/models/stats/game.py create mode 100644 mlbstatsapi/models/stats/hitting.py create mode 100644 mlbstatsapi/models/stats/pitching.py create mode 100644 mlbstatsapi/models/stats/running.py create mode 100644 mlbstatsapi/models/stats/stats.py create mode 100644 mlbstatsapi/models/stats/streak.py create mode 100644 mlbstatsapi/models/stats/team.py create mode 100644 mlbstatsapi/models/teams/__init__.py create mode 100644 mlbstatsapi/models/teams/attributes.py create mode 100644 mlbstatsapi/models/teams/team.py create mode 100644 mlbstatsapi/models/venues/__init__.py create mode 100644 mlbstatsapi/models/venues/attributes.py create mode 100644 mlbstatsapi/models/venues/venue.py create mode 100644 pyproject.toml create mode 100644 tests/external_tests/attendance/test_attendance.py create mode 100644 tests/external_tests/awards/test_awards.py create mode 100644 tests/external_tests/division/test_division.py create mode 100644 tests/external_tests/drafts/test_draft.py create mode 100644 tests/external_tests/game/test_game.py create mode 100644 tests/external_tests/gamepace/test_gamepace.py create mode 100644 tests/external_tests/homerunderby/test_homerunderby.py create mode 100644 tests/external_tests/league/test_league.py create mode 100644 tests/external_tests/mlb/test_mlb.py create mode 100644 tests/external_tests/mlbdataadapter/test_mlbadapter.py create mode 100644 tests/external_tests/person/test_person.py create mode 100644 tests/external_tests/schedule/test_schedule.py create mode 100644 tests/external_tests/seasons/test_seasons.py create mode 100644 tests/external_tests/sport/test_sport.py create mode 100644 tests/external_tests/standings/test_standings.py create mode 100644 tests/external_tests/stats/test_catching.py create mode 100644 tests/external_tests/stats/test_fielding.py create mode 100644 tests/external_tests/stats/test_hitting.py create mode 100644 tests/external_tests/stats/test_pitching.py create mode 100644 tests/external_tests/stats/test_player_game_stats.py create mode 100644 tests/external_tests/team/test_roster.py create mode 100644 tests/external_tests/team/test_team.py create mode 100644 tests/external_tests/venue/test_venue.py create mode 100644 tests/mock_tests/awards/test_awards_mock.py create mode 100644 tests/mock_tests/drafts/test_draft_mock.py create mode 100644 tests/mock_tests/gamepace/test_gamepace_mock.py create mode 100644 tests/mock_tests/homerunderby/test_homerunderby_mock.py create mode 100644 tests/mock_tests/mlb/test_mlb_mock.py create mode 100644 tests/mock_tests/mock_json/awards/awards.json create mode 100644 tests/mock_tests/mock_json/divisions/division.json create mode 100644 tests/mock_tests/mock_json/divisions/divisions.json create mode 100644 tests/mock_tests/mock_json/drafts/draft.json create mode 100644 tests/mock_tests/mock_json/gamepace/gamepace.json create mode 100644 tests/mock_tests/mock_json/games/game.json create mode 100644 tests/mock_tests/mock_json/homerunderby/homerunderby.json create mode 100644 tests/mock_tests/mock_json/leagues/league.json create mode 100644 tests/mock_tests/mock_json/leagues/leagues.json create mode 100644 tests/mock_tests/mock_json/people/person.json create mode 100644 tests/mock_tests/mock_json/people/players.json create mode 100644 tests/mock_tests/mock_json/response/error_500.json create mode 100644 tests/mock_tests/mock_json/response/not_found_404.json create mode 100644 tests/mock_tests/mock_json/schedule/schedule_date.json create mode 100644 tests/mock_tests/mock_json/schedule/schedule_start_end_date.json create mode 100644 tests/mock_tests/mock_json/sports/sport.json create mode 100644 tests/mock_tests/mock_json/sports/sports.json create mode 100644 tests/mock_tests/mock_json/standings/standings.json create mode 100644 tests/mock_tests/mock_json/stats/person/game_stats_player_archie.json create mode 100644 tests/mock_tests/mock_json/stats/person/game_stats_player_cal.json create mode 100644 tests/mock_tests/mock_json/stats/person/game_stats_player_shoei_ohtani.json create mode 100644 tests/mock_tests/mock_json/stats/person/game_stats_player_ty_france.json create mode 100644 tests/mock_tests/mock_json/stats/person/hitting_player_pitchlog.json create mode 100644 tests/mock_tests/mock_json/stats/person/hitting_player_playlog.json create mode 100644 tests/mock_tests/mock_json/stats/person/hitting_player_stats.json create mode 100644 tests/mock_tests/mock_json/stats/person/hotcoldzone.json create mode 100644 tests/mock_tests/mock_json/stats/person/pitching_player_pitchlog.json create mode 100644 tests/mock_tests/mock_json/stats/person/pitching_player_playlog.json create mode 100644 tests/mock_tests/mock_json/stats/person/pitching_player_stats.json create mode 100644 tests/mock_tests/mock_json/stats/person/spraychart.json create mode 100644 tests/mock_tests/mock_json/stats/team/hitting_team_stats.json create mode 100644 tests/mock_tests/mock_json/stats/team/pitching_team_stats.json create mode 100644 tests/mock_tests/mock_json/teams/team.json create mode 100644 tests/mock_tests/mock_json/teams/team_coaches.json create mode 100644 tests/mock_tests/mock_json/teams/team_roster_coaches.json create mode 100644 tests/mock_tests/mock_json/teams/team_roster_players.json create mode 100644 tests/mock_tests/mock_json/teams/teams.json create mode 100644 tests/mock_tests/mock_json/venues/venue.json create mode 100644 tests/mock_tests/mock_json/venues/venues.json create mode 100644 tests/mock_tests/schedules/test_schedule_mock.py create mode 100644 tests/mock_tests/standings/test_standings_mock.py create mode 100644 tests/mock_tests/stats/test_game_player_stats_for_game.py create mode 100644 tests/mock_tests/stats/test_hitting_stats_mock.py create mode 100644 tests/mock_tests/stats/test_pitching_stats_mock.py create mode 100644 tests/mock_tests/teams/test_roster_mock.py create mode 100644 tests/mock_tests/teams/test_team_mock.py create mode 100644 tests/mock_tests/teams/test_team_roster_mock.py diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..d2b72b3e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,19 @@ +### Why + +Background/reason for the PR, e.g. what problem is being solved by the changes + +### What + +Overview of the changes made + +### Tests + +How was this tested? Any additional testing done outside automated tests, or no testing needed? + +### Risk and impact + +What is the risk level of the change and **why?** + +- Minimal / Normal / High + +What is the impact if something does go wrong with this PR? diff --git a/.github/scripts/prslackbot.py b/.github/scripts/prslackbot.py new file mode 100644 index 00000000..dae26676 --- /dev/null +++ b/.github/scripts/prslackbot.py @@ -0,0 +1,103 @@ +import sys +import os +import datetime +# Import WebClient from Python SDK (github.com/slackapi/python-slack-sdk) +from slack_sdk import WebClient +from slack_sdk.errors import SlackApiError + +# get from environment variables +PR_URL = os.environ.get("PR_URL") +PR_USER = os.environ.get("PR_USER") +PR_USER_IMAGE = os.environ.get("PR_USER_IMAGE") +PR_NUMBER = os.environ.get("PR_NUMBER") +PR_TITLE = os.environ.get("PR_TITLE") +PR_TIME = int(datetime.datetime.timestamp(datetime.datetime.strptime(os.environ.get("PR_TIME"), "%Y-%m-%dT%H:%M:%SZ"))) +PR_BODY = os.environ.get("PR_BODY") +PR_REPO = os.environ.get("PR_REPO") +PR_REPO_URL = os.environ.get("PR_REPO_URL") +NUM_COMMIT = os.environ.get("NUM_COMMIT") +HEAD_REPO_NAME = os.environ.get("HEAD_REPO_NAME") +BASE_REPO_NAME = os.environ.get("BASE_REPO_NAME") + +def reportpullrequesturl(channel, slacktoken, msg): + + # WebClient instantiates a client that can call API methods + client = WebClient(token=slacktoken) + + # ID of channel you want to post message to + channel_id = channel + + try: + # Call the conversations.list method using the WebClient + client.chat_postMessage( + channel=channel_id, + text = f'New pull request by KCNilssen', + blocks= [ + { + "type": "context", + "elements": [ + { + "type": "image", + "image_url": f'{PR_USER_IMAGE}', + "alt_text": "user logo" + }, + { + "type": "mrkdwn", + "text": f'{PR_USER} wants to merge {NUM_COMMIT} commits into <{PR_REPO_URL}'+f'/tree/'+f'{BASE_REPO_NAME}|{BASE_REPO_NAME}> from <{PR_REPO_URL}'+f'/tree/'+f'{HEAD_REPO_NAME}|{HEAD_REPO_NAME}> \n{PR_URL}' + } + ] + }, + { + "type": "divider" + } + ], + attachments= [ + { + "color": "#3ca553", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f'*<{PR_URL}|#{PR_NUMBER} {PR_TITLE}>* \n{msg}' + } + }, + { + "type": "context", + "elements": [ + { + "type": "image", + "image_url": 'https://slack-imgs.com/?c=1&o1=wi32.he32.si&url=https%3A%2F%2Fslack.github.com%2Fstatic%2Fimg%2Ffavicon-neutral.png', + "alt_text": "github logo" + }, + { + "type": "mrkdwn", + "text": f'<{PR_REPO_URL}|{PR_REPO}> _|_ _|_ Added by cronbot' + } + ] + } + ] + } + ] + ) + + except SlackApiError as e: + print(f"Error: {e}") + + +def format_pr_template() -> str: + msg = [] + + for line in PR_BODY.splitlines(): + if line[:4] == "### ": + line = '*' + line[4:] + '*' + msg.append(line) + + return "\n".join(msg) + +def main(args): + message = format_pr_template() + reportpullrequesturl(args[1], args[2], message) + +if __name__ == '__main__': + main(sys.argv) \ No newline at end of file diff --git a/.github/scripts/pytest_report_issues.py b/.github/scripts/pytest_report_issues.py new file mode 100644 index 00000000..bcf1da98 --- /dev/null +++ b/.github/scripts/pytest_report_issues.py @@ -0,0 +1,139 @@ +import sys +import re +from slack_sdk import WebClient +from slack_sdk.errors import SlackApiError + +def cronbot_post_uka(slack_webclient_token, channel_id, message, status, linecolor): + + # WebClient instantiates a client that can call API methods + client = WebClient(token=slack_webclient_token) + + try: + # Call the conversations.list method using the WebClient + client.chat_postMessage( + channel=channel_id, + text=f'{status} Test for mlbstatsapi', + attachments= + [ + { + "color": f'{linecolor}', + "blocks": [ + { + "type": "context", + "elements": [ + { + "type": "mrkdwn", + "text": f"```\n{message}\n```" + } + ] + }, + ] + } + ] + ) + + except SlackApiError as e: + print(f"Error: {e}") + +def escape_ansi(line): + ansi_escape = re.compile(r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[@-~]') + return ansi_escape.sub('', line) + +def generate_outputstring(from_list) -> str: + + short_test_summary_info_types = ["FAILED", "ERROR", "SKIPPED", + "XFAILED", "XPASSED", "PASSED"] + + testing_output = "" + + for output in from_list: + + output = escape_ansi(output) + + if len(output) > 78: + + if output[:10] == "==========": + output = output.replace("=", "") + # print ([*output]) + output = '{:=^78}'.format(output[:-2]) + '\r\n' + + elif output[-8] == "[" and output[-3] == "]": + if output[-12:-8] != " ": + output = list(output) + output[-15] = '.' + output[-14] = '.' + output[-13] = '.' + output[-12] = ' ' + output[-11] = ' ' + output[-10] = ' ' + output[-9] = ' ' + output = ''.join(output) + output = output[:72] + output[-8:] + + elif output[:10] == "collecting": + output = ' '.join(output[16:].split()[-3:]) + '\r\n' + + elif output.split()[0] in short_test_summary_info_types: + output_listified = output.split() + output = ' '.join(output_listified[:2]) + output += '\r\n' + + temp_string = ' ' * (len(output_listified[0])) + for word in output_listified[2:]: + if len(temp_string) + 1 + len(word) <= 78: + temp_string+=" " + word + else: + if len(word) > 78 - len(output_listified[0]) + 1: + for char in word: + if len(temp_string) >= 78: + temp_string += '\r\n' + output += temp_string + temp_string = ' ' * (len(output_listified[0]) + 1) + temp_string += char + else: + temp_string += " " + char + else: + temp_string += '\r\n' + output += temp_string + temp_string = ' ' * (len(output_listified[0]) + 1) + temp_string += word + + output += temp_string + output += '\r\n' + + else: + output = output[:75] + "...\r\n" + + if output[0] == "." and output[-8] == "[" and output[-3] == "]": + pass + else: + testing_output+=output + + return testing_output + + +if __name__ == "__main__": + channelid = sys.argv[1] + token = sys.argv[2] + + output_list = [] + + line = None + + for line in sys.stdin: + output_list.append(line) + + if not line: + statusmessage = "failed" + statuscolor = "#cd3920" + elif ("failed" in line or "xfailed" in line): + statusmessage = "Failed" + statuscolor = "#cd3920" + elif ("errors" in line or "error" in line or "SKIPPED" in line): + statusmessage = "Error with" + statuscolor = "#f2a029" + else: + statusmessage = "Successful" + statuscolor = "#3ca553" + + cronbot_post_uka(token, channelid, generate_outputstring(output_list), statusmessage, statuscolor) \ No newline at end of file diff --git a/.github/workflows/build-and-test-mlbstatsapi-prd.yml b/.github/workflows/build-and-test-mlbstatsapi-prd.yml new file mode 100644 index 00000000..ffbf4116 --- /dev/null +++ b/.github/workflows/build-and-test-mlbstatsapi-prd.yml @@ -0,0 +1,41 @@ +name: Python Build MLBstats API + +on: + push: + branches: + - main +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade pytest + python3 -m pip install --upgrade build + python3 -m pip install --upgrade requests + python3 -m pip install --upgrade requests_mock + - name: Test with mocks with pytest + run: | + python3 -m pytest tests/mock_tests/* + - name: Test external tests with pytest + run: | + python3 -m pytest tests/external_tests/* + - name: build and install + run: | + python3 -m build + python3 -m pip install . + - name: Publish a Python distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/build-and-test-mlbstatsapi-test.yml b/.github/workflows/build-and-test-mlbstatsapi-test.yml new file mode 100644 index 00000000..1c83e9c8 --- /dev/null +++ b/.github/workflows/build-and-test-mlbstatsapi-test.yml @@ -0,0 +1,46 @@ +name: Build + +on: + push: + branches: + - development + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade pytest + python3 -m pip install --upgrade build + python3 -m pip install --upgrade requests + python3 -m pip install --upgrade requests_mock + - name: Test with mocks with pytest + run: | + python3 -m pytest tests/mock_tests/* + - name: Test external tests with pytest + run: | + python3 -m pytest tests/external_tests/* + - name: build and install + run: | + python3 -m build + python3 -m pip install . + - name: Publish package to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + + + diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 00000000..d6119323 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,38 @@ +name: Python Build MLBstats API + +on: + push: + branches-ignore: + - 'main' + - 'development' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade pytest + python3 -m pip install --upgrade build + python3 -m pip install --upgrade requests + python3 -m pip install --upgrade requests_mock + - name: Test with mocks with pytest + run: | + python3 -m pytest tests/mock_tests/* + - name: Test external tests with pytest + run: | + python3 -m pytest tests/external_tests/* + - name: build and install + run: | + python3 -m build + python3 -m pip install . \ No newline at end of file diff --git a/.github/workflows/catch-and-report.yml b/.github/workflows/catch-and-report.yml new file mode 100644 index 00000000..5cdb34bd --- /dev/null +++ b/.github/workflows/catch-and-report.yml @@ -0,0 +1,32 @@ +name: External tests - Bidaily + +on: + schedule: + # Run every day at 10:25 am and 4:25 pm PST + - cron: '25 17,23 * * *' + +jobs: + catch_errors_and_report: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10"] + + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade pytest + python3 -m pip install --upgrade requests + python3 -m pip install --upgrade slack_sdk + python3 -m pip install --upgrade requests_mock + - name: Test and report + run: | + script -q -c 'python3 -m pytest --tb=no tests/*' /dev/null | python3 ./.github/scripts/pytest_report_issues.py ${{ secrets.BOT_CHANNEL }} ${{ secrets.SLACK_WEBCLIENT_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/post-pr-url.yml b/.github/workflows/post-pr-url.yml new file mode 100644 index 00000000..dbffd0a2 --- /dev/null +++ b/.github/workflows/post-pr-url.yml @@ -0,0 +1,41 @@ +name: Report push request url + +on: + pull_request: + types: [opened] + +jobs: + reportPR: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade slack_sdk + - name: Report PR url + env: + PR_URL: ${{ github.event.pull_request.html_url }} + PR_USER: ${{ github.event.pull_request.user.login }} + PR_USER_IMAGE: ${{ github.event.pull_request.user.avatar_url }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + PR_TIME: ${{ github.event.pull_request.created_at }} + PR_BODY: ${{ github.event.pull_request.body }} + PR_REPO: ${{ github.event.repository.full_name }} + PR_REPO_URL: ${{ github.event.repository.html_url }} + NUM_COMMIT: ${{ github.event.pull_request.commits }} + HEAD_REPO_NAME: ${{ github.event.pull_request.head.ref }} + BASE_REPO_NAME: ${{ github.event.pull_request.base.ref }} + + run: | + python3 ./.github/scripts/prslackbot.py ${{ secrets.PR_CHANNEL }} ${{ secrets.SLACK_WEBCLIENT_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..155d2d7f --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +utils/*.json +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..f95d6907 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..73cc3890 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,43 @@ +# Contributing to python-mlb-statsapi +We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: + +- Reporting a bug +- Discussing the current state of the code +- Submitting a fix +- Proposing new features +- Becoming a maintainer + +## We Develop with Github +We use github to host code, to track issues and feature requests, as well as accept pull requests. + +## All Code Changes Happen Through Pull Requests +Pull requests are the best way to propose changes to the codebase. We actively welcome your pull requests: + +1. Fork the repo and create your branch from `development`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Issue that pull request! + +## Any contributions you make will be under the MIT Software License +In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. + +## Report bugs using Github's [issues](https://github.com/zero-sum-seattle/python-mlb-statsapi/issues) +We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy! + +## Write bug reports with detail, background, and sample code +**Great Bug Reports** tend to have: + +- A quick summary and/or background +- Steps to reproduce + - Be specific! + - Give sample code if you can. +- What you expected would happen +- What actually happens +- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) + +## Use a Consistent Coding Style +* Adhere to this projects coding style + +## License +By contributing, you agree that your contributions will be licensed under its MIT License. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..96f1555d --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 The Python Packaging Authority + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..0bd83f70 --- /dev/null +++ b/README.md @@ -0,0 +1,590 @@ +
+ +# Python MLB Stats API + +**The Unofficial Python Wrapper for the MLB Stats API** + +[![PyPI version](https://badge.fury.io/py/python-mlb-statsapi.svg)](https://badge.fury.io/py/python-mlb-statsapi) +![Development Branch Status](https://github.com/zero-sum-seattle/python-mlb-statsapi/actions/workflows/build-and-test-mlbstatsapi-test.yml/badge.svg?event=push) +![Periodic External Test Status](https://github.com/zero-sum-seattle/python-mlb-statsapi/actions/workflows/catch-and-report.yml/badge.svg?event=schedule) +![PyPI - Python Version](https://img.shields.io/pypi/pyversions/python-mlb-statsapi) +![GitHub](https://img.shields.io/github/license/zero-sum-seattle/python-mlb-statsapi) + +
+ +### *Copyright Notice* +This package and its authors are not affiliated with MLB or any MLB team. This API wrapper interfaces with MLB's Stats API. Use of MLB data is subject to the notice posted at http://gdx.mlb.com/components/copyright.txt. + +###### This is an educational project - Not for commercial use. + + +![MLB Stats API](https://user-images.githubusercontent.com/2068393/203456246-dfdbdf0f-1e43-4329-aaa9-1c4008f9800d.jpg) + +## Getting Started + +*Python-mlb-statsapi* is a Python library that provides developers with access to the MLB Stats API which allows developers to retrieve information related to MLB teams, players, stats, and more. *Python-mlb-statsapi* written in python 3.10+. + +To get started with the library, refer to the information provided in this README. For a more detailed explanation, check out the documentation and the Wiki section. The Wiki contains information on return objects, endpoint structure, usage examples, and more. It is a valuable resource for getting started, working with the library, and finding the information you need. + + +
+ +### [Examples](#examples) | [Wiki](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki) | [API](https://statsapi.mlb.com/) + +
+ +## Installation +```python +python3 -m pip install -i https://test.pypi.org/simple/ python-mlb-statsapi +``` +## Usage +```python +python3 +>>> import mlbstatsapi +>>> mlb = mlbstatsapi.Mlb() +>>> mlb.get_people_id("Ty France") +[664034] +>>> stats = ['season', 'seasonAdvanced'] +>>> groups = ['hitting'] +>>> mlb.get_player_stats(664034, stats=stats, groups=groups) +{'hitting': {'season': Stat, 'seasonadvanced': Stat }} + +>>> mlb.get_team_id("Oakland Athletics") +[133] + +>>> stats = ['season', 'seasonAdvanced'] +>>> groups = ['pitching'] +>>> mlb.get_team_stats(133, stats, groups) +{'pitching': {'season': Stat, 'seasonadvanced': Stat }} +``` + + +## Documentation + +### [People, Person, Players, Coaches](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-People) +* `Mlb.get_people_id(self, fullname: str, sport_id: int = 1, search_key: str = 'fullname', **params)` - Return Person Id(s) from fullname +* `Mlb.get_person(self, player_id: int, **params)` - Return Person Object from Id +* `Mlb.get_people(self, sport_id: int = 1, **params)` - Return all Players from Sport +### [Draft](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Draft(round)) +* `Mlb.get_draft(self, year_id: int, **params)` - Return a draft for a given year +### [Awards](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Award) +* `Mlb.get_awards(self, award_id: int, **params)` - Return rewards recipinets for a given award +### [Teams](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Team) +* `Mlb.get_team_id(self, team_name: str, search_key: str = 'name', **params)` - Return Team Id(s) from name +* `Mlb.get_team(self, team_id: int, **params)` - Return Team Object from Team Id +* `Mlb.get_teams(self, sport_id: int = 1, **params)` - Return all Teams for Sport +* `Mlb.get_team_coaches(self, team_id: int, **params)` - Return coaching roster for team for current or specified season +* `Mlb.get_team_roster(self, team_id: int, **params)` - Return player roster for team for current or specified season +### [Stats](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Stats) +* `Mlb.get_player_stats(self, person_id: int, stats: list, groups: list, **params)` - Return stats by player id, stat type and groups +* `Mlb.get_team_stats(self, team_id: int, stats: list, groups: list, **params)` - Return stats by team id, stat types and groups +* `Mlb.get_stats(self, stats: list, groups: list, **params: dict)` - Return stats by stat type and group args +* `Mlb.get_players_stats_for_game(self, person_id: int, game_id: int, **params)` - Return player stats for a game +### [Gamepace](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Gamepace) +* `Mlb.get_gamepace(self, season: str, sport_id=1, **params)` - Return pace of game metrics for specific sport, league or team. +### [Venues](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Venue) +* `Mlb.get_venue_id(self, venue_name: str, search_key: str = 'name', **params)` - Return Venue Id(s) +* `Mlb.get_venue(self, venue_id: int, **params)` - Return Venue Object from venue Id +* `Mlb.get_venues(self, **params)` - Return all Venues +### [Sports](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Sport) +* `Mlb.get_sport(self, sport_id: int, **params)` - Return a Sport object from Id +* `Mlb.get_sports(self, **params)` - Return all teams for Sport Id +* `Mlb.get_sport_id(self, sport_name: str, search_key: str = 'name', **params)`- Return Sport Id from name +### [Schedules](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Schedule) +* `Mlb.get_schedule(self, date: str, start_date: str, end_date: str, sport_id: int, team_id: int, **params)` - Return a Schedule +### [Divisions](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Division) +* `Mlb.get_division(self, division_id: int, **params)` - Return a Divison +* `Mlb.get_divisions(self, **params)` - Return all Divisions +* `Mlb.get_division_id(self, division_name: str, search_key: str = 'name', **params)` - Return Division Id(s) from name +### [Leagues](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-League) +* `Mlb.get_league(self, league_id: int, **params)` - Return a League from Id +* `Mlb.get_leagues(self, **params)` - Return all Leagues +* `Mlb.get_league_id(self, league_name: str, search_key: str = 'name', **params)` - Return League Id(s) +### [Seasons](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Season) +* `Mlb.get_season(self, season_id: str, sport_id: int = None, **params)` - Return a season +* `Mlb.get_seasons(self, sportid: int = None, **params)` - Return all seasons +### [Standings](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Standings) +* `Mlb.get_standings(self, league_id: int, season: str, **params)` - Return standings +### [Schedules](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Schedule) +* `Mlb.get_schedule(self, date: str = None, start_date: str = None, end_date: str = None, sport_id: int = 1, team_id: int = None, **params)` - Return a Schedule from dates +* `Mlb.get_scheduled_games_by_date(self, date: str = None,start_date: str = None, end_date: str = None, sport_id: int = 1, **params)` - Return game ids from dates +### [Games](https://github.com/zero-sum-seattle/python-mlb-statsapi/wiki/Data-Types:-Game) +* `Mlb.get_game(self, game_id: int, **params)` - Return the Game for a specific Game Id +* `Mlb.get_game_play_by_play(self, game_id: int, **params)` - Return Play by play data for a game +* `Mlb.get_game_line_score(self, game_id: int, **params)` - Return a Linescore for a game +* `Mlb.get_game_box_score(self, game_id: int, **params)` - Return a Boxscore for a game + + +## Examples + +Let's show some examples of getting stat objects from the API. What is baseball with out stats right? + +### MLB Stats + +#### Player Stats +Get the Id(s) of the players you want stats for and set stat types and groups. +```python +>>> mlb = mlbstatsapi.Mlb() +>>> player_id = mlb.get_people_id("Ty France")[0] +>>> stats = ['season', 'career'] +>>> groups = ['hitting', 'pitching'] +``` +Use player.id and stat types and groups to return a stats dictionary +```python +>>> stat_dict = mlb.get_player_stats(player_id, stats=stats, groups=groups ) +>>> season_hitting_stat = stat_dict['hitting']['season'] +>>> career_pitching_stat = stat_dict['pitching']['career'] +``` +Print season hitting stats +```python +>>> for split in season_hitting_stat.splits: +... for k, v in split.stat.__dict__.items(): +... print(k, v) +gamesplayed 140 +groundouts 163 +airouts 148 +runs 65 +doubles 27 +triples 1 +homeruns 20 +strikeouts 94 +baseonballs 35 +... +>>> for split in career_pitching_stat.splits: +... for k, v in split.stat.__dict__.items(): +... print(k, v) +gamesplayed 2 +gamesstarted 0 +groundouts 2 +airouts 4 +runs 1 +doubles 0 +triples 0 +homeruns 1 +strikeouts 0 +baseonballs 0 +intentionalwalks 0 +hits 2 +hitbypitch 0 +... + +``` +#### Team stats +Get the Team Id(s) +```python +python3 +>>> mlb = mlbstatsapi.Mlb() +>>> team_id = mlb.get_team_id('Seattle Mariners')[0] +``` +Set the stat types and groups. +```python +>>> stats = ['season', 'seasonAdvanced'] +>>> groups = ['hitting'] +``` +Use team.id and the stat types and groups to return season hitting stats +```python +stats = mlb.get_team_stats(team_id, stats=stats, groups=groups) +season_hitting = stats['hitting']['season'] +advanced_hitting = stats['hitting']['seasonadvanced'] +``` +Print season and seasonadvanced stats +```python +>>> for split in season_hitting.splits: +... for k, v in split.stat.__dict__.items(): +... print(k, v) +gamesplayed 162 +groundouts 1273 +airouts 1523 +runs 690 +doubles 229 +triples 19 +>>> +>>> for split in advanced_hitting.splits: +... for k, v in split.stat.__dict__.items(): +... print(k, v) +... +plateappearances 6117 +totalbases 2094 +leftonbase 1129 +sacbunts 9 +sacflies 45 +``` +### More stats examples +#### Expected Stats +Get player Id's +```python +>>> player_id = mlb.get_people_id('Ty France')[0] +``` +Set the stat type and group +```python +>>> stats = ['expectedStatistics'] +>>> group = ['hitting'] +``` +Get Stats +```python +>>> stats = mlb.get_player_stats(player_id, stats=stats, groups=group) +>>> expectedstats = stats['hitting']['expectedstatistics'] +>>> for split in expectedstats.splits: +... for k, v in split.stat.__dict__.items(): +... print(k, v) +avg .259 +slg .394 +woba .317 +wobacon .338 +``` +#### vsPlayer +Get pitcher and batter player Ids +```python +>>> ty_france_id = mlb.get_people_id('Ty France')[0] +>>> shohei_ohtani_id = mlb.get_people_id('Shohei Ohtani')[0] +``` +Set stat type, stat groups, and params +```python +>>> stats = ['vsPlayer'] +>>> group = ['hitting'] +>>> params = {'opposingPlayerId': shohei_ohtani_id} +``` +Get stats +```python +>>> stats = mlb.get_player_stats(ty_france_id, stats=self.stats, groups=self.group, **self.params) +>>> vs_player_total = stats['hitting']['vsplayertotal'] +>>> for split in vs_player_total.splits: +... for k, v in split.stat.__dict__.items(): +... print(k, v) +gamesplayed 4 +groundouts 3 +airouts 4 +runs None +doubles 1 +triples 0 +homeruns 0 +... +>>> vs_player = stats['hitting']['vsplayer'] +>>> for split in vs_player.splits: +... for k, v in split.stat.__dict__.items(): +... print(k, v) +gamesplayed 2 +groundouts 1 +airouts 2 +runs None +doubles 1 +triples 0 +homeruns 0 +``` +#### hotColdZones +Get player Id's +```python +>>> ty_france_id = mlb.get_people_id('Ty France')[0] +>>> shohei_ohtani_id = mlb.get_people_id('Shohei Ohtani')[0] +``` +Set the stat types and groups +```python +>>> stats = ['hotColdZones'] +>>> hitting_group = ['hitting'] +>>> pitching_group = ['pitching'] +``` +The stat groups pitching and hitting both return hotColdZones for a pitcher and hitter. hotColdZones are not assigned to a +stat group because of issues related to the REST API. So hotColdZones will be assigned to the stat key in stats return dict. +```python +>>> hitting_hotcoldzones = mlb.get_player_stats(ty_france_id stats=type, groups=hitting_group) +>>> pitching_hotcoldzones = mlb.get_player_stats(shohei_ohtani_id, stats=type, groups=pitching_group) +``` +hotColdZones returns a list of the HotColdZones +```python +>>> ty_france_hotcoldzones = hitting_hotcoldzones['stats']['hotcoldzones'] +>>> shohei_ohtani_hotcoldzones = pitching_hotcoldzones['stats']['hotcoldzones'] +``` +Loop through hotColdZone objects for Ty France +```python +>>> for split in ty_france_hotcoldzones.splits: +... print(split.stat.name) +... +onBasePercentage +onBasePlusSlugging +sluggingPercentage +exitVelocity +battingAverage +``` +Loop through hotColdZone objects for Shoei Ohtani +```python +>>> for split in shohei_ohtani_hotcoldzones.splits: +... print(split.stat.name) +... +onBasePercentage +onBasePlusSlugging +sluggingPercentage +exitVelocity +battingAverage +``` +Print zone information for obp +```python +>>> for split in ty_france_hotcoldzones.splits: +... if split.stat.name == 'onBasePercentage': +... for zone in split.stat.zones: +... print('zone: ', zone.zone) +... print('value: ', zone.value) +zone: 01 +value: .226 +zone: 02 +value: .400 +zone: 03 +value: .375 +zone: 04 +``` +#### Passing params +Get Team Ids +```python +python3 +>>> mlb = mlbstatsapi.Mlb() +>>> team_id = mlb.get_team_id('Seattle Mariners')[0] +``` +Set the stat types and groups. +```python +>>> stats = ['season', 'seasonAdvanced'] +>>> groups = ['hitting'] +``` +Pass season to get_team_stats() +```python +stats = mlb.get_team_stats(team_id, stats=stats, groups=groups, season=2018) +season_hitting = stats['hitting']['season'] +advanced_hitting = stats['hitting']['seasonadvanced'] +``` +season should be 2018 +```python +>>> for split in season_hitting.splits: +... print('Season: ', split.season) +... for k, v in split.stat.__dict__.items(): +... print(k, v) +... +Season: 2018 +gamesplayed 162 +groundouts 1535 +airouts 1425 +runs 677 +... +>>> for split in advanced_hitting.splits: +... print('Season: ', split.season) +... for k, v in split.stat.__dict__.items(): +... print(k, v) +... +Season: 2018 +plateappearances 6087 +totalbases 2250 +leftonbase 1084 +sacbunts 29 +sacflies 41 +... +``` + +### Gamepace examples +Get pace of game metrics for specific sport, league or team. +```python +>>> mlb = mlbstatsapi.Mlb() +>>> season = 2021 +>>> gamepace = mlb.get_gamepace(season) +``` + +### Schedule Examples +Get a schedule for given date +```python +>>> mlb = mlbstatsapi.Mlb() +>>> schedule = mlb.get_schedule_date('2022-10-13') +``` +Get ScheduleDates from Schedule +```python +dates = schedule.dates +``` +Print Game status and Home and Away Teams +```python +>>> for date in dates: +... for game in date.games: +... print(game.status) +... print(game.teams.home) +... print(game.teams.away) +``` +### Game Examples +Get a Game for a given game id +```python +>>> mlb = mlbstatsapi.Mlb() +>>> game = mlb.get_game(662242) +``` +Get the weather for a game for a given game id +```python +>>> mlb = mlbstatsapi.Mlb() +>>> game = mlb.get_game(662242) +>>> weather = game.gamedata.weather +>>> +>>> print(weather.condition) +>>> print(weather.temp) +>>> print(weather.wind) +``` +Get the current status of a game for a given game id +```python +>>> mlb = mlbstatsapi.mlb() +>>> game = mlb.get_game(662242) +>>> +>>> linescore = game.livedata.linescore +>>> hometeaminfo = game.gamedata.teams.home +>>> awayteaminfo = game.gamedata.teams.away +>>> hometeamstatus = linescore.teams.home +>>> awayteamstatus = linescore.teams.away +>>> +>>> print("home: ", hometeaminfo.franchisename, hometeaminfo.clubname) +>>> print(" runs:", hometeamstatus.runs) +>>> print(" hits:", hometeamstatus.hits) +>>> print(" errors:", hometeamstatus.errors) +>>> print("away: ", awayteaminfo.franchisename, awayteaminfo.clubname) +>>> print(" runs:", awayteamstatus.runs) +>>> print(" hits:", awayteamstatus.hits) +>>> print(" errors:", awayteamstatus.errors) +>>> print("") +>>> print("inning:", linescore.inninghalf, linescore.currentinningordinal) +>>> print("balls:", linescore.balls) +>>> print("strikes:", linescore.strikes) +>>> print("Outs:", linescore.outs) +``` +Get the play by play, line score, and box score objects from a game +```python +>>> mlb = mlbstatsapi.Mlb() +>>> game = mlb.get_game(662242) +>>> +>>> play_by_play = game.livedata.plays +>>> line_score = game.livedata.linescore +>>> box_score = game.livedata.boxscore +``` +#### Play by Play +Get only the play by play for a given game id +```python +>>> mlb = mlbstatsapi.Mlb() +>>> playbyplay = mlb.get_play_by_play(662242) +``` +#### Line Score +Get only the line score for a given game id +```python +>>> mlb = mlbstatsapi.Mlb() +>>> linescore = mlb.get_line_score(662242) +``` +#### Box Score +Get only the box score for a given game id +```python +>>> mlb = mlbstatsapi.Mlb() +>>> boxscore = mlb.get_box_score(662242) +``` + +### People Examples +Get all Players for a given sport id +```python +>>> mlb = mlbstatsapi.Mlb() +>>> sport_id = mlb.get_sport_id() +>>> players = mlb.get_players(sport_id=sport_id) +>>> for player in players: +... print(player.id) +``` +Get a player id +```python +>>> player_id = mlb.get_people_id("Ty France") +>>> print(player_id[0]) +>>> [664034] +``` + +### Team Examples +Get a Team +```python +>>> mlb = mlbstatsapi.Mlb() +>>> team_ids = mlb.get_team_id("Seattle Mariners") +>>> team_id = team_ids[0] +>>> team = mlb.get_team(team_id.id) +>>> print(team.id) +>>> print(team.name) +``` +Get a Player Roster +```python +>>> mlb = mlbstatsapi.Mlb() +>>> team_id = 133 +>>> players = mlb.get_team_roster(team_id) +>>> for player in players: + print(player.jerseynumber) +``` +Get a Coach Roster +```python +>>> mlb = mlbstatsapi.Mlb() +>>> team_id = 133 +>>> coaches = mlb.get_team_coaches(team_id) +>>> for coach in coaches: + print(coach.title) +``` + +### Draft Examples +Get a draft for a year +```python +>>> mlb = mlbstatsapi.Mlb() +>>> draft_year = '2019' +>>> draft = mlb.get_draft(draft_year) +``` +Get Players from Draft +```python +>>> draftpicks = draft[0].picks +>>> for draftpick in draftpicks: +... print(draftpick.id) +... print(draftpick.pickround) +``` + +### Award Examples +Get awards for a given award id +```python +>>> mlb = mlbstatsapi.Mlb() +>>> retiredjersy = self.mlb.get_awards(award_id='RETIREDUNI_108') +>>> for recipient in retiredjersy.awards: +... print (recipient.player.nameFirstLast, recipient.name, recipient.date) +``` + +### Venue Examples +Get a Venue +```python +>>> mlb = mlbstatsapi.Mlb() +>>> vevue_ids = mlb.get_venue_id('PNC Park') +>>> venue_id = venue_ids[0] +>>> venue = mlb.get_team(venue.id) +>>> print(venue.id) +>>> print(venue.name) +``` + +### Sport Examples +Get a Sport +```python +>>> mlb = mlbstatsapi.Mlb() +>>> sport_ids = mlb.get_sport_id('Major League Baseball') +>>> sport_id = sport_ids[0] +>>> sport = mlb.get_sport(sport_id) +``` + +### Division Examples +Get a division +```python +>>> mlb = mlbstatsapi.Mlb() +>>> division = mlb.get_division(200) +>>> print(division.name) +``` + +### League Examples +Get a league +```python +>>> mlb = mlbstatsapi.Mlb() +>>> league = mlb.get_league(103) +>>> print(league.name) +``` + +### Season Examples +Get a Season +```python +>>> mlb = mlbstatsapi.Mlb() +>>> season = mlb.get_season(2018) +>>> print(season.seasonid) +``` + +### Standings Examples +Get a Standings +```python +>>> mlb = mlbstatsapi.Mlb() +>>> standings = mlb.get_standings(103, 2018) +``` diff --git a/mlbstatsapi/__init__.py b/mlbstatsapi/__init__.py new file mode 100644 index 00000000..d61935df --- /dev/null +++ b/mlbstatsapi/__init__.py @@ -0,0 +1,8 @@ +from .mlb_api import Mlb +from .mlb_dataadapter import MlbDataAdapter, MlbResult +from .exceptions import TheMlbStatsApiException + +from .mlb_module import ( + return_splits, + get_stat_attributes + ) diff --git a/mlbstatsapi/exceptions.py b/mlbstatsapi/exceptions.py new file mode 100644 index 00000000..03a329c3 --- /dev/null +++ b/mlbstatsapi/exceptions.py @@ -0,0 +1,2 @@ +class TheMlbStatsApiException(Exception): + pass \ No newline at end of file diff --git a/mlbstatsapi/mlb_api.py b/mlbstatsapi/mlb_api.py new file mode 100644 index 00000000..98246d18 --- /dev/null +++ b/mlbstatsapi/mlb_api.py @@ -0,0 +1,2265 @@ +import logging +import datetime + +from typing import List, Union + +from mlbstatsapi.models.people import Person, Player, Coach +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.sports import Sport +from mlbstatsapi.models.leagues import League +from mlbstatsapi.models.game import Game, Plays, Linescore, BoxScore +from mlbstatsapi.models.venues import Venue +from mlbstatsapi.models.divisions import Division +from mlbstatsapi.models.schedules import Schedule, ScheduleGames +from mlbstatsapi.models.attendances import Attendance +from mlbstatsapi.models.stats import Stat +from mlbstatsapi.models.seasons import Season +from mlbstatsapi.models.drafts import Round +from mlbstatsapi.models.awards import Award +from mlbstatsapi.models.gamepace import Gamepace +from mlbstatsapi.models.homerunderby import Homerunderby +from mlbstatsapi.models.standings import Standings + +from .mlb_dataadapter import MlbDataAdapter +# from .exceptions import TheMlbStatsApiException +from . import mlb_module + + +class Mlb: + """ + A class used to retrive MLB Stats API objects + + ... + + Attributes + ---------- + hostname: str + hostname of statsapi.mlb.com + logger: logging.Loger + logger + """ + def __init__(self, hostname: str = 'statsapi.mlb.com', logger: logging.Logger = None): + self._mlb_adapter_v1 = MlbDataAdapter(hostname, 'v1', logger) + self._mlb_adapter_v1_1 = MlbDataAdapter(hostname, 'v1.1', logger) + self._logger = logger or logging.getLogger(__name__) + self._logger.setLevel(logging.DEBUG) + + def get_people(self, sport_id: int = 1, **params) -> List[Person]: + """ + return the all players for sportid + + Parameters + ---------- + sport_id : int + Insert a sportId to return player information for a particular + sport. + + Other Parameters + ---------------- + season : str + Insert year to return player information for a particular season. + gameType : str + Insert gameType to return player information for a particular + gameType. + + Returns + ------- + list + Returns a list of People + + See Also + -------- + Mlb.get_person : Return Person from id. + Mlb.get_people_id : Return person id from name. + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_people() + [Person, Person, Person] + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'sports/{sport_id}/players', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + people = [] + + if 'people' in mlb_data.data and mlb_data.data['people']: + people = [Person(**person) for person in mlb_data.data['people']] + + return people + + def get_person(self, player_id: int, **params) -> Union[Person, None]: + """ + This endpoint returns statistical data and biographical information + for a player,coach or umpire based on playerId. + + Parameters + ---------- + person_id : int + Insert personId for a specific player, coach or umpire based on + playerId. + + Returns + ------- + Person + Returns a Person + + See Also + -------- + Mlb.get_people : Return a list of People from sport id. + Mlb.get_people_id : Return person id from name. + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_person(660271) + Person + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'people/{player_id}', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return None + + if 'people' in mlb_data.data and mlb_data.data['people']: + for person in mlb_data.data['people']: + return Person(**person) + + def get_persons(self, person_ids: Union[str, List[int]], **params) -> List[Person]: + """ + This endpoint returns statistical data and biographical information + for a players,umpires, and coaches based on playerId. + + Parameters + ---------- + person_ids : str, List[int] + Insert personId(s) to return biographical information for a + specific player. Format '605151,592450' or [605151,592450] + + Other Parameters + ---------------- + hydrate : str + Insert hydration(s) to return statistical or biographical data + for a specific player(s). + Format stats(group=["statGroup1","statGroup2"], + type=["statType1","statType2"]). + Available Hydrations: + hydrations + currentTeam + team + rosterEntries + relatives + transactions + social + education + draft + mixedFeed + articles + videos + xrefId + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + Returns + ------- + Person + Returns a Person + + See Also + -------- + Mlb.get_people : Return a list of People from sport id. + Mlb.get_people_id : Return person id from name. + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_person(660271) + Person + """ + params['personIds'] = person_ids + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'people', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + person_list = [] + + if 'people' in mlb_data.data and mlb_data.data['people']: + for person in mlb_data.data['people']: + person_list.append(Person(**person)) + + return person_list + + def get_people_id(self, fullname: str, sport_id: int = 1, + search_key: str = 'fullname', **params) -> List[int]: + """ + Returns specific player information based on players fullname + + Parameters + ---------- + fullname : str + Person full name + sport_id : int + Insert sportId to return player information for particular sport. + + Other Parameters + ---------------- + season : int + Insert year to return player information for a particular season. + gameType : str + Insert gameType to return player information for a particular + gameType. + + Returns + ------- + list of int + Returns a list of person ids + + See Also + -------- + Mlb.get_people : Return a list of People from sport id. + Mlb.get_person : Return Person from id. + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_people_id("Ty France") + [664034] + """ + # Used to reduce the amount of unneccessary data requested from api + params['fields'] = 'people,id,fullName' + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'sports/{sport_id}/players', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + player_ids = [] + + if 'people' in mlb_data.data and mlb_data.data['people']: + for person in mlb_data.data['people']: + try: + if person[search_key].lower() == fullname.lower(): + player_ids.append(person['id']) + except KeyError: + continue + return player_ids + + def get_teams(self, sport_id: int = 1, **params) -> List[Team]: + """ + return the all Teams + + Parameters + ---------- + sport_id : int + Insert sportId to return team information for a particular sportId + + Other Parameters + ---------------- + season : str + Insert year to return team information for a particular season. + leagueIds : int + Insert leagueId to return team information for particular league. + activeStatus : str + Insert activeStatus to populate a teams based on active/inactive + status for a given season. There are three status types: Y, N, B + allStarStatuses : str + Insert allStarStatuses to populate a teams based on Allstar status + for a given season. There are two status types: Y and N + sportIds : str + Insert sportId to return team information for a particular sportId + Usage: '1' or '1,11,12' + gameType : str + Insert gameType to return team information for a particular + gameType. For a list of all gameTypes: + https://statsapi.mlb.com/api/v1/gameTypes + hydrate : str + Insert Hydration(s) to return data for any available team + hydration. Format "league,venue" + Available Hydrations: + previousSchedule + nextSchedule + venue + social + deviceProperties + game(promotions) + game(atBatPromotions) + game(tickets) + game(atBatTickets) + game(sponsorships) + league + person + sport + division + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + Returns + ------- + list of Teams + returns a list of teams + + See Also + -------- + Mlb.get_team : Return a Team from id + Mlb.get_team_id : Return a list of Teams from sport id. + Mlb.get_team_coaches : Return a list of Coaches from team id + Mlb.get_team_roster : Return a list of Players from team id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_teams() + [Team, Team, Team] + """ + params['sportId'] = sport_id + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'teams', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + teams = [] + + if 'teams' in mlb_data.data and mlb_data.data['teams']: + teams = [Team(**team) for team in mlb_data.data['teams']] + + return teams + + def get_team(self, team_id: int, **params) -> Union[Team, None]: + """ + Returns a team based on teamId. + + Parameters + ---------- + team_id : int + Insert teamId to return a directory of team information for a + particular club. + + Other Parameters + ---------------- + season : int + Insert year to return a directory of team information for a + particular club in a specific season. + sportId : int + Insert a sportId to return a directory of team information for a + particular club in a sport. + hydrate : str + Insert Hydration(s) to return data for any available team + hydration. Format "league,venue" + Available Hydrations: + previousSchedule + nextSchedule + venue + social + deviceProperties + game(promotions) + game(atBatPromotions) + game(tickets) + game(atBatTickets) + game(sponsorships) + league + person + sport + division + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + Returns + ------- + Team + returns a Team from team id + + See Also + -------- + Mlb.get_teams : Return a list of Teams from sport id. + Mlb.get_team_id : Return a list of team ids from name. + Mlb.get_team_coaches : Return a list of Coaches from team id + Mlb.get_team_roster : Return a list of Players from team id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_team(133) + Team + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'teams/{team_id}', + ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return None + + if 'teams' in mlb_data.data and mlb_data.data['teams']: + for team in mlb_data.data['teams']: + return Team(**team) + + def get_team_id(self, team_name: str, + search_key: str = 'name', **params) -> List[int]: + """ + return a team Id + + Parameters + ---------- + team_name : str + Teams name + + search_key : str + search key search json for matching team_name + + Other Parameters + ---------------- + sportId : int + sport id number for team search + + Returns + ------- + list of ints + returns a list of matching team ids + + See Also + -------- + Mlb.get_teams : Return a list of Teams from sport id. + Mlb.get_team : Return a Team from id + Mlb.get_team_coaches : Return a list of Coaches from team id + Mlb.get_team_roster : Return a list of Players from team id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_team_id("Oakland Athletics") + [133] + """ + # Used to reduce the amount of unneccessary data requested from api + params['fields'] = 'teams,id,name' + + mlb_data = self._mlb_adapter_v1.get(endpoint='teams', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + team_ids = [] + + if 'teams' in mlb_data.data and mlb_data.data['teams']: + for team in mlb_data.data['teams']: + try: + if team[search_key].lower() == team_name.lower(): + team_ids.append(team['id']) + except (KeyError): + continue + return team_ids + + def get_team_roster(self, team_id: int, **params) -> List[Player]: + """ + return the team player roster + + Parameters + ---------- + team_id : int + teamId to return a directory of players based on roster status for + a particular club. + + Other Parameters + ---------------- + rosterType : str + Insert teamId to return a directory of players based on roster + status for a particular club. rosterType's include 40Man, + fullSeason, fullRoster, nonRosterInvitees, active, allTime, + depthChart, gameday, and coach. + season : str + Insert year to return a directory of players based on roster + status for a particular club in a specific season. + date : str + Insert date to return a directory of players based on roster + status for a particular club on a specific date. + hydrate : str + Insert Hydration(s) to return data for any available team + hydration. The hydration for Teams contains "person" which has + subhydrations Format "person(subHydration1, subHydrations2)" + Available Hydrations: + "person" + Hydrations Available Through Person + hydrations + awards + currentTeam + team + rosterEntries + relatives + transactions + social + education + stats + draft + mixedFeed + articles + video + xrefId + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + Returns + ------- + list of players + + See Also + -------- + Mlb.get_teams : Return a list of Teams from sport id. + Mlb.get_team : Return a Team from id + Mlb.get_team_coaches : Return a list of Coaches from team id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_team_roster(133) + [Player, Player, Player] + >>> mlb.get_team_roster(133, rosterType=40Man, season=2018) + [Player, Player, Player] + >>> mlb.get_team_roster(133, date='06/05/2018') + [Player, Player, Player] + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'teams/{team_id}/roster', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + players = [] + + if 'roster' in mlb_data.data and mlb_data.data['roster']: + for player in mlb_data.data['roster']: + players.append(Player(**mlb_module.merge_keys(player, ['person']))) + + return players + + def get_team_coaches(self, team_id: int, **params) -> List[Coach]: + """ + Return a directory of coaches for a particular team. + + Parameters + ---------- + team_id : int + Insert teamId to return a directory of coaches for a given team. + + Other Parameters + ---------------- + season : str + Insert year to return a directory of players based on roster status for a particular club in a specific season. + date : str + Insert date to return a directory of players based on roster status for a particular club on a specific date. + fields : str + Comma delimited list of specific fields to be returned. Format: topLevelNode, childNode, attribute + + Returns + ------- + list of Coaches + returns a list of Coaches + + See Also + -------- + Mlb.get_teams : Return a list of Teams from sport id. + Mlb.get_team : Return a Team from id + Mlb.get_team_roster : Return a list of Players from team id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_team_coaches(133) + [Coach, Coach, Coach] + >>> mlb.get_team_coaches(133, season='2018') + [Coach, Coach, Coach] + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'teams/{team_id}/coaches', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + coaches = [] + + if 'roster' in mlb_data.data and mlb_data.data['roster']: + for coach in mlb_data.data['roster']: + coaches.append(Coach(**mlb_module.merge_keys(coach, ['person']))) + + return coaches + + def get_schedule(self, + date: str = None, + start_date: str = None, + end_date: str = None, + sport_id: int = 1, + team_id: int = None, **params) -> Union[Schedule, None]: + """ + return the schedule created from the included params. + + Calling get_schedule without startDate or endDate results in a schedule returned + for todays date. Calling with startDate and endDate as the same date returns a + schedule for just that desired date. Different results in the schedule for multiple + days. + + Parameters + ---------- + date : str + Date + start_date : str "yyyy-mm-dd" + Start date + end_date : str "yyyy-mm-dd" + End date + sport_id : int + sport id of schedule defaults to 1 + team_id : int + get schedule for team with team_id + + Other Parameters + ---------------- + leagueId : int,str + Insert leagueId to return all schedules based on a particular + scheduleType for a specific league. Usage: 1 or '1,11 + gamePks : int,str + Insert gamePks to return all schedules based on a particular + scheduleType for specific games. Usage: 531493 or '531493,531497' + venueIds : int + Insert venueId to return all schedules based on a particular + scheduleType for a specific venueId. + gameTypes : str + Insert gameTypes to return schedule information for all games in + particular gameTypes. For a list of all gameTypes: + https://statsapi.mlb.com/api/v1/gameTypes + + scheduleType : str + Insert one or mutliple of the three available scheduleTypes to + return data for a particular schedule. Format "games,events,xref" + eventTypes : str + Insert one or mutliple of the three available eventTypes to + return data for a particular schedule. Format "primary,secondary" + There are two different schedule eventTypes: + primary- returns calendar/schedule pages. + secondary returns ticket pages. + hydrate : str + Insert Hydration(s) to return data for any available schedule + hydration. The hydrations for schedule contain "venue" and "team" + which have subhydrations. + Format "team(subHydration1, subHydrations2)" + Available Hydrations: + tickets + game(content) + game(content(all)) + game(content(media(all))) + game(content(editorial(all))) + game(content(highlights(all))) + game(content(editorial(preview))) + game(content(editorial(recap))) + game(content(editorial(articles))) + game(content(editorial(wrap))) + game(content(media(epg))) + game(content(media(milestones))) + game(content(highlights(scoreboard))) + game(content(highlights(scoreboardPreview))) + game(content(highlights(highlights))) + game(content(highlights(gamecenter))) + game(content(highlights(milestone))) + game(content(highlights(live))) + game(content(media(featured))) + game(content(summary)) + game(content(gamenotes)) + game(tickets) + game(atBatTickets) + game(promotions) + game(atBatPromotions) + game(sponsorships) + lineup + linescore + linescore(matchup) + linescore(runners) + linescore(defense) + decisions + scoringplays + broadcasts + broadcasts(all) + radioBroadcasts + metadata + game(seriesSummary) + seriesStatus + event(performers) + event(promotions) + event(timezone) + event(tickets) + event(venue) + event(designations) + event(game) + event(status) + weather + officials + probablePitcher + venue + relatedVenues + parentVenues + residentVenues + relatedVenues(venue) + parentVenues(venue) + residentVenues(venue) + location + social + relatedApplications + timezone + menu + metadata + performers + images + schedule + nextSchedule + previousSchedule + ticketManagement + xrefId + team + previousSchedule + nextSchedule + venue + springVenue + social + deviceProperties + game(promotions) + game(promotions) + game(atBatPromotions) + game(tickets) + game(atBatTickets) + game(sponsorships) + league + videos + person + sport + standings + division + xref + + + Returns + ------- + Schedule + returns the Schedule for the dates + + See Also + -------- + Mlb.get_scheduled_games_by_date : return a list of scheduledgames + + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_schedule(start_date="2021-08-01", end_date="2021-08-11") + Schedule + + """ + + if start_date and end_date: + params['startDate'] = start_date + params['endDate'] = end_date + elif date and not (start_date or end_date): + params['date'] = date + else: + return None + + + if team_id: + params['teamId'] = team_id + + params['sportId'] = sport_id + + mlb_data = self._mlb_adapter_v1.get(endpoint='schedule', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return None + + # if mlb_data is not empty, and 'dates' key is in mlb_data.data and mlb_data.data['dates] + # can sometimes be an empty list when there are no scheduled game for the date(s). + # Only check for existance 'dates' key for this reason. + + if 'dates' in mlb_data.data and mlb_data.data['dates']: + return Schedule(**mlb_data.data) + + def get_scheduled_games_by_date(self, date: str = None, + start_date: str = None, + end_date: str = None, + sport_id: int = 1, + **params) -> List[ScheduleGames]: + """ + return game ids for a specific date and game status + + Parameters + ---------- + date : str + start date, 'yyyy-mm-dd' + start_date : str "yyyy-mm-dd" + Start date + end_date : str + end date, 'yyyy-mm-dd' + spord_id : int + spord id of schedule defaults to 1 + + Other Parameters + ---------------- + leagueId : int,str + Insert leagueId to return all schedules based on a particular + scheduleType for a specific league. Usage: 1 or '1,11 + gamePks : int,str + Insert gamePks to return all schedules based on a particular + scheduleType for specific games. Usage: 531493 or '531493,531497' + venueIds : int + Insert venueId to return all schedules based on a particular + scheduleType for a specific venueId. + gameTypes : str + Insert gameTypes to return schedule information for all games in + particular gameTypes. For a list of all gameTypes: + https://statsapi.mlb.com/api/v1/gameTypes + + Returns + ------- + list of ScheduleGames + returns a list of matching game ids + + See Also + -------- + Mlb.get_game_play_by_play : return play by play data for a game + Mlb.get_game_line_score : return a linescore for a game + Mlb.get_todays_game_ids : return a list of game ids for today + Mlb.get_game : return a specific game from game id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_game_ids() + """ + if start_date and end_date: + params['startDate'] = start_date + params['endDate'] = end_date + elif date and not (start_date or end_date): + params['date'] = date + else: + return None + + params['sportId'] = sport_id + + games = [] + + mlb_data = self._mlb_adapter_v1.get(endpoint='schedule', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + if 'dates' in mlb_data.data and mlb_data.data['dates']: + for date in mlb_data.data['dates']: + for game in date['games']: + games.append(ScheduleGames(**game)) + + return games + + def get_game(self, game_id: int, **params) -> Union[Game, None]: + """ + Return the game for a specific game id + Gumbo Live Feed for a specific gamePk. + + Parameters + ---------- + game_id : int + Insert gamePk to return the GUMBO live feed for a specific game. + + Other Parameters + ---------------- + timecode : str + Use this parameter to return a snapshot of the data at the + specified time. Format: YYYYMMDD_HHMMSS. + Return timecodes from timecodes endpoint + https://statsapi.mlb.com/api/v1.1/game/534196/feed/live/timestamps + hydrate : str + Insert hydration(s) to return putout credits or defensive + positioning data for all plays in a particular game. + Format 'credits,alignment,flags' + Available Hydrations: + credits + alignment + flags + officials + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + !!! To use this we would need to make almost every !!! + !!! data class attr optonal? Do we want this? !!! + + Returns + ------- + Game + + See Also + -------- + Mlb.get_game_play_by_play : return play by play data for a game + Mlb.get_game_line_score : return a linescore for a game + Mlb.get_game_box_score : return a boxscore for a game + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_game(662242) + Game + """ + + mlb_data = self._mlb_adapter_v1_1.get(endpoint=f'game/{game_id}/feed/live', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return None + + if 'gamepk' in mlb_data.data and mlb_data.data['gamepk'] == game_id: + return Game(**mlb_data.data) + + def get_game_play_by_play(self, game_id: int, **params) -> Union[Plays, None]: + """ + return the playbyplay of a game for a specific game id + + Parameters + ---------- + game_id : int + Game id number + + Other Parameters + ---------------- + timecode : int + Use this parameter to return a snapshot of the data at the + specified time. Format: YYYYMMDD_HHMMSS + fields : + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + !!! To use this we would need to make almost every !!! + !!! data class attr optonal? Do we want this? !!! + + Returns + ------- + Plays + + See Also + -------- + Mlb.get_game_line_score : return a linescore for a game + Mlb.get_game_box_score : return a boxscore for a game + Mlb.get_game : return a specific game from game id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_game_play_by_play(662242) + Plays + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'game/{game_id}/playByPlay', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return None + + if 'allplays' in mlb_data.data and mlb_data.data['allplays']: + return Plays(**mlb_data.data) + + def get_game_line_score(self, game_id: int, **params) -> Union[Linescore, None]: + """ + return the Linescore of a game for a specific game id + + Parameters + ---------- + game_id : int + Game id number + + Other Parameters + ---------------- + timecode : int + Use this parameter to return a snapshot of the data at the + specified time. Format: YYYYMMDD_HHMMSS + fields : + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + !!! To use this we would need to make almost every !!! + !!! data class attr optonal? Do we want this? !!! + + Returns + ------- + Linescore + + See Also + -------- + Mlb.get_game_play_by_play : return play by play data for a game + Mlb.get_game_box_score : return a boxscore for a game + Mlb.get_game : return a specific game from game id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_game_line_scrore(662242) + Linescore + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'game/{game_id}/linescore', ep_params=params) + + if 'teams' in mlb_data.data and mlb_data.data['teams']: + return Linescore(**mlb_data.data) + + def get_game_box_score(self, game_id: int, **params) -> Union[BoxScore, None]: + """ + return the boxscore of a game for a specific game id + + Parameters + ---------- + game_id : int + Game id number + + Other Parameters + ---------------- + timecode : int + Use this parameter to return a snapshot of the data at the + specified time. Format: YYYYMMDD_HHMMSS + fields : + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + !!! To use this we would need to make almost every !!! + !!! data class attr optonal? Do we want this? !!! + + Returns + ------- + BoxScore + + See Also + -------- + Mlb.get_game_play_by_play : return play by play data for a game + Mlb.get_game_line_score : return a linescore for a game + Mlb.get_game : return a specific game from game id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_game_box_score(662242) + BoxScore + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'game/{game_id}/boxscore', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return None + + if 'teams' in mlb_data.data and mlb_data.data['teams']: + return BoxScore(**mlb_data.data) + + + def get_game_ids(self, date: str = None, + start_date: str = None, + end_date: str = None, + sport_id: int = 1, + **params) -> List[int]: + """ + return game ids for a specific date and game status + + Parameters + ---------- + date : str + date, 'yyyy-mm-dd' + start_date : str + start date, 'yyyy-mm-dd' + end_date : str + end date, 'yyyy-mm-dd' + spord_id : int + spord id of schedule defaults to 1 + + Returns + ------- + list of ints + returns a list of matching game ids + + See Also + -------- + Mlb.get_game_play_by_play : return play by play data for a game + Mlb.get_game_line_score : return a linescore for a game + Mlb.get_todays_game_ids : return a list of game ids for today + Mlb.get_game : return a specific game from game id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_game_ids() + """ + if start_date and end_date: + params['startDate'] = start_date + params['endDate'] = end_date + elif date and not (start_date or end_date): + params['date'] = date + else: + return None + + params['sportId'] = sport_id + + mlb_data = self._mlb_adapter_v1.get(endpoint='schedule', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + game_ids = [] + + if 'dates' in mlb_data.data and mlb_data.data['dates']: + for date in mlb_data.data['dates']: + for game in date['games']: + game_ids.append(game.gamepk) + + return game_ids + + def get_gamepace(self, season: str, sport_id=1, **params) -> Union[Gamepace, None]: + """ + Get pace of game metrics for specific sport, league or team. + + Parameters + ---------- + season : int + Insert year to return a directory of pace of game metrics for a + given season. + + Other Parameters + ---------------- + teamIds : int + Insert a teamIds to return directory of pace of game metrics for + a given team. Format '110' or '110,147' + leagueId : int + Insert leagueIds to return a directory of pace of game metrics + for a given league. Format '103' or '103,104' + leagueListId : str + Insert a unique League List Identifier to return a directory of + pace of game metrics for a specific league listId. + Available values : milb_full, milb_short, milb_complex, milb_all, + milb_all_nomex, milb_all_domestic, milb_noncomp, + milb_noncomp_nomex, milb_domcomp, milb_intcomp, win_noabl, + win_caribbean, win_all, abl, mlb, mlb_hist, mlb_milb, + mlb_milb_hist, mlb_milb_win, baseball_all + sportId : int + Insert a sportId to return a directory of pace of game metrics + for a specific sport. Format '11' or '1,11' + gameType : str + Insert gameType(s) a return a directory of pace of game metrics + for a specific gameType. For a list of all gameTypes: + https://statsapi.mlb.com/api/v1/gameTypes + date : str + Insert date to return a directory of pace of game metrics for a + particular date range. Format: MM/DD/YYYY + !!! startDate must be coupled with endDate !!! + endDate : str + Insert date to return a directory of pace of game metrics for a + particular date range. Format: MM/DD/YYYY + !!! endDate must be coupled with startDate !!! + venueIds : id + Insert venueId to return a directory of pace of game metrics for + a particular venueId. + orgType : str + Insert a orgType to return a directory of pace of game metrics + based on team, league or sport. + Available values : T- TEAM, L- LEAGUE, S- SPORT + includeChildren : bool + Insert includeChildren to return a directory of pace of game + metrics for all child teams in a given parent sport. + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + """ + params['sportId'] = sport_id + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'gamePace?season={season}', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return None + + if ('teams' in mlb_data.data and mlb_data.data['teams'] + or 'leagues' in mlb_data.data and mlb_data.data['leagues'] + or 'sports' in mlb_data.data and mlb_data.data['sports']): + + return Gamepace(**mlb_data.data) + + def get_venue(self, venue_id: int, **params) -> Union[Venue, None]: + """ + returns venue directorial information for all available venues in the Stats API. + + Parameters + ---------- + venue_id : int + venueId to return venue directorial information based venueId. + + Other Parameters + ---------------- + fields : str + Comma delimited list of specific fields to be returned. + + Returns + ------- + Venue + + See Also + -------- + Mlb.get_venues : return a list of Venues + Mlb.get_venue_id : return a list of venue ids that match name + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_venue(31) + Venue + """ + params['hydrate'] = ['location', 'fieldInfo', 'timezone'] + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'venues/{venue_id}', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + if 'venues' in mlb_data.data and mlb_data.data['venues']: + for venue in mlb_data.data['venues']: + return Venue(**venue) + + def get_venues(self, **params) -> List[Venue]: + """ + return all venues + + Returns + ------- + list of Venues + returns a list of Venues + + Other Parameters + ---------------- + venueIds : int, List[int] + Insert venueId to return venue directorial information based + venueId. + sportIds : int, List[int] + Insert sportIds to return venue directorial information based a + given sport(s). For a list of all sports: + https://statsapi.mlb.com/api/v1/sports + season : int + Insert year to return venue directorial information for a given + season. + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + See Also + -------- + Mlb.get_venue : return a Venue + Mlb.get_venue_id : return a list of venue ids that match name + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_venues() + [Venue, Venue, Venue] + """ + params['hydrate'] = ['location', 'fieldInfo', 'timezone'] + + mlb_data = self._mlb_adapter_v1.get(endpoint='venues', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + venues = [] + + if 'venues' in mlb_data.data and mlb_data.data['venues']: + venues = [Venue(**venue) for venue in mlb_data.data['venues']] + + return venues + + def get_venue_id(self, venue_name: str, + search_key: str = 'name', **params) -> List[int]: + """ + return venue id + + Parameters + ---------- + venue_name : str + venue name + + Returns + ------- + list of ints + returns a list of matching venue ints + + See Also + -------- + Mlb.get_venue : return a Venue + Mlb.get_venues : return a list of Venues + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_venue_id('PNC Park') + [31] + """ + mlb_data = self._mlb_adapter_v1.get(endpoint='venues', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + venue_ids = [] + + if 'venues' in mlb_data.data and mlb_data.data['venues']: + for venue in mlb_data.data['venues']: + try: + if venue[search_key].lower() == venue_name.lower(): + venue_ids.append(venue['id']) + except KeyError: + continue + return venue_ids + + def get_sport(self, sport_id: int, **params) -> Union[Sport, None]: + """ + return sport object from sport_id + + Parameters + ---------- + sport_id : int + Insert a sportId to return a directory of sport(s). + For a list of all sportIds: http://statsapi.mlb.com/api/v1/sports + + Other Parameters + ---------------- + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + Returns + ------- + Sport + + See Also + -------- + Mlb.get_sports : return a list of sports + Mlb.get_sport_id : return a list of matching sport ids from name + Mlb.get_sport : return a sport from id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_sport(1) + Sport + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'sports/{sport_id}', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return None + + if 'sports' in mlb_data.data and mlb_data.data['sports']: + for sport in mlb_data.data['sports']: + return Sport(**sport) + + def get_sports(self, **params) -> List[Sport]: + """ + return all sports + + Returns + ------- + list of Sports + returns a list of sport objects + + Other Parameters + ---------------- + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + See Also + -------- + Mlb.get_sport_id : return a list of matching sport ids from name + Mlb.get_sport : return a sport from id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_sports() + [Sport, Sport, Sport] + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint='sports', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + sports = [] + + if 'sports' in mlb_data.data and mlb_data.data['sports']: + sports = [Sport(**sport) for sport in mlb_data.data['sports']] + + return sports + + def get_sport_id(self, sport_name: str, + search_key: str = 'name', **params) -> List[int]: + """ + return sport id + + Parameters + ---------- + sport_name : str + Sport name + search_key : str + search key name + + Returns + ------- + list of ints + returns a list of sport ids + + See Also + -------- + Mlb.get_sports : return a list of sports + Mlb.get_sport : return a sport from id + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_sport_id("Major League Baseball") + [1] + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint='sports', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + sport_ids = [] + + if 'sports' in mlb_data.data and mlb_data.data['sports']: + for sport in mlb_data.data['sports']: + try: + if sport[search_key].lower() == sport_name.lower(): + sport_ids.append(sport['id']) + except KeyError: + continue + + return sport_ids + + def get_league(self, league_id: int, **params) -> Union[League, None]: + """ + return league + + Parameters + ---------- + league_id : int + leagueId to return league information for a specific league + + Other Parameters + ---------------- + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + Returns + ------- + League + + See Also + -------- + Mlb.get_leagues : return a list of Leagues + Mlb.get_league_id : return a list of league ids that match name + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_league(103) + [League] + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'leagues/{league_id}', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return None + + if 'leagues' in mlb_data.data and mlb_data.data['leagues']: + for league in mlb_data.data['leagues']: + return League(**league) + + def get_leagues(self, **params) -> List[League]: + """ + return all leagues + + Returns + ------- + list of Leagues + + Other Parameters + ---------------- + leagueId : str + leagueId(s) to return league information for specific leagues. + Format '103,104' + sportId : int + Insert sportId to return league information for a specific sport. + For a list of all sportIds: http://statsapi.mlb.com/api/v1/sports + seasons : str + Insert year(s) to return league information for a specific season. + Format '2017,2018' + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + See Also + -------- + Mlb.get_league : return a League from league id + Mlb.get_league_id : return a list of league ids that match name + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_leagues() + [League, League, League] + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint='leagues', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + leagues = [] + + if 'leagues' in mlb_data.data and mlb_data.data['leagues']: + leagues = [League(**league) for league in mlb_data.data['leagues']] + + return leagues + + def get_league_id(self, league_name: str, + search_key: str = 'name', **params) -> List[int]: + """ + return league id + + Parameters + ---------- + league_name : str + League name + + Returns + ------- + list of ints + + See Also + -------- + Mlb.get_league : return a League from league id + Mlb.get_leagues : return a list of Leagues + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_league_id('American League') + [103] + """ + # Used to reduce the amount of unneccessary data requested from api + params['fields'] = 'leagues,id,name' + + mlb_data = self._mlb_adapter_v1.get(endpoint='leagues', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + league_ids = [] + + if 'leagues' in mlb_data.data and mlb_data.data['leagues']: + for league in mlb_data.data['leagues']: + try: + if league[search_key].lower() == league_name.lower(): + league_ids.append(league['id']) + except KeyError: + continue + return league_ids + + def get_division(self, division_id: int, **params) -> Union[Division, None]: + """ + Returns a division based on divisionId, + + Parameters + ---------- + divison_id : int + divisionId to return a directory of division(s) for a specific division. + + Returns + ------- + Division + returns a Division + + See Also + -------- + Mlb.get_divisions : return a list of Divisions + Mlb.get_division_id : return a list of matching division ids + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_division(200) + Division + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'divisions/{division_id}', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return None + + if 'divisions' in mlb_data.data and mlb_data.data['divisions']: + for division in mlb_data.data['divisions']: + return Division(**division) + + def get_divisions(self, **params) -> List[Division]: + """ + return all divisons + + Other Parameters + ---------------- + divisionId : str + Insert divisionId(s) to return a directory of division(s) for a + specific division. Format '200,201' + leagueId : int + Insert leagueId to return a directory of division(s) for all + divisions in a specific league. + sportId : int + Insert a sportId to return a directory of division(s) for all + divisions in a specific sport. + + Returns + ------- + list of Divisions + returns a list of all divisions + + See Also + -------- + Mlb.get_division : return a Division from id + Mlb.get_division_id : return a list of matching division ids + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_divisions() + [Divison, Division, Division] + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint='divisions', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + divisions = [] + + if 'divisions' in mlb_data.data and mlb_data.data['divisions']: + divisions = [Division(**division) for division in mlb_data.data['divisions']] + + return divisions + + def get_division_id(self, division_name: str, + search_key: str = 'name', **params) -> List[Division]: + """ + return divsion id + + Parameters + ---------- + division_name : str + Division name + search_key : str + search key name + + Returns + ------- + list of ints + returns a matching list of division ids + + See Also + -------- + Mlb.get_division : return a Division from id + Mlb.get_divisions : return a list of Divisions + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_division_id('American League West') + [200] + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint='divisions', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + division_ids = [] + + if 'divisions' in mlb_data.data and mlb_data.data['divisions']: + for division in mlb_data.data['divisions']: + try: + if division[search_key].lower() == division_name.lower(): + division_ids.append(division['id']) + except KeyError: + continue + return division_ids + + def get_season(self, season_id: str, sport_id: int = 1, **params) -> Season: + """ + return a season object for seasonid and sportid + + Parameters + ---------- + sport_id : int + Insert a sportId to return a directory of seasons for a specific sport. + season_id : str + Insert year to return season information for a particular season. + + Other Parameters + ---------------- + withGameTypeDates : bool, optional + Insert a withGameTypeDates to return season information for all gameTypes. + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + Returns + ------- + Season + returns a season object + + See Also + -------- + Mlb.get_all_seasons : return a list of seasons + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_season(season_id="2021", sport_id=1) + Season + """ + if sport_id is not None: + params['sportId'] = sport_id + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'seasons/{season_id}', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return None + + if 'seasons' in mlb_data.data and mlb_data.data['seasons']: + for season in mlb_data.data['seasons']: + return Season(**season) + + def get_seasons(self, sport_id: int = 1, **params) -> List[Season]: + """ + return a season object for sportid + + Parameters + ---------- + sport_id : int + Insert a sportId to return a directory of seasons for a specific + sport. + + Other Parameters + ---------------- + divisionId : int, optional + Insert divisionId to return a directory of seasons for a specific + division. + leagueId : int, optional + Insert leagueId to return a directory of seasons in a specific + league. + withGameTypeDates : bool, optional + Insert a withGameTypeDates to return season information for all + gameTypes. + fields : str + Comma delimited list of specific fields to be returned. + Format: topLevelNode, childNode, attribute + + Returns + ------- + Season + returns a season object + + See Also + -------- + Mlb.get_current_season : return a current Season + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_all_seasons(1) + [Season, Season, Season, Season] + >>> mlb = Mlb() + >>> mlb.get_all_seasons(leagueId=104) + [Season, Season, Season, Season] + >>> mlb = Mlb() + >>> mlb.get_all_seasons(leagueId=103, withGameTypeDates=True) + [Season, Season, Season, Season] + """ + if sport_id is not None: + params['sportId'] = sport_id + + mlb_data = self._mlb_adapter_v1.get(endpoint='seasons/all', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + season_list = [] + + if 'seasons' in mlb_data.data and mlb_data.data['seasons']: + for season in mlb_data.data['seasons']: + season_list.append(Season(**season)) + + return season_list + + def get_standings(self, league_id: int, season: str, **params): + """ + return a list of standings for league_id and season + + Parameters + ---------- + league_id : str + Insert leagueId to return all standings based on a particular + standingType for a specific league. + season : str + Insert year to return all standings based on a particular year. + + Other Parameters + ---------------- + standingsTypes : str + Insert standingType to return all standings based on a particular + year. + Description of all standingTypes: + regularSeason - Regular Season Standings + wildCard - Wild card standings + divisionLeaders - Division Leader standings + wildCardWithLeaders - Wild card standings with Division + Leaders firstHalf - First half standings. Only valid for + leagues with a split season + (Mexican League). + secondHalf - Second half standings. Only valid for leagues + with a split season (Mexican League). + springTraining - Spring Training Standings + postseason - Postseason Standings + byDivision - Standings by Division + byConference - Standings by Conference + byLeague - Standings by League + Find standingTypes at https://statsapi.mlb.com/api/v1/standingsTypes + date : str + Insert date to return standing information for on a particular + date. Format: MM/DD/YYYY + hydrate : str + Insert Hydration(s) to return data for any available standings + hydration. Format "team,league" + Available Hydrations: + team + league + division + sport + conference + record(conference) + record(division) + fields : str + Comma delimited list of specific fields to be returned. Format: topLevelNode, childNode, attribute + + Returns + ------- + list of Standings + returns a list of Standings + See Also + -------- + + Examples + -------- + """ + if league_id is not None: + params['leagueId'] = league_id + + if season is not None: + params['season'] = season + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'standings', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + standings_list = [] + + if 'records' in mlb_data.data and mlb_data.data['records']: + for standing in mlb_data.data['records']: + standings_list.append(Standings(**standing)) + + return standings_list + + + def get_attendance(self, team_id: int = None, league_id: int = None, + league_list_id: str = None, + **params) -> Union[Attendance, None]: + """ + returns attendance data based on teamId, leagueId, or leagueListId. + + Required Parameters (at least one) + ---------- + team_id : int + Insert a teamId to return directory of attendnace for a given team + league_id : int + Insert leagueId(s) to return a directory of attendanace for a + specific league. Format '103,104' + league_list_id : str + Insert a unique League List Identifier to return a directory of + attendanace for a specific league listId. + Available values : milb_full, milb_short, milb_complex, milb_all, + milb_all_nomex, milb_all_domestic, milb_noncomp, + milb_noncomp_nomex, milb_domcomp, milb_intcomp, win_noabl, + win_caribbean, win_all, abl, mlb, mlb_hist, mlb_milb, + mlb_milb_hist, mlb_milb_win, baseball_all + + Parameters + ---------- + season : int + Insert year(s) to return a directory of attendance for a given + season. Season year number format yyyy + date : str 'yyyy-mm-dd' + Insert date to return information for attendance on a particular + date. Format: MM/DD/YYYY + gametype : str + Insert gameType(s) a directory of attendance for a given gameType. + For a list of all gameTypes: + https://statsapi.mlb.com/api/v1/gameTypes + + Returns + ------- + Attendance + + See Also + -------- + Mlb.get_leagues : return a list of Leagues + Mlb.get_venues : return a list of Venues + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_attendance(team_id=133, {'season': 2022}) + Attendance + """ + required_args = {'teamId': team_id, 'leagueId': league_id, 'leagueListId': league_list_id} + + if not any(required_args): + return + + # let's create a list of the args passed + # this will filter out None + for arg_name, arg_value in required_args.items(): + if arg_value: + params[arg_name] = arg_value + + mlb_data = self._mlb_adapter_v1.get('attendance', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return None + + if 'records' in mlb_data.data and mlb_data.data['records']: + return Attendance(**mlb_data.data) + + def get_draft(self, year_id: int, **params) -> List[Round]: + """ + return a draft object for year_id + + Parameters + ---------- + year_id : int + Insert a year_id to return a directory of seasons for a specific sport. + + Other Parameters + ---------------- + round : str + Insert a round to return biographical and financial data for a specific round in a Rule 4 draft. + name : str + Insert the first letter of a draftees last name to return their Rule 4 biographical and financial data. + school : str + Insert the first letter of a draftees school to return their Rule 4 biographical and financial data. + state : str + Insert state to return a list of Rule 4 draftees from that given state + country : str + Insert state to return a list of Rule 4 draftees from that given state + position : str + Insert the position to return Rule 4 biographical and financial data for a players drafted at that position. + teamId : int + Insert teamId to return Rule 4 biographical and financial data for all picks made by a specific team. + playerId : int + Insert MLB playerId to return a player's Rule 4 biographical and financial data a specific Rule 4 draft. + bisPlayerId : int + Insert bisPlayerId to return a player's Rule 4 biographical and financial data a specific Rule 4 draft. + + Returns + ------- + list of DraftPicks + returns a list of DraftPicks + + See Also + -------- + + Examples + -------- + """ + mlb_data = self._mlb_adapter_v1.get(endpoint=f'draft/{year_id}', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + round_list = [] + + if 'drafts' in mlb_data.data and mlb_data.data['drafts']: + if mlb_data.data['drafts']['rounds']: + for round in mlb_data.data['drafts']['rounds']: + round_list.append(Round(**round)) + return round_list + + def get_awards(self, award_id: str, **params) -> List[Award]: + """ + return a list of awards for award_id + + Parameters + ---------- + award_id : str + Insert a awardId to return a directory of players for a given award. + + Other Parameters + ---------------- + sportId : int + Insert a sportId to return a directory of players for a given award in a specific sport. + leagueId : int, List[int] + Insert leagueId(s) to return a directory of players for a given award in a specific league. Format '103,104' + season : int, List[int] + Insert year(s) to return a directory of players for a given award in a given season. Format '2016,2017' + + Returns + ------- + list of Awards + returns a list of awards + See Also + -------- + + Examples + -------- + """ + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'awards/{award_id}/recipients?', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return [] + + awards_list = [] + + if 'awards' in mlb_data.data and mlb_data.data['awards']: + for award in mlb_data.data['awards']: + awards_list.append(Award(**award)) + + return awards_list + + def get_homerun_derby(self, game_id, **params) -> Union[Homerunderby, None]: + """ + The homerun derby endpoint on the Stats API allows for users to + request information from the MLB database pertaining to the + homerun derby. This is endpoint contains Statcast trajectory, + launchSpeed, launchAngle, & hit coordinates data. Also a timeRemaning + string is added to track the progress of the derby in real time. + + Parameters + ---------- + game_id : int + Insert gamePk to return HomerunDerby data for a specific gamepk. + + Other Parameters + ---------------- + fields : str + Format: Comma delimited list of specific fields to be returned. Format: topLevelNode, childNode, attribute + + Returns + ------- + Homerunderby object + + See Also + -------- + + Examples + -------- + """ + mlb_data = self._mlb_adapter_v1.get(endpoint=f'homeRunDerby/{game_id}', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + None + + if 'status' in mlb_data.data and mlb_data.data['status']: + return Homerunderby(**mlb_data.data) + + + def get_team_stats(self, team_id: int, stats: list, groups: list, **params) -> dict: + """ + returns a split stat data for a team + + Parameters + ---------- + team_id : int + the team id + stats : list + list of stat types. List of statTypes can be found at https://statsapi.mlb.com/api/v1/statTypes + groups : list + list of stat grous. List of statGroups can be found at https://statsapi.mlb.com/api/v1/statGroups + + Other Parameters + ---------------- + season : str + Insert year to return team stats for a particular season, season=2018 + + Returns + ------- + dict + returns a dict of stats + + See Also + -------- + Mlb.get_player_stats : Get stats for a player + Mlb.get_stats : Get stats + Mlb.get_player_stats : Get player stats + + + Examples + -------- + >>> mlb = Mlb() + >>> stats = ['season', 'seasonAdvanced'] + >>> groups = ['pitching'] + >>> mlb.get_team_stats(133, stats, groups) + {'pitching': {'season': [PitchingSeason], 'seasonadvanced': [PitchingSeasonAdvanced] }} + """ + params['stats'] = stats + params['group'] = groups + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'teams/{team_id}/stats', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return {} + + if 'stats' in mlb_data.data and mlb_data.data['stats']: + splits = mlb_module.create_split_data(mlb_data.data['stats']) + else: + return {} + + return splits + + def get_players_stats_for_game(self, person_id: int, game_id: int, **params) -> dict: + """ + Insert personId and gamePk to view stats for individual player based on a specific game. + + Fielding, Hitting, & Pitching gameLog Statistics as well as vsPlayer stats. + + Parameters + ---------- + person_id : int + the team id + game_id : list + list of stat types + + Returns + ------- + dict + returns a dict of stats + + See Also + -------- + Mlb.get_team_stats : Get team stats + Mlb.get_player_stats : Get stats for a player + Mlb.get_stats : Get stats + + Examples + -------- + >>> mlb = Mlb() + >>> player_id = 663728 + >>> game_id = 715757 + >>> stats = mlb.get_player_stats_for_game(person_id=person_id, game_id=game_id) + >>> print(stats['stats']['gamelog']) + >>> print(stats['hitting']['playlog']) + """ + mlb_data = self._mlb_adapter_v1.get(endpoint=f'people/{person_id}/stats/game/{game_id}') + if 400 <= mlb_data.status_code <= 499: + return {} + + if 'stats' in mlb_data.data and mlb_data.data['stats']: + splits = mlb_module.create_split_data(mlb_data.data['stats']) + else: + return {} + + return splits + + def get_player_stats(self, person_id: int, stats: list, groups: list, **params) -> dict: + """ + returns stat data for a team + + Parameters + ---------- + person_id : int + the person id + stats : list + list of stat types. List of statTypes can be found at https://statsapi.mlb.com/api/v1/statTypes + groups : list + list of stat grous. List of statGroups can be found at https://statsapi.mlb.com/api/v1/statGroups + + Other Parameters + ---------------- + season : str + Insert year to return team stats for a particular season, season=2018 + eventType : str + Notes for individual events for playLog, playlog can be filered by individual events. + List of eventTypes can be found at https://statsapi.mlb.com/api/v1/eventTypes + + Returns + ------- + dict + returns a dict of stats + + See Also + -------- + Mlb.get_stats : Get stats + Mlb.get_team_stats : Get team stats + Mlb.get_players_stats_for_game : Get player stats for a game + + Examples + -------- + >>> mlb = Mlb() + >>> stats = ['season', 'seasonAdvanced'] + >>> groups = ['hitting'] + >>> mlb.get_player_stats(647351, stats, groups) + {'hitting': {'season': [HittingSeason], 'seasonadvanced': [HittingSeasonAdvanced] }} + """ + params['stats'] = stats + params['group'] = groups + + mlb_data = self._mlb_adapter_v1.get(endpoint=f'people/{person_id}/stats', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return {} + + if 'stats' in mlb_data.data and mlb_data.data['stats']: + splits = mlb_module.create_split_data(mlb_data.data['stats']) + else: + return {} + + return splits + + def get_stats(self, stats: list, groups: list, **params: dict) -> dict: + """ + return a stat dictionary + + Parameters + ---------- + params : dict + dict of params to pass + stats : list + list of stat types. List of statTypes can be found at https://statsapi.mlb.com/api/v1/statTypes + groups : list + list of stat grous. List of statGroups can be found at https://statsapi.mlb.com/api/v1/statGroups + + Other Parameters + ---------------- + season : str + Insert year to return team stats for a particular season, season=2018 + teamId : int + Insert teamId to return statistics for a given team. Default to "Qualified" playerPool. + For a list of all teamIds : Mlb.get_leagues() + leagueId : int + Insert leagueId to return statistics for a given league. Default to "Qualified" playerPool + For a list of all leagueIds : Mlb.get_leagues() + gameType : str + Insert gameType to return statistics for a given sport or league based on gameType. Default to "Qualified" playerPool + Find available gameType at https://statsapi.mlb.com/api/v1/gameTypes + sportIds : int + Insert sportId to return statistics for a given sport. + For a list of all sportIds : Mlb.get_sports() + + Returns + ------- + splits : dict + + See Also + -------- + Mlb.get_team_stats : Get team stats + Mlb.get_player_stats : Get player stats + Mlb.get_players_stats_for_game : Get player stats for a game + + Examples + -------- + >>> mlb = Mlb() + >>> mlb.get_stats() + """ + params['stats'] = stats + params['group'] = groups + + mlb_data = self._mlb_adapter_v1.get(endpoint='stats', ep_params=params) + if 400 <= mlb_data.status_code <= 499: + return {} + + if 'stats' in mlb_data.data and mlb_data.data['stats']: + splits = mlb_module.create_split_data(mlb_data) + else: + return {} + + return splits + + + diff --git a/mlbstatsapi/mlb_dataadapter.py b/mlbstatsapi/mlb_dataadapter.py new file mode 100644 index 00000000..cee9345b --- /dev/null +++ b/mlbstatsapi/mlb_dataadapter.py @@ -0,0 +1,141 @@ +from typing import Dict, List +from .exceptions import TheMlbStatsApiException +import requests +import logging + + +class MlbResult: + """ + A class that holds data, status_code, and message returned from statsapi.mlb.com + + Attributes + ---------- + status_code : int + HTTP Return Code + message : str + Message returned from REST Endpoint + data : dict + JSON Data received from request + """ + + def __init__(self, status_code: int, message: str, data: Dict = {}): + self.status_code = int(status_code) + self.message = str(message) + + self.data = data + if 'copyright' in data: + del data['copyright'] + + +class MlbDataAdapter: + """ + Adapter for calling the mlb statsapi endpoint + + Attributes + ---------- + hostname : str + rest endpoint for data + ver : str + api version + logger : logging.Logger + instance of logger class + """ + + def __init__(self, hostname: str = 'statsapi.mlb.com', ver: str = 'v1', logger: logging.Logger = None): + self.url = f'https://{hostname}/api/{ver}/' + self._logger = logger or logging.getLogger(__name__) + self._logger.setLevel(logging.DEBUG) + + def _transform_keys_in_data(self, data) -> dict: + """ + Recursivly transform all the keys in a dictionary to lowercase + + Parameters + ---------- + data : dict + MlbResult data dictionary + + Returns + ------- + dict + """ + + if isinstance(data, Dict): + lowered_dict = {} + + for key, value in data.items(): + lowered_dict[key.lower()] = self._transform_keys_in_data(value) + + return lowered_dict + + elif isinstance(data, List): + lowered_list = [] + + for item in data: + lowered_list.append(self._transform_keys_in_data(item)) + + return lowered_list + + else: + return data + + def get(self, endpoint: str, ep_params: Dict = None, data: Dict = None) -> MlbResult: + """ + return a MlbResult from endpoint + + Parameters + ---------- + endpoint : str + rest api endpoint + ep_params : dict + params + data : dict + data to send with requests (we aren't using this) + + Returns + ------- + MlbResult + """ + + full_url = self.url + endpoint + logline_pre = f'url={full_url}' + logline_post = " ,".join((logline_pre, 'success={}, status_code={}, message={}, url={}')) + + try: + self._logger.debug(logline_post) + response = requests.get(url=full_url, params=ep_params) + + except requests.exceptions.RequestException as e: + self._logger.error(msg=(str(e))) + raise TheMlbStatsApiException('Request failed') from e + + try: + data = response.json() + + except (ValueError, requests.JSONDecodeError) as e: + self._logger.error(msg=(str(e))) + raise TheMlbStatsApiException('Bad JSON in response') from e + + if response.status_code <= 200 and response.status_code <= 299: + self._logger.debug(msg=logline_post.format('success', + response.status_code, response.reason, response.url)) + + data = self._transform_keys_in_data(data) + return MlbResult(response.status_code, message=response.reason, data=data) + + elif response.status_code >= 400 and response.status_code <= 499: + self._logger.error(msg=logline_post.format('Invalid Request', + response.status_code, response.reason, response.url)) + + # return MlbResult with 404 and empty data + return MlbResult(response.status_code, message=response.reason, data={}) + + elif response.status_code >= 500 and response.status_code <= 599: + + self._logger.error(msg=logline_post.format('Internal error occurred', + response.status_code, response.reason, response.url)) + + raise TheMlbStatsApiException(f"{response.status_code}: {response.reason}") + + else: + raise TheMlbStatsApiException(f"{response.status_code}: {response.reason}") diff --git a/mlbstatsapi/mlb_module.py b/mlbstatsapi/mlb_module.py new file mode 100644 index 00000000..72f6877a --- /dev/null +++ b/mlbstatsapi/mlb_module.py @@ -0,0 +1,162 @@ +from typing import Union, List, Dict +import importlib +import inspect + +from mlbstatsapi.models.stats import Stat + + +def merge_keys(mlb_dict, mlb_keys: Union[List[Union[dict, str]], str]) -> dict: + """ + A recursive function that merges requested nested dicts inside mlb_dict into mlb_dict base. + + Parameters + ---------- + mlb_dict : dict + mlb_dict is a dictionary that requires transformation + + mlb_keys : str, list[str], list[dict[str, dict]] + key names you want to nest + + Returns + ------- + dict + returns a dict that has been transformed + """ + + if isinstance(mlb_keys, List): + for key in mlb_keys: + + if isinstance(key, Dict): + for nested_key in key: + mlb_merge_item = mlb_dict.pop(nested_key) + mlb_dict.update(merge_keys(mlb_merge_item, key[nested_key])) + else: + mlb_merge_item = mlb_dict.pop(key) + mlb_dict.update(**mlb_merge_item) + else: + mlb_merge_item = mlb_dict.pop(mlb_keys) + mlb_dict.update(**mlb_merge_item) + + return mlb_dict + +def return_splits(split_data: dict, stat_type: str, stat_group: str) -> List['Split']: + """ + The split objects are built using the group name and split data. The stat group name is used to source the correct + stat group classes. + + stat group: hitting will load the classes in hitting.py and use the _type parameter to locate the correct class. + + Parameters + ---------- + split_data : dict + split data + stat_type : str + type of stat + stat_group : str + group of stat + + Returns + ------- + list + returns a list of stat objects + """ + + splits = [] + + stat_module = f"mlbstatsapi.models.stats.{stat_group}" + stat_module = importlib.import_module(stat_module) + + for name, obj in inspect.getmembers(stat_module, predicate=inspect.isclass): + if hasattr(obj, '_stat') and stat_type in obj._stat: + for split in split_data: + if 'stat' in split and split['stat']: + splits.append(obj(**split)) + + return splits + +def get_split_count(stat: dict) -> int: + """ + function that returns split count from stats + + Parameters + ---------- + stat: dict + dict of stats + + Returns + ------- + int + returns number of splits + """ + + + +def create_split_data(stat_data: dict) -> dict: + """ + function that loops through stat information, creates splits, and return stat dict + + Parameters + ---------- + stat_data: dict + dict of params to pass + + Returns + ------- + dict + returns a dict of stats + """ + stats = {} + + for stat in stat_data: + # get type and group of stat + stat_type, stat_group, total_splits = get_stat_attributes(stat) + + if 'splits' in stat and stat['splits']: + split_data = return_splits(stat['splits'], stat_type, stat_group) + stat_object = Stat(group=stat_group, type=stat_type, + totalsplits=total_splits, splits=split_data) + else: + continue + + if stat_group not in stats: + stats[stat_group] = {} + + stats[stat_group][stat_type.lower()] = stat_object + + return stats + +def get_stat_attributes(stats) -> str: + """ + return stat type + Parameters + ---------- + stats : dict + stat data + + Returns + ------- + (stat_type, stat_group) + """ + if 'type' in stats and 'displayname' in stats['type']: + stat_type = stats['type']['displayname'] + else: + stat_type = 'gameLog' + + # default to stats if no group returned + if 'group' in stats and 'displayname' in stats['group']: + stat_group = stats['group']['displayname'] + else: + # if stat_type is None return None + if stat_type: + stat_group = 'stats' + else: + stat_group = None + + if 'totalsplits' in stats: + total_splits = stats['totalsplits'] + else: + total_splits = len(stats['splits']) + + return (stat_type, stat_group, total_splits) + + diff --git a/mlbstatsapi/models/__init__.py b/mlbstatsapi/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mlbstatsapi/models/attendances/__init__.py b/mlbstatsapi/models/attendances/__init__.py new file mode 100644 index 00000000..b32ffa39 --- /dev/null +++ b/mlbstatsapi/models/attendances/__init__.py @@ -0,0 +1 @@ +from .attendance import Attendance \ No newline at end of file diff --git a/mlbstatsapi/models/attendances/attendance.py b/mlbstatsapi/models/attendances/attendance.py new file mode 100644 index 00000000..5132b4c8 --- /dev/null +++ b/mlbstatsapi/models/attendances/attendance.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass, field +from typing import Union, List +from .attributes import AttendanceTotals, AttendanceRecords + +@dataclass +class Attendance: + """ + A class to represent attendance. + Attributes + ---------- + copyright : str + Copyright message + records : List[AttendanceRecords] + List of attendance records + aggregatetotals : AttendanceAggregateTotals + Attendence aggregate total numbers for query + """ + aggregatetotals: Union[AttendanceTotals, dict] + records: Union[List[AttendanceRecords], List[dict]] = field(default_factory=list) + + def __post_init__(self): + self.records = [AttendanceRecords(**record) for record in self.records if self.records] + self.aggregatetotals = AttendanceTotals(**self.aggregatetotals) \ No newline at end of file diff --git a/mlbstatsapi/models/attendances/attributes.py b/mlbstatsapi/models/attendances/attributes.py new file mode 100644 index 00000000..27c82988 --- /dev/null +++ b/mlbstatsapi/models/attendances/attributes.py @@ -0,0 +1,190 @@ +from dataclasses import dataclass, field +from typing import Optional, Union +from mlbstatsapi.models.teams import Team + + +@dataclass +class AttendanceHighLowGameContent: + """ + A class to represent attendance records. + Attributes + ---------- + link : str + games content endpoint link + """ + link: str + + +@dataclass +class AttendanceHighLowGame: + """ + A class to represent attendance High and Low games. + Attributes + ---------- + gamepk : int + Games Id number + link : str + games endpoint link + content : AttendanceHighLowGameContent + Content for this game + daynight : str + Type of time of day for game + """ + gamepk: int + link: str + content: Union[AttendanceHighLowGameContent, dict] + daynight: str + + def __post_init__(self): + self.content = AttendanceHighLowGameContent(**self.content) + + +@dataclass +class AttendenceGameType: + """ + A class to represent Attendance Game Type. + Attributes + ---------- + id : str + Game type id + description : str + Game type description + """ + id: str + description: str + + +@dataclass(repr=False) +class AttendanceRecords: + """ + A class to represent attendance records. + Attributes + ---------- + openingstotal : int + Total amount of openings + openingstotalaway : int + Total amount of opening away games + openingstotalhome : int + Total amount of opening home games + openingstotallost : int + Total amount of openings lost + gamestotal : int + Total amount of games + gamesawaytotal : int + Total amount of away games + gameshometotal : int + Total amount of home games + year : str + Year as a string + attendanceaverageaway : int + Average attendance for away games + attendanceaveragehome : int + Average attendance for home games + attendanceaverageytd : int + Average attendance year to date + attendancehigh : int + Attendance High number + attendancehighdate : str + Attendance high date + attendancehighgame : AttendanceHighLowGame + Attendance high game + attendancelow : int + Attendance low number + attendancelowdate : str + Attendance low date + attendancelowgame : AttendanceHighLowGame + Attendance low game + attendanceopeningaverage : int + Attendance opening average + attendancetotal : int + Attendance total + attendancetotalaway : int + Attendance total away + attendancetotalhome : int + Attendance total home + gametype : AttendenceGameType + Game type + team : Team + Team + """ + openingstotal: int + openingstotalaway: int + openingstotalhome: int + openingstotallost: int + gamestotal: int + gamesawaytotal: int + gameshometotal: int + year: str + attendanceaverageytd: int + attendancetotal: int + gametype: Union[AttendenceGameType, dict] + team: Union[Team, dict] + attendanceaverageaway: Optional[int] = None + attendanceaveragehome: Optional[int] = None + attendancehigh: Optional[int] = None + attendancehighdate: Optional[str] = None + attendancehighgame: Optional[Union[AttendanceHighLowGame, dict]] = None + attendancelow: Optional[int] = None + attendancelowdate: Optional[str] = None + attendancelowgame: Optional[Union[AttendanceHighLowGame, dict]] = None + attendancetotalaway: Optional[int] = None + attendancetotalhome: Optional[int] = None + attendanceopeningaverage: Optional[int] = None + + def __post_init__(self): + self.attendancehighgame = AttendanceHighLowGame(**self.attendancehighgame) if self.attendancehighgame else self.attendancehighgame + self.attendancelowgame = AttendanceHighLowGame(**self.attendancelowgame) if self.attendancelowgame else self.attendancelowgame + self.gameType = AttendenceGameType(**self.gametype) + self.team = Team(**self.team) + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(repr=False) +class AttendanceTotals: + """ + A class to represent attendance aggregate toatls. + Attributes + ---------- + openingstotalaway : int + Total amount of opening game attendance number + openingstotalhome : int + Total amount of opening home game attendance number + openingstotallost : int + Total amount of opening games lost + openingstotalytd : int + Total amount of opening games year to date + attendanceaverageaway : int + Average away game attendance + attendanceaveragehome : int + Average home game attendance + attendanceaverageytd : int + Average attendance year to date + attendancehigh : int + Attendance high + attendancehighdate : str + Attendance high date + attendancetotal : int + Attendance total + attendancetotalaway : int + Attendace total away + attendancetotalhome : int + Attendance total home + """ + openingstotalaway: int + openingstotalhome: int + openingstotallost: int + openingstotalytd: int + attendanceaverageytd: int + attendancehigh: int + attendancehighdate: str + attendancetotal: int + attendancetotalaway: int + attendancetotalhome: int + attendanceaverageaway: Optional[int] = None + attendanceaveragehome: Optional[int] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/awards/__init__.py b/mlbstatsapi/models/awards/__init__.py new file mode 100644 index 00000000..1a8c5305 --- /dev/null +++ b/mlbstatsapi/models/awards/__init__.py @@ -0,0 +1,2 @@ +from .awards import Awards +from .attributes import Award \ No newline at end of file diff --git a/mlbstatsapi/models/awards/attributes.py b/mlbstatsapi/models/awards/attributes.py new file mode 100644 index 00000000..43096459 --- /dev/null +++ b/mlbstatsapi/models/awards/attributes.py @@ -0,0 +1,47 @@ +from typing import Union, Optional +from dataclasses import dataclass + +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.people import Person + +@dataclass(kw_only=True, repr=False) +class Award: + """ + This class represents an award object + + Attributes + ---------- + id : str + Award id + name : str + Name of the award + date : str + Date of when award was given + season : str + Season award is for/from + team : Team + Team award was to/ Player is from + player : Person + Person award is for + votes : int None + Any votes associated with award + notes : str None + Any notes associated with award + """ + + id: str + name: str + date: str + season: str + team: Union[Team, dict] + player: Union[Person, dict] + votes: Optional[int] = None + notes: Optional[str] = None + + def __post_init__(self): + self.team = Team(**self.team) + self.player = Person(**self.player) + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/awards/awards.py b/mlbstatsapi/models/awards/awards.py new file mode 100644 index 00000000..fb4c4b06 --- /dev/null +++ b/mlbstatsapi/models/awards/awards.py @@ -0,0 +1,17 @@ +from typing import List +from dataclasses import dataclass + +from .attributes import Award + +@dataclass +class Awards: + """ + This class represents an awards object + + Attributes + ---------- + awards : List[Award] + Awards + """ + + awards: List[Award] diff --git a/mlbstatsapi/models/data/__init__.py b/mlbstatsapi/models/data/__init__.py new file mode 100644 index 00000000..70fe4d2d --- /dev/null +++ b/mlbstatsapi/models/data/__init__.py @@ -0,0 +1,10 @@ +from .data import ( + PitchBreak, + PitchCoordinates, + PitchData, + HitCoordinates, + HitData, + CodeDesc, + Count, + PlayDetails +) \ No newline at end of file diff --git a/mlbstatsapi/models/data/data.py b/mlbstatsapi/models/data/data.py new file mode 100644 index 00000000..8b1dc5e2 --- /dev/null +++ b/mlbstatsapi/models/data/data.py @@ -0,0 +1,340 @@ +from dataclasses import dataclass, field, InitVar +from typing import List, Union, Dict, Any, Optional + + + + +@dataclass(repr=False) +class PitchBreak: + """ + A class to hold pitch pitch break data + + Attributes + ---------- + breakangle : float + Degrees clockwise (batter's view) that the plane of + the pitch deviates from the vertical + breaklength : float + Max distance that the pitch separates from the straight + line between pitch start and pitch end + breaky : int + Distance from home plate where the break is greatest + spinrate : int + Pitch spinRate + spindirection : int + Pitch spinDirection + """ + breakangle: float + breaklength: float + breaky: float + spinrate: Optional[float] = None + spindirection: Optional[float] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(repr=False) +class PitchCoordinates: + """ + A class to hold pitch coordinates for playLog + + Attributes + ---------- + ay : float, default=None + Ball acceleration on the y axis + az : float, default=None + Ball acceleration on the z axis + pfxx : float, default=None + horizontal movement of the ball in inches + pfxz : float, default=None + Vertical movement of the ball in inches + px : float, default=None + Horizontal position in feet of the ball as it + crosses the front axis of home plate + pz : float, default=None + Vertical position in feet of the ball as it + crosses the front axis of home plate + vx0 : float, default=None + Velocity of the ball from the x-axis + vy0 : float, default=None + Velocity of the ball from the y axis, this + is negative becuase 0,0,0 is behind the batter + and the ball travels from pitcher mound towards 0,0,0 + vz0 : float, default=None + Velocity of the ball from the z axis + x0 : float, default=None + Coordinate location of the ball at the point it was + reeased from pitchers hand on the x axis (time=0) + y0 : float, default=None + Coordinate location of the ball at the point it was + reeased from pitchers hand on the y axis (time=0) + z0 : float, default=None + Coordinate location of the ball at the point it was + reeased from pitchers hand on the z axis (time=0) + ax : float, default=None + Ball acceleration on the x axis + x : float, default=None + X coordinate where pitch crossed front of home plate + y : float, default=None + Y coordinate where pitch crossed front of home plate + """ + ay: Optional[float] = None + az: Optional[float] = None + pfxx: Optional[float] = None + pfxz: Optional[float] = None + px: Optional[float] = None + pz: Optional[float] = None + vx0: Optional[float] = None + vy0: Optional[float] = None + vz0: Optional[float] = None + x0: Optional[float] = None + y0: Optional[float] = None + z0: Optional[float] = None + ax: Optional[float] = None + x: Optional[float] = None + y: Optional[float] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(repr=False) +class PitchData: + """ + A class to hold data on a pitch + + Attributes + ---------- + startspeed : float + The starting speed of the pitch. + endspeed : float + The ending speed of the pitch. + strikezonetop : float + The top of the strike zone. + strikezonebottom : float + The bottom of the strike zone. + coordinates : PitchCoordinates + The coordinates of the pitch. + breaks : PitchBreak + The break data of the pitch. + zone : float + The zone in which the pitch was thrown. + typeconfidence : float + The confidence in the type of pitch thrown. + platetime : float + The amount of time the pitch was in the air. + extension : float + The extension of the pitch. + strikezonewidth : float + The width of the strikezone + strikezonedepth : float + The depth of the strikezone + """ + strikezonetop: float + strikezonebottom: float + breaks: Union[PitchBreak, dict] + coordinates: Optional[Union[PitchCoordinates, dict]] = field(default_factory=dict) + extension: Optional[float] = None + startspeed: Optional[float] = None + endspeed: Optional[float] = None + zone: Optional[float] = None + typeconfidence: Optional[float] = None + platetime: Optional[float] = None + strikezonewidth: Optional[float] = None + strikezonedepth: Optional[float] = None + + + def __post_init__(self): + self.coordinates = PitchCoordinates(**self.coordinates) if self.coordinates else self.coordinates + self.breaks = PitchBreak(**self.breaks) if self.breaks else self.breaks + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass +class HitCoordinates: + """ + A class to represent a play events hit location coordinates. + + Attributes + ---------- + coordx : int + X coordinate for hit + coordy : int + Y coordinate for hit + """ + coordx: Optional[float] = None + coordy: Optional[float] = None + + @property + def x(self): + return self.coordx + + @property + def y(self): + return self.coordy + +@dataclass(repr=False) +class HitData: + """ + A class to represent a play events hit data. + + Attributes + ---------- + launchspeed : float + Hit launch speed + launchangle : int + Hit launch angle + totaldistance : int + Hits total distance + trajectory : str + Hit trajectory + hardness : str + Hit hardness + location : str + Hit location + coordinates : HitCoordinate + Hit coordinates + """ + + + trajectory: str + hardness: str + coordinates: Union[HitCoordinates, dict] + location: Optional[int] = None + launchspeed: Optional[float] = None + launchangle: Optional[str] = None # this is a negative number and I'm brain farting on those + totaldistance: Optional[float] = None + + def __post_init__(self): + self.coordinates = HitCoordinates(**self.coordinates) if self.coordinates else self.coordinates + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass +class CodeDesc: + """ + a class to hold a code and a description + + Attributes + ---------- + code : str + the code to reference the attribute using this + description : str + the description of the attribute using this + """ + code: str + description: Optional[str] = None + + +@dataclass(repr=False) +class Count: + """ + a class to hold a pitch count and base runners + + Attributes + ---------- + code : str + code + balls : int + number of balls + inning : int + inning number + istopinning : bool + bool to hold status of top inning + outs : int + number of outs + runneron1b : bool + bool to hold 1b runner status + runneron2b : bool + bool to hold 2b runner status + runneron3b : bool + bool to hold 3b runner status + strikes : int + strike count + """ + balls: int + outs: int + strikes: int + inning: Optional[int] = None + runneron1b: Optional[bool] = None + runneron2b: Optional[bool] = None + runneron3b: Optional[bool] = None + istopinning: Optional[bool] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(repr=False) +class PlayDetails: + """ + A class to represent a gamelog stat for a hitter + + Attributes + ---------- + call : CodeDesc + play call code and description + description : str + description of the play + event : str + type of event + eventtype : str + type of event + isinplay : bool + is the ball in play true or false + isstrike : bool + is the ball a strike true or false + isball : bool + is it a ball true or false + isbasehit : bool + is the event a base hit true or false + isatbat : bool + is the event at bat true or false + isplateappearance : bool + is the event a at play appears true or false + type : CodeDesc + type of pitch code and description + batside : CodeDesc + bat side code and description + pitchhand : CodeDesc + pitch hand code and description + fromcatcher : bool + """ + call: Optional[Union[CodeDesc, dict]] = None + isinplay: Optional[bool] = None + isstrike: Optional[bool] = None + isscoringplay: Optional[bool] = None + isout: Optional[bool] = None + runnergoing: Optional[bool] = None + isball: Optional[bool] = None + isbasehit: Optional[bool] = None + isatbat: Optional[bool] = None + isplateappearance: Optional[bool] = None + batside: Optional[Union[CodeDesc, dict]] = field(default_factory=dict) + pitchhand: Optional[Union[CodeDesc, dict]] = field(default_factory=dict) + eventtype: Optional[str] = None + event: Optional[str] = None + description: Optional[str] = None + type: Optional[Union[CodeDesc, dict]] = field(default_factory=dict) + awayscore: Optional[int] = None + homescore: Optional[int] = None + hasreview: Optional[bool] = None + code: Optional[str] = None + ballcolor: Optional[str] = None + trailcolor: Optional[str] = None + fromcatcher: Optional[bool] = None + + def __post_init__(self): + self.call = CodeDesc(**self.call) if self.call else self.call + self.batside = CodeDesc(**self.batside) if self.batside else self.batside + self.pitchhand = CodeDesc(**self.pitchhand) if self.pitchhand else self.pitchhand + self.type = CodeDesc(**self.type) if self.type else self.type + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/divisions/__init__.py b/mlbstatsapi/models/divisions/__init__.py new file mode 100644 index 00000000..2e92696a --- /dev/null +++ b/mlbstatsapi/models/divisions/__init__.py @@ -0,0 +1 @@ +from .division import Division \ No newline at end of file diff --git a/mlbstatsapi/models/divisions/division.py b/mlbstatsapi/models/divisions/division.py new file mode 100644 index 00000000..3fb4b052 --- /dev/null +++ b/mlbstatsapi/models/divisions/division.py @@ -0,0 +1,59 @@ +from dataclasses import dataclass +from typing import Optional, Union + +from mlbstatsapi.models.leagues import League +from mlbstatsapi.models.sports import Sport + + +@dataclass(repr=False) +class Division: + """ + A class to represent a division. + + Attributes + ---------- + id : int + id number of the divison + name : str + name of the division + link : str + link of the division + season : str + Current season for the division + nameshort : str + Short name for the division + abbreviation : str + Abbreviation of the divison name + league : League + League this division is in + sport : Sport + Sport this divison is in + haswildcard : bool + If this league has a wildcard + sortorder : int + Sort order + numplayoffteams : int + Number of playoff teams in division + active : bool + Current status of this division + """ + id: int + link: str + name: Optional[str] = None + season: Optional[str] = None + nameshort: Optional[str] = None + abbreviation: Optional[str] = None + league: Optional[Union[League, dict]] = None + sport: Optional[Union[Sport, dict]] = None + haswildcard: Optional[bool] = None + sortorder: Optional[int] = None + numplayoffteams: Optional[int] = None + active: Optional[bool] = None + + def __post_init__(self): + self.league = League(**self.league) if self.league else self.league + self.sport = Sport(**self.sport) if self.sport else self.sport + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/drafts/__init__.py b/mlbstatsapi/models/drafts/__init__.py new file mode 100644 index 00000000..c7f7e6af --- /dev/null +++ b/mlbstatsapi/models/drafts/__init__.py @@ -0,0 +1 @@ +from .rounds import Round \ No newline at end of file diff --git a/mlbstatsapi/models/drafts/attributes.py b/mlbstatsapi/models/drafts/attributes.py new file mode 100644 index 00000000..1e926a9e --- /dev/null +++ b/mlbstatsapi/models/drafts/attributes.py @@ -0,0 +1,44 @@ +from dataclasses import dataclass, field + + +@dataclass +class Home: + """ + A home is a where a draft player is from + + Attributes + ---------- + city : str + The city where the player is from. + state : str + The state where the player is from. + country : str + The country where the player is from. + """ + city: str + state: str + country: str + +@dataclass +class School: + """ + Represents the school the draft player is from. + + Attributes + ---------- + name : str + The name of the school. + schoolclass : str + The class the student is in. + city : str + The city where the school is located. + country : str + The country where the school is located. + state : str + The state where the school is located. + """ + name: str + schoolclass: str + city: str + country: str + state: str \ No newline at end of file diff --git a/mlbstatsapi/models/drafts/rounds.py b/mlbstatsapi/models/drafts/rounds.py new file mode 100644 index 00000000..ce88ceb7 --- /dev/null +++ b/mlbstatsapi/models/drafts/rounds.py @@ -0,0 +1,94 @@ +from dataclasses import dataclass, field +from typing import List, Optional, Union + +from .attributes import School, Home +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.people import Person +from mlbstatsapi.models.data import CodeDesc + +@dataclass(repr=False) +class DraftPick: + """ + Represents a pick made in the MLB draft. + + Attributes + ---------- + bisplayerid : int + The unique identifier of the player associated with this draft pick. + pickround : str + The round of the draft in which this pick was made. + picknumber : int + The number of the pick in the round. + roundpicknumber : int + The number of the pick overall in the draft. + rank : int + The rank of the player among all players eligible for the draft. + pickvalue : str + The value of the pick, if known. + signingbonus : str + The signing bonus associated with this pick, if known. + home : Home + Information about the player's home location. + scoutingreport : str + A scouting report on the player's abilities. + school : School + Information about the player's school or college. + blurb : str + A brief summary of the player's background and accomplishments. + headshotlink : str + A link to a headshot image of the player. + team : Team or dict + The team that made this draft pick. + drafttype : CodeDesc + Information about the type of draft in which this pick was made. + isdrafted : bool + Whether or not the player associated with this pick has been drafted. + ispass : bool + Whether or not the team passed on making a pick in this round. + year : str + The year in which the draft took place. + """ + team: Union[Team, dict] + drafttype: Union[CodeDesc, dict] + isdrafted: bool + ispass: bool + year: str + school: Union[School , dict] + home: Union[Home, dict] + pickround: str + picknumber: int + roundpicknumber: int + headshotlink: Optional[str] = None + person: Optional[Union[Person, dict]] = None + bisplayerid: Optional[int] = None + rank: Optional[int] = None + pickvalue: Optional[str] = None + signingbonus: Optional[str] = None + scoutingreport: Optional[str] = None + blurb: Optional[str] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(repr=False) +class Round: + """ + Represents a round of the MLB draft. + + Attributes + ---------- + round : str + The round number of the draft, represented as a string. + picks : List[DraftPick] + A list of DraftPick objects representing the picks made in this round of the draft. + """ + round: str + picks: List[DraftPick] + + def __post_init__(self): + self.picks = [DraftPick(**pick) for pick in self.picks] + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/game/__init__.py b/mlbstatsapi/models/game/__init__.py new file mode 100644 index 00000000..53426e37 --- /dev/null +++ b/mlbstatsapi/models/game/__init__.py @@ -0,0 +1,5 @@ +from .game import Game +from .attributes import MetaData +from .livedata.plays import Plays +from .livedata.linescore import Linescore +from .livedata.boxscore import BoxScore diff --git a/mlbstatsapi/models/game/attributes.py b/mlbstatsapi/models/game/attributes.py new file mode 100644 index 00000000..9977e63e --- /dev/null +++ b/mlbstatsapi/models/game/attributes.py @@ -0,0 +1,23 @@ +from typing import List +from dataclasses import dataclass + +@dataclass +class MetaData: + """ + A class to represent a Game's metaData. + + Attributes + ---------- + wait : int + No idea what this wait signifies + timestamp : str + The timeStamp + gameevents : List[str] + Current game events for this game + logicalevents : List[str] + Current logical events for this game + """ + wait: int + timestamp: str + gameevents: List[str] + logicalevents: List[str] \ No newline at end of file diff --git a/mlbstatsapi/models/game/game.py b/mlbstatsapi/models/game/game.py new file mode 100644 index 00000000..f44f6089 --- /dev/null +++ b/mlbstatsapi/models/game/game.py @@ -0,0 +1,48 @@ +from typing import Union, Optional +from dataclasses import dataclass + +from mlbstatsapi.models.game.gamedata import GameData +from mlbstatsapi.models.game.livedata import LiveData + +from .attributes import MetaData + + +@dataclass +class Game: + """ + A class to represent a Game. + + Attributes + ---------- + gamepk : int + id number of this game + link : str + link to the api address for this game + copyright : str + MLB AM copyright information + metadata : MetaData + metaData of this game + gamedata : GameData + gameData of this game + livedata : LiveData + liveData of this game + + Methods + ------- + id(): + returns this games id + """ + gamepk: int + link: str + metadata: Union[MetaData, dict] + gamedata: Union[GameData, dict] + livedata: Union[LiveData, dict] + + def __post_init__(self): + self.metadata = MetaData(**self.metadata) + self.gamedata = GameData(**self.gamedata) + self.livedata = LiveData(**self.livedata) + + @property + def id(self): + return self.gamepk diff --git a/mlbstatsapi/models/game/gamedata/__init__.py b/mlbstatsapi/models/game/gamedata/__init__.py new file mode 100644 index 00000000..bf9bbf56 --- /dev/null +++ b/mlbstatsapi/models/game/gamedata/__init__.py @@ -0,0 +1,2 @@ +from .gamedata import GameData +from .attributes import GameStatus \ No newline at end of file diff --git a/mlbstatsapi/models/game/gamedata/attributes.py b/mlbstatsapi/models/game/gamedata/attributes.py new file mode 100644 index 00000000..c7be5367 --- /dev/null +++ b/mlbstatsapi/models/game/gamedata/attributes.py @@ -0,0 +1,258 @@ +from typing import Optional, Union +from dataclasses import dataclass, field +from mlbstatsapi.models.people import Person +from mlbstatsapi.models.teams import Team + + +@dataclass +class GameDataGame: + """ + A class to represent the this game's game info. + + Attributes + ---------- + pk : int + This game's game id + type : str + This game's game type code + doubleheader : str + Represents if this game is a double header or not + id : str + An unknown Id + gamedaytype : str + This game's gameday type code + tiebreaker : str + Is this game a tie breaker + gamenumber : int + The game number for this game. If double header will be 2. + calendareventid : str + The id for this calendar event + season : str + This game's season year + seasondisplay : str + This game's displayed season + """ + pk: int + type: str + doubleheader: str + id: str + gamedaytype: str + tiebreaker: str + gamenumber: int + calendareventid: str + season: str + seasondisplay: str + + +@dataclass(repr=False) +class GameDatetime: + """ + A class to represent the date time for this game. + + Attributes + ---------- + datetime : str + Date time for this game + originaldate : str + The original scheduled date for this game + officialdate : str + The current scheduled date for this game + daynight : str + The current lighting condition game type + time : str + The time + ampm : str + The games am or pm code + """ + datetime: str + originaldate: str + officialdate: str + daynight: str + time: str + ampm: str + resumedate: Optional[str] = None + resumedatetime: Optional[str] = None + resumedfromdate: Optional[str] = None + resumedfromdatetime: Optional[str] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass +class GameStatus: + """ + A class to represent this game's game status. + + Attributes + ---------- + abstractgamestate : str + The abstract game state + codedgamestate : str + The coded game state + detailedstate : str + The detailed game state + statuscode : str + Status code for this game + starttimetbd : bool + If the start time is TBD + abstractgamecode : str + The abstract game code + reason : str + reason for a state. Usually used for delays or cancellations + """ + abstractgamestate: str + codedgamestate: str + detailedstate: str + statuscode: str + starttimetbd: bool + abstractgamecode: str + reason: Optional[str] = None + + +@dataclass +class GameTeams: + """ + A class to represent the home and away teams. + + Attributes + ---------- + away : Team + Away team + home : Team + Home team + """ + away: Union[Team, dict] + home: Union[Team, dict] + + def __post_init__(self): + self.away = Team(**self.away) + self.home = Team(**self.home) + + +@dataclass +class GameWeather: + """ + A class to represent the weather for this game. + + Attributes + ---------- + condition : str + The weather condition + temp : str + The temperature in F + wind : str + The wind in MPH and the direction + """ + condition: str + temp: str + wind: Optional[str] = None + + +@dataclass +class GameInfo: + """ + A class to represent the game info for this game. + + Attributes + ---------- + attendance : int + The attendance for this game + firstpitch : str + The time of the first pitch + gamedurationminutes : int + The duration of the game in minutes + delaydurationminutes : int + The length of delay for the game in minutes + """ + attendance: int + firstpitch: str + gamedurationminutes: int + delaydurationminutes: Optional[int] = None + + +@dataclass +class ReviewInfo: + """ + A class to represent reviewInfo for each team in this game. + + Attributes + ---------- + used : int + How many challenges used + remaining : int + How many challenges are remaining + """ + used: int + remaining: int + + +@dataclass +class GameReview: + """ + A class to represent the Game Reviews for this game. + + Attributes + ---------- + haschallenges : bool + If their are challenges + away : ReviewInfo + Away team review info + home : ReviewInfo + Home team review info + """ + haschallenges: bool + away: Union[ReviewInfo, dict] + home: Union[ReviewInfo, dict] + + def __post_init__(self): + self.away = ReviewInfo(**self.away) + self.home = ReviewInfo(**self.home) + + +@dataclass +class GameFlags: + """ + A class to represent the flags for this game. + + Attributes + ---------- + nohitter : bool + If there is a no hitter in this game + perfectgame : bool + If there this game is a perfect game + awayteamnohitter : bool + If the away team has a no hitter + awayteamperfectgame : bool + If the away team has a perfect game + hometeamnohitter : bool + If the home team has a no hitter + hometeamperfectgame : bool + If the home team has a perfect game + """ + nohitter: bool + perfectgame: bool + awayteamnohitter: bool + awayteamperfectgame: bool + hometeamnohitter: bool + hometeamperfectgame: bool + + +@dataclass +class GameProbablePitchers: + """ + A class to represent the home and away probable pitchers for this game. + + Attributes + ---------- + home : Person + Home team probable pitcher + away : Person + Away team probable pitcher + """ + away: Union[Person, dict] = field(default_factory=dict) + home: Union[Person, dict] = field(default_factory=dict) + + def __post_init__(self): + self.away = Person(**self.away) if self.away else self.away + self.home = Person(**self.home) if self.home else self.home diff --git a/mlbstatsapi/models/game/gamedata/gamedata.py b/mlbstatsapi/models/game/gamedata/gamedata.py new file mode 100644 index 00000000..096fd664 --- /dev/null +++ b/mlbstatsapi/models/game/gamedata/gamedata.py @@ -0,0 +1,92 @@ +from typing import Optional, Union, List +from dataclasses import dataclass, field +from mlbstatsapi.models.venues import Venue +from mlbstatsapi.models.people import Person + +from .attributes import GameDataGame +from .attributes import GameDatetime +from .attributes import GameStatus +from .attributes import GameTeams +from .attributes import GameWeather +from .attributes import GameInfo +from .attributes import GameReview +from .attributes import GameFlags +from .attributes import GameProbablePitchers + +@dataclass(repr=False) +class GameData: + """ + A class to represent a games game data. + + Attributes + ---------- + game : GameDataGame + game information about this game + datetime : GameDatetime + Time and dates for this game + status : GameStatus + information on this game's status + teams : GameTeams + Our two teams for this game, home and away + players : List[Person] + A list of all the players for this game + venue : Venue + Venue information for this game + officialvenue : Venue + The official venue for this game + weather : GameWeather + The weather for this game. + gameinfo : GameInfo + information on this game + review : GameReview + Game review info and team challenges + flags : GameFlags + Flag bools for this game + alerts : List[] + Alerts + probablepitchers : GameProbablePitchers + Home and away probable pitchers for this game + officialscorer : Person + The official scorer for this game + primarydatacaster : Person + The official dataCaster for this game + """ + + game: Union[GameDataGame, dict] + datetime: Union[GameDatetime, dict] + status: Union[GameStatus, dict] + teams: Union[GameTeams, dict] + players: Union[List[Person], dict] + venue: Union[Venue, dict] + officialvenue: Union[Venue, dict] + review: Union[GameReview, dict] + flags: Union[GameFlags, dict] + alerts: List + probablepitchers: Union[GameProbablePitchers, dict] + gameinfo: Union[GameInfo, dict] = field(default_factory=dict) + weather: Union[GameWeather, dict] = field(default_factory=dict) + officialscorer: Optional[Union[Person, dict]] = field(default_factory=dict) + primarydatacaster: Optional[Union[Person, dict]] = field(default_factory=dict) + secondarydatacaster: Optional[Union[Person, dict]] = field(default_factory=dict) + + + def __post_init__(self): + self.game = GameDataGame(**self.game) + self.datetime = GameDatetime(**self.datetime) + self.status = GameStatus(**self.status) + self.teams = GameTeams(**self.teams) + self.players = [Person(**(self.players[key])) for key in self.players] + self.venue = Venue(**self.venue) + self.officialvenue = Venue(**self.officialvenue) + self.weather = GameWeather(**self.weather) if self.weather else self.weather + self.gameinfo = GameInfo(**self.gameinfo) if self.gameinfo else self.gameinfo + self.review = GameReview(**self.review) + self.flags = GameFlags(**self.flags) + self.probablepitchers = GameProbablePitchers(**self.probablepitchers) + self.officialscorer = Person(**self.officialscorer) if self.officialscorer else self.officialscorer + self.primarydatacaster = Person(**self.primarydatacaster) if self.primarydatacaster else self.primarydatacaster + self.secondarydatacaster = Person(**self.secondarydatacaster) if self.secondarydatacaster else self.secondarydatacaster + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) diff --git a/mlbstatsapi/models/game/livedata/__init__.py b/mlbstatsapi/models/game/livedata/__init__.py new file mode 100644 index 00000000..b67d7512 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/__init__.py @@ -0,0 +1 @@ +from .livedata import LiveData diff --git a/mlbstatsapi/models/game/livedata/attributes.py b/mlbstatsapi/models/game/livedata/attributes.py new file mode 100644 index 00000000..3bb038ba --- /dev/null +++ b/mlbstatsapi/models/game/livedata/attributes.py @@ -0,0 +1,51 @@ +from typing import Union, Optional +from dataclasses import dataclass +from mlbstatsapi.models.people import Person + + +@dataclass(repr=False) +class GameDecisions: + """ + A class to represent the winning and loosing pitcher for this game. + Only used when a game is over. + + Attributes + ---------- + winner : Person + The winning person + loser : Person + The loosing person + """ + winner: Union[Person, dict] + loser: Union[Person, dict] + save: Optional[Union[Person, dict]] = None + + def __post_init__(self): + self.winner = Person(**self.winner) + self.loser = Person(**self.loser) + self.save = Person(**self.save) if self.save else self.save + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass +class GameLeaders: + """ + A class to represent this games live data leaders. + Not sure what this data looks like since every game ive seen + has an empty dict for each of these. + + Attributes + ---------- + hitdistance : dict + hit distance + hitspeed : dict + hit speed + pitchspeed : dict + pitch speed + """ + # Dont know what this populated looks like. Every game ive seen its three empty dicts? + hitdistance: dict + hitspeed: dict + pitchspeed: dict \ No newline at end of file diff --git a/mlbstatsapi/models/game/livedata/boxscore/__init__.py b/mlbstatsapi/models/game/livedata/boxscore/__init__.py new file mode 100644 index 00000000..cc041c3f --- /dev/null +++ b/mlbstatsapi/models/game/livedata/boxscore/__init__.py @@ -0,0 +1 @@ +from .boxscore import BoxScore diff --git a/mlbstatsapi/models/game/livedata/boxscore/attributes.py b/mlbstatsapi/models/game/livedata/boxscore/attributes.py new file mode 100644 index 00000000..5e756982 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/boxscore/attributes.py @@ -0,0 +1,191 @@ +from typing import Union, List, Optional +from dataclasses import dataclass, field +from mlbstatsapi.models.people import Person, Position +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.data import CodeDesc + + +@dataclass +class BoxScoreVL: + """ + A class to represent a boxscore team's infos label and value + + Attributes + ---------- + label : str + The label for this peice of info + value : str + The info associated with this label + """ + label: str + value: str = None + + +@dataclass +class BoxScoreTeamInfo: + """ + A class to represent a boxscore team's info + + Attributes + ---------- + title : str + Type of information + fieldlist : List[BoxScoreVL] + List holding the info for this info type + """ + title: str + fieldlist: Union[List[BoxScoreVL], List[dict]] + + def __post_init__(self): + self.fieldlist = [BoxScoreVL(**fieldlists) for fieldlists in self.fieldlist] + + +@dataclass +class GameStatus: + """ + A class representing the game status of a player. + + Attributes + ---------- + iscurrentbatter : bool + Whether the player is the current batter. + iscurrentpitcher : bool + Whether the player is the current pitcher. + isonbench : bool + Whether the player is on the bench. + issubstitute : bool + Whether the player is a substitute. + """ + iscurrentbatter: bool + iscurrentpitcher: bool + isonbench: bool + issubstitute: bool + +@dataclass +class PlayersDictPerson: + """ + A class representing a person in a dictionary of players. + + Attributes + ---------- + person : Person + The person object. + jerseynumber : str + The person's jersey number. + position : Position + The person's position. + status : CodeDesc + The person's status. + parentteamid : int + The ID of the person's parent team. + stats : dict + A dictionary of the person's stats. + seasonstats : dict + A dictionary of the person's season stats. + gameStatus : GameStatus + The person's game status. + battingorder : int + The persons place in the batting order if avaliable. + allpositions : Position + All of the person's positions if avaliable. + """ + person: Union[Person, dict] + jerseynumber: str + position: Union[Position, dict] + status: Union[CodeDesc, dict] + parentteamid: int + stats: dict + seasonstats: dict + gamestatus: Union[GameStatus, dict] + battingorder: Optional[int] = None + allpositions: Optional[Union[List[Position], List[dict]]] = None + + def __post_init__(self): + self.person = Person(**self.person) + self.position = Position(**self.position) + self.status = CodeDesc(**self.status) + self.gamestatus = GameStatus(**self.gamestatus) + self.allpositions = [Position(**allposition) for allposition in self.allpositions] if self.allpositions else self.allpositions + +@dataclass +class BoxScoreTeam: + """ + A class to represent the boxscore team + + Attributes + ---------- + team : Team + This team + teamstats : Dict + Team stats + players : Dict + Players on team + batters : List[int] + List of batters playerid for this team + pitchers : List[int] + List of pitcher playerid for this team + bench : List[int] + List of bench playerid for this team + bullpen : List[int] + Bullpen list of playerid + battingorder : List[int] + Batting order for this team as a list of playerid + info : List[BoxScoreTeamInfo] + Batting and fielding info for team + note : List[str] + Team notes + """ + team: Union[Team, dict] + teamstats: dict + players: dict + batters: List[int] + pitchers: List[int] + bench: List[int] + bullpen: List[int] + battingorder: List[int] + info: Union[List[BoxScoreTeamInfo], List[dict]] + note: List[str] + + def __post_init__(self): + self.team = Team(**self.team) + self.info = [BoxScoreTeamInfo(**infos) for infos in self.info] + + for player in self.players: + self.players[player] = PlayersDictPerson(**self.players[player]) + +@dataclass +class BoxScoreTeams: + """ + A class to represent the boxscore home and away teams + + Attributes + ---------- + home : BoxScoreTeam + Home team boxscore information + away : BoxScoreTeam + Away team boxscore information + """ + home: Union[BoxScoreTeam, dict] + away: Union[BoxScoreTeam, dict] + + def __post_init__(self): + self.home = BoxScoreTeam(**self.home) + self.away = BoxScoreTeam(**self.away) + +@dataclass +class BoxScoreOffical: + """ + A class to represent an official for this game + + Attributes + ---------- + official : Person + The official person + officialtype : str + What type of official this person is + """ + official: Union[Person, dict] + officialtype: str + + def __post_init__(self): + self.official = Person(**self.official) diff --git a/mlbstatsapi/models/game/livedata/boxscore/boxscore.py b/mlbstatsapi/models/game/livedata/boxscore/boxscore.py new file mode 100644 index 00000000..09474ea3 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/boxscore/boxscore.py @@ -0,0 +1,30 @@ +from typing import Union, List +from dataclasses import dataclass, field + +from .attributes import BoxScoreTeams, BoxScoreOffical, BoxScoreVL + +@dataclass +class BoxScore: + """ + A class to represent this games boxscore + + Attributes + ---------- + teams : BoxScoreTeams + Box score data for each team + officials : List[BoxScoreOffical] + The officials for this game + info : List[BoxScoreVL] + Box score information + pitchingnotes : List[str] + Pitching notes for this game + """ + teams: Union[BoxScoreTeams, dict] + officials: Union[List[BoxScoreOffical], List[dict]] + info: Union[List[BoxScoreVL], List[dict]] + pitchingnotes: List[str] + + def __post_init__(self): + self.teams = BoxScoreTeams(**self.teams) + self.officials = [BoxScoreOffical(**official) for official in self.officials] + self.info = [BoxScoreVL(**infos) for infos in self.info] diff --git a/mlbstatsapi/models/game/livedata/linescore/__init__.py b/mlbstatsapi/models/game/livedata/linescore/__init__.py new file mode 100644 index 00000000..09b4b432 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/linescore/__init__.py @@ -0,0 +1 @@ +from .linescore import Linescore diff --git a/mlbstatsapi/models/game/livedata/linescore/attributes.py b/mlbstatsapi/models/game/livedata/linescore/attributes.py new file mode 100644 index 00000000..71bf2f29 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/linescore/attributes.py @@ -0,0 +1,184 @@ +from typing import Union, Optional +from dataclasses import dataclass, field +from mlbstatsapi.models.people import Person +from mlbstatsapi.models.teams import Team + +@dataclass +class LinescoreTeamScoreing: + """ + A class to represent a games Linescore + + Attributes + ---------- + hits : int + Team hits for this inning + errors : int + Team errors for this inning + leftonbase : int + Player left on base for this inning + runs : int + Team runs for this inning + iswinner : bool + If team is winner + """ + hits: int + errors: int + leftonbase: int + runs: Optional[int] = None + iswinner: Optional[bool] = None + +@dataclass +class LinescoreInning: + """ + A class to represent a inning for a games Linescore + + Attributes + ---------- + num : int + Inning number + ordinalnum : str + Inning ordinal + home : LinescoreTeamScoreing + Home team inning info + away : LinescoreTeamScoreing + Away team inning info + """ + num: int + ordinalnum: str + home: Union[LinescoreTeamScoreing, dict] + away: Union[LinescoreTeamScoreing, dict] + + def __post_init__(self): + self.home = LinescoreTeamScoreing(**self.home) if self.home else self.home + self.away = LinescoreTeamScoreing(**self.away) if self.away else self.away + +@dataclass +class LinescoreTeams: + """ + A class to represent home and away teams in the linescore + + Attributes + ---------- + home : LinescoreTeamScoreing + Home team current inning info + away : LinescoreTeamScoreing + Away team current inning info + """ + home: Union[LinescoreTeamScoreing, dict] = field(default_factory=dict) + away: Union[LinescoreTeamScoreing, dict] = field(default_factory=dict) + + def __post_init__(self): + self.home = LinescoreTeamScoreing(**self.home) if self.home else self.home + self.away = LinescoreTeamScoreing(**self.away) if self.away else self.away + +@dataclass(repr=False) +class LinescoreOffense: + """ + A class to represent a games current offense + + Attributes + ---------- + batter : Person + Current batter + ondeck : Person + Current on deck batter + inhole : Person + Current in the hole batter + pitcher : Person + Who is this teams pitcher + battingorder : int + Number in the batting order + team : Team + The team currently on offense + """ + team: Union[Team, dict] + batter: Optional[Union[Person, dict]] = field(default_factory=dict) + ondeck: Optional[Union[Person, dict]] = field(default_factory=dict) + inhole: Optional[Union[Person, dict]] = field(default_factory=dict) + pitcher: Optional[Union[Person, dict]] = field(default_factory=dict) + battingorder: Optional[int] = None + first: Optional[str] = None + second: Optional[str] = None + third: Optional[str] = None + + def __post_init__(self): + self.batter = Person(**self.batter) if self.batter else self.batter + self.ondeck = Person(**self.ondeck) if self.ondeck else self.ondeck + self.inhole = Person(**self.inhole) if self.inhole else self.inhole + self.pitcher = Person(**self.pitcher) if self.pitcher else self.pitcher + self.team = Team(**self.team) + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(repr=False) +class LinescoreDefense: + """ + A class to represent a games current defense + + Attributes + ---------- + pitcher : Person + Current pitcher + catcher : Person + Current catcher + first : Person + Current first + second : Person + Current second + third : Person + Current third + shortstop : Person + Current shortstop + left : Person + Current left + center : Person + Current center + right : Person + Current right + batter : Person + The next batter when this team switches to offense + ondeck : Person + The next ondeck batter when this team switches to offense + inhole : Person + The next inHole batter when this team switches to offense + battingorder : int + Number this team is in the batting order + team : Team + The team that is playing defense currently + """ + team: Union[Team, dict] + pitcher: Optional[Union[Person, dict]] = field(default_factory=dict) + catcher: Optional[Union[Person, dict]] = field(default_factory=dict) + first: Optional[Union[Person, dict]] = field(default_factory=dict) + second: Optional[Union[Person, dict]] = field(default_factory=dict) + third: Optional[Union[Person, dict]] = field(default_factory=dict) + shortstop: Optional[Union[Person, dict]] = field(default_factory=dict) + left: Optional[Union[Person, dict]] = field(default_factory=dict) + center: Optional[Union[Person, dict]] = field(default_factory=dict) + right: Optional[Union[Person, dict]] = field(default_factory=dict) + batter: Optional[Union[Person, dict]] = field(default_factory=dict) + ondeck: Optional[Union[Person, dict]] = field(default_factory=dict) + inhole: Optional[Union[Person, dict]] = field(default_factory=dict) + battingorder: int = None + + + def __post_init__(self): + self.pitcher = Person(**self.pitcher) if self.pitcher else self.pitcher + self.catcher = Person(**self.catcher) if self.catcher else self.catcher + self.first = Person(**self.first) if self.first else self.first + self.second = Person(**self.second) if self.second else self.second + self.third = Person(**self.third) if self.third else self.third + self.shortstop = Person(**self.shortstop) if self.shortstop else self.shortstop + self.left = Person(**self.left) if self.left else self.left + self.center = Person(**self.center) if self.center else self.center + self.right = Person(**self.right) if self.right else self.right + self.batter = Person(**self.batter) if self.batter else self.batter + self.ondeck = Person(**self.ondeck) if self.ondeck else self.ondeck + self.inhole = Person(**self.inhole) if self.inhole else self.inhole + self.team = Team(**self.team) + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/game/livedata/linescore/linescore.py b/mlbstatsapi/models/game/livedata/linescore/linescore.py new file mode 100644 index 00000000..c89338d4 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/linescore/linescore.py @@ -0,0 +1,67 @@ +from typing import Union, List, Optional +from dataclasses import dataclass + +from .attributes import LinescoreInning +from .attributes import LinescoreTeams +from .attributes import LinescoreDefense +from .attributes import LinescoreOffense + +@dataclass(repr=False) +class Linescore: + """ + A class to represent a games Linescore + + Attributes + ---------- + currentinning : int + The games current inning + currentinningordinal : str + This innings ordinal + inningstate : str + What state this inning is in + inninghalf : str + WHich half of the inning are we in + istopinning : bool + Is this the top of the inning + scheduledinnings : int + How many innings are scheduled for this game + innings : List[LinescoreInning] + Data on each inning + teams : LinescoreTeams + Line score data on our teams + defense : LinescoreDefense + Current defense + offense : LinescoreOffense + Current offense + balls : int + current count balls + strikes : int + current count strikes + outs : int + current count outs + """ + + scheduledinnings: int + innings: Union[List[LinescoreInning], List[dict]] + teams: Union[LinescoreTeams, dict] + defense: Union[LinescoreDefense, dict] + offense: Union[LinescoreOffense, dict] + balls: Optional[int] = None + strikes: Optional[int] = None + outs: Optional[int] = None + note: Optional[str] = None + currentinning: Optional[int] = None + currentinningordinal: Optional[str] = None + inningstate: Optional[str] = None + inninghalf: Optional[str] = None + istopinning: Optional[bool] = None + + def __post_init__(self): + self.innings = [LinescoreInning(**inning) for inning in self.innings] + self.teams = LinescoreTeams(**self.teams) + self.defense = LinescoreDefense(**self.defense) + self.offense = LinescoreOffense(**self.offense) + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/game/livedata/livedata.py b/mlbstatsapi/models/game/livedata/livedata.py new file mode 100644 index 00000000..8f883686 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/livedata.py @@ -0,0 +1,45 @@ +from typing import Union, Optional +from dataclasses import dataclass, field + +from mlbstatsapi.models.game.livedata.plays import Plays +from mlbstatsapi.models.game.livedata.linescore import Linescore +from mlbstatsapi.models.game.livedata.boxscore import BoxScore + +from .attributes import GameLeaders, GameDecisions + + +@dataclass(repr=False) +class LiveData: + """ + A class to represent this games live data. + + Attributes + ---------- + plays : Plays + Has the plays for this game + linescore : Linescore + This games linescore + boxscore : BoxScore + This games boxscore + leaders : GameLeaders + The data leaders for this game + decisions : GameDecisions = None + Decisions for this game, Ie a winner or a loser + """ + plays: Union[Plays, dict] + boxscore: Union[BoxScore, dict] + leaders: Union[GameLeaders, dict] + decisions: Optional[Union[GameDecisions, dict]] = field(default_factory=dict) + linescore: Union[Linescore, dict] = field(default_factory=dict) + + + def __post_init__(self): + self.plays = Plays(**self.plays) + self.linescore = Linescore(**self.linescore) if self.linescore else self.linescore + self.boxscore = BoxScore(**self.boxscore) + self.decisions = GameDecisions(**self.decisions) if self.decisions else self.decisions + self.leaders = GameLeaders(**self.leaders) + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/game/livedata/plays/__init__.py b/mlbstatsapi/models/game/livedata/plays/__init__.py new file mode 100644 index 00000000..fd58b650 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/__init__.py @@ -0,0 +1 @@ +from .plays import Plays diff --git a/mlbstatsapi/models/game/livedata/plays/play/__init__.py b/mlbstatsapi/models/game/livedata/plays/play/__init__.py new file mode 100644 index 00000000..be2d3b78 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/play/__init__.py @@ -0,0 +1 @@ +from .play import Play diff --git a/mlbstatsapi/models/game/livedata/plays/play/attributes.py b/mlbstatsapi/models/game/livedata/plays/play/attributes.py new file mode 100644 index 00000000..79c35184 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/play/attributes.py @@ -0,0 +1,111 @@ +from typing import Optional +from dataclasses import dataclass + +@dataclass(repr=False) +class PlayAbout: + """ + A class to represent a information about a play. + + Attributes + ---------- + atbatindex : int + Current at bat index + halfinning : str + What side of the inning + istopinning : bool + Is this inning the top of the inning + inning : int + What number of inning we are in + starttime : str + The start time for this play + endtime : str + The end time for this play + iscomplete : bool + Is this play complete + isscoringplay : bool + is this play a scoring play + hasreview : bool + Dose this play have a review + hasout : bool + Does this play have a out + captivatingindex : int + What is the captivating index for this play + """ + atbatindex: int + halfinning: str + istopinning: bool + inning: int + iscomplete: bool + isscoringplay: bool + hasout: bool + captivatingindex: int + endtime: Optional[str] = None + starttime: Optional[str] = None + hasreview: Optional[bool] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(repr=False) +class PlayResult: + """ + A class to represent a play result. + + Attributes + ---------- + type : str + Play result type + event : str + Play event + eventtype : str + Event type + description : str + Event description + rbi : int + Number of RBI's + awayscore : int + Score for away team + homescore : int + Score for home team + isout : bool + If the play was an out + """ + type: str + rbi: int + awayscore: int + homescore: int + event: Optional[str] = None + eventtype: Optional[str] = None + description: Optional[str] = None + isout: Optional[bool] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(repr=False) +class PlayReviewDetails: + """ + A class to represent play review details. + + Attributes + ---------- + isoverturned : bool + Was it overturned + inprogress : bool + Is it in progress + reviewtype : str + What type of review + challengeteamid : int + The team issuing the challenge review + """ + isoverturned: bool + inprogress: bool + reviewtype: str + challengeteamid: Optional[int] = None + additionalreviews: Optional[str] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/game/livedata/plays/play/matchup/__init__.py b/mlbstatsapi/models/game/livedata/plays/play/matchup/__init__.py new file mode 100644 index 00000000..5527ce5a --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/play/matchup/__init__.py @@ -0,0 +1 @@ +from .matchup import PlayMatchupSplits, PlayMatchup \ No newline at end of file diff --git a/mlbstatsapi/models/game/livedata/plays/play/matchup/attributes.py b/mlbstatsapi/models/game/livedata/plays/play/matchup/attributes.py new file mode 100644 index 00000000..12b3c190 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/play/matchup/attributes.py @@ -0,0 +1,19 @@ +from dataclasses import dataclass + +@dataclass +class PlayMatchupSplits: + """ + A class to represent a playMatchup Split. + + Attributes + ---------- + batter : str + Batter matchup split + pitcher : str + Pitcher matchup split + menonbase : str + Menonbase matchup split + """ + batter: str + pitcher: str + menonbase: str \ No newline at end of file diff --git a/mlbstatsapi/models/game/livedata/plays/play/matchup/matchup.py b/mlbstatsapi/models/game/livedata/plays/play/matchup/matchup.py new file mode 100644 index 00000000..1cfc8d94 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/play/matchup/matchup.py @@ -0,0 +1,64 @@ +from typing import Union, Optional, List +from dataclasses import dataclass + +from mlbstatsapi.models.people import Person +from mlbstatsapi.models.data import CodeDesc + +from .attributes import PlayMatchupSplits + +@dataclass(repr=False) +class PlayMatchup: + """ + A class to represent a play Matchup. + + Attributes + ---------- + batter : Person + Matchup batter + batside : PlayMatchupSide + batters batside + pitcher : Person + Matchup pitcher + pitchhand : PlayMatchupSide + Pitchers side + pitcherhotcoldzones : List + Pitcher hot cold zone stats + splits : PlayMatchupSplits + PlayMatchupSplits + batterhotcoldzonestats : List + Batter hot cold zone stats + postonfirst : Person + Runner on first + postonsecond : Person + Runner on second + postonthird : Person + Runner on third + """ + batter: Union[Person, dict] + batside: Union[CodeDesc, dict] + pitcher: Union[Person, dict] + pitchhand: Union[CodeDesc, dict] + batterhotcoldzones: List + pitcherhotcoldzones: List + splits: Union[PlayMatchupSplits, dict] + batterhotcoldzonestats: Optional[List] = None + pitcherhotcoldzonestats: Optional[List] = None + postonfirst: Optional[Union[Person, dict]] = None + postonsecond: Optional[Union[Person, dict]] = None + postonthird: Optional[Union[Person, dict]] = None + + def __post_init__(self): + self.batter = Person(**self.batter) + self.batside = CodeDesc(**self.batside) + self.pitcher = Person(**self.pitcher) + self.pitchhand = CodeDesc(**self.pitchhand) + self.splits = PlayMatchupSplits(**self.splits) + self.batterhotcoldzonestats = self.batterhotcoldzonestats['stats'] if self.batterhotcoldzonestats else self.batterhotcoldzonestats + self.pitcherhotcoldzonestats = self.pitcherhotcoldzonestats['stats'] if self.pitcherhotcoldzonestats else self.pitcherhotcoldzonestats + self.postonfirst = Person(**self.postonfirst) if self.postonfirst else self.postonfirst + self.postonsecond = Person(**self.postonsecond) if self.postonsecond else self.postonsecond + self.postonthird = Person(**self.postonthird) if self.postonthird else self.postonthird + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/game/livedata/plays/play/play.py b/mlbstatsapi/models/game/livedata/plays/play/play.py new file mode 100644 index 00000000..7bde0527 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/play/play.py @@ -0,0 +1,67 @@ +from typing import Union, Optional, List +from dataclasses import dataclass + +from mlbstatsapi.models.game.livedata.plays.play.matchup import PlayMatchup +from mlbstatsapi.models.game.livedata.plays.play.playrunner import PlayRunner +from mlbstatsapi.models.game.livedata.plays.play.playevent import PlayEvent +from mlbstatsapi.models.data import Count +from .attributes import PlayAbout, PlayResult, PlayReviewDetails + + +@dataclass(repr=False) +class Play: + """ + A class to represent a single play in this game. + + Attributes + ---------- + result : PlayResult + Play result + about : PlayAbout + Information about this play + count : PlayCount + This plays count + matchup : PlayMatchup + This plays matchup + pitchindex : List[int] + Pitch index for this play, indexing playEvents + actionindex : List[int] + Action index for this play, indexing playEvents + runnerindex : List[int] + Runner index for this play, indexing runners + runners : List[PlayRunner] + Runners + playevents : List[PlayEvent] + Play events + playendtime : str + Time this play ends + atbatindex : int + The play index number + reviewdetails : PlayReviewDetails + Information on reviews if present + """ + result: Union[PlayResult, dict] + about: Union[PlayAbout, dict] + count: Union[Count, dict] + matchup: Union[PlayMatchup, dict] + pitchindex: List[int] + actionindex: List[int] + runnerindex: List[int] + runners: Union[List[PlayRunner], List[dict]] + playevents: Union[List[PlayEvent], List[dict]] + atbatindex: int + playendtime: Optional[str] = None + reviewdetails: Optional[Union[PlayReviewDetails, dict]] = None + + def __post_init__(self): + self.result = PlayResult(**self.result) + self.about = PlayAbout(**self.about) + self.count = Count(**self.count) + self.matchup = PlayMatchup(**self.matchup) + self.runners = [PlayRunner(**runner) for runner in self.runners] + self.playevents = [PlayEvent(**playevent) for playevent in self.playevents] + self.reviewdetails = PlayReviewDetails(**self.reviewdetails) if self.reviewdetails else self.reviewdetails + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/game/livedata/plays/play/playevent/__init__.py b/mlbstatsapi/models/game/livedata/plays/play/playevent/__init__.py new file mode 100644 index 00000000..fa265370 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/play/playevent/__init__.py @@ -0,0 +1 @@ +from .playevent import PlayEvent \ No newline at end of file diff --git a/mlbstatsapi/models/game/livedata/plays/play/playevent/playevent.py b/mlbstatsapi/models/game/livedata/plays/play/playevent/playevent.py new file mode 100644 index 00000000..236fbf71 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/play/playevent/playevent.py @@ -0,0 +1,85 @@ +from typing import Union, Optional +from dataclasses import dataclass +from mlbstatsapi.models.people import Person, Position +from mlbstatsapi.models.data import Count, HitData, PitchData, PlayDetails + +@dataclass(repr=False) +class PlayEvent: + """ + A class to represent a information about a play. + + Attributes + ---------- + details : PlayDetails + Event details + index : int + Event index + starttime : str + Event start time + endtime : str + Event end time + ispitch : bool + Is this event a pitch + type : str + Type + playid : str + Unique play id ? + pitchnumber : int + Pitch number + actionplayid : str + Unique action play id ? + isbaserunningplay : bool + Is there base running this play + issubstitution : bool + Is this a substitution + battingorder : str + A weird batting order string that only has appeared once + count : PlayCount + Count + pitchdata : PitchData + Pitch data + hitdata : HitData + Hit data + player : Person + Player + position : Position + Position + replacedplayer : Person + Replaced player + """ + details: Union[PlayDetails, dict] + index: int + ispitch: bool + type: str + pfxid: Optional[str] = None + starttime: Optional[str] = None + endtime: Optional[str] = None + umpire: Optional[str] = None + base: Optional[str] = None + playid: Optional[str] = None + pitchnumber: Optional[int] = None + actionplayid: Optional[str] = None + isbaserunningplay: Optional[bool] = None + issubstitution: Optional[bool] = None + battingorder: Optional[str] = None + count: Optional[Union[Count, dict]] = None + pitchdata: Optional[Union[PitchData, dict]] = None + hitdata: Optional[Union[HitData, dict]] = None + player: Optional[Union[Person, dict]] = None + position: Optional[Union[Position, dict]] = None + replacedplayer: Optional[Union[Person, dict]] = None + reviewdetails: Optional[dict] = None + injurytype: Optional[str] = None + + def __post_init__(self): + self.details = PlayDetails(**self.details) + self.count = Count(**self.count) if self.count else self.count + self.pitchdata = PitchData(**self.pitchdata) if self.pitchdata else self.pitchdata + self.hitdata = HitData(**self.hitdata) if self.hitdata else self.hitdata + self.player = Person(**self.player) if self.player else self.player + self.position = Position(**self.position) if self.position else self.position + self.replacedplayer = Person(**self.replacedplayer) if self.replacedplayer else self.replacedplayer + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/game/livedata/plays/play/playrunner/__init__.py b/mlbstatsapi/models/game/livedata/plays/play/playrunner/__init__.py new file mode 100644 index 00000000..bfb8f16f --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/play/playrunner/__init__.py @@ -0,0 +1 @@ +from .playrunner import PlayRunner diff --git a/mlbstatsapi/models/game/livedata/plays/play/playrunner/attributes.py b/mlbstatsapi/models/game/livedata/plays/play/playrunner/attributes.py new file mode 100644 index 00000000..86d649b0 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/play/playrunner/attributes.py @@ -0,0 +1,106 @@ +from typing import Union, Optional +from dataclasses import dataclass + +from mlbstatsapi.models.people import Person, Position + + +@dataclass +class RunnerCredits: + """ + A class to represent a runners credit. + + Attributes + ---------- + player: Person + The player + position: RunnerCreditsPosition + The position + credit: str + The credit + """ + player: Union[Person, dict] + position: Union[Position, dict] + credit: str + + def __post_init__(self): + self.player = Person(**self.player) + self.position = Position(**self.position) + + +@dataclass(repr=False) +class RunnerMovement: + """ + A class to represent a play runner. + + Attributes + ---------- + isout: bool + Was the running movement an out + outnumber: int + What is the outnumber + originbase: str + Original base + start: str + What base the runner started from + end: str + What base the runner ended at + outbase: str + Base runner was made out + """ + isout: bool + outnumber: int + originbase: Optional[str] = None + start: Optional[str] = None + end: Optional[str] = None + outbase: Optional[str] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(repr=False) +class RunnerDetails: + """ + A class to represent a play runner. + + Attributes + ---------- + event: str + Runner event + eventtype: str + Runner event type + runner: Person + Who the runner is + isscoringevent: bool + Was this a scoring events + rbi: bool + Was this a rbi + earned: bool + Was it earned + teamunearned: bool + Was it unearned + playindex: int + Play index + movementreason: str + Reason for the movement + responsiblepitcher: Person + WHo was the responsible pitcher + """ + event: str + eventtype: str + runner: Union[Person, dict] + isscoringevent: bool + rbi: bool + earned: bool + teamunearned: bool + playindex: int + movementreason: Optional[str] = None + responsiblepitcher: Optional[Union[Person, dict]] = None + + def __post_init__(self): + self.runner = Person(**self.runner) + self.responsiblepitcher = Person(**self.responsiblepitcher) if self.responsiblepitcher else self.responsiblepitcher + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/game/livedata/plays/play/playrunner/playrunner.py b/mlbstatsapi/models/game/livedata/plays/play/playrunner/playrunner.py new file mode 100644 index 00000000..d6f09f8d --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/play/playrunner/playrunner.py @@ -0,0 +1,28 @@ +from typing import Union, List, Optional +from dataclasses import dataclass, field + +from .attributes import RunnerMovement, RunnerDetails, RunnerCredits + + +@dataclass +class PlayRunner: + """ + A class to represent a play runner. + + Attributes + ---------- + movement: RunnerMovement + Runner movements + details: RunnerDetails + Runner details + credits: List[RunnerCredits] + Runner credits + """ + movement: Union[RunnerMovement, dict] + details: Union[RunnerDetails, dict] + credits: Optional[Union[List[RunnerCredits], List[dict]]] = field(default_factory=list) + + def __post_init__(self): + self.movement = RunnerMovement(**self.movement) + self.details = RunnerDetails(**self.details) + self.credits = [RunnerCredits(**credit) for credit in self.credits] diff --git a/mlbstatsapi/models/game/livedata/plays/playbyinning/__init__.py b/mlbstatsapi/models/game/livedata/plays/playbyinning/__init__.py new file mode 100644 index 00000000..3d02fc80 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/playbyinning/__init__.py @@ -0,0 +1 @@ +from .playbyinning import PlayByInning diff --git a/mlbstatsapi/models/game/livedata/plays/playbyinning/attributes.py b/mlbstatsapi/models/game/livedata/plays/playbyinning/attributes.py new file mode 100644 index 00000000..ccbf46fa --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/playbyinning/attributes.py @@ -0,0 +1,75 @@ +from typing import Union, List +from dataclasses import dataclass + +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.people import Person + +@dataclass +class HitCoordinates: + """ + A class to represent a Hits coordinates. + + Attributes + ---------- + x : float + X coordinate for hit + y : float + Y coordinate for hit + """ + x: float + y: float + +@dataclass +class HitsByTeam: + """ + A class to represent a Hit during an inning. + + Attributes + ---------- + team : Team + This team + inning : int + This inning number + pitcher : Person + The pitcher + batter : Person + The batter + coordinates : HitCoordinates + Hit coordinates + type : str + Type + description : str + description + """ + team: Union[Team, dict] + inning: int + pitcher: Union[Person, dict] + batter: Union[Person, dict] + coordinates: Union[HitCoordinates, dict] + type: str + description: str + + def __post_init__(self): + self.team = Team(**self.team) + self.pitcher = Person(**self.pitcher) + self.batter = Person(**self.batter) + self.coordinates = HitCoordinates(**self.coordinates) + +@dataclass +class PlayByInningHits: + """ + A class to represent a play by inning in this game. + + Attributes + ---------- + home : List[HitsByTeam] + Home team hits + away : List[HitsByTeam] + Away team hits + """ + home: Union[List[HitsByTeam], List[dict]] + away: Union[List[HitsByTeam], List[dict]] + + def __post_init__(self): + self.home = [HitsByTeam(**home_hit) for home_hit in self.home] + self.away = [HitsByTeam(**away_hit) for away_hit in self.away] diff --git a/mlbstatsapi/models/game/livedata/plays/playbyinning/playbyinning.py b/mlbstatsapi/models/game/livedata/plays/playbyinning/playbyinning.py new file mode 100644 index 00000000..d5b84950 --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/playbyinning/playbyinning.py @@ -0,0 +1,31 @@ +from typing import Union, List +from dataclasses import dataclass + +from .attributes import PlayByInningHits + +@dataclass +class PlayByInning: + """ + A class to represent a play by inning in this game. + + Attributes + ---------- + startindex : int + Starting play index number, indexed with Plays.allPlays + endindex : int + End play index number, indexed with Plays.allPlays + top : List[int] + Play indexes for top of the inning + bottom : List[int] + play indexes for bottom of the inning + hits : PlayByInningHits + Hits for the inning by home and away + """ + startindex: int + endindex: int + top: List[int] + bottom: List[int] + hits: Union[PlayByInningHits, dict] + + def __post_init__(self): + self.hits = PlayByInningHits(**self.hits) diff --git a/mlbstatsapi/models/game/livedata/plays/plays.py b/mlbstatsapi/models/game/livedata/plays/plays.py new file mode 100644 index 00000000..c21c3ead --- /dev/null +++ b/mlbstatsapi/models/game/livedata/plays/plays.py @@ -0,0 +1,37 @@ +from typing import Union, List +from dataclasses import dataclass, field + +from mlbstatsapi.models.game.livedata.plays.play import Play +from mlbstatsapi.models.game.livedata.plays.playbyinning import PlayByInning + + +@dataclass(repr=False) +class Plays: + """ + A class to represent the plays in this game. + + Attributes + ---------- + allplays : List[Play] + All the plays in this game + currentplay : Play + The current play in this game + scoringplays : List[int] + Which plays are scoring plays, indexed with allPlays + playsbyinning : List[PlayByInning] + Plays by inning + """ + allplays: Union[List[Play], List[dict]] + scoringplays: List[int] + playsbyinning: Union[List[PlayByInning], List[dict]] + currentplay: Union[Play, dict] = field(default_factory=dict) + + + def __post_init__(self): + self.allplays = [Play(**play) for play in self.allplays if play] + self.currentplay = Play(**self.currentplay) if self.currentplay else self.currentplay + self.playsbyinning = [PlayByInning(**inning) for inning in self.playsbyinning if inning] + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/gamepace/__init__.py b/mlbstatsapi/models/gamepace/__init__.py new file mode 100644 index 00000000..4d13e2a0 --- /dev/null +++ b/mlbstatsapi/models/gamepace/__init__.py @@ -0,0 +1,2 @@ +from .gamepace import Gamepace +from .attributes import Gamepacedata \ No newline at end of file diff --git a/mlbstatsapi/models/gamepace/attributes.py b/mlbstatsapi/models/gamepace/attributes.py new file mode 100644 index 00000000..9866b1c5 --- /dev/null +++ b/mlbstatsapi/models/gamepace/attributes.py @@ -0,0 +1,186 @@ +from typing import Union, Optional +from dataclasses import dataclass + +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.leagues import League +from mlbstatsapi.models.sports import Sport + + +@dataclass(kw_only=True) +class Prportalcalculatedfields: + """ + This dataclass represents the calculated fields for a baseball game. + + Attributes: + ---------- + total7inngames : int + The total number of 7-inning games played. + total9inngames : int + The total number of 9-inning games played. + totalextrainngames : int + The total number of extra-inning games played. + timeper7inngame : str + The average time per 7-inning game. + timeper9inngame : str + The average time per 9-inning game. + timeperextrainngame : str + The average time per extra-inning game. + """ + total7inngames: int + total9inngames: int + totalextrainngames: int + timeper7inngame: str + timeper9inngame: str + timeperextrainngame: str + + +@dataclass(kw_only=True, repr=False) +class Gamepacedata: + """ + This dataclass represents a league in a sport, with various statistics and metrics related to its games and performances. + + Attributes: + ---------- + hitsper9inn : float + The number of hits per 9 innings played. + runsper9inn : float + The number of runs scored per 9 innings played. + pitchesper9inn : float + The number of pitches thrown per 9 innings played. + plateappearancesper9inn : float + The number of plate appearances per 9 innings played. + hitspergame : float + The number of hits per game played. + runspergame : float + The number of runs scored per game played. + inningsplayedpergame : float + The number of innings played per game. + pitchespergame : float + The number of pitches thrown per game played. + pitcherspergame : float + The number of pitchers used per game played. + plateappearancespergame : float + The number of plate appearances per game played. + totalgametime : str + The total time spent playing games in the league. + totalinningsplayed : float + The total number of innings played in the league. + totalhits : int + The total number of hits in the league. + totalruns : int + The total number of runs scored in the league. + totalplateappearances : int + The total number of plate appearances in the league. + totalpitchers : int + The total number of pitchers used in the league. + totalpitches : int + The total number of pitches thrown in the league. + totalgames : int + The total number of games played in the league. + total7inngames : int + The total number of 7-inning games played in the league. + total9inngames : int + The total number of 9-inning games played in the league. + totalextrainngames : int + The total number of extra inning games played in the league. + timepergame : str + The amount of time spent per game in the league. + timeperpitch : str + The amount of time spent per pitch in the league. + timeperhit : str + The amount of time spent per hit in the league. + timeperrun : str + The amount of time spent per run scored in the league. + timeperplateappearance : str + The amount of time spent per plate appearance in the league. + timeper9inn : str + The amount of time spent per 9 innings played in the league. + timeper77plateappearances : str + The amount of time spent per 7-7 plate appearances in the league. + totalextrainntime : str + The total amount of time spent on extra inning games in the league. + timeper7inngame : str + The amount of time spent per 7-inning game in the league. + total7inngamescompletedearly: int + The total number of 7-inning games completed early in the league. + timeper7inngamewithoutextrainn: str + The amount of time spent per 7-inning game without extra innings in the league. + total7inngamesscheduled : int + The total number of 7-inning games scheduled in the league. + total7inngameswithoutextrainn : int + The total number of 7-inning games played without extra innings in the league. + total9inngamescompletedearly : int + The total number of 9-inning games completed early in the league. + total9inngameswithoutextrainn : int + The total number of 9-inning games + total9inngamesscheduled : int + The total number of 9 inning games scheduled + hitsperrun : float + The number of hits per run + pitchesperpitcher : float + Number of pitches thrown per pitcher + season : str + Season number + team: Team + Team + league : League + League + sport : Sport + Sport` + prportalcalculatedfields : Prportalcalculatedfields + calculated fields for a league + """ + hitsper9inn: Optional[float] = None + runsper9inn: Optional[float] = None + pitchesper9inn: Optional[float] = None + plateappearancesper9inn: Optional[float] = None + hitspergame: Optional[float] = None + runspergame: Optional[float] = None + inningsplayedpergame: Optional[float] = None + pitchespergame: Optional[float] = None + pitcherspergame: Optional[float] = None + plateappearancespergame: Optional[float] = None + totalgametime: Optional[str] = None + totalinningsplayed: Optional[float] = None + totalhits: Optional[int] = None + totalruns: Optional[int] = None + totalplateappearances: Optional[int] = None + totalpitchers: Optional[int] = None + totalpitches: Optional[int] = None + totalgames: Optional[int] = None + total7inngames: Optional[int] = None + total9inngames: Optional[int] = None + totalextrainngames: Optional[int] = None + timepergame: Optional[str] = None + timeperpitch: Optional[str] = None + timeperhit: Optional[str] = None + timeperrun: Optional[str] = None + timeperplateappearance: Optional[str] = None + timeper9inn: Optional[str] = None + timeper77plateappearances: Optional[str] = None + totalextrainntime: Optional[str] = None + timeper7inngame: Optional[str] = None + total7inngamescompletedearly: Optional[int] = None + timeper7inngamewithoutextrainn: Optional[str] = None + total7inngamesscheduled: Optional[int] = None + total7inngameswithoutextrainn: Optional[int] = None + total9inngamescompletedearly: Optional[int] = None + total9inngameswithoutextrainn: Optional[int] = None + total9inngamesscheduled: Optional[int] = None + hitsperrun: Optional[float] = None + pitchesperpitcher: Optional[float] = None + season: Optional[str] = None + team: Optional[Union[Team, dict]] = None + league: Optional[Union[League, dict]] = None + sport: Optional[Union[Sport, dict]] = None + prportalcalculatedfields: Optional[Union[Prportalcalculatedfields, dict]] = None + + def __post_init__(self): + self.team = Team(**self.team) if self.team else None + self.league = League(**self.league) if self.league else None + self.sport = Sport(**self.sport) if self.sport else None + self.prportalcalculatedfields = Prportalcalculatedfields(**self.prportalcalculatedfields) if self.prportalcalculatedfields else None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/gamepace/gamepace.py b/mlbstatsapi/models/gamepace/gamepace.py new file mode 100644 index 00000000..764117ea --- /dev/null +++ b/mlbstatsapi/models/gamepace/gamepace.py @@ -0,0 +1,28 @@ +from dataclasses import dataclass +from typing import Optional, List + +from .attributes import Gamepacedata + +@dataclass +class Gamepace: + """ + A dataclass representing a gamepace. + + Attributes: + ---------- + teams : List[Gamepacedata] + A list of teams in the gamepace. + leagues : List[Gamepacedata] + A list of leagues in the gamepace. + sports : List[Gamepacedata] + A list of sports in the gamepace. + """ + teams: Optional[List[Gamepacedata]] = None + leagues: Optional[List[Gamepacedata]] = None + sports: Optional[List[Gamepacedata]] = None + + + def __post_init__(self): + self.teams = [Gamepacedata(**teams) for teams in self.teams] + self.leagues = [Gamepacedata(**leagues) for leagues in self.leagues] + self.sports = [Gamepacedata(**sports) for sports in self.sports] \ No newline at end of file diff --git a/mlbstatsapi/models/homerunderby/__init__.py b/mlbstatsapi/models/homerunderby/__init__.py new file mode 100644 index 00000000..8b290c1c --- /dev/null +++ b/mlbstatsapi/models/homerunderby/__init__.py @@ -0,0 +1,2 @@ +from .homerunderby import Homerunderby +from .attributes import Round \ No newline at end of file diff --git a/mlbstatsapi/models/homerunderby/attributes.py b/mlbstatsapi/models/homerunderby/attributes.py new file mode 100644 index 00000000..29463a5e --- /dev/null +++ b/mlbstatsapi/models/homerunderby/attributes.py @@ -0,0 +1,326 @@ +from typing import Union, Optional, List +from dataclasses import dataclass + +from mlbstatsapi.models.venues import Venue +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.people import Person + +@dataclass +class Eventtype: + """ + The Eventtype class holds information about the type of an event. + + Attributes: + ___________ + code : str + The unique code of the event type. + name : str + The name of the event type. + """ + code: str + name: str + +@dataclass +class Info: + """ + The Info class holds information about an event. + + Attributes + ---------- + id : int + The unique identifier of the event. + name : str + The name of the event. + eventtype : Eventtype + The type of event. Can be an instance of the Eventtype class or a + dictionary containing the attributes for the Eventtype class. + eventdate : str + The date of the event. + venue : Venue + The venue of the event. Can be an instance of the Venue class or + a dictionary containing the attributes for the Venue class. + ismultiday : bool + Whether the event spans multiple days. + isprimarycalendar : bool + Whether the event is on the primary calendar. + filecode : str + The code of the file associated with the event. + eventnumber : int + The number of the event. + publicfacing : bool + Whether the event is public-facing. + teams : List[Team] + The teams participating in the event. Can be a list of instances of + the Team class or a list of dictionaries containing the attributes + for the Team class. + """ + id: int + name: str + eventtype: Union[Eventtype, dict] + eventdate: str + venue: Union[Venue, dict] + ismultiday: bool + isprimarycalendar: bool + filecode: str + eventnumber: int + publicfacing: bool + teams: List[Union[Team, dict]] + + def __post_init__(self): + self.eventtype = Eventtype(**self.eventtype) + self.venue = Venue(**self.venue) + self.teams = [Team(**team) for team in self.teams] + +@dataclass +class Status: + """ + A class representing the status of a round. + + Attributes + ---------- + state : str + The current state of the game or round (e.g. "in progress", "paused", + "ended") + currentround : int + The number of the current round in the game + currentroundtimeleft : str + The amount of time left in the current round, in a human-readable + format (e.g. "4:00") + intiebreaker : bool + Whether the game or round is currently in a tiebreaker + tiebreakernum : int + The number of the current tiebreaker, if applicable + clockstopped : bool + Whether the round clock is currently stopped + bonustime : bool + Whether the round is currently in bonus time + """ + state: str + currentround: int + currentroundtimeleft: str + intiebreaker: bool + tiebreakernum: int + clockstopped: bool + bonustime: bool + +@dataclass +class Coordinates: + """" + A class representing the coordinates of a hit + + Attributes + ---------- + coordx : float + The x-coordinate of the hit + coordy : float + The y-coordinate of the hit + landingposx : float + The x-coordinate of the hits's landing position, + if applicable + landingposy : float + The y-coordinate of the hits's landing position, + if applicable + """ + coordx: float + coordy: float + landingposx: float + landingposy: float + +@dataclass +class Trajectorydata: + """" + A class representing data on a hit's trajectory in three-dimensional space. + + Attributes + ---------- + trajectorypolynomialx : List[int] + A list of coefficients for the polynomial representing the + x-coordinate of the hits's trajectory + trajectorypolynomialy : List[int] + A list of coefficients for the polynomial representing the + y-coordinate of the hits's trajectory + trajectorypolynomialz : List[int] + A list of coefficients for the polynomial representing the + z-coordinate of the hits's trajectory + validtimeinterval : List[int] + A list of two elements representing the start and end times for which + the polynomial coefficients are valid + measuredtimeinterval : List[int] + A list of two elements representing the start and end times of the + interval during which the hits's trajectory was measured + """ + trajectorypolynomialx: List[int] + trajectorypolynomialy: List[int] + trajectorypolynomialz: List[int] + validtimeinterval: List[int] + measuredtimeinterval: List[int] + +@dataclass +class Hitdata: + """" + A class representing data on a hit + + Attributes + ---------- + launchspeed : float + The speed at which the hit was launched + totaldistance : int + The total distance the hit traveled + launchangle : float: None + The angle at which the hit was launched, if applicable + coordinates : Coordinates: None + Coordinates for the hit + trajectorydata : Trajectorydata: None + Trajectory data for the hit + """ + totaldistance: int + launchspeed: Optional[float] = None + launchangle: Optional[float] = None + coordinates: Optional[Union[Coordinates, dict]] = None + trajectorydata: Optional[Union[Trajectorydata, dict]] = None + + def __post_init__(self): + self.coordinates = Coordinates(**self.coordinates) if self.coordinates else None + self.trajectorydata = Trajectorydata(**self.trajectorydata) if self.trajectorydata else None + +@dataclass +class Hits: + """" + A class representing a hit in the homerun derby + + Attributes + ---------- + bonustime : bool + A boolean indicating whether the hit occurred during bonus time. + homerun : bool + A boolean indicating whether the hit was a homerun. + tiebreaker : bool + A boolean indicating whether the hit occurred during a tiebreaker. + hitdata : Hitdata + An object containing the data for the hit. This can either be a + Hitdata object or a dictionary. + ishomerun : bool + A boolean indicating whether the hit was a homerun. This attribute + is a duplicate of the `homerun` attribute. + playid : str + A string containing the ID of the play in which the hit occurred. + timeremaining : str + A string indicating the amount of time remaining in the game when the + hit occurred. + isbonustime : bool + A boolean indicating whether the hit occurred during bonus time. This + attribute is a duplicate of the `bonustime` attribute. + istiebreaker : bool + A boolean indicating whether the hit occurred during a tiebreaker. + This attribute is a duplicate of the `tiebreaker` attribute. + """ + bonustime: bool + homerun: bool + tiebreaker: bool + hitdata: Union[Hitdata, dict] + ishomerun: bool + timeremaining: str + isbonustime: bool + istiebreaker: bool + playid: Optional[str] = None + + def __post_init__(self): + self.hitdata = Hitdata(**self.hitdata) + +@dataclass +class Seed: + """" + A class representing either a high or a low seed in the matchup for + the homerun derby. + + Attributes + ---------- + complete : bool + A boolean indicating whether the seed has been completed. + started : bool + A boolean indicating whether the seed has been started. + winner : bool + A boolean indicating whether the player for this seed is the winner + of the game. + player : Person + An object containing the data for the player associated with this + seed. This can either be a Person object or a dictionary. + topderbyhitdata : Hitdata + An object containing the data for the top hit in the seed. This can + either be a Hitdata object or a dictionary. + hits : Hits + An object containing the data for the hits in the seed. This can + either be a Hits object or a dictionary. + seed : int + An integer representing the seed number. + order : int + An integer representing the order in which the seed was played. + iswinner : bool + A boolean indicating whether the player for this seed is the winner + of the game. This attribute is a duplicate of the `winner` attribute. + iscomplete : bool + A boolean indicating whether the seed has been completed. This + attribute is a duplicate of the `complete` attribute. + isstarted : bool + A boolean indicating whether the seed has been started. This + attribute is a duplicate of the `started` attribute. + numhomeruns : int + An integer representing the number of homeruns hit in the seed. + """ + complete: bool + started: bool + winner: bool + player: Union[Person, dict] + topderbyhitdata: Union[Hitdata, dict] + hits: Union[Hits, dict] + seed: int + order: int + iswinner: bool + iscomplete: bool + isstarted: bool + numhomeruns: int + + def __post_init__(self): + self.player = Person(**self.player) + self.topderbyhitdata = Hitdata(**self.topderbyhitdata) + self.hits = [Hits(**hit) for hit in self.hits] + +@dataclass +class Matchup: + """" + A class representing a matchup in the homerun derby + + Attributes + ---------- + topseed : Seed + Containing the top seed in the matchup. + bottomseed : Seed + Containing the bottom seed in the matchup. + """ + topseed: Union[Seed, dict] + bottomseed: Union[Seed, dict] + + def __post_init__(self): + self.topseed = Seed(**self.topseed) + self.bottomseed = Seed(**self.bottomseed) + +@dataclass +class Round: + """" + A class representing a round in the homerun derby + + Attributes + ---------- + round : int + An integer representing the round number. + numbatters : int + An integer representing the number of batters in the round. + matchups : List[Matchup] + A list of objects containing the data for the matchups in the round. + """ + round: int + numbatters: int + matchups: List[Union[Matchup, dict]] + + def __post_init__(self): + self.matchups = [Matchup(**matchup) for matchup in self.matchups] \ No newline at end of file diff --git a/mlbstatsapi/models/homerunderby/homerunderby.py b/mlbstatsapi/models/homerunderby/homerunderby.py new file mode 100644 index 00000000..360b95ec --- /dev/null +++ b/mlbstatsapi/models/homerunderby/homerunderby.py @@ -0,0 +1,42 @@ +from typing import Union, List +from dataclasses import dataclass + +from mlbstatsapi.models.people import Person + +from .attributes import Info, Status, Round + + +@dataclass +class Homerunderby: + """ + A class representing a homerun derby + + Attributes + ---------- + info : Info + An object containing information about the game. + status : Status + An object containing the status of the game. + rounds : Round + A list of Round objects representing the rounds in the game. + players : List[Person] + A list of objects containing the data for the players in the game. + """ + info: Union[Info, dict] + status: Union[Status, dict] + rounds: List[Round] + players: List[Union[Person, dict]] + + def __post_init__(self): + self.info = Info(**self.info) + self.status = Status(**self.status) + self.rounds = [Round(**round) for round in self.rounds] + + player_list = [] + + for player in self.players: + if 'stats' in player: + player.pop('stats') + player_list.append(Person(**player)) + + self.players = player_list \ No newline at end of file diff --git a/mlbstatsapi/models/leagues/__init__.py b/mlbstatsapi/models/leagues/__init__.py new file mode 100644 index 00000000..da987204 --- /dev/null +++ b/mlbstatsapi/models/leagues/__init__.py @@ -0,0 +1,2 @@ +from .league import League +from .league import LeagueRecord diff --git a/mlbstatsapi/models/leagues/league.py b/mlbstatsapi/models/leagues/league.py new file mode 100644 index 00000000..152986a6 --- /dev/null +++ b/mlbstatsapi/models/leagues/league.py @@ -0,0 +1,105 @@ +from dataclasses import dataclass +from typing import Optional, Union + +from mlbstatsapi.models.sports import Sport +from mlbstatsapi.models.seasons import Season + + +@dataclass +class LeagueRecord: + """ + A class to represent a leaguerecord. + + Attributes + ---------- + wins : int + number of wins in leaguerecord + losses : int + number of losses in leaguerecord + ties : int + number of ties in leaguerecord + pct : str + winning pct of leaguerecord + """ + wins: int + losses: int + pct: str + ties: Optional[int] = None + + +@dataclass(repr=False) +class League: + """ + A class to represent a league. + + Attributes + ---------- + id : int + id number of the league + name : str + name of the league + link : str + link of the league + abbreviation : str + abbreviation the league + nameshort : str + Short name for the league + seasonstate : str + State of the leagues season + haswildcard : bool + Status of the leagues wildcard + hassplitseason : bool + Status of the leagues split season + numgames : int + Total number of league games + hasplayoffpoints : bool + Status of the leagues playoff points + numteams : int + Total number of team in league + numwildcardteams : int + Total number of wildcard teams in league + seasondateinfo : Season + Season obj + season : str + League season + orgcode : str + Leagues orginization code + conferencesinuse : bool + Status of the in use conferences of the league + divisionsinuse : bool + Status of leagues divisions in use + sport : Sport + What 'sport' this league is a part of + sortorder : int + League sort order + active : bool + Status on the activity of the league + """ + id: int + link: str + name: Optional[str] = None + abbreviation: Optional[str] = None + nameshort: Optional[str] = None + seasonstate: Optional[str] = None + haswildcard: Optional[bool] = None + hassplitseason: Optional[bool] = None + numgames: Optional[int] = None + hasplayoffpoints: Optional[bool] = None + numteams: Optional[int] = None + numwildcardteams: Optional[int] = None + seasondateinfo: Optional[Union[Season, dict]] = None + season: Optional[str] = None + orgcode: Optional[str] = None + conferencesinuse: Optional[bool] = None + divisionsinuse: Optional[bool] = None + sport: Optional[Union[Sport, dict]] = None + sortorder: Optional[int] = None + active: Optional[bool] = None + + def __post_init__(self): + self.seasondateinfo = Season(**self.seasondateinfo) if self.seasondateinfo else self.seasondateinfo + self.sport = Sport(**self.sport) if self.sport else self.sport + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/people/__init__.py b/mlbstatsapi/models/people/__init__.py new file mode 100644 index 00000000..14b3469b --- /dev/null +++ b/mlbstatsapi/models/people/__init__.py @@ -0,0 +1,2 @@ +from .attributes import BatSide, Position, PitchHand, Status +from .people import Player, Coach, Person, Batter, Pitcher, DraftPick diff --git a/mlbstatsapi/models/people/attributes.py b/mlbstatsapi/models/people/attributes.py new file mode 100644 index 00000000..778753e2 --- /dev/null +++ b/mlbstatsapi/models/people/attributes.py @@ -0,0 +1,113 @@ +from dataclasses import dataclass + + +@dataclass +class BatSide: + """ + A class to represent a BatSide. + + Attributes + ---------- + code : str + code number of the BatSide + descritpion: str + description of the BatSide + """ + code: str + description: str + + +@dataclass +class PitchHand: + """ + A class to represent a PitchHand. + + Attributes + ---------- + code : str + code number of the PitchHand + descritpion: str + description of the PitchHand + """ + code: str + description: str + + +@dataclass +class Position: + """ + A class to represent a batside. + + Attributes + ---------- + code: str + code number of the Position + name: str + the name of the Position + type: str + the type of the Position + abbreviation: str + the abbreviation of the Position + """ + code: str + name: str + type: str + abbreviation: str + + +@dataclass +class Status: + """ + A dataclass to hold player status + + Attributes + ---------- + code: str + code of the player + description: str + description of the status + """ + code: str + description: str + +@dataclass +class Home: + """ + A home is a where a draft player is from + + Attributes + ---------- + city : str + The city where the player is from. + state : str + The state where the player is from. + country : str + The country where the player is from. + """ + city: str + state: str + country: str + +@dataclass +class School: + """ + Represents the school the draft player is from. + + Attributes + ---------- + name : str + The name of the school. + schoolclass : str + The class the student is in. + city : str + The city where the school is located. + country : str + The country where the school is located. + state : str + The state where the school is located. + """ + name: str + schoolclass: str + city: str + country: str + state: str \ No newline at end of file diff --git a/mlbstatsapi/models/people/people.py b/mlbstatsapi/models/people/people.py new file mode 100644 index 00000000..ed01f1d6 --- /dev/null +++ b/mlbstatsapi/models/people/people.py @@ -0,0 +1,294 @@ +from dataclasses import dataclass, field, InitVar +from typing import Union, Dict, Any, Optional + +from .attributes import BatSide, Position, PitchHand, Status, Home, School +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.data import CodeDesc +# from mlbstatsapi.models.drafts import Home, College + + +@dataclass(repr=False) +class Person: + """ + A class to represent a Person. + + Attributes + ---------- + id : int + id number of the person + full_name : str + full_name of the person + link : str + Api link to person + primaryposition : Position + PrimaryPosition of the Person + pitchhand : str + PitchHand of the Person + batside : str + BatSide of the Person + fullname : str + full name of the Person + firstname : str + First name of the Person + lastname : str + Last name of the Person + primarynumber : str + Primary number of the Person + birthdate : str + Birth date of the Person + currentteam : str + The current Team of the Person + currentage : str + The current age of the Person + birthcity : str + The birthcity of the Person + birthstateprovince : str + The province of the birth state + height : str + The height of the Person + weight : str + The weight of the Person + active : str + The active status of the Person + usename : str + The use name of the Person + middlename : str + The middle name of the Person + boxscorename : str + The box score name of the Person + nickname : str + The nickname of the Person + draftyear : int + The draft year of the Person + mlbdebutdate : str + The MLB debut date of the Person + namefirstlast : str + The first and last name of the Person + nameslug : str + The name slug of the Person + firstlastname : str + The first and last name of the Person + lastfirstname : str + The last and first name of the Person + lastinitname : str + The last init name of the Person + initlastname : str + The init last name of the Person + fullfmlname : str + The full fml name of the Person + fulllfmname : str + The full lfm name of the Person + uselastname : str + The last name of the + birthcountry : str + The birth country of the Person + pronunciation : str + The pronuciation of the Person's name + strikezonetop : float + The strike zone top of the Person + strikezonebottom : float + The strike zone bottom of the Person + nametitle : str + The name title of the Person + gender : str + The gender of the Person + isplayer : bool + The player status of the Person + isverified : bool + The verification of the Person + namematrilineal : str + The name matrilineal of the Person + deathdate : str + The death date of the Person + deathcity : str + The death city of the Person + deathcountry : str + The death country of the Person + lastplayeddate : str + The last played date of the Person + namesuffix : str + The namesuffix of the Person + """ + + id: int + link: str + primaryposition: Union[Position, Dict[str, Any]] = field(default_factory=dict) + pitchhand: Union[PitchHand, Dict[str, Any]] = field(default_factory=dict) + batside: Union[BatSide, Dict[str, Any]] = field(default_factory=dict) + fullname: Optional[str] = None + firstname: Optional[str] = None + lastname: Optional[str] = None + primarynumber: Optional[str] = None + birthdate: Optional[str] = None + currentteam: Optional[str] = None + currentage: Optional[str] = None + birthcity: Optional[str] = None + birthstateprovince: Optional[str] = None + height: Optional[str] = None + weight: Optional[int] = None + active: Optional[bool] = None + usename: Optional[str] = None + middlename: Optional[str] = None + boxscorename: Optional[str] = None + nickname: Optional[str] = None + draftyear: Optional[int] = None + mlbdebutdate: Optional[str] = None + namefirstlast: Optional[str] = None + nameslug: Optional[str] = None + firstlastname: Optional[str] = None + lastfirstname: Optional[str] = None + lastinitname: Optional[str] = None + initlastname: Optional[str] = None + fullfmlname: Optional[str] = None + fulllfmname: Optional[str] = None + birthcountry: Optional[str] = None + pronunciation: Optional[str] = None + strikezonetop: Optional[float] = None + strikezonebottom: Optional[float] = None + nametitle: Optional[str] = None + gender: Optional[str] = None + isplayer: Optional[bool] = None + isverified: Optional[bool] = None + namematrilineal: Optional[str] = None + deathdate: Optional[str] = None + deathcity: Optional[str] = None + deathcountry: Optional[str] = None + deathstateprovince: Optional[str] = None + lastplayeddate: Optional[str] = None + uselastname: Optional[str] = None + namesuffix: Optional[str] = None + + def __post_init__(self): + self.primaryposition = Position(**self.primaryposition) if self.primaryposition else self.primaryposition + self.pitchhand = PitchHand(**self.pitchhand) if self.pitchhand else self.pitchhand + self.batside = BatSide(**self.batside) if self.batside else self.batside + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(kw_only=True, repr=False) +class Player(Person): + """ + A class to represent a Player. + + Attributes + ---------- + jerseynumber : str + id number of the person + status : + Status of the player + parentteamid : int + parent team id + """ + jerseynumber: str + parentteamid: int + position: InitVar[dict] + status: Union[Status, dict] + + def __post_init__(self, position: dict): + self.primaryposition = Position(**position) + + +@dataclass(kw_only=True, repr=False) +class Coach(Person): + """ + A class to represent a Player. + + Attributes + ---------- + jerseynumber : str + id number of the person + job : str + job of the coach + jobid : str + job id of the coach + title : str + title of the coach + parentteamid : int + """ + jerseynumber: str + job: str + jobid: str + title: str + +@dataclass(kw_only=True) +class Batter(Person): + """ + A class to represent a Batter. + """ + pass + + +@dataclass(kw_only=True) +class Pitcher(Person): + """ + A class to represent a Pitcher + """ + pass + + +@dataclass(kw_only=True) +class DraftPick(Person): + """ + Represents a pick made in the MLB draft. + + Attributes + ---------- + bisplayerid : int + The unique identifier of the player associated with this draft pick. + pickround : str + The round of the draft in which this pick was made. + picknumber : int + The number of the pick in the round. + roundpicknumber : int + The number of the pick overall in the draft. + rank : int + The rank of the player among all players eligible for the draft. + pickvalue : str + The value of the pick, if known. + signingbonus : str + The signing bonus associated with this pick, if known. + home : Home + Information about the player's home location. + scoutingreport : str + A scouting report on the player's abilities. + school : School + Information about the player's school or college. + blurb : str + A brief summary of the player's background and accomplishments. + headshotlink : str + A link to a headshot image of the player. + team : Team or dict + The team that made this draft pick. + drafttype : CodeDesc + Information about the type of draft in which this pick was made. + isdrafted : bool + Whether or not the player associated with this pick has been drafted. + ispass : bool + Whether or not the team passed on making a pick in this round. + year : str + The year in which the draft took place. + """ + + bisplayerid: Optional[int] = None + pickround: str + picknumber: int + roundpicknumber: int + rank: Optional[int] = None + pickvalue: Optional[str] = None + signingbonus: Optional[str] = None + home: Union[Home , dict] + scoutingreport: Optional[str] = None + school: Union[School , dict] + blurb: Optional[str] = None + headshotlink: str + team: Union[Team, dict] + drafttype: Union[CodeDesc, dict] + isdrafted: bool + ispass: bool + year: str + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/schedules/__init__.py b/mlbstatsapi/models/schedules/__init__.py new file mode 100644 index 00000000..cd8723d8 --- /dev/null +++ b/mlbstatsapi/models/schedules/__init__.py @@ -0,0 +1,2 @@ +from .schedule import Schedule +from .attributes import ScheduleGames \ No newline at end of file diff --git a/mlbstatsapi/models/schedules/attributes.py b/mlbstatsapi/models/schedules/attributes.py new file mode 100644 index 00000000..d8f6d735 --- /dev/null +++ b/mlbstatsapi/models/schedules/attributes.py @@ -0,0 +1,218 @@ +from dataclasses import dataclass, field +from typing import Optional, Union, List +from mlbstatsapi.models.venues import Venue +from mlbstatsapi.models.game.gamedata import GameStatus +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.leagues import LeagueRecord + + +@dataclass(repr=False) +class ScheduleGameTeam: + """ + A class to represent the sheduled games teams shedule information in a Scheduled Dates game. + + Attributes + ---------- + leaguerecord : LeagueRecord + League record for this team + score : int + Current score for this team in this game + team : Team + Team info for this game + iswinner : bool + If this team is the winner of this game + splitsquad : bool + Split squad + seriesnumber : int + Series number + """ + leaguerecord: Union[LeagueRecord, dict] + team: Union[Team, dict] + splitsquad: bool + seriesnumber: Optional[int] = None + score: Optional[int] = None + iswinner: Optional[bool] = False + + def __post_init__(self): + self.leaguerecord = LeagueRecord(**self.leaguerecord) + self.team = Team(**self.team) + + def __repr__(self): + return f'ScheduleGameTeam(gamepk={self.leaguerecord}, team={self.team})' + +@dataclass +class ScheduleHomeAndAway: + """ + A class to represent both away and home teams in a Schedules Dates game. + + Attributes + ---------- + home : ScheduleGameTeam + Home team info for this game + away : ScheduleGameTeam + Away team info for this game + """ + home: Union[ScheduleGameTeam, dict] + away: Union[ScheduleGameTeam, dict] + + def __post_init__(self): + self.home = ScheduleGameTeam(**self.home) + self.away = ScheduleGameTeam(**self.away) + + +@dataclass(repr=False) +class ScheduleGames: + """ + A class to represent a Game in a Schedules Dates. + + Attributes + ---------- + gamepk : int + The games id number + link : str + The link for this game + gametype : str + This games game type + season : str + The season this game takes place in + gamedate : str + The date for this game + officialdate : str + The official date for this game + status : GameStatus + The status of this game + teams : ScheduleHomeAndAway + Holds teams and thier info for this game + venue : Venue + The venue this game takes place in + content : dict + Content for this game. Havent found a populated reference yet. Stays as dict + istie : bool + If this game is a tie + gamenumber : int + Game number for this game + publicfacing : bool + Is this game public facing + doubleheader : str + The double header status for this game, "n','y'? + gamedaytype : str + The type of gameday for this game + tiebreaker : str + Tie breaker for this game, 'n','y'? + calendareventid : str + Calender event Id for this game + seasondisplay : str + Displayed season for this game + daynight : str + Day or night game as a string, 'am','pm'? + scheduledinnings : int + Number of scheduled inning for the game + reversehomeawaystatus : bool + If reverse home and away? + inningbreaklength : int + Length of break between innings + gamesinseries : int + Number of games in current series + seriesgamenumber : int + Game number in the current series + seriesdescription : str + Description of this current series + recordsource : str + Record source + ifnecessary : str + If necessary + ifnecessarydescription : str + If necessary description + rescheduledate : str = None + If game is rescheduled, this is the rescheduled date + reschedulegamedate : str = None + rescheduled game date + rescheduledfrom : str = None + rescheduled from + rescheduledfromdate : str = None + rescheduled from date + istie : bool = None + Is tie + """ + gamepk: int + link: str + gametype: str + season: str + gamedate: str + officialdate: str + venue: Venue + content: dict + gamenumber: int + publicfacing: bool + doubleheader: str + gamedaytype: str + tiebreaker: str + calendareventid: str + seasondisplay: str + daynight: str + scheduledinnings: int + reversehomeawaystatus: bool + seriesdescription: str + recordsource: str + ifnecessary: str + ifnecessarydescription: str + status: Union[GameStatus, dict] = field(default_factory=dict) + teams: Union[ScheduleHomeAndAway, dict] = field(default_factory=dict) + description: Optional[str] = None + inningbreaklength: Optional[int] = None + rescheduledate: Optional[str] = None + reschedulegamedate: Optional[str] = None + rescheduledfrom: Optional[str] = None + rescheduledfromdate: Optional[str] = None + istie: Optional[bool] = None + resumedate: Optional[str] = None + resumegamedate: Optional[str] = None + resumedfrom: Optional[str] = None + resumedfromdate: Optional[str] = None + seriesgamenumber: Optional[int] = None + gamesinseries: Optional[int] = None + + def __post_init__(self): + self.status = GameStatus(**self.status) if self.status else self.status + self.teams = ScheduleHomeAndAway(**self.teams) if self.teams else self.teams + self.venue = Venue(**self.venue) if self.venue else self.venue + + def __repr__(self): + return f'ScheduleGames(gamepk={self.gamepk}, link={self.link})' + +@dataclass(repr=False) +class ScheduleDates: + """ + A class to represent a Schedules Dates. + + Attributes + ---------- + date : str + Date for the group of games + totalitems : int + Total amount of items for this date + totalevents : int + The number of events for this date + totalgames : int + The number of games for this date + totalgamesinprogress : int + The number of games that are currently in progress for this date + games : List[ScheduleGames] + A list of games for this date + events : list + A list of events for this date. Need to handle this but cant find a populated + reference for this object. It stays as a list for now. + """ + date: str + totalitems: int + totalevents: int + totalgames: int + totalgamesinprogress: int + events: List + games: List[ScheduleGames] = field(default_factory=list) + + def __post_init__(self): + self.games = [ScheduleGames(**game) for game in self.games ] if self.games else self.games + + def __repr__(self): + return f'ScheduleDates(date={self.date}, totalgames={self.totalgames})' \ No newline at end of file diff --git a/mlbstatsapi/models/schedules/schedule.py b/mlbstatsapi/models/schedules/schedule.py new file mode 100644 index 00000000..d1268cd1 --- /dev/null +++ b/mlbstatsapi/models/schedules/schedule.py @@ -0,0 +1,38 @@ +from typing import List +from dataclasses import dataclass, field + +from .attributes import ScheduleDates + + +@dataclass(repr=False) +class Schedule: + """ + A class to represent a Schedule. + + Attributes + ---------- + copyright : str + Copyright + totalitems : int + Total items in schedule + totalevents : int + Total events in schedule + totalgames : int + Total games in schedule + totalgamesinprogress : int + Total games in progress in schedule + dates : ScheduleDates + List of dates with games in schedule + """ + totalitems: int + totalevents: int + totalgames: int + totalgamesinprogress: int + dates: List[ScheduleDates] = field(default_factory=list) + + def __post_init__(self): + self.dates = [ScheduleDates(**date) for date in self.dates if self.dates] + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/seasons/__init__.py b/mlbstatsapi/models/seasons/__init__.py new file mode 100644 index 00000000..4a91fc8b --- /dev/null +++ b/mlbstatsapi/models/seasons/__init__.py @@ -0,0 +1 @@ +from .season import Season \ No newline at end of file diff --git a/mlbstatsapi/models/seasons/season.py b/mlbstatsapi/models/seasons/season.py new file mode 100644 index 00000000..c709c543 --- /dev/null +++ b/mlbstatsapi/models/seasons/season.py @@ -0,0 +1,80 @@ +from typing import Optional +from dataclasses import dataclass + + +@dataclass(repr=False) +class Season: + """ + This class represents a season object + + Attributes + ---------- + seasonid : str + season id + haswildcard : bool + wild card status + preseasonstartdate : str + pre-season start date + preseasonenddate : str + pre-season end date + seasonstartdate : str + season start date + springstartdate : str + spring start date + springenddate : str + spring end date + regularseasonstartdate : str + regular season start date + lastdate1sthalf : str + last date 1st half + allstardate : str + all star date + firstdate2ndhalf : str + first date 2nd half + regularseasonenddate : str + regular season end date + postseasonstartdate : str + post season start date + postseasonenddate : str + post season end date + seasonenddate : str + season end date + offseasonstartdate : str + off season start date + offseasonenddate : str + off season end date + seasonlevelgamedaytype : str + season level game day type + gamelevelgamedaytype : str + game level game day type + qualifierplateappearances : float + qualifier plate appearances + qualifieroutspitched : int + qualifier outs pitched + """ + + seasonid: str + haswildcard: Optional[bool] = None + preseasonstartdate: Optional[str] = None + preseasonenddate: Optional[str] = None + seasonstartdate: Optional[str] = None + springstartdate: Optional[str] = None + springenddate: Optional[str] = None + regularseasonstartdate: Optional[str] = None + lastdate1sthalf: Optional[str] = None + allstardate: Optional[str] = None + firstdate2ndhalf: Optional[str] = None + regularseasonenddate: Optional[str] = None + postseasonstartdate: Optional[str] = None + postseasonenddate: Optional[str] = None + seasonenddate: Optional[str] = None + offseasonstartdate: Optional[str] = None + offseasonenddate: Optional[str] = None + seasonlevelgamedaytype: Optional[str] = None + gamelevelgamedaytype: Optional[str] = None + qualifierplateappearances: Optional[float] = None + qualifieroutspitched: Optional[int] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/sports/__init__.py b/mlbstatsapi/models/sports/__init__.py new file mode 100644 index 00000000..7b4597dd --- /dev/null +++ b/mlbstatsapi/models/sports/__init__.py @@ -0,0 +1 @@ +from .sport import Sport \ No newline at end of file diff --git a/mlbstatsapi/models/sports/sport.py b/mlbstatsapi/models/sports/sport.py new file mode 100644 index 00000000..92c200de --- /dev/null +++ b/mlbstatsapi/models/sports/sport.py @@ -0,0 +1,37 @@ +from typing import Optional +from dataclasses import dataclass, InitVar + + +@dataclass(repr=False) +class Sport: + """ + A class to represent a sport. + + Attributes + ---------- + id : int + id number of the sport + link : str + link of the sport + name : str + name the sport + code : str + Sport code + abbreviation : str + Abbreviation for the sport + sortorder : int + Some sort of sorting order + activestatus : bool + Is the sport active + """ + id: int + link: str + name: Optional[str] = None + code: Optional[str] = None + abbreviation: Optional[str] = None + sortorder: Optional[int] = None + activestatus: Optional[bool] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/standings/__init__.py b/mlbstatsapi/models/standings/__init__.py new file mode 100644 index 00000000..554de97a --- /dev/null +++ b/mlbstatsapi/models/standings/__init__.py @@ -0,0 +1,2 @@ +from .standings import Standings +from .attributes import Teamrecords \ No newline at end of file diff --git a/mlbstatsapi/models/standings/attributes.py b/mlbstatsapi/models/standings/attributes.py new file mode 100644 index 00000000..1b23f3c2 --- /dev/null +++ b/mlbstatsapi/models/standings/attributes.py @@ -0,0 +1,140 @@ +from typing import Union, Optional +from dataclasses import dataclass + +from mlbstatsapi.models.teams import Team, TeamRecord + +@dataclass +class Streak: + """ + + + Attributes: + ___________ + streaktype : str + Steak type + streaknumber : int + Streak number + streakcode : str + Steak code + """ + streaktype: str + streaknumber: int + streakcode: str + +@dataclass(repr=False) +class Teamrecords(TeamRecord): + """ + Team Records + + Attributes: + ___________ + team: Team + The team for which the data belongs to. Can be an instance of the Team class or a dictionary with relevant information about the team. + season: int + The season for which the data belongs to. + streak: Streak + The streak of the team. Can be an instance of the Streak class or a dictionary with relevant information about the streak. + divisionrank: str + The rank of the team in their division. + leaguerank: str + The rank of the team in their league. + sportrank: str + The rank of the team in their sport. + gamesplayed: int + The number of games played by the team. + gamesback: str + The number of games behind the leader in the division. + wildcardgamesback: str + The number of games behind the leader in the wild card race. + leaguegamesback: str + The number of games behind the leader in the league. + springleaguegamesback: str + The number of games behind the leader in the spring league. + sportgamesback: str + The number of games behind the leader in the sport. + divisiongamesback: str + The number of games behind the leader in the division. + conferencegamesback: str + The number of games behind the leader in the conference. + leaguerecord: OverallleagueRecord + The overall league record of the team. Can be an instance of the OverallleagueRecord class or a dictionary with relevant information about the record. + lastupdated: str + The date when the data was last updated. + records: Records + The records of the team. Can be an instance of the Records class or a dictionary with relevant information about the records. + runsallowed: int + The number of runs allowed by the team. + runsscored: int + The number of runs scored by the team. + divisionchamp: bool + A flag indicating whether the team is the division champion. + divisionleader: bool + A flag indicating whether the team is the leader in their division. + haswildcard: bool + A flag indicating whether the team has a wild card spot. + clinched: bool + A flag indicating whether the team has clinched a spot in the playoffs. + eliminationnumber: str + The number of games the team needs to win or the number of games their opponents need to lose in order to be eliminated from playoff contention. + wildcardeliminationnumber: str + The number of games the team needs to win or the number of games their opponents need to lose in order to be eliminated from wild card contention. + wins: int + The number of wins of the team. + losses: int + The number of losses of the team. + rundifferential: int + The run differential of the team (runs scored minus runs allowed). + winningpercentage: str + The winning percentage of the team. + wildcardrank: str + The rank of the team in the wild card race. + wildcardleader: bool + A flag indicating whether the team is the leader in the wild card race. + magicnumber: str + The number of games the team needs to win or the number of games their opponents need to lose in order to clinch a spot in the playoffs. + clinchindicator: Optional + + """ + team: Union[Team, dict] + season: int + streak: Union[Streak, dict] + divisionrank: str + leaguerank: str + sportrank: str + # gamesplayed: int + gamesback: str + # wildcardgamesback: str + # leaguegamesback: str + # springleaguegamesback: str + # sportgamesback: str + # divisiongamesback: str + # conferencegamesback: str + # leaguerecord: Union[OverallleagueRecord, dict] + lastupdated: str + # records: Union[Records, dict] + runsallowed: int + runsscored: int + divisionchamp: bool + # divisionleader: bool + haswildcard: bool + clinched: bool + eliminationnumber: str + wildcardeliminationnumber: str + # wins: int + # losses: int + rundifferential: int + # winningpercentage: str + wildcardrank: Optional[str] = None + wildcardleader: Optional[bool] = None + magicnumber: Optional[str] = None + clinchindicator: Optional[str] = None + + def __post_init__(self): + self.team = Team(**self.team) + self.streak = Streak(**self.streak) + # self.leaguerecord = OverallleagueRecord(**self.leaguerecord) + # self.records = Records(**self.records) + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/standings/standings.py b/mlbstatsapi/models/standings/standings.py new file mode 100644 index 00000000..6426dea2 --- /dev/null +++ b/mlbstatsapi/models/standings/standings.py @@ -0,0 +1,47 @@ +from typing import Union, Optional, List +from dataclasses import dataclass + +from mlbstatsapi.models.leagues import League +from mlbstatsapi.models.divisions import Division +from mlbstatsapi.models.sports import Sport + +from .attributes import Teamrecords + + +@dataclass(repr=False) +class Standings: + """ + A class representing a standings in a league. + + + Attributes + ---------- + standingstype : str + A string indicating the type of standings. + league : league + An object containing information about the league. + division : Division + An object containing information about the division + sport : Sport + An object containing information about the sport. + lastupdated : str + A string indicating the last time the standing was updated. + teamrecords : List[Teamrecords] + A list of Teamrecord objects containing the data for the teams standings. + """ + standingstype: str + league: Union[League, dict] + division: Union[Sport, dict] + lastupdated: str + teamrecords: List[Union[Teamrecords, dict]] + sport: Optional[Union[Sport, dict]] = None + + def __post_init__(self): + self.league = League(**self.league) + self.division = Division(**self.division) + self.sport = Sport(**self.sport) if self.sport else None + self.teamrecords = [Teamrecords(**teamrecord) for teamrecord in self.teamrecords] + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/stats/__init__.py b/mlbstatsapi/models/stats/__init__.py new file mode 100644 index 00000000..533a5ead --- /dev/null +++ b/mlbstatsapi/models/stats/__init__.py @@ -0,0 +1,129 @@ +from .stats import ( + Stat, + Split, + PitchArsenal, + HotColdZones, + ZoneCodes, + SprayCharts, + Sabermetrics, + ExpectedStatistics, + +) + + +from .hitting import ( + SimpleHittingSplit, + AdvancedHittingSplit, + HittingSabermetrics, + OpponentsFacedHitting, + HittingGameLog, + HittingPlayLog, + HittingSeason, + HittingCareer, + HittingHomeAndAway, + HittingWinLoss, + HittingYearByYear, + HittingDayOfWeek, + HittingByMonth, + HittingDateRangeAdvanced, + HittingDateRange, + HittingLastXGames, + HittingCareerAdvanced, + HittingPitchLog, + HittingDayOfWeekPlayoffs, + HittingByMonthPlayoffs, + HittingHomeAndAwayPlayoffs, + HittingWinLossPlayoffs, + HittingExpectedStatistics, + HittingSeasonAdvanced, + HittingSingleSeason, + HittingVsTeam, + HittingVsTeam5Y, + HittingVsTeamTotal, +) + +from .pitching import ( + SimplePitchingSplit, + AdvancedPitchingSplit, + PitchingSeasonAdvanced, + PitchingYearByYear, + PitchingSeason, + PitchingSabermetrics, + PitchingCareer, + PitchingCareerAdvanced, + PitchingLog, + PitchingGameLog, + PitchingHomeAndAway, + PitchingOpponentsFaced, + PitchingByDayOfWeek, + PitchingWinLoss, + PitchingYearByYearAdvanced, + PitchingYearByYearPlayoffs, + PitchingByDateRangeAdvanced, + PitchingByMonth, + PitchingByMonthPlayoffs, + PitchingByDayOfWeek, + PitchingByDayOfWeekPlayOffs, + PitchingHomeAndAwayPlayoffs, + PitchingWinLossPlayoffs, + PitchingRankings, + PitchingPlayLog, + PitchingSingleSeasonAdvanced, + PitchingVsTeam, + PitchingVsTeam5Y, + PitchingVsTeamTotal, + +) + +from .catching import ( + CatchingYearByYear, + CatchingSingleSeason, + CatchingYearByYearPlayoffs, + CatchingSeason, + CatchingYearByYearPlayoffs, + CatchingYearByYear, + CatchingProjected, + CatchingCareer, + CatchingCareerRegularSeason, + CatchingGameLog, + CatchingLastXGames, + CatchingByDateRange, + CatchingByDayOfWeek, + CatchingHomeAndAway, + CatchingWinLoss, +) + +from .fielding import ( + SimpleFieldingSplit, + FieldingGameLog, + FieldingLastXGames, + FieldingByMonthPlayoffs, + FieldingByMonth, + FieldingByDateRangeAdvanced, + FieldingByDayOfWeek, + FieldingWinLossPlayoffs, + FieldingWinLoss, + FieldingYearByYearPlayoffs, + FieldingYearByYearAdvanced, + FieldingYearByYear, + FieldingHomeAndAwayPlayoffs, + FieldingHomeAndAway, + FieldingCareerPlayoffs, + FieldingCareer, + FieldingSeason, + FieldingSeasonAdvanced, + FieldingSingleSeasonAdvanced, + FieldingSingleSeason, +) + +from .game import ( + SeasonGame, + CareerGame, + CareerPlayoffsGame, + CareerRegularSeasonGame + +) + +from .running import ( + RunningOpponentsFaced +) \ No newline at end of file diff --git a/mlbstatsapi/models/stats/catching.py b/mlbstatsapi/models/stats/catching.py new file mode 100644 index 00000000..29820d8f --- /dev/null +++ b/mlbstatsapi/models/stats/catching.py @@ -0,0 +1,306 @@ +from dataclasses import dataclass +from typing import Optional, Union + +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.game import Game + +from .stats import Split + + +@dataclass(repr=False) +class SimpleCatchingSplit: + """ + A class to represent a simple catching split + + gamesplayed : int + The number of games played by the catcher + runs : int + The number of runs given up while catching. + homeruns : int + The number of home runs given up while catching. + strikeouts : int + The number of strike outs while catching. + baseonballs : int + The number of base on balls while catching. + intentionalwalks : int + The number of intentional walks while catching. + hits : int + The number of hits while catching. + hitbypitch : int + The number of batters hit by a pitch while catching. + avg : str + The batting average while catching. + atbats : int + The number of at bats while catching. + obp : str + The on base percentage while catching. + slg : str + The slugging percentage while catching. + ops : str + The on-base slugging while catching. + see also: https://www.mlb.com/glossary/standard-stats/on-base-plus-slugging + caughtstealing : int + The number of runners caught stealing by the catcher. + stolenbases : int + The number of stolen bases while catching. + stolenbasepercentage : str + The stolen base percentage against the catcher. + earnedruns : int + The earned run amount against the catcher. + battersfaced : int + The number of batters faced while catching. + gamespitched : int + The number of games pitched while catching. + hitbatsmen : int + The number of batters hit by pitches while catching. + wildpitches : int + The number of wild pitches while catching. + pickoffs : int + The number of pick offs while catching. + totalbases : int + The total number of bases + strikeoutwalkratio : str + The strike out to walk ratio while catching. + catchersinterference : int + The number of times catcher interference commited + sacbunts : int + The number of sac bunts performed while catching. + sacflies : int + The number of sac flies while catching. + passedball : int + The number of passed balls while catching. + """ + gamesplayed: Optional[int] = None + runs: Optional[int] = None + homeruns: Optional[int] = None + strikeouts: Optional[int] = None + baseonballs: Optional[int] = None + intentionalwalks: Optional[int] = None + hits: Optional[int] = None + hitbypitch: Optional[int] = None + avg: Optional[str] = None + atbats: Optional[int] = None + obp: Optional[str] = None + slg: Optional[str] = None + ops: Optional[str] = None + caughtstealing: Optional[int] = None + stolenbases: Optional[int] = None + stolenbasepercentage: Optional[str] = None + earnedruns: Optional[int] = None + battersfaced: Optional[int] = None + gamespitched: Optional[int] = None + hitbatsmen: Optional[int] = None + wildpitches: Optional[int] = None + pickoffs: Optional[int] = None + totalbases: Optional[int] = None + strikeoutwalkratio: Optional[str] = None + catchersinterference: Optional[int] = None + sacbunts: Optional[int] = None + sacflies: Optional[int] = None + passedball: Optional[int] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(kw_only=True, repr=False) +class CatchingSeason(Split): + """ + A class to represent a catching season statistic + + Attributes + ---------- + """ + _stat = ['season'] + stat: Union[SimpleCatchingSplit, dict] + + def __post_init__(self): + self.stat = SimpleCatchingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class CatchingSingleSeason(Split): + """ + A class to represent a catching statsSingleSeason statistic + + Attributes + ---------- + """ + _stat = ['statsSingleSeason'] + stat: Union[SimpleCatchingSplit, dict] + + def __post_init__(self): + self.stat = SimpleCatchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class CatchingYearByYearPlayoffs(Split): + """ + A class to represent a catching yearByYearPlayoffs statistic + + Attributes + ---------- + """ + _stat = ['yearByYearPlayoffs'] + stat: Union[SimpleCatchingSplit, dict] + + def __post_init__(self): + self.stat = SimpleCatchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class CatchingYearByYear(Split): + """ + A class to represent a catching yearByYear statistic + + Attributes + ---------- + """ + _stat = ['yearByYear'] + stat: Union[SimpleCatchingSplit, dict] + + def __post_init__(self): + self.stat = SimpleCatchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class CatchingProjected(Split): + """ + A class to represent a catching projectedRos statistic + + Attributes + ---------- + """ + _stat = ['projectedRos'] + stat: Union[SimpleCatchingSplit, dict] + + def __post_init__(self): + self.stat = SimpleCatchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class CatchingCareer(Split): + """ + A class to represent a catching career statistic + + Attributes + ---------- + """ + _stat = ['career'] + stat: Union[SimpleCatchingSplit, dict] + + def __post_init__(self): + self.stat = SimpleCatchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class CatchingCareerRegularSeason(Split): + """ + A class to represent a catching careerRegularSeason statistic + + Attributes + ---------- + """ + _stat = ['careerRegularSeason'] + stat: Union[SimpleCatchingSplit, dict] + + def __post_init__(self): + self.stat = SimpleCatchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class CatchingGameLog(Split): + """ + A class to represent a catching gameLog statistic + + Attributes + ---------- + """ + _stat = ['gameLog'] + ishome: bool + iswin: bool + date: str + game: Union[Game, dict] + opponent: Union[Team, dict] + + def __post_init__(self): + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class CatchingLastXGames(Split): + """ + A class to represent a catching lastXGames statistic + + Attributes + ---------- + """ + _stat = ['lastXGames'] + stat: Union[SimpleCatchingSplit, dict] + + def __post_init__(self): + self.stat = SimpleCatchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class CatchingByDateRange(Split): + """ + A class to represent a catching byDateRange statistic + + Attributes + ---------- + """ + _stat = ['byDateRange'] + stat: Union[SimpleCatchingSplit, dict] + + def __post_init__(self): + self.stat = SimpleCatchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class CatchingByDayOfWeek(Split): + """ + A class to represent a catching byDayOfWeek statistic + + Attributes + ---------- + """ + _stat = ['byDayOfWeek'] + dayofweek: int + stat: Union[SimpleCatchingSplit, dict] + + def __post_init__(self): + self.stat = SimpleCatchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class CatchingHomeAndAway(Split): + """ + A class to represent a catching homeAndAway statistic + + Attributes + ---------- + """ + _stat = ['homeAndAway'] + ishome: bool + stat: Union[SimpleCatchingSplit, dict] + + def __post_init__(self): + self.stat = SimpleCatchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class CatchingWinLoss(Split): + """ + A class to represent a catching winLoss statistic + + Attributes + ---------- + """ + _stat = ['winLoss'] + iswin: bool + stat: Union[SimpleCatchingSplit, dict] + + def __post_init__(self): + self.stat = SimpleCatchingSplit(**self.stat) + super().__post_init__() diff --git a/mlbstatsapi/models/stats/fielding.py b/mlbstatsapi/models/stats/fielding.py new file mode 100644 index 00000000..97606de7 --- /dev/null +++ b/mlbstatsapi/models/stats/fielding.py @@ -0,0 +1,455 @@ +from dataclasses import dataclass, field +from typing import Optional, Union + +from mlbstatsapi.models.people import Position +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.game import Game + +from .stats import Stat, Split + + +@dataclass(repr=False) +class SimpleFieldingSplit: + """ + A class to represent a simple fielding split + + gamesplayed: int + The number of games played + gamesstarted: int + The number of games started + caughtstealing: int + The number of runners caught stealing + stolenbases: int + The number of stolen bases + stolenbasepercentage: str + The stolen base percentage + assists: int + The number of assists + putouts: int + The number of put outs + errors: int + The number of errors commited + chances: int + The number of chances + fielding: str + The number of fielding + rangefactorpergame: str + Range rating per game. + see also: https://www.mlb.com/glossary/advanced-stats/range-factor + rangefactorper9inn: str + Range factor per 9 innings. + see also: https://www.mlb.com/glossary/advanced-stats/range-factor + innings: str + The number of innings played. + games: int + The number of games played. + passedball: int + The number of passed balls. + doubleplays: int + The number of double plays. + tripleplays: int + The number of triple plays. + catcherera: str + The catcher ERA of the fielding stat. + catchersinterference: int + The number of times catchers interfence was commited. + wildpitches: int + The number of wild pitches. + throwingerrors: int + The number of throwing errors. + pickoffs: int + The number of pick offs. + """ + position: Optional[Union[Position, dict]] = field(default_factory=dict) + gamesplayed: Optional[int] = None + gamesstarted: Optional[int] = None + caughtstealing: Optional[int] = None + stolenbases: Optional[int] = None + stolenbasepercentage: Optional[str] = None + assists: Optional[int] = None + putouts: Optional[int] = None + errors: Optional[int] = None + chances: Optional[int] = None + fielding: Optional[str] = None + rangefactorpergame: Optional[str] = None + rangefactorper9inn: Optional[str] = None + innings: Optional[str] = None + games: Optional[int] = None + passedball: Optional[int] = None + doubleplays: Optional[int] = None + tripleplays: Optional[int] = None + catcherera: Optional[str] = None + catchersinterference: Optional[int] = None + wildpitches: Optional[int] = None + throwingerrors: Optional[int] = None + pickoffs: Optional[int] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(kw_only=True, repr=False) +class FieldingSeasonAdvanced(Split): + """ + A class to represent a fielding season Advanced statistic + Attributes + ---------- + position : Position + The position of the player + """ + _stat = ['seasonAdvanced'] + position: Optional[Union[Position, dict]] = field(default_factory=dict) + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingCareerAdvanced(Split): + """ + A class to represent a fielding career Advanced statistic + Attributes + ---------- + position : Position + The position of the player + """ + _stat = ['careerAdvanced'] + position: Optional[Union[Position, dict]] = field(default_factory=dict) + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingSingleSeasonAdvanced(Split): + """ + A class to represent a fielding season statistic + + Attributes + ---------- + position : Position + The position of the player + """ + _stat = ['statsSingleSeasonAdvanced'] + position: Optional[Union[Position, dict]] = field(default_factory=dict) + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingSeason(Split): + """ + A class to represent a fielding season statistic + + Attributes + ---------- + position : Position + The position of the player + """ + _stat = ['season'] + position: Optional[Union[Position, dict]] = field(default_factory=dict) + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingSingleSeason(Split): + """ + A class to represent a fielding statsSingleSeason statistic + + Attributes + ---------- + position : Position + The position of the player + """ + _stat = ['statsSingleSeason'] + position: Optional[Union[Position, dict]] = field(default_factory=dict) + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingCareer(Split): + """ + A class to represent a fielding career statistic + + Attributes + ---------- + position : Position + The position of the player + """ + _stat = ['career', 'careerRegularSeason'] + position: Optional[Union[Position, dict]] = field(default_factory=dict) + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingCareerPlayoffs(Split): + """ + A class to represent a fielding careerPlayoffs statistic + + Attributes + ---------- + position : Position + The position of the player + """ + _stat = ['careerPlayoffs'] + position: Optional[Union[Position, dict]] = field(default_factory=dict) + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingHomeAndAway(Split): + """ + A class to represent a fielding homeAndAway statistic + + Attributes + ---------- + ishome : bool + A bool value for is the game at home + """ + _stat = ['homeAndAway'] + ishome: bool + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingHomeAndAwayPlayoffs(Split): + """ + A class to represent a fielding homeAndAwayPlayoffs statistic + + Attributes + ---------- + ishome : bool + A bool value for is the game at home + """ + _stat = ['homeAndAwayPlayoffs'] + ishome: bool + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingYearByYear(Split): + """ + A class to represent a fielding yearByYear statistic + + Attributes + ---------- + """ + _stat = ['yearByYear'] + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingYearByYearAdvanced(Split): + """ + A class to represent a fielding yearByYearAdvanced statistic + + Attributes + ---------- + """ + _stat = ['yearByYearAdvanced'] + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingYearByYearPlayoffs(Split): + """ + A class to represent a fielding yearByYearPlayoffs statistic + + Attributes + ---------- + """ + _stat = ['yearByYearPlayoffs'] + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + +@dataclass(kw_only=True, repr=False) +class FieldingWinLoss(Split): + """ + A class to represent a fielding winLoss statistic + + Attributes + ---------- + iswin : bool + is the game a win + """ + _stat = ['winLoss'] + iswin: bool + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingWinLossPlayoffs(Split): + """ + A class to represent a fielding winLossPlayoffs statistic + + Attributes + ---------- + iswin : bool + is the game a win + """ + _stat = ['winLossPlayoffs'] + iswin: bool + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingByDayOfWeek(Split): + """ + A class to represent a fielding byDayOfWeek statistic + + Attributes + ---------- + """ + _stat = ['byDayOfWeek'] + dayofweek: str + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingByDateRangeAdvanced(Split): + """ + A class to represent a fielding byDateRangeAdvanced stat + + Attributes + ---------- + """ + _stat = ['byDateRangeAdvanced'] + position: Union[Position, dict] = field(default_factory=dict) + + def __post_init__(self): + self.position = Position(**self.position) if self.position else self.position + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingByMonth(Split): + """ + A class to represent a fielding byMonth stat + + Attributes + ---------- + month : int + the month of the stat + """ + _stat = ['byMonth'] + month: int + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingByMonthPlayoffs(Split): + """ + A class to represent a fielding byMonthPlayoffs stat + + Attributes + ---------- + month : int + the month of the stat + """ + _stat = ['byMonthPlayoffs'] + month: int + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingLastXGames(Split): + """ + A class to represent a fielding lastXGames stat + + Attributes + ---------- + """ + _stat = ['lastXGames'] + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class FieldingGameLog(Split): + """ + A class to represent a fielding gameLog stats + + Attributes + ---------- + """ + _stat = ['gameLog'] + opponent: Union[Team, dict] = field(default_factory=dict) + date: str + ishome: bool + iswin: bool + position: Union[Position, dict] = field(default_factory=dict) + game: Union[Game, dict] = field(default_factory=dict) + stat: Union[SimpleFieldingSplit, dict] + + def __post_init__(self): + self.stat = SimpleFieldingSplit(**self.stat) + super().__post_init__() + diff --git a/mlbstatsapi/models/stats/game.py b/mlbstatsapi/models/stats/game.py new file mode 100644 index 00000000..ca8f27d1 --- /dev/null +++ b/mlbstatsapi/models/stats/game.py @@ -0,0 +1,72 @@ +from dataclasses import dataclass, field +from typing import Optional, Union, List + +from .stats import Split + +@dataclass +class SimpleGameStats: + """ + A class to represent a simple game statistics + + Attributes + ---------- + firstdateplayed : str + first date of game played + gamesplayed : int + number of the games player + gamesstarted : int + number of the games started + lastdateplayed : str + last date of the game played + """ + firstdateplayed: str + gamesplayed: int + gamesstarted: int + lastdateplayed: str + + def __post_init__(self): + super().__post_init__() + +@dataclass(kw_only=True) +class SeasonGame(Split): + """ + A class to represent a game statistic + + Used for the following stat types: + season, career, careerRegularSeason, careerPlayoffs, statsSingleSeason + """ + type_ = ['season', 'statsSingleSeason'] + + def __post_init__(self): + super().__post_init__() + +@dataclass(kw_only=True) +class CareerGame(Split): + """ + A class to represent a game statistic + """ + type_ = ['career'] + + def __post_init__(self): + super().__post_init__() + +@dataclass(kw_only=True) +class CareerRegularSeasonGame(Split, SimpleGameStats): + """ + A class to represent a game statistic + """ + type_ = ['careerRegularSeason'] + + def __post_init__(self): + super().__post_init__() + +@dataclass(kw_only=True) +class CareerPlayoffsGame(Split, SimpleGameStats): + """ + A class to represent a game statistic + + """ + type_ = ['careerPlayoffs'] + + def __post_init__(self): + super().__post_init__() \ No newline at end of file diff --git a/mlbstatsapi/models/stats/hitting.py b/mlbstatsapi/models/stats/hitting.py new file mode 100644 index 00000000..d00f1927 --- /dev/null +++ b/mlbstatsapi/models/stats/hitting.py @@ -0,0 +1,948 @@ +from dataclasses import dataclass, field +from typing import Optional, Union, List + +from mlbstatsapi.models.people import Person, Position, Batter, Pitcher +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.game import Game +from mlbstatsapi.mlb_module import merge_keys +from mlbstatsapi.models.data import ( + PitchData, + HitData, + Count, + PlayDetails +) +from .stats import ( + Sabermetrics, + ExpectedStatistics, + Split +) +@dataclass(repr=False) +class AdvancedHittingSplit: + """ + A class to represent a advanced hitting statistics + + Attributes + ---------- + plateappearances : int + The number of plate appearances. + totalbases : int + The total number of bases. + leftonbase : int + The amount of runners left on base. + sacbunts : int + The amount of sac bunts. + sacflies : int + The amount of sac flies. + babip : str + Batting Average on Balls in Play. + see here: https://www.mlb.com/glossary/advanced-stats/babip + extrabasehits : int + The amount of extra base hits. e.g doubles, triples, homeruns + see here: https://www.mlb.com/glossary/standard-stats/extra-base-hit + hitbypitch : int + The amount of times the batter has been hit by a pitch. + gidp : int + The amount of hits that lead to a double play. + see here: https://www.mlb.com/glossary/standard-stats/ground-into-double-play + gidpopp : int + The amount of GIDP opportunities. + numberofpitches : int + The number of pitches the batter has faced. + see here: https://www.mlb.com/glossary/standard-stats/number-of-pitches + pitchesperplateappearance : str + The avg amount of pitches per plate appearance for the hitter. + see here: https://www.mlb.com/glossary/advanced-stats/pitches-per-plate-appearance + walksperplateappearance : str + The avg walks per plate appearance. + see here: https://www.mlb.com/glossary/advanced-stats/walk-rate + strikeoutsperplateappearance : str + The amount of strike outs per plate appearance. + see here: https://www.mlb.com/glossary/advanced-stats/plate-appearances-per-strikeout + homerunsperplateappearance : str + The amount of home runs per plate appearance. + see here: https://en.wikipedia.org/wiki/At_bats_per_home_run + walksperstrikeout : str + The amount of walks per strike out. + see here: https://www.mlb.com/glossary/advanced-stats/strikeout-to-walk-ratio + iso : str + Isolasted power. + see also: https://www.mlb.com/glossary/advanced-stats/isolated-power + reachedonerror : int + The amount of times the batter has reached base on a error. + see also: https://www.mlb.com/glossary/standard-stats/reached-on-error + walkoffs : int + The amount of times the batter has walked off a game. + see also: https://www.mlb.com/glossary/standard-stats/walk-off + flyouts : int + The amount of flyouts for the batter. + see also: https://www.mlb.com/glossary/standard-stats/flyout + totalswings : int + The amount of swings the batter has taken at the plate. + swingandmisses : int + The amount of swing and misses the batter has taken at the plate. + ballsinplay : int + The amount of balls the batter has put in play. + popouts : int + The amount of popouts the batter has put in play. + lineouts : int + The amount of lineouts the batter has put in play. + groundouts : int + The amount of groundouts the batter has hit into. + flyhits : int + The amount of fly hits the batter has hit. + pophits : int + The amount of pop hits the batter has hit. + groundhits : int + The amount of ground hits the batter has hit. + linehits : int + The amount of line hits the the batter has hit. + """ + plateappearances: Optional[int] = None + totalbases: Optional[int] = None + leftonbase: Optional[int] = None + sacbunts: Optional[int] = None + sacflies: Optional[int] = None + babip: Optional[str] = None + extrabasehits: Optional[int] = None + hitbypitch: Optional[int] = None + gidp: Optional[int] = None + gidpopp: Optional[int] = None + numberofpitches: Optional[int] = None + pitchesperplateappearance: Optional[str] = None + walksperplateappearance: Optional[str] = None + strikeoutsperplateappearance: Optional[str] = None + homerunsperplateappearance: Optional[str] = None + walksperstrikeout: Optional[str] = None + iso: Optional[str] = None + reachedonerror: Optional[int] = None + walkoffs: Optional[int] = None + flyouts: Optional[int] = None + totalswings: Optional[int] = None + swingandmisses: Optional[int] = None + ballsinplay: Optional[int] = None + popouts: Optional[int] = None + lineouts: Optional[int] = None + groundouts: Optional[int] = None + flyhits: Optional[int] = None + pophits: Optional[int] = None + groundhits: Optional[int] = None + linehits: Optional[int] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(repr=False) +class SimpleHittingSplit: + """ + A class to represent a simple hitting statistics + + gamesplayed : int + The number of games played by the batter. + groundouts : int + The amount of groundouts hit by the batter. + airouts : int + The amount of air hits by the batter. + runs : int + The amount of runs plated by the batter. + doubles : int + The amount of doubles hit by the batter. + triples : int + The amount of triples hit by the batter. + homeruns : int + The amount of homeruns hit by the batter. + strikeouts : int + The amount of strikeouts for the batter. + baseonballs : int + The amount of base on balls (walks) for the batter. + intentionalwalks : int + The number of intentional walks for the batter. + hits : int + The number of hits for the batter. + hitbypitch : int + The number of pitches the batter has been hit by. + avg : str + The batting avg of the batter. + atbats : int + The number of at bats of the batter. + obp : str + The on base percentage of the batter. + see also: https://www.mlb.com/glossary/standard-stats/on-base-percentage + slg : str + The slugging percentage of the batter. + see also: https://www.mlb.com/glossary/standard-stats/slugging-percentage + ops : str + The on-base plug slugging of the batter. + see also: https://www.mlb.com/glossary/standard-stats/on-base-plus-slugging + caughtstealing : int + The amount of times the batter has been caught stealing. + stolenbases : int + The amount of stolen bases acheived by the batter. + stolenbasepercentage : int + The stolen base percentage of the batter. + groundintodoubleplay : int + The number of times the batter has hit into a double play. + groundintotripleplay : int + The number of times the batter has hit into a triple play. + numberofpitches : int + The number of pitches the batter has faced. + plateappearances : int + The number of plate appearances of the batter. + totalbases : int + The number of bases acheived by batter. + rbi : int + The number of Runs Batted In by the batter. + see also: https://www.mlb.com/glossary/standard-stats/runs-batted-in + leftonbase : int + The number of runners left on base by the batter. + sacbunts : int + The number of sac bunts performed by the batter. + sacflies : int + The number of sac flies performed by the batter. + babip : str + The batting average of balls in play of the batter. + see also: https://www.mlb.com/glossary/advanced-stats/babip + groundoutstoairouts : int + The groundout-to-airout ratio of the batter. + see also: https://www.mlb.com/glossary/advanced-stats/babip + catchersinterference : int + The number of times the batter has reached base due to catchers interference. + see also: https://www.mlb.com/glossary/rules/catcher-interference + atbatsperhomerun : int + The number of bats per home run of the batter. + """ + gamesplayed: Optional[int] = None + groundouts: Optional[int] = None + airouts: Optional[int] = None + runs: Optional[int] = None + doubles: Optional[int] = None + triples: Optional[int] = None + homeruns: Optional[int] = None + strikeouts: Optional[int] = None + baseonballs: Optional[int] = None + intentionalwalks: Optional[int] = None + hits: Optional[int] = None + hitbypitch: Optional[int] = None + avg: Optional[str] = None + atbats: Optional[int] = None + obp: Optional[str] = None + slg: Optional[str] = None + ops: Optional[str] = None + caughtstealing: Optional[int] = None + stolenbases: Optional[int] = None + stolenbasepercentage: Optional[int] = None + groundintodoubleplay: Optional[int] = None + groundintotripleplay: Optional[int] = None + numberofpitches: Optional[int] = None + plateappearances: Optional[int] = None + totalbases: Optional[int] = None + rbi: Optional[int] = None + leftonbase: Optional[int] = None + sacbunts: Optional[int] = None + sacflies: Optional[int] = None + babip: Optional[str] = None + groundoutstoairouts: Optional[int] = None + catchersinterference: Optional[int] = None + atbatsperhomerun: Optional[int] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(kw_only=True, repr=False) +class HittingWinLoss(Split): + """ + A class to represent a hitting winLoss statistic + + Attributes + ---------- + iswin : bool + the bool to hold if a win or not for hitting winLoss + stat : dict + the hitting split stat + """ + _stat = ['winLoss'] + iswin: bool + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class HittingWinLossPlayoffs(Split): + """ + A class to represent a hitting winLossPlayoffs statistic + + Attributes + ---------- + iswin : bool + the bool to hold if a win or not for hitting winLoss + stat : dict + the hitting split stat + """ + _stat = ['winLossPlayoffs'] + iswin: bool + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class HittingHomeAndAway(Split): + """ + A class to represent a hitting homeAndAway statistic + + Attributes + ---------- + ishome : bool + the bool to hold if it ishome hitting homeAndAway + stat : dict + the hitting split stat + """ + _stat = ['homeAndAway'] + ishome: bool + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class HittingHomeAndAwayPlayoffs(Split): + """ + A class to represent a hitting homeAndAway Playoff statistic + + Attributes + ---------- + ishome : bool + the bool to hold if it ishome hitting homeAndAway + stat : dict + the hitting split stat + """ + _stat = ['homeAndAwayPlayoffs'] + ishome: bool + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class HittingCareer(Split): + """ + A class to represent a hitting career, careerRegularSeason or careerPlayoffs statistic + + Attributes + ---------- + stat : dict + the hitting split stat + """ + _stat = ['career', 'careerRegularSeason', 'careerPlayoffs'] + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + super().__post_init__() + self.stat = SimpleHittingSplit(**self.stat) + + +@dataclass(kw_only=True, repr=False) +class HittingSeason(Split): + """ + A class to represent a hitting season statistic + + Attributes + ---------- + stat : dict + the hitting split stat + """ + _stat = ['season'] + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + super().__post_init__() + self.stat = SimpleHittingSplit(**self.stat) + +@dataclass(kw_only=True, repr=False) +class HittingSingleSeason(Split): + """ + A class to represent a hitting statsSingleSeason statistic + + Attributes + ---------- + stat : dict + the hitting split stat + """ + _stat = ['statsSingleSeason'] + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + super().__post_init__() + self.stat = SimpleHittingSplit(**self.stat) + +@dataclass(kw_only=True, repr=False) +class HittingSeasonAdvanced(Split): + """ + A class to represent a hitting seasonAdvanced statistic + + Attributes + ---------- + stat : dict + the hitting split stat + """ + _stat = ['seasonAdvanced'] + stat: Union[AdvancedHittingSplit, dict] + + def __post_init__(self): + super().__post_init__() + self.stat = AdvancedHittingSplit(**self.stat) + +@dataclass(kw_only=True, repr=False) +class HittingCareerAdvanced(Split): + """ + A class to represent a hitting season statistic + + Attributes + ---------- + """ + _stat = ['careerAdvanced'] + stat: Union[AdvancedHittingSplit, dict] + + def __post_init__(self): + self.stat = AdvancedHittingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class HittingYearByYear(Split): + """ + A class to represent a hitting yearbyyear or yearByYearPlayoffs statistic + + Attributes + ---------- + """ + _stat = ['yearByYear'] + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class HittingYearByYearPlayoffs(Split): + """ + A class to represent a hitting yearByYearPlayoffs statistic + + Attributes + ---------- + """ + _stat = ['yearByYearPlayoffs'] + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class OpponentsFacedHitting(Split): + """ + A class to represent a hitting opponentsFaced statistic + + Attributes + ---------- + batter : Person + the batter of that stat object + fieldingteam : Team + the defence team of the stat object + pitcher : Person + the pitcher of that stat object + group : str + stat group + """ + _stat = ['opponentsFaced'] + group: str + batter: Union[Batter, dict] + fieldingteam: Union[Team, dict] + pitcher: Union[Pitcher, dict] + + def __post_init__(self): + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class HittingSabermetrics(Split): + """ + A class to represent a hitting sabermetric statistic + + """ + _stat = ['sabermetrics'] + stat: Union[Sabermetrics, dict] + + def __post_init__(self): + self.stat = Sabermetrics(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class HittingGameLog(Split): + """ + A class to represent a gamelog stat for a hitter + + Attributes + ---------- + positionsplayed : List[Position] + stat : SimpleHittingSplit + ishome : bool + bool to hold ishome + iswin : bool + bool to hold iswin + game : Game + Game of the log + date : str + date of the log + gametype : str + type of game + opponent : Team + Team of the opponent + sport : Sport + Sport of the stat + league : League + League of the stat + player : Person + Player of the stat + """ + ishome: bool + iswin: bool + game: Union[Game, dict] + date: str + opponent: Union[Team, dict] + _stat = ['gameLog'] + positionsplayed: Optional[List[Position]] = field(default_factory=list) + stat: Union[SimpleHittingSplit, dict] = field(default_factory=dict) + + def __post_init__(self): + if self.positionsplayed: + self.positionsplayed = [Position(**position) for position in self.positionsplayed] + self.stat = SimpleHittingSplit(**self.stat) if self.stat else self.stat + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class HittingPlay: + """ + A class to represent a gamelog stat for a hitter + + Attributes + ---------- + """ + details: Union[PlayDetails, dict] + count: Union[Count, dict] + ispitch: bool + pitchnumber: Optional[int] = None + atbatnumber: Optional[int] = None + index: Optional[str] = None + playid: Optional[str] = None + pitchdata: Optional[Union[PitchData, dict]] = field(default_factory=dict) + hitdata: Optional[Union[HitData, dict]] = field(default_factory=dict) + starttime: Optional[str] = None + endtime: Optional[str] = None + type: Optional[str] = None + + + def __post_init__(self): + self.details = PlayDetails(**self.details) + self.count = Count(**self.count) + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(kw_only=True, repr=False) +class HittingPlayLog(Split): + """ + A class to represent a gamelog stat for a hitter + + Attributes + ---------- + season : str + season for the stat + opponent : Team + opponent + date : str + date of log + gametype : str + game type code + ishome : bool + is the game at home bool + pitcher : Person + pitcher of the log + batter : Person + batter of the log + game : Game + the game of the log + + """ + stat: Union[HittingPlay, dict] + opponent: Optional[Union[Team, dict]] = field(default_factory=dict) + date: Optional[str] = None + ishome: Optional[bool] = None + pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) + batter: Optional[Union[Batter, dict]] = field(default_factory=dict) + game: Optional[Union[Game, dict]] = field(default_factory=dict) + + _stat = ['playLog'] + + def __post_init__(self): + self.stat = HittingPlay(**self.stat['play']) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class HittingPitchLog(Split): + """ + A class to represent a gamelog stat for a hitter + + Attributes + ---------- + season : str + season for the stat + stat : PlayLog + information regarding the play for the stat + opponent : Team + opponent + date : str + date of log + gametype : str + game type code + ishome : bool + is the game at home bool + pitcher : Person + pitcher of the log + batter : Person + batter of the log + game : Game + the game of the log + + """ + stat: PlayDetails + _stat = ['pitchLog'] + opponent: Union[Team, dict] + date: str + ishome: bool + pitcher: Union[Pitcher, dict] + batter: Union[Batter, dict] + game: Union[Game, dict] + playid: Optional[str] = None + + def __post_init__(self): + self.stat = HittingPlay(**self.stat['play']) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class HittingLastXGames(Split): + """ + A class to represent a lastXGames statistic + + Attributes + ---------- + """ + _stat = ['lastXGames'] + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class HittingDateRange(Split): + """ + A class to represent a byDateRange statistic + + Attributes + ---------- + """ + _stat = ['byDateRange'] + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class HittingDateRangeAdvanced(Split): + """ + A class to represent a byDateRangeAdvanced statistic + + Attributes + ---------- + """ + _stat = ['byDateRangeAdvanced'] + stat: Union[AdvancedHittingSplit, dict] + + def __post_init__(self): + self.stat = AdvancedHittingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class HittingDateRangeAdvancedByMonth(Split): + """ + A class to represent a byDateRangeAdvanced statistic + + Attributes + ---------- + """ + _stat = ['byDateRangeAdvancedbyMonth'] + stat: Union[AdvancedHittingSplit, dict] + + def __post_init__(self): + self.stat = AdvancedHittingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class HittingByMonth(Split): + """ + A class to represent a byMonth hitting statistic + + Attributes + ---------- + """ + _stat = ['byMonth'] + month: int + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class HittingByMonthPlayoffs(Split): + """ + A class to represent a yearByYear hitting statistic + + Attributes + ---------- + month : str + the month of the stat + """ + _stat = ['byMonthPlayoffs'] + month: int + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class HittingDayOfWeek(Split): + """ + A class to represent a yearByYear hitting statistic + + Attributes + ---------- + dayofweek : int + the day of the week + """ + _stat = ['byDayOfWeek'] + dayofweek: int + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + + +@dataclass(kw_only=True, repr=False) +class HittingDayOfWeekPlayoffs(Split): + """ + A class to represent a yearByYear hitting statistic + + Attributes + ---------- + dayofweek : int + the day of the week + """ + _stat = ['byDayOfWeekPlayoffs'] + dayofweek: int + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class HittingExpectedStatistics(Split): + """ + A class to represent a excepted statistics statType: expectedStatistics. + Attributes + ---------- + avg : str + slg : str + woba : str + wobaCon : str + rank : int + """ + _stat = ['expectedStatistics'] + stat: Union[ExpectedStatistics, dict] + + def __post_init__(self): + self.stat = ExpectedStatistics(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class HittingVsTeam(Split): + """ + A class to represent a vsTeam hitting statistic + + requires the use of the opposingTeamId parameter + Attributes + ---------- + dayofweek : int + the day of the week + """ + _stat = ['vsTeam'] + opponent: Union[Team, dict] + batter: Optional[Union[Batter, dict]] = field(default_factory=dict) + pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class HittingVsTeamTotal(Split): + """ + A class to represent a vsTeamTotal hitting statistic + + requires the use of the opposingTeamId parameter + + Attributes + ---------- + opponent: Team + opponent team + batter: Person + batting person + pitcher: Person + pitching person + """ + _stat = ['vsTeamTotal'] + opponent: Union[Team, dict] + batter: Optional[Union[Batter, dict]] = field(default_factory=dict) + pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class HittingVsTeam5Y(Split): + """ + A class to represent a vsTeam5Y hitting statistic + + requires the use of the opposingTeamId parameter + + Attributes + ---------- + """ + _stat = ['vsTeam5Y'] + opponent: Union[Team, dict] + batter: Optional[Union[Batter, dict]] = field(default_factory=dict) + pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class HittingVsPlayer(Split): + """ + A class to represent a yearByYear hitting statistic + + This class is for the stat type vsPlayer* + + Requires the param opposingPlayerId set + + Attributes + ---------- + pitcher : Pitcher + The pitcher of the hitting vsplayer stat + batter : Batter + The batter of the hitting vsplayer stat + opponent : Team + The team of the hitting vsplayer stat + """ + _stat = ['vsPlayer'] + pitcher: Union[Pitcher, dict] + batter: Union[Batter, dict] + opponent: Optional[Union[Team, dict]] = field(default_factory=dict) + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class HittingVsPlayerTotal(Split): + """ + A class to represent a yearByYear hitting statistic + + This class is for the stat type vsPlayer* + + Requires the param opposingPlayerId set + + Attributes + ---------- + pitcher : Pitcher + The pitcher of the hitting vsplayer stat + batter : Batter + The batter of the hitting vsplayer stat + opponent : Team + The team of the hitting vsplayer stat + """ + _stat = ['vsPlayerTotal'] + pitcher: Union[Pitcher, dict] + batter: Union[Batter, dict] + opponent: Optional[Union[Team, dict]] = field(default_factory=dict) + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class HittingVsPlayer5Y(Split): + """ + A class to represent a yearByYear hitting statistic + + This class is for the stat type vsPlayer* + + Requires the param opposingPlayerId set + + Attributes + ---------- + pitcher : Pitcher + The pitcher of the hitting vsplayer stat + batter : Batter + The batter of the hitting vsplayer stat + opponent : Team + The team of the hitting vsplayer stat + """ + _stat = ['vsPlayer5Y'] + pitcher: Union[Pitcher, dict] + batter: Union[Person, dict] + opponent: Optional[Union[Team, dict]] = field(default_factory=dict) + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() diff --git a/mlbstatsapi/models/stats/pitching.py b/mlbstatsapi/models/stats/pitching.py new file mode 100644 index 00000000..e69ab324 --- /dev/null +++ b/mlbstatsapi/models/stats/pitching.py @@ -0,0 +1,1051 @@ +from dataclasses import InitVar, dataclass, field +from typing import Optional, Union, List + +from mlbstatsapi.models.people import Person, Pitcher, Batter +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.game import Game +from mlbstatsapi.mlb_module import merge_keys +from mlbstatsapi.models.data import ( + Count, + PlayDetails, + +) + +from .stats import Sabermetrics, ExpectedStatistics, Split +from .hitting import SimpleHittingSplit + + +@dataclass(repr=False) +class SimplePitchingSplit: + """ + A class to represent a advanced pitching statistics + + attributes are all optional as there is no documentation for the stats endpoint + + Attributes + ---------- + gamesplayed : int + The games played by the pitcher. + gamesstarted : int + The games started by the pitcher. + groundouts : int + The number of groundouts for the pitcher. + airouts : int + The number of airouts for the pitcher. + runs : int + The number of runs given up by the pitcher. + doubles : int + The number of doubles given up by the pitcher. + triples : int + The number of triples given up by the pitcher. + homeruns : int + The number of home runs given up by the pitcher. + strikeouts : int + The number of strike outs performed by the pitcher. + baseonballs : int + The number of base on balls (walks) performed by the pitcher. + intentionalwalks : int + The number of intentional walks performed by the pitcher. + hits : int + The number of hits given up by the pitcher. + hitbypitch : int + The number of batters hit by the pitcher. + avg : int + The batting avg against the pitcher. + atbats : int + The at bats pitched by the pitcher. + obp : str + The on base percentage again the pitcher. + slg : str + The slugging percentage against the pitcher. + ops : str + The on base slugging against the pitcher. + see also: https://www.mlb.com/glossary/standard-stats/on-base-plus-slugging + caughtstealing : int + The number of runners caught stealing against the pitcher. + stolenbases : int + The number of stolen bases while pitching. + stolenbasepercentage : int + The stolen base percentage while pitching. + groundintodoubleplay : int + The number of hits against + numberofpitches : int + The number of pitches thrown. + era : str + The earned run average of the pitcher. + inningspitched : int + The number of innings pitched by the pitcher. + wins : int + The number of wins by the pitcher. + losses : int + The number of losses by the pitcher. + saves : int + The number of saves by the pitcher. + saveopportunities : int + The number of save opportunities by the pitcher. + holds : int + The number of holds by the pitcher. + blownsaves : int + The number of blown saves performed by the pitcher. + earnedruns : int + The number of earned runs given up by the pitcher. + whip : str + The number of walks and hits per inning pitched. + see also: https://www.mlb.com/glossary/standard-stats/walks-and-hits-per-inning-pitched + outs : int + The number of outs + gamespitched : int + The number of games pitched. + completegames : int + The number of complete games pitched. + shutouts : int + The number of shut outs pitched. + strikes : int + The number of strikes thown by the pitcher. + hitbatsmen : int + The number of batters hit by a pitch. + strikepercentage : str + The strike percentage thrown by the pitcher. + wildpitches : int + The number of wild pitches thown by the pitcher. + balks : int + The number of balks commited by the pitcher. + totalbases : int + The total bases given up by the pitcher. + pickoffs : int + The number of pick offs performed by the pitcher. + winpercentage : str + The winpercentage of the pitcher. + groundoutstoairouts : str + The groundout-to-airout ratio of the pitcher. + gamesfinished : int + The number of games finished by the pitcher. + pitchesperinning : str + The number of pitches thown per inning by the pitcher. + strikeoutsper9inn : str + The number of strike outs per 9 innings by the pitcher + strikeoutwalkratio : str + The strike out to walk ratio of the pitcher. + hitsper9inn : str + The number of hits per 9 innings pitched. + walksper9inn : str + The number of walks per 9 innings pitched. + homerunsper9 : str + The number of home runs per 9 innings pitched. + runsscoredper9 : str + The number of runs scored per 9 innings pitched. + sacbunts : int + The number of sac bunts given up when pitched. + catchersinterference : int + The number of times a runner has reached from catchers interference while pitching. + battersfaced : int + The number of batters faced by the pitcher. + sacflies : int + The number of sac flies given up by the pitcher. + inheritedrunnersscored : int + The number of inherited runners scored by the pitcher. + inheritedrunners : int + The number of inherited runners for the pitcher. + """ + gamesplayed: Optional[int] = None + gamesstarted: Optional[int] = None + groundouts: Optional[int] = None + airouts: Optional[int] = None + runs: Optional[int] = None + doubles: Optional[int] = None + triples: Optional[int] = None + homeruns: Optional[int] = None + strikeouts: Optional[int] = None + baseonballs: Optional[int] = None + intentionalwalks: Optional[int] = None + hits: Optional[int] = None + hitbypitch: Optional[int] = None + avg: Optional[str] = None + atbats: Optional[int] = None + obp: Optional[str] = None + slg: Optional[str] = None + ops: Optional[str] = None + caughtstealing: Optional[int] = None + stolenbases: Optional[int] = None + stolenbasepercentage: Optional[str] = None + groundintodoubleplay: Optional[int] = None + numberofpitches: Optional[int] = None + era: Optional[str] = None + inningspitched: Optional[str] = None + wins: Optional[int] = None + losses: Optional[int] = None + saves: Optional[int] = None + saveopportunities: Optional[int] = None + holds: Optional[int] = None + blownsaves: Optional[int] = None + earnedruns: Optional[int] = None + whip: Optional[str] = None + outs: Optional[int] = None + gamespitched: Optional[int] = None + completegames: Optional[int] = None + shutouts: Optional[int] = None + strikes: Optional[int] = None + strikepercentage: Optional[str] = None + hitbatsmen: Optional[int] = None + balks: Optional[int] = None + wildpitches: Optional[int] = None + pickoffs: Optional[int] = None + totalbases: Optional[int] = None + groundoutstoairouts: Optional[str] = None + winpercentage: Optional[str] = None + pitchesperinning: Optional[str] = None + gamesfinished: Optional[int] = None + strikeoutwalkratio: Optional[str] = None + strikeoutsper9inn: Optional[str] = None + walksper9inn: Optional[str] = None + hitsper9inn: Optional[str] = None + runsscoredper9: Optional[str] = None + homerunsper9: Optional[str] = None + catchersinterference: Optional[int] = None + sacbunts: Optional[int] = None + sacflies: Optional[int] = None + battersfaced: Optional[int] = None + inheritedrunners: Optional[int] = None + inheritedrunnersscored: Optional[int] = None + balls: Optional[int] = None + outspitched: Optional[int] = None + rbi: Optional[int] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + + +@dataclass(repr=False) +class AdvancedPitchingSplit: + """ + A class to represent a advanced pitching statistics + + winningpercentage : str + The winning percentage of the pitcher. + runsscoredper9 : str + The number of runs scored per 9 innings + battersfaced : int + The number of batters faced + babip : str + The BABIP of the pitcher. + obp : str + The on base percentage again the pitcher. + slg : str + The slugging percentage against the pitcher. + ops : str + The on base slugging against the pitcher. + see also: https://www.mlb.com/glossary/standard-stats/on-base-plus-slugging + strikeoutsper9 : str + The number of strike outs per 9 innings by the pitcher + baseonballsper9 : str + The number of base on balls per 9 innings by the pitcher. + homerunsper9 : str + The number of home runs per 9 innings by the pitcher. + hitsper9 : str + The number of hits per 9 innings by the pitcher. + strikesoutstowalks : str + The strike out to walk ratio of the pitcher. + stolenbases : int + The number of stolen bases while pitching. + caughtstealing : int + The number of runners caught stealing by the pitcher. + qualitystarts : int + The number of quality starts performed by the pitcher. + gamesfinished : int + The number of games finished performed by the pitcher. + doubles : int + The number of doubles given up by the pitcher. + triples : int + The number of triples given up by the pitcher. + gidp : int + The amount of hits that lead to a double play. + see here: https://www.mlb.com/glossary/standard-stats/ground-into-double-play + gidpopp : int + The amount of GIDP opportunities. + wildpitches : int + The number of wild pitches thown by the pitcher. + balks : int + The number of balks commited by the pitcher. + pickoffs : int + The number of pick offs attempted by the pitcher. + totalswings : int + The number of swings against the pitcher. + swingandmisses : int + The number of swing and misses against the pitcher. + ballsinplay : int + The number of balls put into play against the pitcher. + runsupport : int + The number of run support + strikepercentage : str + The strike percentage thown by the pitcher. + pitchesperinning : str + The number of pitches per inning + pitchesperplateappearance : str + The avg number of pitches per plate appearance of the pitcher. + walksperplateappearance : str + The number of walks per plate appearance for the pitcher. + strikeoutsperplateappearance : str + The strike outs per plate appearance for the pitcher. + homerunsperplateappearance : str + The home runs per plate appearance for the pitcher. + walksperstrikeout : str + The walk per strike out ratio of the pitcher + iso : str + Isolasted power. + see also: https://www.mlb.com/glossary/advanced-stats/isolated-power + flyouts : int + The number of ly outs given up by the pitcher. + popouts : int + The number of pop outs given up by the pitcher. + lineouts : int + The number of line outs given up by the pitcher. + groundouts : int + The number of ground outs given up by the pitcher. + flyhits : int + The number of fly hits given up by the pitcher. + pophits : int + The number of pop hits given up by the pitcher. + linehits : int + The number of line hits given up by the pitcher. + groundhits : int + The number of ground hits given up by the pitcher. + inheritedrunners : int + The number of inherited runners for the pitcher. + inheritedrunnersscored : int + The number of inherited runners scored for the pitcher. + bequeathedrunners : int + The number of bequeathed runners. + see also: https://www.mlb.com/glossary/advanced-stats/bequeathed-runners + bequeathedrunnersscored : int + The number of bequeathed runners scored. + see also: https://www.mlb.com/glossary/advanced-stats/bequeathed-runners + """ + winningpercentage: Optional[str] = None + runsscoredper9: Optional[str] = None + battersfaced: Optional[int] = None + babip: Optional[str] = None + obp: Optional[str] = None + slg: Optional[str] = None + ops: Optional[str] = None + strikeoutsper9: Optional[str] = None + baseonballsper9: Optional[str] = None + homerunsper9: Optional[str] = None + hitsper9: Optional[str] = None + strikesoutstowalks: Optional[str] = None + stolenbases: Optional[int] = None + caughtstealing: Optional[int] = None + qualitystarts: Optional[int] = None + gamesfinished: Optional[int] = None + doubles: Optional[int] = None + triples: Optional[int] = None + gidp: Optional[int] = None + gidpopp: Optional[int] = None + wildpitches: Optional[int] = None + balks: Optional[int] = None + pickoffs: Optional[int] = None + totalswings: Optional[int] = None + swingandmisses: Optional[int] = None + ballsinplay: Optional[int] = None + runsupport: Optional[int] = None + strikepercentage: Optional[str] = None + pitchesperinning: Optional[str] = None + pitchesperplateappearance: Optional[str] = None + walksperplateappearance: Optional[str] = None + strikeoutsperplateappearance: Optional[str] = None + homerunsperplateappearance: Optional[str] = None + walksperstrikeout: Optional[str] = None + iso: Optional[str] = None + flyouts: Optional[int] = None + popouts: Optional[int] = None + lineouts: Optional[int] = None + groundouts: Optional[int] = None + flyhits: Optional[int] = None + pophits: Optional[int] = None + linehits: Optional[int] = None + groundhits: Optional[int] = None + inheritedrunners: Optional[int] = None + inheritedrunnersscored: Optional[int] = None + bequeathedrunners: Optional[int] = None + bequeathedrunnersscored: Optional[int] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + + +@dataclass(kw_only=True, repr=False) +class PitchingSabermetrics(Split): + """ + A class to represent a pitching sabermetric statistics + + Attributes + ---------- + fip : float + Fielding Independent Pitching + fipminus : float + Fielding Independent Pitching Minus + ra9war : float + Runs Allowed 9 innings Wins Above Replacement + rar : float + Runs Above Replacement + war : float + Wins Above Replacement + gametype : str + the gametype code of the pitching season + numteams : str + the number of teams for the pitching season + """ + _stat = ['sabermetrics'] + stat: Union[Sabermetrics, dict] + + def __post_init__(self): + self.stat = Sabermetrics(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class PitchingSeason(Split): + """ + A class to represent a pitching season statistic + + Attributes + ---------- + """ + _stat = ['season', 'statsSingleSeason'] + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class PitchingCareer(Split): + """ + A class to represent a pitching season statistic + + Attributes + ---------- + """ + _stat = ['career'] + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class PitchingCareerAdvanced(Split): + """ + A class to represent a pitching season statistic + + Attributes + ---------- + """ + _stat = ['careerAdvanced'] + stat: Union[AdvancedPitchingSplit, dict] + + def __post_init__(self): + self.stat = AdvancedPitchingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class PitchingYearByYear(Split): + """ + A class to represent a yearByYear season statistic + + Attributes + ---------- + """ + _stat = ['yearByYear'] + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingYearByYearPlayoffs(Split): + """ + A class to represent a yearByYear season statistic + + Attributes + ---------- + """ + _stat = ['yearByYearPlayoffs'] + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingYearByYearAdvanced(Split): + """ + A class to represent a pitching yearByYear statistic + + Attributes + ---------- + """ + _stat = ['yearByYearAdvanced'] + stat: Union[AdvancedPitchingSplit, dict] + + def __post_init__(self): + self.stat = AdvancedPitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingSeasonAdvanced(Split): + """ + A class to represent a pitching seasonAdvanced statistic + + Attributes + ---------- + """ + _stat = ['seasonAdvanced'] + stat: Union[AdvancedPitchingSplit, dict] + + def __post_init__(self): + self.stat = AdvancedPitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingSingleSeasonAdvanced(Split): + """ + A class to represent a pitching seasonAdvanced statistic + + Attributes + ---------- + """ + _stat = ['statsSingleSeasonAdvanced'] + stat: Union[AdvancedPitchingSplit, dict] + + def __post_init__(self): + self.stat = AdvancedPitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingGameLog(Split): + """ + A class to represent a gamelog stat for a pitcher + + Attributes + ---------- + ishome : bool + bool to hold ishome + iswin : bool + bool to hold iswin + game : Game + Game of the log + date : str + date of the log + gametype : str + type of game + opponent : Team + Team of the opponent + """ + ishome: bool + iswin: bool + game: Union[Game, dict] + date: str + opponent: Union[Team, dict] + _stat = ['gameLog'] + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass +class PitchingPlay: + """ + A class to represent a gamelog stat for a hitter + + Attributes + ---------- + details : dict + a dict containing PlayDetails + count : dict + a dict containing the pitch Count + pitchnumber : int + pitcher number + atbatnumber : int + at bat number + ispitch : bool + ispitch bool + playid : str + A play id + """ + details: Union[PlayDetails, dict] + count: Union[Count, dict] + pitchnumber: int + atbatnumber: int + ispitch: bool + playid: str + + def __post_init__(self): + self.details = PlayDetails(**self.details) + self.count = Count(**self.count) + +@dataclass(kw_only=True, repr=False) +class PitchingLog(Split): + """ + A class to represent a pitchLog stat for a pitcher. + + Attributes + ---------- + season : str + season for the stat + stat : PlayLog + information regarding the play for the stat + team : Team + team of the stat + player : Person + player of the stat + opponent : Team + opponent + date : str + date of log + gametype : str + game type code + ishome : bool + is the game at home bool + pitcher : Person + pitcher of the log + batter : Person + batter of the log + game : Game + the game of the log + + """ + _stat = ['pitchLog'] + stat: Union[PitchingPlay, dict] + season: str + opponent: Union[Team, dict] + date: str + ishome: bool + pitcher: Union[Pitcher, dict] + batter: Union[Batter, dict] + game: Union[Game, dict] + + def __post_init__(self): + self.stat = PitchingPlay(**self.stat['play']) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingPlayLog(Split): + """ + A class to represent a playLog stat for a pitcher. + + Attributes + ---------- + season : str + season for the stat + stat : PlayLog + information regarding the play for the stat + team : Team + team of the stat + player : Person + player of the stat + opponent : Team + opponent + date : str + date of log + gametype : str + game type code + ishome : bool + is the game at home bool + pitcher : Person + pitcher of the log + batter : Person + batter of the log + game : Game + the game of the log + + """ + _stat = ['playLog'] + stat: Union[PitchingPlay, dict] + season: str + opponent: Union[Team, dict] + date: str + ishome: bool + pitcher: Union[Pitcher, dict] + batter: Union[Batter, dict] + game: Union[Game, dict] + + def __post_init__(self): + self.stat = PitchingPlay(**self.stat['play']) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingByDateRange(Split): + """ + A class to represent a byDateRange stat for a pitcher. + + Attributes + ---------- + dayofweek : int + """ + _stat = ['byDateRange'] + dayofweek: Optional[int] = None + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingByDateRangeAdvanced(Split): + """ + A class to represent a byDateRangeAdvanced stat for a pitcher. + + Attributes + ---------- + dayofweek : int + """ + _stat = ['byDateRangeAdvanced'] + dayofweek: Optional[int] = None + stat: Union[AdvancedPitchingSplit, dict] + + def __post_init__(self): + self.stat = AdvancedPitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingByMonth(Split): + """ + A class to represent a byMonth stat for a pitcher. + + Attributes + ---------- + month : int + """ + _stat = ['byMonth'] + month: int + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingByMonthPlayoffs(Split): + """ + A class to represent a byMonthPlayoffs stat for a pitcher. + + Attributes + ---------- + month : int + """ + _stat = ['byMonthPlayoffs'] + month: int + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingByDayOfWeek(Split): + """ + A class to represent a byDayOfWeek stat for a pitcher. + + Attributes + ---------- + dayofweek : int + """ + _stat = ['byDayOfWeek'] + dayofweek: Optional[int] = None + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingByDayOfWeekPlayOffs(Split): + """ + A class to represent a byDayOfWeekPlayoffs stat for a pitcher. + + Attributes + ---------- + dayofweek : int + """ + _stat = ['byDayOfWeekPlayoffs'] + dayofweek: Optional[int] = None + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingHomeAndAway(Split): + """ + A class to represent a homeAndAway stat for a pitcher. + + Attributes + ---------- + ishome : bool + """ + _stat = ['homeAndAway'] + ishome: bool + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingHomeAndAwayPlayoffs(Split): + """ + A class to represent a homeAndAwayPlayoffs stat for a pitcher. + + Attributes + ---------- + ishome : bool + """ + _stat = ['homeAndAwayPlayoffs'] + ishome: bool + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingWinLoss(Split): + """ + A class to represent a winLoss stat for a pitcher. + + Attributes + ---------- + iswin : bool + """ + _stat = ['winLoss'] + iswin: bool + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingWinLossPlayoffs(Split): + """ + A class to represent a winLossPlayoffs stat for a pitcher. + + Attributes + ---------- + iswin : bool + """ + _stat = ['winLossPlayoffs'] + iswin: bool + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingRankings(Split): + """ + A class to represent a rankingsByYear stat for a pitcher. + + Attributes + ---------- + """ + _stat = ['rankingsByYear'] + outspitched: Optional[int] = None + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingOpponentsFaced(Split): + """ + A class to represent an opponentsFaced stat for a pitcher. + + Attributes + ---------- + group : str + pitch : Person + batter : Person + battingteam : Team + """ + _stat = ['opponentsFaced'] + group: str + pitcher: Union[Pitcher, dict] + batter: Union[Batter, dict] + battingteam: Union[Team, dict] + + def __post_init__(self): + self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher + self.batter = Batter(**self.batter) if self.batter else self.batter + self.battingteam = Team(**self.battingteam) if self.battingteam else self.battingteam + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingExpectedStatistics(Split): + """ + A class to represent an expectedStatistics stat for a pitcher. + + Attributes + ---------- + avg : str + slg : str + woba : str + wobaCon : str + rank : int + """ + _stat = ['expectedStatistics'] + stat: Union[ExpectedStatistics, dict] + + def __post_init__(self): + self.stat = ExpectedStatistics(**self.stat) + super().__post_init__() + + + +@dataclass(kw_only=True, repr=False) +class PitchingVsPlayer5Y(Split): + """ + A class to represent a vsPlayer5Y pitching statistic + + requires the use of opposingTeamId params + + Attributes + ---------- + """ + _stat = ['vsPlayer5Y'] + opponent: Union[Team, dict] + batter: Optional[Union[Batter, dict]] = field(default_factory=dict) + pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher + self.batter = Batter(**self.batter) if self.batter else self.batter + self.opponent = Team(**self.opponent) if self.opponent else self.opponent + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class PitchingVsPlayer(Split): + """ + A class to represent a vsPlayer pitching statistic + + requires the use of opposingTeamId params + + Attributes + ---------- + """ + _stat = ['vsPlayer'] + opponent: Union[Team, dict] + batter: Optional[Union[Batter, dict]] = field(default_factory=dict) + pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher + self.batter = Batter(**self.batter) if self.batter else self.batter + self.opponent = Team(**self.opponent) if self.opponent else self.opponent + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class PitchingVsPlayerTotal(Split): + """ + A class to represent a vsPlayerTotal pitching statistic + + requires the use of opposingTeamId params + + Attributes + ---------- + """ + _stat = ['vsPlayerTotal'] + opponent: Optional[Union[Team, dict]] = field(default_factory=dict) + batter: Optional[Union[Batter, dict]] = field(default_factory=dict) + pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) + stat: Union[SimplePitchingSplit, dict] + + def __post_init__(self): + self.stat = SimplePitchingSplit(**self.stat) + self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher + self.batter = Batter(**self.batter) if self.batter else self.batter + self.opponent = Team(**self.opponent) if self.opponent else self.opponent + super().__post_init__() + + +# These stat_types return a hitting stat for a pitching stat group +# odd, but need to deal with it. +@dataclass(kw_only=True, repr=False) +class PitchingVsTeam(Split): + """ + A class to represent a vsTeam pitching statistic + + Attributes + ---------- + """ + _stat = ['vsTeam'] + opponent: Union[Team, dict] + batter: Optional[Union[Batter, dict]] = field(default_factory=dict) + pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher + self.batter = Batter(**self.batter) if self.batter else self.batter + self.opponent = Team(**self.opponent) if self.opponent else self.opponent + self.stat = SimpleHittingSplit(**self.stat) + super().__post_init__() + + +@dataclass(kw_only=True, repr=False) +class PitchingVsTeamTotal(Split): + """ + A class to represent a vsTeamTotal pitching statistic + + Attributes + ---------- + """ + _stat = ['vsTeamTotal'] + opponent: Union[Team, dict] + batter: Optional[Union[Batter, dict]] = field(default_factory=dict) + pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher + self.batter = Batter(**self.batter) if self.batter else self.batter + self.opponent = Team(**self.opponent) if self.opponent else self.opponent + super().__post_init__() + +@dataclass(kw_only=True, repr=False) +class PitchingVsTeam5Y(Split): + """ + A class to represent a vsTeam5Y pitching statistic + + Attributes + ---------- + """ + _stat = ['vsTeam5Y'] + opponent: Union[Team, dict] + batter: Optional[Union[Batter, dict]] = field(default_factory=dict) + pitcher: Optional[Union[Pitcher, dict]] = field(default_factory=dict) + stat: Union[SimpleHittingSplit, dict] + + def __post_init__(self): + self.stat = SimpleHittingSplit(**self.stat) + self.pitcher = Pitcher(**self.pitcher) if self.pitcher else self.pitcher + self.batter = Batter(**self.batter) if self.batter else self.batter + self.opponent = Team(**self.opponent) if self.opponent else self.opponent + super().__post_init__() diff --git a/mlbstatsapi/models/stats/running.py b/mlbstatsapi/models/stats/running.py new file mode 100644 index 00000000..261e4035 --- /dev/null +++ b/mlbstatsapi/models/stats/running.py @@ -0,0 +1,29 @@ +from dataclasses import dataclass, field +from typing import Optional, Union, List + +from mlbstatsapi.models.people import Batter, Pitcher + +from .stats import Stat + +@dataclass(kw_only=True) +class RunningOpponentsFaced(Stat): + """ + A class to represent a running opponentsFaced statistic + + Attributes + ---------- + batter : Batter + The batter of the stat + group : str + The stat group of the stat + pitcher : Pitcher + The pitcher of the stat + """ + _stat = ['opponentsFaced'] + batter: Union[Batter, dict] + group: str + pitcher: Union[Pitcher, dict] + + def __post_init__(self): + self.batter = Batter(**self.batter) if self.batter else self.batter + self.pitcher = Pitcher(**self.pitcher) if self.batter else self.batter \ No newline at end of file diff --git a/mlbstatsapi/models/stats/stats.py b/mlbstatsapi/models/stats/stats.py new file mode 100644 index 00000000..36eb7aa4 --- /dev/null +++ b/mlbstatsapi/models/stats/stats.py @@ -0,0 +1,308 @@ +from dataclasses import dataclass, field +from typing import Optional, Union, List + +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.people import Person, Batter, Position +from mlbstatsapi.models.sports import Sport +from mlbstatsapi.models.leagues import League +from mlbstatsapi.models.data import CodeDesc + +@dataclass +class PitchArsenalSplit: + """ + A class to represent a pitching pitch arsenal split + + Attributes + ---------- + percentage : float + count : int + totalPitches : int + averageSpeed : float + type : Union[CodeDesc, dict] + """ + percentage: float + count: int + totalpitches: int + averagespeed: float + type: Union[CodeDesc, dict] + +@dataclass +class ExpectedStatistics: + """ + a class to hold a code and a description + + Attributes + ---------- + """ + avg: str + slg: str + woba: str + wobacon: str + +@dataclass(repr=False) +class Sabermetrics: + """ + a class to hold a code and a description + + Attributes + ---------- + woba : float + wRaa : float + wRc : float + wRcPlus : float + rar : float + war : float + batting : float + fielding : float + baseRunning : float + positional : float + wLeague : float + replacement : float + spd : float + ubr : float + wGdp : float + wSb : float + """ + + woba: Optional[float] = None + wraa: Optional[float] = None + wrc: Optional[float] = None + wrcplus: Optional[float] = None + rar: Optional[float] = None + war: Optional[float] = None + batting: Optional[float] = None + fielding: Optional[float] = None + baserunning: Optional[float] = None + positional: Optional[float] = None + wleague: Optional[float] = None + replacement: Optional[float] = None + spd: Optional[float] = None + ubr: Optional[float] = None + wgdp: Optional[float] = None + wsb: Optional[float] = None + + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(kw_only=True, repr=False) +class Split: + """ + Base class for splits + + Attributes + ---------- + season : str + numteams : int + gametype : str + rank : int + position : Position + team : Team + player : Person + sport : Sport + league : League + """ + season: Optional[str] = None + numteams: Optional[int] = None + gametype: Optional[str] = None + rank: Optional[int] = None + position: Optional[Union[Position, dict]] = field(default_factory=dict) + team: Optional[Union[Team, dict]] = field(default_factory=dict) + player: Optional[Union[Person, dict]] = field(default_factory=dict) + sport: Optional[Union[Sport, dict]] = field(default_factory=dict) + league: Optional[Union[League, dict]] = field(default_factory=dict) + + def __post_init__(self): + self.position = Position(**self.position) if self.position else self.position + self.team = Team(**self.team) if self.team else self.team + self.sport = Sport(**self.sport) if self.sport else self.sport + self.league = League(**self.league) if self.league else self.league + self.player = Person(**self.player) if self.player else self.player + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + + +@dataclass(kw_only=True, repr=False) +class Stat: + """ + Base class for stats + + Attributes + ---------- + group : str + type of the stat group + type : str + type of the stat + totalsplits : int + The number of split objects + exemptions : list + not sure what this is + splits : list + a list of split objects + """ + group: str + type: str + totalsplits: int + exemptions: Optional[List] = field(default_factory=list) + splits: Optional[List] = field(default_factory=list) + + def __repr__(self): + return f'Stat(group={self.group}, type={self.type})' + +@dataclass(kw_only=True) +class PitchArsenal(Split): + """ + A class to represent a pitcharsenal stat for a hitter and pitcher + + Attributes + ---------- + """ + _stat = ['pitchArsenal'] + stat: Union[PitchArsenalSplit, dict] + + def __post_init__(self): + self.stat = PitchArsenalSplit(**self.stat) + + +@dataclass(kw_only=True) +class ZoneCodes: + """ + A class to represent a zone code statistic used in hot cold zones + + Attributes + ---------- + zone : str + zone code location + color : str + rgba code for the color of zone + temp : str + temp description of the zone + value : str + batting percentage of the zone + """ + zone: str + value: str + color: Optional[str] = None + temp: Optional[str] = None + +@dataclass(kw_only=True) +class Zones: + """ + A class to represent a hot cold zone statistic + + Attributes + ---------- + name : str + name of the hot cold zone + zones : List[ZoneCodes] + a list of zone codes to describe the zone + """ + name: str + zones: List[ZoneCodes] + + def __post_init__(self): + self.zones = [ZoneCodes(**zone) for zone in self.zones] + +@dataclass(kw_only=True) +class HotColdZones(Split): + """ + A class to represent a hotcoldzone statistic + + Attributes + ---------- + stat : Zones + the holdcoldzones for the stat + """ + stat: Zones + _stat = ['hotColdZones'] + + def __post_init__(self): + self.stat = Zones(**self.stat) + +@dataclass +class Chart: + """ + A class to represent a chart for SprayCharts + + Attributes + ---------- + leftfield : int + percentage + leftcenterfield : int + percentage + centerfield : int + percentage + rightcenterfield : int + percentage + rightfield : int + percentage + """ + leftfield: int + leftcenterfield: int + centerfield: int + rightcenterfield: int + rightfield: int + +@dataclass(kw_only=True) +class SprayCharts(Split): + + + _stat = ['sprayChart'] + stat: Union[Chart, dict] + batter: Optional[Union[Batter, dict]] = field(default_factory=dict) + + def __post_init__(self): + self.batter = Batter(**self.batter) if self.batter else self.batter + self.stat = Chart(**self.stat) + +@dataclass(kw_only=True) +class OutsAboveAverage(Split): + """ + A class to represent a outs above average statistic + + NOTE: This stat type returns a empty list, or keys with with the value 0 + """ + _stat = ['outsAboveAverage'] + attempts: int + totaloutsaboveaverageback: int + totaloutsaboveaveragebackunrounded: int + outsaboveaveragebackstraight: int + outsaboveaveragebackstraightunrounded: int + outsaboveaveragebackleft: int + outsaboveaveragebackleftunrounded: int + outsaboveaveragebackright: int + outsaboveaveragebackrightunrounded: int + totaloutsaboveaveragein: int + totaloutsaboveaverageinunrounded: int + outsaboveaverageinstraight: int + outsaboveaverageinstraightunrounded: int + outsaboveaverageinleft: int + outsaboveaverageinleftunrounded: int + outsaboveaverageinright: int + outsaboveaverageinrightunrounded: int + player: Union[Person, dict] + gametype: str + + +# +# These dataclasses are for the game stats end point only +# url: https://statsapi.mlb.com/api/v1/people/663728/stats/game/715757 +# The gamelog stats in this JSON have different keys set for their stat +# and group. This breaks my logic of handling stat classes +# + +@dataclass +class PlayerGameLogStat(Split): + """ + A class to represent a chart for SprayCharts + + Attributes + ---------- + """ + type: str + group: str + stat: dict + _stat = ['gameLog'] diff --git a/mlbstatsapi/models/stats/streak.py b/mlbstatsapi/models/stats/streak.py new file mode 100644 index 00000000..38fbb072 --- /dev/null +++ b/mlbstatsapi/models/stats/streak.py @@ -0,0 +1,4 @@ +from dataclasses import dataclass, field +from typing import Optional, Union + +from .stats import Splits \ No newline at end of file diff --git a/mlbstatsapi/models/stats/team.py b/mlbstatsapi/models/stats/team.py new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/mlbstatsapi/models/stats/team.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/mlbstatsapi/models/teams/__init__.py b/mlbstatsapi/models/teams/__init__.py new file mode 100644 index 00000000..091851d1 --- /dev/null +++ b/mlbstatsapi/models/teams/__init__.py @@ -0,0 +1,2 @@ +from .team import Team +from .attributes import TeamRecord \ No newline at end of file diff --git a/mlbstatsapi/models/teams/attributes.py b/mlbstatsapi/models/teams/attributes.py new file mode 100644 index 00000000..4bd92ca7 --- /dev/null +++ b/mlbstatsapi/models/teams/attributes.py @@ -0,0 +1,185 @@ +from typing import Union, Optional, List +from dataclasses import dataclass + +from mlbstatsapi.models.divisions import Division +from mlbstatsapi.models.leagues import League + +@dataclass +class Record: + """ + Record + + Attributes: + ___________ + wins : int + Number of wins + losses : int + Number of losses + pct : str + Percentage + """ + wins: int + losses: int + pct: str + +@dataclass +class OverallleagueRecord(Record): + """ + Overall League Record + + + Attributes: + ___________ + wins : int + Overall number of wins in league + losses : int + Overall number of losses in league + pct : str + Overall percentage in league + """ + ties: int + +@dataclass +class Typerecords(Record): + """ + Type records + + Attributes: + ___________ + wins : int + Number of wins in type + losses : int + Number of losses in type + pct : str + Percentage in type + type : str + Type of record + """ + type: str + +@dataclass +class Divisionrecords(Record): + """ + Division records + + Attributes: + ___________ + wins : int + Number of wins in division + losses : int + Number of losses in division + pct : str + Percentage in division + division : Divison + Division + """ + division: Union[Division, dict] + +@dataclass +class Leaguerecords(Record): + """ + League records + + Attributes: + ___________ + wins : int + Number of wins in league + losses : int + Number of losses in league + pct : str + Percentage in league + league : League + League + """ + league: Union[League, dict] + +@dataclass +class Records: + """" + A class representing the records of a team. + + Attributes: + ___________ + splitrecords : Typerecords + A list of split records + divisionrecords : Divisionrecords + A list of division records + overallrecords : Typerecords + A list of overall records + leaguerecords : Leaguerecords + A list of league records + expectedrecords : Typerecords + A list of expected records + """ + splitrecords: Optional[List[Union[Typerecords, dict]]] = None + divisionrecords: Optional[List[Union[Divisionrecords, dict]]] = None + overallrecords: Optional[List[Union[Typerecords, dict]]] = None + leaguerecords: Optional[List[Union[Leaguerecords, dict]]] = None + expectedrecords: Optional[List[Union[Typerecords, dict]]] = None + + def __post_init__(self): + self.splitrecords = [Typerecords(**splitrecord) for splitrecord in self.splitrecords] if self.splitrecords else None + self.divisionrecords = [Divisionrecords(**divisionrecord) for divisionrecord in self.divisionrecords] if self.divisionrecords else None + self.overallrecords = [Typerecords(**overallrecord) for overallrecord in self.overallrecords] if self.overallrecords else None + self.leaguerecords = [Leaguerecords(**leaguerecord) for leaguerecord in self.leaguerecords] if self.leaguerecords else None + self.expectedrecords = [Typerecords(**expectedrecord) for expectedrecord in self.expectedrecords] if self.expectedrecords else None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass(repr=False) +class TeamRecord: + """ + A class to represent a teams current record. + + Attributes + ---------- + gamesplayed: int + The number of games played by the team. + wildcardgamesback: str + The number of games behind the leader in the wild card race. + leaguegamesback: str + The number of games behind the leader in the league. + springleaguegamesback: str + The number of games behind the leader in the spring league. + sportgamesback: str + The number of games behind the leader in the sport. + divisiongamesback: str + The number of games behind the leader in the division. + conferencegamesback: str + The number of games behind the leader in the conference. + leaguerecord: OverallleagueRecord + The overall league record of the team. Can be an instance of the OverallleagueRecord class or a dictionary with relevant information about the record. + records: Records + The records of the team. Can be an instance of the Records class or a dictionary with relevant information about the records. + divisionleader: bool + A flag indicating whether the team is the leader in their division. + wins: int + The number of wins of the team. + losses: int + The number of losses of the team. + winningpercentage: str + The winning percentage of the team. + """ + gamesplayed: int + wildcardgamesback: str + leaguegamesback: str + springleaguegamesback: str + sportgamesback: str + divisiongamesback: str + conferencegamesback: str + leaguerecord: Union[OverallleagueRecord, dict] + records: Union[Records, dict] + divisionleader: bool + wins: int + losses: int + winningpercentage: str + + def __post_init__(self): + self.leaguerecord = OverallleagueRecord(**self.leaguerecord) + self.records = Records(**self.records) + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/teams/team.py b/mlbstatsapi/models/teams/team.py new file mode 100644 index 00000000..1bb8ff48 --- /dev/null +++ b/mlbstatsapi/models/teams/team.py @@ -0,0 +1,105 @@ +from typing import List, Dict, Union, Optional +from dataclasses import dataclass, field + +from mlbstatsapi.models.leagues import League +from mlbstatsapi.models.venues import Venue +from mlbstatsapi.models.divisions import Division +from mlbstatsapi.models.sports import Sport + +from .attributes import TeamRecord +# from mlbstatsapi.models.standings import Teamrecords + + +@dataclass(repr=False) +class Team: + """ + A class to represent a Team. + + Attributes + ---------- + id : int + id number of the team + name : str + name of the team + link : str + The API link for the team + springleague : League + The spring league of the team + allstarstatus : str + The all status status of the team + season : str + The team's current season + venue : Venue + The team's home venue + springvenue : Venue + The team's spring venue + teamcode : str + team code + filecode : str + filecode name of the team + abbreviation : str + The abbreviation of the team name + teamname : str + The team name + locationname : str + The location of the team + firstyearofplay : str + The first year the team began play + league : League + The league of the team + division : Division + The division the team is in + sport : Sport + The sport of the team + shortname : str + The shortname of the team + record : TeamRecord + The record of the team + franchisename : str + The franchisename of the team + clubname : str + The clubname of the team + active : str + Active status of the team + parentorgname : str + The name of the parent team or org + parentorgid : str + The id of the partent team or org + """ + id: int + link: str + name: Optional[str] = field(default_factory=dict) + springleague: Union[League, dict] = field(default_factory=dict) + allstarstatus: Optional[str] = None + season: Optional[str] = None + venue: Union[Venue, dict] = field(default_factory=dict) + springvenue: Union[Venue, dict] = field(default_factory=dict) + teamcode: Optional[str] = None + filecode: Optional[str] = None + abbreviation: Optional[str] = None + teamname: Optional[str] = None + locationname: Optional[str] = None + firstyearofplay: Optional[str] = None + league: Union[League, dict] = field(default_factory=dict) + division: Union[Division, dict] = field(default_factory=dict) + sport: Union[Sport, dict] = field(default_factory=dict) + shortname: Optional[str] = None + record: Union[TeamRecord, dict] = None + franchisename: Optional[str] = None + clubname: Optional[str] = None + active: Optional[str] = None + parentorgname: Optional[str] = None + parentorgid: Optional[str] = None + + def __post_init__(self): + self.springleague = League(**self.springleague) if self.springleague else self.springleague + self.venue = Venue(**self.venue) if self.venue else self.venue + self.springvenue = Venue(**self.springvenue) if self.springvenue else self.springvenue + self.league = League(**self.league) if self.league else self.league + self.division = Division(**self.division) if self.division else self.division + self.record = TeamRecord(**self.record) if self.record else self.record + self.sport = Sport(**self.sport) if self.sport else self.sport + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/venues/__init__.py b/mlbstatsapi/models/venues/__init__.py new file mode 100644 index 00000000..4cefb86c --- /dev/null +++ b/mlbstatsapi/models/venues/__init__.py @@ -0,0 +1 @@ +from .venue import Venue \ No newline at end of file diff --git a/mlbstatsapi/models/venues/attributes.py b/mlbstatsapi/models/venues/attributes.py new file mode 100644 index 00000000..b89626ee --- /dev/null +++ b/mlbstatsapi/models/venues/attributes.py @@ -0,0 +1,121 @@ +from typing import Optional, Union +from dataclasses import dataclass, field + +@dataclass +class VenueDefaultCoordinates: + """ + A class to represent a venue. + + Attributes + ---------- + latitude : float + The latatude coordinate for this venue + longitude : float + The longitude coordinate for this venue + """ + latitude: float + longitude: float + +@dataclass(repr=False) +class Location: + """ + A class to represent a Location used by venue. + + Attributes + ---------- + address1 : str + Venues first address line + address2 : str + Venues second address line + city : str + City the venue is in + state : str + The State the venue is in + stateAbbrev : str + The staes abbreviation + postalCode : str + Postal code for this venue + defaultCoordinates : VenueDefaultCoordinates + Long and lat for this venues location + country : str + What country this venue is in + phone : str + Phone number for this venue + """ + city: str + country: str + stateabbrev: Optional[str] = None + address1: Optional[str] = None + state: Optional[str] = None + postalcode: Optional[str] = None + phone: Optional[str] = None + address2: Optional[str] = None + defaultcoordinates: Optional[Union[VenueDefaultCoordinates, dict]] = field(default_factory=dict) + + def __post_init__(self): + self.defaultcoordinates = VenueDefaultCoordinates(**self.defaultcoordinates) if self.defaultcoordinates else self.defaultcoordinates + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) + +@dataclass +class TimeZone: + """ + A class to represent a TimeZone Used by venue. + + Attributes + ---------- + id : str + id string for a venues timezone + offset : int + The offset for this timezone from + tz : str + Timezone string + """ + id: str + offset: int + tz: str + +@dataclass(repr=False) +class FieldInfo: + """ + A class to represent a venue Field info. + + Attributes + ---------- + capacity : int + Capacity for this venue + turfType : str + The type of turf in this venue + roofType : str + What kind of roof for this venue + leftLine : int + Distance down the left line + left : int + Distance to left + leftCenter : int + Distance to left center + center : int + Distance to center + rightCenter : int + Distance to right center + right : int + Distance to right + rightLine : int + Distance to right line + """ + capacity: Optional[int] = None + turftype: Optional[str] = None + rooftype: Optional[str] = None + leftline: Optional[int] = None + left: Optional[int] = None + leftcenter: Optional[int] = None + center: Optional[int] = None + rightcenter: Optional[int] = None + right: Optional[int] = None + rightline: Optional[int] = None + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/mlbstatsapi/models/venues/venue.py b/mlbstatsapi/models/venues/venue.py new file mode 100644 index 00000000..076f4d58 --- /dev/null +++ b/mlbstatsapi/models/venues/venue.py @@ -0,0 +1,42 @@ +from typing import Optional, Union +from dataclasses import dataclass +from .attributes import Location, TimeZone, FieldInfo + +@dataclass(repr=False) +class Venue: + """ + A class to represent a venue. + + Attributes + ---------- + id : int + id for this venue + name : str + Name for this venue + link : str + Link to venues endpoint + location : Location + Location for this venue + timezone : TimeZone + Timezone for this venue + fieldinfo : FieldInfo + Info on this venue's field + active : bool + Is this field currently active + """ + id: int + link: str + name: Optional[str] = None + location: Optional[Union[Location, dict]] = None + timezone: Optional[Union[TimeZone, dict]] = None + fieldinfo: Optional[Union[FieldInfo, dict]] = None + active: Optional[bool] = None + + def __post_init__(self): + self.location = Location(**self.location) if self.location else self.location + self.timezone = TimeZone(**self.timezone) if self.timezone else self.timezone + self.fieldinfo = FieldInfo(**self.fieldinfo) if self.fieldinfo else self.fieldinfo + + def __repr__(self) -> str: + kws = [f'{key}={value}' for key, value in self.__dict__.items() if value is not None and value] + return "{}({})".format(type(self).__name__, ", ".join(kws)) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..d737086c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "python-mlb-statsapi" +version = "0.5.2" + +authors = [ + { name="Matthew Spah", email="spahmatthew@gmail.com" }, + { name="Kristian Nilssen", email="krinilssen@gmail.com" }, +] +description = "mlbstatsapi python wrapper" +readme = "README.md" +license = { file="LICENSE" } +requires-python = ">=3.10" +dependencies = [ + "requests>=2", + "requests_mock>=1.10.0" +] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +[project.urls] +"Homepage" = "https://github.com/zero-sum-seattle/python-mlb-statsapi" +"Bug Tracker" = "https://github.com/zero-sum-seattle/python-mlb-statsapi/issues" diff --git a/tests/external_tests/attendance/test_attendance.py b/tests/external_tests/attendance/test_attendance.py new file mode 100644 index 00000000..b5ee5525 --- /dev/null +++ b/tests/external_tests/attendance/test_attendance.py @@ -0,0 +1,38 @@ +import unittest +from unittest.mock import Mock, patch +from mlbstatsapi.models.attendances import Attendance, attendance +from mlbstatsapi import Mlb + +class TestAttendance(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + params = {'season': 2022} + cls.attendance_team_away = cls.mlb.get_attendance(team_id=113) + cls.attendance_team_home = cls.mlb.get_attendance(team_id=134) + cls.attendance_season = cls.mlb.get_attendance(team_id=113, params=params) + + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_attendance_instance_type_error(self): + with self.assertRaises(TypeError): + attendance = Attendance() + + def test_attendance_instance_position_arguments(self): + self.assertEqual(self.attendance_team_away.records[0].team.id, 113) + self.assertEqual(self.attendance_team_home.records[0].team.id, 134) + self.assertEqual(self.attendance_season.records[0].team.id, 113) + + def test_attendance_has_attributes(self): + self.assertIsInstance(self.attendance_team_away, Attendance) + self.assertIsInstance(self.attendance_team_home, Attendance) + self.assertIsInstance(self.attendance_season, Attendance) + self.assertTrue(hasattr(self.attendance_team_away, "records")) + self.assertTrue(hasattr(self.attendance_team_away, "aggregatetotals")) + self.assertTrue(hasattr(self.attendance_team_home, "records")) + self.assertTrue(hasattr(self.attendance_team_home, "aggregatetotals")) + self.assertTrue(hasattr(self.attendance_season, "records")) + self.assertTrue(hasattr(self.attendance_season, "aggregatetotals")) diff --git a/tests/external_tests/awards/test_awards.py b/tests/external_tests/awards/test_awards.py new file mode 100644 index 00000000..f2bcc72b --- /dev/null +++ b/tests/external_tests/awards/test_awards.py @@ -0,0 +1,69 @@ +import unittest +from mlbstatsapi.models.awards import Award +from mlbstatsapi import Mlb + + +class TestAwards(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_awards(self): + """This test should return a 200 and Round""" + + # set draft id + award_id = "RETIREDUNI_108" + + # call get_draft return list + awards = self.mlb.get_awards(award_id) + + # draft should not be None + self.assertIsNotNone(awards) + + # list should not be empty + self.assertNotEqual(awards, []) + + # items in list should be an award + self.assertIsInstance(awards[0], Award) + + award = awards[0] + + # award should not be none + self.assertIsNotNone(award) + + # award should have attrs set + self.assertTrue(award.id) + self.assertTrue(award.name) + + def test_get_awards_404(self): + """This test should return a 200 and """ + + # set award id to invalid award + award_id = "THIS_IS_NOT_AN_AWARDID" + + # call get_awards return list + awards = self.mlb.get_awards(award_id) + + # awards should not be None + self.assertIsNotNone(awards) + + # list should be empty + self.assertEqual(awards, []) + + + def test_get_awards_with_params(self): + """this test should return results for retired uniform number awards""" + + award_id = "RETIREDUNI_108" + leagueId = "103" + + # call get_awards return list + awards = self.mlb.get_awards(award_id, leagueId=leagueId) + # awards should not be None + self.assertIsNotNone(awards) + # list should not be empty + self.assertNotEqual(awards, []) diff --git a/tests/external_tests/division/test_division.py b/tests/external_tests/division/test_division.py new file mode 100644 index 00000000..da1f0249 --- /dev/null +++ b/tests/external_tests/division/test_division.py @@ -0,0 +1,38 @@ +import unittest +from mlbstatsapi.models.divisions import Division +from mlbstatsapi import Mlb + + +class TestDivision(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.division = cls.mlb.get_division(200) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_divisions_instance_type_error(self): + with self.assertRaises(TypeError): + division = Division() + + def test_divisions_instance_position_arguments(self): + self.assertEqual(self.division.id, 200) + self.assertEqual(self.division.link, "/api/v1/divisions/200") + self.assertEqual(self.division.name, "American League West") + + def test_divisions_has_attributes(self): + self.assertIsInstance(self.division, Division) + self.assertTrue(hasattr(self.division, "id")) + self.assertTrue(hasattr(self.division, "name")) + self.assertTrue(hasattr(self.division, "link")) + self.assertTrue(hasattr(self.division, "season")) + self.assertTrue(hasattr(self.division, "nameshort")) + self.assertTrue(hasattr(self.division, "abbreviation")) + self.assertTrue(hasattr(self.division, "league")) + self.assertTrue(hasattr(self.division, "sport")) + self.assertTrue(hasattr(self.division, "haswildcard")) + self.assertTrue(hasattr(self.division, "sortorder")) + self.assertTrue(hasattr(self.division, "numplayoffteams")) + self.assertTrue(hasattr(self.division, "active")) diff --git a/tests/external_tests/drafts/test_draft.py b/tests/external_tests/drafts/test_draft.py new file mode 100644 index 00000000..93ac81e8 --- /dev/null +++ b/tests/external_tests/drafts/test_draft.py @@ -0,0 +1,79 @@ +import unittest +from mlbstatsapi import Mlb +from mlbstatsapi.models.drafts import Round + + +class TestRound(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_draft_by_year_id(self): + """This test should return a 200 and Round""" + + # set draft id + draft_id = "2019" + + # call get_draft return list + draft = self.mlb.get_draft(draft_id) + + # draft should not be None + self.assertIsNotNone(draft) + + # list should not be empty + self.assertNotEqual(draft, []) + + # items in list should be Round + self.assertIsInstance(draft[0], Round) + + draftpicks = draft[0].picks + + # draftpicks should not be none + self.assertIsNotNone(draftpicks) + + # list should not be empty + self.assertNotEqual(draftpicks, []) + + draftpick = draftpicks[0] + + # draft pick should have attrs set + self.assertTrue(draftpick.pickround) + + def test_get_draft_by_year_id_404(self): + """This test should return a 200 and """ + + # set draft id to invalid year + draft_id = "20192" + + # call get_draft return list + draft = self.mlb.get_draft(draft_id) + + # draft should not be None + self.assertIsNotNone(draft) + + # list should be empty + self.assertEqual(draft, []) + + + def test_get_draft_with_params(self): + """this test should return results for draft round 1 2019""" + + draft_id = "2019" + round = "1" + + # call get_draft return list + draft = self.mlb.get_draft(draft_id, round=round) + # draft should not be None + self.assertIsNotNone(draft) + # list should not be empty and return len one + self.assertNotEqual(draft, []) + self.assertTrue(len(draft) == 1) + + # round attr should equal 1 + one_round = draft[0] + self.assertTrue(one_round.round == "1") + diff --git a/tests/external_tests/game/test_game.py b/tests/external_tests/game/test_game.py new file mode 100644 index 00000000..8a75448f --- /dev/null +++ b/tests/external_tests/game/test_game.py @@ -0,0 +1,31 @@ +import unittest +from mlbstatsapi.mlb_api import Mlb +from mlbstatsapi.models.game import Game + +# Game with id of 662242 is used for this testing. +# +# 662242 info: +# Cincinnati Reds (id:113) at Pittsburgh Pirates (id:134) +# 2022-09-26 at 6:35 pm +# 8766 attended with duration of 185 minutes and 38 minutes of delay +# Pirates win 8 - 3 + + +class TestGame(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.game = cls.mlb.get_game(662242) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_game_creation(self): + self.assertEqual(self.game.id, 662242) + self.assertIsInstance(self.game, Game) + + def test_game_attrs(self): + self.assertTrue(hasattr(self.game, "metadata")) + self.assertTrue(hasattr(self.game, "gamedata")) + self.assertTrue(hasattr(self.game, "livedata")) diff --git a/tests/external_tests/gamepace/test_gamepace.py b/tests/external_tests/gamepace/test_gamepace.py new file mode 100644 index 00000000..68d44fda --- /dev/null +++ b/tests/external_tests/gamepace/test_gamepace.py @@ -0,0 +1,67 @@ +import unittest +from mlbstatsapi.models.gamepace import Gamepace, Gamepacedata +from mlbstatsapi import Mlb + + +# class TestGamepace(unittest.TestCase): +# @classmethod +# def setUpClass(cls) -> None: +# cls.mlb = Mlb() + +# @classmethod +# def tearDownClass(cls) -> None: +# pass + +# def test_get_gamepace(self): +# """This test should return a 200 and Round""" + +# # set draft id +# season_id = 2021 + +# # call get_gamepace return Gamepace object +# gamepace = self.mlb.get_gamepace(season_id) + +# # Gamepace should not be None +# self.assertIsNotNone(gamepace) + +# self.assertIsInstance(gamepace, Gamepace) + +# # list should not be empty +# self.assertNotEqual(gamepace.sports, []) + +# # items in list should be gamepace data +# self.assertIsInstance(gamepace.sports[0], Gamepacedata) + +# sportgamepace = gamepace.sports[0] + +# # sportgamepace should not be none +# self.assertIsNotNone(sportgamepace) + +# # sportgamepace should have attrs set +# self.assertTrue(sportgamepace.hitspergame) +# self.assertTrue(sportgamepace.totalgames) + +# def test_get_gamepace_404(self): +# """This test should return a 200 and """ + +# # set gamepace season to invalid year +# season_id = '2040,21' + +# # call get_gamepace return Gamepace object +# gamepace = self.mlb.get_gamepace(season_id) + +# # gamepace should be None +# self.assertIsNone(gamepace) + +# def test_get_gamepace_with_params(self): +# """this test should return results for gamepace with leagueid 103""" + +# season_id = 2021 +# leagueId = "103" + +# # call get_gamepace return gamepace object +# gamepace = self.mlb.get_gamepace(season_id, leagueId=leagueId) +# # gamepace should not be None +# self.assertIsNotNone(gamepace) +# # list should not be empty +# self.assertNotEqual(gamepace.leagues, []) \ No newline at end of file diff --git a/tests/external_tests/homerunderby/test_homerunderby.py b/tests/external_tests/homerunderby/test_homerunderby.py new file mode 100644 index 00000000..a145f7e5 --- /dev/null +++ b/tests/external_tests/homerunderby/test_homerunderby.py @@ -0,0 +1,45 @@ +import unittest +from mlbstatsapi.models.homerunderby import Homerunderby, Round +from mlbstatsapi import Mlb + + +class TestHomerunderby(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_gamepace(self): + """This test should return a 200 and Round""" + + # set draft id + game_id = 511101 + + # call get_gamepace return Gamepace object + derby = self.mlb.get_homerun_derby(game_id) + + # Gamepace should not be None + self.assertIsNotNone(derby) + + self.assertIsInstance(derby, Homerunderby) + + # list should not be empty + self.assertNotEqual(derby.rounds, []) + + # items in list should be gamepace data + self.assertIsInstance(derby.rounds[0], Round) + + def test_get_homerunderby_404(self): + """This test should return a 200 and """ + + # set gameid to invalid id + game_id = '100394810242' + + # call get_gamepace return Gamepace object + derby = self.mlb.get_homerun_derby(game_id) + + # gamepace should be None + self.assertIsNone(derby) \ No newline at end of file diff --git a/tests/external_tests/league/test_league.py b/tests/external_tests/league/test_league.py new file mode 100644 index 00000000..ced2d979 --- /dev/null +++ b/tests/external_tests/league/test_league.py @@ -0,0 +1,46 @@ +import unittest +from mlbstatsapi.models.leagues import League +from mlbstatsapi import Mlb + + +class TestLeague(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.league = cls.mlb.get_league(103) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_league_instance_type_error(self): + with self.assertRaises(TypeError): + league = League() + + def test_league_instance_position_arguments(self): + self.assertEqual(self.league.id, 103) + self.assertEqual(self.league.link, "/api/v1/league/103") + self.assertEqual(self.league.name, "American League") + + def test_league_has_attributes(self): + self.assertIsInstance(self.league, League) + self.assertTrue(hasattr(self.league, "id")) + self.assertTrue(hasattr(self.league, "name")) + self.assertTrue(hasattr(self.league, "link")) + self.assertTrue(hasattr(self.league, "abbreviation")) + self.assertTrue(hasattr(self.league, "nameshort")) + self.assertTrue(hasattr(self.league, "seasonstate")) + self.assertTrue(hasattr(self.league, "haswildcard")) + self.assertTrue(hasattr(self.league, "hassplitseason")) + self.assertTrue(hasattr(self.league, "numgames")) + self.assertTrue(hasattr(self.league, "hasplayoffpoints")) + self.assertTrue(hasattr(self.league, "numteams")) + self.assertTrue(hasattr(self.league, "numwildcardteams")) + self.assertTrue(hasattr(self.league, "seasondateinfo")) + self.assertTrue(hasattr(self.league, "season")) + self.assertTrue(hasattr(self.league, "orgcode")) + self.assertTrue(hasattr(self.league, "conferencesinuse")) + self.assertTrue(hasattr(self.league, "divisionsinuse")) + self.assertTrue(hasattr(self.league, "sport")) + self.assertTrue(hasattr(self.league, "sortorder")) + self.assertTrue(hasattr(self.league, "active")) diff --git a/tests/external_tests/mlb/test_mlb.py b/tests/external_tests/mlb/test_mlb.py new file mode 100644 index 00000000..f799ab2a --- /dev/null +++ b/tests/external_tests/mlb/test_mlb.py @@ -0,0 +1,317 @@ +from typing import Dict, List +from unittest.mock import patch +import unittest +import requests +import json +import os + +from mlbstatsapi.models.people import Person +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.game import Game, Plays, Linescore, BoxScore +from mlbstatsapi.models.venues import Venue +from mlbstatsapi.models.sports import Sport +from mlbstatsapi.models.leagues import League +from mlbstatsapi.models.divisions import Division +from mlbstatsapi.models.schedules import Schedule + +from mlbstatsapi import Mlb +from mlbstatsapi import MlbResult +from mlbstatsapi import TheMlbStatsApiException + +class TestMlbDataApi(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + pass + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_mlb_adapter_200(self): + mlbdata = self.mlb._mlb_adapter_v1.get("/divisions") + self.assertIsInstance(mlbdata, MlbResult) + self.assertEqual(mlbdata.status_code, 200) + self.assertIsInstance(mlbdata.data, Dict) + + def test_mlb_adapter_500(self): + """mlb should raise a exception when adapter returns a 500""" + with self.assertRaises(TheMlbStatsApiException): + self.mlb._mlb_adapter_v1.get(endpoint="teams/133/stats?stats=vsPlayer&group=catching") + + def test_mlb_adapter_400(self): + """mlb should return a MlbResult object with a empty data, and a status code""" + # invalid endpoint + mlbdata = self.mlb._mlb_adapter_v1.get(endpoint="teams/19990") + + # result.status_code should be 404 + self.assertEqual(mlbdata.status_code, 404) + + # result.data should be None + self.assertEqual(mlbdata.data, {}) + + +class TestMlbGetPeople(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + pass + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_mlbdataapi_get_people(self): + """mlb get_people should return a list of all sport 1 people""" + mlbdata = self.mlb.get_people() + self.assertIsInstance(mlbdata, List) + self.assertIsInstance(mlbdata[0], Person) + + def test_mlbdataapi_get_people_with_sportid(self): + """mlb get_people should return a list of all sport 1 people""" + mlbdata = self.mlb.get_people(sport_id=11) + self.assertIsInstance(mlbdata, List) + self.assertIsInstance(mlbdata[0], Person) + + def test_mlb_get_person(self): + """mlb get_person should return a Person object""" + person = self.mlb.get_person('664034') + self.assertIsInstance(person, Person) + self.assertEqual(person.id, 664034) + + def test_mlb_failed_get_person(self): + """mlb get_person should return None for invalid id""" + person = self.mlb.get_person('664') + self.assertIsNone(person) + + def test_mlb_get_person_id(self): + """mlb get_person_id should return a person id""" + id = self.mlb.get_people_id('Ty France') + self.assertEqual(id, [664034]) + + def test_mlb_get_person_id_with_sportid(self): + """mlb get_person_id should return a person id""" + id = self.mlb.get_people_id('Fernando Abad', sport_id=11) + self.assertEqual(id, [472551]) + + def test_mlb_get_invalid_person_id(self): + """mlb get_person_id should return empty list for invalid name""" + id = self.mlb.get_people_id('Joe Blow') + self.assertEqual(id, []) + + +class TestMlbGetTeam(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_mlb_get_team(self): + """mlb get_team should return Team object""" + team = self.mlb.get_team('133') + self.assertIsInstance(team, Team) + self.assertEqual(team.id, 133) + + def test_mlbdataapi_get_teams(self): + """mlb get_teams should return a list of Teams""" + mlbdata = self.mlb.get_teams() + self.assertIsInstance(mlbdata, List) + self.assertIsInstance(mlbdata[0], Team) + + def test_mlbdataapi_get_teams_with_sportid(self): + """mlb get_teams should return a list of Teams""" + mlbdata = self.mlb.get_teams(sport_id=16) + self.assertIsInstance(mlbdata, List) + self.assertIsInstance(mlbdata[0], Team) + + def test_mlb_failed_get_team(self): + """mlb get_team should return None for invalid team id""" + team = self.mlb.get_team('19999') + self.assertIsNone(team) + + def test_mlb_get_team_id(self): + """mlb get_team_id should return a list of matching team id's""" + id = self.mlb.get_team_id('Seattle Mariners') + self.assertEqual(id, [136]) + + def test_mlb_get_team_minor_id(self): + """mlb get_team_id should return a list of matching team id's""" + id = self.mlb.get_team_id('DSL Brewers 2') + self.assertEqual(id, [2101]) + + def test_mlb_get_bad_team_id(self): + """mlb get_team_id should return a empty list for invalid team name""" + id = self.mlb.get_team_id('Banananananana') + self.assertEqual(id, []) + + +class TestMlbGetSport(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + pass + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_sport(self): + """mlb get_sport should return a Sport Object""" + sport = self.mlb.get_sport(1) + self.assertIsInstance(sport, Sport) + self.assertEqual(sport.id, 1) + + def test_get_sports(self): + """mlb get_sports should return a list of sport objects""" + sports = self.mlb.get_sports() + self.assertIsInstance(sports, List) + self.assertIsInstance(sports[0], Sport) + + def test_get_sport_id(self): + """mlb get_sport id should return a sport id""" + id = self.mlb.get_sport_id('Major League Baseball') + self.assertEqual(id, [1]) + + def test_get_sport_invalid_name(self): + """mlb get_sport should return a empty list for invalid sport name""" + id = self.mlb.get_sport_id('NFL') + self.assertEqual(id, []) + + +class TestMlbGetLeague(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + pass + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_league(self): + """mlb get_league should return a League object""" + league = self.mlb.get_league(103) + self.assertIsInstance(league, League) + self.assertEqual(league.id, 103) + + def test_get_leagues(self): + """mlb get_leagues should return a list of Leagues""" + leagues = self.mlb.get_leagues() + self.assertIsInstance(leagues, List) + self.assertIsInstance(leagues[0], League) + + def test_get_league_id(self): + """mlb get_league_id should return a league id""" + id = self.mlb.get_league_id('American League') + self.assertEqual(id, [103]) + + def test_get_invalid_league_id(self): + """mlb get_league_id should return a empty list with invalid league name""" + id = self.mlb.get_league_id('Russian League') + self.assertEqual(id, []) + + +class TestMlbGetDivision(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + pass + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_division(self): + """mlb get_division should return a Division object""" + division = self.mlb.get_division(200) + self.assertIsInstance(division, Division) + self.assertEqual(division.id, 200) + + def test_get_divisions(self): + """mlb get_divisions should return a list of Divisions""" + divisions = self.mlb.get_divisions() + self.assertIsInstance(divisions, List) + self.assertIsInstance(divisions[0], Division) + + def test_get_division_id(self): + """mlb get_division_id should return a division id""" + id = self.mlb.get_division_id('American League West') + self.assertEqual(id, [200]) + + def test_get_division_fail_id(self): + """mlb get_division_id should return a empty list for invalid division name""" + id = self.mlb.get_division_id('Canada West') + self.assertEqual(id, []) + + +class TestMlbGetVenue(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + pass + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_mlb_get_venue(self): + """mlb get_division should return a Division object""" + venue = self.mlb.get_venue(31) + self.assertIsInstance(venue, Venue) + self.assertEqual(venue.id, 31) + + def test_get_venues(self): + """mlb get_divisions should return a list of Divisions""" + venues = self.mlb.get_venues() + self.assertIsInstance(venues, List) + self.assertIsInstance(venues[0], Venue) + + def test_mlb_get_venue_id(self): + """mlb get_division_id should return a division id""" + id = self.mlb.get_venue_id('PNC Park') + self.assertEqual(id, [31]) + + def test_get_venue_id(self): + """mlb get_division_id should return a empty list for invalid venue name""" + id = self.mlb.get_venue_id('Highschool Park') + self.assertEqual(id, []) + + +class TestMlbGetGame(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + pass + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_mlb_get_game(self): + game = self.mlb.get_game(662242) + self.assertIsInstance(game, Game) + self.assertEqual(game.id, 662242) + + def test_get_game_playByPlay(self): + playbyplay = self.mlb.get_game_play_by_play(662242) + self.assertIsInstance(playbyplay, Plays) + + def test_get_game_linescore(self): + linescore = self.mlb.get_game_line_score(662242) + self.assertIsInstance(linescore, Linescore) + + def test_get_game_boxscore(self): + boxscore = self.mlb.get_game_box_score(662242) + self.assertIsInstance(boxscore, BoxScore) + + def test_get_attendance(self): + params = {'season': 2022} + attendance_team_away = self.mlb.get_attendance(team_id=113) + attendance_team_home = self.mlb.get_attendance(team_id=134) + attendance_season = self.mlb.get_attendance(team_id=113, params=params) + self.assertEqual(attendance_team_away.records[0].team.id, 113) + self.assertEqual(attendance_team_home.records[0].team.id, 134) + self.assertEqual(attendance_season.records[0].team.id, 113) diff --git a/tests/external_tests/mlbdataadapter/test_mlbadapter.py b/tests/external_tests/mlbdataadapter/test_mlbadapter.py new file mode 100644 index 00000000..9c0d59f6 --- /dev/null +++ b/tests/external_tests/mlbdataadapter/test_mlbadapter.py @@ -0,0 +1,151 @@ +import unittest +import requests +from unittest.mock import patch +from typing import List, Dict +from mlbstatsapi import MlbDataAdapter, TheMlbStatsApiException + + +class TestMlbAdapter(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb_adapter = MlbDataAdapter() + + @classmethod + def tearDownClass(cls) -> None: + pass + + # I don't know if this is best practice + def lower_keys_in_response(self, data): + if isinstance(data, Dict): + for key, value in data.items(): + self.assertTrue(key.islower()) + self.lower_keys_in_response(value) + + elif isinstance(data, List): + for item in data: + self.lower_keys_in_response(item) + + def test_mlbadapter_get_200(self): + """mlbadapter should return 200 and data for sports endpoint""" + + # pretty stable external + result = self.mlb_adapter.get(endpoint="sports") + + # status code should be 200 + self.assertEqual(result.status_code, 200) + + # data should not be None + self.assertTrue(result.data) + + def test_mlbadapter_get_400(self): + """mlbadapter should return 404, and result.data should be None""" + + # invalid endpoint + result = self.mlb_adapter.get(endpoint="teams/19990") + + # result.status_code should be 404 + self.assertEqual(result.status_code, 404) + + # result.data should be None + self.assertEqual(result.data, {}) + + def test_mlbadapter_get_500(self): + """mlbadapter should raise TheMlbStatsApiException for 500""" + + # bad endpoint should raise exception due to 500 + with self.assertRaises(TheMlbStatsApiException): + result = self.mlb_adapter.get(endpoint="teams/133/stats?stats=vsPlayer&group=catching") + + def test_mlbadapter_get_params(self): + """mlbadapter should accept params and parse them to the url""" + + # stat type season, stat group hitting + self.params = {"stats": "season", "group": "hitting"} + + # use team stats end point for params + result = self.mlb_adapter.get(endpoint="teams/133/stats", ep_params=self.params) + + # result should return 200 + self.assertEqual(result.status_code, 200) + + # result should have data + self.assertTrue(result.data) + + def test_mlbadapter_transform_keys_in_data(self): + """mlbadapter transform keys should make all keys lowercase""" + + # pretty stable external + result = self.mlb_adapter.get(endpoint="sports") + + # status code should be 200 + self.assertEqual(result.status_code, 200) + + # data should not be None + self.assertTrue(result.data) + + # data should all be lowercase + self.lower_keys_in_response(result.data) + + +class MlbAdapterMockTesting(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb_adapter = MlbDataAdapter() + cls.response = requests.Response() + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_mlbadapter_mock_bad_json(self): + """mlbadapter should raise TheMlbStatsApiException""" + # setting up mock + self.response.status_code = 200 + self.response._content = '{"some bad json": sdfsd'.encode() + + # params to pass + self.params = {"stats": "season", "group": "hitting"} + + # patch mlbdataadapter to return bad JSON + with patch("mlbstatsapi.mlb_dataadapter.requests.get", return_value=self.response): + + # mlb_adapter should raise exception due to bad JSON + with self.assertRaises(TheMlbStatsApiException): + result = self.mlb_adapter.get(endpoint="teams/133/stats", ep_params=self.params) + + def test_mlbadapter_mock_404_json(self): + """mlbadapter should raise TheMlbStatsApiException""" + # setting up mock + self.response.status_code = 404 + self.response._content = '{ "messageNumber": 10, "message": "Object not found", "timestamp": "2022-10-13T18:16:41.886604Z", "traceId": null }'.encode() + + # params to pass + self.params = { "stats": "standard", "group": "hitting" } + + # patch mlbdataadapter to return 404 response + with patch("mlbstatsapi.mlb_dataadapter.requests.get", return_value=self.response): + + # mlb_adapter should raise exception due to bad JSON + result = self.mlb_adapter.get(endpoint="teams/133/stats", ep_params=self.params) + + # result.status_code should be 404 + self.assertEqual(result.status_code, 404) + + # result.data should be None + self.assertEqual(result.data, {}) + + def test_mlbadapter_mock_500_json(self): + """mlbadapter should raise TheMlbStatsApiException""" + # setting up mock + self.response.status_code = 500 + self.response._content = '{ "messageNumber" : 1, "message" : "Internal error occurred", "timestamp" : "2022-10-13T18:37:47.600274Z", "traceId" : "9318607c0b50f493e9056648614a5cea" }'.encode() + + # params to pass + self.params = {"stats": "standard", "group": "hitting"} + + # patch mlbdataadapter to return mocked response + with patch("mlbstatsapi.mlb_dataadapter.requests.get", return_value=self.response): + + # mlb_adapter should raise exception due to 500 status code + with self.assertRaises(TheMlbStatsApiException): + result = self.mlb_adapter.get(endpoint="teams/133/stats", ep_params=self.params) \ No newline at end of file diff --git a/tests/external_tests/person/test_person.py b/tests/external_tests/person/test_person.py new file mode 100644 index 00000000..d2792172 --- /dev/null +++ b/tests/external_tests/person/test_person.py @@ -0,0 +1,59 @@ +import unittest +from mlbstatsapi.models.people import Person, Position +from mlbstatsapi import Mlb + + +class TestPerson(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.player = cls.mlb.get_person(664034) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_player_instance_type_error(self): + with self.assertRaises(TypeError): + player = Person() + + def test_player_instance_position_arguments(self): + self.assertEqual(self.player.id, 664034) + self.assertIsInstance(self.player, Person) + self.assertEqual(self.player.fullname, "Ty France") + self.assertEqual(self.player.link, "/api/v1/people/664034") + + def test_get_persons(self): + # set draft id + player_ids_l = [605151,592450] + player_ids_s = '605151,592450' + + # call get_persons return list of players objects + players_l = self.mlb.get_persons(player_ids_l) + players_s = self.mlb.get_persons(player_ids_s) + + # players should not be None + self.assertIsNotNone(players_l) + self.assertIsNotNone(players_s) + + # list should not be empty + self.assertNotEqual(players_l, []) + self.assertNotEqual(players_s, []) + + # items in list should be Person data + self.assertIsInstance(players_l[0], Person) + self.assertIsInstance(players_s[0], Person) + +class TestPersonPrimaryPosition(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.position_player = cls.mlb.get_person(664034) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_player_position_player_position(self): + self.assertIsInstance(self.position_player.primaryposition, Position) + self.assertTrue(hasattr(self.position_player.primaryposition, "code")) \ No newline at end of file diff --git a/tests/external_tests/schedule/test_schedule.py b/tests/external_tests/schedule/test_schedule.py new file mode 100644 index 00000000..d5a80856 --- /dev/null +++ b/tests/external_tests/schedule/test_schedule.py @@ -0,0 +1,32 @@ +import unittest +from mlbstatsapi.models.schedules import Schedule +from mlbstatsapi import Mlb + + +class TestSchedule(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.schedule = cls.mlb.get_schedule(date='2022-10-07') + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_schedule_instance_type_error(self): + with self.assertRaises(TypeError): + schedule = Schedule() + + def test_schedule_instance_position_arguments(self): + self.assertEqual(self.schedule.totalitems, 4) + self.assertEqual(self.schedule.totalevents, 0) + self.assertEqual(self.schedule.totalgames, 4) + + def test_schedule_has_attributes(self): + self.assertIsInstance(self.schedule, Schedule) + self.assertTrue(hasattr(self.schedule, "totalitems")) + self.assertTrue(hasattr(self.schedule, "totalevents")) + self.assertTrue(hasattr(self.schedule, "totalgames")) + self.assertTrue(hasattr(self.schedule, "totalgamesinprogress")) + self.assertTrue(hasattr(self.schedule, "dates")) + diff --git a/tests/external_tests/seasons/test_seasons.py b/tests/external_tests/seasons/test_seasons.py new file mode 100644 index 00000000..2da336a8 --- /dev/null +++ b/tests/external_tests/seasons/test_seasons.py @@ -0,0 +1,69 @@ +import unittest +from mlbstatsapi.models.seasons import Season +from mlbstatsapi import Mlb + + +class TestSeasons(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_season(self): + """get_season should return a season""" + season = self.mlb.get_season(season_id="2021") + self.assertIsInstance(season, Season) + + def test_get_season_leagueid(self): + """get_season should return a season for a sport""" + season = self.mlb.get_season(season_id="2022", sport_id=1, leagueId=11) + self.assertIsInstance(season, Season) + + def test_get_seasons_divisionId(self): + """get_seasons should return a season for a sport""" + seasons = self.mlb.get_seasons(sport_id=1, divisionId=11) + # test if seasons is a list + self.assertIsInstance(seasons, list) + self.assertNotEqual(seasons, []) + + # test if list contains seasons + season = seasons[0] + self.assertIsInstance(season, Season) + + def test_get_seasons_gametypes(self): + """get_season should return a Season object""" + seasons = self.mlb.get_seasons(sport_id=1, withGameTypeDates=True) + # test if seasons is a list + self.assertIsInstance(seasons, list) + self.assertNotEqual(seasons, []) + + # test if list contains seasons + season = seasons[0] + self.assertIsInstance(season, Season) + + def test_get_seasons_sportid(self): + """get_seasons should return a list of all seasons""" + seasons = self.mlb.get_seasons(sport_id=1) + + # test if seasons is a list + self.assertIsInstance(seasons, list) + self.assertNotEqual(seasons, []) + + # test if list contains seasons + season = seasons[0] + self.assertIsInstance(season, Season) + + def test_get_seasons_leagueId(self): + """get_seasons should return a list of all seasons""" + seasons = self.mlb.get_seasons(sport_id=1, leagueId=104) + + # test if seasons is a list + self.assertIsInstance(seasons, list) + self.assertNotEqual(seasons, []) + + # test if list contains seasons + season = seasons[0] + self.assertIsInstance(season, Season) diff --git a/tests/external_tests/sport/test_sport.py b/tests/external_tests/sport/test_sport.py new file mode 100644 index 00000000..865f750e --- /dev/null +++ b/tests/external_tests/sport/test_sport.py @@ -0,0 +1,33 @@ +import unittest +from mlbstatsapi.models.sports import Sport +from mlbstatsapi import Mlb + + +class TestSport(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.sport = cls.mlb.get_sport(1) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_sport_instance_type_error(self): + with self.assertRaises(TypeError): + sport = Sport() + + def test_sport_instance_position_arguments(self): + self.assertEqual(self.sport.id, 1) + self.assertEqual(self.sport.link, "/api/v1/sports/1") + self.assertEqual(self.sport.name, "Major League Baseball") + + def test_sport_attributes(self): + self.assertIsInstance(self.sport, Sport) + self.assertTrue(hasattr(self.sport, "id")) + self.assertTrue(hasattr(self.sport, "link")) + self.assertTrue(hasattr(self.sport, "name")) + self.assertTrue(hasattr(self.sport, "code")) + self.assertTrue(hasattr(self.sport, "abbreviation")) + self.assertTrue(hasattr(self.sport, "sortorder")) + self.assertTrue(hasattr(self.sport, "activestatus")) diff --git a/tests/external_tests/standings/test_standings.py b/tests/external_tests/standings/test_standings.py new file mode 100644 index 00000000..daae205e --- /dev/null +++ b/tests/external_tests/standings/test_standings.py @@ -0,0 +1,75 @@ +import unittest +from mlbstatsapi.models.standings import Standings +from mlbstatsapi import Mlb + + +class TestStandings(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_standings(self): + """This test should return a 200 and Round""" + + # set league id + league_id = 103 + # set season + season = 2018 + + # call get_standings return list of standings + standings = self.mlb.get_standings(league_id, season) + + # Gamepace should not be None + self.assertIsNotNone(standings) + + # list should not be empty + self.assertNotEqual(standings, []) + + # items in list should be standings + self.assertIsInstance(standings[0], Standings) + + standing = standings[0] + + # sportgamepace should not be none + self.assertIsNotNone(standing) + + # sportgamepace should have attrs set + self.assertTrue(standing.standingstype) + self.assertTrue(standing.lastupdated) + + def test_get_standings_404(self): + """This test should return a 200 and """ + + # set league id to invalid id + league_id = 1032 + # set season to invlaid season + season = 400 + + # call get_standings return list of standings + standings = self.mlb.get_standings(league_id, season) + + # standings should not be None + self.assertIsNotNone(standings) + + # list should be empty + self.assertEqual(standings, []) + + def test_get_standings_with_params(self): + """this test should return results for standings""" + + # set league id + league_id = 103 + # set season + season = 2018 + standingsTypes='wildCard,regularSeason' + + # call get_standings return list of standings + standings = self.mlb.get_standings(league_id, season, standingsTypes=standingsTypes) + # standings should not be None + self.assertIsNotNone(standings) + # list should not be empty + self.assertNotEqual(standings, []) \ No newline at end of file diff --git a/tests/external_tests/stats/test_catching.py b/tests/external_tests/stats/test_catching.py new file mode 100644 index 00000000..04bd23fd --- /dev/null +++ b/tests/external_tests/stats/test_catching.py @@ -0,0 +1,91 @@ +import unittest +import time + +from mlbstatsapi.mlb_api import Mlb + + +class TestCatchingStats(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.al_team = 133 + cls.shoei_ohtani = 660271 + cls.catching_player = 663728 + cls.stats_200_blank = ('projected', 'projectedRos', 'standard', 'advanced', 'firstYearStats', 'lastYearStats', + 'vsOpponents', 'outsAboveAverage', 'tracking', 'availableStats', 'gameTypeStats', 'vsOpponents') + cls.catching = 'catching' + cls.stats_500 = ('careerStatSplits', 'metricLog', 'metricAverages', 'statSplits', 'statSplitsAdvanced') + # these stat groups require a team with recent playoff appearences + cls.stats_playoffs = ('byMonthPlayoffs', 'byDayOfWeekPlayoffs', 'homeAndAwayPlayoffs', 'winLossPlayoffs') + # These stat groups require addition params passed like playerid or teamid + cls.stats_require_params = ('vsTeam', 'vsTeam5Y', 'vsTeamTotal', 'vsPlayer', 'vsPlayerTotal', 'vsPlayer5Y') + # These stat types should all return a stat split object for hitting and pitching stat groups + cls.catching = ( + "yearByYear", "yearByYearPlayoffs", "season", "career", "careerRegularSeason", + "careerPlayoffs", "gameLog", "lastXGames", "byDateRange", "byDateRangeAdvanced", + "byMonth", "byDayOfWeek", "homeAndAway", "winLoss", "atGameStart" + ) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_catching_stat_attributes_player(self): + """mlb get stats should return pitching stats""" + self.stats = ['season', 'career'] + self.group = ['catching'] + # let's get some stats + stats = self.mlb.get_player_stats(self.catching_player, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('catching' in stats) + self.assertFalse('hitting' in stats) + self.assertEqual(len(stats['catching']), 2) + + # check for split objects + self.assertTrue(stats['catching']['season']) + self.assertTrue(stats['catching']['career']) + + season = stats['catching']['season'] + career = stats['catching']['career'] + + self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.group, 'catching') + self.assertEqual(season.type, 'season') + + self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.group, 'catching') + self.assertEqual(career.type, 'career') + + def test_catching_stat_attributes_team(self): + """mlb get stats should return pitching stats""" + self.stats = ['season', 'career'] + self.group = ['catching'] + # let's get some stats + stats = self.mlb.get_team_stats(self.al_team, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('catching' in stats) + self.assertFalse('hitting' in stats) + self.assertEqual(len(stats['catching']), 2) + + # check for split objects + self.assertTrue(stats['catching']['season']) + self.assertTrue(stats['catching']['career']) + + season = stats['catching']['season'] + career = stats['catching']['career'] + + self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.group, 'catching') + self.assertEqual(season.type, 'season') + + self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.group, 'catching') + self.assertEqual(career.type, 'career') diff --git a/tests/external_tests/stats/test_fielding.py b/tests/external_tests/stats/test_fielding.py new file mode 100644 index 00000000..8d3b6bda --- /dev/null +++ b/tests/external_tests/stats/test_fielding.py @@ -0,0 +1,107 @@ +import unittest +import time + +from mlbstatsapi.mlb_api import Mlb + + +class TestPitchingStats(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.al_team = 133 + cls.shoei_ohtani = 660271 + cls.utility_player = 647351 + cls.stats_200_blank = ('projected', 'projectedRos', 'standard', 'advanced', 'firstYearStats', 'lastYearStats', + 'vsOpponents', 'outsAboveAverage', 'tracking', 'availableStats', 'gameTypeStats', 'vsOpponents') + cls.pitching = 'pitching' + cls.stats_500 = ('careerStatSplits', 'metricLog', 'metricAverages', 'statSplits', 'statSplitsAdvanced') + # these stat groups require a team with recent playoff appearences + cls.stats_playoffs = ('byMonthPlayoffs', 'byDayOfWeekPlayoffs', 'homeAndAwayPlayoffs', 'winLossPlayoffs') + # These stat groups require addition params passed like playerid or teamid + cls.stats_require_params = ('vsTeam', 'vsTeam5Y', 'vsTeamTotal', 'vsPlayer', 'vsPlayerTotal', 'vsPlayer5Y') + # These stat types should all return a stat split object for hitting and pitching stat groups + cls.fielding = ( + "yearByYear", "yearByYearPlayoffs", "season", "career", "careerRegularSeason", + "careerPlayoffs", "gameLog", "lastXGames", "byDateRange", "byDateRangeAdvanced", + "byMonth", "byDayOfWeek", "homeAndAway", "winLoss", "opponentsFaced", "atGameStart", + ) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_fielding_stat_attributes_player(self): + """mlb get stats should return pitching stats""" + self.stats = ['season', 'career'] + self.group = ['fielding'] + # let's get some stats + stats = self.mlb.get_player_stats(self.utility_player, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('fielding' in stats) + self.assertFalse('hitting' in stats) + self.assertEqual(len(stats['fielding']), 2) + + # check for split objects + self.assertTrue(stats['fielding']['season']) + self.assertTrue(stats['fielding']['career']) + + # let's pull out a object and test it + # check for split objects + self.assertTrue(stats['fielding']['season']) + self.assertTrue(stats['fielding']['career']) + + season = stats['fielding']['season'] + career = stats['fielding']['career'] + + self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.group, 'fielding') + self.assertEqual(season.type, 'season') + + self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.group, 'fielding') + self.assertEqual(career.type, 'career') + + def test_fielding_stat_attributes_team(self): + """mlb get stats should return pitching stats""" + self.stats = ['season', 'career', 'seasonAdvanced', 'careerAdvanced'] + self.group = ['fielding'] + # let's get some stats + stats = self.mlb.get_team_stats(self.al_team, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('fielding' in stats) + self.assertFalse('hitting' in stats) + self.assertEqual(len(stats['fielding']), 4) + # check for split objects + self.assertTrue(stats['fielding']['season']) + self.assertTrue(stats['fielding']['career']) + self.assertTrue(stats['fielding']['seasonadvanced']) + self.assertTrue(stats['fielding']['careeradvanced']) + + season = stats['fielding']['season'] + career = stats['fielding']['career'] + season_advanced = stats['fielding']['seasonadvanced'] + career_advanced = stats['fielding']['careeradvanced'] + + self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.group, 'fielding') + self.assertEqual(season.type, 'season') + + self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.group, 'fielding') + self.assertEqual(career.type, 'career') + + self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.group, 'fielding') + self.assertEqual(season_advanced.type, 'seasonAdvanced') + + self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.group, 'fielding') + self.assertEqual(career_advanced.type, 'careerAdvanced') diff --git a/tests/external_tests/stats/test_hitting.py b/tests/external_tests/stats/test_hitting.py new file mode 100644 index 00000000..1e199ef4 --- /dev/null +++ b/tests/external_tests/stats/test_hitting.py @@ -0,0 +1,348 @@ +import unittest +import time + +from mlbstatsapi.mlb_api import Mlb + + +class TestHittingStats(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.al_team = 133 + cls.shoei_ohtani = 660271 + cls.catching_player = 663728 + cls.ty_france = 664034 + cls.utility_player = 647351 + cls.soto = 665742 + cls.stats_200_blank = ('projected', 'projectedRos', 'standard', 'advanced', 'firstYearStats', 'lastYearStats', + 'vsOpponents', 'outsAboveAverage', 'tracking', 'availableStats', 'gameTypeStats', 'vsOpponents') + cls.hitting = 'hitting' + cls.stats_500 = ('careerStatSplits', 'metricLog', 'metricAverages', 'statSplits', 'statSplitsAdvanced') + # these stat groups require a team with recent playoff appearences + cls.stats_playoffs = ('byMonthPlayoffs', 'byDayOfWeekPlayoffs', 'homeAndAwayPlayoffs', 'winLossPlayoffs') + # These stat groups require addition params passed like playerid or teamid + cls.stats_require_params = ('vsTeam', 'vsTeam5Y', 'vsTeamTotal', 'vsPlayer', 'vsPlayerTotal', 'vsPlayer5Y') + # These stat types should all return a stat split object for hitting and pitching stat groups + cls.hitting = ( + "yearByYear", "yearByYearAdvanced", "yearByYearPlayoffs", "season", + "career", "careerRegularSeason", "careerAdvanced", "seasonAdvanced", "careerPlayoffs", + "gameLog", "playLog", "pitchLog", "pitchArsenal", "expectedStatistics", "sabermetrics", + "sprayChart", "lastXGames", "byDateRange", "byDateRangeAdvanced", "byMonth", "byDayOfWeek", + "homeAndAway", "winLoss", "rankings", "rankingsByYear", "statsSingleSeason", "statsSingleSeasonAdvanced", + "hotColdZones", "opponentsFaced", "atGameStart" + ) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_hitting_stat_attributes_player(self): + """mlb get stats should return pitching stats""" + self.stats = ['season', 'career','seasonAdvanced', 'careerAdvanced'] + self.group = ['hitting'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + self.assertFalse('pitching' in stats) + self.assertEqual(len(stats['hitting']), 4) + + # check for split objects + self.assertTrue(stats['hitting']['season']) + self.assertTrue(stats['hitting']['career']) + self.assertTrue(stats['hitting']['seasonadvanced']) + self.assertTrue(stats['hitting']['careeradvanced']) + + # let's pull out a object and test it + season = stats['hitting']['season'] + career = stats['hitting']['career'] + season_advanced = stats['hitting']['seasonadvanced'] + career_advanced = stats['hitting']['careeradvanced'] + # check that attrs exist and contain data + + self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.group, 'hitting') + self.assertEqual(season.type, 'season') + + self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.group, 'hitting') + self.assertEqual(career.type, 'career') + + self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.group, 'hitting') + self.assertEqual(season_advanced.type, 'seasonAdvanced') + + self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.group, 'hitting') + self.assertEqual(career_advanced.type, 'careerAdvanced') + + def test_hitting_stat_attributes_team(self): + """mlb get stats should return pitching stats""" + self.stats = ['season', 'career', 'seasonAdvanced', 'careerAdvanced'] + self.group = ['hitting'] + # let's get some stats + # let's get some stats + stats = self.mlb.get_team_stats(self.al_team, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + self.assertFalse('pitching' in stats) + self.assertEqual(len(stats['hitting']), 4) + + # check for split objects + self.assertTrue(stats['hitting']['season']) + self.assertTrue(stats['hitting']['career']) + self.assertTrue(stats['hitting']['seasonadvanced']) + self.assertTrue(stats['hitting']['careeradvanced']) + + # let's pull out a object and test it + season = stats['hitting']['season'] + career = stats['hitting']['career'] + season_advanced = stats['hitting']['seasonadvanced'] + career_advanced = stats['hitting']['careeradvanced'] + + self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.group, 'hitting') + self.assertEqual(season.type, 'season') + + self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.group, 'hitting') + self.assertEqual(career.type, 'career') + + self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.group, 'hitting') + self.assertEqual(season_advanced.type, 'seasonAdvanced') + + self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.group, 'hitting') + self.assertEqual(career_advanced.type, 'careerAdvanced') + + + + def test_hitting_traded_stats_player(self): + """mlb get stats should return multiple splits for being a traded player""" + self.stats = ['season'] + self.group = ['hitting'] + self.params = {'season': 2022} + # let's get some stats + stats = self.mlb.get_player_stats(self.soto, stats=self.stats, groups=self.group, **self.params) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + + # check for split objects + self.assertTrue(stats['hitting']['season']) + + season = stats['hitting']['season'] + + def test_hitting_excepected_stats_player(self): + """mlb get stats should return pitching stats""" + self.stats = ['expectedStatistics'] + self.group = ['hitting'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + + # check for split objects + self.assertTrue(stats['hitting']['expectedstatistics']) + + def test_hitting_bydate_stats_player(self): + """mlb get stats should return pitching stats""" + self.stats = ['byDateRange', 'byDateRangeAdvanced'] + self.group = ['hitting'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + + # check for split objects + self.assertTrue(stats['hitting']['bydaterange']) + self.assertTrue(stats['hitting']['bydaterangeadvanced']) + + def test_hitting_bymonth_stats_player(self): + """mlb get stats should return pitching stats""" + self.stats = ['byMonth'] + self.group = ['hitting'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + + # check for split objects + self.assertTrue(stats['hitting']['bymonth']) + + def test_hitting_bydayofweek_stats_player(self): + """mlb get stats should return hitting stats""" + self.stats = ['byDayOfWeek'] + self.group = ['hitting'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + + # check for split objects + self.assertTrue(stats['hitting']['bydayofweek']) + + def test_hitting_vsplayer_stats_player(self): + """mlb get stats should return hitting stats""" + self.stats = ['vsPlayer'] + self.group = ['hitting'] + self.params = {'opposingPlayerId': 660271} + # let's get some stats + stats = self.mlb.get_player_stats(self.ty_france, stats=self.stats, groups=self.group, **self.params) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + + # check for split objects + self.assertTrue(stats['hitting']['vsplayer']) + + def test_hitting_vsteam_stats_player(self): + """mlb get stats should return hitting stats""" + self.stats = ['vsTeam'] + self.group = ['hitting'] + self.params = {'opposingTeamId': 133} + + # let's get some stats + stats = self.mlb.get_player_stats(self.ty_france, stats=self.stats, groups=self.group, **self.params) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + + # check for split objects + self.assertTrue(stats['hitting']['vsteam']) + + def test_hitting_vsteam_stats_team(self): + """mlb get stats should return hitting stats""" + self.stats = ['vsTeam'] + self.group = ['hitting'] + self.params = {'opposingTeamId': 136} + + # let's get some stats + stats = self.mlb.get_team_stats(self.al_team, stats=self.stats, groups=self.group, **self.params) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + + # check for split objects + self.assertTrue(stats['hitting']['vsteam']) + + def test_hitting_pitchlog_stats_player(self): + """mlb get stats should return hitting stats""" + self.stats = ['pitchLog'] + self.group = ['hitting'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + + # check for split objects + self.assertTrue(stats['hitting']['pitchlog']) + + pitchlog = stats['hitting']['pitchlog'] + self.assertTrue(len(pitchlog.splits) > 1) + self.assertEqual(pitchlog.totalsplits, len(pitchlog.splits)) + + + def test_hitting_pitchlog_stats_player(self): + """mlb get stats should return hitting stats""" + self.stats = ['playLog'] + self.group = ['hitting'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + + # check for split objects + self.assertTrue(stats['hitting']['playlog']) + + # playlogs should return multiple splits + playlogs = stats['hitting']['playlog'] + self.assertTrue(len(playlogs.splits) > 1) + self.assertEqual(playlogs.totalsplits, len(playlogs.splits)) + + + def test_hitting_pitchArsenal_stats_player(self): + """mlb get stats should return hitting stats""" + self.stats = ['pitchArsenal'] + self.group = ['hitting'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('stats' in stats) + + # check for split objects + self.assertTrue(stats['stats']['pitcharsenal']) + + pitcharsenal = stats['stats']['pitcharsenal'] + self.assertTrue(len(pitcharsenal.splits) > 1) + self.assertEqual(pitcharsenal.totalsplits, len(pitcharsenal.splits)) + + def test_hitting_hotcoldzones_stats_player(self): + """mlb get stats should return hitting stats""" + self.stats = ['hotColdZones'] + self.group = ['hitting'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('stats' in stats) + + # check for split objects + self.assertTrue(stats['stats']['hotcoldzones']) + + # hotcoldzone should return 5 splits + hotcoldzone = stats['stats']['hotcoldzones'] + self.assertEqual(len(hotcoldzone.splits), 5) + self.assertEqual(hotcoldzone.totalsplits, len(hotcoldzone.splits)) diff --git a/tests/external_tests/stats/test_pitching.py b/tests/external_tests/stats/test_pitching.py new file mode 100644 index 00000000..54c2c0fd --- /dev/null +++ b/tests/external_tests/stats/test_pitching.py @@ -0,0 +1,266 @@ +import unittest +import time + +from mlbstatsapi.mlb_api import Mlb + + +class TestPitchingStats(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.al_team = 133 + cls.shoei_ohtani = 660271 + cls.utility_player = 647351 + cls.ty_france = 664034 + cls.stats_200_blank = ('projected', 'projectedRos', 'standard', 'advanced', 'firstYearStats', 'lastYearStats', + 'vsOpponents', 'outsAboveAverage', 'tracking', 'availableStats', 'gameTypeStats', 'vsOpponents') + cls.pitching = 'pitching' + cls.stats_500 = ('careerStatSplits', 'metricLog', 'metricAverages', 'statSplits', 'statSplitsAdvanced') + # these stat groups require a team with recent playoff appearences + cls.stats_playoffs = ('byMonthPlayoffs', 'bydayofweekPlayoffs', 'homeAndAwayPlayoffs', 'winLossPlayoffs') + # These stat groups require addition params passed like playerid or teamid + cls.stats_require_params = ('vsTeam', 'vsTeam5Y', 'vsTeamTotal', 'vsPlayer', 'vsPlayerTotal', 'vsPlayer5Y') + # These stat types should all return a stat split object for hitting and pitching stat groups + cls.pitching = ( + "yearByYear", "yearByYearAdvanced", "yearByYearPlayoffs", "season", + "career", "careerRegularSeason", "careerAdvanced", "seasonAdvanced", "careerPlayoffs", + "gameLog", "playLog", "pitchLog", "pitchArsenal", "expectedStatistics", "sabermetrics", + "sprayChart", "lastXGames", "byDateRange", "byDateRangeAdvanced", "byMonth", "byDayOfWeek", + "homeAndAway", "winLoss", "rankings", "rankingsByYear", "statsSingleSeason", "statsSingleSeasonAdvanced", + "hotColdZones", "opponentsFaced", "atGameStart" + ) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_pitching_stat_attributes_player(self): + """mlb get stats should return pitching stats""" + self.stats = ['season', 'career', 'seasonAdvanced', 'careerAdvanced'] + self.group = ['pitching'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('pitching' in stats) + self.assertFalse('hitting' in stats) + self.assertEqual(len(stats['pitching']), 4) + + # check for split objects + self.assertTrue(stats['pitching']['season']) + self.assertTrue(stats['pitching']['career']) + self.assertTrue(stats['pitching']['seasonadvanced']) + self.assertTrue(stats['pitching']['careeradvanced']) + + season = stats['pitching']['season'] + career = stats['pitching']['career'] + season_advanced = stats['pitching']['seasonadvanced'] + career_advanced = stats['pitching']['careeradvanced'] + + self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.group, 'pitching') + self.assertEqual(season.type, 'season') + + self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.group, 'pitching') + self.assertEqual(career.type, 'career') + + self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.group, 'pitching') + self.assertEqual(season_advanced.type, 'seasonAdvanced') + + self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.group, 'pitching') + self.assertEqual(career_advanced.type, 'careerAdvanced') + + def test_pitching_stat_attributes_team(self): + """mlb get stats should return pitching stats""" + self.stats = ['season', 'career','seasonAdvanced', 'careerAdvanced'] + self.group = ['pitching'] + # let's get some stats + stats = self.mlb.get_team_stats(self.al_team, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('pitching' in stats) + self.assertFalse('hitting' in stats) + self.assertEqual(len(stats['pitching']), 4) + + # check for split objects + self.assertTrue(stats['pitching']['season']) + self.assertTrue(stats['pitching']['career']) + self.assertTrue(stats['pitching']['seasonadvanced']) + self.assertTrue(stats['pitching']['careeradvanced']) + + season = stats['pitching']['season'] + career = stats['pitching']['career'] + season_advanced = stats['pitching']['seasonadvanced'] + career_advanced = stats['pitching']['careeradvanced'] + + self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.group, 'pitching') + self.assertEqual(season.type, 'season') + + self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.group, 'pitching') + self.assertEqual(career.type, 'career') + + self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.group, 'pitching') + self.assertEqual(season_advanced.type, 'seasonAdvanced') + + self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.group, 'pitching') + self.assertEqual(career_advanced.type, 'careerAdvanced') + + def test_pitching_excepected_stats_player(self): + """mlb get stats should return pitching stats""" + self.stats = ['expectedStatistics'] + self.group = ['pitching'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('pitching' in stats) + + # check for split objects + self.assertTrue(stats['pitching']['expectedstatistics']) + + + def test_pitching_bydate_stats_player(self): + """mlb get stats should return pitching stats""" + self.stats = ['byDateRange', 'byDateRangeAdvanced'] + self.group = ['pitching'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('pitching' in stats) + + # check for split objects + self.assertTrue(stats['pitching']['bydaterange']) + self.assertTrue(stats['pitching']['bydaterangeadvanced']) + + def test_pitching_bymonth_stats_player(self): + """mlb get stats should return pitching stats""" + self.stats = ['byMonth'] + self.group = ['pitching'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('pitching' in stats) + + # check for split objects + self.assertTrue(stats['pitching']['bymonth']) + + def test_pitching_bydayofweek_stats_player(self): + """mlb get stats should return pitching stats""" + self.stats = ['byDayOfWeek'] + self.group = ['pitching'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('pitching' in stats) + + # check for split objects + self.assertTrue(stats['pitching']['bydayofweek']) + + def test_pitching_vsplayer_stats_player(self): + """mlb get stats should return hitting stats""" + self.stats = ['vsPlayer'] + self.group = ['pitching'] + self.params = {'opposingPlayerId': 664034} + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group, **self.params) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('pitching' in stats) + + # check for split objects + self.assertTrue(stats['pitching']['vsplayer']) + + def test_pitching_pitchlog_stats_player(self): + """mlb get stats should return hitting stats""" + self.stats = ['pitchLog'] + self.group = ['pitching'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('pitching' in stats) + + # check for split objects + self.assertTrue(stats['pitching']['pitchlog']) + + def test_pitching_playlog_stats_player(self): + """mlb get stats should return hitting stats""" + self.stats = ['playLog'] + self.group = ['pitching'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('pitching' in stats) + + # check for split objects + self.assertTrue(stats['pitching']['playlog']) + + def test_pitching_pitchArsenal_stats_player(self): + """mlb get stats should return hitting stats""" + self.stats = ['pitchArsenal'] + self.group = ['pitching'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('stats' in stats) + + # check for split objects + self.assertTrue(stats['stats']['pitcharsenal']) + + def test_pitching_hotcoldzones_stats_player(self): + """mlb get stats should return hitting stats""" + self.stats = ['hotColdZones'] + self.group = ['pitching'] + # let's get some stats + stats = self.mlb.get_player_stats(self.shoei_ohtani, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('stats' in stats) + + # check for split objects + self.assertTrue(stats['stats']['hotcoldzones']) \ No newline at end of file diff --git a/tests/external_tests/stats/test_player_game_stats.py b/tests/external_tests/stats/test_player_game_stats.py new file mode 100644 index 00000000..d121e9ae --- /dev/null +++ b/tests/external_tests/stats/test_player_game_stats.py @@ -0,0 +1,125 @@ +import unittest +import time + +from mlbstatsapi.mlb_api import Mlb + + +class TestHittingStats(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.al_team = 133 + cls.shoei_ohtani = 660271 + cls.ty_france = 664034 + cls.shoei_game_id = 531368 + cls.ty_game_id = 715757 + cls.cal_realeigh = 663728 + cls.cal_game_id = 715757 + cls.archie_bradley = 605151 + cls.archie_game_id = 531368 + + @classmethod + def tearDownClass(cls) -> None: + pass + + + def test_get_players_stats_for_shoei_ohtana(self): + """return player stat objects""" + + game_stats = self.mlb.get_players_stats_for_game(person_id=self.shoei_ohtani, + game_id=self.shoei_game_id) + + # game stats should not be None + self.assertIsNotNone(game_stats) + + # game_stats should be a dict + self.assertIsInstance(game_stats, dict) + + print(game_stats) + # game_stats should have hitting stats + self.assertTrue(game_stats['pitching']) + + # game_stats should have vsplayer5y and playlog stats + self.assertTrue(game_stats['pitching']['vsplayer5y']) + + stat = game_stats['pitching']['vsplayer5y'] + + for split in stat.splits: + self.assertTrue(split.team) + self.assertTrue(split.stat) + + def test_get_players_stats_for_ty_france(self): + """return player stat objects""" + + game_stats = self.mlb.get_players_stats_for_game(person_id=self.ty_france, + game_id=self.ty_game_id) + + # game stats should not be None + self.assertIsNotNone(game_stats) + + # game_stats should be a dict + self.assertIsInstance(game_stats, dict) + + # game_stats should have hitting stats + self.assertTrue(game_stats['hitting']) + + # game_stats should have vsplayer5y and playlog stats + self.assertTrue(game_stats['hitting']['vsplayer5y']) + self.assertTrue(game_stats['hitting']['playlog']) + self.assertTrue(game_stats['stats']['gamelog']) + + stat = game_stats['hitting']['vsplayer5y'] + + for split in stat.splits: + self.assertTrue(split.team) + self.assertTrue(split.stat) + + def test_get_players_stats_for_cal_r(self): + """return player stat objects""" + + game_stats = self.mlb.get_players_stats_for_game(person_id=self.cal_realeigh, + game_id=self.cal_game_id) + + # game stats should not be None + self.assertIsNotNone(game_stats) + + # game_stats should be a dict + self.assertIsInstance(game_stats, dict) + + # game_stats should have hitting stats + self.assertTrue(game_stats['hitting']) + + # game_stats should have vsplayer5y and playlog stats + self.assertTrue(game_stats['hitting']['vsplayer5y']) + self.assertTrue(game_stats['hitting']['playlog']) + self.assertTrue(game_stats['stats']['gamelog']) + + stat = game_stats['stats']['gamelog'] + + + def test_get_players_stats_for_archie(self): + """return player stat objects""" + + self.game_stats = self.mlb.get_players_stats_for_game(person_id=self.archie_bradley, + game_id=self.archie_game_id) + + # game stats should not be None + self.assertIsNotNone(self.game_stats) + + # game_stats should be a dict + self.assertIsInstance(self.game_stats, dict) + + # game_stats should have hitting stats + self.assertTrue(self.game_stats['pitching']) + + # game_stats should have vsplayer5y and playlog stats + self.assertTrue(self.game_stats['pitching']['vsplayer5y']) + self.assertTrue(self.game_stats['stats']['gamelog']) + + game_log_stat = self.game_stats['stats']['gamelog'] + self.assertTrue(len(game_log_stat.splits) == 3) + stat = self.game_stats['pitching']['vsplayer5y'] + + for split in stat.splits: + self.assertTrue(split.team) + self.assertTrue(split.stat) \ No newline at end of file diff --git a/tests/external_tests/team/test_roster.py b/tests/external_tests/team/test_roster.py new file mode 100644 index 00000000..a2681685 --- /dev/null +++ b/tests/external_tests/team/test_roster.py @@ -0,0 +1,60 @@ +import unittest +from mlbstatsapi.models.people import Player, Coach +from mlbstatsapi import Mlb + + +class TestTeamRoster(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.team = cls.mlb.get_team(133) + cls.roster = cls.mlb.get_team_roster('133') + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_team_roster_list_of_player_objects(self): + """Default Team Roster should return a list of players""" + self.assertIsInstance(self.roster, list) + # Roster should return a list + self.assertIsInstance(self.roster, list) + + # Roster should not return a empty list + self.assertNotEqual(self.roster, []) + for player in self.roster: + self.assertIsInstance(player, Player) + + def test_team_roster_fail_list_of_player_objects(self): + """This should return a empty list""" + roster = self.mlb.get_team_roster('1333') + self.assertListEqual(roster, []) + + +class TestCoachRoster(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.team = cls.mlb.get_team(133) + cls.roster = cls.mlb.get_team_coaches('133') + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_team_roster_list_of_coach_objects(self): + """Default Team Roster should return a list of coaches""" + # Roster should return a list + self.assertIsInstance(self.roster, list) + + # Roster should not return a empty list + self.assertNotEqual(self.roster, []) + + # Roster should return a list of Coaches + for player in self.roster: + self.assertIsInstance(player, Coach) + + def test_team_roster_fail_list_of_coach_objects(self): + """This should return a empty list""" + roster = self.mlb.get_team_coaches('1333') + self.assertListEqual(roster, []) \ No newline at end of file diff --git a/tests/external_tests/team/test_team.py b/tests/external_tests/team/test_team.py new file mode 100644 index 00000000..34fdac2c --- /dev/null +++ b/tests/external_tests/team/test_team.py @@ -0,0 +1,61 @@ +import unittest +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.venues import Venue +from mlbstatsapi.models.divisions import Division +from mlbstatsapi.models.sports import Sport +from mlbstatsapi import Mlb + + +class TestTeam(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_team_instance_type_error(self): + with self.assertRaises(TypeError): + team = Team() + + def test_team_instance_id_instance_success(self): + """get_team should return a Team object""" + self.team = self.mlb.get_team(133) + + # team should not be None + self.assertIsNotNone(self.team) + + # Team object should have attrs set + self.assertEqual(self.team.id, 133) + self.assertIsInstance(self.team, Team) + self.assertEqual(self.team.name, "Oakland Athletics") + self.assertEqual(self.team.link, "/api/v1/teams/133") + self.assertIsInstance(self.team.sport, Sport) + self.assertIsInstance(self.team.venue, Venue) + self.assertIsInstance(self.team.division, Division) + + def test_get_teams_for_sport(self): + """get_teams should return a list of teams""" + self.teams = self.mlb.get_teams(sport_id=1) + + # teams should not be none + self.assertIsNotNone(self.teams) + + # teams should be a list + self.assertIsInstance(self.teams, list) + + # teams should not be empty list + self.assertNotEqual(self.teams, []) + + self.teams = self.mlb.get_teams(sport_id=11) + + # teams should not be none + self.assertIsNotNone(self.teams) + + # teams should be a list + self.assertIsInstance(self.teams, list) + + # teams should not be empty list + self.assertNotEqual(self.teams, []) \ No newline at end of file diff --git a/tests/external_tests/venue/test_venue.py b/tests/external_tests/venue/test_venue.py new file mode 100644 index 00000000..30ae8087 --- /dev/null +++ b/tests/external_tests/venue/test_venue.py @@ -0,0 +1,34 @@ +import unittest +from mlbstatsapi.models.venues import Venue +from mlbstatsapi import Mlb + + +class TestVenue(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.venue = cls.mlb.get_venue(31) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_venue_instance_type_error(self): + with self.assertRaises(TypeError): + venue = Venue() + + def test_venue_instance_position_arguments(self): + self.assertEqual(self.venue.id, 31) + self.assertEqual(self.venue.link, "/api/v1/venues/31") + self.assertEqual(self.venue.name, "PNC Park") + self.assertEqual(self.venue.active, True) + + def test_venue_attributes(self): + self.assertIsInstance(self.venue, Venue) + self.assertTrue(hasattr(self.venue, "id")) + self.assertTrue(hasattr(self.venue, "link")) + self.assertTrue(hasattr(self.venue, "name")) + self.assertTrue(hasattr(self.venue, "location")) + self.assertTrue(hasattr(self.venue, "timezone")) + self.assertTrue(hasattr(self.venue, "fieldinfo")) + self.assertTrue(hasattr(self.venue, "active")) diff --git a/tests/mock_tests/awards/test_awards_mock.py b/tests/mock_tests/awards/test_awards_mock.py new file mode 100644 index 00000000..456ba067 --- /dev/null +++ b/tests/mock_tests/awards/test_awards_mock.py @@ -0,0 +1,56 @@ +from typing import Dict, List +from unittest.mock import patch +import unittest +import requests_mock +import json +import os + + +from mlbstatsapi import Mlb +from mlbstatsapi.models.awards import Award + + +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_awards = os.path.join(current_directory, "../mock_json/awards/awards.json") +AWARDS_JSON_FILE = open(path_to_awards, "r", encoding="utf-8-sig").read() + +@requests_mock.Mocker() +class TestAwardsMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.awards_mock = json.loads(AWARDS_JSON_FILE) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_awards(self, m): + """This test should return a 200 and Round""" + m.get('https://statsapi.mlb.com/api/v1/awards/RETIREDUNI_108/recipients?', json=self.awards_mock, + status_code=200) + + # set draft id + award_id = "RETIREDUNI_108" + + # call get_draft return list + awards = self.mlb.get_awards(award_id) + + # draft should not be None + self.assertIsNotNone(awards) + + # list should not be empty + self.assertNotEqual(awards, []) + + # items in list should be an award + self.assertIsInstance(awards[0], Award) + + award = awards[0] + + # award should not be none + self.assertIsNotNone(award) + + # award should have attrs set + self.assertTrue(award.id) + self.assertTrue(award.name) \ No newline at end of file diff --git a/tests/mock_tests/drafts/test_draft_mock.py b/tests/mock_tests/drafts/test_draft_mock.py new file mode 100644 index 00000000..6c7a48b3 --- /dev/null +++ b/tests/mock_tests/drafts/test_draft_mock.py @@ -0,0 +1,61 @@ +from typing import Dict, List +from unittest.mock import patch +import unittest +import requests_mock +import json +import os + + +from mlbstatsapi import Mlb +from mlbstatsapi import MlbResult +from mlbstatsapi import TheMlbStatsApiException +from mlbstatsapi.models.drafts import Round + + +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_drafts = os.path.join(current_directory, "../mock_json/drafts/draft.json") +DRAFTS = open(path_to_drafts, "r", encoding="utf-8-sig").read() + +@requests_mock.Mocker() +class TestDraftMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.drafts_mock = json.loads(DRAFTS) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_draft_by_year_id(self, m): + """This test should return a 200 and Round""" + m.get('https://statsapi.mlb.com/api/v1/draft/2018', json=self.drafts_mock, + status_code=200) + # set draft id + draft_id = "2018" + + # call get_draft return list + draft = self.mlb.get_draft(draft_id) + + # draft should not be None + self.assertIsNotNone(draft) + + # list should not be empty + self.assertNotEqual(draft, []) + + # items in list should be Round + self.assertIsInstance(draft[0], Round) + + draftpicks = draft[0].picks + + # draftpicks should not be none + self.assertIsNotNone(draftpicks) + + # list should not be empty + self.assertNotEqual(draftpicks, []) + + draftpick = draftpicks[0] + + # draft pick should have attrs set + self.assertTrue(draftpick.pickround) \ No newline at end of file diff --git a/tests/mock_tests/gamepace/test_gamepace_mock.py b/tests/mock_tests/gamepace/test_gamepace_mock.py new file mode 100644 index 00000000..e52500da --- /dev/null +++ b/tests/mock_tests/gamepace/test_gamepace_mock.py @@ -0,0 +1,58 @@ +from typing import Dict, List +from unittest.mock import patch +import unittest +import requests_mock +import json +import os + + +from mlbstatsapi import Mlb +from mlbstatsapi.models.gamepace import Gamepace, Gamepacedata + + +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_gamepace = os.path.join(current_directory, "../mock_json/gamepace/gamepace.json") +GAMEPACE_JSON_FILE = open(path_to_gamepace, "r", encoding="utf-8-sig").read() + +@requests_mock.Mocker() +class TestGamepaceMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.gamepace_mock = json.loads(GAMEPACE_JSON_FILE) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_gamepace(self, m): + """This test should return a 200 and Round""" + m.get('https://statsapi.mlb.com/api/v1/gamePace?season=2021', json=self.gamepace_mock, + status_code=200) + + # set draft id + season_id = 2021 + + # call get_gamepace return Gamepace object + gamepace = self.mlb.get_gamepace(season_id) + + # Gamepace should not be None + self.assertIsNotNone(gamepace) + + self.assertIsInstance(gamepace, Gamepace) + + # list should not be empty + self.assertNotEqual(gamepace.sports, []) + + # items in list should be gamepace data + self.assertIsInstance(gamepace.sports[0], Gamepacedata) + + sportgamepace = gamepace.sports[0] + + # sportgamepace should not be none + self.assertIsNotNone(sportgamepace) + + # sportgamepace should have attrs set + self.assertTrue(sportgamepace.hitspergame) + self.assertTrue(sportgamepace.totalgames) \ No newline at end of file diff --git a/tests/mock_tests/homerunderby/test_homerunderby_mock.py b/tests/mock_tests/homerunderby/test_homerunderby_mock.py new file mode 100644 index 00000000..c3eaeca7 --- /dev/null +++ b/tests/mock_tests/homerunderby/test_homerunderby_mock.py @@ -0,0 +1,49 @@ +from typing import Dict, List +from unittest.mock import patch +import unittest +import requests_mock +import json +import os + + +from mlbstatsapi import Mlb +from mlbstatsapi.models.homerunderby import Homerunderby, Round + + +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_homerunderby = os.path.join(current_directory, "../mock_json/homerunderby/homerunderby.json") +HOMERUNDERBY_JSON_FILE = open(path_to_homerunderby, "r", encoding="utf-8-sig").read() + +@requests_mock.Mocker() +class TestHomerunderbyMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.homerunderby_mock = json.loads(HOMERUNDERBY_JSON_FILE) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_homerunderby(self, m): + """This test should return a 200 and Round""" + m.get('https://statsapi.mlb.com/api/v1/homeRunDerby/511101', json=self.homerunderby_mock, + status_code=200) + + # set draft id + game_id = 511101 + + # call get_gamepace return Gamepace object + derby = self.mlb.get_homerun_derby(game_id) + + # Gamepace should not be None + self.assertIsNotNone(derby) + + self.assertIsInstance(derby, Homerunderby) + + # list should not be empty + self.assertNotEqual(derby.rounds, []) + + # items in list should be gamepace data + self.assertIsInstance(derby.rounds[0], Round) \ No newline at end of file diff --git a/tests/mock_tests/mlb/test_mlb_mock.py b/tests/mock_tests/mlb/test_mlb_mock.py new file mode 100644 index 00000000..9100a242 --- /dev/null +++ b/tests/mock_tests/mlb/test_mlb_mock.py @@ -0,0 +1,453 @@ +from typing import Dict, List +from unittest.mock import patch +import unittest +import requests_mock +import json +import os + +from mlbstatsapi.models.people import Person +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.game import Game, Plays, Linescore, BoxScore +from mlbstatsapi.models.venues import Venue +from mlbstatsapi.models.sports import Sport +from mlbstatsapi.models.leagues import League +from mlbstatsapi.models.divisions import Division +from mlbstatsapi.models.schedules import Schedule + +from mlbstatsapi import Mlb +from mlbstatsapi import MlbResult +from mlbstatsapi import TheMlbStatsApiException + +# Mocked JSON directory +# TODO Find a better way to structure and handle this :) +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_teams_file = os.path.join(current_directory, "../mock_json/teams/teams.json") +path_to_oakland_file = os.path.join(current_directory, "../mock_json/teams/team.json") +path_to_players_file = os.path.join(current_directory, "../mock_json/people/players.json") +path_to_person_file = os.path.join(current_directory, "../mock_json/people/person.json") +path_to_not_found = os.path.join(current_directory, "../mock_json/response/not_found_404.json") +path_to_error = os.path.join(current_directory, "../mock_json/response/error_500.json") +path_to_divisions = os.path.join(current_directory, "../mock_json/divisions/divisions.json") +path_to_division = os.path.join(current_directory, "../mock_json/divisions/division.json") +path_to_sports = os.path.join(current_directory, "../mock_json/sports/sports.json") +path_to_sports = os.path.join(current_directory, "../mock_json/sports/sport.json") +path_to_leagues = os.path.join(current_directory, "../mock_json/leagues/leagues.json") +path_to_league = os.path.join(current_directory, "../mock_json/leagues/league.json") +path_to_venues = os.path.join(current_directory, "../mock_json/venues/venues.json") +path_to_venue = os.path.join(current_directory, "../mock_json/venues/venue.json") +path_to_game = os.path.join(current_directory, "../mock_json/games/game.json") + + +LEAGUES_JSON_FILE = open(path_to_leagues, "r", encoding="utf-8-sig").read() +LEAGUE_JSON_FILE = open(path_to_league, "r", encoding="utf-8-sig").read() +SPORTS_JSON_FILE = open(path_to_sports, "r", encoding="utf-8-sig").read() +SPORT_JSON_FILE = open(path_to_sports, "r", encoding="utf-8-sig").read() +TEAMS_JSON_FILE = open(path_to_teams_file, "r", encoding="utf-8-sig").read() +TEAM_JSON_FILE = open(path_to_oakland_file, "r", encoding="utf-8-sig").read() +PEOPLE_JSON_FILE = open(path_to_players_file, "r", encoding="utf-8-sig").read() +PERSON_JSON_FILE = open(path_to_person_file, "r", encoding="utf-8-sig").read() +DIVISIONS_JSON_FILE = open(path_to_divisions, "r", encoding="utf-8-sig").read() +DIVISION_JSON_FILE = open(path_to_division, "r", encoding="utf-8-sig").read() +VENUES_JSON_FILE = open(path_to_venues, "r", encoding="utf-8-sig").read() +VENUE_JSON_FILE = open(path_to_venue, "r", encoding="utf-8-sig").read() +GAME_JSON_FILE = open(path_to_game, "r", encoding="utf-8-sig").read() +NOT_FOUND_404 = open(path_to_not_found, "r", encoding="utf-8-sig").read() +ERROR_500 = open(path_to_error, "r", encoding="utf-8-sig").read() + + +@requests_mock.Mocker() +class TestMlbDataApiMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.mock_divisions = json.loads(DIVISIONS_JSON_FILE) + cls.error_500 = json.loads(ERROR_500) + cls.mock_not_found = json.loads(NOT_FOUND_404) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_mlb_adapter_200(self, m): + m.get('https://statsapi.mlb.com/api/v1/divisions', json=self.mock_divisions, + status_code=200) + + mlbdata = self.mlb._mlb_adapter_v1.get("divisions") + self.assertIsInstance(mlbdata, MlbResult) + self.assertEqual(mlbdata.status_code, 200) + self.assertIsInstance(mlbdata.data, Dict) + + def test_mlb_adapter_500(self, m): + """mlb should raise a exception when adapter returns a 500""" + m.get('https://statsapi.mlb.com/api/v1/teams/133/stats?stats=vsPlayer&group=catching', + json=self.mock_divisions, status_code=500) + with self.assertRaises(TheMlbStatsApiException): + self.mlb._mlb_adapter_v1.get(endpoint="teams/133/stats?stats=vsPlayer&group=catching") + + def test_mlb_adapter_400(self, m): + """mlb should return a MlbResult object with a empty data, and a status code""" + m.get('https://statsapi.mlb.com/api/v1/teams/19990', json=self.mock_not_found, + status_code=404) + # invalid endpoint + mlbdata = self.mlb._mlb_adapter_v1.get(endpoint="teams/19990") + + # result.status_code should be 404 + self.assertEqual(mlbdata.status_code, 404) + + # result.data should be None + self.assertEqual(mlbdata.data, {}) + + +@requests_mock.Mocker() +class TestMlbGetPeopleMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.mock_people = json.loads(PEOPLE_JSON_FILE) + cls.mock_person = json.loads(PERSON_JSON_FILE) + cls.mock_not_found = json.loads(NOT_FOUND_404) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_mlbdataapi_get_people(self, m): + """mlb get_people should return a list of all sport 1 people""" + m.get('https://statsapi.mlb.com/api/v1/sports/1/players', json=self.mock_people, + status_code=200) + mlbdata = self.mlb.get_people() + self.assertIsInstance(mlbdata, List) + self.assertIsInstance(mlbdata[0], Person) + + def test_mlbdataapi_get_people_with_sportid(self, m): + """mlb get_people should return a list of all sport 1 people""" + m.get('https://statsapi.mlb.com/api/v1/sports/11/players', json=self.mock_people, + status_code=200) + mlbdata = self.mlb.get_people(sport_id=11) + self.assertIsInstance(mlbdata, List) + self.assertIsInstance(mlbdata[0], Person) + + def test_mlb_get_person(self, m): + """mlb get_person should return a Person object""" + m.get('https://statsapi.mlb.com/api/v1/people/676265', json=self.mock_people, + status_code=200) + person = self.mlb.get_person('676265') + self.assertIsInstance(person, Person) + self.assertEqual(person.id, 676265) + + def test_mlb_failed_get_person(self, m): + """mlb get_person should return None and 404 for invalid id""" + m.get('https://statsapi.mlb.com/api/v1/people/664', json=self.mock_not_found, + status_code=404) + person = self.mlb.get_person('664') + self.assertIsNone(person) + + def test_mlb_get_person_id(self, m): + """mlb get_person_id should return a person id""" + m.get('https://statsapi.mlb.com/api/v1/sports/1/players', json=self.mock_people, + status_code=200) + id = self.mlb.get_people_id('CJ Abrams') + self.assertEqual(id, [682928]) + + def test_mlb_get_person_id_with_sportid(self, m): + """mlb get_person_id should return a person id""" + m.get('https://statsapi.mlb.com/api/v1/sports/11/players', json=self.mock_people, + status_code=200) + id = self.mlb.get_people_id('Albert Abreu', sport_id=11) + self.assertEqual(id, [656061]) + + def test_mlb_get_invalid_person_id(self, m): + m.get('https://statsapi.mlb.com/api/v1/sports/1/players', json=self.mock_people, + status_code=200) + """mlb get_person_id should return empty list for invalid name""" + id = self.mlb.get_people_id('Joe Blow') + self.assertEqual(id, []) + + +@requests_mock.Mocker() +class TestMlbGetTeamMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.mock_teams = json.loads(TEAMS_JSON_FILE) + cls.mock_team = json.loads(TEAM_JSON_FILE) + cls.mock_not_found = json.loads(NOT_FOUND_404) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_mlb_get_team(self, m): + """mlb get_team should return Team object""" + m.get('https://statsapi.mlb.com/api/v1/teams/133', json=self.mock_team, + status_code=200) + + team = self.mlb.get_team('133') + self.assertIsInstance(team, Team) + self.assertEqual(team.id, 133) + + def test_mlbdataapi_get_teams(self, m): + """mlb get_teams should return a list of Teams""" + m.get('https://statsapi.mlb.com/api/v1/teams', json=self.mock_teams) + + mlbdata = self.mlb.get_teams() + self.assertIsInstance(mlbdata, List) + self.assertIsInstance(mlbdata[0], Team) + + def test_mlbdataapi_get_teams_with_sportid(self, m): + """mlb get_teams should return a list of Teams""" + m.get('https://statsapi.mlb.com/api/v1/teams', json=self.mock_teams) + mlbdata = self.mlb.get_teams(sport_id=16) + self.assertIsInstance(mlbdata, List) + self.assertIsInstance(mlbdata[0], Team) + + def test_mlb_failed_get_team(self, m): + """mlb get_team should return None for invalid team id""" + m.get('https://statsapi.mlb.com/api/v1/teams/19999', json=self.mock_not_found, + status_code=404) + team = self.mlb.get_team('19999') + self.assertIsNone(team) + + def test_mlb_get_team_id(self, m): + """mlb get_team_id should return a list of matching team id's""" + m.get('https://statsapi.mlb.com/api/v1/teams', json=self.mock_teams, + status_code=200) + + id = self.mlb.get_team_id('Seattle Mariners') + self.assertEqual(id, [136]) + + def test_mlb_get_team_minor_id(self, m): + """mlb get_team_id should return a list of matching team id's""" + m.get('https://statsapi.mlb.com/api/v1/teams', json=self.mock_teams, + status_code=200) + + id = self.mlb.get_team_id('DSL Brewers 2') + self.assertEqual(id, [2101]) + + def test_mlb_get_bad_team_id(self, m): + """mlb get_team_id should return a empty list for invalid team name""" + m.get('https://statsapi.mlb.com/api/v1/teams', json=self.mock_teams, + status_code=200) + id = self.mlb.get_team_id('Banananananana') + self.assertEqual(id, []) + + +@requests_mock.Mocker() +class TestMlbGetSport(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.mock_sports = json.loads(SPORTS_JSON_FILE) + cls.mock_sport = json.loads(SPORT_JSON_FILE) + cls.mock_not_found = json.loads(NOT_FOUND_404) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_sport(self, m): + """mlb get_sport should return a Sport Object""" + m.get('https://statsapi.mlb.com/api/v1/sports/1', json=self.mock_sport, + status_code=200) + sport = self.mlb.get_sport(1) + self.assertIsInstance(sport, Sport) + self.assertEqual(sport.id, 1) + + def test_get_sports(self, m): + """mlb get_sports should return a list of sport objects""" + m.get('https://statsapi.mlb.com/api/v1/sports', json=self.mock_sports, + status_code=200) + sports = self.mlb.get_sports() + self.assertIsInstance(sports, List) + self.assertIsInstance(sports[0], Sport) + + def test_get_sport_id(self, m): + """mlb get_sport id should return a sport id""" + m.get('https://statsapi.mlb.com/api/v1/sports', json=self.mock_sports, + status_code=200) + id = self.mlb.get_sport_id('Major League Baseball') + self.assertEqual(id, [1]) + + def test_get_sport_invalid_name(self, m): + """mlb get_sport should return a empty list for invalid sport name""" + m.get('https://statsapi.mlb.com/api/v1/sports', json=self.mock_sports, + status_code=200) + id = self.mlb.get_sport_id('NFL') + self.assertEqual(id, []) + + +@requests_mock.Mocker() +class TestMlbGetLeagueMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.mock_leagues = json.loads(LEAGUES_JSON_FILE) + cls.mock_league = json.loads(LEAGUE_JSON_FILE) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_league(self, m): + """mlb get_league should return a League object""" + m.get('https://statsapi.mlb.com/api/v1/leagues/103', json=self.mock_league, + status_code=200) + league = self.mlb.get_league(103) + self.assertIsInstance(league, League) + self.assertEqual(league.id, 103) + + def test_get_leagues(self, m): + """mlb get_leagues should return a list of Leagues""" + m.get('https://statsapi.mlb.com/api/v1/leagues', json=self.mock_leagues, + status_code=200) + leagues = self.mlb.get_leagues() + self.assertIsInstance(leagues, List) + self.assertIsInstance(leagues[0], League) + + def test_get_league_id(self, m): + """mlb get_league_id should return a league id""" + m.get('https://statsapi.mlb.com/api/v1/leagues', json=self.mock_leagues, + status_code=200) + id = self.mlb.get_league_id('American League') + self.assertEqual(id, [103]) + + def test_get_invalid_league_id(self, m): + """mlb get_league_id should return a empty list with invalid league name""" + m.get('https://statsapi.mlb.com/api/v1/leagues', json=self.mock_leagues, + status_code=200) + id = self.mlb.get_league_id('Russian League') + self.assertEqual(id, []) + + +@requests_mock.Mocker() +class TestMlbGetDivision(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.mock_divisions = json.loads(DIVISIONS_JSON_FILE) + cls.mock_division = json.loads(DIVISION_JSON_FILE) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_division(self, m): + """mlb get_division should return a Division object""" + m.get('https://statsapi.mlb.com/api/v1/divisions/200', json=self.mock_division, + status_code=200) + division = self.mlb.get_division(200) + self.assertIsInstance(division, Division) + self.assertEqual(division.id, 200) + + def test_get_divisions(self, m): + """mlb get_divisions should return a list of Divisions""" + m.get('https://statsapi.mlb.com/api/v1/divisions', json=self.mock_divisions, + status_code=200) + divisions = self.mlb.get_divisions() + self.assertIsInstance(divisions, List) + self.assertIsInstance(divisions[0], Division) + + def test_get_division_id(self, m): + """mlb get_division_id should return a division id""" + m.get('https://statsapi.mlb.com/api/v1/divisions', json=self.mock_divisions, + status_code=200) + id = self.mlb.get_division_id('American League West') + self.assertEqual(id, [200]) + + def test_get_division_fail_id(self, m): + m.get('https://statsapi.mlb.com/api/v1/divisions', json=self.mock_divisions, + status_code=200) + """mlb get_division_id should return a empty list for invalid division name""" + id = self.mlb.get_division_id('Canada West') + self.assertEqual(id, []) + + +@requests_mock.Mocker() +class TestMlbGetVenue(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.mock_venues = json.loads(VENUES_JSON_FILE) + cls.mock_venue = json.loads(VENUE_JSON_FILE) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_mlb_get_venue(self, m): + """mlb get_division should return a Division object""" + m.get('https://statsapi.mlb.com/api/v1/venues/31', json=self.mock_venue, + status_code=200) + venue = self.mlb.get_venue(31) + self.assertIsInstance(venue, Venue) + self.assertEqual(venue.id, 31) + + def test_get_venues(self, m): + """mlb get_divisions should return a list of Divisions""" + m.get('https://statsapi.mlb.com/api/v1/venues', json=self.mock_venues, + status_code=200) + venues = self.mlb.get_venues() + self.assertIsInstance(venues, List) + self.assertIsInstance(venues[0], Venue) + + def test_mlb_get_venue_id(self, m): + """mlb get_division_id should return a division id""" + """mlb get_divisions should return a list of Divisions""" + m.get('https://statsapi.mlb.com/api/v1/venues', json=self.mock_venues, + status_code=200) + id = self.mlb.get_venue_id('PNC Park') + self.assertEqual(id, [31]) + + def test_get_venue_id(self, m): + """mlb get_division_id should return a empty list for invalid venue name""" + """mlb get_divisions should return a list of Divisions""" + m.get('https://statsapi.mlb.com/api/v1/venues', json=self.mock_venues, + status_code=200) + id = self.mlb.get_venue_id('Highschool Park') + self.assertEqual(id, []) + +@requests_mock.Mocker() +class TestMlbGetGame(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.mock_game = json.loads(GAME_JSON_FILE) + pass + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_mlb_get_game(self, m): + """mlb get_game should return a Game object""" + m.get('https://statsapi.mlb.com/api/v1.1/game/715720/feed/live', json=self.mock_game, + status_code=200) + game = self.mlb.get_game(715720) + self.assertIsInstance(game, Game) + self.assertEqual(game.id, 715720) + + # def test_get_game_playByPlay(self): + # playbyplay = self.mlb.get_game_play_by_play(662242) + # self.assertIsInstance(playbyplay, Plays) + + # def test_get_game_linescore(self): + # linescore = self.mlb.get_game_line_score(662242) + # self.assertIsInstance(linescore, Linescore) + + # def test_get_game_boxscore(self): + # boxscore = self.mlb.get_game_box_score(662242) + # self.assertIsInstance(boxscore, BoxScore) + + # def test_get_todays_games_id(self): + # todaysGames = self.mlb.get_todays_game_ids() + # self.assertIsInstance(todaysGames, List) + + # def test_get_attendance(self): + # params = {'season': 2022} + # attendance_team_away = self.mlb.get_attendance(team_id=113) + # attendance_team_home = self.mlb.get_attendance(team_id=134) + # attendance_season = self.mlb.get_attendance(team_id=113, params=params) + # self.assertEqual(attendance_team_away.records[0].team.id, 113) + # self.assertEqual(attendance_team_home.records[0].team.id, 134) + # self.assertEqual(attendance_season.records[0].team.id, 113) \ No newline at end of file diff --git a/tests/mock_tests/mock_json/awards/awards.json b/tests/mock_tests/mock_json/awards/awards.json new file mode 100644 index 00000000..79dee61d --- /dev/null +++ b/tests/mock_tests/mock_json/awards/awards.json @@ -0,0 +1,137 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "awards": [ + { + "id": "RETIREDUNI_108", + "name": "Uniform number retired", + "date": "1998-01-01", + "season": "1998", + "team": { + "id": 108, + "link": "/api/v1/teams/108" + }, + "player": { + "id": 114414, + "link": "/api/v1/people/114414", + "primaryPosition": { + "code": "6", + "name": "Shortstop", + "type": "Infielder", + "abbreviation": "SS" + }, + "nameFirstLast": "Jim Fregosi" + }, + "notes": "11" + }, + { + "id": "RETIREDUNI_108", + "name": "Uniform number retired", + "date": "1997-04-15", + "season": "1997", + "team": { + "id": 108, + "link": "/api/v1/teams/108" + }, + "player": { + "id": 121314, + "link": "/api/v1/people/121314", + "primaryPosition": { + "code": "4", + "name": "Second Base", + "type": "Infielder", + "abbreviation": "2B" + }, + "nameFirstLast": "Jackie Robinson" + }, + "notes": "42" + }, + { + "id": "RETIREDUNI_108", + "name": "Uniform number retired", + "date": "1995-08-02", + "season": "1995", + "team": { + "id": 108, + "link": "/api/v1/teams/108" + }, + "player": { + "id": 121011, + "link": "/api/v1/people/121011", + "primaryPosition": { + "code": "X", + "name": "Unknown", + "type": "Unknown", + "abbreviation": "X" + }, + "nameFirstLast": "Jimmie Reese" + }, + "notes": "50" + }, + { + "id": "RETIREDUNI_108", + "name": "Uniform number retired", + "date": "1992-06-16", + "season": "1992", + "team": { + "id": 108, + "link": "/api/v1/teams/108" + }, + "player": { + "id": 121597, + "link": "/api/v1/people/121597", + "primaryPosition": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "nameFirstLast": "Nolan Ryan" + }, + "notes": "30" + }, + { + "id": "RETIREDUNI_108", + "name": "Uniform number retired", + "date": "1991-08-06", + "season": "1991", + "team": { + "id": 108, + "link": "/api/v1/teams/108" + }, + "player": { + "id": 111986, + "link": "/api/v1/people/111986", + "primaryPosition": { + "code": "3", + "name": "First Base", + "type": "Infielder", + "abbreviation": "1B" + }, + "nameFirstLast": "Rod Carew" + }, + "notes": "29" + }, + { + "id": "RETIREDUNI_108", + "name": "Uniform number retired", + "date": "1982-01-01", + "season": "1982", + "team": { + "id": 108, + "link": "/api/v1/teams/108" + }, + "player": { + "id": 476405, + "link": "/api/v1/people/476405", + "primaryPosition": { + "code": "X", + "name": "Unknown", + "type": "Unknown", + "abbreviation": "X" + }, + "nameFirstLast": "Gene Autry" + }, + "notes": "26" + } + ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/divisions/division.json b/tests/mock_tests/mock_json/divisions/division.json new file mode 100644 index 00000000..86648928 --- /dev/null +++ b/tests/mock_tests/mock_json/divisions/division.json @@ -0,0 +1,25 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "divisions": [ + { + "id": 200, + "name": "American League West", + "season": "2022", + "nameShort": "AL West", + "link": "/api/v1/divisions/200", + "abbreviation": "ALW", + "league": { + "id": 103, + "link": "/api/v1/league/103" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1" + }, + "hasWildcard": false, + "sortOrder": 24, + "numPlayoffTeams": 1, + "active": true + } + ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/divisions/divisions.json b/tests/mock_tests/mock_json/divisions/divisions.json new file mode 100644 index 00000000..416ff7b8 --- /dev/null +++ b/tests/mock_tests/mock_json/divisions/divisions.json @@ -0,0 +1,105 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "divisions": [ + { + "id": 205, + "name": "National League Central", + "season": "2022", + "nameShort": "NL Central", + "link": "/api/v1/divisions/205", + "abbreviation": "NLC", + "league": { + "id": 104, + "link": "/api/v1/league/104" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1" + }, + "hasWildcard": false, + "sortOrder": 33, + "numPlayoffTeams": 1, + "active": true + }, + { + "id": 200, + "name": "American League West", + "season": "2022", + "nameShort": "AL West", + "link": "/api/v1/divisions/200", + "abbreviation": "ALW", + "league": { + "id": 103, + "link": "/api/v1/league/103" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1" + }, + "hasWildcard": false, + "sortOrder": 24, + "numPlayoffTeams": 1, + "active": true + }, + { + "id": 233, + "name": "Pacific Coast League East", + "season": "2022", + "nameShort": "PCL East", + "link": "/api/v1/divisions/233", + "abbreviation": "PCLE", + "league": { + "id": 112, + "link": "/api/v1/league/112" + }, + "sport": { + "id": 11, + "link": "/api/v1/sports/11" + }, + "hasWildcard": false, + "sortOrder": 122, + "numPlayoffTeams": 1, + "active": true + }, + { + "id": 212, + "name": "Eastern League Northeast", + "season": "2022", + "nameShort": "EAS Northeast", + "link": "/api/v1/divisions/212", + "abbreviation": "EASNE", + "league": { + "id": 113, + "link": "/api/v1/league/113" + }, + "sport": { + "id": 12, + "link": "/api/v1/sports/12" + }, + "hasWildcard": false, + "sortOrder": 213, + "numPlayoffTeams": 1, + "active": true + }, + { + "id": 201, + "name": "American League East", + "season": "2022", + "nameShort": "AL East", + "link": "/api/v1/divisions/201", + "abbreviation": "ALE", + "league": { + "id": 103, + "link": "/api/v1/league/103" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1" + }, + "hasWildcard": false, + "sortOrder": 22, + "numPlayoffTeams": 1, + "active": true + } + ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/drafts/draft.json b/tests/mock_tests/mock_json/drafts/draft.json new file mode 100644 index 00000000..a7c2f6bc --- /dev/null +++ b/tests/mock_tests/mock_json/drafts/draft.json @@ -0,0 +1,380 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "drafts": { + "draftYear": 2018, + "rounds": [ + { + "round": "1", + "picks": [ + { + "bisPlayerId": 759143, + "pickRound": "1", + "pickNumber": 1, + "roundPickNumber": 1, + "rank": 1, + "pickValue": "8096300", + "signingBonus": "7500000", + "home": { + "city": "Springville", + "state": "AL", + "country": "USA" + }, + "scoutingReport": "http://m.mlb.com/video/topic/262722544/v1868819183", + "school": { + "name": "Auburn", + "schoolClass": "JR", + "city": "Auburn", + "country": "USA", + "state": "AL" + }, + "blurb": "Raw and undrafted out of an Alabama high school three years ago, Mize has blossomed at Auburn and not only is a lock to join Gregg Olson (1988) and Frank Thomas (1989) as Tigers selected in the top 10 picks, but he's also the prohibitive favorite to go No. 1 overall. He has the best combination of stuff and control among college pitchers in the 2018 Draft, and he has proven he can stay healthy. Shut down last spring at Auburn and again during the summer with Team USA with a tired arm and a flexor strain in his forearm, he has had no issues this year. Mize can get swings and misses with three different pitches, the best of which is a mid-80s splitter that dives at the plate. He sets it up with a 92-97 mph fastball that he commands exceptionally well despite its running life. His mid-80s slider has taken a step forward this spring, consistently grading as a plus offering. Mize has an athletic frame and a clean delivery, so there's no glaring flaw that can be blamed for his health concerns. He pounds the strike zone, ranking first in NCAA Division I in K/BB ratio (12.1) and fourth in walks per nine innings (1.0) as a sophomore and posting similar numbers as a junior. Scouts also love the way he competes on the mound.", + "headshotLink": "https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:silo:current.png/w_120,q_auto:best/v1/people/663554/headshot/draft/current", + "person": { + "id": 663554, + "fullName": "Casey Mize", + "link": "/api/v1/people/663554", + "firstName": "Casey", + "lastName": "Mize", + "primaryNumber": "12", + "birthDate": "1997-05-01", + "currentAge": 25, + "birthCity": "Springville", + "birthStateProvince": "AL", + "birthCountry": "USA", + "height": "6' 3\"", + "weight": 212, + "active": true, + "primaryPosition": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "useName": "Casey", + "useLastName": "Mize", + "middleName": "A.", + "boxscoreName": "Mize", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2018, + "mlbDebutDate": "2020-08-19", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Casey Mize", + "nameSlug": "casey-mize-663554", + "firstLastName": "Casey Mize", + "lastFirstName": "Mize, Casey", + "lastInitName": "Mize, C", + "initLastName": "C Mize", + "fullFMLName": "Casey A. Mize", + "fullLFMName": "Mize, Casey A.", + "strikeZoneTop": 3.49, + "strikeZoneBottom": 1.601 + }, + "team": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 116, + "name": "Detroit Tigers", + "link": "/api/v1/teams/116" + }, + "draftType": { + "code": "JR", + "description": "June Amateur Draft" + }, + "isDrafted": true, + "isPass": false, + "year": "2018" + }, + { + "bisPlayerId": 390858, + "pickRound": "1", + "pickNumber": 2, + "roundPickNumber": 2, + "rank": 6, + "pickValue": "7494600", + "signingBonus": "7025000", + "home": { + "city": "Buford", + "state": "GA", + "country": "USA" + }, + "scoutingReport": "http://m.mlb.com/video/topic/262722544/v1868827883", + "school": { + "name": "Georgia Tech", + "schoolClass": "JR", + "city": "Atlanta", + "country": "USA", + "state": "GA" + }, + "blurb": "Bart's power potential could have gotten him selected in the first five rounds of the Draft after he led Buford High to the 2015 Georgia 4-A state championship, but he dropped to the Rays in the 27th round because he was committed to Georgia Tech. His development at the plate and improvement behind it has him positioned as the top college catcher available in 2018. He's poised to join Jason Varitek and Matt Wieters as Yellow Jackets backstops popped in the first round, and he's even been linked to the Giants as a possible No. 2 overall pick. Bart's bat speed, strength and leverage give him power to all fields from the right side of the plate. He has the swing and the feel to hit for a solid average, and he has made huge strides with his plate discipline this spring. He has enough natural pop that he doesn't need to sell out for home runs, and he's not falling into that trap as much as he did in the past. There were questions about Bart's long-term catching ability when he arrived at Georgia Tech, but he has cleaned up his receiving enough that there no longer are doubts that he'll stay behind the plate. His strong arm never has been in question and he threw out 40 percent of basestealers in his first two college seasons. Though he's a well-below-average runner, he's relatively athletic for a catcher.", + "headshotLink": "https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:silo:current.png/w_120,q_auto:best/v1/people/663698/headshot/draft/current", + "person": { + "id": 663698, + "fullName": "Joey Bart", + "link": "/api/v1/people/663698", + "firstName": "Joey", + "lastName": "Bart", + "primaryNumber": "21", + "birthDate": "1996-12-15", + "currentAge": 25, + "birthCity": "Buford", + "birthStateProvince": "GA", + "birthCountry": "USA", + "height": "6' 2\"", + "weight": 238, + "active": true, + "primaryPosition": { + "code": "2", + "name": "Catcher", + "type": "Catcher", + "abbreviation": "C" + }, + "useName": "Joey", + "useLastName": "Bart", + "middleName": "Andrew", + "boxscoreName": "Bart", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2018, + "mlbDebutDate": "2020-08-20", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Joey Bart", + "nameSlug": "joey-bart-663698", + "firstLastName": "Joey Bart", + "lastFirstName": "Bart, Joey", + "lastInitName": "Bart, J", + "initLastName": "J Bart", + "fullFMLName": "Joseph Andrew Bart", + "fullLFMName": "Bart, Joseph Andrew", + "strikeZoneTop": 3.34, + "strikeZoneBottom": 1.49 + }, + "team": { + "springLeague": { + "id": 114, + "name": "Cactus League", + "link": "/api/v1/league/114", + "abbreviation": "CL" + }, + "allStarStatus": "N", + "id": 137, + "name": "San Francisco Giants", + "link": "/api/v1/teams/137" + }, + "draftType": { + "code": "JR", + "description": "June Amateur Draft" + }, + "isDrafted": true, + "isPass": false, + "year": "2018" + }, + { + "bisPlayerId": 764614, + "pickRound": "1", + "pickNumber": 3, + "roundPickNumber": 3, + "rank": 7, + "pickValue": "6947500", + "signingBonus": "5850000", + "home": { + "city": "Omaha", + "state": "NE", + "country": "USA" + }, + "scoutingReport": "http://m.mlb.com/video/topic/262722544/v1868832683", + "school": { + "name": "Wichita State", + "schoolClass": "JR", + "city": "Wichita", + "country": "USA", + "state": "KS" + }, + "blurb": "Coming into 2018, scouts were divided as to which of Wichita State's two potential first-round position players was better. Greyson Jenista has more all-around ability and won the Cape Cod League MVP award last summer, but Bohm posted better numbers there and does a better job of tapping into his considerable power potential. Though Jenista has had a good junior season, Bohm has been great and settled the argument, thrusting himself into consideration as a top-five-overall pick. Bohm manages the strike zone very well and makes consistent hard contact from the right side of the plate. He doesn't strike out as much as Jenista, has an edge in bat speed and his stroke is more geared to generate power at this point. He understands that he doesn't have to sell out to hit home runs, so he doesn't. Bohm doesn't offer much when he's outside of the batter's box, however. Though he has worked diligently to improve at third base, he lacks quickness and range, his hands are just fair and his arm is only average. He'll probably wind up at first base but has the offensive upside to profile there.", + "headshotLink": "https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:silo:current.png/w_120,q_auto:best/v1/people/664761/headshot/draft/current", + "person": { + "id": 664761, + "fullName": "Alec Bohm", + "link": "/api/v1/people/664761", + "firstName": "Alec", + "lastName": "Bohm", + "primaryNumber": "28", + "birthDate": "1996-08-03", + "currentAge": 26, + "birthCity": "Omaha", + "birthStateProvince": "NE", + "birthCountry": "USA", + "height": "6' 5\"", + "weight": 218, + "active": true, + "primaryPosition": { + "code": "5", + "name": "Third Base", + "type": "Infielder", + "abbreviation": "3B" + }, + "useName": "Alec", + "useLastName": "Bohm", + "middleName": "Daniel", + "boxscoreName": "Bohm", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2018, + "pronunciation": "bome; like home", + "mlbDebutDate": "2020-08-13", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Alec Bohm", + "nameSlug": "alec-bohm-664761", + "firstLastName": "Alec Bohm", + "lastFirstName": "Bohm, Alec", + "lastInitName": "Bohm, A", + "initLastName": "A Bohm", + "fullFMLName": "Alec Daniel Bohm", + "fullLFMName": "Bohm, Alec Daniel", + "strikeZoneTop": 3.45, + "strikeZoneBottom": 1.55 + }, + "team": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 143, + "name": "Philadelphia Phillies", + "link": "/api/v1/teams/143" + }, + "draftType": { + "code": "JR", + "description": "June Amateur Draft" + }, + "isDrafted": true, + "isPass": false, + "year": "2018" + }, + { + "bisPlayerId": 400315, + "pickRound": "1", + "pickNumber": 4, + "roundPickNumber": 4, + "rank": 3, + "pickValue": "6411400", + "signingBonus": "6411400", + "home": { + "city": "Elk Grove", + "state": "CA", + "country": "USA" + }, + "scoutingReport": "http://m.mlb.com/video/topic/262722544/v1868818883", + "school": { + "name": "Oregon State", + "schoolClass": "JR", + "city": "Corvallis", + "country": "USA", + "state": "OR" + }, + "blurb": "In the not-too-distant past, a player like Madrigal would have been overlooked by many teams because of his size. The combination of more organizations embracing analytics and the success of players like Jose Altuve, not to mention the superb performance of the 5-foot-7 infielder, had Madrigal poised to perhaps be the top college position player taken in the 2018 Draft. A broken wrist at the start of the season slowed him down, but he returned in April swinging a hot bat. While Madrigal might have been more of a gut feel kind of player for scouts, he now has track record on top of being a scout favorite. Analytics departments love him because of his approach at the plate that led to more walks than strikeouts in 2017, and while he doesn't have a ton of over-the-fence power, he makes consistent hard contact and is a legitimate extra-base threat. His speed and instincts should allow him to continue to be a base stealer. While Madrigal has played mostly second base in deference to Cadyn Grenier at Oregon State, some feel he could handle shortstop if need be. If not, he has the chance to be a Gold Glove caliber second baseman in the future. Even with the injury, he should land squarely in the top 10 in June.", + "headshotLink": "https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:silo:current.png/w_120,q_auto:best/v1/people/663611/headshot/draft/current", + "person": { + "id": 663611, + "fullName": "Nick Madrigal", + "link": "/api/v1/people/663611", + "firstName": "Nick", + "lastName": "Madrigal", + "primaryNumber": "1", + "birthDate": "1997-03-05", + "currentAge": 25, + "birthCity": "Sacramento", + "birthStateProvince": "CA", + "birthCountry": "USA", + "height": "5' 8\"", + "weight": 175, + "active": true, + "primaryPosition": { + "code": "4", + "name": "Second Base", + "type": "Infielder", + "abbreviation": "2B" + }, + "useName": "Nick", + "useLastName": "Madrigal", + "middleName": "M.", + "boxscoreName": "Madrigal", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2018, + "mlbDebutDate": "2020-07-31", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Nick Madrigal", + "nameSlug": "nick-madrigal-663611", + "firstLastName": "Nick Madrigal", + "lastFirstName": "Madrigal, Nick", + "lastInitName": "Madrigal, N", + "initLastName": "N Madrigal", + "fullFMLName": "Nicklaus M. Madrigal", + "fullLFMName": "Madrigal, Nicklaus M.", + "strikeZoneTop": 2.98, + "strikeZoneBottom": 1.31 + }, + "team": { + "springLeague": { + "id": 114, + "name": "Cactus League", + "link": "/api/v1/league/114", + "abbreviation": "CL" + }, + "allStarStatus": "N", + "id": 145, + "name": "Chicago White Sox", + "link": "/api/v1/teams/145" + }, + "draftType": { + "code": "JR", + "description": "June Amateur Draft" + }, + "isDrafted": true, + "isPass": false, + "year": "2018" + } ] + } ] + } +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/gamepace/gamepace.json b/tests/mock_tests/mock_json/gamepace/gamepace.json new file mode 100644 index 00000000..0de13123 --- /dev/null +++ b/tests/mock_tests/mock_json/gamepace/gamepace.json @@ -0,0 +1,594 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "teams": [], + "leagues": [], + "sports": [ + { + "hitsPer9Inn": 16.68, + "runsPer9Inn": 9.3, + "pitchesPer9Inn": 299.83, + "plateAppearancesPer9Inn": 76.8, + "hitsPerGame": 16.26, + "runsPerGame": 9.06, + "inningsPlayedPerGame": 8.8, + "pitchesPerGame": 292.24, + "pitchersPerGame": 8.87, + "plateAppearancesPerGame": 74.85, + "totalGameTime": "7749:55:59", + "totalInningsPlayed": 21379.5, + "totalHits": 39484, + "totalRuns": 22010, + "totalPlateAppearances": 181818, + "totalPitchers": 21545, + "totalPitches": 709851, + "totalGames": 2429, + "total7InnGames": 118, + "total9InnGames": 2095, + "totalExtraInnGames": 231, + "timePerGame": "03:11:26", + "timePerPitch": "00:00:39", + "timePerHit": "00:11:46", + "timePerRun": "00:21:07", + "timePerPlateAppearance": "00:02:33", + "timePer9Inn": "03:16:24", + "timePer77PlateAppearances": "03:16:55", + "totalExtraInnTime": "871:52:00", + "timePer7InnGame": "02:24:52", + "timePer7InnGameWithoutExtraInn": "253:31:59", + "total7InnGamesScheduled": 121, + "total7InnGamesWithoutExtraInn": 105, + "total9InnGamesCompletedEarly": 7, + "total9InnGamesWithoutExtraInn": 2086, + "total9InnGamesScheduled": 2308, + "hitsPerRun": 1.794, + "pitchesPerPitcher": 32.947, + "season": "2021", + "sport": { + "id": 1, + "code": "mlb", + "link": "/api/v1/sports/1" + }, + "prPortalCalculatedFields": { + "total7InnGames": 118, + "total9InnGames": 2095, + "totalExtraInnGames": 216, + "timePer7InnGame": "02:25:50", + "timePer9InnGame": "03:10:04", + "timePerExtraInnGame": "03:49:32" + } + }, + { + "hitsPer9Inn": 17.59, + "runsPer9Inn": 10.64, + "pitchesPer9Inn": 309.67, + "plateAppearancesPer9Inn": 78.96, + "hitsPerGame": 16.89, + "runsPerGame": 10.22, + "inningsPlayedPerGame": 8.68, + "pitchesPerGame": 297.43, + "pitchersPerGame": 8.66, + "plateAppearancesPerGame": 75.84, + "totalGameTime": "5835:27:59", + "totalInningsPlayed": 16704, + "totalHits": 32518, + "totalRuns": 19679, + "totalPlateAppearances": 145997, + "totalPitchers": 16679, + "totalPitches": 572552, + "totalGames": 1925, + "total7InnGames": 202, + "total9InnGames": 1579, + "totalExtraInnGames": 158, + "timePerGame": "03:01:53", + "timePerPitch": "00:00:36", + "timePerHit": "00:10:46", + "timePerRun": "00:17:47", + "timePerPlateAppearance": "00:02:23", + "timePer9Inn": "03:09:22", + "timePer77PlateAppearances": "03:04:39", + "totalExtraInnTime": "555:33:00", + "timePer7InnGame": "02:19:38", + "timePer7InnGameWithoutExtraInn": "386:21:00", + "total7InnGamesScheduled": 184, + "total7InnGamesCompletedEarly": 3, + "total7InnGamesWithoutExtraInn": 166, + "total9InnGamesCompletedEarly": 27, + "total9InnGamesWithoutExtraInn": 1571, + "total9InnGamesScheduled": 1741, + "hitsPerRun": 1.652, + "pitchesPerPitcher": 34.328, + "season": "2021", + "sport": { + "id": 11, + "code": "aaa", + "link": "/api/v1/sports/11" + }, + "prPortalCalculatedFields": { + "total7InnGames": 202, + "total9InnGames": 1579, + "totalExtraInnGames": 144, + "timePer7InnGame": "02:19:38", + "timePer9InnGame": "03:04:11", + "timePerExtraInnGame": "03:35:53" + } + }, + { + "hitsPer9Inn": 16.68, + "runsPer9Inn": 9.87, + "pitchesPer9Inn": 304.21, + "plateAppearancesPer9Inn": 77.79, + "hitsPerGame": 15.95, + "runsPerGame": 9.43, + "inningsPlayedPerGame": 8.63, + "pitchesPerGame": 290.88, + "pitchersPerGame": 7.47, + "plateAppearancesPerGame": 74.38, + "totalGameTime": "5075:12:59", + "totalInningsPlayed": 15027.5, + "totalHits": 27764, + "totalRuns": 16423, + "totalPlateAppearances": 129504, + "totalPitchers": 13001, + "totalPitches": 506430, + "totalGames": 1741, + "total7InnGames": 212, + "total9InnGames": 1403, + "totalExtraInnGames": 138, + "timePerGame": "02:54:54", + "timePerPitch": "00:00:36", + "timePerHit": "00:10:58", + "timePerRun": "00:18:32", + "timePerPlateAppearance": "00:02:21", + "timePer9Inn": "03:02:55", + "timePer77PlateAppearances": "03:01:03", + "totalExtraInnTime": "479:41:00", + "timePer7InnGame": "02:13:22", + "timePer7InnGameWithoutExtraInn": "400:07:59", + "total7InnGamesScheduled": 196, + "total7InnGamesCompletedEarly": 2, + "total7InnGamesWithoutExtraInn": 180, + "total9InnGamesCompletedEarly": 24, + "total9InnGamesWithoutExtraInn": 1397, + "total9InnGamesScheduled": 1545, + "hitsPerRun": 1.691, + "pitchesPerPitcher": 38.953, + "season": "2021", + "sport": { + "id": 12, + "code": "aax", + "link": "/api/v1/sports/12" + }, + "prPortalCalculatedFields": { + "total7InnGames": 212, + "total9InnGames": 1403, + "totalExtraInnGames": 126, + "timePer7InnGame": "02:13:53", + "timePer9InnGame": "02:57:40", + "timePerExtraInnGame": "03:33:03" + } + }, + { + "hitsPer9Inn": 16.67, + "runsPer9Inn": 10.51, + "pitchesPer9Inn": 306.75, + "plateAppearancesPer9Inn": 78.5, + "hitsPerGame": 16.05, + "runsPerGame": 10.12, + "inningsPlayedPerGame": 8.69, + "pitchesPerGame": 295.21, + "pitchersPerGame": 7.39, + "plateAppearancesPerGame": 75.55, + "totalGameTime": "5398:07:00", + "totalInningsPlayed": 15398.5, + "totalHits": 28433, + "totalRuns": 17926, + "totalPlateAppearances": 133872, + "totalPitchers": 13087, + "totalPitches": 523119, + "totalGames": 1772, + "total7InnGames": 169, + "total9InnGames": 1476, + "totalExtraInnGames": 142, + "timePerGame": "03:02:46", + "timePerPitch": "00:00:37", + "timePerHit": "00:11:23", + "timePerRun": "00:18:04", + "timePerPlateAppearance": "00:02:25", + "timePer9Inn": "03:09:55", + "timePer77PlateAppearances": "03:06:17", + "totalExtraInnTime": "498:22:59", + "timePer7InnGame": "02:19:31", + "timePer7InnGameWithoutExtraInn": "346:29:00", + "total7InnGamesScheduled": 166, + "total7InnGamesWithoutExtraInn": 149, + "total9InnGamesCompletedEarly": 16, + "total9InnGamesWithoutExtraInn": 1465, + "total9InnGamesScheduled": 1606, + "hitsPerRun": 1.586, + "pitchesPerPitcher": 39.972, + "season": "2021", + "sport": { + "id": 13, + "code": "afa", + "link": "/api/v1/sports/13" + }, + "prPortalCalculatedFields": { + "total7InnGames": 169, + "total9InnGames": 1476, + "totalExtraInnGames": 127, + "timePer7InnGame": "02:21:38", + "timePer9InnGame": "03:04:36", + "timePerExtraInnGame": "03:36:11" + } + }, + { + "hitsPer9Inn": 16.88, + "runsPer9Inn": 11.05, + "pitchesPer9Inn": 311.92, + "plateAppearancesPer9Inn": 80.16, + "hitsPerGame": 16.1, + "runsPerGame": 10.54, + "inningsPlayedPerGame": 8.61, + "pitchesPerGame": 297.49, + "pitchersPerGame": 7.31, + "plateAppearancesPerGame": 76.45, + "totalGameTime": "5265:45:00", + "totalInningsPlayed": 15308.5, + "totalHits": 28605, + "totalRuns": 18729, + "totalPlateAppearances": 135845, + "totalPitchers": 12988, + "totalPitches": 528642, + "totalGames": 1777, + "total7InnGames": 224, + "total9InnGames": 1432, + "totalExtraInnGames": 139, + "timePerGame": "02:57:47", + "timePerPitch": "00:00:35", + "timePerHit": "00:11:02", + "timePerRun": "00:16:52", + "timePerPlateAppearance": "00:02:19", + "timePer9Inn": "03:06:25", + "timePer77PlateAppearances": "02:59:05", + "totalExtraInnTime": "465:30:00", + "timePer7InnGame": "02:24:00", + "timePer7InnGameWithoutExtraInn": "465:37:59", + "total7InnGamesScheduled": 215, + "total7InnGamesCompletedEarly": 2, + "total7InnGamesWithoutExtraInn": 194, + "total9InnGamesCompletedEarly": 20, + "total9InnGamesWithoutExtraInn": 1422, + "total9InnGamesScheduled": 1562, + "hitsPerRun": 1.527, + "pitchesPerPitcher": 40.702, + "season": "2021", + "sport": { + "id": 14, + "code": "afx", + "link": "/api/v1/sports/14" + }, + "prPortalCalculatedFields": { + "total7InnGames": 224, + "total9InnGames": 1432, + "totalExtraInnGames": 121, + "timePer7InnGame": "02:24:27", + "timePer9InnGame": "03:00:34", + "timePerExtraInnGame": "03:26:42" + } + }, + { + "hitsPer9Inn": 16.3, + "runsPer9Inn": 11.47, + "pitchesPer9Inn": 159.19, + "plateAppearancesPer9Inn": 80.6, + "hitsPerGame": 13.98, + "runsPerGame": 9.83, + "inningsPlayedPerGame": 7.76, + "pitchesPerGame": 136.48, + "pitchersPerGame": 7.67, + "plateAppearancesPerGame": 69.1, + "totalGameTime": "6650:29:00", + "totalInningsPlayed": 17870, + "totalHits": 32192, + "totalRuns": 22642, + "totalPlateAppearances": 159142, + "totalPitchers": 17658, + "totalPitches": 314302, + "totalGames": 2303, + "total7InnGames": 1140, + "total9InnGames": 1060, + "totalExtraInnGames": 191, + "timePerGame": "02:53:15", + "timePerPitch": "00:01:16", + "timePerHit": "00:12:23", + "timePerRun": "00:17:37", + "timePerPlateAppearance": "00:02:30", + "timePer9Inn": "03:22:05", + "timePer77PlateAppearances": "03:13:04", + "totalExtraInnTime": "645:43:00", + "timePer7InnGame": "02:32:04", + "timePer7InnGameWithoutExtraInn": "2329:22:59", + "total7InnGamesScheduled": 1061, + "total7InnGamesCompletedEarly": 19, + "total7InnGamesWithoutExtraInn": 919, + "total9InnGamesCompletedEarly": 155, + "total9InnGamesWithoutExtraInn": 1000, + "total9InnGamesScheduled": 1242, + "hitsPerRun": 1.422, + "pitchesPerPitcher": 17.799, + "season": "2021", + "sport": { + "id": 16, + "code": "rok", + "link": "/api/v1/sports/16" + }, + "prPortalCalculatedFields": { + "total7InnGames": 1140, + "total9InnGames": 1060, + "totalExtraInnGames": 84, + "timePer7InnGame": "02:33:42", + "timePer9InnGame": "03:12:28", + "timePerExtraInnGame": "03:55:22" + } + }, + { + "hitsPer9Inn": 17.93, + "runsPer9Inn": 9.58, + "pitchesPer9Inn": 282.55, + "plateAppearancesPer9Inn": 78.78, + "hitsPerGame": 17.49, + "runsPerGame": 9.34, + "inningsPlayedPerGame": 8.81, + "pitchesPerGame": 275.56, + "pitchersPerGame": 10.79, + "plateAppearancesPerGame": 76.84, + "totalGameTime": "2944:27:00", + "totalInningsPlayed": 7425.5, + "totalHits": 14742, + "totalRuns": 7876, + "totalPlateAppearances": 64772, + "totalPitchers": 9097, + "totalPitches": 232298, + "totalGames": 843, + "total7InnGames": 47, + "total9InnGames": 725, + "totalExtraInnGames": 73, + "timePerGame": "03:29:34", + "timePerPitch": "00:00:45", + "timePerHit": "00:11:59", + "timePerRun": "00:22:25", + "timePerPlateAppearance": "00:02:43", + "timePer9Inn": "03:34:53", + "timePer77PlateAppearances": "03:30:01", + "totalExtraInnTime": "314:00:00", + "timePer7InnGame": "02:23:05", + "timePer7InnGameWithoutExtraInn": "97:46:59", + "total7InnGamesScheduled": 43, + "total7InnGamesWithoutExtraInn": 41, + "total9InnGamesCompletedEarly": 4, + "total9InnGamesWithoutExtraInn": 725, + "total9InnGamesScheduled": 800, + "hitsPerRun": 1.872, + "pitchesPerPitcher": 25.536, + "season": "2021", + "sport": { + "id": 17, + "code": "win", + "link": "/api/v1/sports/17" + }, + "prPortalCalculatedFields": { + "total7InnGames": 47, + "total9InnGames": 725, + "totalExtraInnGames": 71, + "timePer7InnGame": "02:27:29", + "timePer9InnGame": "03:28:37", + "timePerExtraInnGame": "04:20:17" + } + }, + { + "hitsPer9Inn": 17.25, + "runsPer9Inn": 12.7, + "pitchesPer9Inn": 319.42, + "plateAppearancesPer9Inn": 81.67, + "hitsPerGame": 16.2, + "runsPerGame": 11.92, + "inningsPlayedPerGame": 8.47, + "pitchesPerGame": 299.9, + "pitchersPerGame": 8.06, + "plateAppearancesPerGame": 76.68, + "totalGameTime": "154:03:00", + "totalInningsPlayed": 423.5, + "totalHits": 810, + "totalRuns": 596, + "totalPlateAppearances": 3834, + "totalPitchers": 403, + "totalPitches": 14995, + "totalGames": 50, + "total7InnGames": 8, + "total9InnGames": 41, + "totalExtraInnGames": 1, + "timePerGame": "03:04:51", + "timePerPitch": "00:00:36", + "timePerHit": "00:11:24", + "timePerRun": "00:15:30", + "timePerPlateAppearance": "00:02:24", + "timePer9Inn": "03:16:53", + "timePer77PlateAppearances": "03:05:37", + "totalExtraInnTime": "03:19:00", + "timePer7InnGameWithoutExtraInn": "00:00:00", + "total9InnGamesCompletedEarly": 10, + "total9InnGamesWithoutExtraInn": 39, + "total9InnGamesScheduled": 50, + "hitsPerRun": 1.359, + "pitchesPerPitcher": 37.208, + "season": "2021", + "sport": { + "id": 22, + "code": "bbc", + "link": "/api/v1/sports/22" + }, + "prPortalCalculatedFields": { + "total7InnGames": 8, + "total9InnGames": 41, + "totalExtraInnGames": 1, + "timePer7InnGame": "02:38:37", + "timePer9InnGame": "03:09:38", + "timePerExtraInnGame": "03:19:00" + } + }, + { + "hitsPer9Inn": 20.34, + "runsPer9Inn": 12.36, + "pitchesPer9Inn": 310.01, + "plateAppearancesPer9Inn": 82.08, + "hitsPerGame": 19.25, + "runsPerGame": 11.7, + "inningsPlayedPerGame": 8.55, + "pitchesPerGame": 293.52, + "pitchersPerGame": 8.84, + "plateAppearancesPerGame": 77.72, + "totalGameTime": "3434:27:00", + "totalInningsPlayed": 9072.5, + "totalHits": 20429, + "totalRuns": 12413, + "totalPlateAppearances": 82457, + "totalPitchers": 9383, + "totalPitches": 311427, + "totalGames": 1061, + "total7InnGames": 170, + "total9InnGames": 820, + "totalExtraInnGames": 87, + "timePerGame": "03:14:13", + "timePerPitch": "00:00:39", + "timePerHit": "00:10:05", + "timePerRun": "00:16:36", + "timePerPlateAppearance": "00:02:29", + "timePer9Inn": "03:25:07", + "timePer77PlateAppearances": "03:12:25", + "totalExtraInnTime": "330:42:59", + "timePer7InnGame": "02:33:04", + "timePer7InnGameWithoutExtraInn": "392:54:00", + "total7InnGamesScheduled": 173, + "total7InnGamesCompletedEarly": 1, + "total7InnGamesWithoutExtraInn": 154, + "total9InnGamesCompletedEarly": 14, + "total9InnGamesWithoutExtraInn": 805, + "total9InnGamesScheduled": 888, + "hitsPerRun": 1.646, + "pitchesPerPitcher": 33.191, + "season": "2021", + "sport": { + "id": 23, + "code": "ind", + "link": "/api/v1/sports/23" + }, + "prPortalCalculatedFields": { + "total7InnGames": 170, + "total9InnGames": 820, + "totalExtraInnGames": 71, + "timePer7InnGame": "02:32:55", + "timePer9InnGame": "03:19:05", + "timePerExtraInnGame": "03:56:50" + } + }, + { + "hitsPer9Inn": 18.56, + "runsPer9Inn": 9.91, + "pitchesPer9Inn": 311.55, + "plateAppearancesPer9Inn": 81.63, + "hitsPerGame": 17.6, + "runsPerGame": 9.4, + "inningsPlayedPerGame": 8.7, + "pitchesPerGame": 295.4, + "pitchersPerGame": 10.2, + "plateAppearancesPerGame": 77.4, + "totalGameTime": "15:39:59", + "totalInningsPlayed": 43.5, + "totalHits": 88, + "totalRuns": 47, + "totalPlateAppearances": 387, + "totalPitchers": 51, + "totalPitches": 1477, + "totalGames": 5, + "total9InnGames": 5, + "timePerGame": "03:07:59", + "timePerPitch": "00:00:38", + "timePerHit": "00:10:40", + "timePerRun": "00:19:59", + "timePerPlateAppearance": "00:02:25", + "timePer9Inn": "03:18:16", + "timePer77PlateAppearances": "03:07:01", + "totalExtraInnTime": "00:00:00", + "timePer7InnGameWithoutExtraInn": "00:00:00", + "total9InnGamesCompletedEarly": 1, + "total9InnGamesWithoutExtraInn": 4, + "total9InnGamesScheduled": 5, + "hitsPerRun": 1.872, + "pitchesPerPitcher": 28.961, + "season": "2021", + "sport": { + "id": 51, + "code": "int", + "link": "/api/v1/sports/51" + }, + "prPortalCalculatedFields": { + "total7InnGames": null, + "total9InnGames": 5, + "totalExtraInnGames": null, + "timePer7InnGame": null, + "timePer9InnGame": "03:07:59", + "timePerExtraInnGame": null + } + }, + { + "hitsPer9Inn": 11.82, + "runsPer9Inn": 8.76, + "pitchesPer9Inn": 316.06, + "plateAppearancesPer9Inn": 70.16, + "hitsPerGame": 12.33, + "runsPerGame": 9.14, + "inningsPlayedPerGame": 9.5, + "pitchesPerGame": 329.71, + "pitchersPerGame": 11.38, + "plateAppearancesPerGame": 73.19, + "totalGameTime": "59:37:00", + "totalInningsPlayed": 199.5, + "totalHits": 259, + "totalRuns": 192, + "totalPlateAppearances": 1537, + "totalPitchers": 239, + "totalPitches": 6924, + "totalGames": 21, + "total7InnGames": 1, + "total9InnGames": 9, + "timePerGame": "02:50:20", + "timePerPitch": "00:00:30", + "timePerHit": "00:13:48", + "timePerRun": "00:18:37", + "timePerPlateAppearance": "00:02:19", + "timePer9Inn": "02:43:16", + "timePer77PlateAppearances": "02:59:11", + "totalExtraInnTime": "00:00:00", + "timePer7InnGameWithoutExtraInn": "00:00:00", + "total9InnGamesCompletedEarly": 1, + "total9InnGamesWithoutExtraInn": 9, + "total9InnGamesScheduled": 10, + "hitsPerRun": 1.349, + "pitchesPerPitcher": 28.971, + "season": "2021", + "sport": { + "id": 586, + "code": "hsb", + "link": "/api/v1/sports/586" + }, + "prPortalCalculatedFields": { + "total7InnGames": 1, + "total9InnGames": 9, + "totalExtraInnGames": 11, + "timePer7InnGame": "02:00:00", + "timePer9InnGame": "02:57:06", + "timePerExtraInnGame": "02:49:21" + } + } + ] + } \ No newline at end of file diff --git a/tests/mock_tests/mock_json/games/game.json b/tests/mock_tests/mock_json/games/game.json new file mode 100644 index 00000000..2ede2ac2 --- /dev/null +++ b/tests/mock_tests/mock_json/games/game.json @@ -0,0 +1,3315 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "gamePk": 715720, + "link": "/api/v1.1/game/715720/feed/live", + "metaData": { + "wait": 10, + "timeStamp": "20221104_040316", + "gameEvents": [ + "field_out", + "game_finished" + ], + "logicalEvents": [ + "midInning", + "countChange", + "count32", + "newRightHandedHit", + "basesEmpty", + "gameStateChangeToGameOver" + ] + }, + "gameData": { + "game": { + "pk": 715720, + "type": "W", + "doubleHeader": "N", + "id": "2022/11/03/houmlb-phimlb-1", + "gamedayType": "P", + "tiebreaker": "N", + "gameNumber": 1, + "calendarEventID": "14-715720-2022-11-03", + "season": "2022", + "seasonDisplay": "2022" + }, + "datetime": { + "dateTime": "2022-11-04T00:03:00Z", + "originalDate": "2022-11-03", + "officialDate": "2022-11-03", + "dayNight": "night", + "time": "8:03", + "ampm": "PM" + }, + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117", + "season": 2022, + "venue": { + "id": 2392, + "name": "Minute Maid Park", + "link": "/api/v1/venues/2392" + }, + "springVenue": { + "id": 5000, + "link": "/api/v1/venues/5000" + }, + "teamCode": "hou", + "fileCode": "hou", + "abbreviation": "HOU", + "teamName": "Astros", + "locationName": "Houston", + "firstYearOfPlay": "1962", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "division": { + "id": 200, + "name": "American League West", + "link": "/api/v1/divisions/200" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "name": "Major League Baseball" + }, + "shortName": "Houston", + "record": { + "gamesPlayed": 5, + "wildCardGamesBack": "-", + "leagueGamesBack": "-", + "springLeagueGamesBack": "-", + "sportGamesBack": "-", + "divisionGamesBack": "-", + "conferenceGamesBack": "-", + "leagueRecord": { + "wins": 3, + "losses": 2, + "ties": 0, + "pct": ".600" + }, + "records": {}, + "divisionLeader": false, + "wins": 3, + "losses": 2, + "winningPercentage": ".600" + }, + "franchiseName": "Houston", + "clubName": "Astros", + "active": true + }, + "home": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 143, + "name": "Philadelphia Phillies", + "link": "/api/v1/teams/143", + "season": 2022, + "venue": { + "id": 2681, + "name": "Citizens Bank Park", + "link": "/api/v1/venues/2681" + }, + "springVenue": { + "id": 2700, + "link": "/api/v1/venues/2700" + }, + "teamCode": "phi", + "fileCode": "phi", + "abbreviation": "PHI", + "teamName": "Phillies", + "locationName": "Philadelphia", + "firstYearOfPlay": "1883", + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + }, + "division": { + "id": 204, + "name": "National League East", + "link": "/api/v1/divisions/204" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "name": "Major League Baseball" + }, + "shortName": "Philadelphia", + "record": { + "gamesPlayed": 5, + "wildCardGamesBack": "-", + "leagueGamesBack": "-", + "springLeagueGamesBack": "-", + "sportGamesBack": "-", + "divisionGamesBack": "-", + "conferenceGamesBack": "-", + "leagueRecord": { + "wins": 2, + "losses": 3, + "ties": 0, + "pct": ".400" + }, + "records": {}, + "divisionLeader": false, + "wins": 2, + "losses": 3, + "winningPercentage": ".400" + }, + "franchiseName": "Philadelphia", + "clubName": "Phillies", + "active": true + } + }, + "players": { + "ID669016": { + "id": 669016, + "fullName": "Brandon Marsh", + "link": "/api/v1/people/669016", + "firstName": "Brandon", + "lastName": "Marsh", + "primaryNumber": "16", + "birthDate": "1997-12-18", + "currentAge": 24, + "birthCity": "Buford", + "birthStateProvince": "GA", + "birthCountry": "USA", + "height": "6' 4\"", + "weight": 215, + "active": true, + "primaryPosition": { + "code": "8", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "CF" + }, + "useName": "Brandon", + "middleName": "Chase", + "boxscoreName": "Marsh", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2016, + "mlbDebutDate": "2021-07-18", + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Brandon Marsh", + "nameSlug": "brandon-marsh-669016", + "firstLastName": "Brandon Marsh", + "lastFirstName": "Marsh, Brandon", + "lastInitName": "Marsh, B", + "initLastName": "B Marsh", + "fullFMLName": "Brandon Chase Marsh", + "fullLFMName": "Marsh, Brandon Chase", + "strikeZoneTop": 3.11, + "strikeZoneBottom": 1.46 + }, + "ID664285": { + "id": 664285, + "fullName": "Framber Valdez", + "link": "/api/v1/people/664285", + "firstName": "Framber", + "lastName": "Valdez", + "primaryNumber": "59", + "birthDate": "1993-11-19", + "currentAge": 29, + "birthCity": "Palenque", + "birthCountry": "Dominican Republic", + "height": "5' 11\"", + "weight": 239, + "active": true, + "primaryPosition": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "useName": "Framber", + "boxscoreName": "Valdez, F", + "gender": "M", + "nameMatrilineal": "Pinales", + "isPlayer": true, + "isVerified": true, + "pronunciation": "FRAHM-burr", + "mlbDebutDate": "2018-08-21", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "L", + "description": "Left" + }, + "nameFirstLast": "Framber Valdez", + "nameSlug": "framber-valdez-664285", + "firstLastName": "Framber Valdez", + "lastFirstName": "Valdez, Framber", + "lastInitName": "Valdez, F", + "initLastName": "F Valdez", + "fullFMLName": "Framber Valdez", + "fullLFMName": "Valdez, Framber", + "strikeZoneTop": 3.319, + "strikeZoneBottom": 1.513 + } + }, + "venue": { + "id": 2681, + "name": "Citizens Bank Park", + "link": "/api/v1/venues/2681", + "location": { + "address1": "One Citizens Bank Way", + "city": "Philadelphia", + "state": "Pennsylvania", + "stateAbbrev": "PA", + "postalCode": "19148", + "defaultCoordinates": { + "latitude": 39.90539086, + "longitude": -75.16716957 + }, + "country": "USA", + "phone": "(215) 463-6000" + }, + "timeZone": { + "id": "America/New_York", + "offset": -5, + "tz": "EST" + }, + "fieldInfo": { + "capacity": 42901, + "turfType": "Grass", + "roofType": "Open", + "leftLine": 329, + "left": 369, + "leftCenter": 381, + "center": 401, + "rightCenter": 398, + "right": 369, + "rightLine": 330 + }, + "active": true + }, + "officialVenue": { + "id": 2681, + "link": "/api/v1/venues/2681" + }, + "weather": { + "condition": "Clear", + "temp": "59", + "wind": "2 mph, Out To CF" + }, + "gameInfo": { + "attendance": 45693, + "firstPitch": "2022-11-04T00:06:00.000Z", + "gameDurationMinutes": 237 + }, + "review": { + "hasChallenges": true, + "away": { + "used": 0, + "remaining": 2 + }, + "home": { + "used": 0, + "remaining": 2 + } + }, + "flags": { + "noHitter": false, + "perfectGame": false, + "awayTeamNoHitter": false, + "awayTeamPerfectGame": false, + "homeTeamNoHitter": false, + "homeTeamPerfectGame": false + }, + "alerts": [], + "probablePitchers": { + "away": { + "id": 434378, + "fullName": "Justin Verlander", + "link": "/api/v1/people/434378" + }, + "home": { + "id": 592789, + "fullName": "Noah Syndergaard", + "link": "/api/v1/people/592789" + } + }, + "officialScorer": { + "id": 433437, + "fullName": "Mike Maconi", + "link": "/api/v1/people/433437" + }, + "primaryDatacaster": { + "id": 427544, + "fullName": "Hank Widmer", + "link": "/api/v1/people/427544" + } + }, + "liveData": { + "plays": { + "allPlays": [ + { + "result": { + "type": "atBat", + "event": "Double", + "eventType": "double", + "description": "Jose Altuve doubles (3) on a sharp fly ball to center fielder Brandon Marsh. Jose Altuve to 3rd. Jose Altuve advances to 3rd, on a fielding error by center fielder Brandon Marsh.", + "rbi": 0, + "awayScore": 0, + "homeScore": 0 + }, + "about": { + "atBatIndex": 0, + "halfInning": "top", + "isTopInning": true, + "inning": 1, + "startTime": "2022-11-04T00:06:04.539Z", + "endTime": "2022-11-04T00:06:40.700Z", + "isComplete": true, + "isScoringPlay": false, + "hasReview": false, + "hasOut": false, + "captivatingIndex": 34 + }, + "count": { + "balls": 1, + "strikes": 0, + "outs": 0 + }, + "matchup": { + "batter": { + "id": 514888, + "fullName": "Jose Altuve", + "link": "/api/v1/people/514888" + }, + "batSide": { + "code": "R", + "description": "Right" + }, + "pitcher": { + "id": 592789, + "fullName": "Noah Syndergaard", + "link": "/api/v1/people/592789" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "postOnThird": { + "id": 514888, + "fullName": "Jose Altuve", + "link": "/api/v1/people/514888" + }, + "batterHotColdZones": [], + "pitcherHotColdZones": [], + "splits": { + "batter": "vs_RHP", + "pitcher": "vs_RHB", + "menOnBase": "RISP" + } + }, + "pitchIndex": [ + 3, + 4 + ], + "actionIndex": [ + 0, + 1, + 2 + ], + "runnerIndex": [ + 0, + 1 + ], + "runners": [ + { + "movement": { + "originBase": null, + "start": null, + "end": "2B", + "outBase": null, + "isOut": false, + "outNumber": null + }, + "details": { + "event": "Double", + "eventType": "double", + "movementReason": null, + "runner": { + "id": 514888, + "fullName": "Jose Altuve", + "link": "/api/v1/people/514888" + }, + "responsiblePitcher": null, + "isScoringEvent": false, + "rbi": false, + "earned": false, + "teamUnearned": false, + "playIndex": 4 + }, + "credits": [ + { + "player": { + "id": 669016, + "link": "/api/v1/people/669016" + }, + "position": { + "code": "8", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "CF" + }, + "credit": "f_fielded_ball" + } + ] + }, + { + "movement": { + "originBase": null, + "start": "2B", + "end": "3B", + "outBase": null, + "isOut": false, + "outNumber": null + }, + "details": { + "event": "Error", + "eventType": "error", + "movementReason": "r_adv_play", + "runner": { + "id": 514888, + "fullName": "Jose Altuve", + "link": "/api/v1/people/514888" + }, + "responsiblePitcher": null, + "isScoringEvent": false, + "rbi": false, + "earned": false, + "teamUnearned": false, + "playIndex": 4 + }, + "credits": [ + { + "player": { + "id": 669016, + "link": "/api/v1/people/669016" + }, + "position": { + "code": "8", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "CF" + }, + "credit": "f_fielding_error" + } + ] + } + ], + "playEvents": [ + { + "details": { + "description": "Status Change - Pre-Game", + "event": "Game Advisory", + "eventType": "game_advisory", + "awayScore": 0, + "homeScore": 0, + "isScoringPlay": false, + "hasReview": false + }, + "count": { + "balls": 0, + "strikes": 0, + "outs": 0 + }, + "index": 0, + "startTime": "2022-11-03T20:27:06.286Z", + "endTime": "2022-11-03T23:40:33.028Z", + "isPitch": false, + "type": "action", + "player": { + "id": 514888, + "link": "/api/v1/people/514888" + } + }, + { + "details": { + "description": "Status Change - Warmup", + "event": "Game Advisory", + "eventType": "game_advisory", + "awayScore": 0, + "homeScore": 0, + "isScoringPlay": false, + "hasReview": false + }, + "count": { + "balls": 0, + "strikes": 0, + "outs": 0 + }, + "index": 1, + "startTime": "2022-11-03T23:40:33.028Z", + "endTime": "2022-11-04T00:05:45.901Z", + "isPitch": false, + "type": "action", + "player": { + "id": 514888, + "link": "/api/v1/people/514888" + } + }, + { + "details": { + "description": "Status Change - In Progress", + "event": "Game Advisory", + "eventType": "game_advisory", + "awayScore": 0, + "homeScore": 0, + "isScoringPlay": false, + "hasReview": false + }, + "count": { + "balls": 0, + "strikes": 0, + "outs": 0 + }, + "index": 2, + "startTime": "2022-11-04T00:05:45.901Z", + "endTime": "2022-11-04T00:06:06.873Z", + "isPitch": false, + "type": "action", + "player": { + "id": 514888, + "link": "/api/v1/people/514888" + } + }, + { + "details": { + "call": { + "code": "B", + "description": "Ball" + }, + "description": "Ball", + "code": "B", + "ballColor": "rgba(39, 161, 39, 1.0)", + "trailColor": "rgba(50, 0, 221, 1.0)", + "isInPlay": false, + "isStrike": false, + "isBall": true, + "type": { + "code": "SI", + "description": "Sinker" + }, + "hasReview": false + }, + "count": { + "balls": 1, + "strikes": 0, + "outs": 0 + }, + "pitchData": { + "startSpeed": 94.6, + "endSpeed": 86.6, + "strikeZoneTop": 2.93, + "strikeZoneBottom": 1.35, + "coordinates": { + "aY": 30.82, + "aZ": -18.55, + "pfxX": -6.53, + "pfxZ": 7.13, + "pX": 1.07, + "pZ": 2.11, + "vX0": 7.72, + "vY0": -137.49, + "vZ0": -7.23, + "x": 76.12, + "y": 181.81, + "x0": -0.93, + "y0": 50, + "z0": 6.04, + "aX": -12.49 + }, + "breaks": { + "breakAngle": 28.8, + "breakLength": 4.8, + "breakY": 24, + "spinRate": 2120, + "spinDirection": 216 + }, + "zone": 14, + "typeConfidence": 0.84, + "plateTime": 0.4, + "extension": 6.65 + }, + "index": 3, + "playId": "01d0b33f-b99e-477a-92c5-280a1b4bda0a", + "pitchNumber": 1, + "startTime": "2022-11-04T00:06:06.873Z", + "endTime": "2022-11-04T00:06:10.638Z", + "isPitch": true, + "type": "pitch" + }, + { + "details": { + "call": { + "code": "D", + "description": "In play, no out" + }, + "description": "In play, no out", + "code": "D", + "ballColor": "rgba(26, 86, 190, 1.0)", + "trailColor": "rgba(50, 0, 221, 1.0)", + "isInPlay": true, + "isStrike": false, + "isBall": false, + "type": { + "code": "SI", + "description": "Sinker" + }, + "hasReview": false + }, + "count": { + "balls": 1, + "strikes": 0, + "outs": 0 + }, + "pitchData": { + "startSpeed": 93.9, + "endSpeed": 85.8, + "strikeZoneTop": 2.85, + "strikeZoneBottom": 1.3, + "coordinates": { + "aY": 30.77, + "aZ": -17.31, + "pfxX": -7.5, + "pfxZ": 7.9, + "pX": 0.44, + "pZ": 2.33, + "vX0": 6.17, + "vY0": -136.51, + "vZ0": -6.87, + "x": 100.33, + "y": 175.86, + "x0": -0.88, + "y0": 50, + "z0": 6.08, + "aX": -14.11 + }, + "breaks": { + "breakAngle": 33.6, + "breakLength": 4.8, + "breakY": 24, + "spinRate": 2238, + "spinDirection": 214 + }, + "zone": 6, + "typeConfidence": 0.91, + "plateTime": 0.4, + "extension": 6.69 + }, + "hitData": { + "launchSpeed": 104.9, + "launchAngle": 26, + "totalDistance": 399, + "trajectory": "fly_ball", + "hardness": "hard", + "location": "8", + "coordinates": { + "coordX": 162.78, + "coordY": 41.75 + } + }, + "index": 4, + "playId": "cfd9887e-d9a1-41d9-9eab-8f8610185f31", + "pitchNumber": 2, + "startTime": "2022-11-04T00:06:23.669Z", + "endTime": "2022-11-04T00:06:40.700Z", + "isPitch": true, + "type": "pitch" + } + ], + "playEndTime": "2022-11-04T00:06:40.700Z", + "atBatIndex": 0 + }, + { + "result": { + "type": "atBat", + "event": "Single", + "eventType": "single", + "description": "Jeremy Pena singles on a ground ball to center fielder Brandon Marsh. Jose Altuve scores.", + "rbi": 1, + "awayScore": 1, + "homeScore": 0 + }, + "about": { + "atBatIndex": 1, + "halfInning": "top", + "isTopInning": true, + "inning": 1, + "startTime": "2022-11-04T00:07:07.446Z", + "endTime": "2022-11-04T00:07:54.663Z", + "isComplete": true, + "isScoringPlay": true, + "hasReview": false, + "hasOut": false, + "captivatingIndex": 33 + }, + "count": { + "balls": 0, + "strikes": 1, + "outs": 0 + }, + "matchup": { + "batter": { + "id": 665161, + "fullName": "Jeremy Pena", + "link": "/api/v1/people/665161" + }, + "batSide": { + "code": "R", + "description": "Right" + }, + "pitcher": { + "id": 592789, + "fullName": "Noah Syndergaard", + "link": "/api/v1/people/592789" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "postOnFirst": { + "id": 665161, + "fullName": "Jeremy Pena", + "link": "/api/v1/people/665161" + }, + "batterHotColdZones": [], + "pitcherHotColdZones": [], + "splits": { + "batter": "vs_RHP", + "pitcher": "vs_RHB", + "menOnBase": "Men_On" + } + }, + "pitchIndex": [ + 0, + 1 + ], + "actionIndex": [], + "runnerIndex": [ + 0, + 1 + ], + "runners": [ + { + "movement": { + "originBase": null, + "start": null, + "end": "1B", + "outBase": null, + "isOut": false, + "outNumber": null + }, + "details": { + "event": "Single", + "eventType": "single", + "movementReason": null, + "runner": { + "id": 665161, + "fullName": "Jeremy Pena", + "link": "/api/v1/people/665161" + }, + "responsiblePitcher": null, + "isScoringEvent": false, + "rbi": false, + "earned": false, + "teamUnearned": false, + "playIndex": 1 + }, + "credits": [ + { + "player": { + "id": 669016, + "link": "/api/v1/people/669016" + }, + "position": { + "code": "8", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "CF" + }, + "credit": "f_fielded_ball" + } + ] + }, + { + "movement": { + "originBase": "3B", + "start": "3B", + "end": "score", + "outBase": null, + "isOut": false, + "outNumber": null + }, + "details": { + "event": "Single", + "eventType": "single", + "movementReason": "r_adv_play", + "runner": { + "id": 514888, + "fullName": "Jose Altuve", + "link": "/api/v1/people/514888" + }, + "responsiblePitcher": { + "id": 592789, + "link": "/api/v1/people/592789" + }, + "isScoringEvent": true, + "rbi": true, + "earned": true, + "teamUnearned": false, + "playIndex": 1 + }, + "credits": [] + } + ], + "playEvents": [ + { + "details": { + "call": { + "code": "S", + "description": "Swinging Strike" + }, + "description": "Swinging Strike", + "code": "S", + "ballColor": "rgba(170, 21, 11, 1.0)", + "trailColor": "rgba(0, 34, 255, 1.0)", + "isInPlay": false, + "isStrike": true, + "isBall": false, + "type": { + "code": "CU", + "description": "Curveball" + }, + "hasReview": false + }, + "count": { + "balls": 0, + "strikes": 1, + "outs": 0 + }, + "pitchData": { + "startSpeed": 78.5, + "endSpeed": 72.9, + "strikeZoneTop": 3.63, + "strikeZoneBottom": 1.75, + "coordinates": { + "aY": 19.67, + "aZ": -26.82, + "pfxX": 6, + "pfxZ": 4.01, + "pX": 1.39, + "pZ": 2.19, + "vX0": 3.32, + "vY0": -114.33, + "vZ0": -3.07, + "x": 64.01, + "y": 179.76, + "x0": -0.86, + "y0": 50, + "z0": 6.16, + "aX": 8 + }, + "breaks": { + "breakAngle": 16.8, + "breakLength": 8.4, + "breakY": 24, + "spinRate": 2136, + "spinDirection": 64 + }, + "zone": 14, + "typeConfidence": 0.9, + "plateTime": 0.48, + "extension": 6.8 + }, + "index": 0, + "playId": "8767a33e-bd4d-4e7e-bf31-85bcd6f5e8ff", + "pitchNumber": 1, + "startTime": "2022-11-04T00:07:19.109Z", + "endTime": "2022-11-04T00:07:24.013Z", + "isPitch": true, + "type": "pitch" + }, + { + "details": { + "call": { + "code": "E", + "description": "In play, run(s)" + }, + "description": "In play, run(s)", + "code": "E", + "ballColor": "rgba(26, 86, 190, 1.0)", + "trailColor": "rgba(50, 0, 221, 1.0)", + "isInPlay": true, + "isStrike": false, + "isBall": false, + "type": { + "code": "SI", + "description": "Sinker" + }, + "hasReview": false + }, + "count": { + "balls": 0, + "strikes": 1, + "outs": 0 + }, + "pitchData": { + "startSpeed": 95.4, + "endSpeed": 87.1, + "strikeZoneTop": 3.63, + "strikeZoneBottom": 1.75, + "coordinates": { + "aY": 32.39, + "aZ": -18.18, + "pfxX": -8.13, + "pfxZ": 7.2, + "pX": -0.77, + "pZ": 2.47, + "vX0": 3, + "vY0": -138.78, + "vZ0": -6.81, + "x": 146.42, + "y": 172.19, + "x0": -0.82, + "y0": 50, + "z0": 6.17, + "aX": -15.78 + }, + "breaks": { + "breakAngle": 37.2, + "breakLength": 4.8, + "breakY": 24, + "spinRate": 2257, + "spinDirection": 218 + }, + "zone": 13, + "typeConfidence": 0.91, + "plateTime": 0.4, + "extension": 6.56 + }, + "hitData": { + "launchSpeed": 84.1, + "launchAngle": 5, + "totalDistance": 80, + "trajectory": "ground_ball", + "hardness": "medium", + "location": "8", + "coordinates": { + "coordX": 119.07, + "coordY": 91.76 + } + }, + "index": 1, + "playId": "b8b5e484-a190-4411-a4da-cf888a80960d", + "pitchNumber": 2, + "startTime": "2022-11-04T00:07:41.564Z", + "endTime": "2022-11-04T00:07:54.663Z", + "isPitch": true, + "type": "pitch" + } + ], + "playEndTime": "2022-11-04T00:07:54.663Z", + "atBatIndex": 1 + } + ], + + "currentPlay": { + "result": { + "type": "atBat", + "event": "Groundout", + "eventType": "field_out", + "description": "Nick Castellanos grounds out, shortstop Jeremy Pena to first baseman Trey Mancini.", + "rbi": 0, + "awayScore": 3, + "homeScore": 2 + }, + "about": { + "atBatIndex": 77, + "halfInning": "bottom", + "isTopInning": false, + "inning": 9, + "startTime": "2022-11-04T03:59:56.824Z", + "endTime": "2022-11-04T04:03:16.716Z", + "isComplete": true, + "isScoringPlay": false, + "hasReview": false, + "hasOut": true, + "captivatingIndex": 0 + }, + "count": { + "balls": 3, + "strikes": 2, + "outs": 3 + }, + "matchup": { + "batter": { + "id": 592206, + "fullName": "Nick Castellanos", + "link": "/api/v1/people/592206" + }, + "batSide": { + "code": "R", + "description": "Right" + }, + "pitcher": { + "id": 519151, + "fullName": "Ryan Pressly", + "link": "/api/v1/people/519151" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "batterHotColdZoneStats": { + "stats": [ + { + "type": { + "displayName": "hotColdZones" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "name": "onBasePlusSlugging", + "zones": [ + { + "zone": "01", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "02", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "03", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": "1.000" + }, + { + "zone": "04", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "05", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "06", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "07", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "08", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "09", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "11", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": "1.000" + }, + { + "zone": "12", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "13", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": ".750" + }, + { + "zone": "14", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".286" + } + ] + } + }, + { + "stat": { + "name": "battingAverage", + "zones": [ + { + "zone": "01", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "02", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "03", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".500" + }, + { + "zone": "04", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "05", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "06", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "07", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "08", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "09", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "11", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "12", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "13", + "color": "rgba(150, 188, 255, .55)", + "temp": "cool", + "value": ".250" + }, + { + "zone": "14", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".143" + } + ] + } + }, + { + "stat": { + "name": "exitVelocity", + "zones": [ + { + "zone": "01", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "02", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "90.53" + }, + { + "zone": "03", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "96.72" + }, + { + "zone": "04", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "05", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "06", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": "79.97" + }, + { + "zone": "07", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "08", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": "62.05" + }, + { + "zone": "09", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": "85.83" + }, + { + "zone": "11", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "12", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "89.32" + }, + { + "zone": "13", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "104.92" + }, + { + "zone": "14", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": "83.11" + } + ] + } + } + ] + } + ] + }, + "batterHotColdZones": [ + { + "zone": "01", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "02", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "03", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": "1.000" + }, + { + "zone": "04", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "05", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "06", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "07", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": "-" + }, + { + "zone": "08", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "09", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "11", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": "1.000" + }, + { + "zone": "12", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".000" + }, + { + "zone": "13", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": ".750" + }, + { + "zone": "14", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".286" + } + ], + "pitcherHotColdZones": [], + "splits": { + "batter": "vs_RHP", + "pitcher": "vs_RHB", + "menOnBase": "Empty" + } + }, + "pitchIndex": [ + 0, + 1, + 2, + 3, + 4, + 5 + ], + "actionIndex": [], + "runnerIndex": [ + 0 + ], + "runners": [ + { + "movement": { + "originBase": null, + "start": null, + "end": null, + "outBase": "1B", + "isOut": true, + "outNumber": 3 + }, + "details": { + "event": "Groundout", + "eventType": "field_out", + "movementReason": null, + "runner": { + "id": 592206, + "fullName": "Nick Castellanos", + "link": "/api/v1/people/592206" + }, + "responsiblePitcher": null, + "isScoringEvent": false, + "rbi": false, + "earned": false, + "teamUnearned": false, + "playIndex": 5 + }, + "credits": [ + { + "player": { + "id": 665161, + "link": "/api/v1/people/665161" + }, + "position": { + "code": "6", + "name": "Shortstop", + "type": "Infielder", + "abbreviation": "SS" + }, + "credit": "f_assist" + }, + { + "player": { + "id": 641820, + "link": "/api/v1/people/641820" + }, + "position": { + "code": "3", + "name": "First Base", + "type": "Infielder", + "abbreviation": "1B" + }, + "credit": "f_putout" + } + ] + } + ], + "playEvents": [ + { + "details": { + "call": { + "code": "S", + "description": "Swinging Strike" + }, + "description": "Swinging Strike", + "code": "S", + "ballColor": "rgba(170, 21, 11, 1.0)", + "trailColor": "rgba(0, 0, 254, 1.0)", + "isInPlay": false, + "isStrike": true, + "isBall": false, + "type": { + "code": "SL", + "description": "Slider" + }, + "hasReview": false + }, + "count": { + "balls": 0, + "strikes": 1, + "outs": 2 + }, + "pitchData": { + "startSpeed": 88.1, + "endSpeed": 81.8, + "strikeZoneTop": 3.67, + "strikeZoneBottom": 1.76, + "coordinates": { + "aY": 24.26, + "aZ": -26.98, + "pfxX": 2.26, + "pfxZ": 3.09, + "pX": 0.78, + "pZ": 0.96, + "vX0": 1.78, + "vY0": -128.22, + "vZ0": -6.87, + "x": 87.29, + "y": 212.97, + "x0": -0.21, + "y0": 50.01, + "z0": 5.75, + "aX": 3.8 + }, + "breaks": { + "breakAngle": 8.4, + "breakLength": 7.2, + "breakY": 24, + "spinRate": 2637, + "spinDirection": 150 + }, + "zone": 14, + "typeConfidence": 0.9, + "plateTime": 0.43, + "extension": 6.33 + }, + "index": 0, + "playId": "2df1b100-bc69-4863-96bf-40b78ab7c051", + "pitchNumber": 1, + "startTime": "2022-11-04T04:00:01.915Z", + "endTime": "2022-11-04T04:00:04.915Z", + "isPitch": true, + "type": "pitch" + }, + { + "details": { + "call": { + "code": "F", + "description": "Foul" + }, + "description": "Foul", + "code": "F", + "ballColor": "rgba(170, 21, 11, 1.0)", + "trailColor": "rgba(0, 0, 254, 1.0)", + "isInPlay": false, + "isStrike": true, + "isBall": false, + "type": { + "code": "SL", + "description": "Slider" + }, + "hasReview": false + }, + "count": { + "balls": 0, + "strikes": 2, + "outs": 2 + }, + "pitchData": { + "startSpeed": 88.7, + "endSpeed": 82.1, + "strikeZoneTop": 3.67, + "strikeZoneBottom": 1.76, + "coordinates": { + "aY": 24.99, + "aZ": -29.08, + "pfxX": 4.28, + "pfxZ": 1.81, + "pX": 0.2, + "pZ": 2.66, + "vX0": -0.05, + "vY0": -129.36, + "vZ0": -2.62, + "x": 109.23, + "y": 166.93, + "x0": -0.33, + "y0": 50, + "z0": 5.9, + "aX": 7.32 + }, + "breaks": { + "breakAngle": 14.4, + "breakLength": 7.2, + "breakY": 24, + "spinRate": 2686, + "spinDirection": 135 + }, + "zone": 5, + "typeConfidence": 0.91, + "plateTime": 0.42, + "extension": 6.25 + }, + "index": 1, + "playId": "a639e5b6-621e-4efc-b4dc-2cbf02c7e907", + "pitchNumber": 2, + "startTime": "2022-11-04T04:00:31.181Z", + "endTime": "2022-11-04T04:00:39.186Z", + "isPitch": true, + "type": "pitch" + }, + { + "details": { + "call": { + "code": "B", + "description": "Ball" + }, + "description": "Ball", + "code": "B", + "ballColor": "rgba(39, 161, 39, 1.0)", + "trailColor": "rgba(0, 0, 254, 1.0)", + "isInPlay": false, + "isStrike": false, + "isBall": true, + "type": { + "code": "SL", + "description": "Slider" + }, + "hasReview": false + }, + "count": { + "balls": 1, + "strikes": 2, + "outs": 2 + }, + "pitchData": { + "startSpeed": 89.6, + "endSpeed": 82.9, + "strikeZoneTop": 3.55, + "strikeZoneBottom": 1.79, + "coordinates": { + "aY": 26.05, + "aZ": -27.47, + "pfxX": 2.32, + "pfxZ": 2.72, + "pX": 1.33, + "pZ": 1.22, + "vX0": 3.24, + "vY0": -130.44, + "vZ0": -6.35, + "x": 66.19, + "y": 205.92, + "x0": -0.23, + "y0": 50, + "z0": 5.74, + "aX": 4.02 + }, + "breaks": { + "breakAngle": 9.6, + "breakLength": 7.2, + "breakY": 24, + "spinRate": 2728, + "spinDirection": 128 + }, + "zone": 14, + "typeConfidence": 0.9, + "plateTime": 0.42, + "extension": 6.38 + }, + "index": 2, + "playId": "6c3a3fba-5070-441c-8746-a0b37f77d39d", + "pitchNumber": 3, + "startTime": "2022-11-04T04:01:22.328Z", + "endTime": "2022-11-04T04:01:26.880Z", + "isPitch": true, + "type": "pitch" + }, + { + "details": { + "call": { + "code": "*B", + "description": "Ball In Dirt" + }, + "description": "Ball In Dirt", + "code": "*B", + "ballColor": "rgba(39, 161, 39, 1.0)", + "trailColor": "rgba(0, 34, 255, 1.0)", + "isInPlay": false, + "isStrike": false, + "isBall": true, + "type": { + "code": "CU", + "description": "Curveball" + }, + "hasReview": false + }, + "count": { + "balls": 2, + "strikes": 2, + "outs": 2 + }, + "pitchData": { + "startSpeed": 82.3, + "endSpeed": 75.7, + "strikeZoneTop": 3.5, + "strikeZoneBottom": 1.71, + "coordinates": { + "aY": 26.01, + "aZ": -39.49, + "pfxX": 7.54, + "pfxZ": -5.1, + "pX": 1.41, + "pZ": 0.24, + "vX0": 1.51, + "vY0": -119.82, + "vZ0": -4.64, + "x": 63.25, + "y": 232.21, + "x0": -0.21, + "y0": 50, + "z0": 5.78, + "aX": 10.82 + }, + "breaks": { + "breakAngle": 15.6, + "breakLength": 12, + "breakY": 24, + "spinRate": 3064, + "spinDirection": 26 + }, + "zone": 14, + "typeConfidence": 0.88, + "plateTime": 0.46, + "extension": 6.3 + }, + "index": 3, + "playId": "ea104c1d-c5c5-4f60-920c-38e73c35ac15", + "pitchNumber": 4, + "startTime": "2022-11-04T04:01:49.179Z", + "endTime": "2022-11-04T04:01:52.708Z", + "isPitch": true, + "type": "pitch" + }, + { + "details": { + "call": { + "code": "*B", + "description": "Ball In Dirt" + }, + "description": "Ball In Dirt", + "code": "*B", + "ballColor": "rgba(39, 161, 39, 1.0)", + "trailColor": "rgba(0, 85, 254, 1.0)", + "isInPlay": false, + "isStrike": false, + "isBall": true, + "type": { + "code": "CH", + "description": "Changeup" + }, + "hasReview": false + }, + "count": { + "balls": 3, + "strikes": 2, + "outs": 2 + }, + "pitchData": { + "startSpeed": 89.5, + "endSpeed": 81.3, + "strikeZoneTop": 3.55, + "strikeZoneBottom": 1.74, + "coordinates": { + "aY": 31.14, + "aZ": -26.19, + "pfxX": -10.68, + "pfxZ": 3.55, + "pX": 0.43, + "pZ": -0.15, + "vX0": 5.62, + "vY0": -129.91, + "vZ0": -9.08, + "x": 100.46, + "y": 242.71, + "x0": -0.39, + "y0": 50, + "z0": 5.43, + "aX": -17.98 + }, + "breaks": { + "breakAngle": 31.2, + "breakLength": 8.4, + "breakY": 24, + "spinRate": 1942, + "spinDirection": 223 + }, + "zone": 14, + "typeConfidence": 0.93, + "plateTime": 0.42, + "extension": 6.55 + }, + "index": 4, + "playId": "8a911aa3-4381-4f4b-83ec-23f14a661444", + "pitchNumber": 5, + "startTime": "2022-11-04T04:02:24.112Z", + "endTime": "2022-11-04T04:02:27.562Z", + "isPitch": true, + "type": "pitch" + }, + { + "details": { + "call": { + "code": "X", + "description": "In play, out(s)" + }, + "description": "In play, out(s)", + "code": "X", + "ballColor": "rgba(26, 86, 190, 1.0)", + "trailColor": "rgba(0, 0, 254, 1.0)", + "isInPlay": true, + "isStrike": false, + "isBall": false, + "type": { + "code": "SL", + "description": "Slider" + }, + "hasReview": false, + "runnerGoing": true + }, + "count": { + "balls": 3, + "strikes": 2, + "outs": 2 + }, + "pitchData": { + "startSpeed": 88.3, + "endSpeed": 82, + "strikeZoneTop": 3.67, + "strikeZoneBottom": 1.76, + "coordinates": { + "aY": 23.48, + "aZ": -30.77, + "pfxX": 3.99, + "pfxZ": 0.82, + "pX": 0.1, + "pZ": 3.31, + "vX0": 0.05, + "vY0": -128.81, + "vZ0": -0.7, + "x": 113.21, + "y": 149.43, + "x0": -0.44, + "y0": 50, + "z0": 5.94, + "aX": 6.79 + }, + "breaks": { + "breakAngle": 12, + "breakLength": 7.2, + "breakY": 24, + "spinRate": 2683, + "spinDirection": 134 + }, + "zone": 2, + "typeConfidence": 0.91, + "plateTime": 0.42, + "extension": 6.3 + }, + "hitData": { + "launchSpeed": 90.5, + "launchAngle": -17, + "totalDistance": 12, + "trajectory": "ground_ball", + "hardness": "medium", + "location": "6", + "coordinates": { + "coordX": 109.94, + "coordY": 149.05 + } + }, + "index": 5, + "playId": "6e9a9c6d-3163-4575-893d-61df462e9b88", + "pitchNumber": 6, + "startTime": "2022-11-04T04:03:07.728Z", + "endTime": "2022-11-04T04:03:16.716Z", + "isPitch": true, + "type": "pitch" + } + ], + "playEndTime": "2022-11-04T04:03:16.716Z", + "atBatIndex": 77 + }, + "scoringPlays": [ + 1, + 4, + 26, + 60, + 67 + ], + "playsByInning": [ + { + "startIndex": 0, + "endIndex": 8, + "top": [ + 0, + 1, + 2, + 3 + ], + "bottom": [ + 4, + 5, + 6, + 7, + 8 + ], + "hits": { + "away": [ + { + "team": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "inning": 1, + "pitcher": { + "id": 592789, + "fullName": "Noah Syndergaard", + "link": "/api/v1/people/592789" + }, + "batter": { + "id": 514888, + "fullName": "Jose Altuve", + "link": "/api/v1/people/514888" + }, + "coordinates": { + "x": 162.78, + "y": 41.75 + }, + "type": "H", + "description": "Double" + }, + { + "team": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "inning": 1, + "pitcher": { + "id": 592789, + "fullName": "Noah Syndergaard", + "link": "/api/v1/people/592789" + }, + "batter": { + "id": 665161, + "fullName": "Jeremy Pena", + "link": "/api/v1/people/665161" + }, + "coordinates": { + "x": 119.07, + "y": 91.76 + }, + "type": "H", + "description": "Single" + } + ], + "home": [ + { + "team": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 143, + "name": "Philadelphia Phillies", + "link": "/api/v1/teams/143" + }, + "inning": 1, + "pitcher": { + "id": 434378, + "fullName": "Justin Verlander", + "link": "/api/v1/people/434378" + }, + "batter": { + "id": 656941, + "fullName": "Kyle Schwarber", + "link": "/api/v1/people/656941" + }, + "coordinates": { + "x": 221.12, + "y": 83.65 + }, + "type": "H", + "description": "Home Run" + }, + { + "team": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 143, + "name": "Philadelphia Phillies", + "link": "/api/v1/teams/143" + }, + "inning": 1, + "pitcher": { + "id": 434378, + "fullName": "Justin Verlander", + "link": "/api/v1/people/434378" + }, + "batter": { + "id": 656555, + "fullName": "Rhys Hoskins", + "link": "/api/v1/people/656555" + }, + "coordinates": { + "x": 82.58, + "y": 90.81 + }, + "type": "O", + "description": "Lineout" + }, + { + "team": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 143, + "name": "Philadelphia Phillies", + "link": "/api/v1/teams/143" + }, + "inning": 1, + "pitcher": { + "id": 434378, + "fullName": "Justin Verlander", + "link": "/api/v1/people/434378" + }, + "batter": { + "id": 592663, + "fullName": "J.T. Realmuto", + "link": "/api/v1/people/592663" + }, + "coordinates": { + "x": 154.14, + "y": 133.8 + }, + "type": "O", + "description": "Pop Out" + }, + { + "team": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 143, + "name": "Philadelphia Phillies", + "link": "/api/v1/teams/143" + }, + "inning": 1, + "pitcher": { + "id": 434378, + "fullName": "Justin Verlander", + "link": "/api/v1/people/434378" + }, + "batter": { + "id": 592206, + "fullName": "Nick Castellanos", + "link": "/api/v1/people/592206" + }, + "coordinates": { + "x": 100.82, + "y": 105.95 + }, + "type": "O", + "description": "Flyout" + } + ] + } + } + ] + }, + "linescore": { + "currentInning": 9, + "currentInningOrdinal": "9th", + "inningState": "Bottom", + "inningHalf": "Bottom", + "isTopInning": false, + "scheduledInnings": 9, + "innings": [ + { + "num": 1, + "ordinalNum": "1st", + "home": { + "runs": 1, + "hits": 1, + "errors": 1, + "leftOnBase": 1 + }, + "away": { + "runs": 1, + "hits": 2, + "errors": 0, + "leftOnBase": 0 + } + }, + { + "num": 2, + "ordinalNum": "2nd", + "home": { + "runs": 0, + "hits": 1, + "errors": 0, + "leftOnBase": 3 + }, + "away": { + "runs": 0, + "hits": 0, + "errors": 0, + "leftOnBase": 0 + } + }, + { + "num": 3, + "ordinalNum": "3rd", + "home": { + "runs": 0, + "hits": 1, + "errors": 0, + "leftOnBase": 2 + }, + "away": { + "runs": 0, + "hits": 0, + "errors": 0, + "leftOnBase": 0 + } + }, + { + "num": 4, + "ordinalNum": "4th", + "home": { + "runs": 0, + "hits": 0, + "errors": 0, + "leftOnBase": 0 + }, + "away": { + "runs": 1, + "hits": 2, + "errors": 0, + "leftOnBase": 1 + } + }, + { + "num": 5, + "ordinalNum": "5th", + "home": { + "runs": 0, + "hits": 1, + "errors": 0, + "leftOnBase": 1 + }, + "away": { + "runs": 0, + "hits": 0, + "errors": 0, + "leftOnBase": 0 + } + }, + { + "num": 6, + "ordinalNum": "6th", + "home": { + "runs": 0, + "hits": 1, + "errors": 0, + "leftOnBase": 2 + }, + "away": { + "runs": 0, + "hits": 1, + "errors": 0, + "leftOnBase": 2 + } + }, + { + "num": 7, + "ordinalNum": "7th", + "home": { + "runs": 0, + "hits": 0, + "errors": 0, + "leftOnBase": 0 + }, + "away": { + "runs": 0, + "hits": 1, + "errors": 0, + "leftOnBase": 1 + } + }, + { + "num": 8, + "ordinalNum": "8th", + "home": { + "runs": 1, + "hits": 1, + "errors": 0, + "leftOnBase": 2 + }, + "away": { + "runs": 1, + "hits": 1, + "errors": 0, + "leftOnBase": 2 + } + }, + { + "num": 9, + "ordinalNum": "9th", + "home": { + "runs": 0, + "hits": 0, + "errors": 0, + "leftOnBase": 1 + }, + "away": { + "runs": 0, + "hits": 2, + "errors": 0, + "leftOnBase": 1 + } + } + ], + "teams": { + "home": { + "runs": 2, + "hits": 6, + "errors": 1, + "leftOnBase": 12 + }, + "away": { + "runs": 3, + "hits": 9, + "errors": 0, + "leftOnBase": 7 + } + }, + "defense": { + "pitcher": { + "id": 519151, + "fullName": "Ryan Pressly", + "link": "/api/v1/people/519151" + }, + "catcher": { + "id": 455117, + "fullName": "Martin Maldonado", + "link": "/api/v1/people/455117" + }, + "first": { + "id": 641820, + "fullName": "Trey Mancini", + "link": "/api/v1/people/641820" + }, + "second": { + "id": 514888, + "fullName": "Jose Altuve", + "link": "/api/v1/people/514888" + }, + "third": { + "id": 608324, + "fullName": "Alex Bregman", + "link": "/api/v1/people/608324" + }, + "shortstop": { + "id": 665161, + "fullName": "Jeremy Pena", + "link": "/api/v1/people/665161" + }, + "left": { + "id": 670541, + "fullName": "Yordan Alvarez", + "link": "/api/v1/people/670541" + }, + "center": { + "id": 676801, + "fullName": "Chas McCormick", + "link": "/api/v1/people/676801" + }, + "right": { + "id": 663656, + "fullName": "Kyle Tucker", + "link": "/api/v1/people/663656" + }, + "batter": { + "id": 665161, + "fullName": "Jeremy Pena", + "link": "/api/v1/people/665161" + }, + "onDeck": { + "id": 670541, + "fullName": "Yordan Alvarez", + "link": "/api/v1/people/670541" + }, + "inHole": { + "id": 608324, + "fullName": "Alex Bregman", + "link": "/api/v1/people/608324" + }, + "battingOrder": 2, + "team": { + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + } + }, + "offense": { + "batter": { + "id": 592206, + "fullName": "Nick Castellanos", + "link": "/api/v1/people/592206" + }, + "onDeck": { + "id": 664761, + "fullName": "Alec Bohm", + "link": "/api/v1/people/664761" + }, + "inHole": { + "id": 681082, + "fullName": "Bryson Stott", + "link": "/api/v1/people/681082" + }, + "pitcher": { + "id": 621107, + "fullName": "Zach Eflin", + "link": "/api/v1/people/621107" + }, + "battingOrder": 5, + "team": { + "id": 143, + "name": "Philadelphia Phillies", + "link": "/api/v1/teams/143" + } + }, + "balls": 3, + "strikes": 2, + "outs": 3 + }, + + "boxscore": { + "teams": { + "away": { + "team": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "teamStats": { + "batting": { + "flyOuts": 3, + "groundOuts": 8, + "runs": 3, + "doubles": 3, + "triples": 0, + "homeRuns": 1, + "strikeOuts": 12, + "baseOnBalls": 2, + "intentionalWalks": 0, + "hits": 9, + "hitByPitch": 1, + "avg": ".240", + "atBats": 34, + "obp": ".303", + "slg": ".368", + "ops": ".671", + "caughtStealing": 1, + "stolenBases": 1, + "stolenBasePercentage": ".500", + "groundIntoDoublePlay": 1, + "groundIntoTriplePlay": 0, + "plateAppearances": 37, + "totalBases": 15, + "rbi": 3, + "leftOnBase": 18, + "sacBunts": 0, + "sacFlies": 0, + "catchersInterference": 0, + "pickoffs": 0, + "atBatsPerHomeRun": "34.00" + }, + "pitching": { + "groundOuts": 0, + "airOuts": 0, + "runs": 2, + "doubles": 0, + "triples": 0, + "homeRuns": 1, + "strikeOuts": 12, + "baseOnBalls": 6, + "intentionalWalks": 0, + "hits": 6, + "hitByPitch": 2, + "atBats": 33, + "obp": ".341", + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "numberOfPitches": 165, + "era": "3.20", + "inningsPitched": "9.0", + "saveOpportunities": 0, + "earnedRuns": 2, + "whip": "1.07", + "battersFaced": 41, + "outs": 27, + "completeGames": 0, + "shutouts": 0, + "pitchesThrown": 165, + "balls": 63, + "strikes": 102, + "strikePercentage": ".620", + "hitBatsmen": 2, + "balks": 0, + "wildPitches": 0, + "pickoffs": 0, + "groundOutsToAirouts": "-.--", + "rbi": 2, + "pitchesPerInning": "18.33", + "runsScoredPer9": "2.00", + "homeRunsPer9": "1.00", + "inheritedRunners": 0, + "inheritedRunnersScored": 0, + "catchersInterference": 0, + "sacBunts": 0, + "sacFlies": 0, + "passedBall": 0 + }, + "fielding": { + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "assists": 3, + "putOuts": 27, + "errors": 0, + "chances": 30, + "passedBall": 0, + "pickoffs": 0 + } + }, + "players": { + "ID664285": { + "person": { + "id": 664285, + "fullName": "Framber Valdez", + "link": "/api/v1/people/664285" + }, + "jerseyNumber": "59", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 117, + "stats": { + "batting": {}, + "pitching": {}, + "fielding": {} + }, + "seasonStats": { + "batting": { + "gamesPlayed": 0, + "flyOuts": 0, + "groundOuts": 0, + "runs": 0, + "doubles": 0, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 0, + "baseOnBalls": 0, + "intentionalWalks": 0, + "hits": 0, + "hitByPitch": 0, + "avg": ".000", + "atBats": 0, + "obp": ".000", + "slg": ".000", + "ops": ".000", + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "groundIntoDoublePlay": 0, + "groundIntoTriplePlay": 0, + "plateAppearances": 0, + "totalBases": 0, + "rbi": 0, + "leftOnBase": 0, + "sacBunts": 0, + "sacFlies": 0, + "babip": ".---", + "catchersInterference": 0, + "pickoffs": 0, + "atBatsPerHomeRun": "-.--" + }, + "pitching": { + "gamesPlayed": 3, + "gamesStarted": 3, + "groundOuts": 24, + "airOuts": 8, + "runs": 5, + "doubles": 5, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 24, + "baseOnBalls": 6, + "intentionalWalks": 0, + "hits": 12, + "hitByPitch": 0, + "atBats": 68, + "obp": ".243", + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "numberOfPitches": 297, + "era": "1.42", + "inningsPitched": "19.0", + "wins": 2, + "losses": 0, + "saves": 0, + "saveOpportunities": 0, + "holds": 0, + "blownSaves": 0, + "earnedRuns": 3, + "whip": "0.95", + "battersFaced": 74, + "outs": 57, + "gamesPitched": 3, + "completeGames": 0, + "shutouts": 0, + "pitchesThrown": 297, + "balls": 99, + "strikes": 198, + "strikePercentage": ".670", + "hitBatsmen": 0, + "balks": 0, + "wildPitches": 0, + "pickoffs": 0, + "groundOutsToAirouts": "3.00", + "rbi": 0, + "winPercentage": "1.000", + "pitchesPerInning": "15.63", + "gamesFinished": 0, + "strikeoutWalkRatio": "4.00", + "strikeoutsPer9Inn": "11.37", + "walksPer9Inn": "2.84", + "hitsPer9Inn": "5.68", + "runsScoredPer9": "2.37", + "homeRunsPer9": "0.00", + "inheritedRunners": 0, + "inheritedRunnersScored": 0, + "catchersInterference": 0, + "sacBunts": 0, + "sacFlies": 0, + "passedBall": 0 + }, + "fielding": { + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "assists": 1, + "putOuts": 0, + "errors": 2, + "chances": 3, + "fielding": ".333", + "passedBall": 0, + "pickoffs": 0 + } + }, + "gameStatus": { + "isCurrentBatter": false, + "isCurrentPitcher": false, + "isOnBench": true, + "isSubstitute": false + } + } + }, + "batters": [ + 514888, + 665161, + 670541, + 608324, + 663656, + 493329, + 641820, + 682073, + 676801, + 455117, + 434378, + 593576, + 650556, + 606160, + 519151 + ], + "pitchers": [ + 434378, + 593576, + 650556, + 606160, + 519151 + ], + "bench": [ + 649557, + 643289, + 543877 + ], + "bullpen": [ + 686613, + 677651, + 664299, + 621121, + 519293, + 592773, + 664353, + 664285 + ], + "battingOrder": [ + 514888, + 665161, + 670541, + 608324, + 663656, + 641820, + 682073, + 676801, + 455117 + ], + "info": [ + { + "title": "BATTING", + "fieldList": [ + { + "label": "2B", + "value": "Altuve (3, Syndergaard); Bregman (5, Brogdon); Gurriel (1, Domínguez)." + }, + { + "label": "HR", + "value": "Peña (4, 4th inning off Syndergaard, 0 on, 0 out)." + }, + { + "label": "TB", + "value": "Altuve 3; Bregman 2; Gurriel 2; Hensley; Maldonado; Peña 6." + }, + { + "label": "RBI", + "value": "Alvarez, Y (11); Peña 2 (8)." + }, + { + "label": "Runners left in scoring position, 2 out", + "value": "Tucker; Mancini 2; Gurriel; Maldonado." + }, + { + "label": "GIDP", + "value": "Altuve." + }, + { + "label": "Team RISP", + "value": "1-for-11." + }, + { + "label": "Team LOB", + "value": "7." + } + ] + }, + { + "title": "BASERUNNING", + "fieldList": [ + { + "label": "SB", + "value": "Bregman (1, 2nd base off Robertson/Realmuto)." + }, + { + "label": "CS", + "value": "Peña (1, 2nd base by Syndergaard/Realmuto)." + } + ] + } + ], + "note": [ + { + "label": "a", + "value": "Struck out for Gurriel in the 8th." + } + ] + }, + "home": { + "team": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 143, + "name": "Philadelphia Phillies", + "link": "/api/v1/teams/143" + }, + "teamStats": { + "batting": { + "flyOuts": 5, + "groundOuts": 4, + "runs": 2, + "doubles": 1, + "triples": 0, + "homeRuns": 1, + "strikeOuts": 12, + "baseOnBalls": 6, + "intentionalWalks": 0, + "hits": 6, + "hitByPitch": 2, + "avg": ".174", + "atBats": 33, + "obp": ".272", + "slg": ".342", + "ops": ".614", + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "groundIntoDoublePlay": 0, + "groundIntoTriplePlay": 0, + "plateAppearances": 41, + "totalBases": 10, + "rbi": 2, + "leftOnBase": 18, + "sacBunts": 0, + "sacFlies": 0, + "catchersInterference": 0, + "pickoffs": 0, + "atBatsPerHomeRun": "33.00" + }, + "pitching": { + "groundOuts": 0, + "airOuts": 0, + "runs": 3, + "doubles": 0, + "triples": 0, + "homeRuns": 1, + "strikeOuts": 12, + "baseOnBalls": 2, + "intentionalWalks": 0, + "hits": 9, + "hitByPitch": 1, + "atBats": 34, + "obp": ".324", + "caughtStealing": 1, + "stolenBases": 1, + "stolenBasePercentage": ".500", + "numberOfPitches": 149, + "era": "3.40", + "inningsPitched": "9.0", + "saveOpportunities": 0, + "earnedRuns": 3, + "whip": "1.20", + "battersFaced": 37, + "outs": 27, + "completeGames": 0, + "shutouts": 0, + "pitchesThrown": 149, + "balls": 49, + "strikes": 100, + "strikePercentage": ".670", + "hitBatsmen": 1, + "balks": 0, + "wildPitches": 1, + "pickoffs": 0, + "groundOutsToAirouts": "-.--", + "rbi": 3, + "pitchesPerInning": "16.56", + "runsScoredPer9": "3.00", + "homeRunsPer9": "1.00", + "inheritedRunners": 0, + "inheritedRunnersScored": 0, + "catchersInterference": 0, + "sacBunts": 0, + "sacFlies": 0, + "passedBall": 0 + }, + "fielding": { + "caughtStealing": 1, + "stolenBases": 1, + "stolenBasePercentage": ".500", + "assists": 9, + "putOuts": 27, + "errors": 1, + "chances": 37, + "passedBall": 0, + "pickoffs": 0 + } + }, + "players": { + "ID516416": { + "person": { + "id": 516416, + "fullName": "Jean Segura", + "link": "/api/v1/people/516416" + }, + "jerseyNumber": "2", + "position": { + "code": "4", + "name": "Second Base", + "type": "Infielder", + "abbreviation": "2B" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 143, + "battingOrder": "800", + "stats": { + "batting": { + "gamesPlayed": 1, + "flyOuts": 0, + "groundOuts": 1, + "runs": 0, + "doubles": 0, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 1, + "baseOnBalls": 0, + "intentionalWalks": 0, + "hits": 2, + "hitByPitch": 0, + "atBats": 4, + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "groundIntoDoublePlay": 0, + "groundIntoTriplePlay": 0, + "plateAppearances": 4, + "totalBases": 2, + "rbi": 1, + "leftOnBase": 1, + "sacBunts": 0, + "sacFlies": 0, + "catchersInterference": 0, + "pickoffs": 0, + "atBatsPerHomeRun": "-.--" + }, + "pitching": {}, + "fielding": { + "gamesStarted": 1, + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "assists": 0, + "putOuts": 0, + "errors": 0, + "chances": 0, + "fielding": ".000", + "passedBall": 0, + "pickoffs": 0 + } + }, + "seasonStats": { + "batting": { + "gamesPlayed": 16, + "flyOuts": 0, + "groundOuts": 14, + "runs": 4, + "doubles": 2, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 12, + "baseOnBalls": 2, + "intentionalWalks": 0, + "hits": 12, + "hitByPitch": 1, + "avg": ".226", + "atBats": 53, + "obp": ".263", + "slg": ".264", + "ops": ".527", + "caughtStealing": 0, + "stolenBases": 1, + "stolenBasePercentage": "1.000", + "groundIntoDoublePlay": 0, + "groundIntoTriplePlay": 0, + "plateAppearances": 58, + "totalBases": 14, + "rbi": 7, + "leftOnBase": 21, + "sacBunts": 1, + "sacFlies": 1, + "babip": ".286", + "catchersInterference": 0, + "pickoffs": 0, + "atBatsPerHomeRun": "-.--" + }, + "pitching": { + "gamesPlayed": 0, + "gamesStarted": 0, + "groundOuts": 0, + "airOuts": 0, + "runs": 0, + "doubles": 0, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 0, + "baseOnBalls": 0, + "intentionalWalks": 0, + "hits": 0, + "hitByPitch": 0, + "atBats": 0, + "obp": ".000", + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "numberOfPitches": 0, + "era": "-.--", + "inningsPitched": "0.0", + "wins": 0, + "losses": 0, + "saves": 0, + "saveOpportunities": 0, + "holds": 0, + "blownSaves": 0, + "earnedRuns": 0, + "whip": "-", + "battersFaced": 0, + "outs": 0, + "gamesPitched": 0, + "completeGames": 0, + "shutouts": 0, + "balls": 0, + "strikes": 0, + "strikePercentage": "-.--", + "hitBatsmen": 0, + "balks": 0, + "wildPitches": 0, + "pickoffs": 0, + "groundOutsToAirouts": "-.--", + "rbi": 0, + "winPercentage": ".---", + "pitchesPerInning": "-.--", + "gamesFinished": 0, + "strikeoutWalkRatio": "-.--", + "strikeoutsPer9Inn": "-.--", + "walksPer9Inn": "-.--", + "hitsPer9Inn": "-.--", + "runsScoredPer9": "-.--", + "homeRunsPer9": "-.--", + "inheritedRunners": 0, + "inheritedRunnersScored": 0, + "catchersInterference": 0, + "sacBunts": 0, + "sacFlies": 0, + "passedBall": 0 + }, + "fielding": { + "gamesStarted": 1, + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "assists": 36, + "putOuts": 15, + "errors": 1, + "chances": 52, + "fielding": ".981", + "passedBall": 0, + "pickoffs": 0 + } + }, + "gameStatus": { + "isCurrentBatter": false, + "isCurrentPitcher": false, + "isOnBench": false, + "isSubstitute": false + }, + "allPositions": [ + { + "code": "4", + "name": "Second Base", + "type": "Infielder", + "abbreviation": "2B" + } + ] + } + }, + "batters": [ + 656941, + 656555, + 592663, + 547180, + 592206, + 664761, + 681082, + 516416, + 669016, + 592789, + 641401, + 621237, + 622554, + 502085, + 621107 + ], + "pitchers": [ + 592789, + 641401, + 621237, + 622554, + 502085, + 621107 + ], + "bench": [ + 665155, + 624641, + 596117, + 663837 + ], + "bullpen": [ + 571479, + 502043, + 543272, + 656793, + 605400, + 624133, + 554430 + ], + "battingOrder": [ + 656941, + 656555, + 592663, + 547180, + 592206, + 664761, + 681082, + 516416, + 669016 + ], + "info": [ + { + "title": "BATTING", + "fieldList": [ + { + "label": "2B", + "value": "Harper (7, Verlander)." + }, + { + "label": "HR", + "value": "Schwarber (5, 1st inning off Verlander, 0 on, 0 out)." + }, + { + "label": "TB", + "value": "Bohm 2; Harper 2; Schwarber 4; Segura 2." + }, + { + "label": "RBI", + "value": "Schwarber (9); Segura (7)." + }, + { + "label": "Runners left in scoring position, 2 out", + "value": "Stott; Hoskins 2; Castellanos, N; Schwarber 2." + }, + { + "label": "Team RISP", + "value": "1-for-7." + }, + { + "label": "Team LOB", + "value": "12." + } + ] + }, + { + "title": "FIELDING", + "fieldList": [ + { + "label": "E", + "value": "Marsh (1, fielding)." + }, + { + "label": "DP", + "value": "2 (Realmuto-Stott; Bohm-Hoskins)." + } + ] + } + ], + "note": [] + } + }, + "officials": [ + { + "official": { + "id": 490319, + "fullName": "Jordan Baker", + "link": "/api/v1/people/490319" + }, + "officialType": "Home Plate" + }, + { + "official": { + "id": 484198, + "fullName": "Alan Porter", + "link": "/api/v1/people/484198" + }, + "officialType": "First Base" + }, + { + "official": { + "id": 428442, + "fullName": "James Hoye", + "link": "/api/v1/people/428442" + }, + "officialType": "Second Base" + }, + { + "official": { + "id": 573596, + "fullName": "Pat Hoberg", + "link": "/api/v1/people/573596" + }, + "officialType": "Third Base" + }, + { + "official": { + "id": 427248, + "fullName": "Dan Iassogna", + "link": "/api/v1/people/427248" + }, + "officialType": "Left Field" + }, + { + "official": { + "id": 503586, + "fullName": "Tripp Gibson", + "link": "/api/v1/people/503586" + }, + "officialType": "Right Field" + } + ], + "info": [ + { + "label": "WP", + "value": "Domínguez." + }, + { + "label": "HBP", + "value": "Bregman (by Alvarado); Marsh (by Abreu, B); Harper (by Pressly)." + }, + { + "label": "Pitches-strikes", + "value": "Verlander 94-58; Neris 11-7; Abreu, B 17-12; Montero 17-8; Pressly 26-17; Syndergaard 44-31; Brogdon 26-18; Alvarado 20-14; Domínguez 26-16; Robertson 20-11; Eflin 13-10." + }, + { + "label": "Groundouts-flyouts", + "value": "Verlander 1-3; Neris 0-1; Abreu, B 1-0; Montero 0-0; Pressly 2-1; Syndergaard 1-2; Brogdon 1-0; Alvarado 1-0; Domínguez 3-0; Robertson 1-1; Eflin 1-0." + }, + { + "label": "Batters faced", + "value": "Verlander 23; Neris 3; Abreu, B 5; Montero 4; Pressly 6; Syndergaard 11; Brogdon 7; Alvarado 5; Domínguez 6; Robertson 4; Eflin 4." + }, + { + "label": "Inherited runners-scored", + "value": "Abreu, B 1-0; Pressly 2-0; Robertson 2-1." + }, + { + "label": "Umpires", + "value": "HP: Jordan Baker. 1B: Alan Porter. 2B: James Hoye. 3B: Pat Hoberg. LF: Dan Iassogna. RF: Tripp Gibson. " + }, + { + "label": "Weather", + "value": "59 degrees, Clear." + }, + { + "label": "Wind", + "value": "2 mph, Out To CF." + }, + { + "label": "First pitch", + "value": "8:06 PM." + }, + { + "label": "T", + "value": "3:57." + }, + { + "label": "Att", + "value": "45,693." + }, + { + "label": "Venue", + "value": "Citizens Bank Park." + }, + { + "label": "November 3, 2022" + } + ], + "pitchingNotes": [ + "Syndergaard pitched to 1 batter in the 4th.", + "Domínguez pitched to 2 batters in the 8th." + ] + }, + "decisions": { + "winner": { + "id": 434378, + "fullName": "Justin Verlander", + "link": "/api/v1/people/434378" + }, + "loser": { + "id": 592789, + "fullName": "Noah Syndergaard", + "link": "/api/v1/people/592789" + }, + "save": { + "id": 519151, + "fullName": "Ryan Pressly", + "link": "/api/v1/people/519151" + } + }, + "leaders": { + "hitDistance": {}, + "hitSpeed": {}, + "pitchSpeed": {} + } + } +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/homerunderby/homerunderby.json b/tests/mock_tests/mock_json/homerunderby/homerunderby.json new file mode 100644 index 00000000..c58c63b2 --- /dev/null +++ b/tests/mock_tests/mock_json/homerunderby/homerunderby.json @@ -0,0 +1,6844 @@ +{ + "info": { + "id": 511101, + "name": "All-Star Workout Day: Home Run Derby", + "eventType": { + "code": "O", + "name": "Other" + }, + "eventDate": "2017-07-11T00:00:00Z", + "venue": { + "id": 4169, + "name": "Marlins Park", + "link": "/api/v1/venues/4169" + }, + "isMultiDay": false, + "isPrimaryCalendar": true, + "fileCode": "2017/07/10/mlb-112", + "eventNumber": 103, + "publicFacing": true, + "teams": [ + { + "id": 108, + "name": "Los Angeles Angels", + "link": "/api/v1/teams/108" + }, + { + "id": 109, + "name": "Arizona Diamondbacks", + "link": "/api/v1/teams/109" + }, + { + "id": 110, + "name": "Baltimore Orioles", + "link": "/api/v1/teams/110" + }, + { + "id": 111, + "name": "Boston Red Sox", + "link": "/api/v1/teams/111" + }, + { + "id": 112, + "name": "Chicago Cubs", + "link": "/api/v1/teams/112" + }, + { + "id": 113, + "name": "Cincinnati Reds", + "link": "/api/v1/teams/113" + }, + { + "id": 114, + "name": "Cleveland Indians", + "link": "/api/v1/teams/114" + }, + { + "id": 115, + "name": "Colorado Rockies", + "link": "/api/v1/teams/115" + }, + { + "id": 116, + "name": "Detroit Tigers", + "link": "/api/v1/teams/116" + }, + { + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + { + "id": 118, + "name": "Kansas City Royals", + "link": "/api/v1/teams/118" + }, + { + "id": 119, + "name": "Los Angeles Dodgers", + "link": "/api/v1/teams/119" + }, + { + "id": 120, + "name": "Washington Nationals", + "link": "/api/v1/teams/120" + }, + { + "id": 121, + "name": "New York Mets", + "link": "/api/v1/teams/121" + }, + { + "id": 133, + "name": "Oakland Athletics", + "link": "/api/v1/teams/133" + }, + { + "id": 134, + "name": "Pittsburgh Pirates", + "link": "/api/v1/teams/134" + }, + { + "id": 135, + "name": "San Diego Padres", + "link": "/api/v1/teams/135" + }, + { + "id": 136, + "name": "Seattle Mariners", + "link": "/api/v1/teams/136" + }, + { + "id": 137, + "name": "San Francisco Giants", + "link": "/api/v1/teams/137" + }, + { + "id": 138, + "name": "St. Louis Cardinals", + "link": "/api/v1/teams/138" + }, + { + "id": 139, + "name": "Tampa Bay Rays", + "link": "/api/v1/teams/139" + }, + { + "id": 140, + "name": "Texas Rangers", + "link": "/api/v1/teams/140" + }, + { + "id": 141, + "name": "Toronto Blue Jays", + "link": "/api/v1/teams/141" + }, + { + "id": 142, + "name": "Minnesota Twins", + "link": "/api/v1/teams/142" + }, + { + "id": 143, + "name": "Philadelphia Phillies", + "link": "/api/v1/teams/143" + }, + { + "id": 144, + "name": "Atlanta Braves", + "link": "/api/v1/teams/144" + }, + { + "id": 145, + "name": "Chicago White Sox", + "link": "/api/v1/teams/145" + }, + { + "id": 146, + "name": "Miami Marlins", + "link": "/api/v1/teams/146" + }, + { + "id": 147, + "name": "New York Yankees", + "link": "/api/v1/teams/147" + }, + { + "id": 158, + "name": "Milwaukee Brewers", + "link": "/api/v1/teams/158" + } + ] + }, + "status": { + "state": "Final", + "currentRound": 3, + "currentRoundTimeLeft": "4:00", + "inTieBreaker": false, + "tieBreakerNum": 0, + "clockStopped": true, + "bonusTime": true + }, + "rounds": [ + { + "round": 1, + "numBatters": 8, + "matchups": [ + { + "topSeed": { + "started": true, + "complete": true, + "winner": false, + "player": { + "id": 519317, + "fullName": "Giancarlo Stanton", + "link": "/api/v1/people/519317" + }, + "topDerbyHitData": { + "launchSpeed": 121.000001, + "totalDistance": 496, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 121.000001, + "launchAngle": 6.000001, + "totalDistance": 182, + "coordinates": { + "coordX": 88.7243330720901, + "coordY": 134.31028486245282, + "landingPosX": -85.23144067224341, + "landingPosY": 160.48996663472863 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 32.133364421674685, + -66.68177828357233, + 2.070769956391603, + -13.891723119049406, + 21.300870928230722, + -18.428821964292624, + 9.64726412341314, + -2.80468269768424, + 0.3461559726713602 + ], + "trajectoryPolynomialY": [ + -80.38511483794548, + 183.60708999717554, + 1.9398590566955645, + -83.24744161880986, + 107.46699935411986, + -75.8437972027841, + 30.500597357307203, + -6.266237646386986, + 0.46574753944250435 + ], + "trajectoryPolynomialZ": [ + -16.945359890152943, + 61.2831761801478, + -59.31602043808958, + 71.38011443572952, + -103.76044739306225, + 98.04201598716374, + -55.94896536745857, + 17.555738441394, + -2.32074310023237 + ], + "validTimeInterval": [ + 0.4759656710027564, + 1.6986445040105695 + ], + "measuredTimeInterval": [ + 0.4759656710027564, + 1.6940913467558407 + ] + } + }, + "isHomeRun": false, + "playId": "0e88a3d5-4a16-4937-bf14-3ecc00dc9f9c", + "timeRemaining": "3:48", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 106.000001, + "launchAngle": 30.000001, + "totalDistance": 429, + "coordinates": { + "coordX": 110.58006036082529, + "coordY": 17.54192101425582, + "landingPosX": -35.2579518715961, + "landingPosY": 427.48279857976337 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 17.953641853805618, + -47.785715529449035, + 19.42977276671014, + -3.398978193204823, + -0.7845600359480832, + 0.5648372220325301, + -0.12491478842188641, + 0.012787884173502963, + -0.0005116782848867642 + ], + "trajectoryPolynomialY": [ + -66.31634863346272, + 160.5211813028262, + -36.11032539848496, + 9.681963294491283, + -2.516707210792061, + 0.5691870257391934, + -0.08869025651715008, + 0.00782796474781931, + -0.00029018914747536324 + ], + "trajectoryPolynomialZ": [ + -36.393471357507615, + 91.5299905886195, + -15.65007934327053, + 0.2973915221293711, + 0.08933182403782632, + -0.05388748620348726, + 0.01194164933460883, + -0.0011875818416647143, + 0.000045182991279599504 + ], + "validTimeInterval": [ + 0.4714241300024811, + 6.162768734494625 + ], + "measuredTimeInterval": [ + 0.4714241300024811, + 3.463147770437759 + ] + } + }, + "isHomeRun": true, + "playId": "0c455416-98ce-426e-bce9-d5fbfa6eeb8c", + "timeRemaining": "3:37", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 97.000001, + "launchAngle": 42.000001, + "totalDistance": 327, + "coordinates": { + "coordX": 123.6711850261219, + "coordY": 61.53667970251183, + "landingPosX": -5.324874687462839, + "landingPosY": 326.888041354369 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 11.442830519559907, + -32.714179112103515, + 19.70100611581722, + -9.73760203297563, + 3.7895069913206543, + -0.9628104019413473, + 0.14543777378795839, + -0.011806365049053717, + 0.0003962286759741023 + ], + "trajectoryPolynomialY": [ + -51.52449369027318, + 128.41242917627702, + -30.717925302147954, + 6.321989954375865, + -1.0487046334597734, + 0.19197996920603916, + -0.031105424454408108, + 0.002965537296096508, + -0.00011511837013536847 + ], + "trajectoryPolynomialZ": [ + -45.82764801754007, + 115.92112594476804, + -23.08365080730633, + 2.533793242606385, + -0.5082814756006739, + 0.07513239460160473, + -0.009138363968517723, + 0.0009282840894892433, + -0.00004468450492824837 + ], + "validTimeInterval": [ + 0.4676550828285342, + 6.717715507633166 + ], + "measuredTimeInterval": [ + 0.4676550828285342, + 3.463395813743793 + ] + } + }, + "isHomeRun": false, + "playId": "bfb7f1ca-0243-4ff8-99e8-6b5c2bcaec01", + "timeRemaining": "3:31", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 108.000001, + "launchAngle": 37.000001, + "totalDistance": 407, + "coordinates": { + "coordX": 103.63887521682382, + "coordY": 27.712815929136184, + "landingPosX": -51.129088689621945, + "landingPosY": 404.22687593731223 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 22.11777352814273, + -64.82683008883255, + 43.729144962070485, + -23.484888465357976, + 8.260301239898004, + -1.7900460914595002, + 0.23044614413603823, + -0.016144190152150892, + 0.00047342155140281427 + ], + "trajectoryPolynomialY": [ + -59.48529051309239, + 150.67648353491663, + -39.45504873030753, + 11.827020884150974, + -3.497711817184801, + 0.8439234131657698, + -0.13069790135381584, + 0.01105152105388803, + -0.0003855397713096895 + ], + "trajectoryPolynomialZ": [ + -41.37211950440423, + 99.69395343010652, + -2.2063495096027896, + -11.88325005012371, + 5.425383921428397, + -1.3660144698858492, + 0.195036267377348, + -0.014726005619849776, + 0.0004575291407745803 + ], + "validTimeInterval": [ + 0.4609681786426706, + 6.949481972587923 + ], + "measuredTimeInterval": [ + 0.4609681786426706, + 3.4589575640625956 + ] + } + }, + "isHomeRun": false, + "playId": "f3f52161-3420-490f-9fca-c3fae8abe790", + "timeRemaining": "3:25", + "isBonusTime": false, + "isTieBreaker": false + } + ], + "seed": 1, + "order": 0, + "isWinner": false, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 16 + }, + "bottomSeed": { + "started": true, + "complete": true, + "winner": true, + "player": { + "id": 596142, + "fullName": "Gary Sanchez", + "link": "/api/v1/people/596142" + }, + "topDerbyHitData": { + "launchSpeed": 116.000001, + "totalDistance": 483, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": true, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 102.000001, + "launchAngle": 17.000001, + "totalDistance": 362, + "coordinates": { + "coordX": 128.06783905459645, + "coordY": 46.173862836932216, + "landingPosX": 4.728148849554798, + "landingPosY": 362.01538104139655 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 13.094878554833674, + -37.49084726856301, + 31.314510126056227, + -21.433535249341062, + 13.098849669769185, + -5.504980293162981, + 1.3858770002872753, + -0.1867196181016621, + 0.01034853052468345 + ], + "trajectoryPolynomialY": [ + -72.10282216713524, + 166.69978721524342, + -31.238858108712254, + 11.264964805199053, + -4.806233237870712, + 1.5817631627214666, + -0.33333496968736576, + 0.039703566914027676, + -0.002030856684404277 + ], + "trajectoryPolynomialZ": [ + -21.718079188331732, + 58.05426407617164, + -20.113440517975853, + 10.610115158981998, + -7.581842515491339, + 3.122960688699821, + -0.7504642631697852, + 0.09755216809938365, + -0.005283914335952961 + ], + "validTimeInterval": [ + 0.48341292457954527, + 4.063265829852958 + ], + "measuredTimeInterval": [ + 0.48341292457954527, + 3.4770070652472453 + ] + } + }, + "isHomeRun": false, + "playId": "1bfc5d7b-2106-4b06-8e70-0e776ae85ebb", + "timeRemaining": "3:47", + "isBonusTime": true, + "isTieBreaker": false + }, + { + "bonusTime": true, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 108.000001, + "launchAngle": 41.000001, + "totalDistance": 386, + "coordinates": { + "coordX": 56.93756901879006, + "coordY": 50.54517213524309, + "landingPosX": -157.91241241209238, + "landingPosY": 352.0203086570565 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 19.915803161961918, + -63.119169077402276, + 28.605163569572035, + -12.450888250791204, + 3.6563363185650277, + -0.7041858970605714, + 0.08426342013500406, + -0.005665233301506127, + 0.0001633606289764684 + ], + "trajectoryPolynomialY": [ + -42.251542289543, + 131.33769279796806, + -35.990168695616916, + 10.529272554881022, + -2.9835324798867218, + 0.7080047847122113, + -0.10884130186474762, + 0.009110677748630603, + -0.0003135589506138113 + ], + "trajectoryPolynomialZ": [ + -37.21739353355734, + 113.54770164571433, + -14.284747071921403, + -3.40221314094148, + 1.6930316490522417, + -0.4032943503369199, + 0.05259642901919969, + -0.0035496216188313947, + 0.0000969579050803057 + ], + "validTimeInterval": [ + 0.3782047406712765, + 7.012902616647778 + ], + "measuredTimeInterval": [ + 0.3782047406712765, + 3.291140964666276 + ] + } + }, + "isHomeRun": false, + "playId": "57e58a07-a7bd-404f-a999-f4d0378d1f5e", + "timeRemaining": "3:40", + "isBonusTime": true, + "isTieBreaker": false + }, + { + "bonusTime": true, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 109.000001, + "launchAngle": 28.000001, + "totalDistance": 460, + "coordinates": { + "coordX": 3.1774312294387244, + "coordY": 45.00794583354539, + "landingPosX": -280.83587353718224, + "landingPosY": 364.6812699199271 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 36.81101840126961, + -87.40607599264676, + 13.613092175613067, + 2.051386750158945, + -3.123085862041744, + 1.1201187989278287, + -0.20153938464594462, + 0.018548747719822747, + -0.0006943362605052715 + ], + "trajectoryPolynomialY": [ + -60.05814327506049, + 156.21866945616242, + -52.68422471362074, + 24.150369307312708, + -9.401529631341143, + 2.440429669344176, + -0.3777853769246343, + 0.031508949173452004, + -0.0010907387943752285 + ], + "trajectoryPolynomialZ": [ + -36.67474670118904, + 95.48908071967205, + -21.429969216464336, + 6.1839275572501045, + -3.1135558899957987, + 0.9306889839661944, + -0.15879318379116808, + 0.014380899606614818, + -0.000535915718511479 + ], + "validTimeInterval": [ + 0.462729503784802, + 6.2825665399175685 + ], + "measuredTimeInterval": [ + 0.462729503784802, + 3.4597287764248192 + ] + } + }, + "isHomeRun": true, + "playId": "2c444c33-312d-4347-84ff-dfcc28821fc9", + "timeRemaining": "3:32", + "isBonusTime": true, + "isTieBreaker": false + }, + { + "bonusTime": true, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 108.000001, + "launchAngle": 24.000001, + "totalDistance": 427, + "coordinates": { + "coordX": 170.6700993838187, + "coordY": 23.007052018788784, + "landingPosX": 102.13893510794496, + "landingPosY": 414.9866844289492 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 1.6133522241794302, + -9.923144982133365, + 16.0060621318061, + -3.4707404924169416, + 0.1040047569689039, + 0.09893432207097931, + -0.01681121862668346, + 0.0007237390267797789, + 0.00001759109699769518 + ], + "trajectoryPolynomialY": [ + -71.08568241746529, + 168.63259021348915, + -27.508701869348553, + 1.6410182252459942, + 1.4480957892686084, + -0.6346922827753753, + 0.12829566625866887, + -0.013373751441837846, + 0.0005742229682095629 + ], + "trajectoryPolynomialZ": [ + -30.2703993367465, + 78.76702274298165, + -20.200830731134534, + 5.593902316576526, + -3.128283430001512, + 1.0873295126468225, + -0.21533501141429548, + 0.022505087565565053, + -0.0009638802634657251 + ], + "validTimeInterval": [ + 0.4692779353693911, + 5.339882793767803 + ], + "measuredTimeInterval": [ + 0.4692779353693911, + 3.4551229225039353 + ] + } + }, + "isHomeRun": true, + "playId": "4bb9141f-8a7b-48d9-9581-677143300243", + "timeRemaining": "3:25", + "isBonusTime": true, + "isTieBreaker": false + } + ], + "seed": 8, + "order": 0, + "isWinner": true, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 17 + } + }, + { + "topSeed": { + "started": true, + "complete": true, + "winner": true, + "player": { + "id": 592450, + "fullName": "Aaron Judge", + "link": "/api/v1/people/592450" + }, + "topDerbyHitData": { + "launchSpeed": 119.000001, + "totalDistance": 501, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 116.000001, + "launchAngle": 24.000001, + "totalDistance": 432, + "coordinates": { + "coordX": 152.23431192095995, + "coordY": 17.462108974907352, + "landingPosX": 59.985196358599474, + "landingPosY": 427.66529015288035 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 11.666719589595703, + -41.7910733270962, + 48.358572718678055, + -29.103145377994732, + 13.058353753397284, + -3.8797053765046394, + 0.6944605549191295, + -0.06729114339108329, + 0.0027080929729614935 + ], + "trajectoryPolynomialY": [ + -74.6131683172858, + 172.3620587808619, + -13.989495035859655, + -12.978193162208749, + 10.490492195976332, + -3.9051537559143914, + 0.8097473417622155, + -0.08929191008616807, + 0.004080715536188254 + ], + "trajectoryPolynomialZ": [ + -33.125121824857416, + 87.97028797099065, + -21.338316765821652, + -0.4605431055713959, + 1.649972819529468, + -0.7046383230201017, + 0.1471887415108612, + -0.015556715874453012, + 0.0006684593025411387 + ], + "validTimeInterval": [ + 0.4629489192172733, + 4.839530340115688 + ], + "measuredTimeInterval": [ + 0.4629489192172733, + 3.458821731735707 + ] + } + }, + "isHomeRun": true, + "playId": "60b3223d-cfda-4560-b392-b8265870ce94", + "timeRemaining": "3:51", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 111.000001, + "launchAngle": 34.000001, + "totalDistance": 421, + "coordinates": { + "coordX": 202.1633443543779, + "coordY": 36.96874713020469, + "landingPosX": 174.1487705181562, + "landingPosY": 383.0630332472286 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -6.437009736252265, + 13.785214225341754, + 0.9593226532014192, + 3.4589905768537115, + -1.6936509484109121, + 0.3776596639408269, + -0.045292302668318296, + 0.0028430861022843536, + -0.0000732579862301883 + ], + "trajectoryPolynomialY": [ + -66.67279001126805, + 165.23501136659493, + -39.56587763703457, + 8.372479143455651, + -0.9534079397776531, + -0.05016899458112834, + 0.033301980068176966, + -0.004222952324420904, + 0.00018618766512724818 + ], + "trajectoryPolynomialZ": [ + -41.88120363123631, + 107.67598109490727, + -20.731693549878667, + 1.7374697871595632, + -0.6592160045870228, + 0.23537202873807556, + -0.04781398370269039, + 0.004998615991160244, + -0.00020804897857718287 + ], + "validTimeInterval": [ + 0.45660916261481743, + 6.427680329606956 + ], + "measuredTimeInterval": [ + 0.45660916261481743, + 3.4549267507598485 + ] + } + }, + "isHomeRun": true, + "playId": "03dca4fd-1ad2-45d0-86f9-aa3033d5d4bd", + "timeRemaining": "3:44", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 110.000001, + "launchAngle": 35.000001, + "totalDistance": 440, + "coordinates": { + "coordX": 47.1346598089731, + "coordY": 29.141419255404287, + "landingPosX": -180.32692953790982, + "landingPosY": 400.960350354216 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 26.091785209967494, + -73.67140933790292, + 42.311943669428224, + -29.59203079169257, + 12.53092651909764, + -3.1107998741345484, + 0.4483640774188864, + -0.0348166246689891, + 0.0011271670992178192 + ], + "trajectoryPolynomialY": [ + -55.17798594364114, + 137.3980289573027, + -17.97705016805673, + -6.623587324095905, + 5.85562635885081, + -1.8496722448928549, + 0.3056981485182808, + -0.026157224634322483, + 0.0009145937090574275 + ], + "trajectoryPolynomialZ": [ + -40.72738357579742, + 105.56382100867425, + -12.682497597210547, + -4.5458769288088785, + 2.179620808229748, + -0.5265862303769482, + 0.07172210354627225, + -0.005167277730695544, + 0.00015308345699558182 + ], + "validTimeInterval": [ + 0.44467454653677113, + 6.5772667096226565 + ], + "measuredTimeInterval": [ + 0.44467454653677113, + 3.4389224781742422 + ] + } + }, + "isHomeRun": true, + "playId": "51daf290-1fc7-43c3-8581-52e2bdf751e3", + "timeRemaining": "3:37", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 97.000001, + "launchAngle": 45.000001, + "totalDistance": 308, + "coordinates": { + "coordX": 218.05494004481167, + "coordY": 106.03442254935189, + "landingPosX": 210.48517190021545, + "landingPosY": 225.14320237311114 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -12.686948515360747, + 24.156711214291974, + 10.903001765769803, + -3.0724074533341805, + -0.3888356763138595, + 0.44589921740715344, + -0.1116595564562452, + 0.012431031642490147, + -0.000531273675374895 + ], + "trajectoryPolynomialY": [ + -47.8136000390016, + 121.17681612757613, + -33.44109119134633, + 6.3009227609716225, + 0.12451267481227246, + -0.45673485166931643, + 0.12095327377423312, + -0.013908940891734231, + 0.0006107605180383515 + ], + "trajectoryPolynomialZ": [ + -46.41767394327658, + 119.12787317785329, + -19.51127701139883, + -5.068901809880354, + 3.259822226040015, + -0.8512678444382307, + 0.11351972157432706, + -0.00725903824030832, + 0.00016369571235410715 + ], + "validTimeInterval": [ + 0.4543121912046953, + 6.216240291790588 + ], + "measuredTimeInterval": [ + 0.4543121912046953, + 3.4478668905937604 + ] + } + }, + "isHomeRun": false, + "playId": "0b268062-4c0c-468e-aa96-2dde270036b2", + "timeRemaining": "3:30", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 88.000001, + "launchAngle": 41.000001, + "totalDistance": 288, + "coordinates": { + "coordX": 239.57047959151865, + "coordY": 150.186113414085, + "landingPosX": 259.680815695214, + "landingPosY": 124.18961708127755 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -28.30709101734004, + 64.0407676426009, + -4.0810265729195505, + 9.218728425454083, + -8.041779619903997, + 3.244559938684504, + -0.6904411312298475, + 0.0754756031885284, + -0.003350117146177953 + ], + "trajectoryPolynomialY": [ + -33.773357995895466, + 75.71867438950726, + 13.158754785235576, + -37.36818470064655, + 24.563357225321766, + -8.618688989726811, + 1.714101582001203, + -0.18161786158852716, + 0.007958125685852458 + ], + "trajectoryPolynomialZ": [ + -42.28678534430751, + 115.27111515717725, + -44.52500487513918, + 21.931875143147607, + -11.855797837019626, + 3.90030244457828, + -0.7399232601832968, + 0.07492273758510339, + -0.003140240237894658 + ], + "validTimeInterval": [ + 0.4534699598430477, + 5.095041615116463 + ], + "measuredTimeInterval": [ + 0.4534699598430477, + 3.457074493262007 + ] + } + }, + "isHomeRun": false, + "playId": "4afee44c-2173-4381-ad27-be096f370bcb", + "timeRemaining": "3:22", + "isBonusTime": false, + "isTieBreaker": false + } + ], + "seed": 2, + "order": 0, + "isWinner": true, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 23 + }, + "bottomSeed": { + "started": true, + "complete": true, + "winner": false, + "player": { + "id": 571506, + "fullName": "Justin Bour", + "link": "/api/v1/people/571506" + }, + "topDerbyHitData": { + "launchSpeed": 113.000001, + "totalDistance": 464, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 105.000001, + "launchAngle": 14.000001, + "totalDistance": 267, + "coordinates": { + "coordX": 186.10493232753385, + "coordY": 104.44852932417032, + "landingPosX": 137.43094077138355, + "landingPosY": 228.76937396102628 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -31.23035290106623, + 50.99406884526712, + 23.35491957681605, + -29.278282790312918, + 26.06614568320225, + -15.018133411493485, + 5.181729689711075, + -0.9899707833741286, + 0.08106834366250408 + ], + "trajectoryPolynomialY": [ + -76.55126229043516, + 175.3898567042789, + -67.09472859911139, + 67.79732372846777, + -59.29031644226867, + 31.389596133258266, + -9.552377920611114, + 1.5459051177383853, + -0.10304989068282713 + ], + "trajectoryPolynomialZ": [ + -23.730954728052666, + 66.43599388544577, + -37.012031288438386, + 18.128840513810644, + -12.558148666679381, + 6.369812531220422, + -2.004024466995714, + 0.34310452761008686, + -0.02419095511579621 + ], + "validTimeInterval": [ + 0.5272339287578544, + 2.808716472703157 + ], + "measuredTimeInterval": [ + 0.5272339287578544, + 2.7869821910115675 + ] + } + }, + "isHomeRun": false, + "playId": "8595b82a-7857-4fb4-8539-42ff2a554ddb", + "timeRemaining": "3:48", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 103.000001, + "launchAngle": 28.000001, + "totalDistance": 405, + "coordinates": { + "coordX": 208.1449890862914, + "coordY": 47.37250362941657, + "landingPosX": 187.82590201191346, + "landingPosY": 359.27466866757 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -35.69359631863675, + 81.47170358972899, + -30.649741198922403, + 12.897839343343184, + -3.172873133356951, + 0.33976744537420134, + 0.011506177318740327, + -0.0057466883565445255, + 0.0003580160850880116 + ], + "trajectoryPolynomialY": [ + -63.70450884400308, + 147.21822148136735, + -35.7851503103105, + 12.547926996821445, + -4.666091962902631, + 1.317721577168986, + -0.22878625936288963, + 0.021411575880373037, + -0.0008270643322902749 + ], + "trajectoryPolynomialZ": [ + -37.54043662491953, + 85.82157958100018, + -12.09271227860325, + -6.5130708910672706, + 5.269297729654644, + -1.9597127409357482, + 0.3819314244620591, + -0.037934654793021966, + 0.0015222795090048253 + ], + "validTimeInterval": [ + 0.5144426533324121, + 5.741727199230815 + ], + "measuredTimeInterval": [ + 0.5144426533324121, + 3.508160513967526 + ] + } + }, + "isHomeRun": true, + "playId": "bd8ce204-45ab-4fc9-9889-91349755a837", + "timeRemaining": "3:42", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 103.000001, + "launchAngle": 32.000001, + "totalDistance": 401, + "coordinates": { + "coordX": 194.3807787443953, + "coordY": 42.99543031509171, + "landingPosX": 156.35380308409486, + "landingPosY": 369.2829205716723 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -31.11341437123758, + 80.1535213860467, + -38.43540169253998, + 21.84871885203033, + -8.77534724242268, + 2.183008920652544, + -0.3202336424078933, + 0.02541380709633881, + -0.0008416614752553332 + ], + "trajectoryPolynomialY": [ + -58.918929717117095, + 140.54867300150858, + -26.6867930599358, + 0.5409972763436257, + 2.2228978380588518, + -0.7995729770689544, + 0.13525338678228083, + -0.011622378605211769, + 0.0004073949765718861 + ], + "trajectoryPolynomialZ": [ + -40.74128844864424, + 97.81799693013534, + -21.14889665737596, + 5.155614879921873, + -2.1093100578336115, + 0.523612204173907, + -0.07735142963613062, + 0.006359068045264049, + -0.0002223971519483935 + ], + "validTimeInterval": [ + 0.49069404652818294, + 6.5371521764109435 + ], + "measuredTimeInterval": [ + 0.49069404652818294, + 3.4866788977917995 + ] + } + }, + "isHomeRun": true, + "playId": "e9d6f1e3-b623-44e5-9307-df1dc64f410b", + "timeRemaining": "3:35", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 107.000001, + "launchAngle": 29.000001, + "totalDistance": 433, + "coordinates": { + "coordX": 231.20788835542209, + "coordY": 47.194387679581894, + "landingPosX": 240.5596098913303, + "landingPosY": 359.6819337888212 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -36.01595145784577, + 77.05556573228971, + -16.473435257158414, + 9.527466823633508, + -4.231519036537629, + 1.0709546790006612, + -0.15210984932351893, + 0.01138851332139564, + -0.0003509901568323753 + ], + "trajectoryPolynomialY": [ + -62.594114382536915, + 142.00470797204326, + -19.99111394260506, + -4.434806213691169, + 3.9636110493630303, + -1.1279034770973149, + 0.16724258940356654, + -0.012917233728691267, + 0.0004101719681518532 + ], + "trajectoryPolynomialZ": [ + -41.24007884183484, + 99.87866212600721, + -28.316133891727322, + 8.111770858293568, + -2.3973394103582826, + 0.378295947495194, + -0.026930603749520006, + 0.0002180879565900203, + 0.0000470429613898708 + ], + "validTimeInterval": [ + 0.5086920309443367, + 6.139364028014606 + ], + "measuredTimeInterval": [ + 0.5086920309443367, + 3.5022326031406443 + ] + } + }, + "isHomeRun": true, + "playId": "66889ca5-3a97-4fbb-b0f7-5414e4b9a98f", + "timeRemaining": "3:19", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 100.000001, + "launchAngle": 40.000001, + "totalDistance": 357, + "coordinates": { + "coordX": 209.2265973702912, + "coordY": 72.15264607622888, + "landingPosX": 190.29901758263205, + "landingPosY": 302.61445532008344 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -30.68732986339886, + 73.72111192740626, + -29.194453833526804, + 14.74641516833107, + -5.500483829633695, + 1.3153646775904297, + -0.18792380204001752, + 0.014601865699121223, + -0.000474466962923361 + ], + "trajectoryPolynomialY": [ + -52.2733777426132, + 131.39207403947782, + -39.56204664342587, + 11.835386182359988, + -2.961155897172239, + 0.570648408524814, + -0.07338071692501669, + 0.005420299586330446, + -0.00017205407551289608 + ], + "trajectoryPolynomialZ": [ + -45.42050856102751, + 105.93325790684773, + -9.66046944371198, + -6.562285430515487, + 3.157916946732596, + -0.8045026979207762, + 0.11503679425893694, + -0.008628025222542366, + 0.00026470286778908946 + ], + "validTimeInterval": [ + 0.4892412635245745, + 6.837277762547954 + ], + "measuredTimeInterval": [ + 0.4892412635245745, + 3.484939742739366 + ] + } + }, + "isHomeRun": false, + "playId": "62dbe2d2-2925-4e6f-bfe6-e839a77c0595", + "timeRemaining": "3:12", + "isBonusTime": false, + "isTieBreaker": false + } + ], + "seed": 7, + "order": 0, + "isWinner": false, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 22 + } + }, + { + "topSeed": { + "started": true, + "complete": true, + "winner": true, + "player": { + "id": 641355, + "fullName": "Cody Bellinger", + "link": "/api/v1/people/641355" + }, + "topDerbyHitData": { + "launchSpeed": 107.000001, + "totalDistance": 446, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 101.000001, + "launchAngle": 25.000001, + "totalDistance": 393, + "coordinates": { + "coordX": 200.90661953539495, + "coordY": 49.835146119285895, + "landingPosX": 171.27524803879635, + "landingPosY": 353.64379510927324 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -26.579599215084748, + 55.804824648764814, + -14.490598663429482, + 11.20034915227204, + -5.095857843653617, + 1.3471487530483164, + -0.21839552870189136, + 0.020585371989835607, + -0.0008661230177144835 + ], + "trajectoryPolynomialY": [ + -69.73925715956119, + 142.98592780330162, + -13.885283828399086, + -9.005927481296641, + 7.898280594664914, + -3.0560010553923735, + 0.6621765097640518, + -0.07646137134051677, + 0.0036475151392283307 + ], + "trajectoryPolynomialZ": [ + -34.66224899817344, + 77.76838285777907, + -16.07150067382376, + -0.18048921180303681, + -0.1476112928849612, + 0.30623723643890544, + -0.1099959894855787, + 0.016187165992406865, + -0.0008781911040318562 + ], + "validTimeInterval": [ + 0.5359713197318186, + 4.952248785552106 + ], + "measuredTimeInterval": [ + 0.5359713197318186, + 3.542482311806344 + ] + } + }, + "isHomeRun": true, + "playId": "b39d1888-60f8-4153-8eca-c3057050beb4", + "timeRemaining": "3:51", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 91.000001, + "launchAngle": 33.000001, + "totalDistance": 347, + "coordinates": { + "coordX": 229.03603171082676, + "coordY": 92.87441727227215, + "landingPosX": 235.59362306913755, + "landingPosY": 255.23377623699568 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -31.07606079762967, + 58.710127324631316, + 13.236442682148663, + -14.978007013486316, + 7.6166852453942955, + -2.288987415658921, + 0.4009351552285837, + -0.03774032450457298, + 0.001476252880168941 + ], + "trajectoryPolynomialY": [ + -51.90908013643929, + 123.98693409753247, + -46.9844646329148, + 27.56781159565868, + -13.669429755736424, + 4.367488064796727, + -0.8161555989218521, + 0.0813005082347954, + -0.0033382869647823566 + ], + "trajectoryPolynomialZ": [ + -42.31943718042757, + 100.98006597307436, + -33.948147666942404, + 10.972249422966796, + -3.531868313787294, + 0.6539871222571757, + -0.06454275605821126, + 0.0027484906181256645, + -0.0000099662509988667 + ], + "validTimeInterval": [ + 0.5171462446299829, + 5.415612431153393 + ], + "measuredTimeInterval": [ + 0.5171462446299829, + 3.514134884028505 + ] + } + }, + "isHomeRun": true, + "playId": "a88eaa69-d284-4b79-9bdb-e58b69ed7740", + "timeRemaining": "3:44", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 105.000001, + "launchAngle": 31.000001, + "totalDistance": 441, + "coordinates": { + "coordX": 244.01843258990493, + "coordY": 51.93101269935917, + "landingPosX": 269.85113519152424, + "landingPosY": 348.8515608503417 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -37.263976002667825, + 77.73020476854634, + -15.673013045204488, + 8.437842484011968, + -3.3958268443293336, + 0.8052354038967259, + -0.10917485277807198, + 0.007888299210184264, + -0.00023625991461409757 + ], + "trajectoryPolynomialY": [ + -66.08096518926104, + 141.90653592966112, + -26.280639372255465, + 0.8639004071592885, + 1.6282495372483052, + -0.5469460455597928, + 0.08782127277844042, + -0.0074524346238281245, + 0.00026776239269868434 + ], + "trajectoryPolynomialZ": [ + -44.84514816483656, + 102.56576018265066, + -29.194336680028844, + 12.380074010123124, + -6.230312066229839, + 1.8408221579740118, + -0.31095328890870144, + 0.027945071799641525, + -0.0010365154580103824 + ], + "validTimeInterval": [ + 0.5322035910307238, + 6.107865822475993 + ], + "measuredTimeInterval": [ + 0.5322035910307238, + 3.5291948954269596 + ] + } + }, + "isHomeRun": true, + "playId": "f453cf1e-01a8-47fd-a761-7c5d510df5bd", + "timeRemaining": "3:32", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 107.000001, + "launchAngle": 22.000001, + "totalDistance": 320, + "coordinates": { + "coordX": 245.30133481596658, + "coordY": 131.14657200722252, + "landingPosX": 272.78451275336175, + "landingPosY": 167.72384939921596 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -53.215051217915516, + 104.58498545148383, + 1.2253765099486953, + -5.890543484570903, + 3.7299975125118077, + -1.1671958907193416, + 0.12686263463750758, + 0.013878573689619552, + -0.003039649503679142 + ], + "trajectoryPolynomialY": [ + -57.704085767843175, + 135.8199674474336, + -52.6576019560711, + 37.38215596970105, + -26.32145784777265, + 12.13542296116971, + -3.3518842348124376, + 0.5056055047222483, + -0.03205816855832465 + ], + "trajectoryPolynomialZ": [ + -35.40070783399069, + 87.99333279622972, + -28.396549431431406, + -3.084117879255543, + 6.0551229776345386, + -3.337787633399517, + 1.0039791459171188, + -0.1613351268783602, + 0.010769198345137197 + ], + "validTimeInterval": [ + 0.5222630202129205, + 3.4606706129863465 + ], + "measuredTimeInterval": [ + 0.5222630202129205, + 2.8764594894990765 + ] + } + }, + "isHomeRun": false, + "playId": "4bac67d7-1605-47f7-89e4-a7efd4ba2596", + "timeRemaining": "3:25", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 105.000001, + "launchAngle": 14.000001, + "totalDistance": 224, + "coordinates": { + "coordX": 186.65825675813358, + "coordY": 127.51745760663445, + "landingPosX": 138.6961264076425, + "landingPosY": 176.02188064101 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -38.49511246877996, + 70.16107667811086, + 12.445319166281477, + -10.407453562418649, + 7.456917974368746, + -4.511583731872339, + 1.7003052177838296, + -0.34712641942193506, + 0.029479944988539387 + ], + "trajectoryPolynomialY": [ + -72.05540188287905, + 158.5517529382944, + -41.73783545558252, + 27.072656692129694, + -23.61308950252806, + 14.60700236901937, + -5.498375633059084, + 1.1243698349900366, + -0.09595473237709627 + ], + "trajectoryPolynomialZ": [ + -24.660971297554422, + 71.01936825320178, + -36.00137331147121, + 7.585824067041697, + -3.021659080189109, + 1.5806906201702438, + -0.6571162153614677, + 0.15165517990615385, + -0.014302022963821727 + ], + "validTimeInterval": [ + 0.5226203977522114, + 2.3241816770490806 + ], + "measuredTimeInterval": [ + 0.5226203977522114, + 2.341359043008405 + ] + } + }, + "isHomeRun": false, + "playId": "0f3b52e9-a884-4e18-b949-a51bf1c60314", + "timeRemaining": "3:13", + "isBonusTime": false, + "isTieBreaker": false + } + ], + "seed": 3, + "order": 0, + "isWinner": true, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 15 + }, + "bottomSeed": { + "started": true, + "complete": true, + "winner": false, + "player": { + "id": 453568, + "fullName": "Charlie Blackmon", + "link": "/api/v1/people/453568" + }, + "topDerbyHitData": { + "launchSpeed": 109.000001, + "totalDistance": 434, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 102.000001, + "launchAngle": 25.000001, + "totalDistance": 385, + "coordinates": { + "coordX": 207.40417666448155, + "coordY": 57.346671924531165, + "landingPosX": 186.13202192384261, + "landingPosY": 336.4685647568304 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -20.09258833392705, + 45.0716275642169, + 2.734268776261993, + -0.5598739707927305, + 0.22207890554230705, + -0.1050985658994884, + 0.0003034182912983427, + 0.005458516155649751, + -0.0006158327251677256 + ], + "trajectoryPolynomialY": [ + -57.14855066667697, + 144.70894693760857, + -23.076043997512468, + 1.3368662428762343, + 2.6610714739279624, + -1.6761222397289415, + 0.4812829244984773, + -0.06829852587973946, + 0.0038467935893331806 + ], + "trajectoryPolynomialZ": [ + -30.39294170584981, + 87.07626359206391, + -29.15770771394138, + 9.651017496426512, + -5.1952443270233, + 1.694565977834303, + -0.2963979781917054, + 0.025139923209351694, + -0.0007531666021533598 + ], + "validTimeInterval": [ + 0.4415779340473463, + 4.4736073529926355 + ], + "measuredTimeInterval": [ + 0.4415779340473463, + 3.4352146265964265 + ] + } + }, + "isHomeRun": true, + "playId": "01f94263-cf4d-4c41-92eb-c6591cf01b70", + "timeRemaining": "3:49", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 101.000001, + "launchAngle": 34.000001, + "totalDistance": 411, + "coordinates": { + "coordX": 184.94535000098531, + "coordY": 34.785765821493754, + "landingPosX": 134.77953623820798, + "landingPosY": 388.05445680141594 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -12.974042279603063, + 29.560208759035312, + -0.7499011069588813, + 2.037695916166788, + -2.055503891976323, + 0.7944159800451738, + -0.15269581249832773, + 0.014710990920982351, + -0.000568161636002049 + ], + "trajectoryPolynomialY": [ + -53.60205450084968, + 135.7112737343806, + -18.721906946303168, + -3.1873043563048538, + 3.589727331603172, + -1.1606571158282575, + 0.19482357783363394, + -0.01701353863692126, + 0.0006108488777741818 + ], + "trajectoryPolynomialZ": [ + -37.457921403759826, + 103.12092309302182, + -27.44135271916074, + 8.119298256213499, + -3.2796196717324437, + 0.844131486455707, + -0.13006070054287108, + 0.011001264675775302, + -0.00039200701322375015 + ], + "validTimeInterval": [ + 0.43809388213984446, + 6.203399668272613 + ], + "measuredTimeInterval": [ + 0.43809388213984446, + 3.43338287814944 + ] + } + }, + "isHomeRun": true, + "playId": "250cf6ad-8b63-494b-af5b-75e5dcc9820f", + "timeRemaining": "3:35", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 103.000001, + "launchAngle": 27.000001, + "totalDistance": 406, + "coordinates": { + "coordX": 156.37720966638165, + "coordY": 29.60607390982338, + "landingPosX": 69.45800187762543, + "landingPosY": 399.89790965562855 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -11.766319200699806, + 27.56182225559978, + 0.47884769624598744, + -5.596369216623072, + 3.3688005303832287, + -0.9918697548036913, + 0.1574935209936433, + -0.012724478323509236, + 0.0004015193540906218 + ], + "trajectoryPolynomialY": [ + -60.784759915137684, + 156.08448307453446, + -30.6454460132598, + 7.100676035262571, + -0.8317493241849683, + -0.14537254408613104, + 0.07234058755715471, + -0.010510388636588089, + 0.000548044347307581 + ], + "trajectoryPolynomialZ": [ + -29.508787181317814, + 83.79002084076465, + -20.389327980832284, + 5.7462103243772695, + -3.8750200325037105, + 1.4473857175923257, + -0.29309306619722136, + 0.03077871118276801, + -0.0013182722607114306 + ], + "validTimeInterval": [ + 0.43729390107160515, + 5.158450517282521 + ], + "measuredTimeInterval": [ + 0.43729390107160515, + 3.433935412057264 + ] + } + }, + "isHomeRun": false, + "playId": "dac8eed1-039d-49e0-999f-c6cb0532acc3", + "timeRemaining": "3:26", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 107.000001, + "launchAngle": 28.000001, + "totalDistance": 392, + "coordinates": { + "coordX": 254, + "coordY": 94.73929268754718, + "landingPosX": 300.65014042761356, + "landingPosY": 250.96970716948482 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -33.22047914296476, + 75.12926254530787, + 6.456842190276966, + -3.600351903205897, + -0.4480893417339568, + 0.9474936400281875, + -0.34442497017938334, + 0.05391301077767671, + -0.0031860521621225567 + ], + "trajectoryPolynomialY": [ + -49.251773536165366, + 125.24707935590347, + -1.5002333853222658, + -33.332775607875476, + 27.078727870147226, + -11.242704229255358, + 2.6175555113948796, + -0.3217458745839063, + 0.016223805420503796 + ], + "trajectoryPolynomialZ": [ + -33.68003616146317, + 97.5379305947529, + -31.709750543280474, + 10.665210513486068, + -5.816814072348198, + 1.9916927657545527, + -0.3879246749255409, + 0.03979128433119829, + -0.0016715583579501037 + ], + "validTimeInterval": [ + 0.4310430340739074, + 4.658249454972322 + ], + "measuredTimeInterval": [ + 0.4310430340739074, + 3.4442551697690686 + ] + } + }, + "isHomeRun": false, + "playId": "06736afd-ad61-4b63-8ad5-527795726b8e", + "timeRemaining": "3:17", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 104.000001, + "launchAngle": 35.000001, + "totalDistance": 427, + "coordinates": { + "coordX": 235.37139987905886, + "coordY": 53.25777150345223, + "landingPosX": 250.0795491616683, + "landingPosY": 345.81790448367946 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -22.385633885708355, + 52.43271371665016, + -2.020297472413774, + 3.2559801885719666, + -2.319533877110467, + 0.7446158621217059, + -0.1265285894782041, + 0.011142843454764476, + -0.0004012691489904721 + ], + "trajectoryPolynomialY": [ + -50.696857986975715, + 129.90587534008637, + -17.11226489245102, + -4.601168591193699, + 4.115577173657373, + -1.2408113272350774, + 0.19522699371954721, + -0.01597648324453924, + 0.0005383870774914361 + ], + "trajectoryPolynomialZ": [ + -40.66239056382674, + 112.16339527924049, + -32.711979366901026, + 9.32529412074285, + -2.9908875889517197, + 0.5769616039685144, + -0.06288708490547469, + 0.0034760192473285013, + -0.00006891150970503498 + ], + "validTimeInterval": [ + 0.4380486357040005, + 6.034079519493022 + ], + "measuredTimeInterval": [ + 0.4380486357040005, + 3.433791886585291 + ] + } + }, + "isHomeRun": true, + "playId": "21f1980c-38ac-426e-87f5-3c29b86909ed", + "timeRemaining": "3:08", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 95.000001, + "launchAngle": 35.000001, + "totalDistance": 380, + "coordinates": { + "coordX": 150.58910677025906, + "coordY": 40.30828221691212, + "landingPosX": 56.22340705334517, + "landingPosY": 375.42712998717286 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -10.769992524037647, + 25.530444538407586, + 3.736437833041462, + -8.239551279550529, + 4.317222091710564, + -1.2095471912045102, + 0.1924811994660623, + -0.016372776525334542, + 0.0005788642169509761 + ], + "trajectoryPolynomialY": [ + -49.655974540089474, + 128.85652057903522, + -21.86141850601539, + 2.4255422893712875, + 0.4391660647134536, + -0.21650911773381923, + 0.03602763988944109, + -0.0028878698323616964, + 0.00009320204235900607 + ], + "trajectoryPolynomialZ": [ + -34.69364778177862, + 95.93726058257033, + -21.134995761828957, + 3.593283125552127, + -1.2329677929122027, + 0.27347347818613327, + -0.03660665914963555, + 0.0027850467095116315, + -0.00009285087740814056 + ], + "validTimeInterval": [ + 0.43286712486264284, + 6.00826690513604 + ], + "measuredTimeInterval": [ + 0.43286712486264284, + 3.425972778749777 + ] + } + }, + "isHomeRun": false, + "playId": "18fbe610-ed18-4bf6-87f6-7b84b4b6023f", + "timeRemaining": "3:00", + "isBonusTime": false, + "isTieBreaker": false + } + ], + "seed": 6, + "order": 0, + "isWinner": false, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 14 + } + }, + { + "topSeed": { + "started": true, + "complete": true, + "winner": false, + "player": { + "id": 519058, + "fullName": "Mike Moustakas", + "link": "/api/v1/people/519058" + }, + "topDerbyHitData": { + "launchSpeed": 114.000001, + "totalDistance": 442, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": true, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 93.000001, + "launchAngle": 34.000001, + "totalDistance": 371, + "coordinates": { + "coordX": 236.27924865253675, + "coordY": 85.20253641780303, + "landingPosX": 252.15536068304752, + "landingPosY": 272.77566111210206 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -34.66422641603215, + 76.1800778643161, + -16.447233214816087, + 9.339285775599501, + -4.005576631564804, + 1.0231010866896426, + -0.15089624722097242, + 0.011994035220394847, + -0.0004001007685951225 + ], + "trajectoryPolynomialY": [ + -45.63697631395248, + 103.57547711234518, + -2.6776353035797285, + -15.420317374454466, + 9.059611525650796, + -2.622817774532146, + 0.4296402187686882, + -0.03794297897382419, + 0.0014046698191999389 + ], + "trajectoryPolynomialZ": [ + -40.96231022110542, + 105.48585666255184, + -42.153796618369654, + 23.897490871858608, + -11.852742900120534, + 3.4404450436542535, + -0.5764993324652243, + 0.051884253556380176, + -0.001943733743007319 + ], + "validTimeInterval": [ + 0.49022772061043784, + 5.861607725493213 + ], + "measuredTimeInterval": [ + 0.49022772061043784, + 3.482705256168998 + ] + } + }, + "isHomeRun": true, + "playId": "0588db57-7b85-4e03-84bd-114f6fb7a18f", + "timeRemaining": "3:55", + "isBonusTime": true, + "isTieBreaker": false + }, + { + "bonusTime": true, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 84.000001, + "launchAngle": 57.000001, + "totalDistance": 196, + "coordinates": { + "coordX": 99.77259252210048, + "coordY": 122.90037363317398, + "landingPosX": -59.969409233174744, + "landingPosY": 186.57892096234937 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -0.5596477021534753, + 3.620539154497677, + -7.883434652787471, + 1.109332553270047, + 1.400770514239467, + -0.9062422982907359, + 0.22586314489718312, + -0.025928432453453852, + 0.0011408179717921653 + ], + "trajectoryPolynomialY": [ + -37.59427001194602, + 93.22568172108535, + -33.521216093195946, + 14.160147005906172, + -5.175262049192635, + 1.3294900203622255, + -0.20559922660712385, + 0.016849948961809424, + -0.0005623232726033056 + ], + "trajectoryPolynomialZ": [ + -53.69726705898439, + 124.52188086659, + -21.574920313111345, + -2.913021783377937, + 2.528979705516365, + -0.7618567126654989, + 0.11409041538345442, + -0.008439019394053506, + 0.00024458286546404735 + ], + "validTimeInterval": [ + 0.4973632298846754, + 6.139553781642444 + ], + "measuredTimeInterval": [ + 0.4973632298846754, + 3.4182863338057303 + ] + } + }, + "isHomeRun": false, + "playId": "4b8c0864-a8e8-4006-9a4b-20c835fcd926", + "timeRemaining": "3:48", + "isBonusTime": true, + "isTieBreaker": false + }, + { + "bonusTime": true, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 86.000001, + "launchAngle": 55.000001, + "totalDistance": 193, + "coordinates": { + "coordX": 131.434698289419, + "coordY": 120.30868230517883, + "landingPosX": 12.426529234795144, + "landingPosY": 192.5048668640012 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -5.385900236100319, + 8.774382514265763, + 5.095911747745187, + -7.497900989575934, + 3.58346231141905, + -0.9039041329187226, + 0.1290675825990657, + -0.009885415460852741, + 0.0003162884566954511 + ], + "trajectoryPolynomialY": [ + -38.85431499565069, + 101.79192110208079, + -43.75315941476822, + 22.398768791910808, + -9.559087180844024, + 2.746297664918818, + -0.4676786306595311, + 0.042298576685728145, + -0.0015677867819416532 + ], + "trajectoryPolynomialZ": [ + -49.98730262067688, + 114.25693379735674, + -2.516667733264242, + -20.533462565274966, + 11.474451166487924, + -3.350465030543145, + 0.5417506989856162, + -0.04604470628212461, + 0.001611249692792185 + ], + "validTimeInterval": [ + 0.4882744884163292, + 6.230109815564722 + ], + "measuredTimeInterval": [ + 0.4882744884163292, + 3.1553965360612026 + ] + } + }, + "isHomeRun": false, + "playId": "2174ce51-2c4b-486c-8d8d-a364834338b7", + "timeRemaining": "3:36", + "isBonusTime": true, + "isTieBreaker": false + }, + { + "bonusTime": true, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 102.000001, + "launchAngle": 41.000001, + "totalDistance": 356, + "coordinates": { + "coordX": 196.0377408510617, + "coordY": 65.31755813109254, + "landingPosX": 160.14247486731526, + "landingPosY": 318.24299910474906 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -30.890832095892662, + 73.09528724256187, + -30.16582833305708, + 16.58274993031512, + -6.357118742936324, + 1.4769423642269255, + -0.20085303436481206, + 0.01476597185322518, + -0.00045381748885773524 + ], + "trajectoryPolynomialY": [ + -55.23868437971997, + 138.04561443256125, + -51.35381528237902, + 22.017659910388975, + -7.828024629184009, + 1.9420776061084308, + -0.29599041515132113, + 0.024622401959679113, + -0.0008527686485277423 + ], + "trajectoryPolynomialZ": [ + -47.75404011162212, + 110.48820106536957, + -14.869267224183936, + -4.047590432831183, + 2.6240753459123574, + -0.7941265850478363, + 0.12904016758775622, + -0.010749754997509724, + 0.000361803697916375 + ], + "validTimeInterval": [ + 0.496325370696401, + 6.707597953704158 + ], + "measuredTimeInterval": [ + 0.496325370696401, + 3.474710300492077 + ] + } + }, + "isHomeRun": false, + "playId": "d8f97062-0a22-4526-95bc-38b91a47737b", + "timeRemaining": "3:28", + "isBonusTime": true, + "isTieBreaker": false + }, + { + "bonusTime": true, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 105.000001, + "launchAngle": 16.000001, + "totalDistance": 361, + "coordinates": { + "coordX": 102.60916525685693, + "coordY": 48.253093340111775, + "landingPosX": -53.48353786774849, + "landingPosY": 357.26118545266576 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 0.9401881171495963, + -2.179780992239855, + -1.2082247728025135, + -7.628411336512196, + 7.471174852846773, + -3.458583339667764, + 0.8642626288371651, + -0.11252127213235223, + 0.006011471392666945 + ], + "trajectoryPolynomialY": [ + -76.48518347022264, + 171.0271048045091, + -26.860456623714324, + 1.2646956483722882, + 3.4459226729290804, + -2.0668010793560714, + 0.5714990946553604, + -0.07897574815715078, + 0.0043806569537130175 + ], + "trajectoryPolynomialZ": [ + -21.948858601551606, + 55.8792050457388, + -8.803531915626522, + -6.932562633342842, + 5.4612826479449295, + -2.266834374024247, + 0.5101726754506377, + -0.05857824969410412, + 0.002676747020049578 + ], + "validTimeInterval": [ + 0.49259109406247475, + 3.9822877493358875 + ], + "measuredTimeInterval": [ + 0.49259109406247475, + 3.4927096893112326 + ] + } + }, + "isHomeRun": false, + "playId": "8d2424ea-f816-4168-8d04-29e6374306b6", + "timeRemaining": "3:17", + "isBonusTime": true, + "isTieBreaker": false + } + ], + "seed": 4, + "order": 0, + "isWinner": false, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 10 + }, + "bottomSeed": { + "started": true, + "complete": true, + "winner": true, + "player": { + "id": 593934, + "fullName": "Miguel Sano", + "link": "/api/v1/people/593934" + }, + "topDerbyHitData": { + "launchSpeed": 112.000001, + "totalDistance": 470, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 110.000001, + "launchAngle": 17.000001, + "totalDistance": 378, + "coordinates": { + "coordX": 83.25111595400686, + "coordY": 44.952072310285075, + "landingPosX": -97.74604385797306, + "landingPosY": 364.80902567255043 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 29.980714830946457, + -60.96103373977202, + 20.58549096633612, + -5.478407723425202, + -0.25871090086089504, + 0.8351167347571943, + -0.3027596295835288, + 0.04786521754859341, + -0.0028866147718369563 + ], + "trajectoryPolynomialY": [ + -97.01267463356734, + 190.55160122679814, + -47.594006825118285, + 18.090251520672307, + -6.253374410085835, + 1.5344156828950053, + -0.24095816587022997, + 0.02173763420404064, + -0.0008596973820318606 + ], + "trajectoryPolynomialZ": [ + -29.87046506827852, + 57.55281009886572, + 0.4602797422028197, + -17.074321086608098, + 12.851323998950852, + -5.662131522844612, + 1.4114795497050159, + -0.1842224074587854, + 0.009805327456035543 + ], + "validTimeInterval": [ + 0.5897908432238708, + 4.287487986778531 + ], + "measuredTimeInterval": [ + 0.5897908432238708, + 3.588242281905554 + ] + } + }, + "isHomeRun": false, + "playId": "1c00950b-1211-4d3f-9642-22c57779b38a", + "timeRemaining": "3:56", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 99.000001, + "launchAngle": 39.000001, + "totalDistance": 355, + "coordinates": { + "coordX": 70.88942884942998, + "coordY": 59.52665896846892, + "landingPosX": -126.01124976562923, + "landingPosY": 331.48398763951207 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 40.24174077183768, + -99.0523174670937, + 67.87159816165781, + -37.22126158931539, + 12.707351367291748, + -2.6373481624077693, + 0.32414389729631915, + -0.021685186946740893, + 0.0006078921530673204 + ], + "trajectoryPolynomialY": [ + -65.42577868042935, + 139.16946658098286, + -46.01987787716469, + 17.669161015881528, + -5.955804347668018, + 1.4549018288084106, + -0.21982708105890636, + 0.018098567440086682, + -0.0006188474610375352 + ], + "trajectoryPolynomialZ": [ + -54.0173822803722, + 103.39017576518098, + -3.178057265815599, + -11.011261421290115, + 4.781029865623327, + -1.1439241379986267, + 0.15565693444350034, + -0.011223850288208154, + 0.0003334456747877558 + ], + "validTimeInterval": [ + 0.581173763770995, + 6.7798120694350015 + ], + "measuredTimeInterval": [ + 0.581173763770995, + 3.5446935263257613 + ] + } + }, + "isHomeRun": false, + "playId": "10013712-a268-4da1-bbe0-b1c29d69bfad", + "timeRemaining": "3:44", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 106.000001, + "launchAngle": 28.000001, + "totalDistance": 446, + "coordinates": { + "coordX": 30.78716176330454, + "coordY": 34.290429706453125, + "landingPosX": -217.70575933896097, + "landingPosY": 389.1870511768179 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 36.883817893267775, + -63.471139889753495, + -11.675740342584115, + 21.701360680992106, + -10.990025531808117, + 2.9009938960297807, + -0.4319376196336247, + 0.03438994290584651, + -0.0011411744775014874 + ], + "trajectoryPolynomialY": [ + -80.24305682027735, + 177.05572984954676, + -72.28533407994173, + 36.83094065342342, + -13.849404192394578, + 3.326708355561376, + -0.4778506725609609, + 0.03740845557566621, + -0.001228455066634081 + ], + "trajectoryPolynomialZ": [ + -49.60605652592098, + 105.95047162533196, + -31.135291523126284, + 13.649625800768742, + -6.47889833651492, + 1.8125194640195659, + -0.2912909833874237, + 0.024994047314098797, + -0.0008874824079523265 + ], + "validTimeInterval": [ + 0.5701045384971246, + 6.405940109786139 + ], + "measuredTimeInterval": [ + 0.5701045384971246, + 3.564538319051005 + ] + } + }, + "isHomeRun": true, + "playId": "bfb5b178-4084-40c5-8275-aae336acd21e", + "timeRemaining": "3:34", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 91.000001, + "launchAngle": 22.000001, + "totalDistance": 347, + "coordinates": { + "coordX": 76.38638456943244, + "coordY": 61.14615250370565, + "landingPosX": -113.44236786652247, + "landingPosY": 327.78098837635844 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 26.72908740237709, + -55.9866797628736, + 25.54480748734103, + -11.116327491201304, + 1.904115869153572, + 0.2877268966091262, + -0.18196893870370032, + 0.029159655546379448, + -0.001633634144757712 + ], + "trajectoryPolynomialY": [ + -71.56522452918561, + 139.6982470336282, + -22.410729028773183, + 2.4882594119252976, + 0.41921186505903774, + -0.20044054930031624, + 0.025326089718677277, + -0.0005561539951099525, + -0.0000709406463799938 + ], + "trajectoryPolynomialZ": [ + -35.13040304295472, + 73.81214012049566, + -20.862611900680125, + 6.390063881319498, + -2.8333435110679197, + 0.6533994649196762, + -0.06586261070832519, + 0.0005349757051701804, + 0.0002449240643566047 + ], + "validTimeInterval": [ + 0.5833708993407125, + 4.818266529223499 + ], + "measuredTimeInterval": [ + 0.5833708993407125, + 3.5754489662655593 + ] + } + }, + "isHomeRun": false, + "playId": "c33fe3c1-963b-4581-a714-3ad2ac2746d8", + "timeRemaining": "3:25", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 111.000001, + "launchAngle": 14.000001, + "totalDistance": 334, + "coordinates": { + "coordX": 125.92088460727861, + "coordY": 58.630013924889454, + "landingPosX": -0.1808986788629484, + "landingPosY": 333.5341816436516 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 19.062719373728566, + -45.309463287968526, + 29.864520883697104, + -11.685851070109804, + 4.319459925221751, + -1.6334772896350884, + 0.44748180118678466, + -0.06669612768758428, + 0.0040127457279616075 + ], + "trajectoryPolynomialY": [ + -102.08797046921893, + 189.5351201312341, + -34.0620879795393, + 11.515773397259924, + -7.350901785315273, + 3.875522730886514, + -1.1894544419068007, + 0.18997320045079566, + -0.012299852177967545 + ], + "trajectoryPolynomialZ": [ + -28.25635526848967, + 58.572860962526775, + -11.89429981475477, + -1.2805214072478, + 0.3765172566953986, + -0.22622317196327082, + 0.09176468052052415, + -0.015889794105854612, + 0.000978817602493996 + ], + "validTimeInterval": [ + 0.6038278719112877, + 3.5545920320675055 + ], + "measuredTimeInterval": [ + 0.6038278719112877, + 3.5577095301488963 + ] + } + }, + "isHomeRun": false, + "playId": "50220d38-e751-4f7a-a948-84f911f28be7", + "timeRemaining": "3:18", + "isBonusTime": false, + "isTieBreaker": false + } + ], + "seed": 5, + "order": 0, + "isWinner": true, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 11 + } + } + ] + }, + { + "round": 2, + "numBatters": 4, + "matchups": [ + { + "topSeed": { + "started": true, + "complete": true, + "winner": false, + "player": { + "id": 596142, + "fullName": "Gary Sanchez", + "link": "/api/v1/people/596142" + }, + "topDerbyHitData": { + "launchSpeed": 115.000001, + "totalDistance": 485, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 103.000001, + "launchAngle": 40.000001, + "totalDistance": 361, + "coordinates": { + "coordX": 121.81982293611986, + "coordY": 46.749182703652934, + "landingPosX": -9.558045308984367, + "landingPosY": 360.69990246973737 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 14.976359446935366, + -48.127177334390794, + 39.20292262592414, + -21.470551620073824, + 7.552523858767389, + -1.642605135497605, + 0.21318667942174058, + -0.015108452080860686, + 0.0004494311011173917 + ], + "trajectoryPolynomialY": [ + -54.69480442683808, + 144.94498719240005, + -42.543235304238394, + 13.235340298046005, + -3.7438655041690647, + 0.8431213599390969, + -0.12393315130822442, + 0.010116744113117372, + -0.00034429244335784333 + ], + "trajectoryPolynomialZ": [ + -42.01432165129799, + 106.61535535098156, + -6.029137635166186, + -9.98415142458621, + 4.70961453498431, + -1.1975706666057804, + 0.17162583406419912, + -0.012968563444773972, + 0.00040257841330889704 + ], + "validTimeInterval": [ + 0.4414880930249713, + 6.959762629157723 + ], + "measuredTimeInterval": [ + 0.4414880930249713, + 3.4335012890313656 + ] + } + }, + "isHomeRun": false, + "playId": "bca263d0-b69f-419c-9348-e63ee1787a2a", + "timeRemaining": "--", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 112.000001, + "launchAngle": 15.000001, + "totalDistance": 295, + "coordinates": { + "coordX": 57.09079199856133, + "coordY": 95.51985100314039, + "landingPosX": -157.5620654864356, + "landingPosY": 249.18494742540372 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 28.781746405972132, + -65.99706035773615, + -4.915702325764032, + 2.821568537107654, + -0.9345116748190848, + 0.8962418914144035, + -0.5031689197671917, + 0.12207557099870835, + -0.010892100785461802 + ], + "trajectoryPolynomialY": [ + -64.8782255280748, + 169.86806365681576, + -39.4585196113056, + 17.726385462935475, + -11.709256082058507, + 6.30573424571123, + -2.1063095805158825, + 0.3783322710625158, + -0.02802957071550386 + ], + "trajectoryPolynomialZ": [ + -18.595298565115534, + 61.867095755824664, + -25.681869271216925, + 3.175634084625781, + -0.25225652889069977, + 0.03295931784110042, + -0.08274816220399091, + 0.03057837889358533, + -0.003358461815889666 + ], + "validTimeInterval": [ + 0.4323275565529837, + 2.848279704990467 + ], + "measuredTimeInterval": [ + 0.4323275565529837, + 2.8678611452653113 + ] + } + }, + "isHomeRun": false, + "playId": "79bfad89-ad49-45d5-bb6a-79e8216d127d", + "timeRemaining": "--", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 100.000001, + "launchAngle": 37.000001, + "totalDistance": 372, + "coordinates": { + "coordX": 69.28199628211595, + "coordY": 52.00227671049802, + "landingPosX": -129.68667142235284, + "landingPosY": 348.68861448779387 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 22.015334487659754, + -62.617124884778974, + 24.404718018173284, + -8.56977176567059, + 1.9309019890636432, + -0.2440862560307587, + 0.011786761809046942, + 0.0005156071548967865, + -0.00005603421029159757 + ], + "trajectoryPolynomialY": [ + -49.55767500329447, + 138.3375636926565, + -46.547405667107185, + 20.293310564359622, + -7.3930464926729424, + 1.8428397224908046, + -0.27915440852093054, + 0.02300275932449161, + -0.0007895966757189137 + ], + "trajectoryPolynomialZ": [ + -36.82644080323529, + 98.27568139224688, + -9.809467228308465, + -4.459129272888759, + 1.7386221306234508, + -0.36520636568149534, + 0.04346485665594207, + -0.002684773333150168, + 0.00006583431923263926 + ], + "validTimeInterval": [ + 0.4276812641688056, + 6.509442128426567 + ], + "measuredTimeInterval": [ + 0.4276812641688056, + 3.4248261851156796 + ] + } + }, + "isHomeRun": false, + "playId": "3b4c4f13-aef5-4785-a651-b55abd0b066a", + "timeRemaining": "--", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 95.000001, + "launchAngle": 38.000001, + "totalDistance": 327, + "coordinates": { + "coordX": 192.9389834695009, + "coordY": 78.17815525019783, + "landingPosX": 153.05711388812844, + "landingPosY": 288.837027040278 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -6.328884728969067, + 12.542473268460471, + 7.833346908942505, + 1.7739539174755432, + -3.624899070444327, + 1.5826454304019726, + -0.3284693527194393, + 0.03377072405207541, + -0.0013823526649285136 + ], + "trajectoryPolynomialY": [ + -49.625897098251116, + 133.25806959269445, + -30.42764633135783, + 1.919822519132417, + 2.3785575941744184, + -1.0635313885320419, + 0.21264606829554292, + -0.021302082393202294, + 0.000860650495172911 + ], + "trajectoryPolynomialZ": [ + -37.01965765599853, + 100.78463291341501, + -18.757686970484162, + 3.0232125257206244, + -2.418320083395869, + 1.0045969526739464, + -0.21443548764634307, + 0.022988042301175644, + -0.000979709322596537 + ], + "validTimeInterval": [ + 0.4269932669361625, + 6.114158183928308 + ], + "measuredTimeInterval": [ + 0.4269932669361625, + 3.4245228895979434 + ] + } + }, + "isHomeRun": false, + "playId": "15c49e22-e812-44fc-80e6-de9a84da2e4e", + "timeRemaining": "--", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 105.000001, + "launchAngle": 26.000001, + "totalDistance": 423, + "coordinates": { + "coordX": 170.7016565848818, + "coordY": 25.146058488231603, + "landingPosX": 102.21109118003874, + "landingPosY": 410.0958100858974 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 2.4012905719927122, + -11.168791536111362, + 14.882228101997603, + -1.662115748960572, + -1.4629502261610638, + 0.7546479394610287, + -0.15895975381881727, + 0.01627163446970633, + -0.0006635899974544095 + ], + "trajectoryPolynomialY": [ + -63.455716514023514, + 165.28877656205165, + -36.368434278222814, + 8.851606650465618, + -1.9379317583479188, + 0.3720172986535091, + -0.05384886296473011, + 0.0047397740768216285, + -0.00018129065814753766 + ], + "trajectoryPolynomialZ": [ + -29.44904348622181, + 79.35393065147848, + -11.38173311215865, + -0.8383579187802321, + -0.17079479924889895, + 0.18747608532544185, + -0.04802789057821141, + 0.005492606129044943, + -0.0002406743949191693 + ], + "validTimeInterval": [ + 0.43121842491788687, + 5.764115763785039 + ], + "measuredTimeInterval": [ + 0.43121842491788687, + 3.4289521197804573 + ] + } + }, + "isHomeRun": true, + "playId": "6638a4f4-c7b3-48db-9b7d-e661e47d4cbf", + "timeRemaining": "--", + "isBonusTime": false, + "isTieBreaker": false + } + ], + "seed": 8, + "order": 0, + "isWinner": false, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 10 + }, + "bottomSeed": { + "started": true, + "complete": true, + "winner": true, + "player": { + "id": 593934, + "fullName": "Miguel Sano", + "link": "/api/v1/people/593934" + }, + "topDerbyHitData": { + "launchSpeed": 113.000001, + "totalDistance": 491, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": true, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 113.000001, + "launchAngle": 18.000001, + "totalDistance": 386, + "coordinates": { + "coordX": 89.43077221312979, + "coordY": 39.50363268360081, + "landingPosX": -83.61615566997853, + "landingPosY": 377.26697470659815 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 18.544935782097347, + -36.902739564844445, + 4.554875083665141, + 2.31263663556803, + -4.632964049035132, + 2.820442540524032, + -0.8208158372065597, + 0.11740114854115741, + -0.006654581124376167 + ], + "trajectoryPolynomialY": [ + -86.03935164823672, + 179.59754035526635, + -26.5472277673295, + 2.35927201553885, + 0.013970542663697802, + 0.2388891419039921, + -0.13798781282366498, + 0.027033240573162746, + -0.0018590040409965558 + ], + "trajectoryPolynomialZ": [ + -27.82207977995202, + 65.31564552155137, + -9.767004339026938, + -9.032282815536798, + 7.331671713103125, + -3.1486356753149116, + 0.7541880896884382, + -0.0946541209487221, + 0.004864940532694616 + ], + "validTimeInterval": [ + 0.5278844489337585, + 4.201832813191546 + ], + "measuredTimeInterval": [ + 0.5278844489337585, + 3.5227900679788817 + ] + } + }, + "isHomeRun": false, + "playId": "0bc09647-b5e6-4f05-8b29-49f568fd4e19", + "timeRemaining": "3:55", + "isBonusTime": true, + "isTieBreaker": false + }, + { + "bonusTime": true, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 107.000001, + "launchAngle": 27.000001, + "totalDistance": 440, + "coordinates": { + "coordX": 36.23748983558308, + "coordY": 34.30344160852721, + "landingPosX": -205.2434923422342, + "landingPosY": 389.1572992434237 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 30.285226470714523, + -60.854522542285814, + 1.9475011043744848, + 1.9323414136398858, + 0.18090774579638824, + -0.47166472932212405, + 0.14224504662087617, + -0.01770524999545679, + 0.0008227127888492934 + ], + "trajectoryPolynomialY": [ + -72.63738309645412, + 164.23847995340063, + -46.850740997788165, + 19.843921898329906, + -6.793108447150615, + 1.5009785332865861, + -0.1937987634876677, + 0.012923440111212041, + -0.0003254770265698043 + ], + "trajectoryPolynomialZ": [ + -34.44271641224456, + 78.27756092734884, + -6.384213525425606, + -5.99753648104556, + 3.02924301019549, + -0.9422566761550659, + 0.17081385432116314, + -0.016420738346083093, + 0.0006495911866962578 + ], + "validTimeInterval": [ + 0.5175968763999134, + 5.608399488704569 + ], + "measuredTimeInterval": [ + 0.5175968763999134, + 3.5133391673309218 + ] + } + }, + "isHomeRun": true, + "playId": "5385e926-fc17-4b6a-9b4c-38613777a906", + "timeRemaining": "3:49", + "isBonusTime": true, + "isTieBreaker": false + }, + { + "bonusTime": true, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 90.000001, + "launchAngle": 49.000001, + "totalDistance": 278, + "coordinates": { + "coordX": 143.2727149447031, + "coordY": 84.12603561223001, + "landingPosX": 39.494353834236854, + "landingPosY": 275.23709834731676 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 6.803614378339348, + -23.589980114766902, + 27.275363748006715, + -15.200781294623393, + 5.073559245367791, + -1.0041051490297426, + 0.11464687331502917, + -0.006853550489365684, + 0.00016145075373294874 + ], + "trajectoryPolynomialY": [ + -50.53549688124736, + 116.68411688106016, + -38.226587876844846, + 15.017084038645008, + -5.12126049495556, + 1.2464414214882196, + -0.18779716590893622, + 0.015502362016703419, + -0.0005338803620378575 + ], + "trajectoryPolynomialZ": [ + -54.00743626304775, + 120.9341580229415, + -20.39176485589176, + -2.392665292025555, + 2.1453554633307874, + -0.6634409245866258, + 0.1049016737865642, + -0.008383021114236017, + 0.0002694074349601797 + ], + "validTimeInterval": [ + 0.5223993150237409, + 6.5714953843596255 + ], + "measuredTimeInterval": [ + 0.5223993150237409, + 3.5198131329815823 + ] + } + }, + "isHomeRun": false, + "playId": "012697f7-5dba-4368-8673-dad1791ee3b9", + "timeRemaining": "3:43", + "isBonusTime": true, + "isTieBreaker": false + }, + { + "bonusTime": true, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 106.000001, + "launchAngle": 32.000001, + "totalDistance": 424, + "coordinates": { + "coordX": 101.64663683994279, + "coordY": 20.789229741168015, + "landingPosX": -55.68437531541172, + "landingPosY": 420.05777244575626 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 19.93532849917222, + -48.38048772106888, + 28.523025338263977, + -12.557300498955135, + 3.2975599693169495, + -0.4833493333225733, + 0.03474764311954307, + -0.0006521538074993353, + -0.0000299581071338366 + ], + "trajectoryPolynomialY": [ + -75.65454953916127, + 161.10489869401164, + -33.890849398705825, + 4.72444110164491, + 0.22814006557059766, + -0.22229715174756132, + 0.03973692192289123, + -0.0032673405687452693, + 0.0001074539333277809 + ], + "trajectoryPolynomialZ": [ + -42.86588871069974, + 91.53186078488001, + -6.349034546897743, + -5.050422622154502, + 1.838706150298507, + -0.3770114111586068, + 0.043533857010270655, + -0.00253109351885596, + 0.00005546933813554817 + ], + "validTimeInterval": [ + 0.5362853259616839, + 6.855877000766313 + ], + "measuredTimeInterval": [ + 0.5362853259616839, + 3.5341016850287796 + ] + } + }, + "isHomeRun": true, + "playId": "e705de1a-8d5d-448c-8d5b-deff4c400bb8", + "timeRemaining": "3:27", + "isBonusTime": true, + "isTieBreaker": false + }, + { + "bonusTime": true, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 110.000001, + "launchAngle": 24.000001, + "totalDistance": 459, + "coordinates": { + "coordX": 64.63239559474923, + "coordY": 13.364763515709853, + "landingPosX": -140.31806175807338, + "landingPosY": 437.03393960171957 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 25.126398813422025, + -57.3598953970818, + 26.066043427919823, + -15.33746588603168, + 6.491269211909898, + -1.7855860037996547, + 0.2959664672581608, + -0.026720097045544587, + 0.001007660675636039 + ], + "trajectoryPolynomialY": [ + -78.72762414049646, + 167.5253572491674, + -23.83845576774435, + -2.719656836607042, + 3.914448776424236, + -1.3706401515445608, + 0.24734700922937736, + -0.02316588988368158, + 0.0008902408504869375 + ], + "trajectoryPolynomialZ": [ + -35.98612627445274, + 85.26134221463931, + -25.635837045263813, + 10.758115036302318, + -5.026017032390686, + 1.3938648584923334, + -0.22655356022612197, + 0.020025035070269735, + -0.0007426418102900153 + ], + "validTimeInterval": [ + 0.522176444135453, + 5.834434134565104 + ], + "measuredTimeInterval": [ + 0.522176444135453, + 3.516312222003153 + ] + } + }, + "isHomeRun": true, + "playId": "095e7376-2383-47af-ac6d-004b6fda89a0", + "timeRemaining": "3:16", + "isBonusTime": true, + "isTieBreaker": false + } + ], + "seed": 5, + "order": 0, + "isWinner": true, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 11 + } + }, + { + "topSeed": { + "started": true, + "complete": true, + "winner": false, + "player": { + "id": 641355, + "fullName": "Cody Bellinger", + "link": "/api/v1/people/641355" + }, + "topDerbyHitData": { + "launchSpeed": 107.000001, + "totalDistance": 433, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 101.000001, + "launchAngle": 17.000001, + "totalDistance": 270, + "coordinates": { + "coordX": 206.88794085167578, + "coordY": 118.62115638418805, + "landingPosX": 184.95164003725867, + "landingPosY": 196.36342332379473 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -40.8190128553703, + 78.67306934582832, + 2.762157150547489, + -2.190828122355813, + 1.2330231579884212, + -1.1322162870397356, + 0.54974588374982, + -0.12093408161204001, + 0.010015463351315168 + ], + "trajectoryPolynomialY": [ + -63.559336437006976, + 142.59333952385683, + -33.08693350221554, + 14.91377529916888, + -10.83093401973676, + 5.94742384464642, + -1.9513691046850774, + 0.339926661027046, + -0.02432882320540786 + ], + "trajectoryPolynomialZ": [ + -25.85711082996454, + 70.09095407046244, + -27.562988837925598, + 3.613249292244837, + -0.43692809103446634, + -0.09100625466670875, + 0.04985642789480291, + -0.007637297611794613, + 0.00036275868782898363 + ], + "validTimeInterval": [ + 0.5130773549534475, + 2.961704063937803 + ], + "measuredTimeInterval": [ + 0.5130773549534475, + 2.9838121042693047 + ] + } + }, + "isHomeRun": false, + "playId": "cac8f9d5-5a03-46ea-b24d-9b37a8bdfaed", + "timeRemaining": "3:55", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 105.000001, + "launchAngle": 23.000001, + "totalDistance": 391, + "coordinates": { + "coordX": 218.14709385484053, + "coordY": 60.32548302802883, + "landingPosX": 210.69588314000055, + "landingPosY": 329.6574629640015 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -31.79215210694984, + 64.28359515393032, + -5.43092638027875, + 2.1533645160199373, + -0.5595616086001185, + 0.015502864920676623, + 0.019178144614616848, + -0.003121727201612788, + 0.00013771709169596317 + ], + "trajectoryPolynomialY": [ + -69.4513319670641, + 150.18127091379904, + -23.403402357457605, + 0.887214315318721, + 1.5692991803151484, + -0.7007660638294355, + 0.1627790202998397, + -0.02075888968230361, + 0.001114586398384869 + ], + "trajectoryPolynomialZ": [ + -32.60782759527875, + 79.62008468514179, + -19.238375960999093, + -0.7260555675932485, + 2.250168860834686, + -1.1913757145489183, + 0.3009739742231531, + -0.03767022914015348, + 0.0018829832296735347 + ], + "validTimeInterval": [ + 0.5133374391793983, + 4.544478186249687 + ], + "measuredTimeInterval": [ + 0.5133374391793983, + 3.509694585419487 + ] + } + }, + "isHomeRun": true, + "playId": "96ac5f12-7ac4-40ab-8f51-51f7f990b47d", + "timeRemaining": "3:49", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 100.000001, + "launchAngle": 30.000001, + "totalDistance": 410, + "coordinates": { + "coordX": 152.29862338021172, + "coordY": 27.216961051960652, + "landingPosX": 60.132245594079606, + "landingPosY": 405.3606564710787 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -15.329104262191844, + 32.61753623349301, + -4.180736777960404, + -1.4816277576703234, + 0.9951552835570848, + -0.26973077455901395, + 0.04029170142125263, + -0.0032428531656441156, + 0.0001106738584745044 + ], + "trajectoryPolynomialY": [ + -66.55946204830573, + 148.41359023222694, + -30.865990098698774, + 8.844279187794513, + -2.657836102115152, + 0.6528030183541458, + -0.10375084359398133, + 0.009110823165756742, + -0.00033382712647849155 + ], + "trajectoryPolynomialZ": [ + -37.578392875146314, + 87.6522053130862, + -11.186657658240673, + -4.973616405353353, + 2.98240522540992, + -0.8965711326037518, + 0.14875956104181395, + -0.01293172575025189, + 0.0004623069384151862 + ], + "validTimeInterval": [ + 0.5058817313821967, + 6.027002947202469 + ], + "measuredTimeInterval": [ + 0.5058817313821967, + 3.482428497282754 + ] + } + }, + "isHomeRun": true, + "playId": "2f9b75ca-b012-4c07-bdd3-c5124b4a4f49", + "timeRemaining": "3:38", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 104.000001, + "launchAngle": 20.000001, + "totalDistance": 324, + "coordinates": { + "coordX": 204.07012816665423, + "coordY": 86.2607704150284, + "landingPosX": 178.5086638417319, + "landingPosY": 270.3559912420418 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -33.40542862639973, + 63.0645143024393, + 6.705902459075797, + -7.6144302574519624, + 5.954271146664167, + -3.375795314097079, + 1.0742899356837983, + -0.1732076614764447, + 0.011135769057698218 + ], + "trajectoryPolynomialY": [ + -69.8468269732927, + 155.59592034763602, + -38.52120181497758, + 21.702452255934457, + -15.039435626211082, + 7.2623580963993435, + -2.053979316871558, + 0.30586077945566725, + -0.018540996004047933 + ], + "trajectoryPolynomialZ": [ + -29.560532182701607, + 76.7618369795854, + -26.98794870448559, + 4.487347535364083, + -1.1393649895070936, + 0.13050433275335716, + 0.017274609671037236, + -0.004934279981081517, + 0.0002227502475406742 + ], + "validTimeInterval": [ + 0.5116060767175139, + 3.5802211890221782 + ], + "measuredTimeInterval": [ + 0.5116060767175139, + 3.41316260316396 + ] + } + }, + "isHomeRun": false, + "playId": "29bb5f73-d5b1-4982-8baf-c123b6ffb97d", + "timeRemaining": "3:30", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 104.000001, + "launchAngle": 28.000001, + "totalDistance": 429, + "coordinates": { + "coordX": 225.38657523829522, + "coordY": 45.615278570871055, + "landingPosX": 227.24907933700086, + "landingPosY": 363.29259338008694 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -33.438553192052304, + 77.77416845661237, + -30.51875381614577, + 20.60709673786185, + -8.92597137213046, + 2.315065552102488, + -0.3518413430864127, + 0.02897305423345071, + -0.0009989755721794107 + ], + "trajectoryPolynomialY": [ + -62.65017938181469, + 134.15557405271954, + -5.40991372768868, + -17.12023424436271, + 11.175300719857756, + -3.5088908664700353, + 0.6083907223926055, + -0.0557349751993809, + 0.0021105749722792277 + ], + "trajectoryPolynomialZ": [ + -37.93166825134946, + 96.15579433992954, + -33.9341477792921, + 15.726720543043394, + -7.161831989839775, + 1.9555135115781082, + -0.31364020465505094, + 0.027364256069585164, + -0.001001942006080205 + ], + "validTimeInterval": [ + 0.5022235450314637, + 5.739487338976742 + ], + "measuredTimeInterval": [ + 0.5022235450314637, + 3.5152882710893385 + ] + } + }, + "isHomeRun": true, + "playId": "8f543a5c-4199-46bd-8293-be593450423e", + "timeRemaining": "3:24", + "isBonusTime": false, + "isTieBreaker": false + } + ], + "seed": 3, + "order": 0, + "isWinner": false, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 12 + }, + "bottomSeed": { + "started": true, + "complete": true, + "winner": true, + "player": { + "id": 592450, + "fullName": "Aaron Judge", + "link": "/api/v1/people/592450" + }, + "topDerbyHitData": { + "launchSpeed": 119.000001, + "totalDistance": 513, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 105.000001, + "launchAngle": 29.000001, + "totalDistance": 426, + "coordinates": { + "coordX": 139.62631679478628, + "coordY": 18.527888379226795, + "landingPosX": 31.156803008303463, + "landingPosY": 425.2283675823297 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 13.114372814218697, + -43.444697720097544, + 41.04281071707479, + -19.281288475529447, + 5.224710500898421, + -0.734147074990245, + 0.03220416334052289, + 0.0033149569931678145, + -0.00031195147915253436 + ], + "trajectoryPolynomialY": [ + -62.4968612222692, + 152.101832169985, + -22.994940121767154, + 2.176688806741588, + 0.26132131980247536, + -0.09618306126583642, + 0.009526259481177435, + -0.00028690383523730193, + -0.0000033091475564405693 + ], + "trajectoryPolynomialZ": [ + -34.210074100346326, + 89.16388683389233, + -14.127901307620766, + -3.9514207943716997, + 3.000800443873732, + -1.0436482650304022, + 0.19450219465478338, + -0.018724165352765453, + 0.000735088315379683 + ], + "validTimeInterval": [ + 0.4510657591674721, + 5.711741418347126 + ], + "measuredTimeInterval": [ + 0.4510657591674721, + 3.430509963967531 + ] + } + }, + "isHomeRun": true, + "playId": "63b64622-2e0f-4da5-aa4d-e70a13849c81", + "timeRemaining": "3:55", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 110.000001, + "launchAngle": 30.000001, + "totalDistance": 414, + "coordinates": { + "coordX": 212.91401267227303, + "coordY": 45.89856878879212, + "landingPosX": 198.73035481806247, + "landingPosY": 362.6448455222562 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -6.438855985392583, + 0.37589203693297446, + 41.69433022951377, + -27.98935313163366, + 12.342443935505866, + -3.4325441624616033, + 0.5715150583912776, + -0.051859009797316176, + 0.001970599877546748 + ], + "trajectoryPolynomialY": [ + -67.11854651757018, + 169.19192555857487, + -42.584990400364006, + 13.435857096040555, + -4.409316048078815, + 1.1330751877601835, + -0.18612816500255583, + 0.016840281198274524, + -0.0006359925461689747 + ], + "trajectoryPolynomialZ": [ + -37.35413753521982, + 97.9140927536823, + -14.962470782956732, + -8.302383328428974, + 6.157675715328853, + -2.1132574754308466, + 0.3912066688981782, + -0.03757436952313326, + 0.0014739575082893504 + ], + "validTimeInterval": [ + 0.44627776378546885, + 5.637018120230749 + ], + "measuredTimeInterval": [ + 0.44627776378546885, + 3.4320086373869487 + ] + } + }, + "isHomeRun": true, + "playId": "0433e579-c8a7-4a08-b111-8abb558a0096", + "timeRemaining": "3:48", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 92.000001, + "launchAngle": 13.000001, + "totalDistance": 149, + "coordinates": { + "coordX": 75.78950191284498, + "coordY": 162.78696338414517, + "landingPosX": -114.80715012062973, + "landingPosY": 95.37756125085912 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 42.29157883125268, + -106.88514041038634, + 18.186202619040753, + -7.539912690312315, + 2.5443131365144556, + -1.011290069848018, + 0.329714696244378, + -0.06120013163934752, + 0.004442378878443006 + ], + "trajectoryPolynomialY": [ + -40.214606950293344, + 105.19483957459741, + -15.599248033457702, + -1.1429001341136806, + 0.973119885493981, + 0.6132123078123569, + -1.1009226584179632, + 0.49341254056566813, + -0.07503491784566829 + ], + "trajectoryPolynomialZ": [ + -14.916572685437222, + 57.413958473195045, + -34.94605371971769, + 5.962700042143592, + -1.9865484060587468, + 0.883173178311394, + -0.34759015190404613, + 0.0820176244124309, + -0.008323069616269082 + ], + "validTimeInterval": [ + 0.43163348048400535, + 1.7426307949371314 + ], + "measuredTimeInterval": [ + 0.43163348048400535, + 1.749253382617091 + ] + } + }, + "isHomeRun": false, + "playId": "e7138220-7af4-4807-b7a9-69899b42bfcf", + "timeRemaining": "3:43", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 109.000001, + "launchAngle": 35.000001, + "totalDistance": 419, + "coordinates": { + "coordX": 196.37091122591642, + "coordY": 35.43603827947621, + "landingPosX": 160.90427454465157, + "landingPosY": 386.5675978665909 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 0.9790565133274507, + -13.53652840102802, + 31.331119432322886, + -15.904136606663243, + 5.129699379225958, + -1.0173575816402805, + 0.1192538028928924, + -0.00749019918343824, + 0.0001924910186830438 + ], + "trajectoryPolynomialY": [ + -63.35691544027855, + 163.5749425930474, + -43.097508007624775, + 12.227503696820799, + -3.155682839084055, + 0.6553377658524232, + -0.09187917538931856, + 0.007317466631983394, + -0.0002457509563505309 + ], + "trajectoryPolynomialZ": [ + -39.79767383047185, + 101.05182024599426, + -7.070532102072456, + -8.50709788806141, + 3.8468083354227214, + -0.9446445494564603, + 0.13236689166385687, + -0.009844833791588296, + 0.00030216896665271134 + ], + "validTimeInterval": [ + 0.4422332068671005, + 6.60776360237486 + ], + "measuredTimeInterval": [ + 0.4422332068671005, + 3.4364255572972167 + ] + } + }, + "isHomeRun": true, + "playId": "b82727ea-1d7d-42ff-8fe7-fc56a9168747", + "timeRemaining": "3:29", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 115.000001, + "launchAngle": 14.000001, + "totalDistance": 289, + "coordinates": { + "coordX": 100.55972634042085, + "coordY": 80.61932440507358, + "landingPosX": -58.169614491284904, + "landingPosY": 283.2552526243542 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 12.752649074583585, + -28.51370070124987, + -1.35176747545074, + 3.6452735274208568, + -3.4150357796796444, + 1.7479129116686578, + -0.4629433206005379, + 0.0603126409937334, + -0.0029385100093522005 + ], + "trajectoryPolynomialY": [ + -75.32148777407228, + 184.3639967683167, + -28.529338019152906, + 5.334031915401769, + -1.0294391899353115, + 0.14628273948186218, + -0.032660954795757084, + 0.011250832370799267, + -0.001543651467191683 + ], + "trajectoryPolynomialZ": [ + -20.695816413796496, + 64.62986920916164, + -32.84107641594626, + 9.491763021243619, + -4.737590071789781, + 2.146467533355032, + -0.6193037933078935, + 0.09643464725216874, + -0.0061828992722838885 + ], + "validTimeInterval": [ + 0.44255098217070477, + 2.729228228264441 + ], + "measuredTimeInterval": [ + 0.44255098217070477, + 2.7306028345375735 + ] + } + }, + "isHomeRun": false, + "playId": "3b4eb848-a7da-4656-a1fa-13bd3ed9c407", + "timeRemaining": "3:22", + "isBonusTime": false, + "isTieBreaker": false + } + ], + "seed": 2, + "order": 0, + "isWinner": true, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 13 + } + } + ] + }, + { + "round": 3, + "numBatters": 2, + "matchups": [ + { + "topSeed": { + "started": true, + "complete": true, + "winner": false, + "player": { + "id": 593934, + "fullName": "Miguel Sano", + "link": "/api/v1/people/593934" + }, + "topDerbyHitData": { + "launchSpeed": 113.000001, + "totalDistance": 449, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 111.000001, + "launchAngle": 15.000001, + "totalDistance": 342, + "coordinates": { + "coordX": 78.21541596703884, + "coordY": 62.785548362510895, + "landingPosX": -109.26025674952353, + "landingPosY": 324.0324821149329 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 21.569312725891503, + -44.28930723807719, + 2.992429067100087, + -1.4322166146013893, + 1.5232981729341364, + -0.7851033014437894, + 0.19118508565688194, + -0.01928745024335872, + 0.0003768791307942121 + ], + "trajectoryPolynomialY": [ + -84.11807827725237, + 186.55901572140127, + -45.59430017431631, + 19.75639475638647, + -8.727426045498722, + 2.6698566526624363, + -0.47495055530605823, + 0.04103735261367299, + -0.0010936526470922297 + ], + "trajectoryPolynomialZ": [ + -22.725560143629494, + 60.42276928173766, + -17.697362475584168, + -2.3295802378774755, + 4.218015080010268, + -2.1965766519444774, + 0.5652216118994227, + -0.07396264178004631, + 0.003984454081644443 + ], + "validTimeInterval": [ + 0.5120402573065941, + 3.6046458481268813 + ], + "measuredTimeInterval": [ + 0.5120402573065941, + 3.5094496946721296 + ] + } + }, + "isHomeRun": false, + "playId": "2aec0aaa-c1ad-4a43-8238-8218aeda9ca9", + "timeRemaining": "3:47", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 95.000001, + "launchAngle": 35.000001, + "totalDistance": 326, + "coordinates": { + "coordX": 229.65514717515006, + "coordY": 106.56017024826282, + "landingPosX": 237.00924101284312, + "landingPosY": 223.941071398635 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -31.045410872653886, + 68.82074719996643, + -27.05639718286903, + 25.174307073558026, + -13.41424831631921, + 4.080527676067807, + -0.7120168881266281, + 0.0665610984936645, + -0.0025884792240084636 + ], + "trajectoryPolynomialY": [ + -56.486848722397234, + 113.15667409366762, + -0.3650590148986456, + -23.284117803458585, + 14.38747764937783, + -4.539143567709174, + 0.8139375182255342, + -0.07852235856655666, + 0.0031603068921493525 + ], + "trajectoryPolynomialZ": [ + -46.37322285208518, + 109.66965025557872, + -36.39549279881304, + 12.86857773352833, + -4.885281022020754, + 1.1423893011496355, + -0.15990388913334452, + 0.012528582185623404, + -0.00042489602151074145 + ], + "validTimeInterval": [ + 0.5282275047137731, + 5.520494960768431 + ], + "measuredTimeInterval": [ + 0.5282275047137731, + 3.52245668296241 + ] + } + }, + "isHomeRun": false, + "playId": "1eb5bf37-9ab1-4e06-a7af-8d45e38db724", + "timeRemaining": "3:38", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 93.000001, + "launchAngle": 36.000001, + "totalDistance": 326, + "coordinates": { + "coordX": 207.7167852726785, + "coordY": 87.78757600252399, + "landingPosX": 186.84680677516937, + "landingPosY": 266.8649245335547 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -14.55049098594951, + 15.05510911564787, + 39.33025107441131, + -33.20105411785453, + 16.838696072511883, + -5.122919996122397, + 0.9031364771445184, + -0.08505601556735895, + 0.0033128468748060574 + ], + "trajectoryPolynomialY": [ + -64.52342099159665, + 152.39074383507565, + -67.99904110382224, + 37.511684495754274, + -15.702476611273926, + 4.1085717924304435, + -0.6206562681105672, + 0.04919379930601188, + -0.0015680518766405431 + ], + "trajectoryPolynomialZ": [ + -44.55888224764817, + 98.31949204519825, + -13.40682857306249, + -7.862054941050579, + 5.198415031114488, + -1.6791573556817514, + 0.2978031883573686, + -0.027607797370613172, + 0.0010487083734722973 + ], + "validTimeInterval": [ + 0.5247829872191551, + 5.604558988195686 + ], + "measuredTimeInterval": [ + 0.5247829872191551, + 3.5196236364101714 + ] + } + }, + "isHomeRun": false, + "playId": "c7a3868f-939a-4779-adde-ed4c08865649", + "timeRemaining": "3:26", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 110.000001, + "launchAngle": 30.000001, + "totalDistance": 447, + "coordinates": { + "coordX": 61.1888412481877, + "coordY": 20.110550269815207, + "landingPosX": -148.19180680891913, + "landingPosY": 421.60958449542255 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 33.43123943130334, + -84.41314920946047, + 47.773030307153746, + -25.17489803031014, + 8.302854438822024, + -1.643426434784565, + 0.1897874277677307, + -0.011731425441735888, + 0.0002977457485332889 + ], + "trajectoryPolynomialY": [ + -73.82895328460468, + 162.9913472212023, + -35.31917760943145, + 5.852460334041308, + 0.054257073647128735, + -0.27260647584558817, + 0.059833809255140244, + -0.005727066475093282, + 0.000212486556909828 + ], + "trajectoryPolynomialZ": [ + -40.83143553447391, + 89.09641036387673, + -4.157053212132953, + -7.48928186416655, + 2.900678666428183, + -0.6311632659098046, + 0.07915791874693695, + -0.005256479715904793, + 0.00014234911268320752 + ], + "validTimeInterval": [ + 0.518026068220682, + 6.606182196150316 + ], + "measuredTimeInterval": [ + 0.518026068220682, + 3.522800510494309 + ] + } + }, + "isHomeRun": true, + "playId": "938e3485-de61-4650-8360-f043de2f3459", + "timeRemaining": "3:11", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 112.000001, + "launchAngle": 19.000001, + "totalDistance": 344, + "coordinates": { + "coordX": 19.051340444521443, + "coordY": 98.49763533598991, + "landingPosX": -244.53991257909985, + "landingPosY": 242.37619336096688 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 49.09495524454299, + -106.88984143802463, + 26.505160506244756, + -23.4490066906286, + 16.257924693042757, + -7.680966744825794, + 2.254042469346377, + -0.35902560862441374, + 0.023461276516289514 + ], + "trajectoryPolynomialY": [ + -72.02170920508509, + 176.4456061241803, + -78.71403475448616, + 54.612782529153655, + -30.432621011594996, + 10.580688105984567, + -2.122531931882594, + 0.22410988283871597, + -0.009588988950233171 + ], + "trajectoryPolynomialZ": [ + -29.597812903823755, + 75.25868405531716, + -19.701189288855907, + -6.051376321887132, + 7.692727484247774, + -4.160057520789649, + 1.2179175271108111, + -0.18654454480101465, + 0.01170007384019499 + ], + "validTimeInterval": [ + 0.5077242054340556, + 3.593974815785598 + ], + "measuredTimeInterval": [ + 0.5077242054340556, + 3.280987674749438 + ] + } + }, + "isHomeRun": false, + "playId": "6f669167-aa90-419b-a440-1b450a56e186", + "timeRemaining": "3:00", + "isBonusTime": false, + "isTieBreaker": false + } + ], + "seed": 5, + "order": 0, + "isWinner": false, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 10 + }, + "bottomSeed": { + "started": true, + "complete": true, + "winner": true, + "player": { + "id": 592450, + "fullName": "Aaron Judge", + "link": "/api/v1/people/592450" + }, + "topDerbyHitData": { + "launchSpeed": 118.000001, + "totalDistance": 480, + "coordinates": {} + }, + "hits": [ + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 118.000001, + "launchAngle": 21.000001, + "totalDistance": 410, + "coordinates": { + "coordX": 74.23757268589279, + "coordY": 32.66805245626361, + "landingPosX": -118.35566245416648, + "landingPosY": 392.89664410281296 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 19.4242277699165, + -46.404448922973955, + 10.133775376428236, + -9.380708117781566, + 6.850654247870971, + -3.1264094806255547, + 0.8451594376852418, + -0.12110845123925051, + 0.007041066762064662 + ], + "trajectoryPolynomialY": [ + -73.51820501141869, + 177.74831829890547, + -25.523700379760733, + -0.08925178625769137, + 2.8335518109098943, + -1.2951575144048826, + 0.29766062022413053, + -0.03561592050811395, + 0.001756664972866099 + ], + "trajectoryPolynomialZ": [ + -31.0051790885736, + 87.41613522822057, + -31.329336964566124, + 8.390287710419672, + -3.6326016704211272, + 1.0245066849583844, + -0.13361500689641695, + 0.0032096212148709623, + 0.0005037385228506815 + ], + "validTimeInterval": [ + 0.45279257926684924, + 4.265768043134013 + ], + "measuredTimeInterval": [ + 0.45279257926684924, + 3.4469477301675226 + ] + } + }, + "isHomeRun": true, + "playId": "654465cb-c7bc-4360-b0e4-4011828535e8", + "timeRemaining": "3:55", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 111.000001, + "launchAngle": 23.000001, + "totalDistance": 423, + "coordinates": { + "coordX": 131.010860943197, + "coordY": 19.455433265965468, + "landingPosX": 11.457417999332497, + "landingPosY": 423.10752056602274 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 7.868806380764921, + -21.795054876094166, + 8.95461543328153, + 2.004963760385575, + -2.873107767010992, + 1.093811820894821, + -0.20618324370215094, + 0.019553876893899664, + -0.0007408624102870518 + ], + "trajectoryPolynomialY": [ + -65.81205122037488, + 161.29914512709615, + -12.305349112277502, + -10.569380286847016, + 8.286383334026029, + -2.9777510627268002, + 0.5947067006237734, + -0.06315327951499684, + 0.002781242685638246 + ], + "trajectoryPolynomialZ": [ + -28.964481044467657, + 80.55979785805135, + -23.95423531102023, + 7.101469718408654, + -2.9066134819786495, + 0.656447293106385, + -0.0737564986729184, + 0.002926303148162511, + 0.00005125260005260649 + ], + "validTimeInterval": [ + 0.4368456012811172, + 4.890280049523282 + ], + "measuredTimeInterval": [ + 0.4368456012811172, + 3.4318774689634086 + ] + } + }, + "isHomeRun": true, + "playId": "5a4e3c8e-6bfa-4bde-8684-322cd66060ab", + "timeRemaining": "3:49", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 116.000001, + "launchAngle": 28.000001, + "totalDistance": 469, + "coordinates": { + "coordX": 163.43857312418197, + "coordY": 2.639889332153359, + "landingPosX": 85.60392843562953, + "landingPosY": 461.556545178728 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 6.606117093866811, + -25.766921679022627, + 31.323940309404275, + -15.543211419515147, + 5.753512609922286, + -1.4725135106498477, + 0.23629356341251417, + -0.021028124426031664, + 0.0007870615889771895 + ], + "trajectoryPolynomialY": [ + -70.72755083654583, + 179.43763322868938, + -40.67417960684654, + 11.407051242162195, + -2.6541885547731923, + 0.4131003341257032, + -0.033747465626928215, + 0.0006544996438802701, + 0.0000496497489235886 + ], + "trajectoryPolynomialZ": [ + -35.10082046765578, + 91.3695683895675, + -8.895559667413947, + -6.809483617404781, + 3.027901099317654, + -0.7194684353680533, + 0.09812493258014676, + -0.007142709529208724, + 0.00021391958905203504 + ], + "validTimeInterval": [ + 0.43934767832264826, + 5.851981345314799 + ], + "measuredTimeInterval": [ + 0.43934767832264826, + 3.4333123139337074 + ] + } + }, + "isHomeRun": true, + "playId": "479d26e2-a91e-458a-b439-84bfb04cdb65", + "timeRemaining": "3:44", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": false, + "homeRun": true, + "tieBreaker": false, + "hitData": { + "launchSpeed": 117.000001, + "launchAngle": 33.000001, + "totalDistance": 480, + "coordinates": { + "coordX": 73.29065621392812, + "coordY": 1.4189004601377633, + "landingPosX": -120.52080292658009, + "landingPosY": 464.3483568130558 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + 19.151318263161805, + -52.46827135810376, + 23.25035833434663, + -12.881714022399846, + 5.288703989249963, + -1.3676511450106135, + 0.20759594730502928, + -0.01686962299996336, + 0.000565907368623422 + ], + "trajectoryPolynomialY": [ + -65.0793398759805, + 170.01539996692455, + -42.85035010986528, + 11.581351786739114, + -2.450231710986242, + 0.3774328660903918, + -0.0383516641121727, + 0.0022638085578647426, + -0.00005891426365712123 + ], + "trajectoryPolynomialZ": [ + -40.02028761539298, + 103.89800329942541, + -8.55203835120496, + -6.468941745945446, + 2.7195556614322642, + -0.6110409732082622, + 0.0782053210815663, + -0.005310080537717338, + 0.00014786258166029026 + ], + "validTimeInterval": [ + 0.4348515227139094, + 6.870367269784164 + ], + "measuredTimeInterval": [ + 0.4348515227139094, + 3.4292042932013174 + ] + } + }, + "isHomeRun": true, + "playId": "585cb708-be3c-4679-9613-5d9ad0e7e977", + "timeRemaining": "3:32", + "isBonusTime": false, + "isTieBreaker": false + }, + { + "bonusTime": true, + "homeRun": false, + "tieBreaker": false, + "hitData": { + "launchSpeed": 106.000001, + "launchAngle": 48.000001, + "totalDistance": 335, + "coordinates": { + "coordX": 204.01521189706668, + "coordY": 80.29556231367057, + "landingPosX": 178.38309686576483, + "landingPosY": 283.99554010301165 + }, + "trajectoryData": { + "trajectoryPolynomialX": [ + -3.459879268708907, + -0.9948691939823067, + 25.828916968366187, + -14.716332105938545, + 5.266150167614627, + -1.1624916644007834, + 0.15301821154749992, + -0.010965189348958638, + 0.0003288199448217296 + ], + "trajectoryPolynomialY": [ + -49.076783010434625, + 133.06680062328073, + -42.40319061257053, + 14.200827982322712, + -4.416224725363385, + 1.0658924510124095, + -0.16300813389304003, + 0.013520162828939914, + -0.00046095015810008306 + ], + "trajectoryPolynomialZ": [ + -51.52732149990005, + 139.3101353157459, + -30.467024927785186, + 4.216909217777, + -0.739240856180158, + 0.07450957428771196, + -0.0033906125557465992, + 0.00004446349663711111, + -1.3578211171350838e-8 + ], + "validTimeInterval": [ + 0.4306836094678908, + 6.914337784272519 + ], + "measuredTimeInterval": [ + 0.4306836094678908, + 3.3647260826094287 + ] + } + }, + "isHomeRun": false, + "playId": "b5de7584-c87a-4ca9-9e17-377119e1eb01", + "timeRemaining": "3:25", + "isBonusTime": true, + "isTieBreaker": false + } + ], + "seed": 2, + "order": 0, + "isWinner": true, + "isComplete": true, + "isStarted": true, + "numHomeRuns": 11 + } + } + ] + } + ], + "players": [ + { + "id": 641355, + "fullName": "Cody Bellinger", + "link": "/api/v1/people/641355", + "firstName": "Cody", + "lastName": "Bellinger", + "primaryNumber": "35", + "birthDate": "1995-07-13", + "currentAge": 27, + "birthCity": "Scottsdale", + "birthStateProvince": "AZ", + "birthCountry": "USA", + "height": "6' 4\"", + "weight": 203, + "active": true, + "currentTeam": { + "springLeague": { + "id": 114, + "name": "Cactus League", + "link": "/api/v1/league/114", + "abbreviation": "CL" + }, + "allStarStatus": "N", + "id": 119, + "name": "Los Angeles Dodgers", + "link": "/api/v1/teams/119", + "season": 2022, + "venue": { + "id": 22, + "name": "Dodger Stadium", + "link": "/api/v1/venues/22" + }, + "springVenue": { + "id": 3809, + "link": "/api/v1/venues/3809" + }, + "teamCode": "lan", + "fileCode": "la", + "abbreviation": "LAD", + "teamName": "Dodgers", + "locationName": "Los Angeles", + "firstYearOfPlay": "1884", + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + }, + "division": { + "id": 203, + "name": "National League West", + "link": "/api/v1/divisions/203" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "name": "Major League Baseball" + }, + "shortName": "LA Dodgers", + "franchiseName": "Los Angeles", + "clubName": "Dodgers", + "active": true + }, + "primaryPosition": { + "code": "8", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "CF" + }, + "useName": "Cody", + "useLastName": "Bellinger", + "middleName": "James", + "boxscoreName": "Bellinger", + "nickName": "Belli", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2013, + "stats": [ + { + "type": { + "displayName": "metricAverages" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "launchSpeed", + "averageValue": 89.6, + "minValue": 34.5, + "maxValue": 112.8, + "unit": "MPH", + "metricId": 1003 + } + }, + "player": { + "id": 641355, + "fullName": "Cody Bellinger", + "link": "/api/v1/people/641355", + "firstName": "Cody", + "lastName": "Bellinger", + "primaryPosition": { + "code": "8", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "CF" + } + }, + "gameType": "R", + "numOccurrences": 336 + }, + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "hrDistance", + "averageValue": 404, + "minValue": 351, + "maxValue": 447, + "unit": "FT", + "metricId": 1031 + } + }, + "player": { + "id": 641355, + "fullName": "Cody Bellinger", + "link": "/api/v1/people/641355", + "firstName": "Cody", + "lastName": "Bellinger", + "primaryPosition": { + "code": "8", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "CF" + } + }, + "gameType": "R", + "numOccurrences": 38 + } + ] + }, + { + "type": { + "displayName": "season" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "gamesPlayed": 132, + "groundOuts": 84, + "airOuts": 125, + "runs": 87, + "doubles": 26, + "triples": 4, + "homeRuns": 39, + "strikeOuts": 146, + "baseOnBalls": 64, + "intentionalWalks": 13, + "hits": 128, + "hitByPitch": 1, + "avg": ".267", + "atBats": 480, + "obp": ".352", + "slg": ".581", + "ops": ".933", + "caughtStealing": 3, + "stolenBases": 10, + "stolenBasePercentage": ".769", + "groundIntoDoublePlay": 5, + "numberOfPitches": 2205, + "plateAppearances": 548, + "totalBases": 279, + "rbi": 97, + "leftOnBase": 224, + "sacBunts": 0, + "sacFlies": 3, + "babip": ".299", + "groundOutsToAirouts": "0.67", + "catchersInterference": 0, + "atBatsPerHomeRun": "12.31" + }, + "team": { + "id": 119, + "name": "Los Angeles Dodgers", + "link": "/api/v1/teams/119" + }, + "player": { + "id": 641355, + "fullName": "Cody Bellinger", + "link": "/api/v1/people/641355" + }, + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } + ] + } + ], + "mlbDebutDate": "2017-04-25", + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "L", + "description": "Left" + }, + "nameFirstLast": "Cody Bellinger", + "nameSlug": "cody-bellinger-641355", + "firstLastName": "Cody Bellinger", + "lastFirstName": "Bellinger, Cody", + "lastInitName": "Bellinger, C", + "initLastName": "C Bellinger", + "fullFMLName": "Cody James Bellinger", + "fullLFMName": "Bellinger, Cody James", + "strikeZoneTop": 3.86, + "strikeZoneBottom": 1.77 + }, + { + "id": 519317, + "fullName": "Giancarlo Stanton", + "link": "/api/v1/people/519317", + "firstName": "Giancarlo", + "lastName": "Stanton", + "primaryNumber": "27", + "birthDate": "1989-11-08", + "currentAge": 33, + "birthCity": "Panorama", + "birthStateProvince": "CA", + "birthCountry": "USA", + "height": "6' 6\"", + "weight": 245, + "active": true, + "currentTeam": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 147, + "name": "New York Yankees", + "link": "/api/v1/teams/147", + "season": 2022, + "venue": { + "id": 3313, + "name": "Yankee Stadium", + "link": "/api/v1/venues/3313" + }, + "springVenue": { + "id": 2523, + "link": "/api/v1/venues/2523" + }, + "teamCode": "nya", + "fileCode": "nyy", + "abbreviation": "NYY", + "teamName": "Yankees", + "locationName": "Bronx", + "firstYearOfPlay": "1903", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "division": { + "id": 201, + "name": "American League East", + "link": "/api/v1/divisions/201" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "name": "Major League Baseball" + }, + "shortName": "NY Yankees", + "franchiseName": "New York", + "clubName": "Yankees", + "active": true + }, + "primaryPosition": { + "code": "O", + "name": "Outfield", + "type": "Outfielder", + "abbreviation": "OF" + }, + "useName": "Giancarlo", + "useLastName": "Stanton", + "middleName": "Cruz-Michael", + "boxscoreName": "Stanton", + "nickName": "Parmigiancarlo", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2007, + "pronunciation": "Zhon-car-lo", + "stats": [ + { + "type": { + "displayName": "metricAverages" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "launchSpeed", + "averageValue": 91.9, + "minValue": 41.1, + "maxValue": 122.2, + "unit": "MPH", + "metricId": 1003 + } + }, + "player": { + "id": 519317, + "fullName": "Giancarlo Stanton", + "link": "/api/v1/people/519317", + "firstName": "Giancarlo", + "lastName": "Stanton", + "primaryPosition": { + "code": "O", + "name": "Outfield", + "type": "Outfielder", + "abbreviation": "OF" + } + }, + "gameType": "R", + "numOccurrences": 436 + }, + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "hrDistance", + "averageValue": 418, + "minValue": 346, + "maxValue": 468, + "unit": "FT", + "metricId": 1031 + } + }, + "player": { + "id": 519317, + "fullName": "Giancarlo Stanton", + "link": "/api/v1/people/519317", + "firstName": "Giancarlo", + "lastName": "Stanton", + "primaryPosition": { + "code": "O", + "name": "Outfield", + "type": "Outfielder", + "abbreviation": "OF" + } + }, + "gameType": "R", + "numOccurrences": 59 + } + ] + }, + { + "type": { + "displayName": "season" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "gamesPlayed": 159, + "groundOuts": 152, + "airOuts": 117, + "runs": 123, + "doubles": 32, + "triples": 0, + "homeRuns": 59, + "strikeOuts": 163, + "baseOnBalls": 85, + "intentionalWalks": 13, + "hits": 168, + "hitByPitch": 7, + "avg": ".281", + "atBats": 597, + "obp": ".376", + "slg": ".631", + "ops": "1.007", + "caughtStealing": 2, + "stolenBases": 2, + "stolenBasePercentage": ".500", + "groundIntoDoublePlay": 13, + "numberOfPitches": 2736, + "plateAppearances": 692, + "totalBases": 377, + "rbi": 132, + "leftOnBase": 239, + "sacBunts": 0, + "sacFlies": 3, + "babip": ".288", + "groundOutsToAirouts": "1.30", + "catchersInterference": 0, + "atBatsPerHomeRun": "10.12" + }, + "team": { + "id": 146, + "name": "Miami Marlins", + "link": "/api/v1/teams/146" + }, + "player": { + "id": 519317, + "fullName": "Giancarlo Stanton", + "link": "/api/v1/people/519317" + }, + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } + ] + } + ], + "mlbDebutDate": "2010-06-08", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Giancarlo Stanton", + "nameSlug": "giancarlo-stanton-519317", + "firstLastName": "Giancarlo Stanton", + "lastFirstName": "Stanton, Giancarlo", + "lastInitName": "Stanton, G", + "initLastName": "G Stanton", + "fullFMLName": "Giancarlo Cruz-Michael Stanton", + "fullLFMName": "Stanton, Giancarlo Cruz-Michael", + "strikeZoneTop": 3.62, + "strikeZoneBottom": 1.75 + }, + { + "id": 519058, + "fullName": "Mike Moustakas", + "link": "/api/v1/people/519058", + "firstName": "Michael", + "lastName": "Moustakas", + "primaryNumber": "9", + "birthDate": "1988-09-11", + "currentAge": 34, + "birthCity": "Los Angeles", + "birthStateProvince": "CA", + "birthCountry": "USA", + "height": "6' 0\"", + "weight": 225, + "active": true, + "currentTeam": { + "springLeague": { + "id": 114, + "name": "Cactus League", + "link": "/api/v1/league/114", + "abbreviation": "CL" + }, + "allStarStatus": "N", + "id": 113, + "name": "Cincinnati Reds", + "link": "/api/v1/teams/113", + "season": 2022, + "venue": { + "id": 2602, + "name": "Great American Ball Park", + "link": "/api/v1/venues/2602" + }, + "springVenue": { + "id": 3834, + "link": "/api/v1/venues/3834" + }, + "teamCode": "cin", + "fileCode": "cin", + "abbreviation": "CIN", + "teamName": "Reds", + "locationName": "Cincinnati", + "firstYearOfPlay": "1882", + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + }, + "division": { + "id": 205, + "name": "National League Central", + "link": "/api/v1/divisions/205" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "name": "Major League Baseball" + }, + "shortName": "Cincinnati", + "franchiseName": "Cincinnati", + "clubName": "Reds", + "active": true + }, + "primaryPosition": { + "code": "10", + "name": "Designated Hitter", + "type": "Hitter", + "abbreviation": "DH" + }, + "useName": "Mike", + "useLastName": "Moustakas", + "middleName": "Christopher", + "boxscoreName": "Moustakas", + "nickName": "Moose", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2007, + "pronunciation": "moo-STOCK-us", + "stats": [ + { + "type": { + "displayName": "metricAverages" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "launchSpeed", + "averageValue": 87.2, + "minValue": 36.8, + "maxValue": 112.9, + "unit": "MPH", + "metricId": 1003 + } + }, + "player": { + "id": 519058, + "fullName": "Mike Moustakas", + "link": "/api/v1/people/519058", + "firstName": "Mike", + "lastName": "Moustakas", + "primaryPosition": { + "code": "10", + "name": "Designated Hitter", + "type": "Hitter", + "abbreviation": "DH" + } + }, + "gameType": "R", + "numOccurrences": 466 + }, + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "hrDistance", + "averageValue": 397, + "minValue": 345, + "maxValue": 440, + "unit": "FT", + "metricId": 1031 + } + }, + "player": { + "id": 519058, + "fullName": "Mike Moustakas", + "link": "/api/v1/people/519058", + "firstName": "Mike", + "lastName": "Moustakas", + "primaryPosition": { + "code": "10", + "name": "Designated Hitter", + "type": "Hitter", + "abbreviation": "DH" + } + }, + "gameType": "R", + "numOccurrences": 38 + } + ] + }, + { + "type": { + "displayName": "season" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "gamesPlayed": 148, + "groundOuts": 124, + "airOuts": 192, + "runs": 75, + "doubles": 24, + "triples": 0, + "homeRuns": 38, + "strikeOuts": 94, + "baseOnBalls": 34, + "intentionalWalks": 7, + "hits": 151, + "hitByPitch": 3, + "avg": ".272", + "atBats": 555, + "obp": ".314", + "slg": ".521", + "ops": ".835", + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "groundIntoDoublePlay": 18, + "numberOfPitches": 2240, + "plateAppearances": 598, + "totalBases": 289, + "rbi": 85, + "leftOnBase": 191, + "sacBunts": 0, + "sacFlies": 6, + "babip": ".263", + "groundOutsToAirouts": "0.65", + "catchersInterference": 0, + "atBatsPerHomeRun": "14.60" + }, + "team": { + "id": 118, + "name": "Kansas City Royals", + "link": "/api/v1/teams/118" + }, + "player": { + "id": 519058, + "fullName": "Mike Moustakas", + "link": "/api/v1/people/519058" + }, + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } + ] + } + ], + "mlbDebutDate": "2011-06-10", + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Mike Moustakas", + "nameSlug": "mike-moustakas-519058", + "firstLastName": "Mike Moustakas", + "lastFirstName": "Moustakas, Mike", + "lastInitName": "Moustakas, M", + "initLastName": "M Moustakas", + "fullFMLName": "Michael Christopher Moustakas", + "fullLFMName": "Moustakas, Michael Christopher", + "strikeZoneTop": 3.4, + "strikeZoneBottom": 1.6 + }, + { + "id": 453568, + "fullName": "Charlie Blackmon", + "link": "/api/v1/people/453568", + "firstName": "Charles", + "lastName": "Blackmon", + "primaryNumber": "19", + "birthDate": "1986-07-01", + "currentAge": 36, + "birthCity": "Dallas", + "birthStateProvince": "TX", + "birthCountry": "USA", + "height": "6' 3\"", + "weight": 221, + "active": true, + "currentTeam": { + "springLeague": { + "id": 114, + "name": "Cactus League", + "link": "/api/v1/league/114", + "abbreviation": "CL" + }, + "allStarStatus": "N", + "id": 115, + "name": "Colorado Rockies", + "link": "/api/v1/teams/115", + "season": 2022, + "venue": { + "id": 19, + "name": "Coors Field", + "link": "/api/v1/venues/19" + }, + "springVenue": { + "id": 4249, + "link": "/api/v1/venues/4249" + }, + "teamCode": "col", + "fileCode": "col", + "abbreviation": "COL", + "teamName": "Rockies", + "locationName": "Denver", + "firstYearOfPlay": "1992", + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + }, + "division": { + "id": 203, + "name": "National League West", + "link": "/api/v1/divisions/203" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "name": "Major League Baseball" + }, + "shortName": "Colorado", + "franchiseName": "Colorado", + "clubName": "Rockies", + "active": true + }, + "primaryPosition": { + "code": "10", + "name": "Designated Hitter", + "type": "Hitter", + "abbreviation": "DH" + }, + "useName": "Charlie", + "useLastName": "Blackmon", + "middleName": "Cobb", + "boxscoreName": "Blackmon", + "nickName": "Chuck Nazty", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2008, + "stats": [ + { + "type": { + "displayName": "metricAverages" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "launchSpeed", + "averageValue": 87.2, + "minValue": 33.6, + "maxValue": 110.8, + "unit": "MPH", + "metricId": 1003 + } + }, + "player": { + "id": 453568, + "fullName": "Charlie Blackmon", + "link": "/api/v1/people/453568", + "firstName": "Charlie", + "lastName": "Blackmon", + "primaryPosition": { + "code": "10", + "name": "Designated Hitter", + "type": "Hitter", + "abbreviation": "DH" + } + }, + "gameType": "R", + "numOccurrences": 510 + }, + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "hrDistance", + "averageValue": 408, + "minValue": 312, + "maxValue": 477, + "unit": "FT", + "metricId": 1031 + } + }, + "player": { + "id": 453568, + "fullName": "Charlie Blackmon", + "link": "/api/v1/people/453568", + "firstName": "Charlie", + "lastName": "Blackmon", + "primaryPosition": { + "code": "10", + "name": "Designated Hitter", + "type": "Hitter", + "abbreviation": "DH" + } + }, + "gameType": "R", + "numOccurrences": 34 + } + ] + }, + { + "type": { + "displayName": "season" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "gamesPlayed": 159, + "groundOuts": 149, + "airOuts": 153, + "runs": 137, + "doubles": 35, + "triples": 14, + "homeRuns": 37, + "strikeOuts": 135, + "baseOnBalls": 65, + "intentionalWalks": 9, + "hits": 213, + "hitByPitch": 10, + "avg": ".331", + "atBats": 644, + "obp": ".399", + "slg": ".601", + "ops": "1.000", + "caughtStealing": 10, + "stolenBases": 14, + "stolenBasePercentage": ".583", + "groundIntoDoublePlay": 4, + "numberOfPitches": 2884, + "plateAppearances": 725, + "totalBases": 387, + "rbi": 104, + "leftOnBase": 150, + "sacBunts": 3, + "sacFlies": 3, + "babip": ".371", + "groundOutsToAirouts": "0.97", + "catchersInterference": 0, + "atBatsPerHomeRun": "17.40" + }, + "team": { + "id": 115, + "name": "Colorado Rockies", + "link": "/api/v1/teams/115" + }, + "player": { + "id": 453568, + "fullName": "Charlie Blackmon", + "link": "/api/v1/people/453568" + }, + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } + ] + } + ], + "mlbDebutDate": "2011-06-07", + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "L", + "description": "Left" + }, + "nameFirstLast": "Charlie Blackmon", + "nameSlug": "charlie-blackmon-453568", + "firstLastName": "Charlie Blackmon", + "lastFirstName": "Blackmon, Charlie", + "lastInitName": "Blackmon, C", + "initLastName": "C Blackmon", + "fullFMLName": "Charles Cobb Blackmon", + "fullLFMName": "Blackmon, Charles Cobb", + "strikeZoneTop": 3.67, + "strikeZoneBottom": 1.69 + }, + { + "id": 596142, + "fullName": "Gary Sanchez", + "link": "/api/v1/people/596142", + "firstName": "Gary", + "lastName": "Sanchez", + "primaryNumber": "24", + "birthDate": "1992-12-02", + "currentAge": 30, + "birthCity": "Santo Domingo", + "birthCountry": "Dominican Republic", + "height": "6' 2\"", + "weight": 230, + "active": true, + "currentTeam": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 142, + "name": "Minnesota Twins", + "link": "/api/v1/teams/142", + "season": 2022, + "venue": { + "id": 3312, + "name": "Target Field", + "link": "/api/v1/venues/3312" + }, + "springVenue": { + "id": 2862, + "link": "/api/v1/venues/2862" + }, + "teamCode": "min", + "fileCode": "min", + "abbreviation": "MIN", + "teamName": "Twins", + "locationName": "Minneapolis", + "firstYearOfPlay": "1901", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "division": { + "id": 202, + "name": "American League Central", + "link": "/api/v1/divisions/202" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "name": "Major League Baseball" + }, + "shortName": "Minnesota", + "franchiseName": "Minnesota", + "clubName": "Twins", + "active": true + }, + "primaryPosition": { + "code": "2", + "name": "Catcher", + "type": "Catcher", + "abbreviation": "C" + }, + "useName": "Gary", + "useLastName": "Sanchez", + "boxscoreName": "Sanchez", + "nickName": "Kraken", + "gender": "M", + "nameMatrilineal": "Herrera", + "isPlayer": true, + "isVerified": true, + "stats": [ + { + "type": { + "displayName": "metricAverages" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "launchSpeed", + "averageValue": 90.5, + "minValue": 20.8, + "maxValue": 115.7, + "unit": "MPH", + "metricId": 1003 + } + }, + "player": { + "id": 596142, + "fullName": "Gary Sanchez", + "link": "/api/v1/people/596142", + "firstName": "Gary", + "lastName": "Sanchez", + "primaryPosition": { + "code": "2", + "name": "Catcher", + "type": "Catcher", + "abbreviation": "C" + } + }, + "gameType": "R", + "numOccurrences": 356 + }, + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "hrDistance", + "averageValue": 416, + "minValue": 357, + "maxValue": 493, + "unit": "FT", + "metricId": 1031 + } + }, + "player": { + "id": 596142, + "fullName": "Gary Sanchez", + "link": "/api/v1/people/596142", + "firstName": "Gary", + "lastName": "Sanchez", + "primaryPosition": { + "code": "2", + "name": "Catcher", + "type": "Catcher", + "abbreviation": "C" + } + }, + "gameType": "R", + "numOccurrences": 32 + } + ] + }, + { + "type": { + "displayName": "season" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "gamesPlayed": 122, + "groundOuts": 115, + "airOuts": 109, + "runs": 79, + "doubles": 20, + "triples": 0, + "homeRuns": 33, + "strikeOuts": 120, + "baseOnBalls": 40, + "intentionalWalks": 1, + "hits": 131, + "hitByPitch": 10, + "avg": ".278", + "atBats": 471, + "obp": ".345", + "slg": ".531", + "ops": ".876", + "caughtStealing": 1, + "stolenBases": 2, + "stolenBasePercentage": ".667", + "groundIntoDoublePlay": 9, + "numberOfPitches": 2031, + "plateAppearances": 525, + "totalBases": 250, + "rbi": 90, + "leftOnBase": 224, + "sacBunts": 0, + "sacFlies": 4, + "babip": ".304", + "groundOutsToAirouts": "1.06", + "catchersInterference": 0, + "atBatsPerHomeRun": "14.27" + }, + "team": { + "id": 147, + "name": "New York Yankees", + "link": "/api/v1/teams/147" + }, + "player": { + "id": 596142, + "fullName": "Gary Sanchez", + "link": "/api/v1/people/596142" + }, + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } + ] + } + ], + "mlbDebutDate": "2015-10-03", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Gary Sanchez", + "nameSlug": "gary-sanchez-596142", + "firstLastName": "Gary Sanchez", + "lastFirstName": "Sanchez, Gary", + "lastInitName": "Sanchez, G", + "initLastName": "G Sanchez", + "fullFMLName": "Gary Sanchez", + "fullLFMName": "Sanchez, Gary", + "strikeZoneTop": 3.18, + "strikeZoneBottom": 1.55 + }, + { + "id": 593934, + "fullName": "Miguel Sano", + "link": "/api/v1/people/593934", + "firstName": "Miguel", + "lastName": "Sano", + "primaryNumber": "22", + "birthDate": "1993-05-11", + "currentAge": 29, + "birthCity": "San Pedro de Macoris", + "birthCountry": "Dominican Republic", + "height": "6' 4\"", + "weight": 272, + "active": true, + "currentTeam": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 142, + "name": "Minnesota Twins", + "link": "/api/v1/teams/142", + "season": 2022, + "venue": { + "id": 3312, + "name": "Target Field", + "link": "/api/v1/venues/3312" + }, + "springVenue": { + "id": 2862, + "link": "/api/v1/venues/2862" + }, + "teamCode": "min", + "fileCode": "min", + "abbreviation": "MIN", + "teamName": "Twins", + "locationName": "Minneapolis", + "firstYearOfPlay": "1901", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "division": { + "id": 202, + "name": "American League Central", + "link": "/api/v1/divisions/202" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "name": "Major League Baseball" + }, + "shortName": "Minnesota", + "franchiseName": "Minnesota", + "clubName": "Twins", + "active": true + }, + "primaryPosition": { + "code": "3", + "name": "First Base", + "type": "Infielder", + "abbreviation": "1B" + }, + "useName": "Miguel", + "useLastName": "Sanó", + "middleName": "Angel", + "boxscoreName": "Sanó", + "nickName": "Boqueton", + "gender": "M", + "nameMatrilineal": "Jean", + "isPlayer": true, + "isVerified": true, + "pronunciation": "sa-NO", + "stats": [ + { + "type": { + "displayName": "metricAverages" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "launchSpeed", + "averageValue": 91.7, + "minValue": 31.2, + "maxValue": 114.6, + "unit": "MPH", + "metricId": 1003 + } + }, + "player": { + "id": 593934, + "fullName": "Miguel Sano", + "link": "/api/v1/people/593934", + "firstName": "Miguel", + "lastName": "Sano", + "primaryPosition": { + "code": "3", + "name": "First Base", + "type": "Infielder", + "abbreviation": "1B" + } + }, + "gameType": "R", + "numOccurrences": 254 + }, + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "hrDistance", + "averageValue": 413, + "minValue": 362, + "maxValue": 469, + "unit": "FT", + "metricId": 1031 + } + }, + "player": { + "id": 593934, + "fullName": "Miguel Sano", + "link": "/api/v1/people/593934", + "firstName": "Miguel", + "lastName": "Sano", + "primaryPosition": { + "code": "3", + "name": "First Base", + "type": "Infielder", + "abbreviation": "1B" + } + }, + "gameType": "R", + "numOccurrences": 26 + } + ] + }, + { + "type": { + "displayName": "season" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "gamesPlayed": 114, + "groundOuts": 69, + "airOuts": 71, + "runs": 75, + "doubles": 15, + "triples": 2, + "homeRuns": 28, + "strikeOuts": 173, + "baseOnBalls": 54, + "intentionalWalks": 5, + "hits": 112, + "hitByPitch": 4, + "avg": ".264", + "atBats": 424, + "obp": ".352", + "slg": ".507", + "ops": ".859", + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "groundIntoDoublePlay": 12, + "numberOfPitches": 1974, + "plateAppearances": 483, + "totalBases": 215, + "rbi": 77, + "leftOnBase": 216, + "sacBunts": 0, + "sacFlies": 1, + "babip": ".375", + "groundOutsToAirouts": "0.97", + "catchersInterference": 0, + "atBatsPerHomeRun": "15.14" + }, + "team": { + "id": 142, + "name": "Minnesota Twins", + "link": "/api/v1/teams/142" + }, + "player": { + "id": 593934, + "fullName": "Miguel Sano", + "link": "/api/v1/people/593934" + }, + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } + ] + } + ], + "mlbDebutDate": "2015-07-02", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Miguel Sano", + "nameSlug": "miguel-sano-593934", + "firstLastName": "Miguel Sanó", + "lastFirstName": "Sanó, Miguel", + "lastInitName": "Sanó, M", + "initLastName": "M Sanó", + "fullFMLName": "Miguel Angel Sanó", + "fullLFMName": "Sanó, Miguel Angel", + "strikeZoneTop": 3.66, + "strikeZoneBottom": 1.79 + }, + { + "id": 571506, + "fullName": "Justin Bour", + "link": "/api/v1/people/571506", + "firstName": "Justin", + "lastName": "Bour", + "primaryNumber": "35", + "birthDate": "1988-05-28", + "currentAge": 34, + "birthCity": "Washington", + "birthStateProvince": "DC", + "birthCountry": "USA", + "height": "6' 4\"", + "weight": 270, + "active": true, + "currentTeam": { + "allStarStatus": "N", + "id": 532, + "name": "Diablos Rojos del Mexico", + "link": "/api/v1/teams/532", + "season": 2022, + "venue": { + "id": 5340, + "name": "Estadio Alfredo Harp Helu", + "link": "/api/v1/venues/5340" + }, + "teamCode": "mxo", + "fileCode": "t532", + "abbreviation": "MEX", + "teamName": "Diablos Rojos", + "locationName": "Mexico City", + "firstYearOfPlay": "1940", + "league": { + "id": 125, + "name": "Mexican League", + "link": "/api/v1/league/125" + }, + "division": { + "id": 223, + "name": "Mexican League Sur", + "link": "/api/v1/divisions/223" + }, + "sport": { + "id": 23, + "link": "/api/v1/sports/23", + "name": "Independent Leagues" + }, + "shortName": "Mexico", + "parentOrgName": "Office of the Commissioner", + "parentOrgId": 11, + "franchiseName": "Mexico", + "clubName": "Diablos Rojos", + "active": true + }, + "primaryPosition": { + "code": "3", + "name": "First Base", + "type": "Infielder", + "abbreviation": "1B" + }, + "useName": "Justin", + "useLastName": "Bour", + "middleName": "James", + "boxscoreName": "Bour", + "nickName": "JB", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2009, + "stats": [ + { + "type": { + "displayName": "metricAverages" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "launchSpeed", + "averageValue": 89.2, + "minValue": 43.7, + "maxValue": 114.8, + "unit": "MPH", + "metricId": 1003 + } + }, + "player": { + "id": 571506, + "fullName": "Justin Bour", + "link": "/api/v1/people/571506", + "firstName": "Justin", + "lastName": "Bour", + "primaryPosition": { + "code": "3", + "name": "First Base", + "type": "Infielder", + "abbreviation": "1B" + } + }, + "gameType": "R", + "numOccurrences": 285 + }, + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "hrDistance", + "averageValue": 399, + "minValue": 345, + "maxValue": 446, + "unit": "FT", + "metricId": 1031 + } + }, + "player": { + "id": 571506, + "fullName": "Justin Bour", + "link": "/api/v1/people/571506", + "firstName": "Justin", + "lastName": "Bour", + "primaryPosition": { + "code": "3", + "name": "First Base", + "type": "Infielder", + "abbreviation": "1B" + } + }, + "gameType": "R", + "numOccurrences": 23 + } + ] + }, + { + "type": { + "displayName": "season" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "gamesPlayed": 108, + "groundOuts": 99, + "airOuts": 78, + "runs": 52, + "doubles": 18, + "triples": 0, + "homeRuns": 25, + "strikeOuts": 95, + "baseOnBalls": 47, + "intentionalWalks": 7, + "hits": 109, + "hitByPitch": 1, + "avg": ".289", + "atBats": 377, + "obp": ".366", + "slg": ".536", + "ops": ".902", + "caughtStealing": 0, + "stolenBases": 1, + "stolenBasePercentage": "1.000", + "groundIntoDoublePlay": 10, + "numberOfPitches": 1744, + "plateAppearances": 429, + "totalBases": 202, + "rbi": 83, + "leftOnBase": 171, + "sacBunts": 0, + "sacFlies": 4, + "babip": ".322", + "groundOutsToAirouts": "1.27", + "catchersInterference": 0, + "atBatsPerHomeRun": "15.08" + }, + "team": { + "id": 146, + "name": "Miami Marlins", + "link": "/api/v1/teams/146" + }, + "player": { + "id": 571506, + "fullName": "Justin Bour", + "link": "/api/v1/people/571506" + }, + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } + ] + } + ], + "mlbDebutDate": "2014-06-05", + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Justin Bour", + "nameSlug": "justin-bour-571506", + "firstLastName": "Justin Bour", + "lastFirstName": "Bour, Justin", + "lastInitName": "Bour, J", + "initLastName": "J Bour", + "fullFMLName": "Justin James Bour", + "fullLFMName": "Bour, Justin James", + "strikeZoneTop": 3.549, + "strikeZoneBottom": 1.627 + }, + { + "id": 592450, + "fullName": "Aaron Judge", + "link": "/api/v1/people/592450", + "firstName": "Aaron", + "lastName": "Judge", + "primaryNumber": "99", + "birthDate": "1992-04-26", + "currentAge": 30, + "birthCity": "Linden", + "birthStateProvince": "CA", + "birthCountry": "USA", + "height": "6' 7\"", + "weight": 282, + "active": true, + "currentTeam": { + "springLeague": { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL" + }, + "allStarStatus": "N", + "id": 147, + "name": "New York Yankees", + "link": "/api/v1/teams/147", + "season": 2022, + "venue": { + "id": 3313, + "name": "Yankee Stadium", + "link": "/api/v1/venues/3313" + }, + "springVenue": { + "id": 2523, + "link": "/api/v1/venues/2523" + }, + "teamCode": "nya", + "fileCode": "nyy", + "abbreviation": "NYY", + "teamName": "Yankees", + "locationName": "Bronx", + "firstYearOfPlay": "1903", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "division": { + "id": 201, + "name": "American League East", + "link": "/api/v1/divisions/201" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "name": "Major League Baseball" + }, + "shortName": "NY Yankees", + "franchiseName": "New York", + "clubName": "Yankees", + "active": true + }, + "primaryPosition": { + "code": "9", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "RF" + }, + "useName": "Aaron", + "useLastName": "Judge", + "middleName": "James", + "boxscoreName": "Judge", + "nickName": "Baj", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2013, + "stats": [ + { + "type": { + "displayName": "metricAverages" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "launchSpeed", + "averageValue": 94.8, + "minValue": 33.7, + "maxValue": 121.1, + "unit": "MPH", + "metricId": 1003 + } + }, + "player": { + "id": 592450, + "fullName": "Aaron Judge", + "link": "/api/v1/people/592450", + "firstName": "Aaron", + "lastName": "Judge", + "primaryPosition": { + "code": "9", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "RF" + } + }, + "gameType": "R", + "numOccurrences": 338 + }, + { + "season": "2017", + "stat": { + "metric": { + "group": "hitting", + "name": "hrDistance", + "averageValue": 412, + "minValue": 337, + "maxValue": 496, + "unit": "FT", + "metricId": 1031 + } + }, + "player": { + "id": 592450, + "fullName": "Aaron Judge", + "link": "/api/v1/people/592450", + "firstName": "Aaron", + "lastName": "Judge", + "primaryPosition": { + "code": "9", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "RF" + } + }, + "gameType": "R", + "numOccurrences": 50 + } + ] + }, + { + "type": { + "displayName": "season" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2017", + "stat": { + "gamesPlayed": 155, + "groundOuts": 85, + "airOuts": 99, + "runs": 128, + "doubles": 24, + "triples": 3, + "homeRuns": 52, + "strikeOuts": 208, + "baseOnBalls": 127, + "intentionalWalks": 11, + "hits": 154, + "hitByPitch": 5, + "avg": ".284", + "atBats": 542, + "obp": ".422", + "slg": ".627", + "ops": "1.049", + "caughtStealing": 4, + "stolenBases": 9, + "stolenBasePercentage": ".692", + "groundIntoDoublePlay": 15, + "numberOfPitches": 2989, + "plateAppearances": 678, + "totalBases": 340, + "rbi": 114, + "leftOnBase": 248, + "sacBunts": 0, + "sacFlies": 4, + "babip": ".357", + "groundOutsToAirouts": "0.86", + "catchersInterference": 0, + "atBatsPerHomeRun": "10.42" + }, + "team": { + "id": 147, + "name": "New York Yankees", + "link": "/api/v1/teams/147" + }, + "player": { + "id": 592450, + "fullName": "Aaron Judge", + "link": "/api/v1/people/592450" + }, + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } + ] + } + ], + "mlbDebutDate": "2016-08-13", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Aaron Judge", + "nameSlug": "aaron-judge-592450", + "firstLastName": "Aaron Judge", + "lastFirstName": "Judge, Aaron", + "lastInitName": "Judge, A", + "initLastName": "A Judge", + "fullFMLName": "Aaron James Judge", + "fullLFMName": "Judge, Aaron James", + "strikeZoneTop": 3.93, + "strikeZoneBottom": 1.84 + } + ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/leagues/league.json b/tests/mock_tests/mock_json/leagues/league.json new file mode 100644 index 00000000..133458f9 --- /dev/null +++ b/tests/mock_tests/mock_json/leagues/league.json @@ -0,0 +1,50 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "leagues": [ + { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103", + "abbreviation": "AL", + "nameShort": "American", + "seasonState": "offseason", + "hasWildCard": true, + "hasSplitSeason": false, + "numGames": 162, + "hasPlayoffPoints": false, + "numTeams": 15, + "numWildcardTeams": 3, + "seasonDateInfo": { + "seasonId": "2022", + "preSeasonStartDate": "2022-01-01", + "preSeasonEndDate": "2022-03-16", + "seasonStartDate": "2022-03-17", + "springStartDate": "2022-03-17", + "springEndDate": "2022-04-06", + "regularSeasonStartDate": "2022-04-07", + "lastDate1stHalf": "2022-07-17", + "allStarDate": "2022-07-19", + "firstDate2ndHalf": "2022-07-21", + "regularSeasonEndDate": "2022-10-05", + "postSeasonStartDate": "2022-10-07", + "postSeasonEndDate": "2022-11-05", + "seasonEndDate": "2022-11-05", + "offseasonStartDate": "2022-11-06", + "offSeasonEndDate": "2022-12-31", + "seasonLevelGamedayType": "P", + "gameLevelGamedayType": "P", + "qualifierPlateAppearances": 3.1, + "qualifierOutsPitched": 3 + }, + "season": "2022", + "orgCode": "AL", + "conferencesInUse": false, + "divisionsInUse": true, + "sport": { + "id": 1, + "link": "/api/v1/sports/1" + }, + "sortOrder": 21, + "active": true + }] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/leagues/leagues.json b/tests/mock_tests/mock_json/leagues/leagues.json new file mode 100644 index 00000000..1f7d0382 --- /dev/null +++ b/tests/mock_tests/mock_json/leagues/leagues.json @@ -0,0 +1,193 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "leagues": [ + { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103", + "abbreviation": "AL", + "nameShort": "American", + "seasonState": "offseason", + "hasWildCard": true, + "hasSplitSeason": false, + "numGames": 162, + "hasPlayoffPoints": false, + "numTeams": 15, + "numWildcardTeams": 3, + "seasonDateInfo": { + "seasonId": "2022", + "preSeasonStartDate": "2022-01-01", + "preSeasonEndDate": "2022-03-16", + "seasonStartDate": "2022-03-17", + "springStartDate": "2022-03-17", + "springEndDate": "2022-04-06", + "regularSeasonStartDate": "2022-04-07", + "lastDate1stHalf": "2022-07-17", + "allStarDate": "2022-07-19", + "firstDate2ndHalf": "2022-07-21", + "regularSeasonEndDate": "2022-10-05", + "postSeasonStartDate": "2022-10-07", + "postSeasonEndDate": "2022-11-05", + "seasonEndDate": "2022-11-05", + "offseasonStartDate": "2022-11-06", + "offSeasonEndDate": "2022-12-31", + "seasonLevelGamedayType": "P", + "gameLevelGamedayType": "P", + "qualifierPlateAppearances": 3.1, + "qualifierOutsPitched": 3 + }, + "season": "2022", + "orgCode": "AL", + "conferencesInUse": false, + "divisionsInUse": true, + "sport": { + "id": 1, + "link": "/api/v1/sports/1" + }, + "sortOrder": 21, + "active": true + }, + { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104", + "abbreviation": "NL", + "nameShort": "National", + "seasonState": "offseason", + "hasWildCard": true, + "hasSplitSeason": false, + "numGames": 162, + "hasPlayoffPoints": false, + "numTeams": 15, + "numWildcardTeams": 3, + "seasonDateInfo": { + "seasonId": "2022", + "preSeasonStartDate": "2022-01-01", + "preSeasonEndDate": "2022-03-16", + "seasonStartDate": "2022-03-17", + "springStartDate": "2022-03-17", + "springEndDate": "2022-04-06", + "regularSeasonStartDate": "2022-04-07", + "lastDate1stHalf": "2022-07-17", + "allStarDate": "2022-07-19", + "firstDate2ndHalf": "2022-07-21", + "regularSeasonEndDate": "2022-10-05", + "postSeasonStartDate": "2022-10-07", + "postSeasonEndDate": "2022-11-05", + "seasonEndDate": "2022-11-05", + "offseasonStartDate": "2022-11-06", + "offSeasonEndDate": "2022-12-31", + "seasonLevelGamedayType": "P", + "gameLevelGamedayType": "P", + "qualifierPlateAppearances": 3.1, + "qualifierOutsPitched": 3 + }, + "season": "2022", + "orgCode": "NL", + "conferencesInUse": false, + "divisionsInUse": true, + "sport": { + "id": 1, + "link": "/api/v1/sports/1" + }, + "sortOrder": 31, + "active": true + }, + { + "id": 114, + "name": "Cactus League", + "link": "/api/v1/league/114", + "abbreviation": "CL", + "nameShort": "Cactus", + "seasonState": "offseason", + "hasWildCard": false, + "hasSplitSeason": false, + "hasPlayoffPoints": false, + "seasonDateInfo": { + "seasonId": "2022", + "preSeasonStartDate": "2022-03-17", + "preSeasonEndDate": "2022-04-06", + "seasonStartDate": "2022-03-17", + "springStartDate": "2022-03-17", + "springEndDate": "2022-04-06", + "offSeasonEndDate": "2022-12-31", + "seasonLevelGamedayType": "F", + "gameLevelGamedayType": "F" + }, + "season": "2022", + "orgCode": "CL", + "conferencesInUse": false, + "divisionsInUse": false, + "sortOrder": 51, + "active": true + }, + { + "id": 115, + "name": "Grapefruit League", + "link": "/api/v1/league/115", + "abbreviation": "GL", + "nameShort": "Grapefruit", + "seasonState": "offseason", + "hasWildCard": false, + "hasSplitSeason": false, + "hasPlayoffPoints": false, + "seasonDateInfo": { + "seasonId": "2022", + "preSeasonStartDate": "2022-03-17", + "preSeasonEndDate": "2022-04-06", + "seasonStartDate": "2022-03-17", + "springStartDate": "2022-03-17", + "springEndDate": "2022-04-06", + "offSeasonEndDate": "2022-12-31", + "seasonLevelGamedayType": "F", + "gameLevelGamedayType": "F" + }, + "season": "2022", + "orgCode": "GL", + "conferencesInUse": false, + "divisionsInUse": false, + "sortOrder": 52, + "active": true + }, + { + "id": 117, + "name": "International League", + "link": "/api/v1/league/117", + "abbreviation": "INT", + "nameShort": "International", + "seasonState": "offseason", + "hasWildCard": false, + "hasSplitSeason": false, + "numGames": 150, + "hasPlayoffPoints": false, + "numTeams": 20, + "seasonDateInfo": { + "seasonId": "2022", + "preSeasonStartDate": "2022-01-01", + "preSeasonEndDate": "2022-03-15", + "seasonStartDate": "2022-04-05", + "regularSeasonStartDate": "2022-04-05", + "regularSeasonEndDate": "2022-09-28", + "postSeasonStartDate": "2022-09-30", + "postSeasonEndDate": "2022-10-01", + "seasonEndDate": "2022-10-01", + "offseasonStartDate": "2022-10-02", + "offSeasonEndDate": "2022-12-31", + "seasonLevelGamedayType": "Y", + "gameLevelGamedayType": "Y", + "qualifierPlateAppearances": 2.7, + "qualifierOutsPitched": 2.4 + }, + "season": "2022", + "orgCode": "INT", + "conferencesInUse": false, + "divisionsInUse": true, + "sport": { + "id": 11, + "link": "/api/v1/sports/11" + }, + "sortOrder": 111, + "active": true + } +] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/people/person.json b/tests/mock_tests/mock_json/people/person.json new file mode 100644 index 00000000..adc68840 --- /dev/null +++ b/tests/mock_tests/mock_json/people/person.json @@ -0,0 +1,53 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "people": [ + { + "id": 676265, + "fullName": "Cory Abbott", + "link": "/api/v1/people/676265", + "firstName": "Cory", + "lastName": "Abbott", + "primaryNumber": "77", + "birthDate": "1995-09-20", + "currentAge": 27, + "birthCity": "San Diego", + "birthStateProvince": "CA", + "birthCountry": "USA", + "height": "6' 1\"", + "weight": 210, + "active": true, + "primaryPosition": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "useName": "Cory", + "middleName": "James", + "boxscoreName": "Abbott", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2017, + "mlbDebutDate": "2021-06-05", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Cory Abbott", + "nameSlug": "cory-abbott-676265", + "firstLastName": "Cory Abbott", + "lastFirstName": "Abbott, Cory", + "lastInitName": "Abbott, C", + "initLastName": "C Abbott", + "fullFMLName": "Cory James Abbott", + "fullLFMName": "Abbott, Cory James", + "strikeZoneTop": 3.411, + "strikeZoneBottom": 1.565 + } + ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/people/players.json b/tests/mock_tests/mock_json/people/players.json new file mode 100644 index 00000000..deddac1f --- /dev/null +++ b/tests/mock_tests/mock_json/people/players.json @@ -0,0 +1,573 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "people": [ + { + "id": 676265, + "fullName": "Cory Abbott", + "link": "/api/v1/people/676265", + "firstName": "Cory", + "lastName": "Abbott", + "primaryNumber": "77", + "birthDate": "1995-09-20", + "currentAge": 27, + "birthCity": "San Diego", + "birthStateProvince": "CA", + "birthCountry": "USA", + "height": "6' 1\"", + "weight": 210, + "active": true, + "currentTeam": { + "id": 120, + "link": "/api/v1/teams/120" + }, + "primaryPosition": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "useName": "Cory", + "middleName": "James", + "boxscoreName": "Abbott", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2017, + "mlbDebutDate": "2021-06-05", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Cory Abbott", + "nameSlug": "cory-abbott-676265", + "firstLastName": "Cory Abbott", + "lastFirstName": "Abbott, Cory", + "lastInitName": "Abbott, C", + "initLastName": "C Abbott", + "fullFMLName": "Cory James Abbott", + "fullLFMName": "Abbott, Cory James", + "strikeZoneTop": 3.411, + "strikeZoneBottom": 1.565 + }, + { + "id": 682928, + "fullName": "CJ Abrams", + "link": "/api/v1/people/682928", + "firstName": "Paul", + "lastName": "Abrams", + "primaryNumber": "5", + "birthDate": "2000-10-03", + "currentAge": 22, + "birthCity": "Alpharetta", + "birthStateProvince": "GA", + "birthCountry": "USA", + "height": "6' 2\"", + "weight": 185, + "active": true, + "currentTeam": { + "id": 120, + "link": "/api/v1/teams/120" + }, + "primaryPosition": { + "code": "6", + "name": "Shortstop", + "type": "Infielder", + "abbreviation": "SS" + }, + "useName": "CJ", + "middleName": "Christopher", + "boxscoreName": "Abrams", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2019, + "mlbDebutDate": "2022-04-08", + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "CJ Abrams", + "nameSlug": "cj-abrams-682928", + "firstLastName": "CJ Abrams", + "lastFirstName": "Abrams, CJ", + "lastInitName": "Abrams, C", + "initLastName": "C Abrams", + "fullFMLName": "Paul Christopher Abrams", + "fullLFMName": "Abrams, Paul Christopher", + "strikeZoneTop": 3.53, + "strikeZoneBottom": 1.62 + }, + { + "id": 656061, + "fullName": "Albert Abreu", + "link": "/api/v1/people/656061", + "firstName": "Albert", + "lastName": "Abreu", + "primaryNumber": "84", + "birthDate": "1995-09-26", + "currentAge": 27, + "birthCity": "Guayubin", + "birthCountry": "Dominican Republic", + "height": "6' 2\"", + "weight": 190, + "active": true, + "currentTeam": { + "id": 147, + "link": "/api/v1/teams/147" + }, + "primaryPosition": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "useName": "Albert", + "middleName": "Enmanuel", + "boxscoreName": "Abreu, A", + "gender": "M", + "nameMatrilineal": "Dias", + "isPlayer": true, + "isVerified": true, + "mlbDebutDate": "2020-08-08", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Albert Abreu", + "nameSlug": "albert-abreu-656061", + "firstLastName": "Albert Abreu", + "lastFirstName": "Abreu, Albert", + "lastInitName": "Abreu, A", + "initLastName": "A Abreu", + "fullFMLName": "Albert Enmanuel Abreu", + "fullLFMName": "Abreu, Albert Enmanuel", + "strikeZoneTop": 3.467, + "strikeZoneBottom": 1.589 + }, + { + "id": 650556, + "fullName": "Bryan Abreu", + "link": "/api/v1/people/650556", + "firstName": "Bryan", + "lastName": "Abreu", + "primaryNumber": "52", + "birthDate": "1997-04-22", + "currentAge": 25, + "birthCity": "Santo Domingo Centro", + "birthCountry": "Dominican Republic", + "height": "6' 1\"", + "weight": 225, + "active": true, + "currentTeam": { + "id": 117, + "link": "/api/v1/teams/117" + }, + "primaryPosition": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "useName": "Bryan", + "middleName": "Enrique", + "boxscoreName": "Abreu, B", + "gender": "M", + "nameMatrilineal": "Jimenez", + "isPlayer": true, + "isVerified": true, + "mlbDebutDate": "2019-07-31", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Bryan Abreu", + "nameSlug": "bryan-abreu-650556", + "firstLastName": "Bryan Abreu", + "lastFirstName": "Abreu, Bryan", + "lastInitName": "Abreu, B", + "initLastName": "B Abreu", + "fullFMLName": "Bryan Enrique Abreu", + "fullLFMName": "Abreu, Bryan Enrique", + "strikeZoneTop": 3.411, + "strikeZoneBottom": 1.565 + }, + { + "id": 547989, + "fullName": "Jose Abreu", + "link": "/api/v1/people/547989", + "firstName": "Jose", + "lastName": "Abreu", + "primaryNumber": "79", + "birthDate": "1987-01-29", + "currentAge": 35, + "birthCity": "Cienfuegos", + "birthCountry": "Cuba", + "height": "6' 3\"", + "weight": 235, + "active": true, + "currentTeam": { + "id": 145, + "name": "Chicago White Sox", + "link": "/api/v1/teams/145" + }, + "primaryPosition": { + "code": "3", + "name": "First Base", + "type": "Infielder", + "abbreviation": "1B" + }, + "useName": "Jose", + "middleName": "Dariel", + "boxscoreName": "Abreu, J", + "nickName": "Mal Tiempo", + "gender": "M", + "nameMatrilineal": "Correa", + "isPlayer": true, + "isVerified": true, + "pronunciation": "uh-BRAY-you", + "mlbDebutDate": "2014-03-31", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Jose Abreu", + "nameSlug": "jose-abreu-547989", + "firstLastName": "José Abreu", + "lastFirstName": "Abreu, José", + "lastInitName": "Abreu, J", + "initLastName": "J Abreu", + "fullFMLName": "José Dariel Abreu", + "fullLFMName": "Abreu, José Dariel", + "strikeZoneTop": 3.43, + "strikeZoneBottom": 1.57 + }, + { + "id": 677800, + "fullName": "Wilyer Abreu", + "link": "/api/v1/people/677800", + "firstName": "Wilyer", + "lastName": "Abreu", + "birthDate": "1999-06-24", + "currentAge": 23, + "birthCity": "Maracaibo", + "birthCountry": "Venezuela", + "height": "6' 0\"", + "weight": 217, + "active": true, + "currentTeam": { + "id": 111, + "link": "/api/v1/teams/111" + }, + "primaryPosition": { + "code": "9", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "RF" + }, + "useName": "Wilyer", + "middleName": "David", + "boxscoreName": "Abreu", + "gender": "M", + "nameMatrilineal": "Villalobos", + "isPlayer": true, + "isVerified": true, + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "L", + "description": "Left" + }, + "nameFirstLast": "Wilyer Abreu", + "nameSlug": "wilyer-abreu-677800", + "firstLastName": "Wilyer Abreu", + "lastFirstName": "Abreu, Wilyer", + "lastInitName": "Abreu, W", + "initLastName": "W Abreu", + "fullFMLName": "Wilyer David Abreu", + "fullLFMName": "Abreu, Wilyer David", + "strikeZoneTop": 3.371, + "strikeZoneBottom": 1.535 + }, + { + "id": 642758, + "fullName": "Domingo Acevedo", + "link": "/api/v1/people/642758", + "firstName": "Domingo", + "lastName": "Acevedo", + "primaryNumber": "68", + "birthDate": "1994-03-06", + "currentAge": 28, + "birthCity": "Villa Los Almacigos", + "birthCountry": "Dominican Republic", + "height": "6' 7\"", + "weight": 240, + "active": true, + "currentTeam": { + "id": 133, + "link": "/api/v1/teams/133" + }, + "primaryPosition": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "useName": "Domingo", + "middleName": "Antonio", + "boxscoreName": "Acevedo", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "mlbDebutDate": "2021-06-21", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Domingo Acevedo", + "nameSlug": "domingo-acevedo-642758", + "firstLastName": "Domingo Acevedo", + "lastFirstName": "Acevedo, Domingo", + "lastInitName": "Acevedo, D", + "initLastName": "D Acevedo", + "fullFMLName": "Domingo Antonio Acevedo", + "fullLFMName": "Acevedo, Domingo Antonio", + "strikeZoneTop": 3.756, + "strikeZoneBottom": 1.746 + }, + { + "id": 682668, + "fullName": "Luisangel Acuna", + "link": "/api/v1/people/682668", + "firstName": "Luisangel", + "lastName": "Acuna", + "birthDate": "2002-03-12", + "currentAge": 20, + "birthCity": "Caracas", + "birthCountry": "Venezuela", + "height": "5' 10\"", + "weight": 181, + "active": true, + "currentTeam": { + "id": 140, + "link": "/api/v1/teams/140" + }, + "primaryPosition": { + "code": "6", + "name": "Shortstop", + "type": "Infielder", + "abbreviation": "SS" + }, + "useName": "Luisangel", + "middleName": "Jose", + "boxscoreName": "Acuña", + "nickName": "Jose", + "gender": "M", + "nameMatrilineal": "Cartaya", + "isPlayer": true, + "isVerified": false, + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Luisangel Acuna", + "nameSlug": "luisangel-acuna-682668", + "firstLastName": "Luisangel Acuña", + "lastFirstName": "Acuña, Luisangel", + "lastInitName": "Acuña, L", + "initLastName": "Acuña", + "fullFMLName": "Luisangel Jose Acuña", + "fullLFMName": "Acuña, Luisangel Jose", + "strikeZoneTop": 3.301, + "strikeZoneBottom": 1.504 + }, + { + "id": 660670, + "fullName": "Ronald Acuna Jr.", + "link": "/api/v1/people/660670", + "firstName": "Ronald", + "lastName": "Acuna", + "primaryNumber": "13", + "birthDate": "1997-12-18", + "currentAge": 24, + "birthCity": "La Guaira", + "birthCountry": "Venezuela", + "height": "6' 0\"", + "weight": 205, + "active": true, + "currentTeam": { + "id": 144, + "link": "/api/v1/teams/144" + }, + "primaryPosition": { + "code": "9", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "RF" + }, + "useName": "Ronald", + "middleName": "Jose", + "boxscoreName": "Acuña Jr.", + "nickName": "El De La Sabana", + "gender": "M", + "nameMatrilineal": "Blanco", + "isPlayer": true, + "isVerified": true, + "pronunciation": "ah-cuhn-YA", + "mlbDebutDate": "2018-04-25", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Ronald Acuna Jr.", + "nameTitle": "Jr.", + "nameSlug": "ronald-acuna-jr-660670", + "firstLastName": "Ronald Acuña Jr.", + "lastFirstName": "Acuña Jr., Ronald", + "lastInitName": "Acuña Jr., R", + "initLastName": "R Acuña", + "fullFMLName": "Ronald Jose Acuña Jr.", + "fullLFMName": "Acuña Jr., Ronald Jose", + "strikeZoneTop": 3.47, + "strikeZoneBottom": 1.57 + }, + { + "id": 592094, + "fullName": "Jason Adam", + "link": "/api/v1/people/592094", + "firstName": "Jason", + "lastName": "Adam", + "primaryNumber": "47", + "birthDate": "1991-08-04", + "currentAge": 31, + "birthCity": "Omaha", + "birthStateProvince": "NE", + "birthCountry": "USA", + "height": "6' 3\"", + "weight": 229, + "active": true, + "currentTeam": { + "id": 139, + "link": "/api/v1/teams/139" + }, + "primaryPosition": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "useName": "Jason", + "middleName": "Kendall", + "boxscoreName": "Adam", + "gender": "M", + "isPlayer": true, + "isVerified": true, + "draftYear": 2010, + "mlbDebutDate": "2018-05-05", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Jason Adam", + "nameSlug": "jason-adam-592094", + "firstLastName": "Jason Adam", + "lastFirstName": "Adam, Jason", + "lastInitName": "Adam, J", + "initLastName": "J Adam", + "fullFMLName": "Jason Kendall Adam", + "fullLFMName": "Adam, Jason Kendall", + "strikeZoneTop": 3.49, + "strikeZoneBottom": 1.601 + }, + { + "id": 642715, + "fullName": "Willy Adames", + "link": "/api/v1/people/642715", + "firstName": "Willy", + "lastName": "Adames", + "primaryNumber": "27", + "birthDate": "1995-09-02", + "currentAge": 27, + "birthCity": "Santiago", + "birthCountry": "Dominican Republic", + "height": "6' 0\"", + "weight": 210, + "active": true, + "currentTeam": { + "id": 158, + "link": "/api/v1/teams/158" + }, + "primaryPosition": { + "code": "6", + "name": "Shortstop", + "type": "Infielder", + "abbreviation": "SS" + }, + "useName": "Willy", + "middleName": "Rafael", + "boxscoreName": "Adames", + "nickName": "The Kid", + "gender": "M", + "nameMatrilineal": "Luna", + "isPlayer": true, + "isVerified": true, + "pronunciation": "ah-DAH-mes", + "mlbDebutDate": "2018-05-22", + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + }, + "nameFirstLast": "Willy Adames", + "nameSlug": "willy-adames-642715", + "firstLastName": "Willy Adames", + "lastFirstName": "Adames, Willy", + "lastInitName": "Adames, W", + "initLastName": "W Adames", + "fullFMLName": "Willy Rafael Adames", + "fullLFMName": "Adames, Willy Rafael", + "strikeZoneTop": 3.36, + "strikeZoneBottom": 1.67 + } + ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/response/error_500.json b/tests/mock_tests/mock_json/response/error_500.json new file mode 100644 index 00000000..6bda141a --- /dev/null +++ b/tests/mock_tests/mock_json/response/error_500.json @@ -0,0 +1,6 @@ +{ + "messageNumber": 1, + "message": "Internal error occurred", + "timestamp": "2022-11-22T19:11:54.301267Z", + "traceId": "c0587282e9dc12cf7d65306edc0adc08" +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/response/not_found_404.json b/tests/mock_tests/mock_json/response/not_found_404.json new file mode 100644 index 00000000..1cce98dd --- /dev/null +++ b/tests/mock_tests/mock_json/response/not_found_404.json @@ -0,0 +1,6 @@ +{ + "messageNumber": 10, + "message": "Object not found", + "timestamp": "2022-11-22T19:02:30.879507Z", + "traceId": null +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/schedule/schedule_date.json b/tests/mock_tests/mock_json/schedule/schedule_date.json new file mode 100644 index 00000000..19db1c5a --- /dev/null +++ b/tests/mock_tests/mock_json/schedule/schedule_date.json @@ -0,0 +1,707 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "totalItems": 9, + "totalEvents": 0, + "totalGames": 9, + "totalGamesInProgress": 0, + "dates": [ + { + "date": "2022-04-07", + "totalItems": 9, + "totalEvents": 0, + "totalGames": 9, + "totalGamesInProgress": 0, + "games": [ + { + "gamePk": 663178, + "link": "/api/v1.1/game/663178/feed/live", + "gameType": "R", + "season": "2022", + "gameDate": "2022-04-07T18:20:00Z", + "officialDate": "2022-04-07", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 0, + "losses": 1, + "pct": ".000" + }, + "score": 4, + "team": { + "id": 158, + "name": "Milwaukee Brewers", + "link": "/api/v1/teams/158" + }, + "isWinner": false, + "splitSquad": false, + "seriesNumber": 1 + }, + "home": { + "leagueRecord": { + "wins": 1, + "losses": 0, + "pct": "1.000" + }, + "score": 5, + "team": { + "id": 112, + "name": "Chicago Cubs", + "link": "/api/v1/teams/112" + }, + "isWinner": true, + "splitSquad": false, + "seriesNumber": 1 + } + }, + "venue": { + "id": 17, + "name": "Wrigley Field", + "link": "/api/v1/venues/17" + }, + "content": { + "link": "/api/v1/game/663178/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-663178-2022-04-07", + "seasonDisplay": "2022", + "dayNight": "day", + "description": "Cubs home opener", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 3, + "seriesGameNumber": 1, + "seriesDescription": "Regular Season", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 662766, + "link": "/api/v1.1/game/662766/feed/live", + "gameType": "R", + "season": "2022", + "gameDate": "2022-04-07T20:10:00Z", + "officialDate": "2022-04-07", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 0, + "losses": 1, + "pct": ".000" + }, + "score": 1, + "team": { + "id": 114, + "name": "Cleveland Guardians", + "link": "/api/v1/teams/114" + }, + "isWinner": false, + "splitSquad": false, + "seriesNumber": 1 + }, + "home": { + "leagueRecord": { + "wins": 1, + "losses": 0, + "pct": "1.000" + }, + "score": 3, + "team": { + "id": 118, + "name": "Kansas City Royals", + "link": "/api/v1/teams/118" + }, + "isWinner": true, + "splitSquad": false, + "seriesNumber": 1 + } + }, + "venue": { + "id": 7, + "name": "Kauffman Stadium", + "link": "/api/v1/venues/7" + }, + "content": { + "link": "/api/v1/game/662766/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-662766-2022-04-07", + "seasonDisplay": "2022", + "dayNight": "day", + "description": "Royals home opener", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 4, + "seriesGameNumber": 1, + "seriesDescription": "Regular Season", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 662021, + "link": "/api/v1.1/game/662021/feed/live", + "gameType": "R", + "season": "2022", + "gameDate": "2022-04-07T20:15:00Z", + "officialDate": "2022-04-07", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 0, + "losses": 1, + "pct": ".000" + }, + "score": 0, + "team": { + "id": 134, + "name": "Pittsburgh Pirates", + "link": "/api/v1/teams/134" + }, + "isWinner": false, + "splitSquad": false, + "seriesNumber": 1 + }, + "home": { + "leagueRecord": { + "wins": 1, + "losses": 0, + "pct": "1.000" + }, + "score": 9, + "team": { + "id": 138, + "name": "St. Louis Cardinals", + "link": "/api/v1/teams/138" + }, + "isWinner": true, + "splitSquad": false, + "seriesNumber": 1 + } + }, + "venue": { + "id": 2889, + "name": "Busch Stadium", + "link": "/api/v1/venues/2889" + }, + "content": { + "link": "/api/v1/game/662021/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-662021-2022-04-07", + "seasonDisplay": "2022", + "dayNight": "day", + "description": "Cardinals home opener", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 3, + "seriesGameNumber": 1, + "seriesDescription": "Regular Season", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 662571, + "link": "/api/v1.1/game/662571/feed/live", + "gameType": "R", + "season": "2022", + "gameDate": "2022-04-07T23:05:00Z", + "officialDate": "2022-04-07", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 1, + "losses": 0, + "pct": "1.000" + }, + "score": 5, + "team": { + "id": 121, + "name": "New York Mets", + "link": "/api/v1/teams/121" + }, + "isWinner": true, + "splitSquad": false, + "seriesNumber": 1 + }, + "home": { + "leagueRecord": { + "wins": 0, + "losses": 1, + "pct": ".000" + }, + "score": 1, + "team": { + "id": 120, + "name": "Washington Nationals", + "link": "/api/v1/teams/120" + }, + "isWinner": false, + "splitSquad": false, + "seriesNumber": 1 + } + }, + "venue": { + "id": 3309, + "name": "Nationals Park", + "link": "/api/v1/venues/3309" + }, + "content": { + "link": "/api/v1/game/662571/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-662571-2022-04-07", + "seasonDisplay": "2022", + "dayNight": "night", + "description": "Nationals home opener", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 4, + "seriesGameNumber": 1, + "seriesDescription": "Regular Season", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 661577, + "link": "/api/v1.1/game/661577/feed/live", + "gameType": "R", + "season": "2022", + "gameDate": "2022-04-08T00:08:00Z", + "officialDate": "2022-04-07", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 1, + "losses": 0, + "pct": "1.000" + }, + "score": 6, + "team": { + "id": 113, + "name": "Cincinnati Reds", + "link": "/api/v1/teams/113" + }, + "isWinner": true, + "splitSquad": false, + "seriesNumber": 1 + }, + "home": { + "leagueRecord": { + "wins": 0, + "losses": 1, + "pct": ".000" + }, + "score": 3, + "team": { + "id": 144, + "name": "Atlanta Braves", + "link": "/api/v1/teams/144" + }, + "isWinner": false, + "splitSquad": false, + "seriesNumber": 1 + } + }, + "venue": { + "id": 4705, + "name": "Truist Park", + "link": "/api/v1/venues/4705" + }, + "content": { + "link": "/api/v1/game/661577/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-661577-2022-04-07", + "seasonDisplay": "2022", + "dayNight": "night", + "description": "Braves home opener", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 145, + "gamesInSeries": 4, + "seriesGameNumber": 1, + "seriesDescription": "Regular Season", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 661042, + "link": "/api/v1.1/game/661042/feed/live", + "gameType": "R", + "season": "2022", + "gameDate": "2022-04-08T01:38:00Z", + "officialDate": "2022-04-07", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 1, + "losses": 0, + "pct": "1.000" + }, + "score": 3, + "team": { + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "isWinner": true, + "splitSquad": false, + "seriesNumber": 1 + }, + "home": { + "leagueRecord": { + "wins": 0, + "losses": 1, + "pct": ".000" + }, + "score": 1, + "team": { + "id": 108, + "name": "Los Angeles Angels", + "link": "/api/v1/teams/108" + }, + "isWinner": false, + "splitSquad": false, + "seriesNumber": 1 + } + }, + "venue": { + "id": 1, + "name": "Angel Stadium", + "link": "/api/v1/venues/1" + }, + "content": { + "link": "/api/v1/game/661042/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-661042-2022-04-07", + "seasonDisplay": "2022", + "dayNight": "night", + "description": "Angels home opener", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "gamesInSeries": 4, + "seriesGameNumber": 1, + "seriesDescription": "Regular Season", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 663418, + "link": "/api/v1.1/game/663418/feed/live", + "gameType": "R", + "season": "2022", + "gameDate": "2022-04-08T01:40:00Z", + "officialDate": "2022-04-07", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 0, + "losses": 1, + "pct": ".000" + }, + "score": 2, + "team": { + "id": 135, + "name": "San Diego Padres", + "link": "/api/v1/teams/135" + }, + "isWinner": false, + "splitSquad": false, + "seriesNumber": 1 + }, + "home": { + "leagueRecord": { + "wins": 1, + "losses": 0, + "pct": "1.000" + }, + "score": 4, + "team": { + "id": 109, + "name": "Arizona Diamondbacks", + "link": "/api/v1/teams/109" + }, + "isWinner": true, + "splitSquad": false, + "seriesNumber": 1 + } + }, + "venue": { + "id": 15, + "name": "Chase Field", + "link": "/api/v1/venues/15" + }, + "content": { + "link": "/api/v1/game/663418/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-663418-2022-04-07", + "seasonDisplay": "2022", + "dayNight": "night", + "description": "D-backs home opener", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 4, + "seriesGameNumber": 1, + "seriesDescription": "Regular Season", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 661333, + "link": "/api/v1.1/game/661333/feed/live", + "gameType": "R", + "season": "2022", + "gameDate": "2022-04-07T17:05:00Z", + "officialDate": "2022-04-08", + "rescheduleDate": "2022-04-08T17:05:00Z", + "rescheduleGameDate": "2022-04-08", + "status": { + "abstractGameState": "Final", + "codedGameState": "D", + "detailedState": "Postponed", + "statusCode": "DI", + "startTimeTBD": false, + "reason": "Inclement Weather", + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 0, + "losses": 1, + "pct": ".000" + }, + "team": { + "id": 111, + "name": "Boston Red Sox", + "link": "/api/v1/teams/111" + }, + "splitSquad": false, + "seriesNumber": 1 + }, + "home": { + "leagueRecord": { + "wins": 1, + "losses": 0, + "pct": "1.000" + }, + "team": { + "id": 147, + "name": "New York Yankees", + "link": "/api/v1/teams/147" + }, + "splitSquad": false, + "seriesNumber": 1 + } + }, + "venue": { + "id": 3313, + "name": "Yankee Stadium", + "link": "/api/v1/venues/3313" + }, + "content": { + "link": "/api/v1/game/661333/content" + }, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-661333-2022-04-07", + "seasonDisplay": "2022", + "dayNight": "day", + "description": "Yankees home opener", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 145, + "gamesInSeries": 3, + "seriesGameNumber": 1, + "seriesDescription": "Regular Season", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 661750, + "link": "/api/v1.1/game/661750/feed/live", + "gameType": "R", + "season": "2022", + "gameDate": "2022-04-07T20:10:00Z", + "officialDate": "2022-04-08", + "rescheduleDate": "2022-04-08T20:10:00Z", + "rescheduleGameDate": "2022-04-08", + "status": { + "abstractGameState": "Final", + "codedGameState": "D", + "detailedState": "Postponed", + "statusCode": "DI", + "startTimeTBD": false, + "reason": "Inclement Weather", + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 1, + "losses": 0, + "pct": "1.000" + }, + "team": { + "id": 136, + "name": "Seattle Mariners", + "link": "/api/v1/teams/136" + }, + "splitSquad": false, + "seriesNumber": 1 + }, + "home": { + "leagueRecord": { + "wins": 0, + "losses": 1, + "pct": ".000" + }, + "team": { + "id": 142, + "name": "Minnesota Twins", + "link": "/api/v1/teams/142" + }, + "splitSquad": false, + "seriesNumber": 1 + } + }, + "venue": { + "id": 3312, + "name": "Target Field", + "link": "/api/v1/venues/3312" + }, + "content": { + "link": "/api/v1/game/661750/content" + }, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-661750-2022-04-07", + "seasonDisplay": "2022", + "dayNight": "day", + "description": "Twins home opener", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 4, + "seriesGameNumber": 1, + "seriesDescription": "Regular Season", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + } + ], + "events": [] + } + ] + } \ No newline at end of file diff --git a/tests/mock_tests/mock_json/schedule/schedule_start_end_date.json b/tests/mock_tests/mock_json/schedule/schedule_start_end_date.json new file mode 100644 index 00000000..06fd695d --- /dev/null +++ b/tests/mock_tests/mock_json/schedule/schedule_start_end_date.json @@ -0,0 +1,587 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "totalItems": 8, + "totalEvents": 0, + "totalGames": 8, + "totalGamesInProgress": 0, + "dates": [ + { + "date": "2022-10-11", + "totalItems": 4, + "totalEvents": 0, + "totalGames": 4, + "totalGamesInProgress": 0, + "games": [ + { + "gamePk": 715743, + "link": "/api/v1.1/game/715743/feed/live", + "gameType": "D", + "season": "2022", + "gameDate": "2022-10-11T17:07:00Z", + "officialDate": "2022-10-11", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 1, + "losses": 0, + "pct": "1.000" + }, + "score": 7, + "team": { + "id": 143, + "name": "Philadelphia Phillies", + "link": "/api/v1/teams/143" + }, + "isWinner": true, + "splitSquad": false, + "seriesNumber": 4 + }, + "home": { + "leagueRecord": { + "wins": 0, + "losses": 1, + "pct": ".000" + }, + "score": 6, + "team": { + "id": 144, + "name": "Atlanta Braves", + "link": "/api/v1/teams/144" + }, + "isWinner": false, + "splitSquad": false, + "seriesNumber": 4 + } + }, + "venue": { + "id": 4705, + "name": "Truist Park", + "link": "/api/v1/venues/4705" + }, + "content": { + "link": "/api/v1/game/715743/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-715743-2022-10-11", + "seasonDisplay": "2022", + "dayNight": "day", + "description": "NLDS Game 1", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 5, + "seriesGameNumber": 1, + "seriesDescription": "Division Series", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 715758, + "link": "/api/v1.1/game/715758/feed/live", + "gameType": "D", + "season": "2022", + "gameDate": "2022-10-11T19:37:00Z", + "officialDate": "2022-10-11", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 0, + "losses": 1, + "pct": ".000" + }, + "score": 7, + "team": { + "id": 136, + "name": "Seattle Mariners", + "link": "/api/v1/teams/136" + }, + "isWinner": false, + "splitSquad": false, + "seriesNumber": 1 + }, + "home": { + "leagueRecord": { + "wins": 1, + "losses": 0, + "pct": "1.000" + }, + "score": 8, + "team": { + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "isWinner": true, + "splitSquad": false, + "seriesNumber": 1 + } + }, + "venue": { + "id": 2392, + "name": "Minute Maid Park", + "link": "/api/v1/venues/2392" + }, + "content": { + "link": "/api/v1/game/715758/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-715758-2022-10-11", + "seasonDisplay": "2022", + "dayNight": "day", + "description": "ALDS Game 1", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 5, + "seriesGameNumber": 1, + "seriesDescription": "Division Series", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 715753, + "link": "/api/v1.1/game/715753/feed/live", + "gameType": "D", + "season": "2022", + "gameDate": "2022-10-11T23:37:00Z", + "officialDate": "2022-10-11", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": {}, + "venue": { + "id": 3313, + "name": "Yankee Stadium", + "link": "/api/v1/venues/3313" + }, + "content": { + "link": "/api/v1/game/715753/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-715753-2022-10-11", + "seasonDisplay": "2022", + "dayNight": "night", + "description": "ALDS Game 1", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 5, + "seriesGameNumber": 1, + "seriesDescription": "Division Series", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 715748, + "link": "/api/v1.1/game/715748/feed/live", + "gameType": "D", + "season": "2022", + "gameDate": "2022-10-12T01:37:00Z", + "officialDate": "2022-10-11", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": {}, + "venue": { + "id": 22, + "name": "Dodger Stadium", + "link": "/api/v1/venues/22" + }, + "content": { + "link": "/api/v1/game/715748/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-715748-2022-10-11", + "seasonDisplay": "2022", + "dayNight": "night", + "description": "NLDS Game 1", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 5, + "seriesGameNumber": 1, + "seriesDescription": "Division Series", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + } + ], + "events": [] + }, + { + "date": "2022-10-12", + "totalItems": 2, + "totalEvents": 0, + "totalGames": 2, + "totalGamesInProgress": 0, + "games": [ + { + "gamePk": 715742, + "link": "/api/v1.1/game/715742/feed/live", + "gameType": "D", + "season": "2022", + "gameDate": "2022-10-12T20:35:00Z", + "officialDate": "2022-10-12", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 1, + "losses": 1, + "pct": ".500" + }, + "score": 0, + "team": { + "id": 143, + "name": "Philadelphia Phillies", + "link": "/api/v1/teams/143" + }, + "isWinner": false, + "splitSquad": false, + "seriesNumber": 4 + }, + "home": { + "leagueRecord": { + "wins": 1, + "losses": 1, + "pct": ".500" + }, + "score": 3, + "team": { + "id": 144, + "name": "Atlanta Braves", + "link": "/api/v1/teams/144" + }, + "isWinner": true, + "splitSquad": false, + "seriesNumber": 4 + } + }, + "venue": { + "id": 4705, + "name": "Truist Park", + "link": "/api/v1/venues/4705" + }, + "content": { + "link": "/api/v1/game/715742/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-715742-2022-10-12", + "seasonDisplay": "2022", + "dayNight": "day", + "description": "NLDS Game 2", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 5, + "seriesGameNumber": 2, + "seriesDescription": "Division Series", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 715747, + "link": "/api/v1.1/game/715747/feed/live", + "gameType": "D", + "season": "2022", + "gameDate": "2022-10-13T00:37:00Z", + "officialDate": "2022-10-12", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 1, + "losses": 1, + "pct": ".500" + }, + "score": 5, + "team": { + "id": 135, + "name": "San Diego Padres", + "link": "/api/v1/teams/135" + }, + "isWinner": true, + "splitSquad": false, + "seriesNumber": 3 + }, + "home": { + "leagueRecord": { + "wins": 1, + "losses": 1, + "pct": ".500" + }, + "score": 3, + "team": { + "id": 119, + "name": "Los Angeles Dodgers", + "link": "/api/v1/teams/119" + }, + "isWinner": false, + "splitSquad": false, + "seriesNumber": 3 + } + }, + "venue": { + "id": 22, + "name": "Dodger Stadium", + "link": "/api/v1/venues/22" + }, + "content": { + "link": "/api/v1/game/715747/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-715747-2022-10-12", + "seasonDisplay": "2022", + "dayNight": "night", + "description": "NLDS Game 2", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 5, + "seriesGameNumber": 2, + "seriesDescription": "Division Series", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + } + ], + "events": [] + }, + { + "date": "2022-10-13", + "totalItems": 2, + "totalEvents": 0, + "totalGames": 2, + "totalGamesInProgress": 0, + "games": [ + { + "gamePk": 715757, + "link": "/api/v1.1/game/715757/feed/live", + "gameType": "D", + "season": "2022", + "gameDate": "2022-10-13T19:37:00Z", + "officialDate": "2022-10-13", + "status": { + "abstractGameState": "Final", + "codedGameState": "F", + "detailedState": "Final", + "statusCode": "F", + "startTimeTBD": false, + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 0, + "losses": 2, + "pct": ".000" + }, + "score": 2, + "team": { + "id": 136, + "name": "Seattle Mariners", + "link": "/api/v1/teams/136" + }, + "isWinner": false, + "splitSquad": false, + "seriesNumber": 1 + }, + "home": { + "leagueRecord": { + "wins": 2, + "losses": 0, + "pct": "1.000" + }, + "score": 4, + "team": { + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "isWinner": true, + "splitSquad": false, + "seriesNumber": 1 + } + }, + "venue": { + "id": 2392, + "name": "Minute Maid Park", + "link": "/api/v1/venues/2392" + }, + "content": { + "link": "/api/v1/game/715757/content" + }, + "isTie": false, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-715757-2022-10-13", + "seasonDisplay": "2022", + "dayNight": "day", + "description": "ALDS Game 2", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 5, + "seriesGameNumber": 2, + "seriesDescription": "Division Series", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + }, + { + "gamePk": 715752, + "link": "/api/v1.1/game/715752/feed/live", + "gameType": "D", + "season": "2022", + "gameDate": "2022-10-13T23:37:00Z", + "officialDate": "2022-10-14", + "rescheduleDate": "2022-10-14T17:07:00Z", + "rescheduleGameDate": "2022-10-14", + "status": { + "abstractGameState": "Final", + "codedGameState": "D", + "detailedState": "Postponed", + "statusCode": "DI", + "startTimeTBD": false, + "reason": "Inclement Weather", + "abstractGameCode": "F" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 1, + "losses": 1, + "pct": ".500" + }, + "team": { + "id": 114, + "name": "Cleveland Guardians", + "link": "/api/v1/teams/114" + }, + "splitSquad": false, + "seriesNumber": 2 + }, + "home": { + "leagueRecord": { + "wins": 1, + "losses": 1, + "pct": ".500" + }, + "team": { + "id": 147, + "name": "New York Yankees", + "link": "/api/v1/teams/147" + }, + "splitSquad": false, + "seriesNumber": 2 + } + }, + "venue": { + "id": 3313, + "name": "Yankee Stadium", + "link": "/api/v1/venues/3313" + }, + "content": { + "link": "/api/v1/game/715752/content" + }, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-715752-2022-10-13", + "seasonDisplay": "2022", + "dayNight": "night", + "description": "ALDS Game 2", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 5, + "seriesGameNumber": 2, + "seriesDescription": "Division Series", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + } + ], + "events": [] + } + ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/sports/sport.json b/tests/mock_tests/mock_json/sports/sport.json new file mode 100644 index 00000000..18bac257 --- /dev/null +++ b/tests/mock_tests/mock_json/sports/sport.json @@ -0,0 +1,13 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "sports": [ + { + "id": 1, + "code": "mlb", + "link": "/api/v1/sports/1", + "name": "Major League Baseball", + "abbreviation": "MLB", + "sortOrder": 11, + "activeStatus": true + } ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/sports/sports.json b/tests/mock_tests/mock_json/sports/sports.json new file mode 100644 index 00000000..b12b6262 --- /dev/null +++ b/tests/mock_tests/mock_json/sports/sports.json @@ -0,0 +1,50 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "sports": [ + { + "id": 1, + "code": "mlb", + "link": "/api/v1/sports/1", + "name": "Major League Baseball", + "abbreviation": "MLB", + "sortOrder": 11, + "activeStatus": true + }, + { + "id": 11, + "code": "aaa", + "link": "/api/v1/sports/11", + "name": "Triple-A", + "abbreviation": "AAA", + "sortOrder": 101, + "activeStatus": true + }, + { + "id": 12, + "code": "aax", + "link": "/api/v1/sports/12", + "name": "Double-A", + "abbreviation": "AA", + "sortOrder": 201, + "activeStatus": true + }, + { + "id": 13, + "code": "afa", + "link": "/api/v1/sports/13", + "name": "High-A", + "abbreviation": "A+", + "sortOrder": 301, + "activeStatus": true + }, + { + "id": 14, + "code": "afx", + "link": "/api/v1/sports/14", + "name": "Single-A", + "abbreviation": "A", + "sortOrder": 401, + "activeStatus": true + } + ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/standings/standings.json b/tests/mock_tests/mock_json/standings/standings.json new file mode 100644 index 00000000..5316c86e --- /dev/null +++ b/tests/mock_tests/mock_json/standings/standings.json @@ -0,0 +1,1422 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "records": [ + { + "standingsType": "regularSeason", + "league": { + "id": 103, + "link": "/api/v1/league/103" + }, + "division": { + "id": 201, + "link": "/api/v1/divisions/201" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1" + }, + "lastUpdated": "2022-01-21T18:46:25.113Z", + "teamRecords": [ + { + "team": { + "id": 111, + "name": "Boston Red Sox", + "link": "/api/v1/teams/111" + }, + "season": "2018", + "streak": { + "streakType": "wins", + "streakNumber": 1, + "streakCode": "W1" + }, + "clinchIndicator": "z", + "divisionRank": "1", + "leagueRank": "1", + "sportRank": "1", + "gamesPlayed": 162, + "gamesBack": "-", + "wildCardGamesBack": "-", + "leagueGamesBack": "-", + "springLeagueGamesBack": "-", + "sportGamesBack": "-", + "divisionGamesBack": "-", + "conferenceGamesBack": "-", + "leagueRecord": { + "wins": 108, + "losses": 54, + "ties": 0, + "pct": ".667" + }, + "lastUpdated": "2022-01-21T18:46:25Z", + "records": { + "splitRecords": [ + { + "wins": 57, + "losses": 24, + "type": "home", + "pct": ".704" + }, + { + "wins": 51, + "losses": 30, + "type": "away", + "pct": ".630" + }, + { + "wins": 21, + "losses": 16, + "type": "left", + "pct": ".568" + }, + { + "wins": 13, + "losses": 5, + "type": "leftHome", + "pct": ".722" + }, + { + "wins": 8, + "losses": 11, + "type": "leftAway", + "pct": ".421" + }, + { + "wins": 44, + "losses": 19, + "type": "rightHome", + "pct": ".698" + }, + { + "wins": 43, + "losses": 19, + "type": "rightAway", + "pct": ".694" + }, + { + "wins": 87, + "losses": 38, + "type": "right", + "pct": ".696" + }, + { + "wins": 5, + "losses": 5, + "type": "lastTen", + "pct": ".500" + }, + { + "wins": 8, + "losses": 5, + "type": "extraInning", + "pct": ".615" + }, + { + "wins": 25, + "losses": 14, + "type": "oneRun", + "pct": ".641" + }, + { + "wins": 41, + "losses": 33, + "type": "winners", + "pct": ".554" + }, + { + "wins": 37, + "losses": 11, + "type": "day", + "pct": ".771" + }, + { + "wins": 71, + "losses": 43, + "type": "night", + "pct": ".623" + }, + { + "wins": 97, + "losses": 46, + "type": "grass", + "pct": ".678" + }, + { + "wins": 11, + "losses": 8, + "type": "turf", + "pct": ".579" + } + ], + "divisionRecords": [ + { + "wins": 21, + "losses": 12, + "pct": ".636", + "division": { + "id": 200, + "name": "American League West", + "link": "/api/v1/divisions/200" + } + }, + { + "wins": 52, + "losses": 24, + "pct": ".684", + "division": { + "id": 201, + "name": "American League East", + "link": "/api/v1/divisions/201" + } + }, + { + "wins": 19, + "losses": 14, + "pct": ".576", + "division": { + "id": 202, + "name": "American League Central", + "link": "/api/v1/divisions/202" + } + } + ], + "overallRecords": [ + { + "wins": 57, + "losses": 24, + "type": "home", + "pct": ".704" + }, + { + "wins": 51, + "losses": 30, + "type": "away", + "pct": ".630" + } + ], + "leagueRecords": [ + { + "wins": 92, + "losses": 50, + "pct": ".648", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + } + }, + { + "wins": 16, + "losses": 4, + "pct": ".800", + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + } + } + ], + "expectedRecords": [ + { + "wins": 103, + "losses": 59, + "type": "xWinLoss", + "pct": ".636" + }, + { + "wins": 103, + "losses": 59, + "type": "xWinLossSeason", + "pct": ".636" + } + ] + }, + "runsAllowed": 647, + "runsScored": 876, + "divisionChamp": true, + "divisionLeader": true, + "hasWildcard": true, + "clinched": true, + "eliminationNumber": "-", + "wildCardEliminationNumber": "-", + "magicNumber": "-", + "wins": 108, + "losses": 54, + "runDifferential": 229, + "winningPercentage": ".667" + }, + { + "team": { + "id": 147, + "name": "New York Yankees", + "link": "/api/v1/teams/147" + }, + "season": "2018", + "streak": { + "streakType": "losses", + "streakNumber": 1, + "streakCode": "L1" + }, + "clinchIndicator": "w", + "divisionRank": "2", + "leagueRank": "3", + "wildCardRank": "1", + "sportRank": "3", + "gamesPlayed": 162, + "gamesBack": "8.0", + "wildCardGamesBack": "+3.0", + "leagueGamesBack": "8.0", + "springLeagueGamesBack": "-", + "sportGamesBack": "8.0", + "divisionGamesBack": "8.0", + "conferenceGamesBack": "-", + "leagueRecord": { + "wins": 100, + "losses": 62, + "ties": 0, + "pct": ".617" + }, + "lastUpdated": "2021-11-23T01:20:19Z", + "records": { + "splitRecords": [ + { + "wins": 53, + "losses": 28, + "type": "home", + "pct": ".654" + }, + { + "wins": 47, + "losses": 34, + "type": "away", + "pct": ".580" + }, + { + "wins": 30, + "losses": 15, + "type": "left", + "pct": ".667" + }, + { + "wins": 18, + "losses": 10, + "type": "leftHome", + "pct": ".643" + }, + { + "wins": 12, + "losses": 5, + "type": "leftAway", + "pct": ".706" + }, + { + "wins": 35, + "losses": 18, + "type": "rightHome", + "pct": ".660" + }, + { + "wins": 35, + "losses": 29, + "type": "rightAway", + "pct": ".547" + }, + { + "wins": 70, + "losses": 47, + "type": "right", + "pct": ".598" + }, + { + "wins": 7, + "losses": 3, + "type": "lastTen", + "pct": ".700" + }, + { + "wins": 9, + "losses": 5, + "type": "extraInning", + "pct": ".643" + }, + { + "wins": 23, + "losses": 17, + "type": "oneRun", + "pct": ".575" + }, + { + "wins": 41, + "losses": 30, + "type": "winners", + "pct": ".577" + }, + { + "wins": 36, + "losses": 21, + "type": "day", + "pct": ".632" + }, + { + "wins": 64, + "losses": 41, + "type": "night", + "pct": ".610" + }, + { + "wins": 90, + "losses": 53, + "type": "grass", + "pct": ".629" + }, + { + "wins": 10, + "losses": 9, + "type": "turf", + "pct": ".526" + } + ], + "divisionRecords": [ + { + "wins": 22, + "losses": 10, + "pct": ".688", + "division": { + "id": 200, + "name": "American League West", + "link": "/api/v1/divisions/200" + } + }, + { + "wins": 44, + "losses": 32, + "pct": ".579", + "division": { + "id": 201, + "name": "American League East", + "link": "/api/v1/divisions/201" + } + }, + { + "wins": 23, + "losses": 11, + "pct": ".676", + "division": { + "id": 202, + "name": "American League Central", + "link": "/api/v1/divisions/202" + } + } + ], + "overallRecords": [ + { + "wins": 53, + "losses": 28, + "type": "home", + "pct": ".654" + }, + { + "wins": 47, + "losses": 34, + "type": "away", + "pct": ".580" + } + ], + "leagueRecords": [ + { + "wins": 89, + "losses": 53, + "pct": ".627", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + } + }, + { + "wins": 11, + "losses": 9, + "pct": ".550", + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + } + } + ], + "expectedRecords": [ + { + "wins": 99, + "losses": 63, + "type": "xWinLoss", + "pct": ".611" + }, + { + "wins": 99, + "losses": 63, + "type": "xWinLossSeason", + "pct": ".611" + } + ] + }, + "runsAllowed": 669, + "runsScored": 851, + "divisionChamp": false, + "divisionLeader": false, + "wildCardLeader": true, + "hasWildcard": true, + "clinched": true, + "eliminationNumber": "E", + "wildCardEliminationNumber": "-", + "wins": 100, + "losses": 62, + "runDifferential": 182, + "winningPercentage": ".617" + } + ] + }, + { + "standingsType": "regularSeason", + "league": { + "id": 103, + "link": "/api/v1/league/103" + }, + "division": { + "id": 202, + "link": "/api/v1/divisions/202" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1" + }, + "lastUpdated": "2022-01-19T19:14:00.522Z", + "teamRecords": [ + { + "team": { + "id": 114, + "name": "Cleveland Indians", + "link": "/api/v1/teams/114" + }, + "season": "2018", + "streak": { + "streakType": "wins", + "streakNumber": 1, + "streakCode": "W1" + }, + "clinchIndicator": "y", + "divisionRank": "1", + "leagueRank": "5", + "sportRank": "5", + "gamesPlayed": 162, + "gamesBack": "-", + "wildCardGamesBack": "-", + "leagueGamesBack": "17.0", + "springLeagueGamesBack": "-", + "sportGamesBack": "17.0", + "divisionGamesBack": "-", + "conferenceGamesBack": "-", + "leagueRecord": { + "wins": 91, + "losses": 71, + "ties": 0, + "pct": ".562" + }, + "lastUpdated": "2021-11-23T01:17:55Z", + "records": { + "splitRecords": [ + { + "wins": 49, + "losses": 32, + "type": "home", + "pct": ".605" + }, + { + "wins": 42, + "losses": 39, + "type": "away", + "pct": ".519" + }, + { + "wins": 22, + "losses": 21, + "type": "left", + "pct": ".512" + }, + { + "wins": 12, + "losses": 11, + "type": "leftHome", + "pct": ".522" + }, + { + "wins": 10, + "losses": 10, + "type": "leftAway", + "pct": ".500" + }, + { + "wins": 37, + "losses": 21, + "type": "rightHome", + "pct": ".638" + }, + { + "wins": 32, + "losses": 29, + "type": "rightAway", + "pct": ".525" + }, + { + "wins": 69, + "losses": 50, + "type": "right", + "pct": ".580" + }, + { + "wins": 6, + "losses": 4, + "type": "lastTen", + "pct": ".600" + }, + { + "wins": 4, + "losses": 9, + "type": "extraInning", + "pct": ".308" + }, + { + "wins": 22, + "losses": 24, + "type": "oneRun", + "pct": ".478" + }, + { + "wins": 23, + "losses": 31, + "type": "winners", + "pct": ".426" + }, + { + "wins": 36, + "losses": 23, + "type": "day", + "pct": ".610" + }, + { + "wins": 55, + "losses": 48, + "type": "night", + "pct": ".534" + }, + { + "wins": 87, + "losses": 66, + "type": "grass", + "pct": ".569" + }, + { + "wins": 4, + "losses": 5, + "type": "turf", + "pct": ".444" + } + ], + "divisionRecords": [ + { + "wins": 14, + "losses": 18, + "pct": ".438", + "division": { + "id": 200, + "name": "American League West", + "link": "/api/v1/divisions/200" + } + }, + { + "wins": 16, + "losses": 18, + "pct": ".471", + "division": { + "id": 201, + "name": "American League East", + "link": "/api/v1/divisions/201" + } + }, + { + "wins": 49, + "losses": 27, + "pct": ".645", + "division": { + "id": 202, + "name": "American League Central", + "link": "/api/v1/divisions/202" + } + } + ], + "overallRecords": [ + { + "wins": 49, + "losses": 32, + "type": "home", + "pct": ".605" + }, + { + "wins": 42, + "losses": 39, + "type": "away", + "pct": ".519" + } + ], + "leagueRecords": [ + { + "wins": 79, + "losses": 63, + "pct": ".556", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + } + }, + { + "wins": 12, + "losses": 8, + "pct": ".600", + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + } + } + ], + "expectedRecords": [ + { + "wins": 98, + "losses": 64, + "type": "xWinLoss", + "pct": ".605" + }, + { + "wins": 98, + "losses": 64, + "type": "xWinLossSeason", + "pct": ".605" + } + ] + }, + "runsAllowed": 648, + "runsScored": 818, + "divisionChamp": true, + "divisionLeader": true, + "hasWildcard": true, + "clinched": true, + "eliminationNumber": "-", + "wildCardEliminationNumber": "-", + "magicNumber": "-", + "wins": 91, + "losses": 71, + "runDifferential": 170, + "winningPercentage": ".562" + }, + { + "team": { + "id": 142, + "name": "Minnesota Twins", + "link": "/api/v1/teams/142" + }, + "season": "2018", + "streak": { + "streakType": "wins", + "streakNumber": 6, + "streakCode": "W6" + }, + "divisionRank": "2", + "leagueRank": "9", + "wildCardRank": "6", + "sportRank": "9", + "gamesPlayed": 162, + "gamesBack": "13.0", + "wildCardGamesBack": "19.0", + "leagueGamesBack": "30.0", + "springLeagueGamesBack": "-", + "sportGamesBack": "30.0", + "divisionGamesBack": "13.0", + "conferenceGamesBack": "-", + "leagueRecord": { + "wins": 78, + "losses": 84, + "ties": 0, + "pct": ".481" + }, + "lastUpdated": "2021-11-13T21:10:05Z", + "records": { + "splitRecords": [ + { + "wins": 49, + "losses": 32, + "type": "home", + "pct": ".605" + }, + { + "wins": 29, + "losses": 52, + "type": "away", + "pct": ".358" + }, + { + "wins": 21, + "losses": 25, + "type": "left", + "pct": ".457" + }, + { + "wins": 14, + "losses": 8, + "type": "leftHome", + "pct": ".636" + }, + { + "wins": 7, + "losses": 17, + "type": "leftAway", + "pct": ".292" + }, + { + "wins": 35, + "losses": 24, + "type": "rightHome", + "pct": ".593" + }, + { + "wins": 22, + "losses": 35, + "type": "rightAway", + "pct": ".386" + }, + { + "wins": 57, + "losses": 59, + "type": "right", + "pct": ".491" + }, + { + "wins": 7, + "losses": 3, + "type": "lastTen", + "pct": ".700" + }, + { + "wins": 5, + "losses": 8, + "type": "extraInning", + "pct": ".385" + }, + { + "wins": 15, + "losses": 21, + "type": "oneRun", + "pct": ".417" + }, + { + "wins": 29, + "losses": 47, + "type": "winners", + "pct": ".382" + }, + { + "wins": 31, + "losses": 32, + "type": "day", + "pct": ".492" + }, + { + "wins": 47, + "losses": 52, + "type": "night", + "pct": ".475" + }, + { + "wins": 74, + "losses": 80, + "type": "grass", + "pct": ".481" + }, + { + "wins": 4, + "losses": 4, + "type": "turf", + "pct": ".500" + } + ], + "divisionRecords": [ + { + "wins": 10, + "losses": 22, + "pct": ".313", + "division": { + "id": 200, + "name": "American League West", + "link": "/api/v1/divisions/200" + } + }, + { + "wins": 18, + "losses": 16, + "pct": ".529", + "division": { + "id": 201, + "name": "American League East", + "link": "/api/v1/divisions/201" + } + }, + { + "wins": 42, + "losses": 34, + "pct": ".553", + "division": { + "id": 202, + "name": "American League Central", + "link": "/api/v1/divisions/202" + } + } + ], + "overallRecords": [ + { + "wins": 49, + "losses": 32, + "type": "home", + "pct": ".605" + }, + { + "wins": 29, + "losses": 52, + "type": "away", + "pct": ".358" + } + ], + "leagueRecords": [ + { + "wins": 70, + "losses": 72, + "pct": ".493", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + } + }, + { + "wins": 8, + "losses": 12, + "pct": ".400", + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + } + } + ], + "expectedRecords": [ + { + "wins": 77, + "losses": 85, + "type": "xWinLoss", + "pct": ".475" + }, + { + "wins": 77, + "losses": 85, + "type": "xWinLossSeason", + "pct": ".475" + } + ] + }, + "runsAllowed": 775, + "runsScored": 738, + "divisionChamp": false, + "divisionLeader": false, + "hasWildcard": true, + "clinched": false, + "eliminationNumber": "E", + "wildCardEliminationNumber": "E", + "wins": 78, + "losses": 84, + "runDifferential": -37, + "winningPercentage": ".481" + } + ] + }, + { + "standingsType": "regularSeason", + "league": { + "id": 103, + "link": "/api/v1/league/103" + }, + "division": { + "id": 200, + "link": "/api/v1/divisions/200" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1" + }, + "lastUpdated": "2022-01-21T18:46:31.644Z", + "teamRecords": [ + { + "team": { + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "season": "2018", + "streak": { + "streakType": "losses", + "streakNumber": 1, + "streakCode": "L1" + }, + "clinchIndicator": "y", + "divisionRank": "1", + "leagueRank": "2", + "sportRank": "2", + "gamesPlayed": 162, + "gamesBack": "-", + "wildCardGamesBack": "-", + "leagueGamesBack": "5.0", + "springLeagueGamesBack": "-", + "sportGamesBack": "5.0", + "divisionGamesBack": "-", + "conferenceGamesBack": "-", + "leagueRecord": { + "wins": 103, + "losses": 59, + "ties": 0, + "pct": ".636" + }, + "lastUpdated": "2021-11-23T01:32:01Z", + "records": { + "splitRecords": [ + { + "wins": 46, + "losses": 35, + "type": "home", + "pct": ".568" + }, + { + "wins": 57, + "losses": 24, + "type": "away", + "pct": ".704" + }, + { + "wins": 37, + "losses": 23, + "type": "left", + "pct": ".617" + }, + { + "wins": 13, + "losses": 17, + "type": "leftHome", + "pct": ".433" + }, + { + "wins": 24, + "losses": 6, + "type": "leftAway", + "pct": ".800" + }, + { + "wins": 33, + "losses": 18, + "type": "rightHome", + "pct": ".647" + }, + { + "wins": 33, + "losses": 18, + "type": "rightAway", + "pct": ".647" + }, + { + "wins": 66, + "losses": 36, + "type": "right", + "pct": ".647" + }, + { + "wins": 8, + "losses": 2, + "type": "lastTen", + "pct": ".800" + }, + { + "wins": 5, + "losses": 6, + "type": "extraInning", + "pct": ".455" + }, + { + "wins": 24, + "losses": 24, + "type": "oneRun", + "pct": ".500" + }, + { + "wins": 41, + "losses": 38, + "type": "winners", + "pct": ".519" + }, + { + "wins": 34, + "losses": 17, + "type": "day", + "pct": ".667" + }, + { + "wins": 69, + "losses": 42, + "type": "night", + "pct": ".622" + }, + { + "wins": 100, + "losses": 55, + "type": "grass", + "pct": ".645" + }, + { + "wins": 3, + "losses": 4, + "type": "turf", + "pct": ".429" + } + ], + "divisionRecords": [ + { + "wins": 46, + "losses": 30, + "pct": ".605", + "division": { + "id": 200, + "name": "American League West", + "link": "/api/v1/divisions/200" + } + }, + { + "wins": 19, + "losses": 15, + "pct": ".559", + "division": { + "id": 201, + "name": "American League East", + "link": "/api/v1/divisions/201" + } + }, + { + "wins": 25, + "losses": 7, + "pct": ".781", + "division": { + "id": 202, + "name": "American League Central", + "link": "/api/v1/divisions/202" + } + } + ], + "overallRecords": [ + { + "wins": 46, + "losses": 35, + "type": "home", + "pct": ".568" + }, + { + "wins": 57, + "losses": 24, + "type": "away", + "pct": ".704" + } + ], + "leagueRecords": [ + { + "wins": 90, + "losses": 52, + "pct": ".634", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + } + }, + { + "wins": 13, + "losses": 7, + "pct": ".650", + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + } + } + ], + "expectedRecords": [ + { + "wins": 109, + "losses": 53, + "type": "xWinLoss", + "pct": ".673" + }, + { + "wins": 109, + "losses": 53, + "type": "xWinLossSeason", + "pct": ".673" + } + ] + }, + "runsAllowed": 534, + "runsScored": 797, + "divisionChamp": true, + "divisionLeader": true, + "hasWildcard": true, + "clinched": true, + "eliminationNumber": "-", + "wildCardEliminationNumber": "-", + "magicNumber": "-", + "wins": 103, + "losses": 59, + "runDifferential": 263, + "winningPercentage": ".636" + }, + { + "team": { + "id": 133, + "name": "Oakland Athletics", + "link": "/api/v1/teams/133" + }, + "season": "2018", + "streak": { + "streakType": "losses", + "streakNumber": 1, + "streakCode": "L1" + }, + "clinchIndicator": "w", + "divisionRank": "2", + "leagueRank": "4", + "wildCardRank": "2", + "sportRank": "4", + "gamesPlayed": 162, + "gamesBack": "6.0", + "wildCardGamesBack": "-", + "leagueGamesBack": "11.0", + "springLeagueGamesBack": "-", + "sportGamesBack": "11.0", + "divisionGamesBack": "6.0", + "conferenceGamesBack": "-", + "leagueRecord": { + "wins": 97, + "losses": 65, + "ties": 0, + "pct": ".599" + }, + "lastUpdated": "2021-11-23T01:32:00Z", + "records": { + "splitRecords": [ + { + "wins": 50, + "losses": 31, + "type": "home", + "pct": ".617" + }, + { + "wins": 47, + "losses": 34, + "type": "away", + "pct": ".580" + }, + { + "wins": 32, + "losses": 25, + "type": "left", + "pct": ".561" + }, + { + "wins": 15, + "losses": 16, + "type": "leftHome", + "pct": ".484" + }, + { + "wins": 17, + "losses": 9, + "type": "leftAway", + "pct": ".654" + }, + { + "wins": 35, + "losses": 15, + "type": "rightHome", + "pct": ".700" + }, + { + "wins": 30, + "losses": 25, + "type": "rightAway", + "pct": ".545" + }, + { + "wins": 65, + "losses": 40, + "type": "right", + "pct": ".619" + }, + { + "wins": 6, + "losses": 4, + "type": "lastTen", + "pct": ".600" + }, + { + "wins": 13, + "losses": 6, + "type": "extraInning", + "pct": ".684" + }, + { + "wins": 31, + "losses": 14, + "type": "oneRun", + "pct": ".689" + }, + { + "wins": 33, + "losses": 40, + "type": "winners", + "pct": ".452" + }, + { + "wins": 37, + "losses": 23, + "type": "day", + "pct": ".617" + }, + { + "wins": 60, + "losses": 42, + "type": "night", + "pct": ".588" + }, + { + "wins": 92, + "losses": 63, + "type": "grass", + "pct": ".594" + }, + { + "wins": 5, + "losses": 2, + "type": "turf", + "pct": ".714" + } + ], + "divisionRecords": [ + { + "wins": 38, + "losses": 38, + "pct": ".500", + "division": { + "id": 200, + "name": "American League West", + "link": "/api/v1/divisions/200" + } + }, + { + "wins": 21, + "losses": 11, + "pct": ".656", + "division": { + "id": 201, + "name": "American League East", + "link": "/api/v1/divisions/201" + } + }, + { + "wins": 26, + "losses": 8, + "pct": ".765", + "division": { + "id": 202, + "name": "American League Central", + "link": "/api/v1/divisions/202" + } + } + ], + "overallRecords": [ + { + "wins": 50, + "losses": 31, + "type": "home", + "pct": ".617" + }, + { + "wins": 47, + "losses": 34, + "type": "away", + "pct": ".580" + } + ], + "leagueRecords": [ + { + "wins": 85, + "losses": 57, + "pct": ".599", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + } + }, + { + "wins": 12, + "losses": 8, + "pct": ".600", + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + } + } + ], + "expectedRecords": [ + { + "wins": 95, + "losses": 67, + "type": "xWinLoss", + "pct": ".586" + }, + { + "wins": 95, + "losses": 67, + "type": "xWinLossSeason", + "pct": ".586" + } + ] + }, + "runsAllowed": 674, + "runsScored": 813, + "divisionChamp": false, + "divisionLeader": false, + "wildCardLeader": true, + "hasWildcard": true, + "clinched": true, + "eliminationNumber": "E", + "wildCardEliminationNumber": "-", + "wins": 97, + "losses": 65, + "runDifferential": 139, + "winningPercentage": ".599" + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/person/game_stats_player_archie.json b/tests/mock_tests/mock_json/stats/person/game_stats_player_archie.json new file mode 100644 index 00000000..db57b71e --- /dev/null +++ b/tests/mock_tests/mock_json/stats/person/game_stats_player_archie.json @@ -0,0 +1,219 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "exemptions": [], + "splits": [ + { + "stat": { + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "assists": 0, + "putOuts": 0, + "errors": 0, + "chances": 0, + "fielding": ".000", + "passedBall": 0, + "pickoffs": 0 + }, + "type": "gameLog", + "group": "fielding" + }, + { + "stat": { + "gamesPlayed": 1, + "gamesStarted": 0, + "flyOuts": 1, + "groundOuts": 2, + "airOuts": 1, + "runs": 0, + "doubles": 0, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 0, + "baseOnBalls": 0, + "intentionalWalks": 0, + "hits": 0, + "hitByPitch": 0, + "atBats": 3, + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "numberOfPitches": 8, + "inningsPitched": "1.0", + "wins": 0, + "losses": 0, + "saves": 0, + "saveOpportunities": 0, + "holds": 0, + "blownSaves": 0, + "earnedRuns": 0, + "battersFaced": 3, + "outs": 3, + "gamesPitched": 1, + "completeGames": 0, + "shutouts": 0, + "pitchesThrown": 8, + "balls": 2, + "strikes": 6, + "strikePercentage": ".750", + "hitBatsmen": 0, + "balks": 0, + "wildPitches": 0, + "pickoffs": 0, + "rbi": 0, + "gamesFinished": 0, + "runsScoredPer9": "0.00", + "homeRunsPer9": "0.00", + "inheritedRunners": 0, + "inheritedRunnersScored": 0, + "catchersInterference": 0, + "sacBunts": 0, + "sacFlies": 0, + "passedBall": 0 + }, + "type": "gameLog", + "group": "pitching" + }, + { + "stat": { + "gamesPlayed": 1, + "flyOuts": 0, + "groundOuts": 0, + "runs": 0, + "doubles": 0, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 0, + "baseOnBalls": 0, + "intentionalWalks": 0, + "hits": 0, + "hitByPitch": 0, + "atBats": 0, + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "groundIntoDoublePlay": 0, + "groundIntoTriplePlay": 0, + "plateAppearances": 0, + "totalBases": 0, + "rbi": 0, + "leftOnBase": 0, + "sacBunts": 0, + "sacFlies": 0, + "catchersInterference": 0, + "pickoffs": 0, + "atBatsPerHomeRun": "-.--" + }, + "type": "gameLog", + "group": "hitting" + } + ] + }, + { + "type": { + "displayName": "vsPlayer5Y" + }, + "group": { + "displayName": "hitting" + }, + "totalSplits": 0, + "exemptions": [], + "splits": [] + }, + { + "type": { + "displayName": "vsPlayer5Y" + }, + "group": { + "displayName": "pitching" + }, + "totalSplits": 1, + "exemptions": [], + "splits": [ + { + "stat": { + "gamesPlayed": 1, + "groundOuts": 1, + "airOuts": 0, + "doubles": 0, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 0, + "baseOnBalls": 0, + "intentionalWalks": 0, + "hits": 0, + "hitByPitch": 0, + "avg": ".000", + "atBats": 1, + "obp": ".000", + "slg": ".000", + "ops": ".000", + "groundIntoDoublePlay": 0, + "numberOfPitches": 2, + "inningsPitched": "0.1", + "outsPitched": 1, + "whip": "0.00", + "battersFaced": 1, + "outs": 1, + "gamesPitched": 1, + "balls": 0, + "strikes": 2, + "strikePercentage": "1.000", + "hitBatsmen": 0, + "balks": 0, + "wildPitches": 0, + "totalBases": 0, + "groundOutsToAirouts": "1.00", + "rbi": 0, + "pitchesPerInning": "6.00", + "strikeoutWalkRatio": "-.--", + "strikeoutsPer9Inn": "0.00", + "walksPer9Inn": "0.00", + "hitsPer9Inn": "0.00", + "homeRunsPer9": "0.00", + "catchersInterference": 0, + "sacBunts": 0, + "sacFlies": 0 + }, + "team": { + "id": 109, + "name": "Arizona Diamondbacks", + "link": "/api/v1/teams/109" + }, + "opponent": { + "id": 136, + "name": "Seattle Mariners", + "link": "/api/v1/teams/136" + }, + "gameType": "R", + "numTeams": 1, + "pitcher": { + "id": 605151, + "fullName": "Archie Bradley", + "link": "/api/v1/people/605151", + "firstName": "Archie", + "lastName": "Bradley" + }, + "batter": { + "id": 429664, + "fullName": "Robinson Cano", + "link": "/api/v1/people/429664", + "firstName": "Robinson", + "lastName": "Cano" + } + } + ] + }, + { + "type": { + "displayName": "playLog" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [] + } ] + } \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/person/game_stats_player_cal.json b/tests/mock_tests/mock_json/stats/person/game_stats_player_cal.json new file mode 100644 index 00000000..1b1ca322 --- /dev/null +++ b/tests/mock_tests/mock_json/stats/person/game_stats_player_cal.json @@ -0,0 +1,489 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "exemptions": [], + "splits": [ + { + "stat": { + "gamesStarted": 1, + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "assists": 0, + "putOuts": 9, + "errors": 0, + "chances": 9, + "fielding": ".000", + "passedBall": 0, + "pickoffs": 0 + }, + "type": "gameLog", + "group": "fielding" + }, + { + "stat": {}, + "type": "gameLog", + "group": "pitching" + }, + { + "stat": { + "gamesPlayed": 1, + "flyOuts": 0, + "groundOuts": 2, + "runs": 0, + "doubles": 0, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 1, + "baseOnBalls": 0, + "intentionalWalks": 0, + "hits": 0, + "hitByPitch": 0, + "atBats": 4, + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "groundIntoDoublePlay": 0, + "groundIntoTriplePlay": 0, + "plateAppearances": 4, + "totalBases": 0, + "rbi": 0, + "leftOnBase": 5, + "sacBunts": 0, + "sacFlies": 0, + "catchersInterference": 0, + "pickoffs": 0, + "atBatsPerHomeRun": "-.--" + }, + "type": "gameLog", + "group": "hitting" + } + ] + }, + { + "type": { + "displayName": "vsPlayer5Y" + }, + "group": { + "displayName": "hitting" + }, + "totalSplits": 1, + "exemptions": [], + "splits": [ + { + "stat": { + "gamesPlayed": 1, + "groundOuts": 1, + "airOuts": 0, + "doubles": 0, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 0, + "baseOnBalls": 0, + "intentionalWalks": 0, + "hits": 0, + "hitByPitch": 0, + "avg": ".000", + "atBats": 1, + "obp": ".000", + "slg": ".000", + "ops": ".000", + "groundIntoDoublePlay": 0, + "groundIntoTriplePlay": 0, + "numberOfPitches": 2, + "plateAppearances": 1, + "totalBases": 0, + "rbi": 0, + "leftOnBase": 1, + "sacBunts": 0, + "sacFlies": 0, + "babip": ".000", + "groundOutsToAirouts": "1.00", + "catchersInterference": 0, + "atBatsPerHomeRun": "-.--" + }, + "team": { + "id": 136, + "name": "Seattle Mariners", + "link": "/api/v1/teams/136" + }, + "opponent": { + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "gameType": "D", + "numTeams": 1, + "pitcher": { + "id": 519151, + "fullName": "Ryan Pressly", + "link": "/api/v1/people/519151", + "firstName": "Ryan", + "lastName": "Pressly" + }, + "batter": { + "id": 663728, + "fullName": "Cal Raleigh", + "link": "/api/v1/people/663728", + "firstName": "Cal", + "lastName": "Raleigh" + } + } + ] + }, + { + "type": { + "displayName": "vsPlayer5Y" + }, + "group": { + "displayName": "pitching" + }, + "totalSplits": 0, + "exemptions": [], + "splits": [] + }, + { + "type": { + "displayName": "playLog" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "play": { + "details": { + "call": { + "code": "X", + "description": "In play, out(s)" + }, + "description": "In play, out(s)", + "code": "X", + "ballColor": "rgba(26, 86, 190, 1.0)", + "trailColor": "rgba(50, 0, 221, 1.0)", + "isInPlay": true, + "isStrike": false, + "isBall": false, + "type": { + "code": "SI", + "description": "Sinker" + }, + "awayScore": 0, + "homeScore": 1, + "isOut": true, + "hasReview": false + }, + "count": { + "balls": 0, + "strikes": 0, + "outs": 1, + "inning": 3 + }, + "pitchData": { + "startSpeed": 95.1, + "endSpeed": 86.8, + "strikeZoneTop": 3.51, + "strikeZoneBottom": 1.68, + "coordinates": { + "aY": 32.2, + "aZ": -21.2, + "pfxX": 6.91, + "pfxZ": 5.7, + "pX": 0.24, + "pZ": 1.93, + "vX0": -3.83, + "vY0": -138.23, + "vZ0": -7.23, + "x": 107.73, + "y": 186.69, + "x0": 0.75, + "y0": 50.01, + "z0": 6.01, + "aX": 13.31 + }, + "breaks": { + "breakAngle": 28.8, + "breakLength": 6, + "breakY": 24, + "spinRate": 2182, + "spinDirection": 149 + }, + "zone": 9, + "typeConfidence": 2, + "plateTime": 0.4, + "extension": 5.68 + }, + "hitData": { + "launchSpeed": 95.3, + "launchAngle": -14, + "totalDistance": 9, + "trajectory": "ground_ball", + "hardness": "medium", + "location": "6", + "coordinates": { + "coordX": 115.68, + "coordY": 157.25 + } + }, + "index": 0, + "playId": "20aa507d-fad7-4386-a095-927440fa458a", + "pitchNumber": 1, + "startTime": "2022-10-13T20:12:55.414Z", + "endTime": "2022-10-13T20:13:03.006Z", + "isPitch": true, + "type": "pitch" + } + } + }, + { + "stat": { + "play": { + "details": { + "call": { + "code": "X", + "description": "In play, out(s)" + }, + "description": "In play, out(s)", + "code": "X", + "ballColor": "rgba(26, 86, 190, 1.0)", + "trailColor": "rgba(50, 0, 221, 1.0)", + "isInPlay": true, + "isStrike": false, + "isBall": false, + "type": { + "code": "SI", + "description": "Sinker" + }, + "awayScore": 2, + "homeScore": 1, + "isOut": true, + "hasReview": false, + "runnerGoing": true + }, + "count": { + "balls": 2, + "strikes": 1, + "outs": 3, + "inning": 4 + }, + "pitchData": { + "startSpeed": 95.6, + "endSpeed": 87.2, + "strikeZoneTop": 3.51, + "strikeZoneBottom": 1.68, + "coordinates": { + "aY": 32.62, + "aZ": -18.67, + "pfxX": 9.33, + "pfxZ": 6.94, + "pX": 0.18, + "pZ": 1.7, + "vX0": -4.59, + "vY0": -138.91, + "vZ0": -8.15, + "x": 110.31, + "y": 192.95, + "x0": 0.64, + "y0": 50, + "z0": 5.92, + "aX": 18.14 + }, + "breaks": { + "breakAngle": 39.6, + "breakLength": 6, + "breakY": 24, + "spinRate": 2174, + "spinDirection": 142 + }, + "zone": 8, + "typeConfidence": 2, + "plateTime": 0.4, + "extension": 5.76 + }, + "hitData": { + "launchSpeed": 107.2, + "launchAngle": 13, + "totalDistance": 334, + "trajectory": "line_drive", + "hardness": "medium", + "location": "8", + "coordinates": { + "coordX": 122.27, + "coordY": 92.16 + } + }, + "index": 6, + "playId": "6e0a125c-72e3-48b6-8748-c3fac153e673", + "pitchNumber": 4, + "startTime": "2022-10-13T20:44:19.661Z", + "endTime": "2022-10-13T20:44:26.045Z", + "isPitch": true, + "type": "pitch" + } + } + }, + { + "stat": { + "play": { + "details": { + "call": { + "code": "X", + "description": "In play, out(s)" + }, + "description": "In play, out(s)", + "code": "X", + "ballColor": "rgba(26, 86, 190, 1.0)", + "trailColor": "rgba(119, 0, 152, 1.0)", + "isInPlay": true, + "isStrike": false, + "isBall": false, + "type": { + "code": "FS", + "description": "Splitter" + }, + "awayScore": 2, + "homeScore": 1, + "isOut": true, + "hasReview": false + }, + "count": { + "balls": 1, + "strikes": 2, + "outs": 3, + "inning": 6 + }, + "pitchData": { + "startSpeed": 85.7, + "endSpeed": 78, + "strikeZoneTop": 3.51, + "strikeZoneBottom": 1.68, + "coordinates": { + "aY": 28.14, + "aZ": -32.75, + "pfxX": -6.85, + "pfxZ": -0.37, + "pX": 0.96, + "pZ": 1.28, + "vX0": 7.59, + "vY0": -124.41, + "vZ0": -4.81, + "x": 80.35, + "y": 204.26, + "x0": -1.26, + "y0": 50.01, + "z0": 5.99, + "aX": -10.59 + }, + "breaks": { + "breakAngle": 14.4, + "breakLength": 9.6, + "breakY": 24, + "spinRate": 1204, + "spinDirection": 249 + }, + "zone": 14, + "typeConfidence": 0.92, + "plateTime": 0.44, + "extension": 6.29 + }, + "hitData": { + "launchSpeed": 70.7, + "launchAngle": -5, + "totalDistance": 17, + "trajectory": "ground_ball", + "hardness": "medium", + "location": "6", + "coordinates": { + "coordX": 139.53, + "coordY": 163.02 + } + }, + "index": 5, + "playId": "ed9db705-9f3f-4944-b035-4a0a2b579de7", + "pitchNumber": 4, + "startTime": "2022-10-13T21:28:55.099Z", + "endTime": "2022-10-13T21:29:02.520Z", + "isPitch": true, + "type": "pitch" + } + } + }, + { + "stat": { + "play": { + "details": { + "call": { + "code": "C", + "description": "Called Strike" + }, + "description": "Called Strike", + "code": "C", + "ballColor": "rgba(170, 21, 11, 1.0)", + "trailColor": "rgba(0, 85, 254, 1.0)", + "isInPlay": false, + "isStrike": true, + "isBall": false, + "type": { + "code": "CH", + "description": "Changeup" + }, + "awayScore": 2, + "homeScore": 3, + "isOut": true, + "hasReview": false + }, + "count": { + "balls": 2, + "strikes": 3, + "outs": 3, + "inning": 8 + }, + "pitchData": { + "startSpeed": 90.9, + "endSpeed": 82.9, + "strikeZoneTop": 3.49, + "strikeZoneBottom": 1.65, + "coordinates": { + "aY": 29.91, + "aZ": -26.31, + "pfxX": -9.85, + "pfxZ": 3.33, + "pX": -0.58, + "pZ": 1.46, + "vX0": 5.09, + "vY0": -132.26, + "vZ0": -3.44, + "x": 139.06, + "y": 199.34, + "x0": -1.25, + "y0": 50, + "z0": 4.72, + "aX": -17.34 + }, + "breaks": { + "breakAngle": 31.2, + "breakLength": 7.2, + "breakY": 24, + "spinRate": 2093, + "spinDirection": 241 + }, + "zone": 13, + "typeConfidence": 0.9, + "plateTime": 0.42, + "extension": 6.08 + }, + "index": 4, + "playId": "69a72dee-3fdb-4790-845e-bdb7f01bf71a", + "pitchNumber": 5, + "startTime": "2022-10-13T22:28:07.930Z", + "endTime": "2022-10-13T22:28:12.844Z", + "isPitch": true, + "type": "pitch" + } + } + } ] + } ] + } \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/person/game_stats_player_shoei_ohtani.json b/tests/mock_tests/mock_json/stats/person/game_stats_player_shoei_ohtani.json new file mode 100644 index 00000000..68d621e1 --- /dev/null +++ b/tests/mock_tests/mock_json/stats/person/game_stats_player_shoei_ohtani.json @@ -0,0 +1,109 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "type": { + "displayName": "vsPlayer5Y" + }, + "group": { + "displayName": "hitting" + }, + "totalSplits": 0, + "exemptions": [], + "splits": [] + }, + { + "type": { + "displayName": "vsPlayer5Y" + }, + "group": { + "displayName": "pitching" + }, + "totalSplits": 1, + "exemptions": [], + "splits": [ + { + "stat": { + "gamesPlayed": 1, + "groundOuts": 0, + "airOuts": 3, + "doubles": 0, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 0, + "baseOnBalls": 0, + "intentionalWalks": 0, + "hits": 0, + "hitByPitch": 0, + "avg": ".000", + "atBats": 3, + "obp": ".000", + "slg": ".000", + "ops": ".000", + "groundIntoDoublePlay": 0, + "numberOfPitches": 7, + "inningsPitched": "1.0", + "outsPitched": 3, + "whip": "0.00", + "battersFaced": 3, + "outs": 3, + "gamesPitched": 1, + "balls": 2, + "strikes": 5, + "strikePercentage": ".710", + "hitBatsmen": 0, + "balks": 0, + "wildPitches": 0, + "totalBases": 0, + "groundOutsToAirouts": "0.00", + "rbi": 0, + "pitchesPerInning": "7.00", + "strikeoutWalkRatio": "-.--", + "strikeoutsPer9Inn": "0.00", + "walksPer9Inn": "0.00", + "hitsPer9Inn": "0.00", + "homeRunsPer9": "0.00", + "catchersInterference": 0, + "sacBunts": 0, + "sacFlies": 0 + }, + "team": { + "id": 108, + "name": "Los Angeles Angels", + "link": "/api/v1/teams/108" + }, + "opponent": { + "id": 136, + "name": "Seattle Mariners", + "link": "/api/v1/teams/136" + }, + "gameType": "R", + "numTeams": 1, + "pitcher": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271", + "firstName": "Shohei", + "lastName": "Ohtani" + }, + "batter": { + "id": 429664, + "fullName": "Robinson Cano", + "link": "/api/v1/people/429664", + "firstName": "Robinson", + "lastName": "Cano" + } + } + ] + }, + { + "type": { + "displayName": "playLog" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [] + } ] + } \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/person/game_stats_player_ty_france.json b/tests/mock_tests/mock_json/stats/person/game_stats_player_ty_france.json new file mode 100644 index 00000000..8b4747b3 --- /dev/null +++ b/tests/mock_tests/mock_json/stats/person/game_stats_player_ty_france.json @@ -0,0 +1,523 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "exemptions": [], + "splits": [ + { + "stat": { + "gamesStarted": 1, + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "assists": 1, + "putOuts": 5, + "errors": 0, + "chances": 6, + "fielding": ".000", + "passedBall": 0, + "pickoffs": 0 + }, + "type": "gameLog", + "group": "fielding" + }, + { + "stat": {}, + "type": "gameLog", + "group": "pitching" + }, + { + "stat": { + "gamesPlayed": 1, + "flyOuts": 0, + "groundOuts": 2, + "runs": 0, + "doubles": 0, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 2, + "baseOnBalls": 1, + "intentionalWalks": 0, + "hits": 0, + "hitByPitch": 0, + "atBats": 4, + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "groundIntoDoublePlay": 0, + "groundIntoTriplePlay": 0, + "plateAppearances": 5, + "totalBases": 0, + "rbi": 0, + "leftOnBase": 1, + "sacBunts": 0, + "sacFlies": 0, + "catchersInterference": 0, + "pickoffs": 0, + "atBatsPerHomeRun": "-.--" + }, + "type": "gameLog", + "group": "hitting" + } + ] + }, + { + "type": { + "displayName": "vsPlayer5Y" + }, + "group": { + "displayName": "hitting" + }, + "totalSplits": 1, + "exemptions": [], + "splits": [ + { + "stat": { + "gamesPlayed": 1, + "groundOuts": 0, + "airOuts": 0, + "doubles": 0, + "triples": 0, + "homeRuns": 0, + "strikeOuts": 1, + "baseOnBalls": 0, + "intentionalWalks": 0, + "hits": 0, + "hitByPitch": 0, + "avg": ".000", + "atBats": 1, + "obp": ".000", + "slg": ".000", + "ops": ".000", + "groundIntoDoublePlay": 0, + "groundIntoTriplePlay": 0, + "numberOfPitches": 5, + "plateAppearances": 1, + "totalBases": 0, + "rbi": 0, + "leftOnBase": 1, + "sacBunts": 0, + "sacFlies": 0, + "babip": ".---", + "groundOutsToAirouts": "-.--", + "catchersInterference": 0, + "atBatsPerHomeRun": "-.--" + }, + "team": { + "id": 136, + "name": "Seattle Mariners", + "link": "/api/v1/teams/136" + }, + "opponent": { + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "gameType": "D", + "numTeams": 1, + "pitcher": {}, + "batter": {} + } + ] + }, + { + "type": { + "displayName": "playLog" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "play": { + "details": { + "call": { + "code": "C", + "description": "Called Strike" + }, + "description": "Called Strike", + "code": "C", + "ballColor": "rgba(170, 21, 11, 1.0)", + "trailColor": "rgba(50, 0, 221, 1.0)", + "isInPlay": false, + "isStrike": true, + "isBall": false, + "type": { + "code": "SI", + "description": "Sinker" + }, + "awayScore": 0, + "homeScore": 0, + "hasReview": false + }, + "count": { + "balls": 3, + "strikes": 3, + "outs": 2, + "inning": 1 + }, + "pitchData": { + "startSpeed": 94.8, + "endSpeed": 85.4, + "strikeZoneTop": 3.24, + "strikeZoneBottom": 1.48, + "coordinates": { + "aY": 36.27, + "aZ": -23.82, + "pfxX": 9.76, + "pfxZ": 4.43, + "pX": -0.27, + "pZ": 1.87, + "vX0": -5.9, + "vY0": -137.7, + "vZ0": -6.7, + "x": 127.18, + "y": 188.25, + "x0": 0.66, + "y0": 50, + "z0": 6, + "aX": 18.37 + }, + "breaks": { + "breakAngle": 33.6, + "breakLength": 7.2, + "breakY": 24, + "spinRate": 2124, + "spinDirection": 144 + }, + "zone": 7, + "typeConfidence": 2, + "plateTime": 0.4, + "extension": 5.82 + }, + "index": 6, + "playId": "6255ee72-8097-4c96-aacb-519483e13666", + "pitchNumber": 7, + "startTime": "2022-10-13T19:43:02.900Z", + "endTime": "2022-10-13T19:43:07.299Z", + "isPitch": true, + "type": "pitch" + } + } + }, + { + "stat": { + "play": { + "details": { + "call": { + "code": "X", + "description": "In play, out(s)" + }, + "description": "In play, out(s)", + "code": "X", + "ballColor": "rgba(26, 86, 190, 1.0)", + "trailColor": "rgba(50, 0, 221, 1.0)", + "isInPlay": true, + "isStrike": false, + "isBall": false, + "type": { + "code": "SI", + "description": "Sinker" + }, + "awayScore": 0, + "homeScore": 1, + "hasReview": false + }, + "count": { + "balls": 2, + "strikes": 2, + "outs": 1, + "inning": 4 + }, + "pitchData": { + "startSpeed": 96, + "endSpeed": 88.3, + "strikeZoneTop": 3.15, + "strikeZoneBottom": 1.45, + "coordinates": { + "aY": 30.26, + "aZ": -22.53, + "pfxX": 6.75, + "pfxZ": 4.85, + "pX": -0.04, + "pZ": 3.06, + "vX0": -3.81, + "vY0": -139.81, + "vZ0": -4.33, + "x": 118.34, + "y": 156.23, + "x0": 0.47, + "y0": 50, + "z0": 6.1, + "aX": 13.42 + }, + "breaks": { + "breakAngle": 28.8, + "breakLength": 6, + "breakY": 24, + "spinRate": 2165, + "spinDirection": 152 + }, + "zone": 2, + "typeConfidence": 2, + "plateTime": 0.39, + "extension": 5.86 + }, + "hitData": { + "launchSpeed": 96.5, + "launchAngle": -19, + "totalDistance": 10, + "trajectory": "ground_ball", + "hardness": "medium", + "location": "4", + "coordinates": { + "coordX": 126.47, + "coordY": 139.76 + } + }, + "index": 4, + "playId": "04096939-df60-4c28-b2d3-b38a3a7b7b12", + "pitchNumber": 5, + "startTime": "2022-10-13T20:29:34.256Z", + "endTime": "2022-10-13T20:29:41.789Z", + "isPitch": true, + "type": "pitch" + } + } + }, + { + "stat": { + "play": { + "details": { + "call": { + "code": "X", + "description": "In play, out(s)" + }, + "description": "In play, out(s)", + "code": "X", + "ballColor": "rgba(26, 86, 190, 1.0)", + "trailColor": "rgba(50, 0, 221, 1.0)", + "isInPlay": true, + "isStrike": false, + "isBall": false, + "type": { + "code": "SI", + "description": "Sinker" + }, + "awayScore": 2, + "homeScore": 1, + "hasReview": false + }, + "count": { + "balls": 1, + "strikes": 0, + "outs": 1, + "inning": 6 + }, + "pitchData": { + "startSpeed": 94.2, + "endSpeed": 86.2, + "strikeZoneTop": 3.15, + "strikeZoneBottom": 1.45, + "coordinates": { + "aY": 31.03, + "aZ": -25.02, + "pfxX": 9.7, + "pfxZ": 3.77, + "pX": 0.18, + "pZ": 1.99, + "vX0": -4.66, + "vY0": -136.98, + "vZ0": -6.26, + "x": 110.32, + "y": 185.12, + "x0": 0.64, + "y0": 50, + "z0": 6.02, + "aX": 18.38 + }, + "breaks": { + "breakAngle": 33.6, + "breakLength": 7.2, + "breakY": 24, + "spinRate": 2280, + "spinDirection": 143 + }, + "zone": 8, + "typeConfidence": 2, + "plateTime": 0.4, + "extension": 5.77 + }, + "hitData": { + "launchSpeed": 91.1, + "launchAngle": -5, + "totalDistance": 21, + "trajectory": "ground_ball", + "hardness": "medium", + "location": "6", + "coordinates": { + "coordX": 110.09, + "coordY": 155.21 + } + }, + "index": 1, + "playId": "f72c09df-338d-4d57-bd78-7aba9840c162", + "pitchNumber": 2, + "startTime": "2022-10-13T21:12:34.175Z", + "endTime": "2022-10-13T21:12:42.037Z", + "isPitch": true, + "type": "pitch" + } + } + }, + { + "stat": { + "play": { + "details": { + "call": { + "code": "B", + "description": "Ball" + }, + "description": "Ball", + "code": "B", + "ballColor": "rgba(39, 161, 39, 1.0)", + "trailColor": "rgba(188, 0, 33, 1.0)", + "isInPlay": false, + "isStrike": false, + "isBall": true, + "type": { + "code": "FF", + "description": "Four-Seam Fastball" + }, + "awayScore": 2, + "homeScore": 3, + "hasReview": false, + "runnerGoing": true + }, + "count": { + "balls": 4, + "strikes": 2, + "outs": 2, + "inning": 7 + }, + "pitchData": { + "startSpeed": 97, + "endSpeed": 87.3, + "strikeZoneTop": 3.22, + "strikeZoneBottom": 1.45, + "coordinates": { + "aY": 36.15, + "aZ": -12.18, + "pfxX": -5.21, + "pfxZ": 10.08, + "pX": 1.81, + "pZ": 3.73, + "vX0": 9.29, + "vY0": -140.92, + "vZ0": -1.33, + "x": 47.84, + "y": 137.96, + "x0": -0.87, + "y0": 50, + "z0": 5.01, + "aX": -10.33 + }, + "breaks": { + "breakAngle": 32.4, + "breakLength": 3.6, + "breakY": 24, + "spinRate": 2487, + "spinDirection": 221 + }, + "zone": 12, + "typeConfidence": 0.92, + "plateTime": 0.39, + "extension": 6.08 + }, + "index": 7, + "playId": "457ababc-dbd8-4177-869f-d3805ae02e94", + "pitchNumber": 6, + "startTime": "2022-10-13T21:56:46.455Z", + "endTime": "2022-10-13T21:56:54.799Z", + "isPitch": true, + "type": "pitch" + } + } + }, + { + "stat": { + "play": { + "details": { + "call": { + "code": "W", + "description": "Swinging Strike (Blocked)" + }, + "description": "Swinging Strike (Blocked)", + "code": "W", + "ballColor": "rgba(170, 21, 11, 1.0)", + "trailColor": "rgba(0, 34, 255, 1.0)", + "isInPlay": false, + "isStrike": true, + "isBall": false, + "type": { + "code": "CU", + "description": "Curveball" + }, + "awayScore": 2, + "homeScore": 4, + "hasReview": false + }, + "count": { + "balls": 2, + "strikes": 3, + "outs": 3, + "inning": 9 + }, + "pitchData": { + "startSpeed": 82.8, + "endSpeed": 75.1, + "strikeZoneTop": 3.15, + "strikeZoneBottom": 1.45, + "coordinates": { + "aY": 29.92, + "aZ": -42.99, + "pfxX": 8.09, + "pfxZ": -7.61, + "pX": 1.18, + "pZ": -0.1, + "vX0": 0.63, + "vY0": -120.31, + "vZ0": -4.62, + "x": 72.09, + "y": 241.54, + "x0": -0.14, + "y0": 50, + "z0": 5.78, + "aX": 11.49 + }, + "breaks": { + "breakAngle": 14.4, + "breakLength": 13.2, + "breakY": 24, + "spinRate": 3386, + "spinDirection": 23 + }, + "zone": 14, + "typeConfidence": 0.9, + "plateTime": 0.46, + "extension": 6.17 + }, + "index": 4, + "playId": "68c2997f-095d-4239-acc4-3c6252afaf68", + "pitchNumber": 5, + "startTime": "2022-10-13T22:54:33.690Z", + "endTime": "2022-10-13T22:54:42.383Z", + "isPitch": true, + "type": "pitch" + } + } + } ] + } ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/person/hitting_player_pitchlog.json b/tests/mock_tests/mock_json/stats/person/hitting_player_pitchlog.json new file mode 100644 index 00000000..d7ceafed --- /dev/null +++ b/tests/mock_tests/mock_json/stats/person/hitting_player_pitchlog.json @@ -0,0 +1,519 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "type": { + "displayName": "pitchLog" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2022", + "stat": { + "play": { + "details": { + "call": { + "code": "C", + "description": "Strike - Called" + }, + "event": "called_strike", + "eventType": "called_strike", + "isInPlay": false, + "isStrike": true, + "isBall": false, + "isBaseHit": false, + "isAtBat": false, + "isPlateAppearance": false, + "type": { + "code": "FF", + "description": "Four-seam FB" + }, + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + } + }, + "count": { + "balls": 0, + "strikes": 0, + "outs": 1, + "inning": 1, + "isTopInning": false, + "runnerOn1b": false, + "runnerOn2b": false, + "runnerOn3b": false + }, + "playId": "80ead676-b428-4993-b28a-c6a9b286fe3a", + "pitchNumber": 1, + "atBatNumber": 6, + "isPitch": true + } + }, + "team": { + "id": 120, + "name": "Washington Nationals", + "link": "/api/v1/teams/120" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "opponent": { + "id": 121, + "name": "New York Mets", + "link": "/api/v1/teams/121" + }, + "date": "2022-04-07", + "gameType": "R", + "isHome": true, + "pitcher": { + "id": 656731, + "fullName": "Tylor Megill", + "link": "/api/v1/people/656731" + }, + "batter": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "game": { + "gamePk": 662571, + "link": "/api/v1.1/game/662571/feed/live", + "content": { + "link": "/api/v1/game/662571/content" + }, + "gameNumber": 1, + "dayNight": "day" + } + }, + { + "season": "2022", + "stat": { + "play": { + "details": { + "call": { + "code": "B", + "description": "Ball - Called" + }, + "event": "ball", + "eventType": "ball", + "isInPlay": false, + "isStrike": false, + "isBall": true, + "isBaseHit": false, + "isAtBat": false, + "isPlateAppearance": false, + "type": { + "code": "FF", + "description": "Four-seam FB" + }, + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + } + }, + "count": { + "balls": 0, + "strikes": 1, + "outs": 1, + "inning": 1, + "isTopInning": false, + "runnerOn1b": false, + "runnerOn2b": false, + "runnerOn3b": false + }, + "playId": "0d7be5e9-8de3-4613-b1cb-8385f5b382c0", + "pitchNumber": 2, + "atBatNumber": 6, + "isPitch": true + } + }, + "team": { + "id": 120, + "name": "Washington Nationals", + "link": "/api/v1/teams/120" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "opponent": { + "id": 121, + "name": "New York Mets", + "link": "/api/v1/teams/121" + }, + "date": "2022-04-07", + "gameType": "R", + "isHome": true, + "pitcher": { + "id": 656731, + "fullName": "Tylor Megill", + "link": "/api/v1/people/656731" + }, + "batter": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "game": { + "gamePk": 662571, + "link": "/api/v1.1/game/662571/feed/live", + "content": { + "link": "/api/v1/game/662571/content" + }, + "gameNumber": 1, + "dayNight": "day" + } + }, + { + "season": "2022", + "stat": { + "play": { + "details": { + "call": { + "code": "B", + "description": "Ball - Called" + }, + "event": "ball", + "eventType": "ball", + "isInPlay": false, + "isStrike": false, + "isBall": true, + "isBaseHit": false, + "isAtBat": false, + "isPlateAppearance": false, + "type": { + "code": "CH", + "description": "Changeup" + }, + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + } + }, + "count": { + "balls": 1, + "strikes": 1, + "outs": 1, + "inning": 1, + "isTopInning": false, + "runnerOn1b": false, + "runnerOn2b": false, + "runnerOn3b": false + }, + "playId": "d8c81931-04a6-4427-90de-b6ab7a5dc6ac", + "pitchNumber": 3, + "atBatNumber": 6, + "isPitch": true + } + }, + "team": { + "id": 120, + "name": "Washington Nationals", + "link": "/api/v1/teams/120" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "opponent": { + "id": 121, + "name": "New York Mets", + "link": "/api/v1/teams/121" + }, + "date": "2022-04-07", + "gameType": "R", + "isHome": true, + "pitcher": { + "id": 656731, + "fullName": "Tylor Megill", + "link": "/api/v1/people/656731" + }, + "batter": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "game": { + "gamePk": 662571, + "link": "/api/v1.1/game/662571/feed/live", + "content": { + "link": "/api/v1/game/662571/content" + }, + "gameNumber": 1, + "dayNight": "day" + } + }, + { + "season": "2022", + "stat": { + "play": { + "details": { + "call": { + "code": "X", + "description": "Hit Into Play - Out(s)" + }, + "description": "Juan Soto grounds out, second baseman Robinson Cano to first baseman Pete Alonso. ", + "event": "field_out", + "eventType": "field_out", + "isInPlay": true, + "isStrike": false, + "isBall": false, + "isBaseHit": false, + "isAtBat": true, + "isPlateAppearance": true, + "type": { + "code": "CH", + "description": "Changeup" + }, + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + } + }, + "count": { + "balls": 2, + "strikes": 1, + "outs": 1, + "inning": 1, + "isTopInning": false, + "runnerOn1b": false, + "runnerOn2b": false, + "runnerOn3b": false + }, + "playId": "11ecdb59-e9f1-4d83-9d2a-5501e1411539", + "pitchNumber": 4, + "atBatNumber": 6, + "isPitch": true + } + }, + "team": { + "id": 120, + "name": "Washington Nationals", + "link": "/api/v1/teams/120" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "opponent": { + "id": 121, + "name": "New York Mets", + "link": "/api/v1/teams/121" + }, + "date": "2022-04-07", + "gameType": "R", + "isHome": true, + "pitcher": { + "id": 656731, + "fullName": "Tylor Megill", + "link": "/api/v1/people/656731" + }, + "batter": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "game": { + "gamePk": 662571, + "link": "/api/v1.1/game/662571/feed/live", + "content": { + "link": "/api/v1/game/662571/content" + }, + "gameNumber": 1, + "dayNight": "day" + } + }, + { + "season": "2022", + "stat": { + "play": { + "details": { + "call": { + "code": "C", + "description": "Strike - Called" + }, + "event": "called_strike", + "eventType": "called_strike", + "isInPlay": false, + "isStrike": true, + "isBall": false, + "isBaseHit": false, + "isAtBat": false, + "isPlateAppearance": false, + "type": { + "code": "CH", + "description": "Changeup" + }, + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + } + }, + "count": { + "balls": 0, + "strikes": 0, + "outs": 1, + "inning": 3, + "isTopInning": false, + "runnerOn1b": true, + "runnerOn2b": false, + "runnerOn3b": true + }, + "playId": "6e2ef485-b459-4703-89d2-7faae059199c", + "pitchNumber": 1, + "atBatNumber": 21, + "isPitch": true + } + }, + "team": { + "id": 120, + "name": "Washington Nationals", + "link": "/api/v1/teams/120" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "opponent": { + "id": 121, + "name": "New York Mets", + "link": "/api/v1/teams/121" + }, + "date": "2022-04-07", + "gameType": "R", + "isHome": true, + "pitcher": { + "id": 656731, + "fullName": "Tylor Megill", + "link": "/api/v1/people/656731" + }, + "batter": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "game": { + "gamePk": 662571, + "link": "/api/v1.1/game/662571/feed/live", + "content": { + "link": "/api/v1/game/662571/content" + }, + "gameNumber": 1, + "dayNight": "day" + } + }, + { + "season": "2022", + "stat": { + "play": { + "details": { + "call": { + "code": "B", + "description": "Ball - Called" + }, + "event": "ball", + "eventType": "ball", + "isInPlay": false, + "isStrike": false, + "isBall": true, + "isBaseHit": false, + "isAtBat": false, + "isPlateAppearance": false, + "type": { + "code": "CH", + "description": "Changeup" + }, + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + } + }, + "count": { + "balls": 0, + "strikes": 1, + "outs": 1, + "inning": 3, + "isTopInning": false, + "runnerOn1b": true, + "runnerOn2b": false, + "runnerOn3b": true + }, + "playId": "7108025e-7980-4571-bf7e-dba9eb0909af", + "pitchNumber": 2, + "atBatNumber": 21, + "isPitch": true + } + }, + "team": { + "id": 120, + "name": "Washington Nationals", + "link": "/api/v1/teams/120" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "opponent": { + "id": 121, + "name": "New York Mets", + "link": "/api/v1/teams/121" + }, + "date": "2022-04-07", + "gameType": "R", + "isHome": true, + "pitcher": { + "id": 656731, + "fullName": "Tylor Megill", + "link": "/api/v1/people/656731" + }, + "batter": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "game": { + "gamePk": 662571, + "link": "/api/v1.1/game/662571/feed/live", + "content": { + "link": "/api/v1/game/662571/content" + }, + "gameNumber": 1, + "dayNight": "day" + } + } ] + } ] + } \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/person/hitting_player_playlog.json b/tests/mock_tests/mock_json/stats/person/hitting_player_playlog.json new file mode 100644 index 00000000..809a64a9 --- /dev/null +++ b/tests/mock_tests/mock_json/stats/person/hitting_player_playlog.json @@ -0,0 +1,184 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "type": { + "displayName": "playLog" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2022", + "stat": { + "play": { + "details": { + "call": { + "code": "X", + "description": "Hit Into Play - Out(s)" + }, + "description": "Juan Soto grounds out, second baseman Robinson Cano to first baseman Pete Alonso. ", + "event": "field_out", + "eventType": "field_out", + "isInPlay": true, + "isStrike": false, + "isBall": false, + "isBaseHit": false, + "isAtBat": true, + "isPlateAppearance": true, + "type": { + "code": "CH", + "description": "Changeup" + }, + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + } + }, + "count": { + "balls": 2, + "strikes": 1, + "outs": 1, + "inning": 1, + "isTopInning": false, + "runnerOn1b": false, + "runnerOn2b": false, + "runnerOn3b": false + }, + "playId": "11ecdb59-e9f1-4d83-9d2a-5501e1411539", + "pitchNumber": 4, + "atBatNumber": 6, + "isPitch": true + } + }, + "team": { + "id": 120, + "name": "Washington Nationals", + "link": "/api/v1/teams/120" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "opponent": { + "id": 121, + "name": "New York Mets", + "link": "/api/v1/teams/121" + }, + "date": "2022-04-07", + "gameType": "R", + "isHome": true, + "pitcher": { + "id": 656731, + "fullName": "Tylor Megill", + "link": "/api/v1/people/656731" + }, + "batter": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "game": { + "gamePk": 662571, + "link": "/api/v1.1/game/662571/feed/live", + "content": { + "link": "/api/v1/game/662571/content" + }, + "gameNumber": 1, + "dayNight": "day" + } + }, + { + "season": "2022", + "stat": { + "play": { + "details": { + "call": { + "code": "S", + "description": "Strike - Swinging" + }, + "description": "Juan Soto strikes out swinging. ", + "event": "strikeout", + "eventType": "strikeout", + "isInPlay": false, + "isStrike": true, + "isBall": false, + "isBaseHit": false, + "isAtBat": true, + "isPlateAppearance": true, + "type": { + "code": "FF", + "description": "Four-seam FB" + }, + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + } + }, + "count": { + "balls": 2, + "strikes": 2, + "outs": 1, + "inning": 3, + "isTopInning": false, + "runnerOn1b": true, + "runnerOn2b": false, + "runnerOn3b": true + }, + "playId": "af4ab082-2308-4320-9445-7d40a4947bcf", + "pitchNumber": 5, + "atBatNumber": 21, + "isPitch": true + } + }, + "team": { + "id": 120, + "name": "Washington Nationals", + "link": "/api/v1/teams/120" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "opponent": { + "id": 121, + "name": "New York Mets", + "link": "/api/v1/teams/121" + }, + "date": "2022-04-07", + "gameType": "R", + "isHome": true, + "pitcher": { + "id": 656731, + "fullName": "Tylor Megill", + "link": "/api/v1/people/656731" + }, + "batter": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "game": { + "gamePk": 662571, + "link": "/api/v1.1/game/662571/feed/live", + "content": { + "link": "/api/v1/game/662571/content" + }, + "gameNumber": 1, + "dayNight": "day" + } + }] +}] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/person/hitting_player_stats.json b/tests/mock_tests/mock_json/stats/person/hitting_player_stats.json new file mode 100644 index 00000000..d5e6edce --- /dev/null +++ b/tests/mock_tests/mock_json/stats/person/hitting_player_stats.json @@ -0,0 +1,465 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "type": { + "displayName": "career" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "gamesPlayed": 617, + "groundOuts": 627, + "airOuts": 461, + "runs": 430, + "doubles": 116, + "triples": 10, + "homeRuns": 125, + "strikeOuts": 448, + "baseOnBalls": 508, + "intentionalWalks": 54, + "hits": 612, + "hitByPitch": 10, + "avg": ".287", + "atBats": 2136, + "obp": ".424", + "slg": ".526", + "ops": ".950", + "caughtStealing": 14, + "stolenBases": 38, + "stolenBasePercentage": ".731", + "groundIntoDoublePlay": 56, + "numberOfPitches": 11012, + "plateAppearances": 2667, + "totalBases": 1123, + "rbi": 374, + "leftOnBase": 898, + "sacBunts": 1, + "sacFlies": 11, + "babip": ".309", + "groundOutsToAirouts": "1.36", + "catchersInterference": 1, + "atBatsPerHomeRun": "17.09" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R", + "numTeams": 2 + } + ] + }, + { + "type": { + "displayName": "careerAdvanced" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "plateAppearances": 2667, + "totalBases": 1123, + "leftOnBase": 898, + "sacBunts": 1, + "sacFlies": 11, + "babip": ".309", + "extraBaseHits": 251, + "hitByPitch": 10, + "gidp": 56, + "gidpOpp": 383, + "numberOfPitches": 11012, + "pitchesPerPlateAppearance": "4.129", + "walksPerPlateAppearance": ".190", + "strikeoutsPerPlateAppearance": ".168", + "homeRunsPerPlateAppearance": ".047", + "walksPerStrikeout": "1.134", + "iso": ".239", + "reachedOnError": 16, + "walkOffs": 1, + "flyOuts": 262, + "totalSwings": 4109, + "swingAndMisses": 881, + "ballsInPlay": 1700, + "popOuts": 76, + "lineOuts": 123, + "groundOuts": 627, + "flyHits": 139, + "popHits": 1, + "lineHits": 259, + "groundHits": 213 + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R", + "numTeams": 2 + } + ] + }, + { + "type": { + "displayName": "season" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2022", + "stat": { + "gamesPlayed": 153, + "groundOuts": 162, + "airOuts": 139, + "runs": 93, + "doubles": 25, + "triples": 2, + "homeRuns": 27, + "strikeOuts": 96, + "baseOnBalls": 135, + "intentionalWalks": 6, + "hits": 127, + "hitByPitch": 4, + "avg": ".242", + "atBats": 524, + "obp": ".401", + "slg": ".452", + "ops": ".853", + "caughtStealing": 2, + "stolenBases": 6, + "stolenBasePercentage": ".750", + "groundIntoDoublePlay": 12, + "numberOfPitches": 2765, + "plateAppearances": 664, + "totalBases": 237, + "rbi": 62, + "leftOnBase": 203, + "sacBunts": 0, + "sacFlies": 0, + "babip": ".249", + "groundOutsToAirouts": "1.17", + "catchersInterference": 1, + "atBatsPerHomeRun": "19.41" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R", + "numTeams": 2 + }, + { + "season": "2022", + "stat": { + "gamesPlayed": 101, + "groundOuts": 109, + "airOuts": 87, + "runs": 62, + "doubles": 17, + "triples": 1, + "homeRuns": 21, + "strikeOuts": 62, + "baseOnBalls": 91, + "intentionalWalks": 4, + "hits": 84, + "hitByPitch": 3, + "avg": ".246", + "atBats": 342, + "obp": ".408", + "slg": ".485", + "ops": ".893", + "caughtStealing": 2, + "stolenBases": 6, + "stolenBasePercentage": ".750", + "groundIntoDoublePlay": 11, + "numberOfPitches": 1819, + "plateAppearances": 436, + "totalBases": 166, + "rbi": 46, + "leftOnBase": 137, + "sacBunts": 0, + "sacFlies": 0, + "babip": ".243", + "groundOutsToAirouts": "1.25", + "catchersInterference": 0, + "atBatsPerHomeRun": "16.29" + }, + "team": { + "id": 120, + "name": "Washington Nationals", + "link": "/api/v1/teams/120" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + }, + { + "season": "2022", + "stat": { + "gamesPlayed": 52, + "groundOuts": 53, + "airOuts": 52, + "runs": 31, + "doubles": 8, + "triples": 1, + "homeRuns": 6, + "strikeOuts": 34, + "baseOnBalls": 44, + "intentionalWalks": 2, + "hits": 43, + "hitByPitch": 1, + "avg": ".236", + "atBats": 182, + "obp": ".388", + "slg": ".390", + "ops": ".778", + "caughtStealing": 0, + "stolenBases": 0, + "stolenBasePercentage": ".---", + "groundIntoDoublePlay": 1, + "numberOfPitches": 946, + "plateAppearances": 228, + "totalBases": 71, + "rbi": 16, + "leftOnBase": 66, + "sacBunts": 0, + "sacFlies": 0, + "babip": ".261", + "groundOutsToAirouts": "1.02", + "catchersInterference": 1, + "atBatsPerHomeRun": "30.33" + }, + "team": { + "id": 135, + "name": "San Diego Padres", + "link": "/api/v1/teams/135" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } + ] + }, + { + "type": { + "displayName": "seasonAdvanced" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2022", + "stat": { + "plateAppearances": 664, + "totalBases": 237, + "leftOnBase": 203, + "sacBunts": 0, + "sacFlies": 0, + "babip": ".249", + "extraBaseHits": 54, + "hitByPitch": 4, + "gidp": 12, + "gidpOpp": 86, + "numberOfPitches": 2765, + "pitchesPerPlateAppearance": "4.164", + "walksPerPlateAppearance": ".203", + "strikeoutsPerPlateAppearance": ".145", + "homeRunsPerPlateAppearance": ".041", + "walksPerStrikeout": "1.406", + "iso": ".210", + "reachedOnError": 6, + "walkOffs": 0, + "flyOuts": 82, + "totalSwings": 976, + "swingAndMisses": 187, + "ballsInPlay": 428, + "popOuts": 26, + "lineOuts": 31, + "groundOuts": 162, + "flyHits": 34, + "popHits": 1, + "lineHits": 51, + "groundHits": 41 + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R", + "numTeams": 2 + }, + { + "season": "2022", + "stat": { + "plateAppearances": 436, + "totalBases": 166, + "leftOnBase": 137, + "sacBunts": 0, + "sacFlies": 0, + "babip": ".243", + "extraBaseHits": 39, + "hitByPitch": 3, + "gidp": 11, + "gidpOpp": 59, + "numberOfPitches": 1819, + "pitchesPerPlateAppearance": "4.172", + "walksPerPlateAppearance": ".209", + "strikeoutsPerPlateAppearance": ".142", + "homeRunsPerPlateAppearance": ".048", + "walksPerStrikeout": "1.468", + "iso": ".240", + "reachedOnError": 3, + "walkOffs": 0, + "flyOuts": 50, + "totalSwings": 645, + "swingAndMisses": 130, + "ballsInPlay": 280, + "popOuts": 16, + "lineOuts": 21, + "groundOuts": 109, + "flyHits": 27, + "popHits": 1, + "lineHits": 33, + "groundHits": 23 + }, + "team": { + "id": 120, + "name": "Washington Nationals", + "link": "/api/v1/teams/120" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + }, + { + "season": "2022", + "stat": { + "plateAppearances": 228, + "totalBases": 71, + "leftOnBase": 66, + "sacBunts": 0, + "sacFlies": 0, + "babip": ".261", + "extraBaseHits": 15, + "hitByPitch": 1, + "gidp": 1, + "gidpOpp": 27, + "numberOfPitches": 946, + "pitchesPerPlateAppearance": "4.149", + "walksPerPlateAppearance": ".193", + "strikeoutsPerPlateAppearance": ".149", + "homeRunsPerPlateAppearance": ".026", + "walksPerStrikeout": "1.294", + "iso": ".154", + "reachedOnError": 3, + "walkOffs": 0, + "flyOuts": 32, + "totalSwings": 331, + "swingAndMisses": 57, + "ballsInPlay": 148, + "popOuts": 10, + "lineOuts": 10, + "groundOuts": 53, + "flyHits": 7, + "popHits": 0, + "lineHits": 18, + "groundHits": 18 + }, + "team": { + "id": 135, + "name": "San Diego Padres", + "link": "/api/v1/teams/135" + }, + "player": { + "id": 665742, + "fullName": "Juan Soto", + "link": "/api/v1/people/665742" + }, + "league": { + "id": 104, + "name": "National League", + "link": "/api/v1/league/104" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } ] + } ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/person/hotcoldzone.json b/tests/mock_tests/mock_json/stats/person/hotcoldzone.json new file mode 100644 index 00000000..87727b37 --- /dev/null +++ b/tests/mock_tests/mock_json/stats/person/hotcoldzone.json @@ -0,0 +1,438 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "type": { + "displayName": "hotColdZones" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "name": "battingAverage", + "zones": [ + { + "zone": "01", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": ".278" + }, + { + "zone": "02", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".462" + }, + { + "zone": "03", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".435" + }, + { + "zone": "04", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": ".304" + }, + { + "zone": "05", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": ".345" + }, + { + "zone": "06", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": ".308" + }, + { + "zone": "07", + "color": "rgba(150, 188, 255, .55)", + "temp": "cool", + "value": ".224" + }, + { + "zone": "08", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".415" + }, + { + "zone": "09", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".367" + }, + { + "zone": "11", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".184" + }, + { + "zone": "12", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": ".263" + }, + { + "zone": "13", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".144" + }, + { + "zone": "14", + "color": "rgba(150, 188, 255, .55)", + "temp": "cool", + "value": ".212" + } + ] + } + }, + { + "stat": { + "name": "onBasePlusSlugging", + "zones": [ + { + "zone": "01", + "color": "rgba(150, 188, 255, .55)", + "temp": "cool", + "value": ".611" + }, + { + "zone": "02", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "1.769" + }, + { + "zone": "03", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "1.208" + }, + { + "zone": "04", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": ".875" + }, + { + "zone": "05", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": "1.068" + }, + { + "zone": "06", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": "1.075" + }, + { + "zone": "07", + "color": "rgba(150, 188, 255, .55)", + "temp": "cool", + "value": ".633" + }, + { + "zone": "08", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "1.317" + }, + { + "zone": "09", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": "1.033" + }, + { + "zone": "11", + "color": "rgba(150, 188, 255, .55)", + "temp": "cool", + "value": ".607" + }, + { + "zone": "12", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": ".696" + }, + { + "zone": "13", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".420" + }, + { + "zone": "14", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": ".667" + } + ] + } + }, + { + "stat": { + "name": "onBasePercentage", + "zones": [ + { + "zone": "01", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": ".278" + }, + { + "zone": "02", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".462" + }, + { + "zone": "03", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".458" + }, + { + "zone": "04", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": ".304" + }, + { + "zone": "05", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": ".339" + }, + { + "zone": "06", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": ".300" + }, + { + "zone": "07", + "color": "rgba(150, 188, 255, .55)", + "temp": "cool", + "value": ".224" + }, + { + "zone": "08", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".415" + }, + { + "zone": "09", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".367" + }, + { + "zone": "11", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".446" + }, + { + "zone": "12", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".391" + }, + { + "zone": "13", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": ".261" + }, + { + "zone": "14", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".406" + } + ] + } + }, + { + "stat": { + "name": "sluggingPercentage", + "zones": [ + { + "zone": "01", + "color": "rgba(150, 188, 255, .55)", + "temp": "cool", + "value": ".333" + }, + { + "zone": "02", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "1.308" + }, + { + "zone": "03", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": ".750" + }, + { + "zone": "04", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": ".571" + }, + { + "zone": "05", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": ".729" + }, + { + "zone": "06", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": ".775" + }, + { + "zone": "07", + "color": "rgba(255, 255, 255, 0.55)", + "temp": "lukewarm", + "value": ".408" + }, + { + "zone": "08", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": ".902" + }, + { + "zone": "09", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": ".667" + }, + { + "zone": "11", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".161" + }, + { + "zone": "12", + "color": "rgba(150, 188, 255, .55)", + "temp": "cool", + "value": ".304" + }, + { + "zone": "13", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": ".159" + }, + { + "zone": "14", + "color": "rgba(150, 188, 255, .55)", + "temp": "cool", + "value": ".261" + } + ] + } + }, + { + "stat": { + "name": "exitVelocity", + "zones": [ + { + "zone": "01", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "96.84" + }, + { + "zone": "02", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "93.93" + }, + { + "zone": "03", + "color": "rgba(150, 188, 255, .55)", + "temp": "cool", + "value": "88.65" + }, + { + "zone": "04", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "97.78" + }, + { + "zone": "05", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "97.09" + }, + { + "zone": "06", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "97.97" + }, + { + "zone": "07", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "95.06" + }, + { + "zone": "08", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "97.49" + }, + { + "zone": "09", + "color": "rgba(214, 41, 52, .55)", + "temp": "hot", + "value": "100.90" + }, + { + "zone": "11", + "color": "rgba(150, 188, 255, .55)", + "temp": "cool", + "value": "87.63" + }, + { + "zone": "12", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": "85.65" + }, + { + "zone": "13", + "color": "rgba(6, 90, 238, .55)", + "temp": "cold", + "value": "82.70" + }, + { + "zone": "14", + "color": "rgba(234, 147, 153, .55)", + "temp": "warm", + "value": "91.63" + } + ] + } + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/person/pitching_player_pitchlog.json b/tests/mock_tests/mock_json/stats/person/pitching_player_pitchlog.json new file mode 100644 index 00000000..673ad08e --- /dev/null +++ b/tests/mock_tests/mock_json/stats/person/pitching_player_pitchlog.json @@ -0,0 +1,182 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "type": { + "displayName": "pitchLog" + }, + "group": { + "displayName": "pitching" + }, + "exemptions": [], + "splits": [ + { + "season": "2022", + "stat": { + "play": { + "details": { + "call": { + "code": "B", + "description": "Ball - Called" + }, + "event": "ball", + "eventType": "ball", + "isInPlay": false, + "isStrike": false, + "isBall": true, + "isBaseHit": false, + "isAtBat": false, + "isPlateAppearance": false, + "type": { + "code": "FF", + "description": "Four-seam FB" + }, + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + } + }, + "count": { + "balls": 0, + "strikes": 0, + "outs": 0, + "inning": 1, + "isTopInning": true, + "runnerOn1b": false, + "runnerOn2b": false, + "runnerOn3b": false + }, + "playId": "223aeedc-89db-49cf-8d29-43745ea41933", + "pitchNumber": 1, + "atBatNumber": 1, + "isPitch": true + } + }, + "team": { + "id": 108, + "name": "Los Angeles Angels", + "link": "/api/v1/teams/108" + }, + "player": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + }, + "opponent": { + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "date": "2022-04-07", + "gameType": "R", + "isHome": true, + "pitcher": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + }, + "batter": { + "id": 514888, + "fullName": "Jose Altuve", + "link": "/api/v1/people/514888" + }, + "game": { + "gamePk": 661042, + "link": "/api/v1.1/game/661042/feed/live", + "content": { + "link": "/api/v1/game/661042/content" + }, + "gameNumber": 1, + "dayNight": "day" + } + }, + { + "season": "2022", + "stat": { + "play": { + "details": { + "call": { + "code": "C", + "description": "Strike - Called" + }, + "event": "called_strike", + "eventType": "called_strike", + "isInPlay": false, + "isStrike": true, + "isBall": false, + "isBaseHit": false, + "isAtBat": false, + "isPlateAppearance": false, + "type": { + "code": "FF", + "description": "Four-seam FB" + }, + "batSide": { + "code": "R", + "description": "Right" + }, + "pitchHand": { + "code": "R", + "description": "Right" + } + }, + "count": { + "balls": 1, + "strikes": 0, + "outs": 0, + "inning": 1, + "isTopInning": true, + "runnerOn1b": false, + "runnerOn2b": false, + "runnerOn3b": false + }, + "playId": "3f4c48c9-19ad-40a8-946b-273183106e69", + "pitchNumber": 2, + "atBatNumber": 1, + "isPitch": true + } + }, + "team": { + "id": 108, + "name": "Los Angeles Angels", + "link": "/api/v1/teams/108" + }, + "player": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + }, + "opponent": { + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "date": "2022-04-07", + "gameType": "R", + "isHome": true, + "pitcher": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + }, + "batter": { + "id": 514888, + "fullName": "Jose Altuve", + "link": "/api/v1/people/514888" + }, + "game": { + "gamePk": 661042, + "link": "/api/v1.1/game/661042/feed/live", + "content": { + "link": "/api/v1/game/661042/content" + }, + "gameNumber": 1, + "dayNight": "day" + } + } ] + } ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/person/pitching_player_playlog.json b/tests/mock_tests/mock_json/stats/person/pitching_player_playlog.json new file mode 100644 index 00000000..5ee1fe3a --- /dev/null +++ b/tests/mock_tests/mock_json/stats/person/pitching_player_playlog.json @@ -0,0 +1,175 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "type": { + "displayName": "playLog" + }, + "group": { + "displayName": "pitching" + }, + "exemptions": [], + "splits": [ + { + "season": "2022", + "stat": { + "play": { + "details": { + "call": { + "code": "C", + "description": "Strike - Called" + }, + "description": "Jose Altuve called out on strikes. ", + "event": "strikeout", + "eventType": "strikeout", + "isInPlay": false, + "isStrike": true, + "isBall": false, + "isBaseHit": false, + "isAtBat": true, + "isPlateAppearance": true, + "type": {}, + "batSide": {}, + "pitchHand": {} + }, + "count": { + "balls": 2, + "strikes": 2, + "outs": 0, + "inning": 1, + "isTopInning": true, + "runnerOn1b": false, + "runnerOn2b": false, + "runnerOn3b": false + }, + "playId": "e32d8b81-ceac-4131-8870-a4ac6d018800", + "pitchNumber": 5, + "atBatNumber": 1, + "isPitch": true + } + }, + "team": { + "id": 108, + "name": "Los Angeles Angels", + "link": "/api/v1/teams/108" + }, + "player": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + }, + "opponent": { + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "date": "2022-04-07", + "gameType": "R", + "isHome": true, + "pitcher": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + }, + "batter": { + "id": 514888, + "fullName": "Jose Altuve", + "link": "/api/v1/people/514888" + }, + "game": { + "gamePk": 661042, + "link": "/api/v1.1/game/661042/feed/live", + "content": { + "link": "/api/v1/game/661042/content" + }, + "gameNumber": 1, + "dayNight": "day" + } + }, + { + "season": "2022", + "stat": { + "play": { + "details": { + "call": { + "code": "D", + "description": "Hit Into Play - No Out(s)" + }, + "description": "Michael Brantley singles on a line drive to left fielder Jo Adell. ", + "event": "single", + "eventType": "single", + "isInPlay": true, + "isStrike": false, + "isBall": false, + "isBaseHit": true, + "isAtBat": true, + "isPlateAppearance": true, + "type": { + "code": "FF", + "description": "Four-seam FB" + }, + "batSide": { + "code": "L", + "description": "Left" + }, + "pitchHand": { + "code": "R", + "description": "Right" + } + }, + "count": { + "balls": 0, + "strikes": 0, + "outs": 1, + "inning": 1, + "isTopInning": true, + "runnerOn1b": false, + "runnerOn2b": false, + "runnerOn3b": false + }, + "playId": "42e26427-2f9d-4ad5-b70e-e0d3bac4e772", + "pitchNumber": 1, + "atBatNumber": 2, + "isPitch": true + } + }, + "team": { + "id": 108, + "name": "Los Angeles Angels", + "link": "/api/v1/teams/108" + }, + "player": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + }, + "opponent": { + "id": 117, + "name": "Houston Astros", + "link": "/api/v1/teams/117" + }, + "date": "2022-04-07", + "gameType": "R", + "isHome": true, + "pitcher": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + }, + "batter": { + "id": 488726, + "fullName": "Michael Brantley", + "link": "/api/v1/people/488726" + }, + "game": { + "gamePk": 661042, + "link": "/api/v1.1/game/661042/feed/live", + "content": { + "link": "/api/v1/game/661042/content" + }, + "gameNumber": 1, + "dayNight": "day" + } + }] + }] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/person/pitching_player_stats.json b/tests/mock_tests/mock_json/stats/person/pitching_player_stats.json new file mode 100644 index 00000000..d883a4bd --- /dev/null +++ b/tests/mock_tests/mock_json/stats/person/pitching_player_stats.json @@ -0,0 +1,363 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "type": { + "displayName": "career" + }, + "group": { + "displayName": "pitching" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "gamesPlayed": 63, + "gamesStarted": 63, + "groundOuts": 279, + "airOuts": 305, + "runs": 119, + "doubles": 52, + "triples": 2, + "homeRuns": 35, + "strikeOuts": 441, + "baseOnBalls": 118, + "intentionalWalks": 2, + "hits": 263, + "hitByPitch": 13, + "avg": ".206", + "atBats": 1278, + "obp": ".278", + "slg": ".332", + "ops": ".610", + "caughtStealing": 4, + "stolenBases": 7, + "stolenBasePercentage": ".636", + "groundIntoDoublePlay": 20, + "numberOfPitches": 5589, + "era": "2.96", + "inningsPitched": "349.2", + "wins": 28, + "losses": 14, + "saves": 0, + "saveOpportunities": 0, + "holds": 0, + "blownSaves": 0, + "earnedRuns": 115, + "whip": "1.09", + "battersFaced": 1420, + "outs": 1049, + "gamesPitched": 63, + "completeGames": 0, + "shutouts": 0, + "strikes": 3585, + "strikePercentage": ".640", + "hitBatsmen": 13, + "balks": 2, + "wildPitches": 30, + "pickoffs": 0, + "totalBases": 424, + "groundOutsToAirouts": "0.91", + "winPercentage": ".667", + "pitchesPerInning": "15.98", + "gamesFinished": 0, + "strikeoutWalkRatio": "3.74", + "strikeoutsPer9Inn": "11.35", + "walksPer9Inn": "3.04", + "hitsPer9Inn": "6.77", + "runsScoredPer9": "3.06", + "homeRunsPer9": "0.90", + "inheritedRunners": 0, + "inheritedRunnersScored": 0, + "catchersInterference": 1, + "sacBunts": 2, + "sacFlies": 8 + }, + "team": { + "id": 108, + "name": "Los Angeles Angels", + "link": "/api/v1/teams/108" + }, + "player": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + }, + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } + ] + }, + { + "type": { + "displayName": "careerAdvanced" + }, + "group": { + "displayName": "pitching" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "winningPercentage": ".667", + "runsScoredPer9": "3.06", + "battersFaced": 1420, + "babip": ".281", + "obp": ".278", + "slg": ".332", + "ops": ".610", + "strikeoutsPer9": "11.35", + "baseOnBallsPer9": "3.04", + "homeRunsPer9": "0.90", + "hitsPer9": "6.77", + "strikesoutsToWalks": "3.74", + "inheritedRunners": 0, + "inheritedRunnersScored": 0, + "bequeathedRunners": 30, + "bequeathedRunnersScored": 11, + "stolenBases": 7, + "caughtStealing": 4, + "qualityStarts": 35, + "gamesFinished": 0, + "doubles": 52, + "triples": 2, + "gidp": 20, + "gidpOpp": 150, + "wildPitches": 30, + "balks": 2, + "pickoffs": 0, + "totalSwings": 2656, + "swingAndMisses": 844, + "ballsInPlay": 847, + "runSupport": 172, + "strikePercentage": ".640", + "pitchesPerInning": "15.98", + "pitchesPerPlateAppearance": "3.936", + "walksPerPlateAppearance": ".083", + "strikeoutsPerPlateAppearance": ".311", + "homeRunsPerPlateAppearance": ".025", + "walksPerStrikeout": ".268", + "iso": ".126", + "flyOuts": 163, + "popOuts": 71, + "lineOuts": 71, + "groundOuts": 279, + "flyHits": 50, + "popHits": 1, + "lineHits": 124, + "groundHits": 88 + }, + "team": { + "id": 108, + "name": "Los Angeles Angels", + "link": "/api/v1/teams/108" + }, + "player": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + }, + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } + ] + }, + { + "type": { + "displayName": "season" + }, + "group": { + "displayName": "pitching" + }, + "exemptions": [], + "splits": [ + { + "season": "2022", + "stat": { + "gamesPlayed": 28, + "gamesStarted": 28, + "groundOuts": 129, + "airOuts": 141, + "runs": 45, + "doubles": 23, + "triples": 2, + "homeRuns": 14, + "strikeOuts": 219, + "baseOnBalls": 44, + "intentionalWalks": 0, + "hits": 124, + "hitByPitch": 2, + "avg": ".203", + "atBats": 610, + "obp": ".258", + "slg": ".316", + "ops": ".574", + "caughtStealing": 1, + "stolenBases": 4, + "stolenBasePercentage": ".800", + "groundIntoDoublePlay": 10, + "numberOfPitches": 2629, + "era": "2.33", + "inningsPitched": "166.0", + "wins": 15, + "losses": 9, + "saves": 0, + "saveOpportunities": 0, + "holds": 0, + "blownSaves": 0, + "earnedRuns": 43, + "whip": "1.01", + "battersFaced": 660, + "outs": 498, + "gamesPitched": 28, + "completeGames": 0, + "shutouts": 0, + "strikes": 1724, + "strikePercentage": ".660", + "hitBatsmen": 2, + "balks": 0, + "wildPitches": 14, + "pickoffs": 0, + "totalBases": 193, + "groundOutsToAirouts": "0.91", + "winPercentage": ".625", + "pitchesPerInning": "15.84", + "gamesFinished": 0, + "strikeoutWalkRatio": "4.98", + "strikeoutsPer9Inn": "11.87", + "walksPer9Inn": "2.39", + "hitsPer9Inn": "6.72", + "runsScoredPer9": "2.44", + "homeRunsPer9": "0.76", + "inheritedRunners": 0, + "inheritedRunnersScored": 0, + "catchersInterference": 1, + "sacBunts": 0, + "sacFlies": 3 + }, + "team": { + "id": 108, + "name": "Los Angeles Angels", + "link": "/api/v1/teams/108" + }, + "player": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + }, + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } + ] + }, + { + "type": { + "displayName": "seasonAdvanced" + }, + "group": { + "displayName": "pitching" + }, + "exemptions": [], + "splits": [ + { + "season": "2022", + "stat": { + "winningPercentage": ".625", + "runsScoredPer9": "2.44", + "battersFaced": 660, + "babip": ".289", + "obp": ".258", + "slg": ".316", + "ops": ".574", + "strikeoutsPer9": "11.87", + "baseOnBallsPer9": "2.39", + "homeRunsPer9": "0.76", + "hitsPer9": "6.72", + "strikesoutsToWalks": "4.98", + "inheritedRunners": 0, + "inheritedRunnersScored": 0, + "bequeathedRunners": 10, + "bequeathedRunnersScored": 2, + "stolenBases": 4, + "caughtStealing": 1, + "qualityStarts": 16, + "gamesFinished": 0, + "doubles": 23, + "triples": 2, + "gidp": 10, + "gidpOpp": 69, + "wildPitches": 14, + "balks": 0, + "pickoffs": 0, + "totalSwings": 1282, + "swingAndMisses": 425, + "ballsInPlay": 394, + "runSupport": 79, + "strikePercentage": ".660", + "pitchesPerInning": "15.84", + "pitchesPerPlateAppearance": "3.983", + "walksPerPlateAppearance": ".067", + "strikeoutsPerPlateAppearance": ".332", + "homeRunsPerPlateAppearance": ".021", + "walksPerStrikeout": ".201", + "iso": ".113", + "flyOuts": 81, + "popOuts": 33, + "lineOuts": 27, + "groundOuts": 129, + "flyHits": 24, + "popHits": 1, + "lineHits": 63, + "groundHits": 36 + }, + "team": { + "id": 108, + "name": "Los Angeles Angels", + "link": "/api/v1/teams/108" + }, + "player": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + }, + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "abbreviation": "MLB" + }, + "gameType": "R" + } ] + } ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/person/spraychart.json b/tests/mock_tests/mock_json/stats/person/spraychart.json new file mode 100644 index 00000000..69c6ed75 --- /dev/null +++ b/tests/mock_tests/mock_json/stats/person/spraychart.json @@ -0,0 +1,28 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "type": { + "displayName": "sprayChart" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "leftField": 3, + "leftCenterField": 11, + "centerField": 18, + "rightCenterField": 35, + "rightField": 33 + }, + "gameType": "R", + "batter": { + "id": 660271, + "fullName": "Shohei Ohtani", + "link": "/api/v1/people/660271" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/team/hitting_team_stats.json b/tests/mock_tests/mock_json/stats/team/hitting_team_stats.json new file mode 100644 index 00000000..7ac3593c --- /dev/null +++ b/tests/mock_tests/mock_json/stats/team/hitting_team_stats.json @@ -0,0 +1,207 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "type": { + "displayName": "career" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "gamesPlayed": 18951, + "groundOuts": 55579, + "airOuts": 60176, + "runs": 83856, + "doubles": 28656, + "triples": 5286, + "homeRuns": 13714, + "strikeOuts": 89222, + "baseOnBalls": 63371, + "intentionalWalks": 2245, + "hits": 165714, + "hitByPitch": 4586, + "avg": ".258", + "atBats": 642577, + "obp": ".327", + "slg": ".383", + "ops": ".710", + "caughtStealing": 4795, + "stolenBases": 11834, + "stolenBasePercentage": ".712", + "groundIntoDoublePlay": 10338, + "numberOfPitches": 943087, + "plateAppearances": 724217, + "totalBases": 246088, + "rbi": 77208, + "leftOnBase": 120358, + "sacBunts": 10633, + "sacFlies": 3020, + "babip": ".280", + "groundOutsToAirouts": "0.92", + "catchersInterference": 30, + "atBatsPerHomeRun": "46.86" + }, + "team": { + "id": 133, + "link": "/api/v1/teams/133" + } + } + ] + }, + { + "type": { + "displayName": "careerAdvanced" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "plateAppearances": 724217, + "totalBases": 246088, + "leftOnBase": 120358, + "sacBunts": 10633, + "sacFlies": 3020, + "babip": ".280", + "extraBaseHits": 47656, + "hitByPitch": 4586, + "gidp": 10338, + "gidpOpp": 42607, + "numberOfPitches": 943087, + "pitchesPerPlateAppearance": "1.302", + "walksPerPlateAppearance": ".088", + "strikeoutsPerPlateAppearance": ".123", + "homeRunsPerPlateAppearance": ".019", + "walksPerStrikeout": ".710", + "iso": ".125", + "reachedOnError": 3019, + "walkOffs": 731, + "flyOuts": 34906, + "totalSwings": 455234, + "swingAndMisses": -240895, + "ballsInPlay": 567008, + "popOuts": 13180, + "lineOuts": 12090, + "groundOuts": 55579, + "flyHits": 8504, + "popHits": 428, + "lineHits": 21512, + "groundHits": 14797 + }, + "team": { + "id": 133, + "link": "/api/v1/teams/133" + } + } + ] + }, + { + "type": { + "displayName": "season" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2022", + "stat": { + "gamesPlayed": 162, + "groundOuts": 1439, + "airOuts": 1394, + "runs": 568, + "doubles": 249, + "triples": 15, + "homeRuns": 137, + "strikeOuts": 1389, + "baseOnBalls": 433, + "intentionalWalks": 7, + "hits": 1147, + "hitByPitch": 59, + "avg": ".216", + "atBats": 5314, + "obp": ".281", + "slg": ".346", + "ops": ".627", + "caughtStealing": 23, + "stolenBases": 78, + "stolenBasePercentage": ".772", + "groundIntoDoublePlay": 109, + "numberOfPitches": 22568, + "plateAppearances": 5863, + "totalBases": 1837, + "rbi": 537, + "leftOnBase": 969, + "sacBunts": 22, + "sacFlies": 33, + "babip": ".264", + "groundOutsToAirouts": "1.03", + "catchersInterference": 2, + "atBatsPerHomeRun": "38.79" + }, + "team": { + "id": 133, + "name": "Oakland Athletics", + "link": "/api/v1/teams/133" + } + } + ] + }, + { + "type": { + "displayName": "seasonAdvanced" + }, + "group": { + "displayName": "hitting" + }, + "exemptions": [], + "splits": [ + { + "season": "2022", + "stat": { + "plateAppearances": 5863, + "totalBases": 1837, + "leftOnBase": 969, + "sacBunts": 22, + "sacFlies": 33, + "babip": ".264", + "extraBaseHits": 401, + "hitByPitch": 59, + "gidp": 109, + "gidpOpp": 670, + "numberOfPitches": 22568, + "pitchesPerPlateAppearance": "3.849", + "walksPerPlateAppearance": ".074", + "strikeoutsPerPlateAppearance": ".237", + "homeRunsPerPlateAppearance": ".023", + "walksPerStrikeout": ".312", + "iso": ".130", + "reachedOnError": 32, + "walkOffs": 14, + "flyOuts": 758, + "totalSwings": 11013, + "swingAndMisses": 2992, + "ballsInPlay": 3980, + "popOuts": 272, + "lineOuts": 364, + "groundOuts": 1439, + "flyHits": 213, + "popHits": 7, + "lineHits": 546, + "groundHits": 381 + }, + "team": { + "id": 133, + "name": "Oakland Athletics", + "link": "/api/v1/teams/133" + } + } ] + } ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/stats/team/pitching_team_stats.json b/tests/mock_tests/mock_json/stats/team/pitching_team_stats.json new file mode 100644 index 00000000..5241b37d --- /dev/null +++ b/tests/mock_tests/mock_json/stats/team/pitching_team_stats.json @@ -0,0 +1,285 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "stats": [ + { + "type": { + "displayName": "career" + }, + "group": { + "displayName": "pitching" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "gamesPlayed": 18951, + "gamesStarted": 18951, + "groundOuts": 58724, + "airOuts": 59285, + "runs": 86416, + "doubles": 12148, + "triples": 1413, + "homeRuns": 13786, + "strikeOuts": 91707, + "baseOnBalls": 65298, + "intentionalWalks": 2602, + "hits": 168140, + "hitByPitch": 4742, + "avg": ".261", + "atBats": 644309, + "obp": ".332", + "slg": ".162", + "ops": ".494", + "caughtStealing": 2344, + "stolenBases": 4686, + "stolenBasePercentage": ".667", + "groundIntoDoublePlay": 5834, + "numberOfPitches": 917731, + "era": "3.98", + "inningsPitched": "168935.1", + "wins": 9210, + "losses": 9654, + "saves": 3010, + "saveOpportunities": 3557, + "holds": 1686, + "blownSaves": 547, + "earnedRuns": 74762, + "whip": "1.38", + "battersFaced": 725026, + "outs": 506806, + "gamesPitched": 18951, + "completeGames": 5701, + "shutouts": 1210, + "strikes": 590187, + "strikePercentage": ".640", + "hitBatsmen": 4742, + "balks": 590, + "wildPitches": 5187, + "pickoffs": 544, + "totalBases": 104641, + "groundOutsToAirouts": "0.99", + "winPercentage": ".488", + "pitchesPerInning": "5.43", + "gamesFinished": 13245, + "strikeoutWalkRatio": "1.40", + "strikeoutsPer9Inn": "4.89", + "walksPer9Inn": "3.48", + "hitsPer9Inn": "8.96", + "runsScoredPer9": "4.60", + "homeRunsPer9": "0.73", + "catchersInterference": 47, + "sacBunts": 7482, + "sacFlies": 3147 + }, + "team": { + "id": 133, + "link": "/api/v1/teams/133" + } + } + ] + }, + { + "type": { + "displayName": "careerAdvanced" + }, + "group": { + "displayName": "pitching" + }, + "exemptions": [], + "splits": [ + { + "stat": { + "winningPercentage": ".488", + "runsScoredPer9": "4.60", + "battersFaced": 725026, + "babip": ".285", + "obp": ".332", + "slg": ".162", + "ops": ".494", + "strikeoutsPer9": "4.89", + "baseOnBallsPer9": "3.48", + "homeRunsPer9": "0.73", + "hitsPer9": "8.96", + "strikesoutsToWalks": "1.40", + "stolenBases": 4686, + "caughtStealing": 2344, + "qualityStarts": 3735, + "gamesFinished": 13245, + "doubles": 12148, + "triples": 1413, + "gidp": 5834, + "gidpOpp": 43743, + "wildPitches": 5187, + "balks": 590, + "pickoffs": 544, + "totalSwings": 455720, + "swingAndMisses": 106239, + "ballsInPlay": 220814, + "runSupport": 35211, + "strikePercentage": ".640", + "pitchesPerInning": "5.43", + "pitchesPerPlateAppearance": "1.266", + "walksPerPlateAppearance": ".090", + "strikeoutsPerPlateAppearance": ".126", + "homeRunsPerPlateAppearance": ".019", + "walksPerStrikeout": ".712", + "iso": "-.099", + "flyOuts": 33922, + "popOuts": 12919, + "lineOuts": 12444, + "groundOuts": 58724, + "flyHits": 8396, + "popHits": 353, + "lineHits": 21696, + "groundHits": 15605 + }, + "team": { + "id": 133, + "link": "/api/v1/teams/133" + } + } + ] + }, + { + "type": { + "displayName": "season" + }, + "group": { + "displayName": "pitching" + }, + "exemptions": [], + "splits": [ + { + "season": "2022", + "stat": { + "gamesPlayed": 162, + "gamesStarted": 162, + "groundOuts": 1384, + "airOuts": 1563, + "runs": 770, + "doubles": 313, + "triples": 28, + "homeRuns": 195, + "strikeOuts": 1203, + "baseOnBalls": 503, + "intentionalWalks": 37, + "hits": 1394, + "hitByPitch": 72, + "avg": ".254", + "atBats": 5491, + "obp": ".323", + "slg": ".428", + "ops": ".751", + "caughtStealing": 29, + "stolenBases": 77, + "stolenBasePercentage": ".726", + "groundIntoDoublePlay": 118, + "numberOfPitches": 23324, + "era": "4.52", + "inningsPitched": "1426.1", + "wins": 60, + "losses": 102, + "saves": 34, + "saveOpportunities": 59, + "holds": 105, + "blownSaves": 25, + "earnedRuns": 717, + "whip": "1.33", + "battersFaced": 6121, + "outs": 4279, + "gamesPitched": 162, + "completeGames": 0, + "shutouts": 7, + "strikes": 14834, + "strikePercentage": ".640", + "hitBatsmen": 72, + "balks": 5, + "wildPitches": 62, + "pickoffs": 13, + "totalBases": 2348, + "groundOutsToAirouts": "0.89", + "winPercentage": ".370", + "pitchesPerInning": "16.35", + "gamesFinished": 162, + "strikeoutWalkRatio": "2.39", + "strikeoutsPer9Inn": "7.59", + "walksPer9Inn": "3.17", + "hitsPer9Inn": "8.80", + "runsScoredPer9": "4.86", + "homeRunsPer9": "1.23", + "catchersInterference": 2, + "sacBunts": 15, + "sacFlies": 38 + }, + "team": { + "id": 133, + "name": "Oakland Athletics", + "link": "/api/v1/teams/133" + } + } + ] + }, + { + "type": { + "displayName": "seasonAdvanced" + }, + "group": { + "displayName": "pitching" + }, + "exemptions": [], + "splits": [ + { + "season": "2022", + "stat": { + "winningPercentage": ".488", + "runsScoredPer9": "4.86", + "battersFaced": 6121, + "babip": ".290", + "obp": ".323", + "slg": ".428", + "ops": ".751", + "strikeoutsPer9": "7.59", + "baseOnBallsPer9": "3.17", + "homeRunsPer9": "1.23", + "hitsPer9": "8.80", + "strikesoutsToWalks": "2.39", + "stolenBases": 77, + "caughtStealing": 29, + "qualityStarts": 47, + "gamesFinished": 162, + "doubles": 313, + "triples": 28, + "gidp": 118, + "gidpOpp": 860, + "wildPitches": 62, + "balks": 5, + "pickoffs": 13, + "totalSwings": 11106, + "swingAndMisses": 2639, + "ballsInPlay": 4342, + "runSupport": 568, + "strikePercentage": ".640", + "pitchesPerInning": "16.35", + "pitchesPerPlateAppearance": "3.810", + "walksPerPlateAppearance": ".082", + "strikeoutsPerPlateAppearance": ".197", + "homeRunsPerPlateAppearance": ".032", + "walksPerStrikeout": ".418", + "iso": ".174", + "flyOuts": 864, + "popOuts": 325, + "lineOuts": 374, + "groundOuts": 1384, + "flyHits": 325, + "popHits": 6, + "lineHits": 621, + "groundHits": 442 + }, + "team": { + "id": 133, + "name": "Oakland Athletics", + "link": "/api/v1/teams/133" + } + } ] + } ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/teams/team.json b/tests/mock_tests/mock_json/teams/team.json new file mode 100644 index 00000000..751ffe3a --- /dev/null +++ b/tests/mock_tests/mock_json/teams/team.json @@ -0,0 +1,51 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "teams": [ + { + "springLeague": { + "id": 114, + "name": "Cactus League", + "link": "/api/v1/league/114", + "abbreviation": "CL" + }, + "allStarStatus": "N", + "id": 133, + "name": "Oakland Athletics", + "link": "/api/v1/teams/133", + "season": 2022, + "venue": { + "id": 10, + "name": "Oakland Coliseum", + "link": "/api/v1/venues/10" + }, + "springVenue": { + "id": 2507, + "link": "/api/v1/venues/2507" + }, + "teamCode": "oak", + "fileCode": "oak", + "abbreviation": "OAK", + "teamName": "Athletics", + "locationName": "Oakland", + "firstYearOfPlay": "1901", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "division": { + "id": 200, + "name": "American League West", + "link": "/api/v1/divisions/200" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "name": "Major League Baseball" + }, + "shortName": "Oakland", + "franchiseName": "Oakland", + "clubName": "Athletics", + "active": true + } ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/teams/team_coaches.json b/tests/mock_tests/mock_json/teams/team_coaches.json new file mode 100644 index 00000000..6032b224 --- /dev/null +++ b/tests/mock_tests/mock_json/teams/team_coaches.json @@ -0,0 +1,151 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "roster": [ + { + "person": { + "id": 117276, + "fullName": "Mark Kotsay", + "link": "/api/v1/people/117276" + }, + "jerseyNumber": "7", + "job": "Manager", + "jobId": "MNGR", + "title": "Manager" + }, + { + "person": { + "id": 470252, + "fullName": "Darren Bush", + "link": "/api/v1/people/470252" + }, + "jerseyNumber": "51", + "job": "Bench Coach", + "jobId": "COAB", + "title": "Bench Coach" + }, + { + "person": { + "id": 452784, + "fullName": "Tommy Everidge", + "link": "/api/v1/people/452784" + }, + "jerseyNumber": "52", + "job": "Hitting Coach", + "jobId": "COAT", + "title": "Hitting Coach" + }, + { + "person": { + "id": 112849, + "fullName": "Chris Cron", + "link": "/api/v1/people/112849" + }, + "jerseyNumber": "41", + "job": "Assistant Hitting Coach", + "jobId": "COAA", + "title": "Assistant Hitting Coach" + }, + { + "person": { + "id": 491913, + "fullName": "Scott Emerson", + "link": "/api/v1/people/491913" + }, + "jerseyNumber": "14", + "job": "Pitching Coach", + "jobId": "COAP", + "title": "Pitching Coach" + }, + { + "person": { + "id": 110119, + "fullName": "Mike Aldrete", + "link": "/api/v1/people/110119" + }, + "jerseyNumber": "18", + "job": "First Base Coach", + "jobId": "COA1", + "title": "First Base Coach" + }, + { + "person": { + "id": 440009, + "fullName": "Eric Martins", + "link": "/api/v1/people/440009" + }, + "jerseyNumber": "3", + "job": "Third Base Coach", + "jobId": "COA3", + "title": "Third Base Coach" + }, + { + "person": { + "id": 592543, + "fullName": "Mike McCarthy", + "link": "/api/v1/people/592543" + }, + "jerseyNumber": "", + "job": "Bullpen Coach", + "jobId": "COAU", + "title": "Bullpen Coach" + }, + { + "person": { + "id": 116534, + "fullName": "Marcus Jensen", + "link": "/api/v1/people/116534" + }, + "jerseyNumber": "", + "job": "Quality Control Coach", + "jobId": "QUAC", + "title": "Quality Control Coach" + }, + { + "person": { + "id": 458031, + "fullName": "Dustin Hughes", + "link": "/api/v1/people/458031" + }, + "jerseyNumber": "91", + "job": "Bullpen Catcher", + "jobId": "BCAT", + "title": "Bullpen Catcher" + }, + { + "person": { + "id": 470795, + "fullName": "Scott Steinmann", + "link": "/api/v1/people/470795" + }, + "jerseyNumber": "", + "job": "Performance Coach", + "jobId": "PERF", + "title": "Hitting Performance Coach" + }, + { + "person": { + "id": 120288, + "fullName": "Gil Patterson", + "link": "/api/v1/people/120288" + }, + "jerseyNumber": "", + "job": "Mental Performance Coordinator", + "jobId": "MPCO", + "title": "Minor League Pitching Coordinator" + }, + { + "person": { + "id": 460183, + "fullName": "Lloyd Turner", + "link": "/api/v1/people/460183" + }, + "jerseyNumber": "", + "job": "Hitting Analytics Instructor", + "jobId": "HANI", + "title": "Hitting Technology Coach" + } + ], + "link": "/api/v1/teams/133/coaches", + "teamId": 133, + "rosterType": "coach" +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/teams/team_roster_coaches.json b/tests/mock_tests/mock_json/teams/team_roster_coaches.json new file mode 100644 index 00000000..66a427a9 --- /dev/null +++ b/tests/mock_tests/mock_json/teams/team_roster_coaches.json @@ -0,0 +1,150 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "roster": [ + { + "person": { + "id": 117276, + "fullName": "Mark Kotsay", + "link": "/api/v1/people/117276" + }, + "jerseyNumber": "7", + "job": "Manager", + "jobId": "MNGR", + "title": "Manager" + }, + { + "person": { + "id": 470252, + "fullName": "Darren Bush", + "link": "/api/v1/people/470252" + }, + "jerseyNumber": "51", + "job": "Bench Coach", + "jobId": "COAB", + "title": "Bench Coach" + }, + { + "person": { + "id": 452784, + "fullName": "Tommy Everidge", + "link": "/api/v1/people/452784" + }, + "jerseyNumber": "52", + "job": "Hitting Coach", + "jobId": "COAT", + "title": "Hitting Coach" + }, + { + "person": { + "id": 112849, + "fullName": "Chris Cron", + "link": "/api/v1/people/112849" + }, + "jerseyNumber": "41", + "job": "Assistant Hitting Coach", + "jobId": "COAA", + "title": "Assistant Hitting Coach" + }, + { + "person": { + "id": 491913, + "fullName": "Scott Emerson", + "link": "/api/v1/people/491913" + }, + "jerseyNumber": "14", + "job": "Pitching Coach", + "jobId": "COAP", + "title": "Pitching Coach" + }, + { + "person": { + "id": 110119, + "fullName": "Mike Aldrete", + "link": "/api/v1/people/110119" + }, + "jerseyNumber": "18", + "job": "First Base Coach", + "jobId": "COA1", + "title": "First Base Coach" + }, + { + "person": { + "id": 440009, + "fullName": "Eric Martins", + "link": "/api/v1/people/440009" + }, + "jerseyNumber": "3", + "job": "Third Base Coach", + "jobId": "COA3", + "title": "Third Base Coach" + }, + { + "person": { + "id": 592543, + "fullName": "Mike McCarthy", + "link": "/api/v1/people/592543" + }, + "jerseyNumber": "", + "job": "Bullpen Coach", + "jobId": "COAU", + "title": "Bullpen Coach" + }, + { + "person": { + "id": 116534, + "fullName": "Marcus Jensen", + "link": "/api/v1/people/116534" + }, + "jerseyNumber": "", + "job": "Quality Control Coach", + "jobId": "QUAC", + "title": "Quality Control Coach" + }, + { + "person": { + "id": 458031, + "fullName": "Dustin Hughes", + "link": "/api/v1/people/458031" + }, + "jerseyNumber": "91", + "job": "Bullpen Catcher", + "jobId": "BCAT", + "title": "Bullpen Catcher" + }, + { + "person": { + "id": 470795, + "fullName": "Scott Steinmann", + "link": "/api/v1/people/470795" + }, + "jerseyNumber": "", + "job": "Performance Coach", + "jobId": "PERF", + "title": "Hitting Performance Coach" + }, + { + "person": { + "id": 120288, + "fullName": "Gil Patterson", + "link": "/api/v1/people/120288" + }, + "jerseyNumber": "", + "job": "Mental Performance Coordinator", + "jobId": "MPCO", + "title": "Minor League Pitching Coordinator" + }, + { + "person": { + "id": 460183, + "fullName": "Lloyd Turner", + "link": "/api/v1/people/460183" + }, + "jerseyNumber": "", + "job": "Hitting Analytics Instructor", + "jobId": "HANI", + "title": "Hitting Technology Coach" + } ], + "link": "/api/v1/teams/133/coaches", + "teamId": 133, + "rosterType": "coach" +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/teams/team_roster_players.json b/tests/mock_tests/mock_json/teams/team_roster_players.json new file mode 100644 index 00000000..bd544dd6 --- /dev/null +++ b/tests/mock_tests/mock_json/teams/team_roster_players.json @@ -0,0 +1,710 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "roster": [ + { + "person": { + "id": 640462, + "fullName": "A.J. Puk", + "link": "/api/v1/people/640462" + }, + "jerseyNumber": "33", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 670124, + "fullName": "Adam Oller", + "link": "/api/v1/people/670124" + }, + "jerseyNumber": "36", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 661309, + "fullName": "Adrian Martinez", + "link": "/api/v1/people/661309" + }, + "jerseyNumber": "55", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 667670, + "fullName": "Brent Rooker", + "link": "/api/v1/people/667670" + }, + "jerseyNumber": "", + "position": { + "code": "7", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "LF" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 670276, + "fullName": "Cal Stevenson", + "link": "/api/v1/people/670276" + }, + "jerseyNumber": "37", + "position": { + "code": "7", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "LF" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 642137, + "fullName": "Cody Thomas", + "link": "/api/v1/people/642137" + }, + "jerseyNumber": "48", + "position": { + "code": "7", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "LF" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 608344, + "fullName": "Cole Irvin", + "link": "/api/v1/people/608344" + }, + "jerseyNumber": "19", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 668843, + "fullName": "Conner Capel", + "link": "/api/v1/people/668843" + }, + "jerseyNumber": "72", + "position": { + "code": "9", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "RF" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 665506, + "fullName": "Cristian Pache", + "link": "/api/v1/people/665506" + }, + "jerseyNumber": "20", + "position": { + "code": "8", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "CF" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 666204, + "fullName": "Dany Jimenez", + "link": "/api/v1/people/666204" + }, + "jerseyNumber": "56", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 660650, + "fullName": "Dermis Garcia", + "link": "/api/v1/people/660650" + }, + "jerseyNumber": "76", + "position": { + "code": "3", + "name": "First Base", + "type": "Infielder", + "abbreviation": "1B" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 642758, + "fullName": "Domingo Acevedo", + "link": "/api/v1/people/642758" + }, + "jerseyNumber": "68", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 676391, + "fullName": "Ernie Clement", + "link": "/api/v1/people/676391" + }, + "jerseyNumber": "44", + "position": { + "code": "5", + "name": "Third Base", + "type": "Infielder", + "abbreviation": "3B" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 663687, + "fullName": "Hogan Harris", + "link": "/api/v1/people/663687" + }, + "jerseyNumber": "", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 676664, + "fullName": "JP Sears", + "link": "/api/v1/people/676664" + }, + "jerseyNumber": "38", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 621076, + "fullName": "James Kaprielian", + "link": "/api/v1/people/621076" + }, + "jerseyNumber": "32", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 606303, + "fullName": "Joel Payamps", + "link": "/api/v1/people/606303" + }, + "jerseyNumber": "30", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 681146, + "fullName": "Jonah Bride", + "link": "/api/v1/people/681146" + }, + "jerseyNumber": "77", + "position": { + "code": "4", + "name": "Second Base", + "type": "Infielder", + "abbreviation": "2B" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 672478, + "fullName": "Jordan Diaz", + "link": "/api/v1/people/672478" + }, + "jerseyNumber": "75", + "position": { + "code": "5", + "name": "Third Base", + "type": "Infielder", + "abbreviation": "3B" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 686610, + "fullName": "Ken Waldichuk", + "link": "/api/v1/people/686610" + }, + "jerseyNumber": "64", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 675656, + "fullName": "Kevin Smith", + "link": "/api/v1/people/675656" + }, + "jerseyNumber": "1", + "position": { + "code": "6", + "name": "Shortstop", + "type": "Infielder", + "abbreviation": "SS" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 669912, + "fullName": "Kirby Snead", + "link": "/api/v1/people/669912" + }, + "jerseyNumber": "54", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 671732, + "fullName": "Lawrence Butler", + "link": "/api/v1/people/671732" + }, + "jerseyNumber": "", + "position": { + "code": "O", + "name": "Outfield", + "type": "Outfielder", + "abbreviation": "OF" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 665622, + "fullName": "Luis Medina", + "link": "/api/v1/people/665622" + }, + "jerseyNumber": "", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 669397, + "fullName": "Nick Allen", + "link": "/api/v1/people/669397" + }, + "jerseyNumber": "2", + "position": { + "code": "4", + "name": "Second Base", + "type": "Infielder", + "abbreviation": "2B" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 621112, + "fullName": "Paul Blackburn", + "link": "/api/v1/people/621112" + }, + "jerseyNumber": "58", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 657656, + "fullName": "Ramon Laureano", + "link": "/api/v1/people/657656" + }, + "jerseyNumber": "22", + "position": { + "code": "9", + "name": "Outfielder", + "type": "Outfielder", + "abbreviation": "RF" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 594580, + "fullName": "Sam Moll", + "link": "/api/v1/people/594580" + }, + "jerseyNumber": "60", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 669221, + "fullName": "Sean Murphy", + "link": "/api/v1/people/669221" + }, + "jerseyNumber": "12", + "position": { + "code": "2", + "name": "Catcher", + "type": "Catcher", + "abbreviation": "C" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 664913, + "fullName": "Seth Brown", + "link": "/api/v1/people/664913" + }, + "jerseyNumber": "15", + "position": { + "code": "3", + "name": "First Base", + "type": "Infielder", + "abbreviation": "1B" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 669127, + "fullName": "Shea Langeliers", + "link": "/api/v1/people/669127" + }, + "jerseyNumber": "23", + "position": { + "code": "10", + "name": "Designated Hitter", + "type": "Hitter", + "abbreviation": "DH" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 643393, + "fullName": "Tony Kemp", + "link": "/api/v1/people/643393" + }, + "jerseyNumber": "5", + "position": { + "code": "4", + "name": "Second Base", + "type": "Infielder", + "abbreviation": "2B" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 664202, + "fullName": "Tyler Cyr", + "link": "/api/v1/people/664202" + }, + "jerseyNumber": "57", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 605353, + "fullName": "Vimael Machin", + "link": "/api/v1/people/605353" + }, + "jerseyNumber": "31", + "position": { + "code": "4", + "name": "Second Base", + "type": "Infielder", + "abbreviation": "2B" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 660634, + "fullName": "Yonny Hernandez", + "link": "/api/v1/people/660634" + }, + "jerseyNumber": "", + "position": { + "code": "5", + "name": "Third Base", + "type": "Infielder", + "abbreviation": "3B" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 667427, + "fullName": "Zach Jackson", + "link": "/api/v1/people/667427" + }, + "jerseyNumber": "61", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + }, + { + "person": { + "id": 656657, + "fullName": "Zach Logue", + "link": "/api/v1/people/656657" + }, + "jerseyNumber": "67", + "position": { + "code": "1", + "name": "Pitcher", + "type": "Pitcher", + "abbreviation": "P" + }, + "status": { + "code": "A", + "description": "Active" + }, + "parentTeamId": 133 + } ], + "link": "/api/v1/teams/133/roster", + "teamId": 133, + "rosterType": "active" +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/teams/teams.json b/tests/mock_tests/mock_json/teams/teams.json new file mode 100644 index 00000000..6dc99366 --- /dev/null +++ b/tests/mock_tests/mock_json/teams/teams.json @@ -0,0 +1,215 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "teams": [ { + "allStarStatus": "N", + "id": 4124, + "name": "Pensacola Blue Wahoos", + "link": "/api/v1/teams/4124", + "season": 2022, + "venue": { + "id": 4329, + "name": "Blue Wahoos Stadium", + "link": "/api/v1/venues/4329" + }, + "teamCode": "pen", + "fileCode": "t4124", + "abbreviation": "PNS", + "teamName": "Blue Wahoos", + "locationName": "Pensacola", + "firstYearOfPlay": "2012", + "league": { + "id": 111, + "name": "Southern League", + "link": "/api/v1/league/111" + }, + "division": { + "id": 240, + "name": "Southern League South", + "link": "/api/v1/divisions/240" + }, + "sport": { + "id": 12, + "link": "/api/v1/sports/12", + "name": "Double-A" + }, + "shortName": "Pensacola", + "parentOrgName": "Miami Marlins", + "parentOrgId": 146, + "franchiseName": "Pensacola", + "clubName": "Blue Wahoos", + "active": true + }, + { + "allStarStatus": "N", + "id": 2101, + "name": "DSL Brewers 2", + "link": "/api/v1/teams/2101", + "season": 2022, + "venue": { + "id": 401, + "name": "TBD", + "link": "/api/v1/venues/401" + }, + "teamCode": "brc", + "fileCode": "t2101", + "abbreviation": "DSL BRW2", + "teamName": "DSL Brewers 2", + "locationName": "United States", + "firstYearOfPlay": "1990", + "league": { + "id": 130, + "name": "Dominican Summer League", + "link": "/api/v1/league/130" + }, + "division": { + "id": 246, + "name": "Dominican Summer League San Pedro", + "link": "/api/v1/divisions/246" + }, + "sport": { + "id": 16, + "link": "/api/v1/sports/16", + "name": "Rookie" + }, + "shortName": "DSL Brewers 2", + "parentOrgName": "Milwaukee Brewers", + "parentOrgId": 158, + "franchiseName": "DSL Brewers 2", + "clubName": "DSL Brewers 2", + "active": true + }, + { + "allStarStatus": "N", + "id": 3130, + "name": "DSL BAL Orange", + "link": "/api/v1/teams/3130", + "season": 2022, + "venue": { + "id": 401, + "name": "TBD", + "link": "/api/v1/venues/401" + }, + "teamCode": "dba", + "fileCode": "t3130", + "abbreviation": "DSL BALO", + "teamName": "DSL BAL Orange", + "locationName": "United States", + "firstYearOfPlay": "2009", + "league": { + "id": 130, + "name": "Dominican Summer League", + "link": "/api/v1/league/130" + }, + "division": { + "id": 250, + "name": "Dominican Summer League Baseball City", + "link": "/api/v1/divisions/250" + }, + "sport": { + "id": 16, + "link": "/api/v1/sports/16", + "name": "Rookie" + }, + "shortName": "DSL BAL Orange", + "parentOrgName": "Baltimore Orioles", + "parentOrgId": 110, + "franchiseName": "DSL BAL Orange", + "clubName": "DSL BAL Orange", + "active": true + }, + { + "springLeague" : { + "id" : 114, + "name" : "Cactus League", + "link" : "/api/v1/league/114", + "abbreviation" : "CL" + }, + "allStarStatus" : "N", + "id" : 136, + "name" : "Seattle Mariners", + "link" : "/api/v1/teams/136", + "season" : 2022, + "venue" : { + "id" : 680, + "name" : "T-Mobile Park", + "link" : "/api/v1/venues/680" + }, + "springVenue" : { + "id" : 2530, + "link" : "/api/v1/venues/2530" + }, + "teamCode" : "sea", + "fileCode" : "sea", + "abbreviation" : "SEA", + "teamName" : "Mariners", + "locationName" : "Seattle", + "firstYearOfPlay" : "1977", + "league" : { + "id" : 103, + "name" : "American League", + "link" : "/api/v1/league/103" + }, + "division" : { + "id" : 200, + "name" : "American League West", + "link" : "/api/v1/divisions/200" + }, + "sport" : { + "id" : 1, + "link" : "/api/v1/sports/1", + "name" : "Major League Baseball" + }, + "shortName" : "Seattle", + "franchiseName" : "Seattle", + "clubName" : "Mariners", + "active" : true + }, + { + "springLeague": { + "id": 114, + "name": "Cactus League", + "link": "/api/v1/league/114", + "abbreviation": "CL" + }, + "allStarStatus": "N", + "id": 133, + "name": "Oakland Athletics", + "link": "/api/v1/teams/133", + "season": 2022, + "venue": { + "id": 10, + "name": "Oakland Coliseum", + "link": "/api/v1/venues/10" + }, + "springVenue": { + "id": 2507, + "link": "/api/v1/venues/2507" + }, + "teamCode": "oak", + "fileCode": "oak", + "abbreviation": "OAK", + "teamName": "Athletics", + "locationName": "Oakland", + "firstYearOfPlay": "1901", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "division": { + "id": 200, + "name": "American League West", + "link": "/api/v1/divisions/200" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "name": "Major League Baseball" + }, + "shortName": "Oakland", + "franchiseName": "Oakland", + "clubName": "Athletics", + "active": true + } + ] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/venues/venue.json b/tests/mock_tests/mock_json/venues/venue.json new file mode 100644 index 00000000..8eaff74b --- /dev/null +++ b/tests/mock_tests/mock_json/venues/venue.json @@ -0,0 +1,10 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "venues": [ + { + "id": 31, + "name": "PNC Park", + "link": "/api/v1/venues/31", + "active": true + }] +} \ No newline at end of file diff --git a/tests/mock_tests/mock_json/venues/venues.json b/tests/mock_tests/mock_json/venues/venues.json new file mode 100644 index 00000000..57bfafab --- /dev/null +++ b/tests/mock_tests/mock_json/venues/venues.json @@ -0,0 +1,46 @@ +{ + "copyright": "Copyright 2022 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "venues": [ + { + "id": 5515, + "name": "Colorado Convention Center", + "link": "/api/v1/venues/5515", + "active": true + }, + { + "id": 5520, + "name": "Virginia Credit Union Stadium", + "link": "/api/v1/venues/5520", + "active": true + }, + { + "id": 5525, + "name": "ABC Supply Stadium", + "link": "/api/v1/venues/5525", + "active": true + }, + { + "id": 4009, + "name": "Miyagi Baseball Stadium", + "link": "/api/v1/venues/4009", + "active": false + }, + { + "id": 4010, + "name": "Tsuruoka Dream Stadium", + "link": "/api/v1/venues/4010", + "active": false + }, + { + "id": 4011, + "name": "Meiji Jingu Stadium", + "link": "/api/v1/venues/4011", + "active": false + }, + { + "id": 31, + "name": "PNC Park", + "link": "/api/v1/venues/31", + "active": true + } ] +} \ No newline at end of file diff --git a/tests/mock_tests/schedules/test_schedule_mock.py b/tests/mock_tests/schedules/test_schedule_mock.py new file mode 100644 index 00000000..7a1bf7f7 --- /dev/null +++ b/tests/mock_tests/schedules/test_schedule_mock.py @@ -0,0 +1,91 @@ +import unittest +import requests_mock +import json +import os + +from mlbstatsapi import Mlb +from mlbstatsapi.models.schedules import Schedule, ScheduleGames + +# Mocked JSON directory +# TODO Find a better way to structure and handle this :) +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_schedule_date = os.path.join(current_directory, "../mock_json/schedule/schedule_date.json") +path_to_schedule_start_end_dates = os.path.join(current_directory, "../mock_json/schedule/schedule_start_end_date.json") +path_to_not_found = os.path.join(current_directory, "../mock_json/response/not_found_404.json") +path_to_error = os.path.join(current_directory, "../mock_json/response/error_500.json") + +SCHEDULES = open(path_to_schedule_date, "r", encoding="utf-8-sig").read() +SCHEDULE = open(path_to_schedule_start_end_dates, "r", encoding="utf-8-sig").read() +NOT_FOUND_404 = open(path_to_not_found, "r", encoding="utf-8-sig").read() +ERROR_500 = open(path_to_error, "r", encoding="utf-8-sig").read() + +@requests_mock.Mocker() +class TestScheduleMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.al_team = 133 + + cls.mock_schedule = json.loads(SCHEDULE) + cls.mock_schedules = json.loads(SCHEDULE) + + cls.error_500 = json.loads(ERROR_500) + cls.mock_not_found = json.loads(NOT_FOUND_404) + + + @classmethod + def tearDownClass(cls) -> None: + pass + + + def test_get_schedule(self, m ): + """get_schedule should return schedules for date, starDate, endDate""" + m.get('https://statsapi.mlb.com/api/v1/schedule?date=04/07/2022&sportId=1', json=self.mock_schedule, + status_code=200) + self.date = '04/07/2022' + self.sport_id = 1 + schedule = self.mlb.get_schedule(date=self.date) + + # get_schedule should return Schedule object + self.assertIsInstance(schedule, Schedule) + # get_schedule should return dates + self.assertTrue(schedule.dates) + + def test_get_schedule_start_end_dates(self, m): + """get_schedule should return a schedule object with start_date, end_date""" + m.get('https://statsapi.mlb.com/api/v1/schedule?sportId=1&startDate=10/10/2022&endDate=10/13/2022', json=self.mock_schedules, + status_code=200) + self.sport_id = 1 + self.start_date = '10/10/2022' + self.end_date = '10/13/2022' + schedule = self.mlb.get_schedule(start_date=self.start_date, end_date=self.end_date, sport_id=self.sport_id) + + # get_schedule should return Schedule object + self.assertIsInstance(schedule, Schedule) + + # list should contain more than 1 object + self.assertTrue(len(schedule.dates) > 1) + + + def test_get_scheduled_games_by_date(self, m): + """get_schedule should return a ScheduledGames object with start_date, end_date""" + m.get('https://statsapi.mlb.com/api/v1/schedule?sportId=1&startDate=10/10/2022&endDate=10/13/2022', json=self.mock_schedules, + status_code=200) + self.sport_id = 1 + self.start_date = '10/10/2022' + self.end_date = '10/13/2022' + + scheduled_games = self.mlb.get_scheduled_games_by_date(start_date=self.start_date, end_date=self.end_date, sport_id=1) + + # scheduled games should not be none + self.assertIsNotNone(scheduled_games) + + self.assertIsInstance(scheduled_games, list) + + # scheduled games should not be empty list + self.assertNotEqual(scheduled_games, []) + + scheduled_game = scheduled_games[0] + + self.assertIsInstance(scheduled_game, ScheduleGames) \ No newline at end of file diff --git a/tests/mock_tests/standings/test_standings_mock.py b/tests/mock_tests/standings/test_standings_mock.py new file mode 100644 index 00000000..bef712fd --- /dev/null +++ b/tests/mock_tests/standings/test_standings_mock.py @@ -0,0 +1,56 @@ +from unittest.mock import patch +import unittest +import requests_mock +import json +import os + +from mlbstatsapi.models.standings import Standings +from mlbstatsapi import Mlb + +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_standings = os.path.join(current_directory, "../mock_json/standings/standings.json") +STANDINGS_JSON_FILE = open(path_to_standings, "r", encoding="utf-8-sig").read() + +@requests_mock.Mocker() +class TestStandingsMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.standings_mock = json.loads(STANDINGS_JSON_FILE) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_get_standings(self, m): + """This test should return a 200 and Round""" + + m.get('https://statsapi.mlb.com/api/v1/standings?leagueId=103&season=2018', json=self.standings_mock, + status_code=200) + + # set league id + league_id = 103 + # set season + season = 2018 + + # call get_standings return list of standings + standings = self.mlb.get_standings(league_id, season) + + # Gamepace should not be None + self.assertIsNotNone(standings) + + # list should not be empty + self.assertNotEqual(standings, []) + + # items in list should be standings + self.assertIsInstance(standings[0], Standings) + + standing = standings[0] + + # sportgamepace should not be none + self.assertIsNotNone(standing) + + # sportgamepace should have attrs set + self.assertTrue(standing.standingstype) + self.assertTrue(standing.lastupdated) \ No newline at end of file diff --git a/tests/mock_tests/stats/test_game_player_stats_for_game.py b/tests/mock_tests/stats/test_game_player_stats_for_game.py new file mode 100644 index 00000000..54f45171 --- /dev/null +++ b/tests/mock_tests/stats/test_game_player_stats_for_game.py @@ -0,0 +1,165 @@ +import unittest +import time + +from mlbstatsapi.mlb_api import Mlb + +import unittest +import requests_mock +import json +import os + +from mlbstatsapi import Mlb + + +# Mocked JSON directory +# TODO Find a better way to structure and handle this :) +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_not_found = os.path.join(current_directory, "../mock_json/response/not_found_404.json") +path_to_error = os.path.join(current_directory, "../mock_json/response/error_500.json") +path_to_hotcoldzone_file = os.path.join(current_directory, "../mock_json/stats/person/hotcoldzone.json") +path_to_hitting_playlog_file = os.path.join(current_directory, "../mock_json/stats/person/hitting_player_playlog.json") +path_to_shoei_ohtani = os.path.join(current_directory, "../mock_json/stats/person/game_stats_player_shoei_ohtani.json") +path_to_ty_france = os.path.join(current_directory, "../mock_json/stats/person/game_stats_player_ty_france.json") +path_to_cal = os.path.join(current_directory, "../mock_json/stats/person/game_stats_player_cal.json") +path_to_archie = os.path.join(current_directory, "../mock_json/stats/person/game_stats_player_archie.json") +NOT_FOUND_404 = open(path_to_not_found, "r", encoding="utf-8-sig").read() +ERROR_500 = open(path_to_error, "r", encoding="utf-8-sig").read() +SHOEI_ENDPOINT = open(path_to_shoei_ohtani, "r", encoding="utf-8-sig").read() +TY_ENDPOINT = open(path_to_ty_france, "r", encoding="utf-8-sig").read() +CAL_ENDPOINT = open(path_to_cal, "r", encoding="utf-8-sig").read() +ARCHIE_ENDPOINT = open(path_to_archie, "r", encoding="utf-8-sig").read() + + +@requests_mock.Mocker() +class TestHittingStats(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.al_team = 133 + cls.shoei_ohtani = 660271 + cls.ty_france = 664034 + cls.shoei_game_id = 531368 + cls.ty_game_id = 715757 + cls.cal_realeigh = 663728 + cls.cal_game_id = 715757 + cls.archie_bradley = 605151 + cls.archie_game_id = 531368 + cls.mock_ty_france_stats = json.loads(TY_ENDPOINT) + cls.mock_shoei_stats = json.loads(SHOEI_ENDPOINT) + cls.mock_cal_stats = json.loads(CAL_ENDPOINT) + cls.mock_archie_stats = json.loads(ARCHIE_ENDPOINT) + + @classmethod + def tearDownClass(cls) -> None: + pass + + + def test_get_players_stats_for_shoei_ohtana(self, m): + """return player stat objects""" + m.get('https://statsapi.mlb.com/api/v1/people/660271/stats/game/531368', json=self.mock_shoei_stats, + status_code=200) + game_stats = self.mlb.get_players_stats_for_game(person_id=self.shoei_ohtani, + game_id=self.shoei_game_id) + + # game stats should not be None + self.assertIsNotNone(game_stats) + + # game_stats should be a dict + self.assertIsInstance(game_stats, dict) + + # game_stats should have hitting stats + self.assertTrue(game_stats['pitching']) + + # game_stats should have vsplayer5y and playlog stats + self.assertTrue(game_stats['pitching']['vsplayer5y']) + + splits = game_stats['pitching']['vsplayer5y'] + + for split in splits.splits: + self.assertTrue(split.team) + self.assertTrue(split.stat) + + def test_get_players_stats_for_ty_france(self, m): + """return player stat objects""" + m.get('https://statsapi.mlb.com/api/v1/people/664034/stats/game/715757', json=self.mock_ty_france_stats, + status_code=200) + game_stats = self.mlb.get_players_stats_for_game(person_id=self.ty_france, + game_id=self.ty_game_id) + + # game stats should not be None + self.assertIsNotNone(game_stats) + + # game_stats should be a dict + self.assertIsInstance(game_stats, dict) + + # game_stats should have hitting stats + self.assertTrue(game_stats['hitting']) + + # game_stats should have vsplayer5y and playlog stats + self.assertTrue(game_stats['hitting']['vsplayer5y']) + self.assertTrue(game_stats['hitting']['playlog']) + self.assertTrue(game_stats['stats']['gamelog']) + + splits = game_stats['hitting']['vsplayer5y'] + + for split in splits.splits: + self.assertTrue(split.team) + self.assertTrue(split.stat) + + def test_get_players_stats_for_cal_r(self, m): + """return player stat objects""" + m.get('https://statsapi.mlb.com/api/v1/people/663728/stats/game/715757', json=self.mock_cal_stats, + status_code=200) + game_stats = self.mlb.get_players_stats_for_game(person_id=self.cal_realeigh, + game_id=self.cal_game_id) + + # game stats should not be None + self.assertIsNotNone(game_stats) + + # game_stats should be a dict + self.assertIsInstance(game_stats, dict) + + # game_stats should have hitting stats + self.assertTrue(game_stats['hitting']) + + # game_stats should have vsplayer5y and playlog stats + self.assertTrue(game_stats['hitting']['vsplayer5y']) + self.assertTrue(game_stats['hitting']['playlog']) + self.assertTrue(game_stats['stats']['gamelog']) + + splits = game_stats['hitting']['vsplayer5y'] + + for split in splits.splits: + self.assertTrue(split.team) + self.assertTrue(split.stat) + + def test_get_players_stats_for_archie(self, m): + """return player stat objects""" + m.get('https://statsapi.mlb.com/api/v1/people/605151/stats/game/531368', json=self.mock_archie_stats, + status_code=200) + game_stats = self.mlb.get_players_stats_for_game(person_id=self.archie_bradley, + game_id=self.archie_game_id) + + # game stats should not be None + self.assertIsNotNone(game_stats) + + # game_stats should be a dict + self.assertIsInstance(game_stats, dict) + + # game_stats should have hitting stats + self.assertTrue(game_stats['pitching']) + + # game_stats should have vsplayer5y and playlog stats + self.assertTrue(game_stats['pitching']['vsplayer5y']) + self.assertTrue(game_stats['stats']['gamelog']) + + gamelogs = game_stats['stats']['gamelog'] + self.assertEqual(len(gamelogs.splits), 3) + self.assertEqual(gamelogs.totalsplits, len(gamelogs.splits)) + + vsplayer5y = game_stats['pitching']['vsplayer5y'] + + for split in vsplayer5y.splits: + self.assertTrue(split.team) + self.assertTrue(split.stat) \ No newline at end of file diff --git a/tests/mock_tests/stats/test_hitting_stats_mock.py b/tests/mock_tests/stats/test_hitting_stats_mock.py new file mode 100644 index 00000000..5687e15d --- /dev/null +++ b/tests/mock_tests/stats/test_hitting_stats_mock.py @@ -0,0 +1,244 @@ +import unittest +import requests_mock +import json +import os + +from mlbstatsapi import Mlb + + +# Mocked JSON directory +# TODO Find a better way to structure and handle this :) +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_player_stats = os.path.join(current_directory, "../mock_json/stats/person/hitting_player_stats.json") +path_to_team_stats = os.path.join(current_directory, "../mock_json/stats/team/hitting_team_stats.json") +path_to_not_found = os.path.join(current_directory, "../mock_json/response/not_found_404.json") +path_to_error = os.path.join(current_directory, "../mock_json/response/error_500.json") +path_to_hotcoldzone_file = os.path.join(current_directory, "../mock_json/stats/person/hotcoldzone.json") +path_to_hitting_playlog_file = os.path.join(current_directory, "../mock_json/stats/person/hitting_player_playlog.json") +path_to_hitting_pitchlog_file = os.path.join(current_directory, "../mock_json/stats/person/hitting_player_pitchlog.json") +path_to_spraychart_file = os.path.join(current_directory, "../mock_json/stats/person/spraychart.json") + +SPRAYCHART = open(path_to_spraychart_file, "r", encoding="utf-8-sig").read() +HOTCOLDZONE = open(path_to_hotcoldzone_file, "r", encoding="utf-8-sig").read() +PLAYERSTATS = open(path_to_player_stats, "r", encoding="utf-8-sig").read() +TEAMSTATS = open(path_to_team_stats, "r", encoding="utf-8-sig").read() +NOT_FOUND_404 = open(path_to_not_found, "r", encoding="utf-8-sig").read() +ERROR_500 = open(path_to_error, "r", encoding="utf-8-sig").read() +HITTING_PLAY_LOG = open(path_to_hitting_playlog_file, "r", encoding="utf-8-sig").read() +HITTING_PITCH_LOG = open(path_to_hitting_pitchlog_file, "r", encoding="utf-8-sig").read() + +@requests_mock.Mocker() +class TestHittingStatsMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.player = cls.mlb.get_person(665742) + cls.team = cls.mlb.get_team(133) + cls.mock_player_stats = json.loads(PLAYERSTATS) + cls.mock_team_stats = json.loads(TEAMSTATS) + cls.mock_hotcoldzone = json.loads(HOTCOLDZONE) + cls.error_500 = json.loads(ERROR_500) + cls.mock_not_found = json.loads(NOT_FOUND_404) + cls.mock_hitting_playlog = json.loads(HITTING_PLAY_LOG) + cls.mock_hitting_pitchlog = json.loads(HITTING_PITCH_LOG) + cls.mock_spraycharts = json.loads(SPRAYCHART) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_hitting_stat_attributes_player(self, m): + """mlb get stats should return pitching stats""" + m.get('https://statsapi.mlb.com/api/v1/people/665742/stats?stats=season&stats=career&stats=seasonAdvanced&stats=careerAdvanced&group=hitting', json=self.mock_player_stats, + status_code=200) + self.stats = ['season', 'career','seasonAdvanced', 'careerAdvanced'] + self.group = ['hitting'] + # let's get some stats + stats = self.mlb.get_player_stats(self.player.id, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + self.assertFalse('pitching' in stats) + self.assertEqual(len(stats['hitting']), 4) + + # check for split objects + self.assertTrue(stats['hitting']['season']) + self.assertTrue(stats['hitting']['career']) + self.assertTrue(stats['hitting']['seasonadvanced']) + self.assertTrue(stats['hitting']['careeradvanced']) + + # let's pull out a object and test it + season = stats['hitting']['season'] + career = stats['hitting']['career'] + season_advanced = stats['hitting']['seasonadvanced'] + career_advanced = stats['hitting']['careeradvanced'] + # check that attrs exist and contain data + + self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.group, 'hitting') + self.assertEqual(season.type, 'season') + + self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.group, 'hitting') + self.assertEqual(career.type, 'career') + + self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.group, 'hitting') + self.assertEqual(season_advanced.type, 'seasonAdvanced') + + self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.group, 'hitting') + self.assertEqual(career_advanced.type, 'careerAdvanced') + + def test_pitching_stat_attributes_team(self, m): + """mlb get stats should return pitching stats""" + m.get('https://statsapi.mlb.com/api/v1/teams/133/stats?stats=season&stats=career&stats=seasonAdvanced&stats=careerAdvanced&group=hitting', json=self.mock_team_stats, + status_code=200) + self.stats = ['season', 'career', 'seasonAdvanced', 'careerAdvanced'] + self.group = ['hitting'] + # let's get some stats + stats = self.mlb.get_team_stats(self.team.id, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('hitting' in stats) + self.assertFalse('pitching' in stats) + self.assertEqual(len(stats['hitting']), 4) + + # check for split objects + self.assertTrue(stats['hitting']['season']) + self.assertTrue(stats['hitting']['career']) + self.assertTrue(stats['hitting']['seasonadvanced']) + self.assertTrue(stats['hitting']['careeradvanced']) + + # let's pull out a object and test it + season = stats['hitting']['season'] + career = stats['hitting']['career'] + season_advanced = stats['hitting']['seasonadvanced'] + career_advanced = stats['hitting']['careeradvanced'] + # check that attrs exist and contain data + + self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.group, 'hitting') + self.assertEqual(season.type, 'season') + + self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.group, 'hitting') + self.assertEqual(career.type, 'career') + + self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.group, 'hitting') + self.assertEqual(season_advanced.type, 'seasonAdvanced') + + self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.group, 'hitting') + self.assertEqual(career_advanced.type, 'careerAdvanced') + + def test_hitting_hotcoldzones_for_player(self, m): + """get_player_game_stats should return a dict with stats""" + m.get('https://statsapi.mlb.com/api/v1/people/665742/stats?stats=hotColdZones&group=hitting', json=self.mock_hotcoldzone, + status_code=200) + self.stats = ['hotColdZones'] + self.groups = ['hitting'] + stats = self.mlb.get_player_stats(self.player.id, stats=self.stats, groups=self.groups) + + # game_stats should not be None + self.assertIsNotNone(stats) + + # game_stats should not be empty dic + self.assertNotEqual(stats, {}) + + self.assertTrue(stats['stats']['hotcoldzones']) + + # hotcoldzone should return 5 splits + hotcoldzone = stats['stats']['hotcoldzones'] + self.assertEqual(len(hotcoldzone.splits), 5) + self.assertEqual(hotcoldzone.totalsplits, len(hotcoldzone.splits)) + + # hot cold zone should have 13 zones for each zone type + for split in hotcoldzone.splits: + self.assertTrue(split.stat.name) + self.assertEqual(len(split.stat.zones), 13) + + def test_hitting_pitchlog_for_player(self, m): + """get_player_game_stats should return a dict with stats""" + m.get('https://statsapi.mlb.com/api/v1/people/665742/stats?stats=pitchLog&group=hitting', json=self.mock_hitting_pitchlog, + status_code=200) + self.stats = ['pitchLog'] + self.groups = ['hitting'] + stats = self.mlb.get_player_stats(self.player.id, stats=self.stats, groups=self.groups) + + # game_stats should not be None + self.assertIsNotNone(stats) + + # game_stats should not be empty dic + self.assertNotEqual(stats, {}) + + # playlog key should be populated + self.assertTrue('hitting' in stats) + self.assertTrue(stats['hitting']['pitchlog']) + + # pitchlog should have 2 splits from mock + pitchlogs = stats['hitting']['pitchlog'] + self.assertEqual(len(pitchlogs.splits), 6) + self.assertEqual(pitchlogs.totalsplits, len(pitchlogs.splits)) + + for pitchlog in pitchlogs.splits: + self.assertTrue(pitchlog.stat.details) + self.assertTrue(pitchlog.stat.count) + + + def test_hitting_playlog_for_player(self, m): + """get_player_game_stats should return a dict with stats""" + m.get('https://statsapi.mlb.com/api/v1/people/665742/stats?stats=playLog&group=hitting', json=self.mock_hitting_playlog, + status_code=200) + self.stats = ['playLog'] + self.groups = ['hitting'] + stats = self.mlb.get_player_stats(self.player.id, stats=self.stats, groups=self.groups) + + # game_stats should not be None + self.assertIsNotNone(stats) + + # game_stats should not be empty dic + self.assertNotEqual(stats, {}) + + # playlog key should be populated + self.assertTrue('hitting' in stats) + self.assertTrue(stats['hitting']['playlog']) + + # pitchlog items should have 2 splits + pitchlogs = stats['hitting']['playlog'] + self.assertEqual(len(pitchlogs.splits), 2) + self.assertEqual(pitchlogs.totalsplits, len(pitchlogs.splits)) + + for pitchlog in pitchlogs.splits: + self.assertTrue(pitchlog.stat) + + def test_hitting_spraychart_for_player(self, m): + """get_player_game_stats should return a dict with stats""" + m.get('https://statsapi.mlb.com/api/v1/people/665742/stats?stats=sprayChart&group=hitting', json=self.mock_spraycharts, + status_code=200) + self.stats = ['sprayChart'] + self.groups = ['hitting'] + spraychart = self.mlb.get_player_stats(self.player.id, stats=self.stats, groups=self.groups) + + # game_stats should not be None + self.assertIsNotNone(spraychart) + + # game_stats should not be empty dic + self.assertNotEqual(spraychart, {}) + + self.assertTrue(spraychart['stats']['spraychart']) + + spraychart = spraychart['stats']['spraychart'] + self.assertEqual(len(spraychart.splits), 1) + self.assertEqual(spraychart.totalsplits, len(spraychart.splits)) + + for pitchlog in spraychart.splits: + self.assertTrue(pitchlog.stat) \ No newline at end of file diff --git a/tests/mock_tests/stats/test_pitching_stats_mock.py b/tests/mock_tests/stats/test_pitching_stats_mock.py new file mode 100644 index 00000000..fd17f5f0 --- /dev/null +++ b/tests/mock_tests/stats/test_pitching_stats_mock.py @@ -0,0 +1,249 @@ +import unittest +import requests_mock +import json +import os + +from mlbstatsapi import Mlb + + +# Mocked JSON directory +# TODO Find a better way to structure and handle this :) +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_hotcoldzone_file = os.path.join(current_directory, "../mock_json/stats/person/hotcoldzone.json") +path_to_not_found = os.path.join(current_directory, "../mock_json/response/not_found_404.json") +path_to_error = os.path.join(current_directory, "../mock_json/response/error_500.json") +path_to_player_stats = os.path.join(current_directory, "../mock_json/stats/person/pitching_player_stats.json") +path_to_team_stats = os.path.join(current_directory, "../mock_json/stats/team/pitching_team_stats.json") +path_to_pitching_playlog_file = os.path.join(current_directory, "../mock_json/stats/person/pitching_player_playlog.json") +path_to_pitching_pitchlog_file = os.path.join(current_directory, "../mock_json/stats/person/pitching_player_pitchlog.json") +path_to_spraychart_file = os.path.join(current_directory, "../mock_json/stats/person/spraychart.json") + +HOTCOLDZONE = open(path_to_hotcoldzone_file, "r", encoding="utf-8-sig").read() +NOT_FOUND_404 = open(path_to_not_found, "r", encoding="utf-8-sig").read() +ERROR_500 = open(path_to_error, "r", encoding="utf-8-sig").read() +PLAYERSTATS = open(path_to_player_stats, "r", encoding="utf-8-sig").read() +TEAMSTATS = open(path_to_team_stats, "r", encoding="utf-8-sig").read() +PITCHING_PLAY_LOG = open(path_to_pitching_playlog_file, "r", encoding="utf-8-sig").read() +PITCHING_PITCH_LOG = open(path_to_pitching_pitchlog_file, "r", encoding="utf-8-sig").read() +SPRAYCHART = open(path_to_spraychart_file, "r", encoding="utf-8-sig").read() + + +@requests_mock.Mocker() +class TestPitchingStatsMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.pitcher = cls.mlb.get_person(660271) + cls.al_team = cls.mlb.get_team(133) + cls.mock_hotcoldzone = json.loads(HOTCOLDZONE) + cls.error_500 = json.loads(ERROR_500) + cls.mock_not_found = json.loads(NOT_FOUND_404) + cls.mock_player_stats = json.loads(PLAYERSTATS) + cls.mock_team_stats = json.loads(TEAMSTATS) + cls.mock_pitching_playlog = json.loads(PITCHING_PLAY_LOG) + cls.mock_pitching_pitchlog = json.loads(PITCHING_PITCH_LOG) + cls.mock_spraycharts = json.loads(SPRAYCHART) + + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_pitching_stat_attributes_player(self, m): + """mlb get stats should return pitching stats""" + m.get('https://statsapi.mlb.com/api/v1/people/660271/stats?stats=season&stats=career&stats=seasonAdvanced&stats=careerAdvanced&group=pitching', json=self.mock_player_stats, + status_code=200) + self.stats = ['season', 'career', 'seasonAdvanced', 'careerAdvanced'] + self.group = ['pitching'] + # let's get some stats + stats = self.mlb.get_player_stats(self.pitcher.id, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('pitching' in stats) + self.assertFalse('hitting' in stats) + self.assertEqual(len(stats['pitching']), 4) + + # check for split objects + self.assertTrue(stats['pitching']['season']) + self.assertTrue(stats['pitching']['career']) + self.assertTrue(stats['pitching']['seasonadvanced']) + self.assertTrue(stats['pitching']['careeradvanced']) + + season = stats['pitching']['season'] + career = stats['pitching']['career'] + season_advanced = stats['pitching']['seasonadvanced'] + career_advanced = stats['pitching']['careeradvanced'] + + self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.group, 'pitching') + self.assertEqual(season.type, 'season') + + self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.group, 'pitching') + self.assertEqual(career.type, 'career') + + self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.group, 'pitching') + self.assertEqual(season_advanced.type, 'seasonAdvanced') + + self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.group, 'pitching') + self.assertEqual(career_advanced.type, 'careerAdvanced') + + def test_pitching_stat_attributes_team(self, m): + """mlb get stats should return pitching stats""" + m.get('https://statsapi.mlb.com/api/v1/teams/133/stats?stats=season&stats=career&stats=seasonAdvanced&stats=careerAdvanced&group=pitching', json=self.mock_team_stats, + status_code=200) + self.stats = ['season', 'career','seasonAdvanced', 'careerAdvanced'] + self.group = ['pitching'] + # let's get some stats + stats = self.mlb.get_team_stats(self.al_team.id, stats=self.stats, groups=self.group) + + # check for empty dict + self.assertNotEqual(stats, {}) + + # the end point should give us 2 hitting + self.assertTrue('pitching' in stats) + self.assertFalse('hitting' in stats) + self.assertEqual(len(stats['pitching']), 4) + + # check for split objects + self.assertTrue(stats['pitching']['season']) + self.assertTrue(stats['pitching']['career']) + self.assertTrue(stats['pitching']['seasonadvanced']) + self.assertTrue(stats['pitching']['careeradvanced']) + + season = stats['pitching']['season'] + career = stats['pitching']['career'] + season_advanced = stats['pitching']['seasonadvanced'] + career_advanced = stats['pitching']['careeradvanced'] + + self.assertEqual(season.totalsplits, len(season.splits)) + self.assertEqual(season.group, 'pitching') + self.assertEqual(season.type, 'season') + + self.assertEqual(career.totalsplits, len(career.splits)) + self.assertEqual(career.group, 'pitching') + self.assertEqual(career.type, 'career') + + self.assertEqual(season_advanced.totalsplits, len(season_advanced.splits)) + self.assertEqual(season_advanced.group, 'pitching') + self.assertEqual(season_advanced.type, 'seasonAdvanced') + + self.assertEqual(career_advanced.totalsplits, len(career_advanced.splits)) + self.assertEqual(career_advanced.group, 'pitching') + self.assertEqual(career_advanced.type, 'careerAdvanced') + + def test_pitching_play_log_for_player(self, m): + """get_player_game_stats should return a dict with stats""" + m.get('https://statsapi.mlb.com/api/v1/people/660271/stats?stats=hotColdZones&group=pitching', json=self.mock_hotcoldzone, + status_code=200) + self.stats = ['hotColdZones'] + self.groups = ['pitching'] + stats = self.mlb.get_player_stats(self.pitcher.id, stats=self.stats, groups=self.groups) + + # game_stats should not be None + self.assertIsNotNone(stats) + + # game_stats should not be empty dic + self.assertNotEqual(stats, {}) + + # should not be empty + self.assertTrue(stats['stats']['hotcoldzones']) + + hotcoldzone = stats['stats']['hotcoldzones'] + + # check for split objects + self.assertTrue(stats['stats']['hotcoldzones']) + + # hotcoldzone should return 5 splits + hotcoldzone = stats['stats']['hotcoldzones'] + self.assertEqual(len(hotcoldzone.splits), 5) + self.assertEqual(hotcoldzone.totalsplits, len(hotcoldzone.splits)) + + # hot cold zone should have 13 zones for each zone type + for split in hotcoldzone.splits: + self.assertTrue(split.stat.name) + self.assertEqual(len(split.stat.zones), 13) + + def test_pitching_pitchlog_for_pitcher(self, m): + """get_player_game_stats should return a dict with stats""" + m.get('https://statsapi.mlb.com/api/v1/people/660271/stats?stats=pitchLog&group=pitching', json=self.mock_pitching_pitchlog, + status_code=200) + self.stats = ['pitchLog'] + self.groups = ['pitching'] + stats = self.mlb.get_player_stats(self.pitcher.id, stats=self.stats, groups=self.groups) + + # game_stats should not be None + self.assertIsNotNone(stats) + + # game_stats should not be empty dic + self.assertNotEqual(stats, {}) + + # playlog key should be populated + self.assertTrue('pitching' in stats) + self.assertTrue(stats['pitching']['pitchlog']) + + # pitchlog should have 2 splits from mock + pitchlogs = stats['pitching']['pitchlog'] + self.assertEqual(len(pitchlogs.splits), 2) + self.assertEqual(pitchlogs.totalsplits, len(pitchlogs.splits)) + + for pitchlog in pitchlogs.splits: + self.assertTrue(pitchlog.stat.details) + self.assertTrue(pitchlog.stat.count) + + + def test_pitching_playlog_for_pitcher(self, m): + """get_player_game_stats should return a dict with stats""" + m.get('https://statsapi.mlb.com/api/v1/people/660271/stats?stats=playLog&group=pitching', json=self.mock_pitching_playlog, + status_code=200) + self.stats = ['playLog'] + self.groups = ['pitching'] + stats = self.mlb.get_player_stats(self.pitcher.id, stats=self.stats, groups=self.groups) + + # game_stats should not be None + self.assertIsNotNone(stats) + + # game_stats should not be empty dic + self.assertNotEqual(stats, {}) + + # playlog key should be populated + self.assertTrue('pitching' in stats) + self.assertTrue(stats['pitching']['playlog']) + + # pitchlog items should have 2 splits + pitchlogs = stats['pitching']['playlog'] + self.assertEqual(len(pitchlogs.splits), 2) + self.assertEqual(pitchlogs.totalsplits, len(pitchlogs.splits)) + + for pitchlog in pitchlogs.splits: + self.assertTrue(pitchlog.stat) + + def test_pitching_play_log_for_player(self, m): + """get_player_game_stats should return a dict with stats""" + m.get('https://statsapi.mlb.com/api/v1/people/660271/stats?stats=sprayChart&group=pitching', json=self.mock_spraycharts, + status_code=200) + self.stats = ['sprayChart'] + self.groups = ['pitching'] + spraychart = self.mlb.get_player_stats(self.pitcher.id, stats=self.stats, groups=self.groups) + + # game_stats should not be None + self.assertIsNotNone(spraychart) + + # game_stats should not be empty dic + self.assertNotEqual(spraychart, {}) + + self.assertTrue(spraychart['stats']['spraychart']) + + + spraychart = spraychart['stats']['spraychart'] + self.assertEqual(len(spraychart.splits), 1) + self.assertEqual(spraychart.totalsplits, len(spraychart.splits)) + + for pitchlog in spraychart.splits: + self.assertTrue(pitchlog.stat) \ No newline at end of file diff --git a/tests/mock_tests/teams/test_roster_mock.py b/tests/mock_tests/teams/test_roster_mock.py new file mode 100644 index 00000000..e93c5160 --- /dev/null +++ b/tests/mock_tests/teams/test_roster_mock.py @@ -0,0 +1,66 @@ +import unittest +import requests_mock +import json +import os + +from mlbstatsapi import Mlb +from mlbstatsapi.models.teams import Team + +# Mocked JSON directory +# TODO Find a better way to structure and handle this :) +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_player_roster = os.path.join(current_directory, "../mock_json/teams/team_roster_players.json") +path_to_coaches_roster = os.path.join(current_directory, "../mock_json/teams/team_coaches.json") + + +PLAYERS = open(path_to_player_roster, "r", encoding="utf-8-sig").read() +COACHES = open(path_to_coaches_roster, "r", encoding="utf-8-sig").read() + + + + + +@requests_mock.Mocker() +class TestTeamRoster(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.mock_players = json.loads(PLAYERS) + cls.mock_coaches = json.loads(COACHES) + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_team_roster_list_of_player_objects(self, m): + """Default Team Roster should return a list of players""" + m.get('https://statsapi.mlb.com/api/v1/teams/133/roster', json=self.mock_players, + status_code=200) + + roster = self.mlb.get_team_roster(133) + + # roster should not be None + self.assertIsNotNone(roster) + + # roster should be a list + self.assertIsInstance(roster, list) + + # roster should not be a empty list + self.assertNotEqual(roster, []) + + def test_team_roster_list_of_coach_objects(self, m): + """Default Team Roster should return a list of players""" + m.get('https://statsapi.mlb.com/api/v1/teams/133/coaches', json=self.mock_coaches, + status_code=200) + + roster = self.mlb.get_team_coaches(133) + + # roster should not be None + self.assertIsNotNone(roster) + + # roster should be a list + self.assertIsInstance(roster, list) + + # roster should not be a empty list + self.assertNotEqual(roster, []) \ No newline at end of file diff --git a/tests/mock_tests/teams/test_team_mock.py b/tests/mock_tests/teams/test_team_mock.py new file mode 100644 index 00000000..236c141e --- /dev/null +++ b/tests/mock_tests/teams/test_team_mock.py @@ -0,0 +1,78 @@ +import unittest +import requests_mock +import json +import os + +from mlbstatsapi import Mlb +from mlbstatsapi.models.teams import Team + +# Mocked JSON directory +# TODO Find a better way to structure and handle this :) +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_teams = os.path.join(current_directory, "../mock_json/teams/team.json") +path_to_team = os.path.join(current_directory, "../mock_json/teams/teams.json") +path_to_not_found = os.path.join(current_directory, "../mock_json/response/not_found_404.json") +path_to_error = os.path.join(current_directory, "../mock_json/response/error_500.json") + +TEAMS = open(path_to_teams, "r", encoding="utf-8-sig").read() +TEAM = open(path_to_team, "r", encoding="utf-8-sig").read() +NOT_FOUND_404 = open(path_to_not_found, "r", encoding="utf-8-sig").read() +ERROR_500 = open(path_to_error, "r", encoding="utf-8-sig").read() + +@requests_mock.Mocker() +class TestScheduleMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.al_team = 133 + cls.mock_team = json.loads(TEAMS) + cls.mock_teams = json.loads(TEAMS) + + + + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_team_instance_id_instance_success(self, m): + """get_team should return a Team object""" + m.get('https://statsapi.mlb.com/api/v1/teams/133', json=self.mock_team, + status_code=200) + self.team = self.mlb.get_team(133) + + # team should not be None + self.assertIsNotNone(self.team) + + # Team object should have attrs set + self.assertEqual(self.team.id, 133) + self.assertIsInstance(self.team, Team) + self.assertEqual(self.team.name, "Oakland Athletics") + self.assertEqual(self.team.link, "/api/v1/teams/133") + + + def test_get_teams_for_sport(self, m): + """get_teams should return a list of teams""" + m.get('https://statsapi.mlb.com/api/v1/teams', json=self.mock_teams, + status_code=200) + self.teams = self.mlb.get_teams(sport_id=1) + + # teams should not be none + self.assertIsNotNone(self.teams) + + # teams should be a list + self.assertIsInstance(self.teams, list) + + # teams should not be empty list + self.assertNotEqual(self.teams, []) + + self.teams = self.mlb.get_teams(sport_id=11) + + # teams should not be none + self.assertIsNotNone(self.teams) + + # teams should be a list + self.assertIsInstance(self.teams, list) + + # teams should not be empty list + self.assertNotEqual(self.teams, []) \ No newline at end of file diff --git a/tests/mock_tests/teams/test_team_roster_mock.py b/tests/mock_tests/teams/test_team_roster_mock.py new file mode 100644 index 00000000..1f9bac1f --- /dev/null +++ b/tests/mock_tests/teams/test_team_roster_mock.py @@ -0,0 +1,63 @@ +import unittest +import requests_mock +import json +import os + +from mlbstatsapi import Mlb +from mlbstatsapi.models.teams import Team +from mlbstatsapi.models.people import Coach, Player + +# Mocked JSON directory +# TODO Find a better way to structure and handle this :) +path_to_current_file = os.path.realpath(__file__) +current_directory = os.path.dirname(path_to_current_file) +path_to_team_coaches = os.path.join(current_directory, "../mock_json/teams/team_roster_coaches.json") +path_to_team_players = os.path.join(current_directory, "../mock_json/teams/team_roster_players.json") +path_to_not_found = os.path.join(current_directory, "../mock_json/response/not_found_404.json") +path_to_error = os.path.join(current_directory, "../mock_json/response/error_500.json") + +COACHES = open(path_to_team_coaches, "r", encoding="utf-8-sig").read() +PLAYERS = open(path_to_team_players, "r", encoding="utf-8-sig").read() +NOT_FOUND_404 = open(path_to_not_found, "r", encoding="utf-8-sig").read() +ERROR_500 = open(path_to_error, "r", encoding="utf-8-sig").read() + +@requests_mock.Mocker() +class TestTeamRosterMock(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.mlb = Mlb() + cls.al_team_id = 133 + cls.mock_coaches = json.loads(COACHES) + cls.mock_players = json.loads(PLAYERS) + @classmethod + def tearDownClass(cls) -> None: + pass + + def test_team_roster_list_of_player_objects(self, m): + """Default Team Roster should return a list of players""" + m.get('https://statsapi.mlb.com/api/v1/teams/133/roster', json=self.mock_players, + status_code=200) + self.roster = self.mlb.get_team_roster(team_id=self.al_team_id) + self.assertIsInstance(self.roster, list) + # Roster should return a list + self.assertIsInstance(self.roster, list) + + # Roster should not return a empty list + self.assertNotEqual(self.roster, []) + for player in self.roster: + self.assertIsInstance(player, Player) + + def test_team_roster_list_of_coach_objects(self, m): + """Default Team Roster should return a list of coaches""" + m.get('https://statsapi.mlb.com/api/v1/teams/133/coaches', json=self.mock_coaches, + status_code=200) + self.roster = self.mlb.get_team_coaches(team_id=self.al_team_id) + # Roster should return a list + self.assertIsInstance(self.roster, list) + + # Roster should not return a empty list + self.assertNotEqual(self.roster, []) + + # Roster should return a list of Coaches + for player in self.roster: + self.assertIsInstance(player, Coach) \ No newline at end of file