sleeper_api 1.3.0 → 1.3.1

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: 6fd4df8d68adb46808ea692bbf8c715f2218a07a8f93212f5efddfe404741a30
4
- data.tar.gz: cd3b09acb61407c634f04d76f165a1d2e0b7b840b3564117136ecc9343bc286c
3
+ metadata.gz: 3bd436fd0363716d6908e51d9137e89725230afb2c8a71479582a8894f12db57
4
+ data.tar.gz: b5933518c66d46edfc540ac261c0d473f62fc6c93f6cc03c9d7e426edebc9ed3
5
5
  SHA512:
6
- metadata.gz: a63777ce6255da38e963034bf9b016ca327d85dcaefc723ad508c85d58c9674939474ee00bd55b33b007e3268a043e81f8c175f06276cb77ae80b6a619871431
7
- data.tar.gz: aed8fa84f731ddb1e8a01ef2cfb8987305826f665f338954e990fcb8c2c946c3d33f73edf510217ecee1923412bc6470a56931dbd9c60ba7369230c2c847627c
6
+ metadata.gz: 7bdf8104296937896fdf11dcd71d12008e5801d9197552827293d734dd360032ac99dacaa5d7ff656e34ba2938f206bf8f864385d20b6f30230e12d819dc2039
7
+ data.tar.gz: 12eb88e92e6f27af1734b059b24313248ccf1888e6354437b391439920d0e3d2c5c00e263d7a957ad67cc791503978a92786b67b12eb6e871152a1b274e814c3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,69 @@
1
+ ## [1.3.1] - 2026-09-10
2
+
3
+ ### Fixed
4
+
5
+ - **Every request raised `ArgumentError: unknown keyword: quirks_mode` under
6
+ `json` 3.0.** HTTParty 0.24.2 parses with `JSON.parse(body, quirks_mode:
7
+ true, allow_nan: true)`; json 3.0 removed that keyword. Total breakage rather
8
+ than a degradation, and it arrives through a transitive bump rather than
9
+ anything a consumer chose — this gem has no committed lockfile, so CI went
10
+ red with no commit in between. httparty 0.24.2 is the newest release and has
11
+ no fix.
12
+
13
+ `SleeperApi::JsonParser` subclasses `HTTParty::Parser` and overrides its
14
+ `json` method to drop the flag; `Client` parses with it. **Deliberately not a
15
+ `json < 3` pin in the gemspec**, which would fix the same crash by forbidding
16
+ every app that uses this gem from upgrading json at all, for a flag none of
17
+ them asked for.
18
+
19
+ `quirks_mode: true` allowed a bare scalar at the top level, which is not
20
+ academic here — Sleeper answers an unknown username with a literal `null`.
21
+ Both majors parse that correctly without it: `JSON.parse("null")` is `nil`
22
+ under 2.21.2 and under 3.0.2. The suite passes under both.
23
+
24
+ ### Documented
25
+
26
+ Three facts measured against the live API on 2026-09-10, while NFL week 1 was
27
+ half-played — one game finished, one being played, fourteen not yet started.
28
+ No behaviour changed; all three are things a caller could previously only find
29
+ out by being wrong first.
30
+
31
+ - **`#schedule`'s `status` values are named: `pre_game`, `in_game`, `complete`,
32
+ `canceled`.** `in_game` was read off a live game; the other three come from
33
+ the published 2025 and 2026 schedules. It is the only per-game signal that a
34
+ game has been played — this payload has a `date` and no kickoff time, so a
35
+ caller can know a game is under way but never how far into it. **A week is
36
+ not one event**: week 1 of 2026 held three of those states at once, so
37
+ reasoning about "has the week started" from `date` alone gets it wrong for
38
+ everyone whose game is on Sunday. Treat the four as open and match with a
39
+ fallback — a postponement would be a fifth and none has been seen.
40
+
41
+ - **`#projections` is a pre-game, whole-game projection that does not move
42
+ while the game is played.** Five players in one night's game held the same
43
+ `pts_ppr` to the decimal across six hours spanning its kickoff; a game that
44
+ had already finished still projected 19.69 for a player who scored 26.2, so
45
+ it does not settle onto the final either. This is the natural endpoint to
46
+ reach for when building anything live, and it cannot answer the question:
47
+ **there is no live projection here**, so nothing in this payload says whether
48
+ a player is on pace. A player on 8 of a projected 12 in the first quarter is
49
+ ahead of schedule, and this reports 12 all afternoon.
50
+
51
+ - **`#stats` answers a week still being played with a partial set, and says
52
+ nothing about being partial.** Mid-week-1 it returned 301 rows with 42
53
+ carrying a `pts_ppr`. That is distinct from the already-documented empty
54
+ week: a caller treating "the week's stats" as the whole week gets a
55
+ half-filled answer for as long as the week is in progress, which for a
56
+ regular-season week is most of five days. `#schedule`'s per-game `status` is
57
+ the only thing that tells a missing row from a scoreless one.
58
+
59
+ ### Changed
60
+
61
+ - `#schedule`'s spec fixture carries all four statuses rather than `pre_game`
62
+ alone, and an example pins that they pass through untranslated. The narrow
63
+ fixture was a shape live data produces only on a quiet Tuesday, and a fixture
64
+ narrower than the API is how a caller ends up written against a vocabulary of
65
+ one.
66
+
1
67
  ## [1.3.0] - 2026-08-27
2
68
 
3
69
  ### Added
data/CLAUDE.md CHANGED
@@ -19,7 +19,7 @@ bundle exec rubocop -A # autocorrect
19
19
  bin/console # IRB with the gem loaded
20
20
  ```
21
21
 
22
- CI (`.github/workflows/ci.yml`) runs `bundle exec rake ci` on Ruby 3.2 only. The gemspec claims `required_ruby_version >= 2.6.0` and RuboCop targets 2.6, but nothing tests below 3.2 — treat 2.6 compatibility as unverified.
22
+ CI (`.github/workflows/ci.yml`) runs `bundle exec rake ci` on Ruby 3.2 and 3.4. The gemspec claims `required_ruby_version >= 2.6.0` and RuboCop targets 2.6, but nothing tests below 3.2 — treat 2.6 compatibility as unverified.
23
23
 
24
24
  ## Architecture
25
25
 
@@ -44,6 +44,22 @@ Four layers, with a deliberate split between HTTP and modeling:
44
44
 
45
45
  **Never fan out `get_players`.** It's a multi-megabyte payload of every NFL player. It's cached for 24h in an ivar on the client instance — which means the cache dies with the client. Consumers holding a short-lived client re-download it every time.
46
46
 
47
+ ### Undocumented endpoints carry undocumented vocabularies
48
+
49
+ Sleeper publishes no field reference, so every enum this gem passes through was
50
+ found by looking. Four are known — `#schedule`'s game `status`, `Player`'s
51
+ `status` and `injury_status`, and a roster's `lineup_position` — and **none of
52
+ them is closed**. Match with a fallback rather than a whitelist, and never
53
+ translate one into a vocabulary of this gem's own: the caller is the only one
54
+ who knows what to do with a value nobody has seen.
55
+
56
+ **When a value is measured, name it in the docstring and put the date and the
57
+ circumstance next to it.** `in_game` shipped as an unnamed fallback in the
58
+ consuming app for a day before anyone saw a live game; the fallback was right,
59
+ and the thing that made it a fact rather than a guess was writing down that it
60
+ was read at 21:13 on 2026-09-10 during a specific game. A vocabulary with no
61
+ provenance is indistinguishable from one somebody assumed.
62
+
47
63
  ## Testing
48
64
 
49
65
  RSpec + WebMock, with `WebMock.disable_net_connect!` — **specs must never hit the network**. Stub with `stub_request` (see `client_spec.rb`) or `instance_double(SleeperApi::Client)` for resource-object specs (see `league_spec.rb`).
@@ -54,6 +70,24 @@ RSpec + WebMock, with `WebMock.disable_net_connect!` — **specs must never hit
54
70
 
55
71
  `lib/` is at 100% line coverage. Every bug found in this gem so far has been in a `League#format_*` method handling a field Sleeper omitted, exercised only by real data. When adding a formatter, write the nil-field case first.
56
72
 
73
+ ### There is no committed Gemfile.lock, so CI resolves fresh every run
74
+
75
+ That is right for a library — a lockfile would hide exactly the incompatibility
76
+ a consumer is going to hit — but it has a consequence worth naming: **a green
77
+ run does not stay green.** A transitive release can turn CI red with no commit
78
+ in between, and the first PR opened afterwards looks like the culprit.
79
+
80
+ `json` 3.0 did this on 2026-09-11. It removed `quirks_mode`, which
81
+ `httparty` 0.24.2 passes on every JSON parse, so 11 examples began raising
82
+ `ArgumentError: unknown keyword: quirks_mode` — on a documentation-only PR that
83
+ touched no code. Check whether `main` is red before reading a failure as the
84
+ branch's fault; `main`'s last run can be weeks old.
85
+
86
+ `SleeperApi::JsonParser` is the fix, and it is worth reading before reaching for
87
+ a version pin: the gem overrides the one broken method rather than constraining
88
+ `json` in the gemspec, because a constraint there would forbid every consuming
89
+ app from upgrading `json` for a flag none of them asked for.
90
+
57
91
  ## Release
58
92
 
59
93
  Version lives in `lib/sleeper_api/version.rb`. Update `CHANGELOG.md`, then `bundle exec rake release` from a clean `main`, which builds to `pkg/`, tags, pushes the tag, and uploads to rubygems.
@@ -22,6 +22,11 @@ module SleeperApi
22
22
  # pass to make_request.
23
23
  base_uri "https://api.sleeper.app"
24
24
 
25
+ # Not HTTParty's own. Its JSON branch passes `quirks_mode`, which json 3.0
26
+ # removed, so without this every response raises ArgumentError the moment a
27
+ # consumer resolves json 3. See JsonParser.
28
+ parser SleeperApi::JsonParser
29
+
25
30
  # @param config [SleeperApi::Configuration] Client configuration
26
31
  def initialize(config)
27
32
  @config = config
@@ -223,6 +228,14 @@ module SleeperApi
223
228
  # a legitimate answer and is indistinguishable from a typo. Validate the
224
229
  # arguments before you trust an empty body.
225
230
  #
231
+ # **A week still being played answers with a partial set, and says nothing
232
+ # about being partial.** Mid-week-1 of 2026 this returned 301 rows with
233
+ # only 42 carrying a `pts_ppr` — the two teams whose games had finished or
234
+ # started. A caller that treats "the week's stats" as the whole week gets a
235
+ # half-filled answer for as long as the week is in progress, which for a
236
+ # regular-season week is most of five days. #schedule's per-game `status`
237
+ # is the only thing that can tell a missing row from a scoreless one.
238
+ #
226
239
  # Omitting `week` requests season totals, which is a different resource at
227
240
  # a shorter path rather than a default of week 1.
228
241
  #
@@ -249,6 +262,20 @@ module SleeperApi
249
262
  # `adp_dd_ppr` 1000.0 and `pos_rank_*` 999.0 are "unknown" sentinels rather
250
263
  # than values.
251
264
  #
265
+ # ⚠️ **This is a pre-game, whole-game projection and it does not move while
266
+ # the game is played.** Measured on 2026-09-10 across six hours spanning a
267
+ # kickoff: five players in that night's game held the same `pts_ppr` before
268
+ # it started and while it was in progress, to the decimal. Checked again
269
+ # against a game that had already finished — the projection still read
270
+ # 19.69 for a player who had scored 26.2, so it does not settle onto the
271
+ # final either.
272
+ #
273
+ # That matters because it is the natural thing to reach for and the wrong
274
+ # one: **there is no live projection here**, so nothing in this payload can
275
+ # say whether a player is on pace. A player on 8 points of a projected 12
276
+ # in the first quarter is ahead of schedule, and this endpoint will report
277
+ # 12 all afternoon.
278
+ #
252
279
  # @param season [Integer, String] Season year, e.g. 2026
253
280
  # @param week [Integer, String, nil] Week number, or nil for season totals
254
281
  # @param season_type [String] "regular" (default), "pre", or "post"
@@ -269,6 +296,26 @@ module SleeperApi
269
296
  # `post` (weeks 1-4) restart week numbering, so games from different season
270
297
  # types must never be pooled.
271
298
  #
299
+ # **`status` is `pre_game`, `in_game`, `complete` or `canceled`**, observed
300
+ # across the 2025 and 2026 regular seasons. `in_game` was read off a live
301
+ # game on 2026-09-10; the other three come from the published schedule. It
302
+ # is the only per-game signal of whether a game has been played — there is
303
+ # no kickoff time anywhere in this payload, only `date`, so a caller can
304
+ # know that a game is under way but never how far into it.
305
+ #
306
+ # **A week is not one event, and a caller reasoning from `date` alone will
307
+ # get that wrong.** Week 1 of 2026 held all three live states at the same
308
+ # moment: one `complete`, one `in_game`, fourteen `pre_game`.
309
+ #
310
+ # Treat the four as an open vocabulary. A postponement or a suspension
311
+ # would be a fifth value and none has been seen, so match with a fallback
312
+ # rather than a whitelist.
313
+ #
314
+ # **A team can carry two games in one week.** 2026 lists a canceled DAL/SEA
315
+ # in week 6 that was superseded rather than called off, and both teams play
316
+ # someone else that week. Order by status before picking one, or you will
317
+ # hand a team a bye it does not have.
318
+ #
272
319
  # A season Sleeper has not scheduled yet answers 200 with an empty array
273
320
  # rather than 404, so an empty result is a legitimate answer and not an
274
321
  # error.
@@ -0,0 +1,40 @@
1
+ require "httparty"
2
+ require "json"
3
+
4
+ module SleeperApi
5
+ # HTTParty's JSON parser, minus the one argument that stops it working.
6
+ #
7
+ # HTTParty 0.24.2 parses with `JSON.parse(body, quirks_mode: true,
8
+ # allow_nan: true)`. **json 3.0 removed `quirks_mode`**, so every response
9
+ # this gem parses raises `ArgumentError: unknown keyword: quirks_mode` the
10
+ # moment a consumer resolves json 3 — which is total breakage, not a
11
+ # degradation, and it arrives through a transitive bump nobody asked for.
12
+ # httparty 0.24.2 is the newest release and has no fix.
13
+ #
14
+ # **Dropping the option rather than pinning json is what keeps this the
15
+ # gem's problem instead of its consumers'.** A `json < 3` constraint in the
16
+ # gemspec would fix the same crash by forbidding every app that uses this
17
+ # gem from upgrading json at all, for a flag none of them asked for.
18
+ #
19
+ # `quirks_mode: true` allowed a bare scalar at the top level. That is not
20
+ # academic here — Sleeper answers an unknown username with a literal `null`.
21
+ # Both majors parse that correctly without the flag, checked on 2026-09-11:
22
+ # `JSON.parse("null")` is `nil` under json 2.21.2 and under 3.0.2. The flag
23
+ # has been doing nothing for this gem for some time.
24
+ #
25
+ # **@api private.** It is public only because `parser` has to name something
26
+ # a consumer's Ruby can resolve. Nothing here is a promise: it exists to be
27
+ # deleted the day httparty parses without the flag, and it is why 1.3.1 is a
28
+ # patch rather than a minor — a new constant that is not API does not earn
29
+ # one, and a fix for total breakage should reach a `~> 1.3.0` pin.
30
+ #
31
+ # A subclass rather than a `parser` lambda, because everything else in
32
+ # `HTTParty::Parser#parse` still applies — the blank-body guard, the format
33
+ # detection, the supported-format table. One method is wrong; one method is
34
+ # overridden.
35
+ class JsonParser < HTTParty::Parser
36
+ def json
37
+ JSON.parse(body, allow_nan: true)
38
+ end
39
+ end
40
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SleeperApi
4
- VERSION = "1.3.0"
4
+ VERSION = "1.3.1"
5
5
  end
data/lib/sleeper_api.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require_relative "sleeper_api/version"
2
2
  require_relative "sleeper_api/helpers"
3
+ require_relative "sleeper_api/json_parser"
3
4
  require_relative "sleeper_api/client"
4
5
  require_relative "sleeper_api/league"
5
6
  require_relative "sleeper_api/user"
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.3.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eruity1
@@ -148,6 +148,7 @@ files:
148
148
  - lib/sleeper_api/client.rb
149
149
  - lib/sleeper_api/draft.rb
150
150
  - lib/sleeper_api/helpers.rb
151
+ - lib/sleeper_api/json_parser.rb
151
152
  - lib/sleeper_api/league.rb
152
153
  - lib/sleeper_api/user.rb
153
154
  - lib/sleeper_api/version.rb