Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,122 @@ _ = try await client.updateDiscussionStatus(
)
```

#### File Operations

```swift
// List files in a repository
let files = try await client.listFiles(
in: "facebook/bart-large",
kind: .model,
revision: "main",
recursive: true
)

for file in files {
if file.type == .file {
print("\(file.path) - \(file.size ?? 0) bytes")
}
}

// Check if a file exists
let exists = await client.fileExists(
at: "README.md",
in: "facebook/bart-large"
)

// Get file information
let file = try await client.getFile(
at: "pytorch_model.bin",
in: "facebook/bart-large"
)
print("File size: \(file.size ?? 0)")
print("Is LFS: \(file.isLFS)")

// Download file data
let data = try await client.downloadContentsOfFile(
at: "config.json",
from: "openai-community/gpt2"
)
let config = try JSONDecoder().decode(ModelConfig.self, from: data)

// Download file to disk
let destination = FileManager.default.temporaryDirectory
.appendingPathComponent("model.safetensors")

let fileURL = try await client.downloadFile(
at: "model.safetensors",
from: "openai-community/gpt2",
to: destination
)

// Download with progress tracking
let progress = Progress(totalUnitCount: 0)
Task {
for await _ in progress.values(forKeyPath: \.fractionCompleted) {
print("Download progress: \(progress.fractionCompleted * 100)%")
}
}

let fileURL = try await client.downloadFile(
at: "pytorch_model.bin",
from: "facebook/bart-large",
to: destination,
progress: progress
)

// Resume a download
let resumeData: Data = // ... from previous download
let fileURL = try await client.resumeDownloadFile(
resumeData: resumeData,
to: destination,
progress: progress
)

// Upload a file
let result = try await client.uploadFile(
URL(fileURLWithPath: "/path/to/local/file.csv"),
to: "data/new_dataset.csv",
in: "username/my-dataset",
kind: .dataset,
branch: "main",
message: "Add new dataset"
)
print("Uploaded to: \(result.path)")

// Upload multiple files in a batch
let results = try await client.uploadFiles(
[
"README.md": .path("/path/to/readme.md"),
"data.json": .path("/path/to/data.json"),
],
to: "username/my-repo",
message: "Initial commit",
maxConcurrent: 3
)

// Or build a batch programmatically
var batch = FileBatch()
batch["config.json"] = .path("/path/to/config.json")
batch["model.safetensors"] = .url(
URL(fileURLWithPath: "/path/to/model.safetensors"),
mimeType: "application/octet-stream"
)

// Delete a file
try await client.deleteFile(
at: "old_file.txt",
from: "username/my-repo",
message: "Remove old file"
)

// Delete multiple files
try await client.deleteFiles(
at: ["file1.txt", "file2.txt", "old_dir/file3.txt"],
from: "username/my-repo",
message: "Cleanup old files"
)
```

#### User Access Management

```swift
Expand Down
137 changes: 137 additions & 0 deletions Sources/HuggingFace/Hub/File.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import Foundation

/// Information about a file in a repository.
public struct File: Hashable, Codable, Sendable {
/// A Boolean value indicating whether the file exists in the repository.
public let exists: Bool

/// The size of the file in bytes.
public let size: Int64?

/// The entity tag (ETag) for the file, used for caching and change detection.
public let etag: String?

/// The Git revision (commit SHA) at which this file information was retrieved.
public let revision: String?

/// A Boolean value indicating whether the file is stored using Git Large File Storage (LFS).
public let isLFS: Bool

init(
exists: Bool,
size: Int64? = nil,
etag: String? = nil,
revision: String? = nil,
isLFS: Bool = false
) {
self.exists = exists
self.size = size
self.etag = etag
self.revision = revision
self.isLFS = isLFS
}
}

// MARK: -

/// A collection of files to upload in a batch operation.
///
/// Use `FileBatch` to prepare multiple files for uploading to a repository in a single operation.
/// You can add files using subscript notation or dictionary literal syntax.
///
/// ```swift
/// var batch = FileBatch()
/// batch["config.json"] = .path("/path/to/config.json")
/// batch["model.safetensors"] = .url(
/// URL(fileURLWithPath: "/path/to/model.safetensors"),
/// mimeType: "application/octet-stream"
/// )
/// let _ = try await client.uploadFiles(batch, to: "username/my-repo", message: "Initial commit")
/// ```
/// - SeeAlso: `HubClient.uploadFiles(_:to:kind:branch:message:maxConcurrent:)`
public struct FileBatch: Hashable, Codable, Sendable {
/// An entry representing a file to upload.
public struct Entry: Hashable, Codable, Sendable {
/// The file URL pointing to the local file to upload.
public var url: URL

/// The MIME type of the file.
public var mimeType: String?

private init(url: URL, mimeType: String? = nil) {
self.url = url
self.mimeType = mimeType
}

/// Creates a file entry from a file system path.
/// - Parameters:
/// - path: The file system path to the local file.
/// - mimeType: The MIME type of the file. If not provided, the MIME type is inferred from the file extension.
/// - Returns: A file entry for the specified path.
public static func path(_ path: String, mimeType: String? = nil) -> Self {
return Self(url: URL(fileURLWithPath: path), mimeType: mimeType)
}

/// Creates a file entry from a URL.
/// - Parameters:
/// - url: The file URL. Must be a file URL (e.g., `file:///path/to/file`), not a remote URL.
/// - mimeType: Optional MIME type for the file.
/// - Returns: A file entry, or `nil` if the URL is not a file URL.
/// - Note: Only file URLs are accepted because this API requires local file access for upload.
/// Remote URLs (http, https, etc.) are not supported and will return `nil`.
public static func url(_ url: URL, mimeType: String? = nil) -> Self? {
guard url.isFileURL else {
return nil
}
return Self(url: url, mimeType: mimeType)
}
}

private var entries: [String: Entry]

/// Creates an empty file batch.
public init() {
self.entries = [:]
}

/// Creates a file batch with the specified entries.
/// - Parameter entries: A dictionary mapping repository paths to file entries.
public init(_ entries: [String: Entry]) {
self.entries = entries
}

/// Accesses the file entry for the specified repository path.
/// - Parameter path: The path in the repository where the file will be uploaded.
/// - Returns: The file entry for the specified path, or `nil` if no entry exists.
public subscript(path: String) -> Entry? {
get {
return entries[path]
}
set {
entries[path] = newValue
}
}
}

// MARK: - Collection

extension FileBatch: Swift.Collection {
public typealias Index = Dictionary<String, Entry>.Index

public var startIndex: Index { entries.startIndex }
public var endIndex: Index { entries.endIndex }
public func index(after i: Index) -> Index { entries.index(after: i) }
public subscript(position: Index) -> (key: String, value: Entry) { entries[position] }

public func makeIterator() -> Dictionary<String, Entry>.Iterator {
return entries.makeIterator()
}
}

// MARK: - ExpressibleByDictionaryLiteral

extension FileBatch: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, Entry)...) {
self.init(Dictionary(uniqueKeysWithValues: elements))
}
}
Loading