durable_huggingface_hub 0.2.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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +29 -0
  3. data/.rubocop.yml +108 -0
  4. data/CHANGELOG.md +127 -0
  5. data/README.md +547 -0
  6. data/Rakefile +106 -0
  7. data/devenv.lock +171 -0
  8. data/devenv.nix +15 -0
  9. data/devenv.yaml +8 -0
  10. data/huggingface_hub.gemspec +63 -0
  11. data/lib/durable_huggingface_hub/authentication.rb +245 -0
  12. data/lib/durable_huggingface_hub/cache.rb +508 -0
  13. data/lib/durable_huggingface_hub/configuration.rb +191 -0
  14. data/lib/durable_huggingface_hub/constants.rb +145 -0
  15. data/lib/durable_huggingface_hub/errors.rb +412 -0
  16. data/lib/durable_huggingface_hub/file_download.rb +831 -0
  17. data/lib/durable_huggingface_hub/hf_api.rb +1278 -0
  18. data/lib/durable_huggingface_hub/repo_card.rb +430 -0
  19. data/lib/durable_huggingface_hub/types/cache_info.rb +298 -0
  20. data/lib/durable_huggingface_hub/types/commit_info.rb +149 -0
  21. data/lib/durable_huggingface_hub/types/dataset_info.rb +158 -0
  22. data/lib/durable_huggingface_hub/types/model_info.rb +154 -0
  23. data/lib/durable_huggingface_hub/types/space_info.rb +158 -0
  24. data/lib/durable_huggingface_hub/types/user.rb +179 -0
  25. data/lib/durable_huggingface_hub/types.rb +205 -0
  26. data/lib/durable_huggingface_hub/utils/auth.rb +174 -0
  27. data/lib/durable_huggingface_hub/utils/headers.rb +220 -0
  28. data/lib/durable_huggingface_hub/utils/http.rb +329 -0
  29. data/lib/durable_huggingface_hub/utils/paths.rb +230 -0
  30. data/lib/durable_huggingface_hub/utils/progress.rb +217 -0
  31. data/lib/durable_huggingface_hub/utils/retry.rb +165 -0
  32. data/lib/durable_huggingface_hub/utils/validators.rb +236 -0
  33. data/lib/durable_huggingface_hub/version.rb +8 -0
  34. data/lib/huggingface_hub.rb +205 -0
  35. metadata +334 -0
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../types"
4
+
5
+ module DurableHuggingfaceHub
6
+ module Types
7
+ # Information about a model repository on HuggingFace Hub.
8
+ #
9
+ # This structure represents metadata about a model, including its ID,
10
+ # tags, files, statistics, and configuration.
11
+ #
12
+ # @example Creating a ModelInfo from API response
13
+ # model_info = ModelInfo.from_hash({
14
+ # "id" => "bert-base-uncased",
15
+ # "sha" => "a1b2c3d4...",
16
+ # "tags" => ["transformers", "pytorch"],
17
+ # "downloads" => 1000000,
18
+ # "likes" => 500
19
+ # })
20
+ #
21
+ # @example Accessing model information
22
+ # model_info.id # => "bert-base-uncased"
23
+ # model_info.tags # => ["transformers", "pytorch"]
24
+ # model_info.downloads # => 1000000
25
+ class ModelInfo < DurableHuggingfaceHub::Struct
26
+ include Loadable
27
+
28
+ # @!attribute [r] id
29
+ # @return [String] Model repository ID (e.g., "bert-base-uncased")
30
+ attribute :id, Types::RepoId
31
+
32
+ # @!attribute [r] sha
33
+ # @return [String, nil] Git commit SHA of the current revision
34
+ attribute :sha, Types::OptionalString.default(nil)
35
+
36
+ # @!attribute [r] last_modified
37
+ # @return [Time, nil] Timestamp of last modification
38
+ attribute :last_modified, Types::OptionalTimestamp.default(nil)
39
+
40
+ # @!attribute [r] tags
41
+ # @return [Array<String>] Tags associated with the model
42
+ attribute :tags, Types::StringArray.default([].freeze)
43
+
44
+ # @!attribute [r] pipeline_tag
45
+ # @return [String, nil] Primary pipeline/task tag (e.g., "text-classification")
46
+ attribute :pipeline_tag, Types::OptionalString.default(nil)
47
+
48
+ # @!attribute [r] siblings
49
+ # @return [Array<Hash>, nil] List of files in the repository
50
+ attribute :siblings, Types::OptionalFileSiblings.default(nil)
51
+
52
+ # @!attribute [r] private
53
+ # @return [Boolean, nil] Whether the repository is private
54
+ attribute :private, Types::OptionalBool.default(nil)
55
+
56
+ # @!attribute [r] gated
57
+ # @return [Boolean, String, nil] Gated access status (false, "auto", "manual")
58
+ attribute :gated, Types::OptionalGated.default(nil)
59
+
60
+ # @!attribute [r] disabled
61
+ # @return [Boolean, nil] Whether the repository is disabled
62
+ attribute :disabled, Types::OptionalBool.default(nil)
63
+
64
+ # @!attribute [r] downloads
65
+ # @return [Integer, nil] Total number of downloads
66
+ attribute :downloads, Types::OptionalInteger.default(nil)
67
+
68
+ # @!attribute [r] likes
69
+ # @return [Integer, nil] Number of likes/stars
70
+ attribute :likes, Types::OptionalInteger.default(nil)
71
+
72
+ # @!attribute [r] library_name
73
+ # @return [String, nil] Primary library (e.g., "transformers", "diffusers")
74
+ attribute :library_name, Types::OptionalString.default(nil)
75
+
76
+ # @!attribute [r] config
77
+ # @return [Hash, nil] Model configuration data
78
+ attribute :config, Types::OptionalHash.default(nil)
79
+
80
+ # @!attribute [r] author
81
+ # @return [String, nil] Author/organization name
82
+ attribute :author, Types::OptionalString.default(nil)
83
+
84
+ # @!attribute [r] created_at
85
+ # @return [Time, nil] Repository creation timestamp
86
+ attribute :created_at, Types::OptionalTimestamp.default(nil)
87
+
88
+ # @!attribute [r] card_data
89
+ # @return [Hash, nil] Model card metadata
90
+ attribute :card_data, Types::OptionalHash.default(nil)
91
+
92
+ # Returns the list of file names in the repository.
93
+ #
94
+ # @return [Array<String>] File names
95
+ def file_names
96
+ return [] if siblings.nil?
97
+
98
+ siblings.map { |s| s[:rfilename] || s["rfilename"] }.compact
99
+ end
100
+
101
+ # Checks if the model has a specific tag.
102
+ #
103
+ # @param tag [String] Tag to check for
104
+ # @return [Boolean] True if the tag is present
105
+ def has_tag?(tag)
106
+ tags.include?(tag)
107
+ end
108
+
109
+ # Checks if the repository is public.
110
+ #
111
+ # @return [Boolean] True if public (not private)
112
+ def public?
113
+ !private
114
+ end
115
+
116
+ # Checks if the repository is gated.
117
+ #
118
+ # @return [Boolean] True if gated
119
+ def gated?
120
+ case gated
121
+ when true, "auto", "manual"
122
+ true
123
+ else
124
+ false
125
+ end
126
+ end
127
+
128
+ # Checks if the repository is disabled.
129
+ #
130
+ # @return [Boolean] True if disabled
131
+ def disabled?
132
+ disabled == true
133
+ end
134
+
135
+ # Returns a short description of the model.
136
+ #
137
+ # @return [String] Description string
138
+ def to_s
139
+ parts = [id]
140
+ parts << "(#{pipeline_tag})" if pipeline_tag
141
+ parts << "[#{library_name}]" if library_name
142
+ parts.join(" ")
143
+ end
144
+
145
+ # Returns a detailed inspection string.
146
+ #
147
+ # @return [String] Inspection string
148
+ def inspect
149
+ "#<#{self.class.name} id=#{id.inspect} sha=#{sha&.[](0, 7).inspect} " \
150
+ "tags=#{tags.size} files=#{siblings&.size || 0}>"
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../types"
4
+
5
+ module DurableHuggingfaceHub
6
+ module Types
7
+ # Information about a Space repository on HuggingFace Hub.
8
+ #
9
+ # Spaces are interactive ML demos and applications hosted on HuggingFace Hub.
10
+ #
11
+ # @example Creating a SpaceInfo from API response
12
+ # space_info = SpaceInfo.from_hash({
13
+ # "id" => "gradio/hello-world",
14
+ # "sdk" => "gradio",
15
+ # "tags" => ["gradio", "demo"],
16
+ # "likes" => 100
17
+ # })
18
+ #
19
+ # @example Accessing space information
20
+ # space_info.id # => "gradio/hello-world"
21
+ # space_info.sdk # => "gradio"
22
+ # space_info.runtime # => {"stage" => "RUNNING"}
23
+ class SpaceInfo < DurableHuggingfaceHub::Struct
24
+ include Loadable
25
+
26
+ # @!attribute [r] id
27
+ # @return [String] Space repository ID
28
+ attribute :id, Types::RepoId
29
+
30
+ # @!attribute [r] sha
31
+ # @return [String, nil] Git commit SHA of the current revision
32
+ attribute :sha, Types::OptionalString.default(nil)
33
+
34
+ # @!attribute [r] last_modified
35
+ # @return [Time, nil] Timestamp of last modification
36
+ attribute :last_modified, Types::OptionalTimestamp.default(nil)
37
+
38
+ # @!attribute [r] tags
39
+ # @return [Array<String>] Tags associated with the space
40
+ attribute :tags, Types::StringArray.default([].freeze)
41
+
42
+ # @!attribute [r] siblings
43
+ # @return [Array<Hash>, nil] List of files in the repository
44
+ attribute :siblings, Types::OptionalFileSiblings.default(nil)
45
+
46
+ # @!attribute [r] private
47
+ # @return [Boolean, nil] Whether the repository is private
48
+ attribute :private, Types::OptionalBool.default(nil)
49
+
50
+ # @!attribute [r] gated
51
+ # @return [Boolean, String, nil] Gated access status
52
+ attribute :gated, Types::OptionalGated.default(nil)
53
+
54
+ # @!attribute [r] disabled
55
+ # @return [Boolean, nil] Whether the repository is disabled
56
+ attribute :disabled, Types::OptionalBool.default(nil)
57
+
58
+ # @!attribute [r] likes
59
+ # @return [Integer, nil] Number of likes/stars
60
+ attribute :likes, Types::OptionalInteger.default(nil)
61
+
62
+ # @!attribute [r] author
63
+ # @return [String, nil] Author/organization name
64
+ attribute :author, Types::OptionalString.default(nil)
65
+
66
+ # @!attribute [r] created_at
67
+ # @return [Time, nil] Repository creation timestamp
68
+ attribute :created_at, Types::OptionalTimestamp.default(nil)
69
+
70
+ # @!attribute [r] sdk
71
+ # @return [String, nil] SDK used (e.g., "gradio", "streamlit", "static")
72
+ attribute :sdk, Types::OptionalString.default(nil)
73
+
74
+ # @!attribute [r] runtime
75
+ # @return [Hash, nil] Runtime information (stage, hardware, etc.)
76
+ attribute :runtime, Types::OptionalHash.default(nil)
77
+
78
+ # @!attribute [r] card_data
79
+ # @return [Hash, nil] Space card metadata
80
+ attribute :card_data, Types::OptionalHash.default(nil)
81
+
82
+ # Returns the list of file names in the repository.
83
+ #
84
+ # @return [Array<String>] File names
85
+ def file_names
86
+ return [] if siblings.nil?
87
+
88
+ siblings.map { |s| s[:rfilename] || s["rfilename"] }.compact
89
+ end
90
+
91
+ # Checks if the space has a specific tag.
92
+ #
93
+ # @param tag [String] Tag to check for
94
+ # @return [Boolean] True if the tag is present
95
+ def has_tag?(tag)
96
+ tags.include?(tag)
97
+ end
98
+
99
+ # Checks if the repository is public.
100
+ #
101
+ # @return [Boolean] True if public
102
+ def public?
103
+ !private
104
+ end
105
+
106
+ # Checks if the repository is gated.
107
+ #
108
+ # @return [Boolean] True if gated
109
+ def gated?
110
+ case gated
111
+ when true, "auto", "manual"
112
+ true
113
+ else
114
+ false
115
+ end
116
+ end
117
+
118
+ # Checks if the repository is disabled.
119
+ #
120
+ # @return [Boolean] True if disabled
121
+ def disabled?
122
+ disabled == true
123
+ end
124
+
125
+ # Returns the runtime stage if available.
126
+ #
127
+ # @return [String, nil] Runtime stage (e.g., "RUNNING", "STOPPED")
128
+ def runtime_stage
129
+ runtime&.dig("stage") || runtime&.dig(:stage)
130
+ end
131
+
132
+ # Checks if the space is currently running.
133
+ #
134
+ # @return [Boolean] True if running
135
+ def running?
136
+ runtime_stage == "RUNNING"
137
+ end
138
+
139
+ # Returns a short description of the space.
140
+ #
141
+ # @return [String] Description string
142
+ def to_s
143
+ parts = [id]
144
+ parts << "(#{sdk})" if sdk
145
+ parts << "[#{runtime_stage}]" if runtime_stage
146
+ parts.join(" ")
147
+ end
148
+
149
+ # Returns a detailed inspection string.
150
+ #
151
+ # @return [String] Inspection string
152
+ def inspect
153
+ "#<#{self.class.name} id=#{id.inspect} sdk=#{sdk.inspect} " \
154
+ "stage=#{runtime_stage.inspect}>"
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../types"
4
+
5
+ module DurableHuggingfaceHub
6
+ module Types
7
+ # Information about a HuggingFace Hub user.
8
+ #
9
+ # @example Creating a User from API response
10
+ # user = User.from_hash({
11
+ # "name" => "john_doe",
12
+ # "fullname" => "John Doe",
13
+ # "email" => "john@example.com",
14
+ # "isPro" => false
15
+ # })
16
+ #
17
+ # @example Accessing user information
18
+ # user.name # => "john_doe"
19
+ # user.fullname # => "John Doe"
20
+ # user.pro? # => false
21
+ class User < DurableHuggingfaceHub::Struct
22
+ include Loadable
23
+
24
+ # @!attribute [r] type
25
+ # @return [String, nil] User type (e.g., "user")
26
+ attribute :type, Types::OptionalString.default(nil)
27
+
28
+ # @!attribute [r] name
29
+ # @return [String] Username
30
+ attribute :name, Types::String
31
+
32
+ # @!attribute [r] fullname
33
+ # @return [String, nil] Full display name
34
+ attribute :fullname, Types::OptionalString.default(nil)
35
+
36
+ # @!attribute [r] email
37
+ # @return [String, nil] Email address
38
+ attribute :email, Types::OptionalString.default(nil)
39
+
40
+ # @!attribute [r] avatar_url
41
+ # @return [String, nil] Avatar image URL
42
+ attribute :avatar_url, Types::OptionalString.default(nil)
43
+
44
+ # @!attribute [r] is_pro
45
+ # @return [Boolean, nil] Whether user has Pro subscription
46
+ attribute :is_pro, Types::OptionalBool.default(nil)
47
+
48
+ # @!attribute [r] orgs
49
+ # @return [Array<Hash>, nil] Organizations the user belongs to
50
+ attribute :orgs, Types::Array.of(Types::Hash).optional.default(nil)
51
+
52
+ # Transform isPro from API to is_pro
53
+ def self.from_hash(data)
54
+ transformed = data.dup
55
+ if transformed.key?("isPro") && !transformed.key?("is_pro")
56
+ transformed["is_pro"] = transformed.delete("isPro")
57
+ elsif transformed.key?(:isPro) && !transformed.key?(:is_pro)
58
+ transformed[:is_pro] = transformed.delete(:isPro)
59
+ end
60
+
61
+ if transformed.key?("avatarUrl") && !transformed.key?("avatar_url")
62
+ transformed["avatar_url"] = transformed.delete("avatarUrl")
63
+ elsif transformed.key?(:avatarUrl) && !transformed.key?(:avatar_url)
64
+ transformed[:avatar_url] = transformed.delete(:avatarUrl)
65
+ end
66
+
67
+ new(transformed)
68
+ end
69
+
70
+ # Checks if the user has a Pro subscription.
71
+ #
72
+ # @return [Boolean] True if Pro user
73
+ def pro?
74
+ is_pro == true
75
+ end
76
+
77
+ # Returns the display name (fullname if available, otherwise username).
78
+ #
79
+ # @return [String] Display name
80
+ def display_name
81
+ fullname || name
82
+ end
83
+
84
+ # Returns a short description of the user.
85
+ #
86
+ # @return [String] Description string
87
+ def to_s
88
+ display_name
89
+ end
90
+
91
+ # Returns a detailed inspection string.
92
+ #
93
+ # @return [String] Inspection string
94
+ def inspect
95
+ "#<#{self.class.name} name=#{name.inspect} fullname=#{fullname.inspect} pro=#{pro?}>"
96
+ end
97
+ end
98
+
99
+ # Information about a HuggingFace Hub organization.
100
+ #
101
+ # @example Creating an Organization from API response
102
+ # org = Organization.from_hash({
103
+ # "name" => "huggingface",
104
+ # "fullname" => "Hugging Face",
105
+ # "isEnterprise" => true
106
+ # })
107
+ #
108
+ # @example Accessing organization information
109
+ # org.name # => "huggingface"
110
+ # org.fullname # => "Hugging Face"
111
+ # org.enterprise? # => true
112
+ class Organization < DurableHuggingfaceHub::Struct
113
+ include Loadable
114
+
115
+ # @!attribute [r] name
116
+ # @return [String] Organization name/ID
117
+ attribute :name, Types::String
118
+
119
+ # @!attribute [r] fullname
120
+ # @return [String, nil] Full display name
121
+ attribute :fullname, Types::OptionalString.default(nil)
122
+
123
+ # @!attribute [r] avatar_url
124
+ # @return [String, nil] Organization avatar URL
125
+ attribute :avatar_url, Types::OptionalString.default(nil)
126
+
127
+ # @!attribute [r] is_enterprise
128
+ # @return [Boolean, nil] Whether this is an Enterprise organization
129
+ attribute :is_enterprise, Types::OptionalBool.default(nil)
130
+
131
+ # Transform isEnterprise from API to is_enterprise
132
+ def self.from_hash(data)
133
+ transformed = data.dup
134
+ if transformed.key?("isEnterprise") && !transformed.key?("is_enterprise")
135
+ transformed["is_enterprise"] = transformed.delete("isEnterprise")
136
+ elsif transformed.key?(:isEnterprise) && !transformed.key?(:is_enterprise)
137
+ transformed[:is_enterprise] = transformed.delete(:isEnterprise)
138
+ end
139
+
140
+ if transformed.key?("avatarUrl") && !transformed.key?("avatar_url")
141
+ transformed["avatar_url"] = transformed.delete("avatarUrl")
142
+ elsif transformed.key?(:avatarUrl) && !transformed.key?(:avatar_url)
143
+ transformed[:avatar_url] = transformed.delete(:avatarUrl)
144
+ end
145
+
146
+ new(transformed)
147
+ end
148
+
149
+ # Checks if this is an Enterprise organization.
150
+ #
151
+ # @return [Boolean] True if Enterprise
152
+ def enterprise?
153
+ is_enterprise == true
154
+ end
155
+
156
+ # Returns the display name (fullname if available, otherwise name).
157
+ #
158
+ # @return [String] Display name
159
+ def display_name
160
+ fullname || name
161
+ end
162
+
163
+ # Returns a short description of the organization.
164
+ #
165
+ # @return [String] Description string
166
+ def to_s
167
+ display_name
168
+ end
169
+
170
+ # Returns a detailed inspection string.
171
+ #
172
+ # @return [String] Inspection string
173
+ def inspect
174
+ "#<#{self.class.name} name=#{name.inspect} fullname=#{fullname.inspect} " \
175
+ "enterprise=#{enterprise?}>"
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-types"
4
+ require "dry-struct"
5
+
6
+ module DurableHuggingfaceHub
7
+ # Type definitions and custom types for the HuggingFace Hub client.
8
+ #
9
+ # This module sets up the type system using dry-types and defines
10
+ # custom types for domain-specific validation and coercion.
11
+ #
12
+ # @example Using custom types
13
+ # Types::RepoId["organization/model-name"]
14
+ # Types::RepoType["model"]
15
+ # Types::Revision["main"]
16
+ module Types
17
+ include Dry.Types()
18
+
19
+ # Repository ID type with validation.
20
+ #
21
+ # Valid format: "organization/repository-name" or "username/repository-name"
22
+ # May also be just "repository-name" for models in the user's namespace.
23
+ #
24
+ # @example
25
+ # Types::RepoId["bert-base-uncased"]
26
+ # Types::RepoId["huggingface/transformers"]
27
+ RepoId = String.constrained(min_size: 1)
28
+
29
+ # Repository type enumeration.
30
+ #
31
+ # Valid values: "model", "dataset", "space"
32
+ #
33
+ # @example
34
+ # Types::RepoType["model"]
35
+ # Types::RepoType["dataset"]
36
+ RepoType = String.enum("model", "dataset", "space")
37
+
38
+ # Revision type (branch, tag, or commit SHA).
39
+ #
40
+ # Can be a branch name (e.g., "main"), tag (e.g., "v1.0.0"),
41
+ # or Git commit SHA (40 hexadecimal characters).
42
+ #
43
+ # @example
44
+ # Types::Revision["main"]
45
+ # Types::Revision["v1.0.0"]
46
+ # Types::Revision["a1b2c3d4e5f6..."]
47
+ Revision = String.constrained(min_size: 1)
48
+
49
+ # Strict boolean type.
50
+ StrictBool = Strict::Bool
51
+
52
+ # Optional string type.
53
+ OptionalString = String.optional
54
+
55
+ # Optional integer type.
56
+ OptionalInteger = Integer.optional
57
+
58
+ # Optional boolean type.
59
+ OptionalBool = Bool.optional
60
+
61
+ # Array of strings type.
62
+ StringArray = Array.of(String)
63
+
64
+ # Optional array of strings type.
65
+ OptionalStringArray = Array.of(String).optional
66
+
67
+ # Hash with string keys type.
68
+ StringHash = Hash.map(String, Any)
69
+
70
+ # Optional hash type.
71
+ OptionalHash = Hash.optional
72
+
73
+ # Timestamp type (Time, DateTime, or ISO 8601 string).
74
+ Timestamp = Time | DateTime | String
75
+
76
+ # Optional timestamp type.
77
+ OptionalTimestamp = Timestamp.optional
78
+
79
+ # File siblings type (array of hashes representing files in a repository).
80
+ FileSiblings = Array.of(Hash)
81
+
82
+ # Optional file siblings type.
83
+ OptionalFileSiblings = FileSiblings.optional
84
+
85
+ # Gated access type (true, false, "auto", "manual").
86
+ GatedType = Bool | String.enum("auto", "manual")
87
+
88
+ # Optional gated type.
89
+ OptionalGated = GatedType.optional
90
+
91
+ # URL type for web addresses.
92
+ #
93
+ # Accepts any string that looks like a URL.
94
+ #
95
+ # @example
96
+ # Types::URL["https://example.com"]
97
+ # Types::URL["http://example.com/path"]
98
+ URL = String.constrained(format: URI::DEFAULT_PARSER.make_regexp)
99
+
100
+ # Optional URL type.
101
+ OptionalURL = URL.optional
102
+
103
+ # Pathname type for file system paths.
104
+ #
105
+ # Accepts strings, Pathname objects, or anything that responds to #to_path or #to_s.
106
+ #
107
+ # @example
108
+ # Types::PathnameType["/path/to/file"]
109
+ # Types::PathnameType[Pathname.new("/path/to/file")]
110
+ PathnameType = Any.constructor do |value|
111
+ case value
112
+ when Pathname
113
+ value
114
+ when String
115
+ Pathname.new(value)
116
+ else
117
+ Pathname.new(value.to_s)
118
+ end
119
+ end
120
+
121
+ # Optional Pathname type.
122
+ OptionalPathnameType = PathnameType.optional
123
+ end
124
+
125
+ # Autoload type structures
126
+ module Types
127
+ autoload :ModelInfo, "durable_huggingface_hub/types/model_info"
128
+ autoload :DatasetInfo, "durable_huggingface_hub/types/dataset_info"
129
+ autoload :SpaceInfo, "durable_huggingface_hub/types/space_info"
130
+ autoload :CommitInfo, "durable_huggingface_hub/types/commit_info"
131
+ autoload :GitRefInfo, "durable_huggingface_hub/types/commit_info"
132
+ autoload :User, "durable_huggingface_hub/types/user"
133
+ autoload :Organization, "durable_huggingface_hub/types/user"
134
+ autoload :CachedFileInfo, "durable_huggingface_hub/types/cache_info"
135
+ autoload :CachedRevisionInfo, "durable_huggingface_hub/types/cache_info"
136
+ autoload :CachedRepoInfo, "durable_huggingface_hub/types/cache_info"
137
+ autoload :HFCacheInfo, "durable_huggingface_hub/types/cache_info"
138
+ end
139
+
140
+ # Base class for data structures using dry-struct.
141
+ #
142
+ # Provides immutable, type-checked data structures with automatic
143
+ # attribute validation and coercion.
144
+ #
145
+ # @example Defining a data structure
146
+ # class MyData < DurableHuggingfaceHub::Struct
147
+ # attribute :name, Types::String
148
+ # attribute :count, Types::Integer
149
+ # attribute :optional, Types::OptionalString
150
+ # end
151
+ class Struct < Dry::Struct
152
+ # Use type schema for strict attribute validation
153
+ schema schema.strict
154
+
155
+ # Transform attribute keys from camelCase strings to snake_case symbols
156
+ transform_keys do |key|
157
+ # Convert camelCase to snake_case, then to symbol
158
+ key.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase.to_sym
159
+ end
160
+
161
+ # Base class for data structures that can be loaded from JSON/Hash.
162
+ #
163
+ # Provides convenient methods for creating instances from API responses.
164
+ module Loadable
165
+ # Creates an instance from a hash (typically from JSON parsing).
166
+ #
167
+ # @param data [Hash] Data hash
168
+ # @return [Struct] New instance
169
+ def self.included(base)
170
+ base.extend(ClassMethods)
171
+ end
172
+
173
+ module ClassMethods
174
+ # Creates an instance from a hash.
175
+ #
176
+ # @param data [Hash] Data hash with string or symbol keys
177
+ # @return [self] New instance of the struct
178
+ def from_hash(data)
179
+ new(data)
180
+ end
181
+
182
+ # Alias for from_hash.
183
+ #
184
+ # @param data [Hash] Data hash
185
+ # @return [self] New instance
186
+ alias from_json from_hash
187
+ end
188
+
189
+ # Converts the struct to a hash.
190
+ #
191
+ # @return [Hash] Hash representation
192
+ def to_h
193
+ attributes.to_h
194
+ end
195
+
196
+ # Converts the struct to JSON.
197
+ #
198
+ # @return [String] JSON representation
199
+ def to_json(*args)
200
+ require "json"
201
+ to_h.to_json(*args)
202
+ end
203
+ end
204
+ end
205
+ end