sleeper_api 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49f189b10709caa46c846f8733372cfd50d1dff0f66548aba5cb7ce0d32508f5
4
- data.tar.gz: a0670835fa5f56541d041292b498722d0597a0599c724e95f9e3ed193151872c
3
+ metadata.gz: 6fd4df8d68adb46808ea692bbf8c715f2218a07a8f93212f5efddfe404741a30
4
+ data.tar.gz: cd3b09acb61407c634f04d76f165a1d2e0b7b840b3564117136ecc9343bc286c
5
5
  SHA512:
6
- metadata.gz: dd18ff49145f5c1848857f7af5e9b15f02a8aac6f7940fe85517d333a213707f524d3f54d5d211ce94de28be9e955e6e366a3db66101ce329d39264f6eabf983
7
- data.tar.gz: 0b254fa2078732333ee53f3f1074f3700c85c28733478234cd3c303b6b2698967889ad2db33d9802b1a93d00d7dee378387bfdb1d18403f55d0104daedc934b1
6
+ metadata.gz: a63777ce6255da38e963034bf9b016ca327d85dcaefc723ad508c85d58c9674939474ee00bd55b33b007e3268a043e81f8c175f06276cb77ae80b6a619871431
7
+ data.tar.gz: aed8fa84f731ddb1e8a01ef2cfb8987305826f665f338954e990fcb8c2c946c3d33f73edf510217ecee1923412bc6470a56931dbd9c60ba7369230c2c847627c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## [1.3.0] - 2026-08-27
2
+
3
+ ### Added
4
+
5
+ - `Client#stats(season, week:, season_type:, sport:)` and `Client#projections(...)` — per-player weekly statistics and projections, both undocumented, both under `/v1`. Found by probing on 2026-08-27; the consuming app's backlog had recorded "Sleeper has no projections endpoint and no player-stats endpoint" as settled fact, and several of its cards were blocked on that.
6
+
7
+ Each returns an object keyed by player id — plus `TEAM_XXX` keys for team-level rows — holding raw counting stats (`rec`, `rush_yd`, `off_snp`, `rec_rz_tgt`, …) alongside Sleeper's canned `pts_ppr` / `pts_half_ppr` / `pts_std`. 228 distinct fields were observed across one week of 2025. Omitting `week` requests season totals, which is a shorter path and a different resource rather than a default of week 1.
8
+
9
+ Three things a caller has to know, all confirmed live:
10
+
11
+ - **Nothing 404s.** An unplayed week (`regular/2026/1`), a week out of range (`regular/2025/99`) and an unrecognised season type (`banana`) all answer 200 with `{}`. An empty body is indistinguishable from a typo, so validate arguments rather than trusting emptiness.
12
+ - **A row count is not evidence of a projection.** `projections` for a season Sleeper has not projected still returns a full set of entries — 9,386 for 2030 — every one holding only `{"adp_dd_ppr" => 1000.0}` and no `pts_ppr`. Filter on the field you want.
13
+ - **`pre` and `post` restart week numbering at 1**, exactly as `#schedule` does, so rows from different season types must never be pooled.
14
+
15
+ `adp_dd_ppr` 1000.0 and `pos_rank_*` 999.0 are "unknown" sentinels rather than values.
16
+
17
+ Path segments are escaped, unlike the older `#get_user` — an unescaped segment can walk out of the endpoint entirely, which the consuming app had to work around at its own boundary.
18
+
19
+ ### Fixed
20
+
21
+ - README.md contained 272 non-breaking spaces across 90 lines, 58 of them **inside `​```ruby` code fences** — so the documented examples raised a syntax error when copy-pasted. `lib/` and `spec/` were unaffected; only the documentation was broken. Replaced with ordinary spaces.
22
+
1
23
  ## [1.2.0] - 2026-08-23
2
24
 
3
25
  ### Added
data/README.md CHANGED
@@ -2,71 +2,71 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/sleeper_api.svg)](https://badge.fury.io/rb/sleeper_api)
4
4
 
5
- A comprehensive Ruby gem for interacting with [Sleeper's fantasy football API](https://docs.sleeper.com/). Built with performance, reliability, and developer experience in mind.
5
+ A comprehensive Ruby gem for interacting with [Sleeper's fantasy football API](https://docs.sleeper.com/). Built with performance, reliability, and developer experience in mind.
6
6
 
7
7
  ## Features
8
8
 
9
- - Complete API Coverage - Users, leagues, drafts, players, matchups, transactions
9
+ - Complete API Coverage - Users, leagues, drafts, players, matchups, transactions
10
10
 
11
- - Performance Optimized - Smart caching, connection pooling, rate limiting
11
+ - Performance Optimized - Smart caching, connection pooling, rate limiting
12
12
 
13
- - Robust Error Handling - Automatic retries, timeout management, detailed error messages
13
+ - Robust Error Handling - Automatic retries, timeout management, detailed error messages
14
14
 
15
- - Well Tested - 90%+ test coverage with RSpec
15
+ - Well Tested - 90%+ test coverage with RSpec
16
16
 
17
- - Highly Configurable - Custom timeouts, retries, logging
17
+ - Highly Configurable - Custom timeouts, retries, logging
18
18
 
19
- - Production Ready - Type signatures, CI/CD, code quality tools
19
+ - Production Ready - Type signatures, CI/CD, code quality tools
20
20
 
21
21
  ## Installation
22
22
 
23
23
  Add this line to your application's Gemfile:
24
24
 
25
25
  ```ruby
26
- gem 'sleeper_api'
26
+ gem 'sleeper_api'
27
27
  ```
28
28
 
29
29
  ## Quick Start
30
30
 
31
31
  ```ruby
32
- require 'sleeper_api'
32
+ require 'sleeper_api'
33
33
 
34
- # Basic usage with default configuration
35
- client = SleeperApi.client
36
- league = client.league("123456789012345678")
34
+ # Basic usage with default configuration
35
+ client = SleeperApi.client
36
+ league = client.league("123456789012345678")
37
37
 
38
- # Access league information
39
- puts league.name          # "My Fantasy League"
40
- puts league.total_rosters # 12
41
- puts league.status        # "in_season"
38
+ # Access league information
39
+ puts league.name # "My Fantasy League"
40
+ puts league.total_rosters # 12
41
+ puts league.status # "in_season"
42
42
 
43
- # Get rosters
44
- rosters = league.rosters
45
- rosters.each do |roster|
46
-   puts "#{roster[:owner_display_name]}: #{roster[:wins]}-#{roster[:losses]}"
43
+ # Get rosters
44
+ rosters = league.rosters
45
+ rosters.each do |roster|
46
+ puts "#{roster[:owner_display_name]}: #{roster[:wins]}-#{roster[:losses]}"
47
47
  end
48
48
  ```
49
49
 
50
50
  ## Configuration
51
51
 
52
- Customize the gem's behavior:
52
+ Customize the gem's behavior:
53
53
 
54
54
  ```ruby
55
- SleeperApi.configure do |config|
56
-   config.timeout = 45    # Request timeout in seconds (10-60)
57
-   config.retries = 5     # Number of retries on failure (0-5)
58
-   config.logger = Logger.new(STDOUT)  # Custom logger
55
+ SleeperApi.configure do |config|
56
+ config.timeout = 45 # Request timeout in seconds (10-60)
57
+ config.retries = 5 # Number of retries on failure (0-5)
58
+ config.logger = Logger.new(STDOUT) # Custom logger
59
59
  end
60
60
 
61
- # Configuration is applied to all subsequent client instances
62
- client = SleeperApi.client
61
+ # Configuration is applied to all subsequent client instances
62
+ client = SleeperApi.client
63
63
  ```
64
64
 
65
65
  ## API Coverage
66
66
 
67
67
  ### Users
68
68
 
69
- Get user information and their leagues/drafts:
69
+ Get user information and their leagues/drafts:
70
70
 
71
71
  ```ruby
72
72
  # Find a user by username
@@ -118,7 +118,7 @@ puts "Overall: #{summary[:total_wins]}-#{summary[:total_losses]}"
118
118
 
119
119
  ### Leagues
120
120
 
121
- Access league data, rosters, matchups, and transactions:
121
+ Access league data, rosters, matchups, and transactions:
122
122
 
123
123
  ```ruby
124
124
  league = SleeperApi.client.league("123456")
@@ -180,7 +180,7 @@ matchup[:winner_owner] # => "Team Beta"
180
180
 
181
181
  ### Drafts
182
182
 
183
- Access draft information, picks, and traded picks:
183
+ Access draft information, picks, and traded picks:
184
184
 
185
185
  ```ruby
186
186
  # Get a specific draft
@@ -237,19 +237,19 @@ team_3_rounds = draft.team_picks[3] # Team 3's picks by round
237
237
 
238
238
  ### Players
239
239
 
240
- Access player data with automatic caching:
240
+ Access player data with automatic caching:
241
241
 
242
242
  ```ruby
243
- # Get all players (cached for 24 hours)
244
- players = client.get_players
243
+ # Get all players (cached for 24 hours)
244
+ players = client.get_players
245
245
 
246
- # Find specific player
247
- player = client.get_player_by_id("1234")
248
- puts "#{player['first_name']} #{player['last_name']} - #{player['position']}"
246
+ # Find specific player
247
+ player = client.get_player_by_id("1234")
248
+ puts "#{player['first_name']} #{player['last_name']} - #{player['position']}"
249
249
 
250
- # Get trending players
251
- trending_adds = client.trending_players(type: "add", limit: 10)
252
- trending_drops = client.trending_players(type: "drop", limit: 10)
250
+ # Get trending players
251
+ trending_adds = client.trending_players(type: "add", limit: 10)
252
+ trending_drops = client.trending_players(type: "drop", limit: 10)
253
253
  ```
254
254
 
255
255
  ### Player Helper
@@ -313,103 +313,121 @@ rosters.each do |roster|
313
313
  end
314
314
  ```
315
315
 
316
- ### Additional Endpoints
316
+ ### Additional Endpoints
317
317
 
318
318
  ```ruby
319
- # Get NFL state
320
- state = client.get_nfl_state
321
- puts "Current week: #{state['week']}"
319
+ # Get NFL state
320
+ state = client.get_nfl_state
321
+ puts "Current week: #{state['week']}"
322
+
323
+ # Per-player weekly stats and projections (undocumented endpoints).
324
+ # Keyed by player id, plus TEAM_XXX keys for team-level rows.
325
+ week_stats = client.stats(2025, week: 1)
326
+ puts week_stats["4046"]["pts_ppr"] # => 21.4
327
+ puts week_stats["4046"]["off_snp"] # raw counting stats too
328
+
329
+ projected = client.projections(2026, week: 1)
330
+
331
+ # Omit the week for season totals — a different resource, not a default.
332
+ season_stats = client.stats(2025)
333
+
334
+ # Nothing here 404s: an unplayed week, a week out of range and an unknown
335
+ # season type all answer 200 with {}. And a projections call for a season
336
+ # Sleeper has not projected still returns thousands of entries carrying only
337
+ # an `adp_dd_ppr` sentinel — a row count is not evidence of a projection, so
338
+ # filter on the field you actually want.
339
+ real = projected.parsed_response.select { |_id, row| row.key?("pts_ppr") }
322
340
 
323
- # Get playoff brackets
324
- winners_bracket = client.get_league_playoff_bracket("league_id")
325
- losers_bracket = client.get_league_toilet_bowl("league_id")
341
+ # Get playoff brackets
342
+ winners_bracket = client.get_league_playoff_bracket("league_id")
343
+ losers_bracket = client.get_league_toilet_bowl("league_id")
326
344
 
327
- # Get league drafts
328
- league_drafts = client.get_league_drafts("league_id")
345
+ # Get league drafts
346
+ league_drafts = client.get_league_drafts("league_id")
329
347
 
330
- # Get traded picks
331
- traded_picks = client.get_league_traded_picks("league_id")
348
+ # Get traded picks
349
+ traded_picks = client.get_league_traded_picks("league_id")
332
350
  ```
333
351
 
334
- ## Error Handling
352
+ ## Error Handling
335
353
 
336
354
  The gem provides comprehensive error handling:
337
355
 
338
356
  ```ruby
339
357
  begin
340
-   league = client.league("invalid_id")
341
-   # Process league data
342
- rescue SleeperApi::Error => e
343
-   puts "API Error: #{e.message}"
344
- rescue ArgumentError => e
345
-   puts "Invalid parameter: #{e.message}"
358
+ league = client.league("invalid_id")
359
+ # Process league data
360
+ rescue SleeperApi::Error => e
361
+ puts "API Error: #{e.message}"
362
+ rescue ArgumentError => e
363
+ puts "Invalid parameter: #{e.message}"
346
364
  end
347
365
  ```
348
366
 
349
367
  ### Error Types
350
368
 
351
- - SleeperApi::Error - API-related errors (404, 500, timeouts, etc.)
369
+ - SleeperApi::Error - API-related errors (404, 500, timeouts, etc.)
352
370
 
353
- - ArgumentError - Invalid parameters passed to methods
371
+ - ArgumentError - Invalid parameters passed to methods
354
372
 
355
373
  ## Performance Considerations
356
374
 
357
375
  ### Caching
358
376
 
359
- - Player data is cached for 24 hours to reduce API calls
377
+ - Player data is cached for 24 hours to reduce API calls
360
378
 
361
- - League/User/Draft data is cached per instance
379
+ - League/User/Draft data is cached per instance
362
380
 
363
- - Cache files are stored in the current working directory
381
+ - Cache files are stored in the current working directory
364
382
 
365
- ### Rate Limiting
383
+ ### Rate Limiting
366
384
 
367
- - Be mindful of Sleeper's rate limits: stay under 1000 API calls per minute
385
+ - Be mindful of Sleeper's rate limits: stay under 1000 API calls per minute
368
386
 
369
- - The gem automatically handles timeouts and retries
387
+ - The gem automatically handles timeouts and retries
370
388
 
371
389
  - Consider caching frequently accessed data in your application
372
390
 
373
391
  ### Memory Usage
374
392
 
375
- - Large datasets (like all players) are cached to disk
393
+ - Large datasets (like all players) are cached to disk
376
394
 
377
- - League rosters and matchups are fetched lazily
395
+ - League rosters and matchups are fetched lazily
378
396
 
379
- - Use no_data: true when initializing leagues if you don't need immediate data
397
+ - Use no_data: true when initializing leagues if you don't need immediate data
380
398
 
381
399
  ## Testing
382
400
 
383
401
  The gem includes comprehensive tests:
384
402
 
385
403
  ```shellscript
386
- # Run all tests
387
- bundle exec rspec
388
- # Run with coverage
389
- bundle exec rspec --coverage
390
- # Run specific test file
391
- bundle exec rspec spec/sleeper_api/client_spec.rb
404
+ # Run all tests
405
+ bundle exec rspec
406
+ # Run with coverage
407
+ bundle exec rspec --coverage
408
+ # Run specific test file
409
+ bundle exec rspec spec/sleeper_api/client_spec.rb
392
410
  ```
393
411
 
394
412
  ### Setup
395
413
 
396
414
  ```shellscript
397
- git clone https://github.com/eruity1/sleeper_api.git
398
- cd sleeper_api
399
- bundle install
415
+ git clone https://github.com/eruity1/sleeper_api.git
416
+ cd sleeper_api
417
+ bundle install
400
418
  ```
401
419
 
402
420
  ### Code Quality
403
421
 
404
422
  ```shellscript
405
- # Run all checks (tests + linting)
406
- bundle exec rake ci
423
+ # Run all checks (tests + linting)
424
+ bundle exec rake ci
407
425
 
408
- # Run RuboCop
409
- bundle exec rubocop
426
+ # Run RuboCop
427
+ bundle exec rubocop
410
428
 
411
- # Auto-fix RuboCop issues
412
- bundle exec rubocop -a
429
+ # Auto-fix RuboCop issues
430
+ bundle exec rubocop -a
413
431
  ```
414
432
 
415
433
  ### Contributing
@@ -426,24 +444,24 @@ bundle exec rubocop -a
426
444
 
427
445
  ### Development Dependencies
428
446
 
429
- - rspec - Testing framework
447
+ - rspec - Testing framework
430
448
 
431
- - rubocop - Code style and quality
449
+ - rubocop - Code style and quality
432
450
 
433
- - simplecov - Test coverage
451
+ - simplecov - Test coverage
434
452
 
435
- - webmock - HTTP request mocking
453
+ - webmock - HTTP request mocking
436
454
 
437
455
  ## Requirements
438
456
 
439
457
  - Ruby 2.6.0 or higher
440
458
 
441
- - No external dependencies (HTTParty is bundled)
459
+ - No external dependencies (HTTParty is bundled)
442
460
 
443
461
  ## License
444
462
 
445
- The gem is available as open source under the terms of the MIT License.
463
+ The gem is available as open source under the terms of the MIT License.
446
464
 
447
465
  ## Changelog
448
466
 
449
- See CHANGELOG.md for version history and updates.
467
+ See CHANGELOG.md for version history and updates.
@@ -1,5 +1,6 @@
1
1
  require "httparty"
2
2
  require "json"
3
+ require "erb"
3
4
 
4
5
  module SleeperApi
5
6
  # HTTP client for Sleeper API requests.
@@ -209,6 +210,54 @@ module SleeperApi
209
210
  end
210
211
  end
211
212
 
213
+ # Get per-player statistics for one week, or for a whole season.
214
+ #
215
+ # Undocumented. Returns an object keyed by player id — plus `TEAM_XXX` keys
216
+ # for team-level rows — each holding raw counting stats (`rec`, `rush_yd`,
217
+ # `off_snp`, `rec_rz_tgt`…) alongside Sleeper's three canned point totals
218
+ # `pts_ppr` / `pts_half_ppr` / `pts_std`. 228 distinct fields were observed
219
+ # across one week of 2025.
220
+ #
221
+ # **Nothing here 404s.** An unplayed week, a week out of range, and an
222
+ # unrecognised season type all answer 200 with `{}`, so an empty result is
223
+ # a legitimate answer and is indistinguishable from a typo. Validate the
224
+ # arguments before you trust an empty body.
225
+ #
226
+ # Omitting `week` requests season totals, which is a different resource at
227
+ # a shorter path rather than a default of week 1.
228
+ #
229
+ # `pre` and `post` restart week numbering at 1, exactly as #schedule does,
230
+ # so rows from different season types must never be pooled.
231
+ #
232
+ # @param season [Integer, String] Season year, e.g. 2025
233
+ # @param week [Integer, String, nil] Week number, or nil for season totals
234
+ # @param season_type [String] "regular" (default), "pre", or "post"
235
+ # @param sport [String] Sport code (default: "nfl")
236
+ # @return [HTTParty::Response] Player id to stats mapping
237
+ def stats(season, week: nil, season_type: "regular", sport: "nfl")
238
+ make_request(weekly_path("stats", sport, season_type, season, week))
239
+ end
240
+
241
+ # Get per-player projections for one week, or for a whole season.
242
+ #
243
+ # Same shape and same caveats as #stats, with one of its own: for a season
244
+ # Sleeper has not projected, this still returns a full set of entries —
245
+ # 9,386 of them for 2030 — every one holding only `{"adp_dd_ppr" => 1000.0}`
246
+ # and no `pts_ppr` at all. **A row count is not evidence of a projection.**
247
+ # Filter on the field you actually want.
248
+ #
249
+ # `adp_dd_ppr` 1000.0 and `pos_rank_*` 999.0 are "unknown" sentinels rather
250
+ # than values.
251
+ #
252
+ # @param season [Integer, String] Season year, e.g. 2026
253
+ # @param week [Integer, String, nil] Week number, or nil for season totals
254
+ # @param season_type [String] "regular" (default), "pre", or "post"
255
+ # @param sport [String] Sport code (default: "nfl")
256
+ # @return [HTTParty::Response] Player id to projections mapping
257
+ def projections(season, week: nil, season_type: "regular", sport: "nfl")
258
+ make_request(weekly_path("projections", sport, season_type, season, week))
259
+ end
260
+
212
261
  # Get a season's game schedule.
213
262
  #
214
263
  # Undocumented, and served from the host root rather than /v1 — hence the
@@ -271,6 +320,20 @@ module SleeperApi
271
320
 
272
321
  private
273
322
 
323
+ # Path for #stats and #projections. Segments are escaped because they are
324
+ # interpolated into a URI path: an unescaped one can walk out of the
325
+ # endpoint entirely, which is the bug the consuming app had to work around
326
+ # for #get_user.
327
+ #
328
+ # A nil week drops the segment rather than defaulting, because the shorter
329
+ # path is season totals.
330
+ def weekly_path(resource, sport, season_type, season, week)
331
+ segments = [resource, sport, season_type, season, week].compact
332
+ escaped = segments.map { |segment| ERB::Util.url_encode(segment.to_s) }
333
+
334
+ "/v1/#{escaped.join("/")}"
335
+ end
336
+
274
337
  # Make an HTTP request with retry logic and logging.
275
338
  #
276
339
  # @param path [String] API endpoint path
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SleeperApi
4
- VERSION = "1.2.0"
4
+ VERSION = "1.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sleeper_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eruity1