diff --git a/.gitignore b/.gitignore index 3f2490d..231bedf 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,6 @@ archive.zip # vim temporary files *~ -.*.sw? \ No newline at end of file +.*.sw? + +*/.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index df1584f..900a17d 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,30 @@ # NHL-API-PY -## This is being updated with the new, also undocumented, NHL API. - -More endpoints will be flushed out and completed as they -are discovered. If you find any, please submit a PR. - - ## About NHL-api-py is a Python package that provides a simple wrapper around the NHL API, allowing you to easily access and retrieve NHL data in your Python applications. -Note: This is very early, I created this to help me with some machine learning +Note: This is ~~very early~~ maturing, I created this to help me with some machine learning projects around the NHL and the NHL data sets. Special thanks to https://github.com/erunion/sport-api-specifications/tree/master/nhl and https://gitlab.com/dword4/nhlapi/-/blob/master/stats-api.md. +### Developer Note: This is being updated with the new, also undocumented, NHL API. -## Usage +More endpoints will be flushed out and completed as they +are discovered. If you find any, open a ticket or post in the discussions tab. I would love to hear more. + + +--- +## Contact +Im available on [Bluesky](https://bsky.app/profile/coreyjs.dev) for any questions or just general chats about enhancements. + +--- + + + +# Usage ```python from nhlpy import NHLClient @@ -28,9 +35,180 @@ client = NHLClient() # OR client = NHLClient(verbose=True) # a tad more logging such as the URL being called ``` +--- +## Stats with QueryBuilder + +The skater stats endpoint can be accessed using the new query builder. It should make +creating and understanding the queries a bit easier. Filters are being added as I go, and will match up +to what the NHL API will allow. + + + +### Sorting +The sorting is a list of dictionaries similar to below. You can supply your own, otherwise it will +default to the default sort properties that the stat dashboard uses. All sorting defaults are found +in the `nhl-api-py/nhlpy/api/query/sorting/sorting_options.py` file. + +
+Default Sorting + +```python +skater_summary_default_sorting = [ + {"property": "points", "direction": "DESC"}, + {"property": "gamesPlayed", "direction": "ASC"}, + {"property": "playerId", "direction": "ASC"}, +] +``` +
+ +--- + +### Report Types +The following report types are available. These are used to build the request url. So `/summary`, `/bios`, etc. + +```bash +summary +bios +faceoffpercentages +faceoffwins +goalsForAgainst +realtime +penalties +penaltykill +penaltyShots +powerplay +puckPossessions +summaryshooting +percentages +scoringRates +scoringpergame +shootout +shottype +timeonice +``` + +### Available Filters + +```python +from nhlpy.api.query.filters.franchise import FranchiseQuery +from nhlpy.api.query.filters.shoot_catch import ShootCatchesQuery +from nhlpy.api.query.filters.draft import DraftQuery +from nhlpy.api.query.filters.season import SeasonQuery +from nhlpy.api.query.filters.game_type import GameTypeQuery +from nhlpy.api.query.filters.position import PositionQuery, PositionTypes +from nhlpy.api.query.filters.status import StatusQuery +from nhlpy.api.query.filters.opponent import OpponentQuery +from nhlpy.api.query.filters.home_road import HomeRoadQuery +from nhlpy.api.query.filters.experience import ExperienceQuery +from nhlpy.api.query.filters.decision import DecisionQuery + +filters = [ + GameTypeQuery(game_type="2"), + DraftQuery(year="2020", draft_round="2"), + SeasonQuery(season_start="20202021", season_end="20232024"), + PositionQuery(position=PositionTypes.ALL_FORWARDS), + ShootCatchesQuery(shoot_catch="L"), + HomeRoadQuery(home_road="H"), + FranchiseQuery(franchise_id="1"), + StatusQuery(is_active=True) #for active players OR for HOF players StatusQuery(is_hall_of_fame=True), + OpponentQuery(opponent_franchise_id="2"), + ExperienceQuery(is_rookie=True), # for rookies || ExperienceQuery(is_rookie=False) #for veteran + DecisionQuery(decision="W") # OR DecisionQuery(decision="L") OR DecisionQuery(decision="O") +] +``` -### Stats Endpoints (In development) +### Example +```python +from nhlpy.api.query.builder import QueryBuilder, QueryContext +from nhlpy.nhl_client import NHLClient +from nhlpy.api.query.filters.draft import DraftQuery +from nhlpy.api.query.filters.season import SeasonQuery +from nhlpy.api.query.filters.game_type import GameTypeQuery +from nhlpy.api.query.filters.position import PositionQuery, PositionTypes + +client = NHLClient(verbose=True) + +filters = [ + GameTypeQuery(game_type="2"), + DraftQuery(year="2020", draft_round="2"), + SeasonQuery(season_start="20202021", season_end="20232024"), + PositionQuery(position=PositionTypes.ALL_FORWARDS) +] + +query_builder = QueryBuilder() +query_context: QueryContext = query_builder.build(filters=filters) + +data = client.stats.skater_stats_with_query_context( + report_type='summary', + query_context=query_context, + aggregate=True +) +``` + +### Granular Filtering +Each API request uses an additional query parameter called `factCayenneExp`. This defaults to `gamesPlayed>=1` +but can be overridden by setting the `fact_query` parameter in the `QueryContextObject` object. These can +be combined together with `and` to create a more complex query. It supports `>`, `<`, `>=`, `<=`. For example: `shootingPct>=0.01 and timeOnIcePerGame>=60 and faceoffWinPct>=0.01 and shots>=1` + + +This should support the following filters: + +- `gamesPlayed` +- `points` +- `goals` +- `pointsPerGame` +- `penaltyMinutes` +- `plusMinus` +- `ppGoals` # power play goals +- `evGoals` # even strength goals +- `pointsPerGame` +- `penaltyMinutes` +- `evPoints` # even strength points +- `ppPoints` # power play points +- `gameWinningGoals` +- `otGoals` +- `shPoints` # short handed points +- `shGoals` # short handed goals +- `shootingPct` +- `timeOnIcePerGame` +- `faceoffWinPct` +- `shots` + +```python +..... +query_builder = QueryBuilder() +query_context: QueryContext = query_builder.build(filters=filters) + +query_context.fact_query = "gamesPlayed>=1 and goals>=10" # defaults to gamesPlayed>=1 + +data = client.stats.skater_stats_with_query_context( + report_type='summary', + query_context=query_context, + aggregate=True +) +``` + + +### Invalid Query / Errors + +The `QueryContext` object will hold the result of the built query with the supplied queries. +In the event of an invalid query (bad data, wrong option, etc), the `QueryContext` object will +hold all the errors that were encountered during the build process. This should help in debugging. + +You can quickly check the `QueryContext` object for errors by calling `query_context.is_valid()`. Any "invalid" filters +will be removed from the output query, but anything that is still valid will be included. + +```python +... +query_context: QueryContext = query_builder.build(filters=filters) +query_context.is_valid() # False if any of the filters fails its validation check +query_context.errors +``` + +--- + +## Additional Stats Endpoints (In development) ```python @@ -39,33 +217,21 @@ client.stats.club_stats_season(team_abbr="BUF") # kinda weird endpoint. client.stats.player_career_stats(player_id="8478402") # Team Summary Stats. -# These have lots of available parameters. You can also tap into the apache cayenne expressions to build custom -# queries, if you have that knowledge. +# These have lots of available parameters. You can also tap into the apache cayenne expressions to build custom +# queries, if you have that knowledge. client.stats.team_summary(start_season="20202021", end_season="20212022", game_type_id=2) client.stats.team_summary(start_season="20202021", end_season="20212022") -### + # Skater Summary Stats. -# Queries for skaters for year ranges, filterable down by franchise. +# Queries for skaters for year ranges, filterable down by franchise. client.stats.skater_stats_summary(start_season="20232024", end_season="20232024") client.stats.skater_stats_summary(franchise_id=10, start_season="20232024", end_season="20232024") - - # skater_stats_summary_by_expression is more advanced method. It allows for more direct manipulation of the query and - # the cayenne expression clauses. - sort_expr = [ - {"property": "points", "direction": "DESC"}, - {"property": "gamesPlayed", "direction": "ASC"}, - {"property": "playerId", "direction": "ASC"}, - ] -expr = "gameTypeId=2 and seasonId<=20232024 and seasonId>=20222023" -client.stats.skater_stats_summary_by_expression(default_cayenne_exp=expr, sort_expr=sort_expr) - -### - ``` +--- -### Schedule Endpoints +## Schedule Endpoints ```python client.schedule.get_schedule(date="2021-01-13") @@ -82,7 +248,9 @@ client.schedule.get_season_schedule(team_abbr="BUF", season="20212022") client.schedule.schedule_calendar(date="2023-11-23") ``` -### Standings Endpoints +--- + +## Standings Endpoints ```python client.standings.get_standings() @@ -94,8 +262,9 @@ client.standings.get_standings(season="202222023") # for less API calls since this only changes yearly. client.standings.season_standing_manifest() ``` +--- -### Teams Endpoints +## Teams Endpoints ```python client.teams.teams_info() # returns id + abbrevation + name of all teams @@ -103,8 +272,9 @@ client.teams.teams_info() # returns id + abbrevation + name of all teams client.teams.team_stats_summary(lang="en") # I honestly dont know. This is missing teams and has teams long abandoned. ``` +--- -### Game Center +## Game Center ```python client.game_center.boxscore(game_id="2023020280") @@ -115,8 +285,9 @@ client.game_center.landing(game_id="2023020280") client.game_center.score_now() ``` +--- -### Misc Endpoints +## Misc Endpoints ```python client.misc.glossary() @@ -130,7 +301,7 @@ client.misc.draft_year_and_rounds() ``` --- -### Insomnia Rest Client Export +## Insomnia Rest Client Export [Insomnia Rest Client](https://insomnia.rest) is a great tool for testing @@ -142,7 +313,7 @@ into the client and use it to test the endpoints. I will be updating this as I - - - -### Developers +## Developers 1) Install [Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer) @@ -157,6 +328,10 @@ or using pipx 3) `poetry shell` + +### Build Pipeline +The build pipeline will run `black`, `ruff`, and `pytest`. Please make sure these are passing before submitting a PR. + ```python $ poetry shell diff --git a/nhl_api_2_2_1.json b/nhl_api_2_2_1.json deleted file mode 100644 index 134f20a..0000000 --- a/nhl_api_2_2_1.json +++ /dev/null @@ -1,963 +0,0 @@ -{ - "_type": "export", - "__export_format": 4, - "__export_date": "2024-02-08T00:43:33.417Z", - "__export_source": "insomnia.desktop.app:v8.6.1", - "resources": [ - { - "_id": "req_0480976539ba499183de1034a85118c9", - "parentId": "fld_9cc7cf9d180e438887ceedc3387332b5", - "modified": 1707349526885, - "created": 1707349436961, - "url": "https://api.nhle.com/stats/rest/en/draft?", - "name": "season specifics", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.6.1" - } - ], - "authentication": {}, - "metaSortKey": -1707349436961, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "fld_9cc7cf9d180e438887ceedc3387332b5", - "parentId": "wrk_99b45ce56b994ef6b63cae0f80b8c840", - "modified": 1707349372856, - "created": 1707349372856, - "name": "Misc", - "description": "", - "environment": {}, - "environmentPropertyOrder": null, - "metaSortKey": -1707349372856, - "_type": "request_group" - }, - { - "_id": "wrk_99b45ce56b994ef6b63cae0f80b8c840", - "parentId": null, - "modified": 1700602776803, - "created": 1700602776803, - "name": "NHL API", - "description": "", - "scope": "collection", - "_type": "workspace" - }, - { - "_id": "req_0315e7a6fb7c4790a7381fd98c326194", - "parentId": "fld_9cc7cf9d180e438887ceedc3387332b5", - "modified": 1707349393876, - "created": 1707349379690, - "url": "https://api.nhle.com/stats/rest/en/season?", - "name": "countries", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.6.1" - } - ], - "authentication": {}, - "metaSortKey": -1707349379690, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_2b5f840c20e24d5f8efcb9d2cec02a88", - "parentId": "fld_c881ef4f75c945128f84f418bd70e375", - "modified": 1707350567064, - "created": 1707349801992, - "url": "https://api.nhle.com/stats/rest/en/skater/summary", - "name": "Skater Stats summary", - "description": "", - "method": "GET", - "body": {}, - "parameters": [ - { - "name": "isAggregate", - "value": "false", - "id": "pair_0cc2bf1a40d84481b99a038ea9adcee8" - }, - { - "name": "isGame", - "value": "false", - "id": "pair_8d9e01ee3d7c48f09bf73ff23c0afa06" - }, - { - "name": "sort", - "value": "[{\"property\":\"points\",\"direction\":\"DESC\"},{\"property\":\"gamesPlayed\",\"direction\":\"ASC\"},{\"property\":\"playerId\",\"direction\":\"ASC\"}]", - "id": "pair_24ce5eed3b8746ffbcd669aebf41061f" - }, - { - "name": "start", - "value": "0", - "id": "pair_c301087c1eae4e47b2a79a1ac89efb5a" - }, - { - "name": "limit", - "value": "10", - "id": "pair_932bc26db9d44396a1094bf3f8e7c559" - }, - { - "name": "factCayenneExp", - "value": "gamesPlayed>=1", - "id": "pair_bf115daac2f54baab4742306537f208f" - }, - { - "name": "cayenneExp", - "value": "gameTypeId=2 and seasonId<=20232024 and seasonId>=20232024", - "id": "pair_bc3c7fed6ba64e78ab5e0508783ca774" - } - ], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.6.1" - } - ], - "authentication": {}, - "metaSortKey": -1707349801992, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "fld_c881ef4f75c945128f84f418bd70e375", - "parentId": "wrk_99b45ce56b994ef6b63cae0f80b8c840", - "modified": 1707182204260, - "created": 1707182204260, - "name": "Stats", - "description": "", - "environment": {}, - "environmentPropertyOrder": null, - "metaSortKey": -1707182204260, - "_type": "request_group" - }, - { - "_id": "req_0f6b8b2ed72b4992a0d56716dca607e1", - "parentId": "fld_c881ef4f75c945128f84f418bd70e375", - "modified": 1707317239017, - "created": 1707314281816, - "url": "https://api.nhle.com/stats/rest/en/team/summary", - "name": "Team Stats Summary", - "description": "", - "method": "GET", - "body": {}, - "parameters": [ - { - "name": "isAggregate", - "value": "false" - }, - { - "name": "isGame", - "value": "false" - }, - { - "name": "sort", - "value": "[{\"property\":\"points\",\"direction\":\"DESC\"},{\"property\":\"wins\",\"direction\":\"DESC\"},{\"property\":\"teamId\",\"direction\":\"ASC\"}]" - }, - { - "name": "start", - "value": "0" - }, - { - "name": "limit", - "value": "50" - }, - { - "name": "factCayenneExp", - "value": "gamesPlayed>=1" - }, - { - "name": "cayenneExp", - "value": "gameTypeId=2 and seasonId<=20232024 and seasonId>=20232024" - } - ], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.6.1" - } - ], - "authentication": {}, - "metaSortKey": -1707314281816, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_02fd6a9157fd4a1a897bd59dbb1b375d", - "parentId": "fld_c881ef4f75c945128f84f418bd70e375", - "modified": 1707182285031, - "created": 1707182208364, - "url": "https://api-web.nhle.com/v1/club-stats-season/BUF", - "name": "Club Stats Season", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.4.5" - } - ], - "authentication": {}, - "metaSortKey": -1707182208364, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_c815e74a3aa140c7bae0427374b8af2a", - "parentId": "fld_c881ef4f75c945128f84f418bd70e375", - "modified": 1707324381413, - "created": 1707182296597, - "url": "https://api-web.nhle.com/v1/player/8480045/landing", - "name": "Player Stats", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.4.5" - } - ], - "authentication": {}, - "metaSortKey": -1704097754543.5, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_119c61ff426849ad94ef4592599de614", - "parentId": "fld_0a164c32cf894dda9e4e01342d9d6096", - "modified": 1707350408275, - "created": 1707350357013, - "url": "https://api.nhle.com/stats/rest/en/franchise", - "name": "franchise", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.6.1" - } - ], - "authentication": {}, - "metaSortKey": -1707350357013, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "fld_0a164c32cf894dda9e4e01342d9d6096", - "parentId": "wrk_99b45ce56b994ef6b63cae0f80b8c840", - "modified": 1700677055390, - "created": 1700677055390, - "name": "Teams", - "description": "", - "environment": {}, - "environmentPropertyOrder": null, - "metaSortKey": -1700677055390, - "_type": "request_group" - }, - { - "_id": "req_306d3c7b26aa4f2f8b3c355cfece25a0", - "parentId": "fld_0a164c32cf894dda9e4e01342d9d6096", - "modified": 1701016559924, - "created": 1701011877087, - "url": "https://api-web.nhle.com/v1/roster/BUF/20232024", - "name": "roster", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.4.4" - } - ], - "authentication": {}, - "metaSortKey": -1701011877087, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_6455801ab3ba4148b3d6dc570b179a30", - "parentId": "fld_0a164c32cf894dda9e4e01342d9d6096", - "modified": 1700677061232, - "created": 1699714308830, - "url": "https://api.nhle.com/stats/rest/en/team/summary?", - "name": "Team Stats Summary", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.4.0" - } - ], - "authentication": {}, - "metaSortKey": -1700677061196, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_8704273712d04ba8bd0497391c2dbecc", - "parentId": "fld_0a164c32cf894dda9e4e01342d9d6096", - "modified": 1700677118186, - "created": 1700677071513, - "url": "https://api-web.nhle.com/v1/roster-season/BUF", - "name": "Team Roster", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.4.0" - } - ], - "authentication": {}, - "metaSortKey": -1700646562645, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_44977479c368439db2c6331a329a882e", - "parentId": "fld_7154c8f2df85461fa685bde21011fb2a", - "modified": 1700961984501, - "created": 1700615630845, - "url": "https://api-web.nhle.com/v1/gamecenter/2023020310/boxscore", - "name": "GameCenter - BoxScore", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.4.2" - } - ], - "authentication": {}, - "metaSortKey": -1700616064094, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "fld_7154c8f2df85461fa685bde21011fb2a", - "parentId": "wrk_99b45ce56b994ef6b63cae0f80b8c840", - "modified": 1700616051787, - "created": 1700616051787, - "name": "Game Center", - "description": "", - "environment": {}, - "environmentPropertyOrder": null, - "metaSortKey": -1700616051787, - "_type": "request_group" - }, - { - "_id": "req_a207e29ea51847cb853f593fbd178d2b", - "parentId": "fld_7154c8f2df85461fa685bde21011fb2a", - "modified": 1700616823135, - "created": 1700616814036, - "url": "https://api-web.nhle.com/v1/score/now", - "name": "GameCenter - Score Now", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.4.2" - } - ], - "authentication": {}, - "metaSortKey": -1700616052195, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_7a8da7415a8147549b76d3d326425f42", - "parentId": "fld_7154c8f2df85461fa685bde21011fb2a", - "modified": 1700962024971, - "created": 1700616095982, - "url": "https://api-web.nhle.com/v1/gamecenter/2023020310/play-by-play", - "name": "GameCenter - PlayByPlay", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.4.2" - } - ], - "authentication": {}, - "metaSortKey": -1700616040296, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_5c37e19afd9446c29b501d10a2221dc4", - "parentId": "fld_7154c8f2df85461fa685bde21011fb2a", - "modified": 1700616512883, - "created": 1700616507407, - "url": "https://api-web.nhle.com/v1/gamecenter/2023020285/landing", - "name": "GameCenter - Landing", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.4.2" - } - ], - "authentication": {}, - "metaSortKey": -1700616028397, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_ea0ea40c376a4d74b4496a301713df29", - "parentId": "fld_a1b982f4f15f47f5a8e81ddcfbbbb288", - "modified": 1700677761891, - "created": 1700677744770, - "url": "https://api-web.nhle.com/v1/schedule-calendar/2023-11-22", - "name": "Schedule Calendar", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.4.2" - } - ], - "authentication": {}, - "metaSortKey": -1700677744770, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "fld_a1b982f4f15f47f5a8e81ddcfbbbb288", - "parentId": "wrk_99b45ce56b994ef6b63cae0f80b8c840", - "modified": 1700616000421, - "created": 1700616000421, - "name": "Schedule", - "description": "", - "environment": {}, - "environmentPropertyOrder": null, - "metaSortKey": -1700616000421, - "_type": "request_group" - }, - { - "_id": "req_1c805c50423d4853a4d7f8586e6f26ae", - "parentId": "fld_a1b982f4f15f47f5a8e81ddcfbbbb288", - "modified": 1700961969246, - "created": 1699629547871, - "url": "https://api-web.nhle.com/v1/schedule/2023-11-25", - "name": "Schedule By Date", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.3.0" - } - ], - "authentication": {}, - "metaSortKey": -1700616016498, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_f4b06b02014643688ce2adba6aa78da8", - "parentId": "fld_a1b982f4f15f47f5a8e81ddcfbbbb288", - "modified": 1700616031808, - "created": 1698326129771, - "url": "https://api-web.nhle.com/v1/schedule/now", - "name": "Schedule Now", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.3.0" - } - ], - "authentication": {}, - "metaSortKey": -1700616016398, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_299f4997b0384de8aa2785bbb3fb06e3", - "parentId": "fld_a1b982f4f15f47f5a8e81ddcfbbbb288", - "modified": 1700616026777, - "created": 1699634789997, - "url": "https://api-web.nhle.com/v1/club-schedule/BUF/month/now", - "name": "Club Schedule by Month Now", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.3.0" - } - ], - "authentication": {}, - "metaSortKey": -1700616016298, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_0c03a29b2c924a769b8561317f9b644d", - "parentId": "fld_a1b982f4f15f47f5a8e81ddcfbbbb288", - "modified": 1700616022893, - "created": 1699631380607, - "url": "https://api-web.nhle.com/v1/club-schedule/BUF/month/2023-11", - "name": "Club Schedule by Month", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.3.0" - } - ], - "authentication": {}, - "metaSortKey": -1700616016198, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_67fddd56d13a4cc29122f95abe65037f", - "parentId": "fld_a1b982f4f15f47f5a8e81ddcfbbbb288", - "modified": 1700616019954, - "created": 1699632450492, - "url": "https://api-web.nhle.com/v1/club-schedule/BUF/week/now", - "name": "Club Schedule by week", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.3.0" - } - ], - "authentication": {}, - "metaSortKey": -1700616016098, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_6dd56acc7608427d8643fe3eca82ff08", - "parentId": "fld_a1b982f4f15f47f5a8e81ddcfbbbb288", - "modified": 1700616016035, - "created": 1699631552023, - "url": "https://api-web.nhle.com/v1/club-schedule-season/BUF/20232024", - "name": "Club Schedule by Year", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.3.0" - } - ], - "authentication": {}, - "metaSortKey": -1700616015998, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_c6f2a92e978040748964dba407944c13", - "parentId": "fld_9185266547564925aed97555b4070a23", - "modified": 1700615988944, - "created": 1699632326802, - "url": "https://api-web.nhle.com/v1/standings/now", - "name": "Get Standings Now", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.3.0" - } - ], - "authentication": {}, - "metaSortKey": -1700615984150, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "fld_9185266547564925aed97555b4070a23", - "parentId": "wrk_99b45ce56b994ef6b63cae0f80b8c840", - "modified": 1700615973724, - "created": 1700615973724, - "name": "Standings", - "description": "", - "environment": {}, - "environmentPropertyOrder": null, - "metaSortKey": -1700615973724, - "_type": "request_group" - }, - { - "_id": "req_77f077f01ca046b886f4cfb827f13195", - "parentId": "fld_9185266547564925aed97555b4070a23", - "modified": 1700615984089, - "created": 1699644667168, - "url": "https://api-web.nhle.com/v1/standings-season/", - "name": "Get Standings Season", - "description": "", - "method": "GET", - "body": {}, - "parameters": [], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.3.0" - } - ], - "authentication": {}, - "metaSortKey": -1700615984050, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_32a714f5c8194cefbfde6daed74d3dca", - "parentId": "wrk_99b45ce56b994ef6b63cae0f80b8c840", - "modified": 1707351662049, - "created": 1699645690565, - "url": "https://api.nhle.com/stats/rest/en/skater/summary", - "name": "test", - "description": "", - "method": "GET", - "body": {}, - "parameters": [ - { - "name": "isAggregate", - "value": "true", - "id": "pair_2599316deb3b439496549cd36eeb12d6" - }, - { - "name": "isGame", - "value": "false", - "id": "pair_55b04aa132bc4c3e916877b1bee2e89d" - }, - { - "name": "sort", - "value": "[{\"property\":\"points\",\"direction\":\"DESC\"},{\"property\":\"gamesPlayed\",\"direction\":\"ASC\"},{\"property\":\"playerId\",\"direction\":\"ASC\"}]", - "id": "pair_e2ba7d7677c64ba2a5e16754b6b7daaa" - }, - { - "name": "start", - "value": "0", - "id": "pair_1446d0aa039842d3bb9666fe5d7f9642" - }, - { - "name": "limit", - "value": "100", - "id": "pair_a49d43a1d009480b8af6b434558cbe08" - }, - { - "name": "factCayenneExp", - "value": "gamesPlayed>=1", - "id": "pair_e62c86e403d84910974ae4e163ed45e3" - }, - { - "name": "cayenneExp", - "value": "gameTypeId=2 and seasonId<=20232024 and seasonId>=20222023", - "id": "pair_4c990ef1c4604327baf1f4eed039a953" - } - ], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.4.0" - } - ], - "authentication": {}, - "metaSortKey": -1699645690565, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "req_d62d9fb99dd84bbaa0254e500da82bee", - "parentId": "wrk_99b45ce56b994ef6b63cae0f80b8c840", - "modified": 1707352816400, - "created": 1707350296487, - "url": "https://api.nhle.com/stats/rest/en/skater/summary", - "name": "test2", - "description": "", - "method": "GET", - "body": {}, - "parameters": [ - { - "name": "isAggregate", - "value": "false" - }, - { - "name": "isGame", - "value": "false" - }, - { - "name": "start", - "value": "0" - }, - { - "name": "limit", - "value": "70" - }, - { - "name": "factCayenneExp", - "value": "gamesPlayed>=1" - }, - { - "name": "sort", - "value": "[{\"property\": \"points\", \"direction\": \"DESC\"}, {\"property\": \"gamesPlayed\", \"direction\": \"ASC\"}, {\"property\": \"playerId\", \"direction\": \"ASC\"}]" - } - ], - "headers": [ - { - "name": "User-Agent", - "value": "insomnia/8.6.1" - } - ], - "authentication": {}, - "metaSortKey": -1699645690465, - "isPrivate": false, - "pathParameters": [], - "settingStoreCookies": true, - "settingSendCookies": true, - "settingDisableRenderRequestBody": false, - "settingEncodeUrl": true, - "settingRebuildPath": true, - "settingFollowRedirects": "global", - "_type": "request" - }, - { - "_id": "env_03a7e3eb088245d7af86c18a5d983a2d", - "parentId": "wrk_99b45ce56b994ef6b63cae0f80b8c840", - "modified": 1698242404315, - "created": 1698242404315, - "name": "Base Environment", - "data": {}, - "dataPropertyOrder": null, - "color": null, - "isPrivate": false, - "metaSortKey": 1698242404315, - "_type": "environment" - }, - { - "_id": "jar_cd68277a39254c7eb5209b24f8beef3f", - "parentId": "wrk_99b45ce56b994ef6b63cae0f80b8c840", - "modified": 1698242404316, - "created": 1698242404316, - "name": "Default Jar", - "cookies": [], - "_type": "cookie_jar" - } - ] -} \ No newline at end of file diff --git a/nhl_api_2_3_0.json b/nhl_api_2_3_0.json new file mode 100644 index 0000000..6b58067 --- /dev/null +++ b/nhl_api_2_3_0.json @@ -0,0 +1 @@ +{"_type":"export","__export_format":4,"__export_date":"2024-02-15T20:46:14.608Z","__export_source":"insomnia.desktop.app:v8.6.1","resources":[{"_id":"req_0480976539ba499183de1034a85118c9","parentId":"fld_9cc7cf9d180e438887ceedc3387332b5","modified":1707349526885,"created":1707349436961,"url":"https://api.nhle.com/stats/rest/en/draft?","name":"season specifics","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.6.1"}],"authentication":{},"metaSortKey":-1707349436961,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_9cc7cf9d180e438887ceedc3387332b5","parentId":"wrk_99b45ce56b994ef6b63cae0f80b8c840","modified":1707349372856,"created":1707349372856,"name":"Misc","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1707349372856,"_type":"request_group"},{"_id":"wrk_99b45ce56b994ef6b63cae0f80b8c840","parentId":null,"modified":1700602776803,"created":1700602776803,"name":"NHL API","description":"","scope":"collection","_type":"workspace"},{"_id":"req_0315e7a6fb7c4790a7381fd98c326194","parentId":"fld_9cc7cf9d180e438887ceedc3387332b5","modified":1708027314441,"created":1707349379690,"url":"https://api.nhle.com/stats/rest/en/country?","name":"countries","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.6.1"}],"authentication":{},"metaSortKey":-1707349379690,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_ec605f61d7f14f69891d7c0bd4513381","parentId":"fld_c881ef4f75c945128f84f418bd70e375","modified":1707862367424,"created":1707861147763,"url":"https://api-web.nhle.com/v1/player/8476453/game-log/20222023/2","name":"player gamelog","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.6.1"}],"authentication":{},"metaSortKey":-1707861147763,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_c881ef4f75c945128f84f418bd70e375","parentId":"wrk_99b45ce56b994ef6b63cae0f80b8c840","modified":1707182204260,"created":1707182204260,"name":"Stats","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1707182204260,"_type":"request_group"},{"_id":"req_2b5f840c20e24d5f8efcb9d2cec02a88","parentId":"fld_c881ef4f75c945128f84f418bd70e375","modified":1707350567064,"created":1707349801992,"url":"https://api.nhle.com/stats/rest/en/skater/summary","name":"Skater Stats summary","description":"","method":"GET","body":{},"parameters":[{"name":"isAggregate","value":"false","id":"pair_0cc2bf1a40d84481b99a038ea9adcee8"},{"name":"isGame","value":"false","id":"pair_8d9e01ee3d7c48f09bf73ff23c0afa06"},{"name":"sort","value":"[{\"property\":\"points\",\"direction\":\"DESC\"},{\"property\":\"gamesPlayed\",\"direction\":\"ASC\"},{\"property\":\"playerId\",\"direction\":\"ASC\"}]","id":"pair_24ce5eed3b8746ffbcd669aebf41061f"},{"name":"start","value":"0","id":"pair_c301087c1eae4e47b2a79a1ac89efb5a"},{"name":"limit","value":"10","id":"pair_932bc26db9d44396a1094bf3f8e7c559"},{"name":"factCayenneExp","value":"gamesPlayed>=1","id":"pair_bf115daac2f54baab4742306537f208f"},{"name":"cayenneExp","value":"gameTypeId=2 and seasonId<=20232024 and seasonId>=20232024","id":"pair_bc3c7fed6ba64e78ab5e0508783ca774"}],"headers":[{"name":"User-Agent","value":"insomnia/8.6.1"}],"authentication":{},"metaSortKey":-1707349801992,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_0f6b8b2ed72b4992a0d56716dca607e1","parentId":"fld_c881ef4f75c945128f84f418bd70e375","modified":1707317239017,"created":1707314281816,"url":"https://api.nhle.com/stats/rest/en/team/summary","name":"Team Stats Summary","description":"","method":"GET","body":{},"parameters":[{"name":"isAggregate","value":"false"},{"name":"isGame","value":"false"},{"name":"sort","value":"[{\"property\":\"points\",\"direction\":\"DESC\"},{\"property\":\"wins\",\"direction\":\"DESC\"},{\"property\":\"teamId\",\"direction\":\"ASC\"}]"},{"name":"start","value":"0"},{"name":"limit","value":"50"},{"name":"factCayenneExp","value":"gamesPlayed>=1"},{"name":"cayenneExp","value":"gameTypeId=2 and seasonId<=20232024 and seasonId>=20232024"}],"headers":[{"name":"User-Agent","value":"insomnia/8.6.1"}],"authentication":{},"metaSortKey":-1707314281816,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_02fd6a9157fd4a1a897bd59dbb1b375d","parentId":"fld_c881ef4f75c945128f84f418bd70e375","modified":1707182285031,"created":1707182208364,"url":"https://api-web.nhle.com/v1/club-stats-season/BUF","name":"Club Stats Season","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.5"}],"authentication":{},"metaSortKey":-1707182208364,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_c815e74a3aa140c7bae0427374b8af2a","parentId":"fld_c881ef4f75c945128f84f418bd70e375","modified":1707324381413,"created":1707182296597,"url":"https://api-web.nhle.com/v1/player/8480045/landing","name":"Player Stats","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.5"}],"authentication":{},"metaSortKey":-1704097754543.5,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_119c61ff426849ad94ef4592599de614","parentId":"fld_0a164c32cf894dda9e4e01342d9d6096","modified":1707350408275,"created":1707350357013,"url":"https://api.nhle.com/stats/rest/en/franchise","name":"franchise","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.6.1"}],"authentication":{},"metaSortKey":-1707350357013,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_0a164c32cf894dda9e4e01342d9d6096","parentId":"wrk_99b45ce56b994ef6b63cae0f80b8c840","modified":1700677055390,"created":1700677055390,"name":"Teams","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1700677055390,"_type":"request_group"},{"_id":"req_306d3c7b26aa4f2f8b3c355cfece25a0","parentId":"fld_0a164c32cf894dda9e4e01342d9d6096","modified":1701016559924,"created":1701011877087,"url":"https://api-web.nhle.com/v1/roster/BUF/20232024","name":"roster","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.4"}],"authentication":{},"metaSortKey":-1701011877087,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_6455801ab3ba4148b3d6dc570b179a30","parentId":"fld_0a164c32cf894dda9e4e01342d9d6096","modified":1700677061232,"created":1699714308830,"url":"https://api.nhle.com/stats/rest/en/team/summary?","name":"Team Stats Summary","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.0"}],"authentication":{},"metaSortKey":-1700677061196,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_8704273712d04ba8bd0497391c2dbecc","parentId":"fld_0a164c32cf894dda9e4e01342d9d6096","modified":1700677118186,"created":1700677071513,"url":"https://api-web.nhle.com/v1/roster-season/BUF","name":"Team Roster","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.0"}],"authentication":{},"metaSortKey":-1700646562645,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_44977479c368439db2c6331a329a882e","parentId":"fld_7154c8f2df85461fa685bde21011fb2a","modified":1700961984501,"created":1700615630845,"url":"https://api-web.nhle.com/v1/gamecenter/2023020310/boxscore","name":"GameCenter - BoxScore","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{},"metaSortKey":-1700616064094,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_7154c8f2df85461fa685bde21011fb2a","parentId":"wrk_99b45ce56b994ef6b63cae0f80b8c840","modified":1700616051787,"created":1700616051787,"name":"Game Center","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1700616051787,"_type":"request_group"},{"_id":"req_a207e29ea51847cb853f593fbd178d2b","parentId":"fld_7154c8f2df85461fa685bde21011fb2a","modified":1700616823135,"created":1700616814036,"url":"https://api-web.nhle.com/v1/score/now","name":"GameCenter - Score Now","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{},"metaSortKey":-1700616052195,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_7a8da7415a8147549b76d3d326425f42","parentId":"fld_7154c8f2df85461fa685bde21011fb2a","modified":1700962024971,"created":1700616095982,"url":"https://api-web.nhle.com/v1/gamecenter/2023020310/play-by-play","name":"GameCenter - PlayByPlay","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{},"metaSortKey":-1700616040296,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_5c37e19afd9446c29b501d10a2221dc4","parentId":"fld_7154c8f2df85461fa685bde21011fb2a","modified":1700616512883,"created":1700616507407,"url":"https://api-web.nhle.com/v1/gamecenter/2023020285/landing","name":"GameCenter - Landing","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{},"metaSortKey":-1700616028397,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_ea0ea40c376a4d74b4496a301713df29","parentId":"fld_a1b982f4f15f47f5a8e81ddcfbbbb288","modified":1700677761891,"created":1700677744770,"url":"https://api-web.nhle.com/v1/schedule-calendar/2023-11-22","name":"Schedule Calendar","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.4.2"}],"authentication":{},"metaSortKey":-1700677744770,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_a1b982f4f15f47f5a8e81ddcfbbbb288","parentId":"wrk_99b45ce56b994ef6b63cae0f80b8c840","modified":1700616000421,"created":1700616000421,"name":"Schedule","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1700616000421,"_type":"request_group"},{"_id":"req_1c805c50423d4853a4d7f8586e6f26ae","parentId":"fld_a1b982f4f15f47f5a8e81ddcfbbbb288","modified":1700961969246,"created":1699629547871,"url":"https://api-web.nhle.com/v1/schedule/2023-11-25","name":"Schedule By Date","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.3.0"}],"authentication":{},"metaSortKey":-1700616016498,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_f4b06b02014643688ce2adba6aa78da8","parentId":"fld_a1b982f4f15f47f5a8e81ddcfbbbb288","modified":1700616031808,"created":1698326129771,"url":"https://api-web.nhle.com/v1/schedule/now","name":"Schedule Now","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.3.0"}],"authentication":{},"metaSortKey":-1700616016398,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_299f4997b0384de8aa2785bbb3fb06e3","parentId":"fld_a1b982f4f15f47f5a8e81ddcfbbbb288","modified":1700616026777,"created":1699634789997,"url":"https://api-web.nhle.com/v1/club-schedule/BUF/month/now","name":"Club Schedule by Month Now","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.3.0"}],"authentication":{},"metaSortKey":-1700616016298,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_0c03a29b2c924a769b8561317f9b644d","parentId":"fld_a1b982f4f15f47f5a8e81ddcfbbbb288","modified":1700616022893,"created":1699631380607,"url":"https://api-web.nhle.com/v1/club-schedule/BUF/month/2023-11","name":"Club Schedule by Month","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.3.0"}],"authentication":{},"metaSortKey":-1700616016198,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_67fddd56d13a4cc29122f95abe65037f","parentId":"fld_a1b982f4f15f47f5a8e81ddcfbbbb288","modified":1700616019954,"created":1699632450492,"url":"https://api-web.nhle.com/v1/club-schedule/BUF/week/now","name":"Club Schedule by week","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.3.0"}],"authentication":{},"metaSortKey":-1700616016098,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_6dd56acc7608427d8643fe3eca82ff08","parentId":"fld_a1b982f4f15f47f5a8e81ddcfbbbb288","modified":1700616016035,"created":1699631552023,"url":"https://api-web.nhle.com/v1/club-schedule-season/BUF/20232024","name":"Club Schedule by Year","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.3.0"}],"authentication":{},"metaSortKey":-1700616015998,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_c6f2a92e978040748964dba407944c13","parentId":"fld_9185266547564925aed97555b4070a23","modified":1700615988944,"created":1699632326802,"url":"https://api-web.nhle.com/v1/standings/now","name":"Get Standings Now","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.3.0"}],"authentication":{},"metaSortKey":-1700615984150,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_9185266547564925aed97555b4070a23","parentId":"wrk_99b45ce56b994ef6b63cae0f80b8c840","modified":1700615973724,"created":1700615973724,"name":"Standings","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1700615973724,"_type":"request_group"},{"_id":"req_77f077f01ca046b886f4cfb827f13195","parentId":"fld_9185266547564925aed97555b4070a23","modified":1700615984089,"created":1699644667168,"url":"https://api-web.nhle.com/v1/standings-season/","name":"Get Standings Season","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnia/8.3.0"}],"authentication":{},"metaSortKey":-1700615984050,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_32a714f5c8194cefbfde6daed74d3dca","parentId":"wrk_99b45ce56b994ef6b63cae0f80b8c840","modified":1707351662049,"created":1699645690565,"url":"https://api.nhle.com/stats/rest/en/skater/summary","name":"test","description":"","method":"GET","body":{},"parameters":[{"name":"isAggregate","value":"true","id":"pair_2599316deb3b439496549cd36eeb12d6"},{"name":"isGame","value":"false","id":"pair_55b04aa132bc4c3e916877b1bee2e89d"},{"name":"sort","value":"[{\"property\":\"points\",\"direction\":\"DESC\"},{\"property\":\"gamesPlayed\",\"direction\":\"ASC\"},{\"property\":\"playerId\",\"direction\":\"ASC\"}]","id":"pair_e2ba7d7677c64ba2a5e16754b6b7daaa"},{"name":"start","value":"0","id":"pair_1446d0aa039842d3bb9666fe5d7f9642"},{"name":"limit","value":"100","id":"pair_a49d43a1d009480b8af6b434558cbe08"},{"name":"factCayenneExp","value":"gamesPlayed>=1","id":"pair_e62c86e403d84910974ae4e163ed45e3"},{"name":"cayenneExp","value":"gameTypeId=2 and seasonId<=20232024 and seasonId>=20222023","id":"pair_4c990ef1c4604327baf1f4eed039a953"}],"headers":[{"name":"User-Agent","value":"insomnia/8.4.0"}],"authentication":{},"metaSortKey":-1699645690565,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_d62d9fb99dd84bbaa0254e500da82bee","parentId":"wrk_99b45ce56b994ef6b63cae0f80b8c840","modified":1708028653267,"created":1707350296487,"url":"https://api.nhle.com/stats/rest/en/skater/summary","name":"test2","description":"","method":"GET","body":{},"parameters":[{"name":"isAggregate","value":"true"},{"name":"isGame","value":"false"},{"name":"start","value":"0"},{"name":"limit","value":"70"},{"name":"factCayenneExp","value":"goals>=10"},{"name":"sort","value":"[{\"property\": \"points\", \"direction\": \"DESC\"}, {\"property\": \"gamesPlayed\", \"direction\": \"ASC\"}, {\"property\": \"playerId\", \"direction\": \"ASC\"}]"},{"name":"cayenneExp","value":"gameTypeId=2 and isRookie='0' and seasonId >= 20232024 and seasonId <= 20232024 and nationalityCode='USA'"}],"headers":[{"name":"User-Agent","value":"insomnia/8.6.1"}],"authentication":{},"metaSortKey":-1699645690465,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_03a7e3eb088245d7af86c18a5d983a2d","parentId":"wrk_99b45ce56b994ef6b63cae0f80b8c840","modified":1698242404315,"created":1698242404315,"name":"Base Environment","data":{},"dataPropertyOrder":null,"color":null,"isPrivate":false,"metaSortKey":1698242404315,"_type":"environment"},{"_id":"jar_cd68277a39254c7eb5209b24f8beef3f","parentId":"wrk_99b45ce56b994ef6b63cae0f80b8c840","modified":1698242404316,"created":1698242404316,"name":"Default Jar","cookies":[],"_type":"cookie_jar"}]} \ No newline at end of file diff --git a/nhlpy/_version.py b/nhlpy/_version.py index 1f74700..d1b6f45 100644 --- a/nhlpy/_version.py +++ b/nhlpy/_version.py @@ -1,3 +1,3 @@ # Should this be driven by the main pyproject.toml file? yes, is it super convoluted? yes, can it wait? sure -__version__ = "2.2.7" +__version__ = "2.3.1" diff --git a/nhlpy/api/__init__.py b/nhlpy/api/__init__.py index bfc6124..e69de29 100644 --- a/nhlpy/api/__init__.py +++ b/nhlpy/api/__init__.py @@ -1,30 +0,0 @@ -# import httpx -# -# -# class BaseHttpClient: -# def __init__(self, config) -> None: -# self.config = config -# -# def _get(self, resource: str) -> httpx.request: -# """ -# Private method to make a get request to the NHL API. This wraps the lib httpx functionality. -# :param resource: -# :return: -# """ -# if self.config.verbose: -# print(f"API URL: {self.config.api_web_api_ver}{self.config.api_web_api_ver}{resource}") -# r: httpx.request = httpx.get( -# url=f"{self.config.api_web_base_url}{self.config.api_web_api_ver}{resource}", follow_redirects=True -# ) -# return r -# -# def _get_plain(self, full_resource: str) -> httpx.request: -# """ -# Private method to make a get request to any HTTP resource. This wraps the lib httpx functionality. -# :param full_resource: The full resource to get. -# :return: -# """ -# if self.config.verbose: -# print(f"API URL: {full_resource}") -# r: httpx.request = httpx.get(url=full_resource, follow_redirects=True) -# return r.text diff --git a/nhlpy/api/query/__init__.py b/nhlpy/api/query/__init__.py new file mode 100644 index 0000000..6ffd557 --- /dev/null +++ b/nhlpy/api/query/__init__.py @@ -0,0 +1,2 @@ +class InvalidQueryValueException(Exception): + pass diff --git a/nhlpy/api/query/builder.py b/nhlpy/api/query/builder.py new file mode 100644 index 0000000..057f064 --- /dev/null +++ b/nhlpy/api/query/builder.py @@ -0,0 +1,50 @@ +from typing import List +import logging + +from nhlpy.api.query import InvalidQueryValueException +from nhlpy.api.query.filters import QueryBase + + +class QueryContext: + def __init__(self, query: str, filters: List[QueryBase], fact_query: str = None, errors: List[str] = None): + self.query_str = query + self.filters = filters + self.errors = errors + self.fact_query = fact_query if fact_query else "gamesPlayed>=1" + + def is_valid(self) -> bool: + return len(self.errors) == 0 + + +class QueryBuilder: + def __init__(self, verbose: bool = False): + self._verbose = verbose + if self._verbose: + logging.basicConfig(level=logging.INFO) + + def build(self, filters: List[QueryBase]) -> QueryContext: + result_query: str = "" + output_filters: List[str] = [] + errors: List[str] = [] + for f in filters: + if not isinstance(f, QueryBase): + if self._verbose: + logging.info(f"Input filter is not of type QueryBase: {f.__name__}") + continue + + # Validate the filter + try: + if not f.validate(): + raise InvalidQueryValueException(f"Filter failed validation: {str(f)}") + except InvalidQueryValueException as e: + if self._verbose: + logging.error(e) + errors.append(str(e)) + continue + + output_filters.append(f.to_query()) + else: + if len(output_filters) > 0: + result_query = " and ".join(output_filters) + + return QueryContext(query=result_query, filters=filters, errors=errors) diff --git a/nhlpy/api/query/filters/__init__.py b/nhlpy/api/query/filters/__init__.py new file mode 100644 index 0000000..43cd8a6 --- /dev/null +++ b/nhlpy/api/query/filters/__init__.py @@ -0,0 +1,12 @@ +from abc import ABC, abstractmethod +from typing import Union + + +class QueryBase(ABC): + @abstractmethod + def to_query(self) -> str: + pass + + @abstractmethod + def validate(self) -> Union[bool, None]: + return True diff --git a/nhlpy/api/query/filters/decision.py b/nhlpy/api/query/filters/decision.py new file mode 100644 index 0000000..3b10304 --- /dev/null +++ b/nhlpy/api/query/filters/decision.py @@ -0,0 +1,28 @@ +import logging +from typing import Union + +from nhlpy.api.query import InvalidQueryValueException +from nhlpy.api.query.filters import QueryBase + + +logger = logging.getLogger(__name__) + + +class DecisionQuery(QueryBase): + def __init__(self, decision: str): + """ + Decision filter. W=win, L=loss, O=overtime loss, + :param decision: W, L, O + """ + self.decision = decision + self._decision_q = "decision" + + def __str__(self): + return f"DecisionQuery: Value={self.decision}" + + def to_query(self) -> str: + return f"{self._decision_q}='{self.decision}'" + + def validate(self) -> Union[bool, None]: + if self.decision not in ["W", "L", "O"]: + raise InvalidQueryValueException("Decision value must be one of [W, L, O]") diff --git a/nhlpy/api/query/filters/draft.py b/nhlpy/api/query/filters/draft.py new file mode 100644 index 0000000..69743bc --- /dev/null +++ b/nhlpy/api/query/filters/draft.py @@ -0,0 +1,27 @@ +from typing import Optional, Union + +from nhlpy.api.query.filters import QueryBase + + +class DraftQuery(QueryBase): + def __init__(self, year: str, draft_round: Optional[str] = None): + """ + + :param year: + :param draft_round: This seems to default to "1" on the API. Should + check not supplying it. + """ + self.year = year + self.round = draft_round + self._year_q = "draftYear" + self._round_q = "draftRound" + + def to_query(self) -> str: + query = f"{self._year_q}={self.year}" + if self.round: + query += " and " + query += f"{self._round_q}={self.round}" + return query + + def validate(self) -> Union[bool, None]: + return True diff --git a/nhlpy/api/query/filters/experience.py b/nhlpy/api/query/filters/experience.py new file mode 100644 index 0000000..c07d3f1 --- /dev/null +++ b/nhlpy/api/query/filters/experience.py @@ -0,0 +1,20 @@ +from typing import Union + +from nhlpy.api.query.filters import QueryBase + + +class ExperienceQuery(QueryBase): + def __init__(self, is_rookie: bool): + """ + Experience filter. R=rookie, S=sophomore, V=veteran + :param experience: R, S, V + """ + self.is_rookie: bool = is_rookie + self._experience_q = "isRookie" + + def to_query(self) -> str: + val = "1" if self.is_rookie else "0" + return f"{self._experience_q}='{val}'" + + def validate(self) -> Union[bool, None]: + return True diff --git a/nhlpy/api/query/filters/franchise.py b/nhlpy/api/query/filters/franchise.py new file mode 100644 index 0000000..d364464 --- /dev/null +++ b/nhlpy/api/query/filters/franchise.py @@ -0,0 +1,15 @@ +from typing import Union + +from nhlpy.api.query.builder import QueryBase + + +class FranchiseQuery(QueryBase): + def __init__(self, franchise_id: str): + self.franchise_id = franchise_id + self._franchise_q = "franchiseId" + + def to_query(self) -> str: + return f"{self._franchise_q}={self.franchise_id}" + + def validate(self) -> Union[bool, None]: + return True diff --git a/nhlpy/api/query/filters/game_type.py b/nhlpy/api/query/filters/game_type.py new file mode 100644 index 0000000..0d9c816 --- /dev/null +++ b/nhlpy/api/query/filters/game_type.py @@ -0,0 +1,15 @@ +from typing import Union + +from nhlpy.api.query.builder import QueryBase + + +class GameTypeQuery(QueryBase): + def __init__(self, game_type: str): + self.game_type = game_type + self._game_type_q = "gameTypeId" + + def to_query(self) -> str: + return f"{self._game_type_q}={self.game_type}" + + def validate(self) -> Union[bool, None]: + return True diff --git a/nhlpy/api/query/filters/home_road.py b/nhlpy/api/query/filters/home_road.py new file mode 100644 index 0000000..ddc95ae --- /dev/null +++ b/nhlpy/api/query/filters/home_road.py @@ -0,0 +1,19 @@ +from typing import Union + +from nhlpy.api.query.filters import QueryBase + + +class HomeRoadQuery(QueryBase): + def __init__(self, home_road: str): + """ + H or R to indicate home or road games. + :param home_road: + """ + self.home_road = home_road + self._home_road_q = "homeRoad" + + def to_query(self) -> str: + return f"{self._home_road_q}='{self.home_road}'" + + def validate(self) -> Union[bool, None]: + return True diff --git a/nhlpy/api/query/filters/nationality.py b/nhlpy/api/query/filters/nationality.py new file mode 100644 index 0000000..888c35e --- /dev/null +++ b/nhlpy/api/query/filters/nationality.py @@ -0,0 +1,31 @@ +from typing import Union + +from nhlpy.api.query.builder import QueryBase + + +class NationalityQuery(QueryBase): + """ + Country/Nationality codes can be found via client.misc.countries() endpoint. As of 2/15/24 these are the codes" + [ + "AUS", "AUT", "BEL", "BHS", "BLR", "BRA", + "CAN", "CHE", "CHN", "DEU", "DNK", "EST", + "FIN", "FRA", "GBR", "GRC", "GUY", "HRV", + "HTI", "HUN", "IRL", "ISR", "ITA", "JAM", + "JPN", "KAZ", "KOR", "LBN", "LTU", "LVA", + "MEX", "NGA", "NLD", "NOR", "POL", "PRY", + "ROU", "RUS", "SRB", "SVK", "SVN", "SWE", + "THA", "UKR", "USA", "VEN", "YUG", "ZAF", + "CZE" + ] + + """ + + def __init__(self, nation_code: str): + self.nation_code = nation_code + self._nation_q = "nationalityCode" + + def validate(self) -> Union[bool, None]: + return True + + def to_query(self) -> str: + return f"{self._nation_q}='{self.nation_code}'" diff --git a/nhlpy/api/query/filters/opponent.py b/nhlpy/api/query/filters/opponent.py new file mode 100644 index 0000000..245a156 --- /dev/null +++ b/nhlpy/api/query/filters/opponent.py @@ -0,0 +1,19 @@ +from typing import Union + +from nhlpy.api.query.filters import QueryBase + + +class OpponentQuery(QueryBase): + def __init__(self, opponent_franchise_id: str): + """ + Opponent filter. Takes in the ID of the franchise. + :param opponent_id: int + """ + self.opponent_id: str = opponent_franchise_id + self._opponent_q = "opponentFranchiseId" + + def to_query(self) -> str: + return f"{self._opponent_q}={self.opponent_id}" + + def validate(self) -> Union[bool, None]: + return True diff --git a/nhlpy/api/query/filters/position.py b/nhlpy/api/query/filters/position.py new file mode 100644 index 0000000..17f3cf8 --- /dev/null +++ b/nhlpy/api/query/filters/position.py @@ -0,0 +1,32 @@ +from typing import Union +from enum import Enum + +from nhlpy.api.query.builder import QueryBase + + +class PositionTypes(str, Enum): + ALL_FORWARDS = "F" + CENTER = "C" + LEFT_WING = "L" + RIGHT_WING = "R" + DEFENSE = "D" + + +class PositionQuery(QueryBase): + def __init__(self, position: PositionTypes): + self.position = position + self._position_q = "positionCode" + + def to_query(self) -> str: + # All forwards require an OR clause + if self.position == PositionTypes.ALL_FORWARDS: + return ( + f"({self._position_q}='{PositionTypes.LEFT_WING.value}' " + f"or {self._position_q}='{PositionTypes.RIGHT_WING.value}' " + f"or {self._position_q}='{PositionTypes.CENTER.value}')" + ) + + return f"{self._position_q}='{self.position.value}'" + + def validate(self) -> Union[bool, None]: + return True diff --git a/nhlpy/api/query/filters/season.py b/nhlpy/api/query/filters/season.py new file mode 100644 index 0000000..c774b62 --- /dev/null +++ b/nhlpy/api/query/filters/season.py @@ -0,0 +1,22 @@ +from typing import Union + +from nhlpy.api.query.filters import QueryBase + + +class SeasonQuery(QueryBase): + def __init__(self, season_start: str, season_end: str): + self.season_start = season_start + self.season_end = season_end + self._season_start_q = "seasonId" + self._season_start_q_exp = ">=" + self._season_end_q = "seasonId" + self._season_end_q_exp = "<=" + + def to_query(self) -> str: + query = f"{self._season_start_q} {self._season_start_q_exp} {self.season_start}" + query += " and " + query += f"{self._season_end_q} {self._season_end_q_exp} {self.season_end}" + return query + + def validate(self) -> Union[bool, None]: + return True diff --git a/nhlpy/api/query/filters/shoot_catch.py b/nhlpy/api/query/filters/shoot_catch.py new file mode 100644 index 0000000..88ba473 --- /dev/null +++ b/nhlpy/api/query/filters/shoot_catch.py @@ -0,0 +1,19 @@ +from typing import Union + +from nhlpy.api.query.builder import QueryBase + + +class ShootCatchesQuery(QueryBase): + def __init__(self, shoot_catch: str): + """ + Shoot / catch filter. L or R, for both I believe its nothing. + :param shoot_catch: L, R + """ + self.shoot_catch = shoot_catch + self.shoot_catch_q = "shootsCatches" + + def to_query(self) -> str: + return f"{self.shoot_catch_q}={self.shoot_catch}" + + def validate(self) -> Union[bool, None]: + return True diff --git a/nhlpy/api/query/filters/status.py b/nhlpy/api/query/filters/status.py new file mode 100644 index 0000000..b21db5b --- /dev/null +++ b/nhlpy/api/query/filters/status.py @@ -0,0 +1,32 @@ +from typing import Union + +from nhlpy.api.query.filters import QueryBase + +# Not thrilled with this implementation, having 2 bools with the later overridding the first. +# Ill think of a better design pattern for this. + + +class StatusQuery(QueryBase): + def __init__(self, is_active: bool = False, is_hall_of_fame: bool = False): + """ + Player status. is_active=True for current active players, not suppling this + defaults to active/inactive. OR you can specify is_hall_of_fame=True, for + only HOF Players + :param is_active: + :param is_hall_of_fame: + """ + self.is_active: bool = is_active + self.is_hall_of_fame: bool = is_hall_of_fame + self._active_q = "active" + self._hof_q = "isInHallOfFame" + + def to_query(self) -> str: + if self.is_hall_of_fame: + return f"{self._hof_q}=1" + elif self.is_active: + return f"{self._active_q}=1" + else: + return "" + + def validate(self) -> Union[bool, None]: + return True diff --git a/nhlpy/api/query/sorting/__init__.py b/nhlpy/api/query/sorting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nhlpy/api/query/sorting/sorting_options.py b/nhlpy/api/query/sorting/sorting_options.py new file mode 100644 index 0000000..2a9d361 --- /dev/null +++ b/nhlpy/api/query/sorting/sorting_options.py @@ -0,0 +1,146 @@ +import logging +from typing import List + +logger = logging.getLogger(__name__) + +skater_summary_default_sorting = [ + {"property": "points", "direction": "DESC"}, + {"property": "gamesPlayed", "direction": "ASC"}, + {"property": "playerId", "direction": "ASC"}, +] + +skater_bios_default_sorting = [ + {"property": "lastName", "direction": "ASC_CI"}, + {"property": "skaterFullName", "direction": "ASC_CI"}, + {"property": "playerId", "direction": "ASC"}, +] + +faceoffs_default_sorting = [ + {"property": "totalFaceoffs", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + +faceoff_wins_default_sorting = [ + {"property": "totalFaceoffWins", "direction": "DESC"}, + {"property": "faceoffWinPct", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + +goalsForAgainst_default_sorting = [ + {"property": "evenStrengthGoalDifference", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + + +realtime_default_sorting = [{"property": "hits", "direction": "DESC"}, {"property": "playerId", "direction": "ASC"}] + +penalties_default_sorting = [ + {"property": "penaltyMinutes", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + +penaltyKill_default_sorting = [ + {"property": "shTimeOnIce", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + +penalty_shot_default_sorting = [ + {"property": "penaltyShotsGoals", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + +powerplay_default_sorting = [ + {"property": "ppTimeOnIce", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + +puckposs_default_sorting = [{"property": "satPct", "direction": "DESC"}, {"property": "playerId", "direction": "ASC"}] + +summary_shooting_default_sorting = [ + {"property": "satTotal", "direction": "DESC"}, + {"property": "usatTotal", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + +percentages_default_sorting = [ + {"property": "satPercentage", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + +scoringratesdefault_sorting = [ + {"property": "pointsPer605v5", "direction": "DESC"}, + {"property": "goalsPer605v5", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + +scoring_per_game_default_sorting = [ + {"property": "pointsPerGame", "direction": "DESC"}, + {"property": "goalsPerGame", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + +shootout_default_scoring = [ + {"property": "shootoutGoals", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + +shottype_default_sorting = [ + {"property": "shootingPct", "direction": "DESC"}, + {"property": "shootingPctBat", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + + +time_on_ice_default_sorting = [ + {"property": "timeOnIce", "direction": "DESC"}, + {"property": "playerId", "direction": "ASC"}, +] + + +class SortingOptions: + @staticmethod + def get_default_sorting_for_report(report: str) -> List[dict]: + """ + I know this us ugly. But hopefully its out of sight out of mind. + :param report: + :return: + """ + if report == "summary": + return skater_summary_default_sorting + elif report == "bios": + return skater_bios_default_sorting + elif report == "faceoffpercentages": + return faceoffs_default_sorting + elif report == "faceoffwins": + return faceoff_wins_default_sorting + elif report == "goalsForAgainst": + return goalsForAgainst_default_sorting + elif report == "realtime": + return realtime_default_sorting + elif report == "penalties": + return penalties_default_sorting + elif report == "penaltykill": + return penaltyKill_default_sorting + elif report == "penaltyShots": + return penalty_shot_default_sorting + elif report == "powerplay": + return powerplay_default_sorting + elif report == "puckPossessions": + return puckposs_default_sorting + elif report == "summaryshooting": + return summary_shooting_default_sorting + elif report == "percentages": + return percentages_default_sorting + elif report == "scoringRates": + return scoringratesdefault_sorting + elif report == "scoringpergame": + return scoring_per_game_default_sorting + elif report == "shootout": + return shootout_default_scoring + elif report == "shottype": + return shottype_default_sorting + elif report == "timeonice": + return time_on_ice_default_sorting + else: + logger.info("No default sort criteria setup for this report type, defaulting to skater summary") + return skater_summary_default_sorting diff --git a/nhlpy/api/stats.py b/nhlpy/api/stats.py index ede4661..81f4b00 100644 --- a/nhlpy/api/stats.py +++ b/nhlpy/api/stats.py @@ -2,7 +2,8 @@ import json from typing import List - +from nhlpy.api.query.builder import QueryContext +from nhlpy.api.query.sorting.sorting_options import SortingOptions from nhlpy.http_client import HttpClient @@ -101,7 +102,7 @@ def team_summary( "data" ] - def skater_stats_summary( + def skater_stats_summary_simple( self, start_season: str, end_season: str, @@ -130,7 +131,7 @@ def skater_stats_summary( {"property": "gamesPlayed", "direction": "ASC"}, {"property": "playerId", "direction": "ASC"} ] - :param start: Possibly start of the retrived data, based on limit. + :param start: Possibly start of the retrieved data, based on limit. :param limit: How many to return. :param fact_cayenne_exp: An anchor expression almost, default criteria. Only players with more than 1 game played. I default this to gamesPlayed>=1, which is what the nhl.com site uses. But you can play with it. @@ -163,18 +164,16 @@ def skater_stats_summary( "data" ] - def skater_stats_summary_by_expression( + def skater_stats_with_query_context( self, - cayenne_exp: str, - sort_expr: List[dict], + query_context: QueryContext, + report_type: str, + sort_expr: List[dict] = None, aggregate: bool = False, start: int = 0, limit: int = 70, - fact_cayenne_exp: str = "gamesPlayed>=1", ) -> dict: """ - A more bare bones / raw version of skater_stats_summary. This allows for more flexibility in the query params. - You must supply your own cayenne expressions and sort expressions. example: sort_expr = [ @@ -185,6 +184,10 @@ def skater_stats_summary_by_expression( cayenne_exp = "gameTypeId=2 and seasonId<=20232024 and seasonId>=20232024" client.stats.skater_stats_summary_by_expression(cayenne_exp=expr, sort_expr=sort_expr) + :param report_type: summary, bios, faceoffpercentages, faceoffwins, goalsForAgainst, realtime, penalties, + penaltykill, penaltyShots, powerplay, puckPossessions, summaryshooting, percentages, scoringRates, + scoringpergame, shootout, shottype, timeonice + :param query_context: :param aggregate: bool - If doing multiple years, you can choose to aggreate the date per player, or have separate entries for each one. :param sort_expr: A list of key/value pairs for sort criteria. As used in skater_stats_summary(), this is @@ -196,8 +199,6 @@ def skater_stats_summary_by_expression( ] :param start: :param limit: - :param fact_cayenne_exp: - :param default_cayenne_exp: :return: """ q_params = { @@ -205,8 +206,14 @@ def skater_stats_summary_by_expression( "isGame": False, "start": start, "limit": limit, - "factCayenneExp": fact_cayenne_exp, + "factCayenneExp": query_context.fact_query, } + + if not sort_expr: + sort_expr = SortingOptions.get_default_sorting_for_report(report_type) + q_params["sort"] = urllib.parse.quote(json.dumps(sort_expr)) - q_params["cayenneExp"] = cayenne_exp - return self.client.get_by_url("https://api.nhle.com/stats/rest/en/skater/summary", query_params=q_params).json() + q_params["cayenneExp"] = query_context.query_str + return self.client.get_by_url( + f"https://api.nhle.com/stats/rest/en/skater/{report_type}", query_params=q_params + ).json() diff --git a/pyproject.toml b/pyproject.toml index 2c5124c..f5f9340 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "nhl-api-py" -version = "2.2.7" +version = "2.3.1" description = "NHL API. For standings, team stats, outcomes, player information. Contains each individual API endpoint as well as convience methods for easy data loading in Pandas or any ML applications." authors = ["Corey Schaf "] readme = "README.md" diff --git a/tests/query/__init__.py b/tests/query/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/query/filters/test_decision.py b/tests/query/filters/test_decision.py new file mode 100644 index 0000000..633b661 --- /dev/null +++ b/tests/query/filters/test_decision.py @@ -0,0 +1,25 @@ +from pytest import raises + +from nhlpy.api.query import InvalidQueryValueException +from nhlpy.api.query.filters.decision import DecisionQuery + + +def test_win_outcome(): + decision = DecisionQuery(decision="W") + assert decision.to_query() == "decision='W'" + + +def test_loss_outcome(): + decision = DecisionQuery(decision="L") + assert decision.to_query() == "decision='L'" + + +def test_overtime_loss_outcome(): + decision = DecisionQuery(decision="O") + assert decision.to_query() == "decision='O'" + + +def test_invalid_data(): + decision = DecisionQuery(decision="A") + with raises(InvalidQueryValueException): + assert decision.validate() is False diff --git a/tests/query/filters/test_draft.py b/tests/query/filters/test_draft.py new file mode 100644 index 0000000..4e948d1 --- /dev/null +++ b/tests/query/filters/test_draft.py @@ -0,0 +1,11 @@ +from nhlpy.api.query.filters.draft import DraftQuery + + +def test_draft_year_with_round(): + draft = DraftQuery(year="2020", draft_round="2") + assert draft.to_query() == "draftYear=2020 and draftRound=2" + + +def test_draft_year_without_round(): + draft = DraftQuery(year="2020") + assert draft.to_query() == "draftYear=2020" diff --git a/tests/query/filters/test_experience.py b/tests/query/filters/test_experience.py new file mode 100644 index 0000000..3c92a89 --- /dev/null +++ b/tests/query/filters/test_experience.py @@ -0,0 +1,11 @@ +from nhlpy.api.query.filters.experience import ExperienceQuery + + +def test_is_rookie(): + experience = ExperienceQuery(is_rookie=True) + assert experience.to_query() == "isRookie='1'" + + +def test_is_veteran(): + experience = ExperienceQuery(is_rookie=False) + assert experience.to_query() == "isRookie='0'" diff --git a/tests/query/filters/test_franchise.py b/tests/query/filters/test_franchise.py new file mode 100644 index 0000000..4619e43 --- /dev/null +++ b/tests/query/filters/test_franchise.py @@ -0,0 +1,6 @@ +from nhlpy.api.query.filters.franchise import FranchiseQuery + + +def test_franchise_query(): + franchise_query = FranchiseQuery(franchise_id="1") + assert franchise_query.to_query() == "franchiseId=1" diff --git a/tests/query/filters/test_game_type.py b/tests/query/filters/test_game_type.py new file mode 100644 index 0000000..0ca9194 --- /dev/null +++ b/tests/query/filters/test_game_type.py @@ -0,0 +1,11 @@ +from nhlpy.api.query.filters.game_type import GameTypeQuery + + +def test_game_type_preseason(): + game_type = GameTypeQuery(game_type="1") + assert game_type.to_query() == "gameTypeId=1" + + +def test_game_type_regular(): + game_type = GameTypeQuery(game_type="2") + assert game_type.to_query() == "gameTypeId=2" diff --git a/tests/query/filters/test_home_road.py b/tests/query/filters/test_home_road.py new file mode 100644 index 0000000..9147b13 --- /dev/null +++ b/tests/query/filters/test_home_road.py @@ -0,0 +1,11 @@ +from nhlpy.api.query.filters.home_road import HomeRoadQuery + + +def test_home_game(): + home_road = HomeRoadQuery(home_road="H") + assert home_road.to_query() == "homeRoad='H'" + + +def test_road_game(): + home_road = HomeRoadQuery(home_road="R") + assert home_road.to_query() == "homeRoad='R'" diff --git a/tests/query/filters/test_nationality.py b/tests/query/filters/test_nationality.py new file mode 100644 index 0000000..32b6ada --- /dev/null +++ b/tests/query/filters/test_nationality.py @@ -0,0 +1,6 @@ +from nhlpy.api.query.filters.nationality import NationalityQuery + + +def test_nation_usa(): + nation = NationalityQuery(nation_code="USA") + assert nation.to_query() == "nationalityCode='USA'" diff --git a/tests/query/filters/test_position.py b/tests/query/filters/test_position.py new file mode 100644 index 0000000..97395cb --- /dev/null +++ b/tests/query/filters/test_position.py @@ -0,0 +1,26 @@ +from nhlpy.api.query.filters.position import PositionQuery, PositionTypes + + +def test_centers(): + position = PositionQuery(position=PositionTypes.CENTER) + assert position.to_query() == "positionCode='C'" + + +def test_left_wings(): + position = PositionQuery(position=PositionTypes.LEFT_WING) + assert position.to_query() == "positionCode='L'" + + +def test_right_wings(): + position = PositionQuery(position=PositionTypes.RIGHT_WING) + assert position.to_query() == "positionCode='R'" + + +def test_forwards(): + position = PositionQuery(position=PositionTypes.ALL_FORWARDS) + assert position.to_query() == "(positionCode='L' or positionCode='R' or positionCode='C')" + + +def test_defense(): + position = PositionQuery(position=PositionTypes.DEFENSE) + assert position.to_query() == "positionCode='D'" diff --git a/tests/query/filters/test_season.py b/tests/query/filters/test_season.py new file mode 100644 index 0000000..6e40851 --- /dev/null +++ b/tests/query/filters/test_season.py @@ -0,0 +1,16 @@ +from nhlpy.api.query.filters.season import SeasonQuery + + +def test_season_query_range(): + season_query = SeasonQuery(season_start="20202021", season_end="20232024") + assert season_query.to_query() == "seasonId >= 20202021 and seasonId <= 20232024" + + +def test_season_query_same_year(): + season_query = SeasonQuery(season_start="20202021", season_end="20202021") + assert season_query.to_query() == "seasonId >= 20202021 and seasonId <= 20202021" + + +def test_season_query_wrong_range(): + season_query = SeasonQuery(season_start="20232024", season_end="20202020") + assert season_query.to_query() == "seasonId >= 20232024 and seasonId <= 20202020" diff --git a/tests/query/filters/test_shoot_catch.py b/tests/query/filters/test_shoot_catch.py new file mode 100644 index 0000000..b4faf79 --- /dev/null +++ b/tests/query/filters/test_shoot_catch.py @@ -0,0 +1,11 @@ +from nhlpy.api.query.filters.shoot_catch import ShootCatchesQuery + + +def test_shoot_catch_l(): + shoot_catch = ShootCatchesQuery(shoot_catch="L") + assert shoot_catch.to_query() == "shootsCatches=L" + + +def test_shoot_catch_r(): + shoot_catch = ShootCatchesQuery(shoot_catch="R") + assert shoot_catch.to_query() == "shootsCatches=R" diff --git a/tests/query/filters/test_status.py b/tests/query/filters/test_status.py new file mode 100644 index 0000000..73d7460 --- /dev/null +++ b/tests/query/filters/test_status.py @@ -0,0 +1,21 @@ +from nhlpy.api.query.filters.status import StatusQuery + + +def test_active_player(): + status = StatusQuery(is_active=True) + assert status.to_query() == "active=1" + + +def test_hall_of_fame_player(): + status = StatusQuery(is_hall_of_fame=True) + assert status.to_query() == "isInHallOfFame=1" + + +def test_active_and_hof_should_return_hof(): + status = StatusQuery(is_active=True, is_hall_of_fame=True) + assert status.to_query() == "isInHallOfFame=1" + + +def test_inactive_not_hof_returns_empty(): + status = StatusQuery(is_active=False, is_hall_of_fame=False) + assert status.to_query() == "" diff --git a/tests/query/test_builder.py b/tests/query/test_builder.py new file mode 100644 index 0000000..8b34701 --- /dev/null +++ b/tests/query/test_builder.py @@ -0,0 +1,93 @@ +from nhlpy.api.query.builder import QueryBuilder, QueryContext +from nhlpy.api.query.filters.decision import DecisionQuery +from nhlpy.api.query.filters.draft import DraftQuery +from nhlpy.api.query.filters.game_type import GameTypeQuery +from nhlpy.api.query.filters.position import PositionQuery, PositionTypes +from nhlpy.api.query.filters.season import SeasonQuery + + +def test_query_builder_empty_filters(): + qb = QueryBuilder() + context: QueryContext = qb.build(filters=[]) + + assert context.query_str == "" + + +def test_query_builder_invalid_filter(): + qb = QueryBuilder() + context: QueryContext = qb.build(filters=["invalid"]) + + assert context.query_str == "" + + +def test_qb_draft_year(): + qb = QueryBuilder() + filters = [DraftQuery(year="2020", draft_round="2")] + context: QueryContext = qb.build(filters=filters) + + assert context.query_str == "draftYear=2020 and draftRound=2" + assert len(context.filters) == 1 + + +def test_qb_multi_filter(): + qb = QueryBuilder() + filters = [ + GameTypeQuery(game_type="2"), + DraftQuery(year="2020", draft_round="2"), + SeasonQuery(season_start="20202021", season_end="20232024"), + ] + context: QueryContext = qb.build(filters=filters) + + assert ( + context.query_str + == "gameTypeId=2 and draftYear=2020 and draftRound=2 and seasonId >= 20202021 and seasonId <= 20232024" + ) + + +def test_position_draft_query(): + qb = QueryBuilder() + filters = [ + GameTypeQuery(game_type="2"), + DraftQuery(year="2020", draft_round="1"), + PositionQuery(position=PositionTypes.CENTER), + ] + context: QueryContext = qb.build(filters=filters) + + assert context.query_str == "gameTypeId=2 and draftYear=2020 and draftRound=1 and positionCode='C'" + assert len(context.filters) == 3 + + +def test_all_forwards_playoffs_season_query(): + qb = QueryBuilder() + filters = [ + GameTypeQuery(game_type="3"), + SeasonQuery(season_start="20222023", season_end="20222023"), + PositionQuery(position=PositionTypes.ALL_FORWARDS), + ] + context: QueryContext = qb.build(filters=filters) + + assert ( + context.query_str + == "gameTypeId=3 and seasonId >= 20222023 and seasonId <= 20222023 and (positionCode='L' or positionCode='R' " + "or positionCode='C')" + ) + assert len(context.filters) == 3 + + +def test_query_with_invalid_filter_mixed_in(): + qb = QueryBuilder(verbose=True) + filters = [ + GameTypeQuery(game_type="3"), + SeasonQuery(season_start="20222023", season_end="20222023"), + PositionQuery(position=PositionTypes.ALL_FORWARDS), + DecisionQuery(decision="Win"), + ] + context: QueryContext = qb.build(filters=filters) + + assert context.is_valid() is False + + assert ( + context.query_str + == "gameTypeId=3 and seasonId >= 20222023 and seasonId <= 20222023 and (positionCode='L' or positionCode='R' " + "or positionCode='C')" + ) diff --git a/tests/test_stats.py b/tests/test_stats.py index c5d5e47..226b63e 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -71,7 +71,7 @@ def team_test_summary_year_range_playoffs(h_m, nhl_client): @mock.patch("httpx.get") def test_skater_stats_summary(h_m, nhl_client): - nhl_client.stats.skater_stats_summary(start_season="20232024", end_season="20232024") + nhl_client.stats.skater_stats_summary_simple(start_season="20232024", end_season="20232024") h_m.assert_called_once() assert h_m.call_args[1]["url"] == "https://api.nhle.com/stats/rest/en/skater/summary" assert h_m.call_args[1]["params"] == { @@ -89,7 +89,7 @@ def test_skater_stats_summary(h_m, nhl_client): @mock.patch("httpx.get") def test_skater_stats_summary_franchise(h_m, nhl_client): - nhl_client.stats.skater_stats_summary(start_season="20232024", end_season="20232024", franchise_id=19) + nhl_client.stats.skater_stats_summary_simple(start_season="20232024", end_season="20232024", franchise_id=19) h_m.assert_called_once() assert h_m.call_args[1]["url"] == "https://api.nhle.com/stats/rest/en/skater/summary" assert h_m.call_args[1]["params"] == {