sleeper_api 1.0.0 → 1.1.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 +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/CHANGELOG.md +26 -0
- data/CLAUDE.md +63 -0
- data/README.md +2 -0
- data/lib/sleeper_api/client.rb +1 -1
- data/lib/sleeper_api/league.rb +24 -8
- data/lib/sleeper_api/user.rb +2 -1
- data/lib/sleeper_api/version.rb +1 -1
- metadata +4 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e64262b5b1ec981daba5a470cdaed3857b5b334b4b5b974b36369bb71d10fe6
|
|
4
|
+
data.tar.gz: 6e292f1ff2de695f2385736316d1d0329373cdfb8d944d399e42ffcda7014d2d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fcad44e534d0f99daa36897d56b228e432e5374d9e829915fed7cc71612588b9576a96147cedb07cc658cd0a590769161ccd74ef82f48ed7d1b0c2d7dc210e88
|
|
7
|
+
data.tar.gz: ca0a00e724c6b35182add239a7a80c9b8129c3a1af2805f216f21b50971842fa968a1c902ab97686819c676390ef89e3fa0fb4635013d9f3f8f7c5aec565d7e6
|
data/.github/workflows/ci.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
## [1.1.0] - 2026-08-22
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- `League#rosters` truncated every score. Sleeper splits a score across two integer fields — `fpts: 1617` with `fpts_decimal: 78` is 1617.78 — and `total_points` read `fpts` alone. The loss was invisible because what remained was still a plausible score, and no fixture carried a `fpts_decimal` to catch it. **This changes `total_points` from an Integer to a Float** for any roster whose score has a fractional part.
|
|
6
|
+
- `League#rosters` returned `co_owners: nil` for every roster, always. It read `roster["co_owner"]`, singular; no Sleeper payload has ever contained that key. Now reads the plural spelling, and still yields `nil` when the field is absent — Sleeper's documented roster object does not include co-owners, though the published docs are partial (the league `settings` object is rendered only as `{ settings object }`).
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- `League#rosters` now returns `points_against`, combining `fpts_against` with `fpts_against_decimal` the same way. Previously reachable only by digging into the raw `settings` hash that passes through wholesale.
|
|
11
|
+
|
|
12
|
+
## [1.0.1] - 2026-08-12
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- `User#leagues` returned `nil` on every call after the first, because the memoization guard was the method's return value
|
|
17
|
+
- `League#users` raised `NoMethodError` when any league member had no `metadata` (Sleeper omits it for users who never set a team name), which also broke `#playoff_bracket` and `#toilet_bowl`
|
|
18
|
+
- `Client#make_request` raised `NameError: undefined local variable 'config'` when a retry fired with a logger configured — the retry path was only reachable with logging on, so it never surfaced in tests
|
|
19
|
+
- `League#matchups_by_week` returned a literal `nil` element for any roster with no `matchup_id` (bye weeks / unscheduled rosters)
|
|
20
|
+
- `League#matchups_by_week` raised `NoMethodError` when a matchup roster had no `players` or no `points`
|
|
21
|
+
- `League#users` memoization checked an undefined `@fetch_users` ivar instead of `@league_users`
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- SimpleCov now starts before the library is required. It previously started after, so no lines were instrumented and the `minimum_coverage 90` gate passed vacuously on 0/0 lines. Real coverage at the time was 72%; `lib/` is now at 100%.
|
|
26
|
+
|
|
1
27
|
## [1.0.0] - 2025-01-17
|
|
2
28
|
|
|
3
29
|
### Added
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
`sleeper_api` — a published Ruby gem (rubygems.org, v1.0.0) wrapping [Sleeper's fantasy football API](https://docs.sleeper.com/). Read-only public API: no auth, no API key, no write endpoints.
|
|
8
|
+
|
|
9
|
+
Consumed by the `fantasy-football-manager` project in the sibling directory, which is the primary real-world user.
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bin/setup # install deps
|
|
15
|
+
bundle exec rake ci # rubocop + rspec — this is what CI runs
|
|
16
|
+
bundle exec rspec
|
|
17
|
+
bundle exec rspec spec/sleeper_api/league_spec.rb:42 # single example
|
|
18
|
+
bundle exec rubocop -A # autocorrect
|
|
19
|
+
bin/console # IRB with the gem loaded
|
|
20
|
+
```
|
|
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.
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
Four layers, with a deliberate split between HTTP and modeling:
|
|
27
|
+
|
|
28
|
+
- **`SleeperApi`** (`lib/sleeper_api.rb`) — module-level config + memoized global `SleeperApi.client`. `Configuration` validates `timeout` (10–60) and `retries` (0–5), raising `SleeperApi::Error` outside those bounds.
|
|
29
|
+
- **`Client`** — the only thing that talks HTTP. `include HTTParty` with `base_uri "https://api.sleeper.app/v1"`. Every call funnels through the private `make_request`, which handles retry-on-timeout, logging, and converts non-2xx into `SleeperApi::Error`. It also owns the 24-hour in-memory player cache.
|
|
30
|
+
- **`League` / `User` / `Draft`** — resource objects. Each takes `(id, client)`, fetches eagerly in the constructor, memoizes into ivars, and exposes formatted hashes.
|
|
31
|
+
- **`Helpers`** — mixed into all four. `deep_symbolize_keys` plus `player_details`, which reaches through `@client` — so any class including it must define `@client`.
|
|
32
|
+
|
|
33
|
+
### Conventions that matter
|
|
34
|
+
|
|
35
|
+
**Raw data is string-keyed; formatted output is symbol-keyed.** The `ATTRIBUTES` readers (defined via `define_method` on each class) return raw API values straight off the string-keyed hash. The `format_*` private methods return symbol-keyed hashes. Don't mix the two — inside `format_rosters` you're indexing `roster["starters"]` but returning `starters:`.
|
|
36
|
+
|
|
37
|
+
**`Client` returns `HTTParty::Response`, not Hash.** `make_request` returns the response object, which delegates `[]` and `dig` to `parsed_response`, so it usually behaves like a Hash. `get_players` is the exception — it explicitly stores `.parsed_response`. If you add code that needs a real Hash (e.g. `.merge`, `.transform_keys`), call `.parsed_response` yourself; `fetch_playoff_bracket` already does.
|
|
38
|
+
|
|
39
|
+
**Eager construction.** `League.new` and `User.new` hit the network in the constructor. `League.new(id, client, no_data: true)` skips it — used by `User#rosters` to avoid N redundant league fetches. Be aware `no_data: true` leaves `@league_data` nil, so `format_rosters`' `remaining_faab` falls back to `0 - waiver_budget_used`.
|
|
40
|
+
|
|
41
|
+
**Guard nil defensively.** Sleeper omits fields freely — users without `metadata`, rosters without `players`, matchups without `points`. Real leagues hit these; the spec fixtures often don't. Use `&.dig` rather than `[]` on anything nested off an API response.
|
|
42
|
+
|
|
43
|
+
**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.
|
|
44
|
+
|
|
45
|
+
## Testing
|
|
46
|
+
|
|
47
|
+
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`).
|
|
48
|
+
|
|
49
|
+
`spec_helper.rb` resets `SleeperApi`'s `@client` and `@configuration` ivars before each example, since both are module-level memoized state that would otherwise leak between tests.
|
|
50
|
+
|
|
51
|
+
**SimpleCov must start before `require "sleeper_api"`.** It previously started after, which meant zero lines were instrumented and `minimum_coverage 90` passed vacuously on 0/0. Don't reorder those requires back — it silently disables the gate rather than failing loudly.
|
|
52
|
+
|
|
53
|
+
`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.
|
|
54
|
+
|
|
55
|
+
## Release
|
|
56
|
+
|
|
57
|
+
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.
|
|
58
|
+
|
|
59
|
+
**The API key needs the `push_rubygem` scope.** `gem signin` defaults to `index_rubygems` only — read access — and answering `n` to "Do you want to customise scopes?" produces a key that fails the upload with `This API key cannot perform the specified action on this gem`. Answer `y` and enable `push_rubygem`, or edit the key's scopes at https://rubygems.org/profile/api_keys. Credentials land in `~/.local/share/gem/credentials` (the XDG path, not `~/.gem/credentials`).
|
|
60
|
+
|
|
61
|
+
**`rake release` is not atomic.** It tags and pushes the tag *before* uploading, so a failed upload leaves the tag published and the gem unreleased. Recovering means `gem push pkg/sleeper_api-<version>.gem` on the existing artifact — re-running `rake release` fails on the tag that already exists.
|
|
62
|
+
|
|
63
|
+
Consumers do not get a fix until it is published *and* they bump their lockfile.
|
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# SleeperApi
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/sleeper_api)
|
|
4
|
+
|
|
3
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.
|
|
4
6
|
|
|
5
7
|
## Features
|
data/lib/sleeper_api/client.rb
CHANGED
|
@@ -264,7 +264,7 @@ module SleeperApi
|
|
|
264
264
|
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
265
265
|
retries += 1
|
|
266
266
|
if retries <= @config.retries
|
|
267
|
-
@config.logger&.warn("Retrying #{path} (attempt #{retries}/#{config.retries}) due to #{e}")
|
|
267
|
+
@config.logger&.warn("Retrying #{path} (attempt #{retries}/#{@config.retries}) due to #{e}")
|
|
268
268
|
sleep(1)
|
|
269
269
|
retry
|
|
270
270
|
else
|
data/lib/sleeper_api/league.rb
CHANGED
|
@@ -134,7 +134,7 @@ module SleeperApi
|
|
|
134
134
|
# puts "#{user[:display_name]} #{role}"
|
|
135
135
|
# end
|
|
136
136
|
def users
|
|
137
|
-
fetch_users unless @
|
|
137
|
+
fetch_users unless @league_users
|
|
138
138
|
format_users
|
|
139
139
|
end
|
|
140
140
|
|
|
@@ -281,7 +281,8 @@ module SleeperApi
|
|
|
281
281
|
injured_reserve: roster["reserve"] || [],
|
|
282
282
|
taxi: roster["taxi"] || [],
|
|
283
283
|
bench: (roster["players"] || []) - (roster["starters"] || []) - (roster["reserve"] || []) - (roster["taxi"] || []),
|
|
284
|
-
total_points: roster_settings
|
|
284
|
+
total_points: combined_points(roster_settings, "fpts"),
|
|
285
|
+
points_against: combined_points(roster_settings, "fpts_against"),
|
|
285
286
|
wins: roster_settings&.dig("wins"),
|
|
286
287
|
ties: roster_settings&.dig("ties"),
|
|
287
288
|
losses: roster_settings&.dig("losses"),
|
|
@@ -290,7 +291,7 @@ module SleeperApi
|
|
|
290
291
|
faab_used: roster_settings&.dig("waiver_budget_used"),
|
|
291
292
|
waiver_position: roster_settings&.dig("waiver_position"),
|
|
292
293
|
streak: roster_metadata&.dig("streak"),
|
|
293
|
-
co_owners: roster["
|
|
294
|
+
co_owners: roster["co_owners"],
|
|
294
295
|
keepers: roster["keepers"],
|
|
295
296
|
players_map: roster["player_map"],
|
|
296
297
|
players: roster["players"],
|
|
@@ -300,6 +301,21 @@ module SleeperApi
|
|
|
300
301
|
end
|
|
301
302
|
end
|
|
302
303
|
|
|
304
|
+
# Sleeper splits a score across two integer fields: fpts 1617 with
|
|
305
|
+
# fpts_decimal 78 is 1617.78. Reading fpts alone truncates every score in
|
|
306
|
+
# the league, and the loss is invisible because what remains is still a
|
|
307
|
+
# plausible number.
|
|
308
|
+
#
|
|
309
|
+
# Recombined as (whole * 100 + fraction) / 100.0 rather than
|
|
310
|
+
# whole + fraction / 100.0 — the latter accumulates two rounding steps and
|
|
311
|
+
# lands on 1617.7800000000002.
|
|
312
|
+
def combined_points(settings, key)
|
|
313
|
+
whole = settings&.dig(key)
|
|
314
|
+
return nil if whole.nil?
|
|
315
|
+
|
|
316
|
+
((whole * 100) + (settings["#{key}_decimal"] || 0)) / 100.0
|
|
317
|
+
end
|
|
318
|
+
|
|
303
319
|
def format_matchups(week)
|
|
304
320
|
week_matchups = @matchups[week] || []
|
|
305
321
|
return [] if week_matchups.empty?
|
|
@@ -310,14 +326,14 @@ module SleeperApi
|
|
|
310
326
|
{
|
|
311
327
|
matchup_id: matchup_id,
|
|
312
328
|
rosters: matchup_entries.map do |roster|
|
|
313
|
-
starters = roster["starters"]
|
|
329
|
+
starters = roster["starters"] || []
|
|
314
330
|
|
|
315
|
-
bench = roster["players"] - starters
|
|
331
|
+
bench = (roster["players"] || []) - starters
|
|
316
332
|
{
|
|
317
333
|
roster_id: roster["roster_id"],
|
|
318
334
|
points: roster["points"],
|
|
319
335
|
custom_points: roster["custom_points"],
|
|
320
|
-
total_points: roster["points"] + (roster["custom_points"] || 0),
|
|
336
|
+
total_points: (roster["points"] || 0) + (roster["custom_points"] || 0),
|
|
321
337
|
starters: starters,
|
|
322
338
|
bench: bench,
|
|
323
339
|
starter_points: (starters || []).map do |starter_id|
|
|
@@ -329,7 +345,7 @@ module SleeperApi
|
|
|
329
345
|
}
|
|
330
346
|
end
|
|
331
347
|
}
|
|
332
|
-
end
|
|
348
|
+
end.compact
|
|
333
349
|
end
|
|
334
350
|
|
|
335
351
|
def format_users
|
|
@@ -342,7 +358,7 @@ module SleeperApi
|
|
|
342
358
|
username: user["username"],
|
|
343
359
|
display_name: user["display_name"],
|
|
344
360
|
avatar_id: user["avatar"],
|
|
345
|
-
team_name: user_metadata
|
|
361
|
+
team_name: user_metadata&.dig("team_name"),
|
|
346
362
|
commissioner: user["is_owner"],
|
|
347
363
|
is_bot: user["is_bot"],
|
|
348
364
|
metadata: user_metadata.is_a?(Hash) ? user_metadata.transform_keys(&:to_sym) : user_metadata,
|
data/lib/sleeper_api/user.rb
CHANGED
|
@@ -50,7 +50,8 @@ module SleeperApi
|
|
|
50
50
|
def leagues(season = Time.now.year)
|
|
51
51
|
raise ArgumentError, "season must be a valid year" unless season.is_a?(Integer)
|
|
52
52
|
|
|
53
|
-
fetch_leagues(season)
|
|
53
|
+
fetch_leagues(season)
|
|
54
|
+
@leagues
|
|
54
55
|
end
|
|
55
56
|
|
|
56
57
|
# Get all rosters for this user across all leagues in a season.
|
data/lib/sleeper_api/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sleeper_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Eruity1
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: httparty
|
|
@@ -138,6 +137,7 @@ files:
|
|
|
138
137
|
- ".rubocop.yml"
|
|
139
138
|
- ".rubocop_todo.yml"
|
|
140
139
|
- CHANGELOG.md
|
|
140
|
+
- CLAUDE.md
|
|
141
141
|
- Gemfile
|
|
142
142
|
- LICENSE.txt
|
|
143
143
|
- README.md
|
|
@@ -162,7 +162,6 @@ metadata:
|
|
|
162
162
|
source_code_uri: https://github.com/eruity1/sleeper_api
|
|
163
163
|
changelog_uri: https://github.com/eruity1/sleeper_api/blob/main/CHANGELOG.md
|
|
164
164
|
documentation_uri: https://github.com/eruity1/sleeper_api/blob/main/README.md
|
|
165
|
-
post_install_message:
|
|
166
165
|
rdoc_options: []
|
|
167
166
|
require_paths:
|
|
168
167
|
- lib
|
|
@@ -177,8 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
177
176
|
- !ruby/object:Gem::Version
|
|
178
177
|
version: '0'
|
|
179
178
|
requirements: []
|
|
180
|
-
rubygems_version: 3.
|
|
181
|
-
signing_key:
|
|
179
|
+
rubygems_version: 3.6.7
|
|
182
180
|
specification_version: 4
|
|
183
181
|
summary: Comprehensive Ruby wrapper for the Sleeper's fantasy sports API
|
|
184
182
|
test_files: []
|