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"] == {