sleeper_api 1.0.0 → 1.0.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: 7343fb5de26fd02795df2ef4c91322eec909026cb792e52fcb24299c83cff901
4
- data.tar.gz: 5036b7dfa94981abe61d3204e6a1d265164e846961c1450eb92b03614a942ec9
3
+ metadata.gz: 92a31d66b4e0b110e6daf96f0689fdcc626b341bc7550ac8f1d982eaae8796bf
4
+ data.tar.gz: 14b9b6d090c28a8087edb9a45d61eadeae4c8fd6027995b206456cc45c95c9f5
5
5
  SHA512:
6
- metadata.gz: ca60a9cfad9925c86afd87b1f2e219405dad22d80849130ee9b70c7b71321903e9cc27cb539ed9feb4b900b73b42cfcd6c0a284e3b8009b9578a900f7888f052
7
- data.tar.gz: 5559a1bffa0cbe150be4aff3789cc4ecc298e568c9c5e3f518293921efd0965b50ed6c440a56c98683ec62fed12707af16f51cef30c849ead3b34e76beeca8c2
6
+ metadata.gz: d24b6c42b978e3a14c70ff6c9db5c08f6747ff73c64206ccb8a7374741b56e6f034a9451a03f4d93670743414a5c30011c311a709a02743219f593e9cf8299e1
7
+ data.tar.gz: 16e2f0ab8bb4fa2053cc01f0e35b4a3a6e2bde9d8a55173b8781329378323045da0c826711a33c13117ccb8e17e58b04572c34591d9e123235f21d2378ee51d9
@@ -12,7 +12,7 @@ jobs:
12
12
 
13
13
  strategy:
14
14
  matrix:
15
- ruby-version: [3.2]
15
+ ruby-version: ["3.2", "3.4"]
16
16
 
17
17
  steps:
18
18
  - uses: actions/checkout@v4
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [1.0.1] - 2026-08-12
2
+
3
+ ### Fixed
4
+
5
+ - `User#leagues` returned `nil` on every call after the first, because the memoization guard was the method's return value
6
+ - `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`
7
+ - `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
8
+ - `League#matchups_by_week` returned a literal `nil` element for any roster with no `matchup_id` (bye weeks / unscheduled rosters)
9
+ - `League#matchups_by_week` raised `NoMethodError` when a matchup roster had no `players` or no `points`
10
+ - `League#users` memoization checked an undefined `@fetch_users` ivar instead of `@league_users`
11
+
12
+ ### Changed
13
+
14
+ - 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%.
15
+
1
16
  ## [1.0.0] - 2025-01-17
2
17
 
3
18
  ### Added
data/CLAUDE.md ADDED
@@ -0,0 +1,59 @@
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`. `bundle exec rake release` tags and pushes to rubygems. Update `CHANGELOG.md` first.
58
+
59
+ v1.0.0 is published with six bugs that are fixed in the working tree but not yet released — see the unreleased section of `CHANGELOG.md`. Consumers on the published gem still hit them.
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # SleeperApi
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/sleeper_api.svg)](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
@@ -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
@@ -134,7 +134,7 @@ module SleeperApi
134
134
  # puts "#{user[:display_name]} #{role}"
135
135
  # end
136
136
  def users
137
- fetch_users unless @fetch_users
137
+ fetch_users unless @league_users
138
138
  format_users
139
139
  end
140
140
 
@@ -310,14 +310,14 @@ module SleeperApi
310
310
  {
311
311
  matchup_id: matchup_id,
312
312
  rosters: matchup_entries.map do |roster|
313
- starters = roster["starters"]
313
+ starters = roster["starters"] || []
314
314
 
315
- bench = roster["players"] - starters
315
+ bench = (roster["players"] || []) - starters
316
316
  {
317
317
  roster_id: roster["roster_id"],
318
318
  points: roster["points"],
319
319
  custom_points: roster["custom_points"],
320
- total_points: roster["points"] + (roster["custom_points"] || 0),
320
+ total_points: (roster["points"] || 0) + (roster["custom_points"] || 0),
321
321
  starters: starters,
322
322
  bench: bench,
323
323
  starter_points: (starters || []).map do |starter_id|
@@ -329,7 +329,7 @@ module SleeperApi
329
329
  }
330
330
  end
331
331
  }
332
- end
332
+ end.compact
333
333
  end
334
334
 
335
335
  def format_users
@@ -342,7 +342,7 @@ module SleeperApi
342
342
  username: user["username"],
343
343
  display_name: user["display_name"],
344
344
  avatar_id: user["avatar"],
345
- team_name: user_metadata["team_name"],
345
+ team_name: user_metadata&.dig("team_name"),
346
346
  commissioner: user["is_owner"],
347
347
  is_bot: user["is_bot"],
348
348
  metadata: user_metadata.is_a?(Hash) ? user_metadata.transform_keys(&:to_sym) : user_metadata,
@@ -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) unless @leagues
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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SleeperApi
4
- VERSION = "1.0.0"
4
+ VERSION = "1.0.1"
5
5
  end
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.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eruity1
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-09-23 00:00:00.000000000 Z
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.4.10
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: []