From c7a48bfb218ec740436ff83b28caeba7fca0a6cf Mon Sep 17 00:00:00 2001 From: Corey Schaf Date: Thu, 15 Feb 2024 17:28:42 -0500 Subject: [PATCH] Query Builder (#40) * Query Builder - First commit. The goal of this is to make it easy to build complex filters to utilize the stats APIs in the nhl ecosystem * QueryBuilder. Adds more filter support for positions, franchise, game type. Test coverage update * Stat Query Builder. Adds support for factExpressions base expressions passed via query context. Adds Decision, Experience, HomeRoad, Nationality, Status Query filters. Renames stats.skater_stats_summary to stats.skater_stats_summary_simple. README updated with latest query builder howtos. QueryBase updates to have validate() abstract method so each filter can define its own validation rules. QueryBuilder runs each validation upon build(). Updates QueryContext. Adds .is_valid() for quick error checks. Moves factCayenneExpr to QueryContext. --- .gitignore | 4 +- README.md | 241 +++++- nhl_api_2_2_1.json | 963 --------------------- nhl_api_2_3_0.json | 1 + nhlpy/_version.py | 2 +- nhlpy/api/__init__.py | 30 - nhlpy/api/query/__init__.py | 2 + nhlpy/api/query/builder.py | 50 ++ nhlpy/api/query/filters/__init__.py | 12 + nhlpy/api/query/filters/decision.py | 28 + nhlpy/api/query/filters/draft.py | 27 + nhlpy/api/query/filters/experience.py | 20 + nhlpy/api/query/filters/franchise.py | 15 + nhlpy/api/query/filters/game_type.py | 15 + nhlpy/api/query/filters/home_road.py | 19 + nhlpy/api/query/filters/nationality.py | 31 + nhlpy/api/query/filters/opponent.py | 19 + nhlpy/api/query/filters/position.py | 32 + nhlpy/api/query/filters/season.py | 22 + nhlpy/api/query/filters/shoot_catch.py | 19 + nhlpy/api/query/filters/status.py | 32 + nhlpy/api/query/sorting/__init__.py | 0 nhlpy/api/query/sorting/sorting_options.py | 146 ++++ nhlpy/api/stats.py | 35 +- pyproject.toml | 2 +- tests/query/__init__.py | 0 tests/query/filters/test_decision.py | 25 + tests/query/filters/test_draft.py | 11 + tests/query/filters/test_experience.py | 11 + tests/query/filters/test_franchise.py | 6 + tests/query/filters/test_game_type.py | 11 + tests/query/filters/test_home_road.py | 11 + tests/query/filters/test_nationality.py | 6 + tests/query/filters/test_position.py | 26 + tests/query/filters/test_season.py | 16 + tests/query/filters/test_shoot_catch.py | 11 + tests/query/filters/test_status.py | 21 + tests/query/test_builder.py | 93 ++ tests/test_stats.py | 4 +- 39 files changed, 974 insertions(+), 1045 deletions(-) delete mode 100644 nhl_api_2_2_1.json create mode 100644 nhl_api_2_3_0.json create mode 100644 nhlpy/api/query/__init__.py create mode 100644 nhlpy/api/query/builder.py create mode 100644 nhlpy/api/query/filters/__init__.py create mode 100644 nhlpy/api/query/filters/decision.py create mode 100644 nhlpy/api/query/filters/draft.py create mode 100644 nhlpy/api/query/filters/experience.py create mode 100644 nhlpy/api/query/filters/franchise.py create mode 100644 nhlpy/api/query/filters/game_type.py create mode 100644 nhlpy/api/query/filters/home_road.py create mode 100644 nhlpy/api/query/filters/nationality.py create mode 100644 nhlpy/api/query/filters/opponent.py create mode 100644 nhlpy/api/query/filters/position.py create mode 100644 nhlpy/api/query/filters/season.py create mode 100644 nhlpy/api/query/filters/shoot_catch.py create mode 100644 nhlpy/api/query/filters/status.py create mode 100644 nhlpy/api/query/sorting/__init__.py create mode 100644 nhlpy/api/query/sorting/sorting_options.py create mode 100644 tests/query/__init__.py create mode 100644 tests/query/filters/test_decision.py create mode 100644 tests/query/filters/test_draft.py create mode 100644 tests/query/filters/test_experience.py create mode 100644 tests/query/filters/test_franchise.py create mode 100644 tests/query/filters/test_game_type.py create mode 100644 tests/query/filters/test_home_road.py create mode 100644 tests/query/filters/test_nationality.py create mode 100644 tests/query/filters/test_position.py create mode 100644 tests/query/filters/test_season.py create mode 100644 tests/query/filters/test_shoot_catch.py create mode 100644 tests/query/filters/test_status.py create mode 100644 tests/query/test_builder.py 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"] == {