durable_huggingface_hub 0.2.0 → 0.2.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: 06a07c82312c48d5aa31f0a14bdc6b3c59b623dc5a557408ed8c18e83d43ffcf
4
- data.tar.gz: 32398bb88e28325b3f5a61356b94513f159579380c13b6fbfd5cdb2cb491dabb
3
+ metadata.gz: 9693f9faf5186aeabfc3df8e1f2c615ec208cf9b13abc8f6cfcdcd6c0a563241
4
+ data.tar.gz: 6331e9e4eb9226f8d48ea90e61915d8de1f1beea77d8e78205cda7491127efd7
5
5
  SHA512:
6
- metadata.gz: 2796f5bca481a6910f558435adb79c989252584cbaaf0a552774f29fe78952e715a8a667b5de420dd7670dcbb348982f8132aaf56f68490d0c05a38fdfae8645
7
- data.tar.gz: 4d67ed42b115fc43ec4f5fc9c54d2a52afe5776c16dfe081a3d3eb9e355dec552acf7cb30bfda5590a3ac780477fdafc98d2da888b15d04b5659536ba39421f1
6
+ metadata.gz: e343052c327d7ddc433ef77087d2e3b0745e35ccdddc46cd06fd33cc7123d7f33d262ef7dd757fde32ce7ae3aacf4e2ea56049be68a0b4c5d5d852b485353f92
7
+ data.tar.gz: afeb7ed0f145916d722e5057d09325d0727e9e5bcf611bfbe413d7158d351427d02e1f6aec0f7c9728dc4cdb5eec36227855e83257ce9a053b6029db28a2abfa
data/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ### Fixed
11
+ - `hf_hub_download` now follows 3xx redirects issued by HuggingFace Hub
12
+ resolve URLs by adding the `faraday-follow_redirects` middleware
13
+ (`Utils::HttpClient#build_connection`).
14
+ - Streaming downloads no longer produce files prefixed with a redirect body.
15
+ The temp file is now opened once with `"wb"` (truncate) instead of being
16
+ appended to per-chunk with `"ab"`, which also reduces filesystem overhead.
17
+
8
18
  ## [0.2.0] - 2025-01-24
9
19
 
10
20
  ### Added
data/CONTRIBUTORS.md ADDED
@@ -0,0 +1,8 @@
1
+ # Contributors
2
+
3
+ Thank you to everyone who has helped improve this project.
4
+
5
+ ## Bug Reports & Research
6
+
7
+ **Ori Pekelman ([@OriPekelman](https://github.com/OriPekelman))**
8
+ ... for Q&A and excellent research.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # HuggingFace Hub Ruby
2
2
 
3
- A pure Ruby implementation of the HuggingFace Hub client library. This library provides a complete, production-ready interface to HuggingFace Hub for downloading models, datasets, and managing repositories - with zero Python dependencies.
3
+ A pure Ruby implementation of the HuggingFace Hub client library. This library provides a interface to HuggingFace Hub for downloading models, datasets, and managing repositories - with zero Python dependencies.
4
4
 
5
5
  ## Features
6
6
 
@@ -38,6 +38,7 @@ Gem::Specification.new do |spec|
38
38
 
39
39
  # Runtime dependencies
40
40
  spec.add_dependency "faraday", "~> 2.0"
41
+ spec.add_dependency "faraday-follow_redirects", "~> 0.3"
41
42
  spec.add_dependency "faraday-multipart", "~> 1.0"
42
43
  spec.add_dependency "faraday-retry", "~> 2.0"
43
44
  spec.add_dependency "dry-struct", "~> 1.6"
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+ # Integration test reproducing the exact scenario from issue #1:
3
+ # hf_hub_download fails on every dataset/model file: redirects not followed
4
+ # and streaming concatenates redirect body.
5
+ #
6
+ # Requires network access to huggingface.co.
7
+ # Run from the repo root: bash integration-tests/test_hf_hub_download.sh
8
+
9
+ set -euo pipefail
10
+
11
+ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
12
+
13
+ echo "=== hf_hub_download integration test (issue #1) ==="
14
+
15
+ ruby -I "$REPO_ROOT/lib" - <<'RUBY'
16
+ require "huggingface_hub"
17
+
18
+ path = DurableHuggingfaceHub::FileDownload.hf_hub_download(
19
+ repo_id: "Trelis/tiny-shakespeare",
20
+ filename: "input.txt",
21
+ repo_type: "dataset",
22
+ )
23
+
24
+ abort "FAIL: returned path does not exist: #{path}" unless File.exist?(path)
25
+
26
+ first_line = File.open(path, "rb", &:readline).chomp
27
+ if first_line.start_with?("Temporary Redirect")
28
+ abort "FAIL: file is corrupted — starts with redirect body:\n #{first_line}"
29
+ end
30
+
31
+ puts "PASS: downloaded to #{path}"
32
+ puts " first line: #{first_line[0, 80]}"
33
+ RUBY
@@ -54,15 +54,8 @@ module DurableHuggingfaceHub
54
54
  # Update configuration
55
55
  Configuration.instance.token = token
56
56
 
57
- # Return masked token and user info
58
- masked = Utils::Auth.mask_token(token)
59
- username = user_info.name || "unknown"
60
-
61
- puts "Login successful!"
62
- puts "Token: #{masked}"
63
- puts "User: #{username}"
64
-
65
- masked
57
+ # Return user info for CLI
58
+ { name: user_info.name, type: user_info.type || "user" }
66
59
  end
67
60
 
68
61
  # Logs out of HuggingFace Hub by removing the stored token.
@@ -106,7 +99,7 @@ module DurableHuggingfaceHub
106
99
  token = Utils::Auth.get_token!(token: token)
107
100
 
108
101
  client = Utils::HttpClient.new(token: token)
109
- response = client.get("/api/whoami")
102
+ response = client.get("/api/whoami-v2")
110
103
 
111
104
  Types::User.from_hash(response.body)
112
105
  end
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require "json"
5
+
6
+ module DurableHuggingfaceHub
7
+ # Command-line interface for the Hugging Face Hub
8
+ class CLI
9
+ def initialize(args)
10
+ @args = args
11
+ @command = nil
12
+ @options = {}
13
+ end
14
+
15
+ def run
16
+ parse_args
17
+ execute_command
18
+ rescue StandardError => e
19
+ abort "Error: #{e.message}"
20
+ end
21
+
22
+ private
23
+
24
+ def parse_args
25
+ if @args.empty?
26
+ print_usage
27
+ exit 0
28
+ end
29
+
30
+ @command = @args.shift
31
+
32
+ OptionParser.new do |opts|
33
+ case @command
34
+ when "login"
35
+ parse_login_options(opts)
36
+ when "logout"
37
+ parse_logout_options(opts)
38
+ when "whoami"
39
+ parse_whoami_options(opts)
40
+ when "download"
41
+ parse_download_options(opts)
42
+ when "info"
43
+ parse_info_options(opts)
44
+ when "list"
45
+ parse_list_options(opts)
46
+ when "scan-cache"
47
+ parse_scan_cache_options(opts)
48
+ when "help", "--help", "-h"
49
+ print_usage
50
+ exit 0
51
+ else
52
+ puts "Unknown command: #{@command}"
53
+ print_usage
54
+ exit 1
55
+ end
56
+ end.parse!(@args)
57
+ end
58
+
59
+ def parse_login_options(opts)
60
+ opts.banner = "Usage: dhf login [options]"
61
+ opts.on("-t", "--token TOKEN", "HuggingFace API token") { |v| @options[:token] = v }
62
+ opts.on("--add-to-git-credential", "Add token to git credential store") { @options[:add_to_git_credential] = true }
63
+ end
64
+
65
+ def parse_logout_options(opts)
66
+ opts.banner = "Usage: dhf logout"
67
+ end
68
+
69
+ def parse_whoami_options(opts)
70
+ opts.banner = "Usage: dhf whoami [options]"
71
+ opts.on("-t", "--token TOKEN", "HuggingFace API token") { |v| @options[:token] = v }
72
+ end
73
+
74
+ def parse_download_options(opts)
75
+ opts.banner = "Usage: dhf download REPO_ID [options]"
76
+ opts.on("-f", "--filename FILENAME", "Specific file to download") { |v| @options[:filename] = v }
77
+ opts.on("-r", "--revision REVISION", "Git revision (branch, tag, or commit)") { |v| @options[:revision] = v }
78
+ opts.on("--repo-type TYPE", "Repository type (model, dataset, space)") { |v| @options[:repo_type] = v }
79
+ opts.on("--cache-dir DIR", "Path to cache directory") { |v| @options[:cache_dir] = v }
80
+ opts.on("--snapshot", "Download entire repository snapshot") { @options[:snapshot] = true }
81
+ end
82
+
83
+ def parse_info_options(opts)
84
+ opts.banner = "Usage: dhf info REPO_ID [options]"
85
+ opts.on("--repo-type TYPE", "Repository type (model, dataset, space)") { |v| @options[:repo_type] = v }
86
+ opts.on("-r", "--revision REVISION", "Git revision (branch, tag, or commit)") { |v| @options[:revision] = v }
87
+ end
88
+
89
+ def parse_list_options(opts)
90
+ opts.banner = "Usage: dhf list TYPE [options]"
91
+ opts.on("--author AUTHOR", "Filter by author") { |v| @options[:author] = v }
92
+ opts.on("--search QUERY", "Search query") { |v| @options[:search] = v }
93
+ opts.on("--limit N", Integer, "Limit number of results") { |v| @options[:limit] = v }
94
+ opts.on("--full", "Return full information") { @options[:full] = true }
95
+ end
96
+
97
+ def parse_scan_cache_options(opts)
98
+ opts.banner = "Usage: dhf scan-cache [options]"
99
+ opts.on("--cache-dir DIR", "Path to cache directory") { |v| @options[:cache_dir] = v }
100
+ end
101
+
102
+ def execute_command
103
+ case @command
104
+ when "login"
105
+ cmd_login
106
+ when "logout"
107
+ cmd_logout
108
+ when "whoami"
109
+ cmd_whoami
110
+ when "download"
111
+ cmd_download
112
+ when "info"
113
+ cmd_info
114
+ when "list"
115
+ cmd_list
116
+ when "scan-cache"
117
+ cmd_scan_cache
118
+ end
119
+ end
120
+
121
+ def cmd_login
122
+ result = DurableHuggingfaceHub.login(
123
+ token: @options[:token],
124
+ add_to_git_credential: @options[:add_to_git_credential] || false
125
+ )
126
+ puts "Successfully logged in as: #{result[:name]} (#{result[:type]})"
127
+ end
128
+
129
+ def cmd_logout
130
+ DurableHuggingfaceHub.logout
131
+ puts "Successfully logged out"
132
+ end
133
+
134
+ def cmd_whoami
135
+ result = DurableHuggingfaceHub.whoami(token: @options[:token])
136
+ puts JSON.pretty_generate(result)
137
+ end
138
+
139
+ def cmd_download
140
+ repo_id = @args.shift
141
+ abort "Error: REPO_ID required" unless repo_id
142
+
143
+ if @options[:snapshot]
144
+ path = DurableHuggingfaceHub.snapshot_download(
145
+ repo_id: repo_id,
146
+ revision: @options[:revision],
147
+ repo_type: @options[:repo_type] || "model",
148
+ cache_dir: @options[:cache_dir]
149
+ )
150
+ else
151
+ filename = @options[:filename]
152
+ abort "Error: --filename required for single file download" unless filename
153
+
154
+ path = DurableHuggingfaceHub.hf_hub_download(
155
+ repo_id: repo_id,
156
+ filename: filename,
157
+ revision: @options[:revision],
158
+ repo_type: @options[:repo_type] || "model",
159
+ cache_dir: @options[:cache_dir]
160
+ )
161
+ end
162
+
163
+ puts "Downloaded to: #{path}"
164
+ end
165
+
166
+ def cmd_info
167
+ repo_id = @args.shift
168
+ abort "Error: REPO_ID required" unless repo_id
169
+
170
+ repo_type = @options[:repo_type] || "model"
171
+ info = DurableHuggingfaceHub.repo_info(
172
+ repo_id,
173
+ repo_type: repo_type,
174
+ revision: @options[:revision]
175
+ )
176
+
177
+ puts JSON.pretty_generate(info.to_h)
178
+ end
179
+
180
+ def cmd_list
181
+ type = @args.shift
182
+ abort "Error: TYPE required (models, datasets, or spaces)" unless type
183
+
184
+ results = case type
185
+ when "models"
186
+ DurableHuggingfaceHub.list_models(
187
+ author: @options[:author],
188
+ search: @options[:search],
189
+ limit: @options[:limit],
190
+ full: @options[:full] || false
191
+ )
192
+ when "datasets"
193
+ DurableHuggingfaceHub.list_datasets(
194
+ author: @options[:author],
195
+ search: @options[:search],
196
+ limit: @options[:limit],
197
+ full: @options[:full] || false
198
+ )
199
+ when "spaces"
200
+ DurableHuggingfaceHub.list_spaces(
201
+ author: @options[:author],
202
+ search: @options[:search],
203
+ limit: @options[:limit],
204
+ full: @options[:full] || false
205
+ )
206
+ else
207
+ abort "Error: Unknown type '#{type}'. Must be models, datasets, or spaces"
208
+ end
209
+
210
+ results.each do |item|
211
+ puts JSON.pretty_generate(item.to_h)
212
+ end
213
+ end
214
+
215
+ def cmd_scan_cache
216
+ info = DurableHuggingfaceHub.scan_cache_dir(cache_dir: @options[:cache_dir])
217
+ puts JSON.pretty_generate(info.to_h)
218
+ end
219
+
220
+ def print_usage
221
+ puts <<~USAGE
222
+ Usage: dhf COMMAND [options]
223
+
224
+ Commands:
225
+ login Login to HuggingFace Hub
226
+ logout Logout from HuggingFace Hub
227
+ whoami Display current user information
228
+ download REPO_ID Download a file or repository
229
+ info REPO_ID Get information about a repository
230
+ list TYPE List models, datasets, or spaces
231
+ scan-cache Scan the local cache directory
232
+ help Show this help message
233
+
234
+ Examples:
235
+ dhf login --token hf_...
236
+ dhf whoami
237
+ dhf download bert-base-uncased --filename config.json
238
+ dhf download bert-base-uncased --snapshot
239
+ dhf info openai/whisper-large-v3
240
+ dhf list models --author openai --limit 10
241
+ dhf scan-cache
242
+
243
+ Use 'dhf COMMAND --help' for more information on a specific command.
244
+ USAGE
245
+ end
246
+ end
247
+ end
@@ -463,8 +463,11 @@ module DurableHuggingfaceHub
463
463
  end
464
464
  end
465
465
 
466
+ # Use the redirect-resolved URL so the streaming GET never sees a 3xx.
467
+ download_url = metadata[:resolved_url] || url_path
468
+
466
469
  # Download the file to blob storage
467
- download_to_blob(client, url_path, blob_path, metadata, progress)
470
+ download_to_blob(client, download_url, blob_path, metadata, progress)
468
471
 
469
472
  # Create snapshot symlink
470
473
  ensure_snapshot_link(blob_path, snapshot_path)
@@ -483,12 +486,16 @@ module DurableHuggingfaceHub
483
486
  def self.get_file_metadata(client, url_path)
484
487
  response = client.head(url_path)
485
488
 
486
- # Extract metadata from headers (response is now a Faraday::Response object)
487
489
  headers = response.headers
490
+ # After following redirects, env[:url] holds the final resolved URL.
491
+ # We store it so the subsequent streaming GET can target it directly,
492
+ # bypassing the redirect entirely (on_data fires below middleware).
493
+ resolved_url = response.env[:url].to_s
488
494
  {
489
495
  etag: extract_etag(headers["etag"] || headers["x-linked-etag"]),
490
496
  size: headers["x-linked-size"]&.to_i,
491
- commit_hash: headers["x-repo-commit"]
497
+ commit_hash: headers["x-repo-commit"],
498
+ resolved_url: resolved_url
492
499
  }
493
500
  end
494
501
 
@@ -543,11 +550,12 @@ module DurableHuggingfaceHub
543
550
  end
544
551
 
545
552
  begin
546
- # Download file
547
- response = client.request(:get, url_path) do |req|
548
- req.options.on_data = proc do |chunk, _overall_received_bytes, _env|
549
- File.open(temp_path, "ab") { |f| f.write(chunk) }
550
- progress_tracker.update(chunk.bytesize)
553
+ File.open(temp_path, "wb") do |f|
554
+ client.request(:get, url_path) do |req|
555
+ req.options.on_data = proc do |chunk, _overall_received_bytes, _env|
556
+ f.write(chunk)
557
+ progress_tracker.update(chunk.bytesize)
558
+ end
551
559
  end
552
560
  end
553
561
 
@@ -629,12 +629,24 @@ module DurableHuggingfaceHub
629
629
  DurableHuggingfaceHub::Utils::Validators.validate_repo_id(repo_id)
630
630
  repo_type = DurableHuggingfaceHub::Utils::Validators.validate_repo_type(repo_type)
631
631
 
632
- path = "/api/#{repo_type}s"
632
+ # Extract organization from repo_id if not provided
633
+ repo_parts = repo_id.split("/")
634
+ if organization.nil? && repo_parts.length == 2
635
+ # Get current username to check if it's a personal repo
636
+ current_user = whoami
637
+ potential_org = repo_parts[0]
638
+ # Only set organization if it's not the current user
639
+ organization = potential_org unless potential_org == current_user.name
640
+ end
641
+ repo_name = repo_parts.last
642
+
643
+ path = "/api/repos/create"
633
644
  payload = {
634
- name: repo_id.split("/").last,
635
- private: private,
636
- organization: organization
645
+ name: repo_name,
646
+ private: private
637
647
  }
648
+ payload[:type] = repo_type if repo_type != "model"
649
+ payload[:organization] = organization if organization
638
650
 
639
651
  response = http_client.post(path, body: payload, timeout: timeout)
640
652
  body = response.body.is_a?(String) ? JSON.parse(response.body) : response.body
@@ -96,6 +96,24 @@ module DurableHuggingfaceHub
96
96
  # @return [Integer, nil] Trending score
97
97
  attribute :trending_score, Types::OptionalInteger.default(nil)
98
98
 
99
+ # Transform API response to filter out unknown keys
100
+ def self.from_hash(data)
101
+ transformed = data.dup
102
+
103
+ # Filter out unknown keys to avoid dry-struct errors
104
+ known_keys = [:id, :sha, :last_modified, :tags, :siblings, :private, :gated,
105
+ :disabled, :downloads, :likes, :author, :created_at, :card_data,
106
+ :description, :citation, :downloads_all_time, :paperswithcode_id,
107
+ :trending_score,
108
+ "id", "sha", "last_modified", "tags", "siblings", "private", "gated",
109
+ "disabled", "downloads", "likes", "author", "created_at", "card_data",
110
+ "description", "citation", "downloads_all_time", "paperswithcode_id",
111
+ "trending_score"]
112
+ transformed = transformed.select { |k, _| known_keys.include?(k) }
113
+
114
+ new(transformed)
115
+ end
116
+
99
117
  # Returns the list of file names in the repository.
100
118
  #
101
119
  # @return [Array<String>] File names
@@ -25,6 +25,10 @@ module DurableHuggingfaceHub
25
25
  # @return [String, nil] User type (e.g., "user")
26
26
  attribute :type, Types::OptionalString.default(nil)
27
27
 
28
+ # @!attribute [r] id
29
+ # @return [String, nil] User ID
30
+ attribute :id, Types::OptionalString.default(nil)
31
+
28
32
  # @!attribute [r] name
29
33
  # @return [String] Username
30
34
  attribute :name, Types::String
@@ -49,6 +53,10 @@ module DurableHuggingfaceHub
49
53
  # @return [Array<Hash>, nil] Organizations the user belongs to
50
54
  attribute :orgs, Types::Array.of(Types::Hash).optional.default(nil)
51
55
 
56
+ # @!attribute [r] auth
57
+ # @return [Hash, nil] Authentication information including token details
58
+ attribute :auth, Types::Hash.optional.default(nil)
59
+
52
60
  # Transform isPro from API to is_pro
53
61
  def self.from_hash(data)
54
62
  transformed = data.dup
@@ -64,6 +72,11 @@ module DurableHuggingfaceHub
64
72
  transformed[:avatar_url] = transformed.delete(:avatarUrl)
65
73
  end
66
74
 
75
+ # Filter out unknown keys to avoid dry-struct errors
76
+ known_keys = [:type, :name, :fullname, :email, :avatar_url, :is_pro, :orgs,
77
+ "type", "name", "fullname", "email", "avatar_url", "is_pro", "orgs"]
78
+ transformed = transformed.select { |k, _| known_keys.include?(k) }
79
+
67
80
  new(transformed)
68
81
  end
69
82
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "json"
4
4
  require "faraday"
5
+ require "faraday/follow_redirects"
5
6
  require "faraday/retry"
6
7
 
7
8
  require_relative "../configuration"
@@ -173,6 +174,9 @@ module DurableHuggingfaceHub
173
174
  conn.response :logger, @logger, { headers: true, bodies: false }
174
175
  end
175
176
 
177
+ # Follow 3xx redirects (HuggingFace Hub issues 307s for file resolve URLs)
178
+ conn.response :follow_redirects, limit: 5
179
+
176
180
  # Retry middleware with exponential backoff
177
181
  conn.request :retry,
178
182
  max: 3,
@@ -4,5 +4,5 @@ require "rubygems/version"
4
4
 
5
5
  module DurableHuggingfaceHub
6
6
  # Current version of the HuggingFace Hub Ruby client library
7
- VERSION = "0.2.0"
7
+ VERSION = "0.2.1"
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: durable_huggingface_hub
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Berube
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday-follow_redirects
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.3'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.3'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: faraday-multipart
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -274,14 +288,17 @@ files:
274
288
  - ".editorconfig"
275
289
  - ".rubocop.yml"
276
290
  - CHANGELOG.md
291
+ - CONTRIBUTORS.md
277
292
  - README.md
278
293
  - Rakefile
279
294
  - devenv.lock
280
295
  - devenv.nix
281
296
  - devenv.yaml
282
297
  - huggingface_hub.gemspec
298
+ - integration-tests/test_hf_hub_download.sh
283
299
  - lib/durable_huggingface_hub/authentication.rb
284
300
  - lib/durable_huggingface_hub/cache.rb
301
+ - lib/durable_huggingface_hub/cli.rb
285
302
  - lib/durable_huggingface_hub/configuration.rb
286
303
  - lib/durable_huggingface_hub/constants.rb
287
304
  - lib/durable_huggingface_hub/errors.rb
@@ -328,7 +345,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
328
345
  - !ruby/object:Gem::Version
329
346
  version: '0'
330
347
  requirements: []
331
- rubygems_version: 3.6.9
348
+ rubygems_version: 3.7.2
332
349
  specification_version: 4
333
350
  summary: Pure Ruby client for HuggingFace Hub
334
351
  test_files: []