diff --git a/Sources/HuggingFace/Hub/HubClient+Datasets.swift b/Sources/HuggingFace/Hub/HubClient+Datasets.swift index 8f427df..a3730c9 100644 --- a/Sources/HuggingFace/Hub/HubClient+Datasets.swift +++ b/Sources/HuggingFace/Hub/HubClient+Datasets.swift @@ -53,17 +53,22 @@ extension HubClient { revision: String? = nil, full: Bool? = nil ) async throws -> Dataset { - let path: String + var url = httpClient.host + .appending(path: "api") + .appending(path: "datasets") + .appending(path: id.namespace) + .appending(path: id.name) if let revision { - path = "/api/datasets/\(id.namespace)/\(id.name)/revision/\(revision)" - } else { - path = "/api/datasets/\(id.namespace)/\(id.name)" + url = + url + .appending(path: "revision") + .appending(component: revision) } var params: [String: Value] = [:] if let full { params["full"] = .bool(full) } - return try await httpClient.fetch(.get, path, params: params) + return try await httpClient.fetch(.get, url: url, params: params) } /// Gets all available dataset tags hosted in the Hub. @@ -128,8 +133,14 @@ extension HubClient { /// - Returns: `true` if the request was cancelled successfully. /// - Throws: An error if the request fails. public func cancelDatasetAccessRequest(_ id: Repo.ID) async throws -> Bool { - let path = "/api/datasets/\(id.namespace)/\(id.name)/user-access-request/cancel" - let result: Bool = try await httpClient.fetch(.post, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "datasets") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "user-access-request") + .appending(path: "cancel") + let result: Bool = try await httpClient.fetch(.post, url: url) return result } @@ -139,8 +150,14 @@ extension HubClient { /// - Returns: `true` if access was granted successfully. /// - Throws: An error if the request fails. public func grantDatasetAccess(_ id: Repo.ID) async throws -> Bool { - let path = "/api/datasets/\(id.namespace)/\(id.name)/user-access-request/grant" - let result: Bool = try await httpClient.fetch(.post, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "datasets") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "user-access-request") + .appending(path: "grant") + let result: Bool = try await httpClient.fetch(.post, url: url) return result } @@ -150,8 +167,14 @@ extension HubClient { /// - Returns: `true` if the request was handled successfully. /// - Throws: An error if the request fails. public func handleDatasetAccessRequest(_ id: Repo.ID) async throws -> Bool { - let path = "/api/datasets/\(id.namespace)/\(id.name)/user-access-request/handle" - let result: Bool = try await httpClient.fetch(.post, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "datasets") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "user-access-request") + .appending(path: "handle") + let result: Bool = try await httpClient.fetch(.post, url: url) return result } @@ -166,8 +189,14 @@ extension HubClient { _ id: Repo.ID, status: AccessRequest.Status ) async throws -> [AccessRequest] { - let path = "/api/datasets/\(id.namespace)/\(id.name)/user-access-request/\(status.rawValue)" - return try await httpClient.fetch(.get, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "datasets") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "user-access-request") + .appending(path: status.rawValue) + return try await httpClient.fetch(.get, url: url) } /// Gets user access report for a dataset repository. @@ -176,8 +205,12 @@ extension HubClient { /// - Returns: User access report data. /// - Throws: An error if the request fails. public func getDatasetUserAccessReport(_ id: Repo.ID) async throws -> Data { - let path = "/datasets/\(id.namespace)/\(id.name)/user-access-report" - return try await httpClient.fetchData(.get, path) + let url = httpClient.host + .appending(path: "datasets") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "user-access-report") + return try await httpClient.fetchData(.get, url: url) } // MARK: - Dataset Advanced Features @@ -193,13 +226,18 @@ extension HubClient { _ id: Repo.ID, resourceGroupId: String? ) async throws -> ResourceGroup { - let path = "/api/datasets/\(id.namespace)/\(id.name)/resource-group" + let url = httpClient.host + .appending(path: "api") + .appending(path: "datasets") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "resource-group") let params: [String: Value] = [ "resourceGroupId": resourceGroupId.map { .string($0) } ?? .null ] - return try await httpClient.fetch(.post, path, params: params) + return try await httpClient.fetch(.post, url: url, params: params) } /// Scans a dataset repository. @@ -208,8 +246,13 @@ extension HubClient { /// - Returns: `true` if the scan was initiated successfully. /// - Throws: An error if the request fails. public func scanDataset(_ id: Repo.ID) async throws -> Bool { - let path = "/api/datasets/\(id.namespace)/\(id.name)/scan" - let result: Bool = try await httpClient.fetch(.post, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "datasets") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "scan") + let result: Bool = try await httpClient.fetch(.post, url: url) return result } @@ -228,14 +271,20 @@ extension HubClient { tag: String, message: String? = nil ) async throws -> Bool { - let path = "/api/datasets/\(id.namespace)/\(id.name)/tag/\(revision)" + let url = httpClient.host + .appending(path: "api") + .appending(path: "datasets") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "tag") + .appending(component: revision) let params: [String: Value] = [ "tag": .string(tag), "message": message.map { .string($0) } ?? .null, ] - let result: Bool = try await httpClient.fetch(.post, path, params: params) + let result: Bool = try await httpClient.fetch(.post, url: url, params: params) return result } @@ -252,14 +301,20 @@ extension HubClient { revision: String, message: String ) async throws -> String { - let path = "/api/datasets/\(id.namespace)/\(id.name)/super-squash/\(revision)" + let url = httpClient.host + .appending(path: "api") + .appending(path: "datasets") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "super-squash") + .appending(component: revision) let params: [String: Value] = [ "message": .string(message) ] struct Response: Decodable { let commitID: String } - let resp: Response = try await httpClient.fetch(.post, path, params: params) + let resp: Response = try await httpClient.fetch(.post, url: url, params: params) return resp.commitID } } diff --git a/Sources/HuggingFace/Hub/HubClient+Files.swift b/Sources/HuggingFace/Hub/HubClient+Files.swift index e88ec14..0e26a51 100644 --- a/Sources/HuggingFace/Hub/HubClient+Files.swift +++ b/Sources/HuggingFace/Hub/HubClient+Files.swift @@ -46,8 +46,14 @@ public extension HubClient { branch: String = "main", message: String? = nil ) async throws -> (path: String, commit: String?) { - let urlPath = "/api/\(kind.pluralized)/\(repo)/upload/\(branch)" - var request = try await httpClient.createRequest(.post, urlPath) + let url = httpClient.host + .appending(path: "api") + .appending(path: kind.pluralized) + .appending(path: repo.namespace) + .appending(path: repo.name) + .appending(path: "upload") + .appending(component: branch) + var request = try await httpClient.createRequest(.post, url: url) let boundary = "----hf-\(UUID().uuidString)" request.setValue( @@ -195,8 +201,13 @@ public extension HubClient { } let endpoint = useRaw ? "raw" : "resolve" - let urlPath = "/\(repo)/\(endpoint)/\(revision)/\(repoPath)" - var request = try await httpClient.createRequest(.get, urlPath) + let url = httpClient.host + .appending(path: repo.namespace) + .appending(path: repo.name) + .appending(path: endpoint) + .appending(component: revision) + .appending(path: repoPath) + var request = try await httpClient.createRequest(.get, url: url) request.cachePolicy = cachePolicy let (data, response) = try await session.data(for: request) @@ -265,8 +276,13 @@ public extension HubClient { } let endpoint = useRaw ? "raw" : "resolve" - let urlPath = "/\(repo)/\(endpoint)/\(revision)/\(repoPath)" - var request = try await httpClient.createRequest(.get, urlPath) + let url = httpClient.host + .appending(path: repo.namespace) + .appending(path: repo.name) + .appending(path: endpoint) + .appending(component: revision) + .appending(path: repoPath) + var request = try await httpClient.createRequest(.get, url: url) request.cachePolicy = cachePolicy let (tempURL, response) = try await session.download( @@ -424,7 +440,13 @@ public extension HubClient { branch: String = "main", message: String ) async throws { - let urlPath = "/api/\(kind.pluralized)/\(repo)/commit/\(branch)" + let url = httpClient.host + .appending(path: "api") + .appending(path: kind.pluralized) + .appending(path: repo.namespace) + .appending(path: repo.name) + .appending(path: "commit") + .appending(component: branch) let operations = repoPaths.map { path in Value.object(["op": .string("delete"), "path": .string(path)]) } @@ -433,7 +455,7 @@ public extension HubClient { "operations": .array(operations), ] - let _: Bool = try await httpClient.fetch(.post, urlPath, params: params) + let _: Bool = try await httpClient.fetch(.post, url: url, params: params) } } @@ -474,10 +496,16 @@ public extension HubClient { revision: String = "main", recursive: Bool = true ) async throws -> [Git.TreeEntry] { - let urlPath = "/api/\(kind.pluralized)/\(repo)/tree/\(revision)" + let url = httpClient.host + .appending(path: "api") + .appending(path: kind.pluralized) + .appending(path: repo.namespace) + .appending(path: repo.name) + .appending(path: "tree") + .appending(component: revision) let params: [String: Value]? = recursive ? ["recursive": .bool(true)] : nil - return try await httpClient.fetch(.get, urlPath, params: params) + return try await httpClient.fetch(.get, url: url, params: params) } /// Get file information @@ -493,8 +521,13 @@ public extension HubClient { kind _: Repo.Kind = .model, revision: String = "main" ) async throws -> File { - let urlPath = "/\(repo)/resolve/\(revision)/\(repoPath)" - var request = try await httpClient.createRequest(.head, urlPath) + let url = httpClient.host + .appending(path: repo.namespace) + .appending(path: repo.name) + .appending(path: "resolve") + .appending(component: revision) + .appending(path: repoPath) + var request = try await httpClient.createRequest(.head, url: url) request.setValue("bytes=0-0", forHTTPHeaderField: "Range") do { diff --git a/Sources/HuggingFace/Hub/HubClient+Models.swift b/Sources/HuggingFace/Hub/HubClient+Models.swift index 494abe1..7825399 100644 --- a/Sources/HuggingFace/Hub/HubClient+Models.swift +++ b/Sources/HuggingFace/Hub/HubClient+Models.swift @@ -53,17 +53,22 @@ extension HubClient { revision: String? = nil, full: Bool? = nil ) async throws -> Model { - let path: String + var url = httpClient.host + .appending(path: "api") + .appending(path: "models") + .appending(path: id.namespace) + .appending(path: id.name) if let revision { - path = "/api/models/\(id.namespace)/\(id.name)/revision/\(revision)" - } else { - path = "/api/models/\(id.namespace)/\(id.name)" + url = + url + .appending(path: "revision") + .appending(component: revision) } var params: [String: Value] = [:] if let full { params["full"] = .bool(full) } - return try await httpClient.fetch(.get, path, params: params) + return try await httpClient.fetch(.get, url: url, params: params) } /// Gets all available model tags hosted in the Hub. @@ -99,8 +104,14 @@ extension HubClient { /// - Returns: `true` if the request was cancelled successfully. /// - Throws: An error if the request fails. public func cancelModelAccessRequest(_ id: Repo.ID) async throws -> Bool { - let path = "/api/models/\(id.namespace)/\(id.name)/user-access-request/cancel" - let result: Bool = try await httpClient.fetch(.post, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "models") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "user-access-request") + .appending(path: "cancel") + let result: Bool = try await httpClient.fetch(.post, url: url) return result } @@ -110,8 +121,14 @@ extension HubClient { /// - Returns: `true` if access was granted successfully. /// - Throws: An error if the request fails. public func grantModelAccess(_ id: Repo.ID) async throws -> Bool { - let path = "/api/models/\(id.namespace)/\(id.name)/user-access-request/grant" - let result: Bool = try await httpClient.fetch(.post, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "models") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "user-access-request") + .appending(path: "grant") + let result: Bool = try await httpClient.fetch(.post, url: url) return result } @@ -121,8 +138,14 @@ extension HubClient { /// - Returns: `true` if the request was handled successfully. /// - Throws: An error if the request fails. public func handleModelAccessRequest(_ id: Repo.ID) async throws -> Bool { - let path = "/api/models/\(id.namespace)/\(id.name)/user-access-request/handle" - let result: Bool = try await httpClient.fetch(.post, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "models") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "user-access-request") + .appending(path: "handle") + let result: Bool = try await httpClient.fetch(.post, url: url) return result } @@ -137,8 +160,14 @@ extension HubClient { _ id: Repo.ID, status: AccessRequest.Status ) async throws -> [AccessRequest] { - let path = "/api/models/\(id.namespace)/\(id.name)/user-access-request/\(status.rawValue)" - return try await httpClient.fetch(.get, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "models") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "user-access-request") + .appending(path: status.rawValue) + return try await httpClient.fetch(.get, url: url) } /// Gets user access report for a model repository. @@ -147,8 +176,11 @@ extension HubClient { /// - Returns: User access report data. /// - Throws: An error if the request fails. public func getModelUserAccessReport(_ id: Repo.ID) async throws -> Data { - let path = "/\(id.namespace)/\(id.name)/user-access-report" - return try await httpClient.fetchData(.get, path) + let url = httpClient.host + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "user-access-report") + return try await httpClient.fetchData(.get, url: url) } // MARK: - Model Advanced Features @@ -164,13 +196,18 @@ extension HubClient { _ id: Repo.ID, resourceGroupId: String? ) async throws -> ResourceGroup { - let path = "/api/models/\(id.namespace)/\(id.name)/resource-group" + let url = httpClient.host + .appending(path: "api") + .appending(path: "models") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "resource-group") let params: [String: Value] = [ "resourceGroupId": resourceGroupId.map { .string($0) } ?? .null ] - return try await httpClient.fetch(.post, path, params: params) + return try await httpClient.fetch(.post, url: url, params: params) } /// Scans a model repository. @@ -179,8 +216,13 @@ extension HubClient { /// - Returns: `true` if the scan was initiated successfully. /// - Throws: An error if the request fails. public func scanModel(_ id: Repo.ID) async throws -> Bool { - let path = "/api/models/\(id.namespace)/\(id.name)/scan" - let result: Bool = try await httpClient.fetch(.post, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "models") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "scan") + let result: Bool = try await httpClient.fetch(.post, url: url) return result } @@ -199,14 +241,20 @@ extension HubClient { tag: String, message: String? = nil ) async throws -> Bool { - let path = "/api/models/\(id.namespace)/\(id.name)/tag/\(revision)" + let url = httpClient.host + .appending(path: "api") + .appending(path: "models") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "tag") + .appending(component: revision) let params: [String: Value] = [ "tag": .string(tag), "message": message.map { .string($0) } ?? .null, ] - let result: Bool = try await httpClient.fetch(.post, path, params: params) + let result: Bool = try await httpClient.fetch(.post, url: url, params: params) return result } @@ -223,14 +271,20 @@ extension HubClient { revision: String, message: String ) async throws -> String { - let path = "/api/models/\(id.namespace)/\(id.name)/super-squash/\(revision)" + let url = httpClient.host + .appending(path: "api") + .appending(path: "models") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "super-squash") + .appending(component: revision) let params: [String: Value] = [ "message": .string(message) ] struct Response: Decodable { let commitID: String } - let resp: Response = try await httpClient.fetch(.post, path, params: params) + let resp: Response = try await httpClient.fetch(.post, url: url, params: params) return resp.commitID } } diff --git a/Sources/HuggingFace/Hub/HubClient+Organizations.swift b/Sources/HuggingFace/Hub/HubClient+Organizations.swift index 0a358f2..0acd8b8 100644 --- a/Sources/HuggingFace/Hub/HubClient+Organizations.swift +++ b/Sources/HuggingFace/Hub/HubClient+Organizations.swift @@ -100,7 +100,11 @@ extension HubClient { repos: [String: String]? = nil, autoJoin: ResourceGroup.AutoJoin? = nil ) async throws -> Bool { - let path = "/api/organizations/\(name)/resource-groups" + let url = httpClient.host + .appending(path: "api") + .appending(path: "organizations") + .appending(path: name) + .appending(path: "resource-groups") var params: [String: Value] = ["name": .string(resourceGroupName)] if let description { params["description"] = .string(description) } if let users { @@ -128,7 +132,7 @@ extension HubClient { if let role = autoJoin.role { auto["role"] = .string(role.rawValue) } params["autoJoin"] = .object(auto) } - let result: Bool = try await httpClient.fetch(.post, path, params: params) + let result: Bool = try await httpClient.fetch(.post, url: url, params: params) return result } } diff --git a/Sources/HuggingFace/Hub/HubClient+Repos.swift b/Sources/HuggingFace/Hub/HubClient+Repos.swift index 0088217..7376147 100644 --- a/Sources/HuggingFace/Hub/HubClient+Repos.swift +++ b/Sources/HuggingFace/Hub/HubClient+Repos.swift @@ -46,13 +46,18 @@ extension HubClient { _ id: Repo.ID, settings: Repo.Settings ) async throws -> Bool { - let path = "/api/\(kind.rawValue)s/\(id.namespace)/\(id.name)/settings" + let url = httpClient.host + .appending(path: "api") + .appending(path: kind.pluralized) + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "settings") let encoder = JSONEncoder() let data = try encoder.encode(settings) let params = try JSONDecoder().decode([String: Value].self, from: data) - return try await httpClient.fetch(.put, path, params: params) + return try await httpClient.fetch(.put, url: url, params: params) } /// Moves a repository to a new location. diff --git a/Sources/HuggingFace/Hub/HubClient+Spaces.swift b/Sources/HuggingFace/Hub/HubClient+Spaces.swift index 8669e38..aae0713 100644 --- a/Sources/HuggingFace/Hub/HubClient+Spaces.swift +++ b/Sources/HuggingFace/Hub/HubClient+Spaces.swift @@ -50,17 +50,22 @@ extension HubClient { revision: String? = nil, full: Bool? = nil ) async throws -> Space { - let path: String + var url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) if let revision { - path = "/api/spaces/\(id.namespace)/\(id.name)/revision/\(revision)" - } else { - path = "/api/spaces/\(id.namespace)/\(id.name)" + url = + url + .appending(path: "revision") + .appending(component: revision) } var params: [String: Value] = [:] if let full { params["full"] = .bool(full) } - return try await httpClient.fetch(.get, path, params: params) + return try await httpClient.fetch(.get, url: url, params: params) } /// Gets runtime information for a Space. @@ -69,8 +74,13 @@ extension HubClient { /// - Returns: Runtime information for the space. /// - Throws: An error if the request fails or the response cannot be decoded. public func spaceRuntime(_ id: Repo.ID) async throws -> Space.Runtime { - let path = "/api/spaces/\(id.namespace)/\(id.name)/runtime" - return try await httpClient.fetch(.get, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "runtime") + return try await httpClient.fetch(.get, url: url) } /// Puts a Space to sleep. @@ -79,8 +89,13 @@ extension HubClient { /// - Returns: `true` if the operation was successful. /// - Throws: An error if the request fails. public func sleepSpace(_ id: Repo.ID) async throws -> Bool { - let path = "/api/spaces/\(id.namespace)/\(id.name)/sleeptime" - return try await httpClient.fetch(.post, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "sleeptime") + return try await httpClient.fetch(.post, url: url) } /// Restarts a Space. @@ -91,12 +106,17 @@ extension HubClient { /// - Returns: `true` if the operation was successful. /// - Throws: An error if the request fails. public func restartSpace(_ id: Repo.ID, factory: Bool = false) async throws -> Bool { - let path = "/api/spaces/\(id.namespace)/\(id.name)/restart" + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "restart") var params: [String: Value] = [:] if factory { params["factory"] = .bool(true) } - return try await httpClient.fetch(.post, path, params: params) + return try await httpClient.fetch(.post, url: url, params: params) } // MARK: - Space Streaming @@ -111,8 +131,14 @@ extension HubClient { _ id: Repo.ID, logType: String ) -> AsyncThrowingStream { - let path = "/api/spaces/\(id.namespace)/\(id.name)/logs/\(logType)" - return httpClient.fetchStream(.get, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "logs") + .appending(path: logType) + return httpClient.fetchStream(.get, url: url) } /// Streams live metrics for a Space using Server-Sent Events. @@ -120,8 +146,13 @@ extension HubClient { /// - Parameter id: The repository identifier. /// - Returns: An async stream of metrics. public func streamSpaceMetrics(_ id: Repo.ID) -> AsyncThrowingStream { - let path = "/api/spaces/\(id.namespace)/\(id.name)/metrics" - return httpClient.fetchStream(.get, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "metrics") + return httpClient.fetchStream(.get, url: url) } /// Streams events for a Space using Server-Sent Events. @@ -134,12 +165,17 @@ extension HubClient { _ id: Repo.ID, sessionUUID: String? = nil ) -> AsyncThrowingStream { - let path = "/api/spaces/\(id.namespace)/\(id.name)/events" + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "events") var params: [String: Value] = [:] if let sessionUUID { params["session_uuid"] = .string(sessionUUID) } - return httpClient.fetchStream(.get, path, params: params) + return httpClient.fetchStream(.get, url: url, params: params) } // MARK: - Space Secrets Management @@ -159,7 +195,12 @@ extension HubClient { description: String? = nil, value: String? = nil ) async throws -> Bool { - let path = "/api/spaces/\(id.namespace)/\(id.name)/secrets" + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "secrets") let params: [String: Value] = [ "key": .string(key), @@ -167,7 +208,7 @@ extension HubClient { "value": value.map { .string($0) } ?? .string(""), ] - let result: Bool = try await httpClient.fetch(.post, path, params: params) + let result: Bool = try await httpClient.fetch(.post, url: url, params: params) return result } @@ -182,13 +223,18 @@ extension HubClient { _ id: Repo.ID, key: String ) async throws -> Bool { - let path = "/api/spaces/\(id.namespace)/\(id.name)/secrets" + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "secrets") let params: [String: Value] = [ "key": .string(key) ] - let result: Bool = try await httpClient.fetch(.delete, path, params: params) + let result: Bool = try await httpClient.fetch(.delete, url: url, params: params) return result } @@ -209,7 +255,12 @@ extension HubClient { description: String? = nil, value: String? = nil ) async throws -> Bool { - let path = "/api/spaces/\(id.namespace)/\(id.name)/variables" + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "variables") let params: [String: Value] = [ "key": .string(key), @@ -217,7 +268,7 @@ extension HubClient { "value": value.map { .string($0) } ?? .string(""), ] - let result: Bool = try await httpClient.fetch(.post, path, params: params) + let result: Bool = try await httpClient.fetch(.post, url: url, params: params) return result } @@ -232,13 +283,18 @@ extension HubClient { _ id: Repo.ID, key: String ) async throws -> Bool { - let path = "/api/spaces/\(id.namespace)/\(id.name)/variables" + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "variables") let params: [String: Value] = [ "key": .string(key) ] - let result: Bool = try await httpClient.fetch(.delete, path, params: params) + let result: Bool = try await httpClient.fetch(.delete, url: url, params: params) return result } @@ -255,13 +311,18 @@ extension HubClient { _ id: Repo.ID, resourceGroupId: String? ) async throws -> ResourceGroup { - let path = "/api/spaces/\(id.namespace)/\(id.name)/resource-group" + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "resource-group") let params: [String: Value] = [ "resourceGroupId": resourceGroupId.map { .string($0) } ?? .null ] - return try await httpClient.fetch(.post, path, params: params) + return try await httpClient.fetch(.post, url: url, params: params) } /// Scans a space repository. @@ -270,8 +331,13 @@ extension HubClient { /// - Returns: `true` if the scan was initiated successfully. /// - Throws: An error if the request fails. public func scanSpace(_ id: Repo.ID) async throws -> Bool { - let path = "/api/spaces/\(id.namespace)/\(id.name)/scan" - let result: Bool = try await httpClient.fetch(.post, path) + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "scan") + let result: Bool = try await httpClient.fetch(.post, url: url) return result } @@ -290,14 +356,20 @@ extension HubClient { tag: String, message: String? = nil ) async throws -> Bool { - let path = "/api/spaces/\(id.namespace)/\(id.name)/tag/\(revision)" + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "tag") + .appending(component: revision) let params: [String: Value] = [ "tag": .string(tag), "message": message.map { .string($0) } ?? .null, ] - let result: Bool = try await httpClient.fetch(.post, path, params: params) + let result: Bool = try await httpClient.fetch(.post, url: url, params: params) return result } @@ -314,14 +386,20 @@ extension HubClient { revision: String, message: String ) async throws -> String { - let path = "/api/spaces/\(id.namespace)/\(id.name)/super-squash/\(revision)" + let url = httpClient.host + .appending(path: "api") + .appending(path: "spaces") + .appending(path: id.namespace) + .appending(path: id.name) + .appending(path: "super-squash") + .appending(component: revision) let params: [String: Value] = [ "message": .string(message) ] struct Response: Decodable { let commitID: String } - let resp: Response = try await httpClient.fetch(.post, path, params: params) + let resp: Response = try await httpClient.fetch(.post, url: url, params: params) return resp.commitID } } diff --git a/Sources/HuggingFace/Shared/HTTPClient.swift b/Sources/HuggingFace/Shared/HTTPClient.swift index 66801f2..43754b2 100644 --- a/Sources/HuggingFace/Shared/HTTPClient.swift +++ b/Sources/HuggingFace/Shared/HTTPClient.swift @@ -72,6 +72,20 @@ final class HTTPClient: @unchecked Sendable { headers: [String: String]? = nil ) async throws -> T { let request = try await createRequest(method, path, params: params, headers: headers) + return try await performFetch(request: request) + } + + func fetch( + _ method: HTTPMethod, + url: URL, + params: [String: Value]? = nil, + headers: [String: String]? = nil + ) async throws -> T { + let request = try await createRequest(method, url: url, params: params, headers: headers) + return try await performFetch(request: request) + } + + private func performFetch(request: URLRequest) async throws -> T { let (data, response) = try await session.data(for: request) let httpResponse = try validateResponse(response, data: data) @@ -119,11 +133,37 @@ final class HTTPClient: @unchecked Sendable { _ path: String, params: [String: Value]? = nil, headers: [String: String]? = nil + ) -> AsyncThrowingStream { + performFetchStream( + method, + requestBuilder: { [self] in + try await self.createRequest(method, path, params: params, headers: headers) + } + ) + } + + func fetchStream( + _ method: HTTPMethod, + url: URL, + params: [String: Value]? = nil, + headers: [String: String]? = nil + ) -> AsyncThrowingStream { + performFetchStream( + method, + requestBuilder: { [self] in + try await self.createRequest(method, url: url, params: params, headers: headers) + } + ) + } + + private func performFetchStream( + _ method: HTTPMethod, + requestBuilder: @escaping @Sendable () async throws -> URLRequest ) -> AsyncThrowingStream { AsyncThrowingStream { @Sendable continuation in let task = Task { do { - let request = try await createRequest(method, path, params: params, headers: headers) + let request = try await requestBuilder() let (bytes, response) = try await session.bytes(for: request) let httpResponse = try validateResponse(response) @@ -177,6 +217,20 @@ final class HTTPClient: @unchecked Sendable { headers: [String: String]? = nil ) async throws -> Data { let request = try await createRequest(method, path, params: params, headers: headers) + return try await performFetchData(request: request) + } + + func fetchData( + _ method: HTTPMethod, + url: URL, + params: [String: Value]? = nil, + headers: [String: String]? = nil + ) async throws -> Data { + let request = try await createRequest(method, url: url, params: params, headers: headers) + return try await performFetchData(request: request) + } + + private func performFetchData(request: URLRequest) async throws -> Data { let (data, response) = try await session.data(for: request) let _ = try validateResponse(response, data: data) @@ -192,6 +246,28 @@ final class HTTPClient: @unchecked Sendable { var urlComponents = URLComponents(url: host, resolvingAgainstBaseURL: true) urlComponents?.path = path + return try await createRequest(method, urlComponents: urlComponents, params: params, headers: headers) + } + + func createRequest( + _ method: HTTPMethod, + url: URL, + params: [String: Value]? = nil, + headers: [String: String]? = nil + ) async throws -> URLRequest { + let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) + + return try await createRequest(method, urlComponents: urlComponents, params: params, headers: headers) + } + + private func createRequest( + _ method: HTTPMethod, + urlComponents: URLComponents?, + params: [String: Value]? = nil, + headers: [String: String]? = nil + ) async throws -> URLRequest { + var urlComponents = urlComponents + var httpBody: Data? = nil switch method { case .get, .head: @@ -218,7 +294,7 @@ final class HTTPClient: @unchecked Sendable { guard let url = urlComponents?.url else { throw HTTPClientError.requestError( - #"Unable to construct URL with host "\#(host)" and path "\#(path)""# + #"Unable to construct URL from components \#(String(describing: urlComponents))"# ) } var request: URLRequest = URLRequest(url: url)