depasqualeorg/swift-hf-api
A Swift client for the Hugging Face Hub, backed by the Rust hf-hub crate. This package is intentionally limited the models and datasets used for local inference and training.
Status
The library is pre-1.0. Expect breaking changes.
What's covered
- Repositories:
model(owner:name:)anddataset(owner:name:)handles. Each handle exposes the universal protocol methods (info,exists,listTree,pathsInfo,fileMetadata,listCommits,listRefs,commitDiff, …). - Listing:
listModelsandlistDatasetswithhf-hub's filter / sort / search builders. - Downloads:
downloadFile,downloadFileToBytes,downloadFileBytesStream,snapshotDownload. Progress events stream throughAsyncThrowingStream; cancellation flows from the consumer back to the Rust side via theOperationHandletoken. - Uploads:
uploadFile,uploadFileBytes,uploadFolder,createCommit,deleteFile,deleteFolder. Streaming variants emitUploadEvents and resolve to aCommitInfo. - Repo lifecycle:
createRepository,deleteRepository,moveRepository,updateSettings. - Branches and tags:
createBranch,deleteBranch,createTag,deleteTag. - Cache scan:
scanCache()returns aCacheInfothat mirrors the shapehuggingface-cli scan-cachereports. - Auth:
currentUser()for verifying the current token; OAuth refresh is glued intoHFClientviaHFAPIHubAuthso the inner client rotates tokens transparently.
Modules
HFAPI: the main client.HFClient,ModelRepository,DatasetRepository,CacheInfo, error types. Apple platforms also exposeNetworkMonitorfor explicit offline-mode checks; Linux omits it (noNWPathMonitorequivalent – provide your own detection or pass.use/.bypasstoNetworkAccess).HFAPIOAuth: Apple-platform OAuth flow.OAuthManager,OAuthClient, keychain token storage. Linux compiles to an empty target.HFAPIHubAuth: the OAuth↔HFClient bridge.OAuthClientFactory.client(authManager:)returns a fully wiredHFClientwhose token rotates with the manager.HFAPIShared: shared types.TokenProvider(used byHFAPIandHFAPIOAuth).
Usage
Basic client
import HFAPI
// Reads token + endpoint from environment (HF_TOKEN, HF_ENDPOINT, etc.).
let client = try HFClient()
let info = try await client.model(owner: "openai-community", name: "gpt2").info()
print(info.id, info.tags ?? [])Configure explicitly with named parameters:
let client = try HFClient(
endpoint: "https://huggingface.co",
auth: .token("hf_…"),
userAgent: "MyApp/1.0"
)Snapshot download
let url = try await client
.model(owner: "openai-community", name: "gpt2")
.snapshotDownload(allowPatterns: ["*.json", "*.txt"])
print("Snapshot at:", url.path)Streaming progress
downloadFileStream returns a `DownloadStream – an AsyncSequence of DownloadEvent. Iterate for progress, then await stream.value for the on-disk URL. Call stream.cancel()` (or break out of the loop) to abort.
let stream = client
.model(owner: "owner", name: "name")
.downloadFileStream("model.safetensors")
for try await event in stream {
if case let .aggregateProgress(bytesCompleted, totalBytes, _) = event {
print("\(bytesCompleted) / \(totalBytes)")
}
}
let url = try await stream.valueAuthentication
HFClient accepts a token in three shapes; pick whichever fits the use case.
Static token
For a single fixed token (CLI scripts, environment-driven flows):
let client = try HFClient(auth: .token("hf_…"))When auth is omitted (or set to .env), HFClient resolves a token from the environment at construction time, checking in priority order: HF_TOKEN, HUGGING_FACE_HUB_TOKEN, the file at HF_TOKEN_PATH, $HF_HOME/token, ~/.cache/huggingface/token, then ~/.huggingface/token. The order matches the HF CLI and Python huggingface_hub library. Pass auth: .unauthenticated to skip env detection entirely.
OAuth via OAuthClientFactory (recommended for OAuth flows)
OAuthManager is @MainActor-isolated, so construct it from a main-actor context (e.g., a SwiftUI App initializer or a @MainActor setup function):
import HFAPI
import HFAPIOAuth
import HFAPIHubAuth
@MainActor
func makeAuthenticatedClient() async throws -> HFClient {
let manager = try OAuthManager(
clientID: "your-client-id",
redirectURL: URL(string: "myapp://oauth")!,
scope: .basic,
keychainService: "com.example.app",
keychainAccount: "huggingface"
)
return try OAuthClientFactory.client(authManager: manager)
}
// Hub calls transparently consult the manager for a fresh token.
// When the OAuth token rotates, the inner Rust client is rebuilt.
let client = try await makeAuthenticatedClient()
let user = try await client.currentUser()The bridge propagates OAuth errors precisely. When validToken() throws (refresh-token expired, keychain inaccessible), the next Hub call surfaces HFError.tokenProviderFailed(message:) carrying the original OAuthError's localizedDescription. Pattern-match on tokenProviderFailed to drive a re-sign-in prompt.
Composable provider (TokenProvider)
Pass a TokenProvider from HFAPIShared for multi-source chains. The cases are .fixed(token:), .environment, .oauth(manager:), .composite([…]), and .custom { … }. .environment uses the same six-source lookup list as Auth.env.
import HFAPIShared
let client = try HFClient(auth: .provider(.composite([
.oauth(manager: authManager),
.environment,
.fixed(token: "hf_fallback_token"),
])))Note: TokenProvider.composite([…]) short-circuits when a sub-provider throws – it does not advance to the next on error, only on nil. To skip an OAuth provider that's failing rather than abort the chain, wrap it in a .custom that catches: .custom { try? await manager.validToken() }.
Custom closure
For one-off custom token logic where a TokenProvider would be overkill:
let client = try HFClient(auth: .provider {
try await myCustomTokenStore.fetchCurrent()
})The closure is @Sendable () async throws -> String?. Returning nil runs the request unauthenticated; throwing aborts the Hub call with HFError.tokenProviderFailed(message:). Wrap with try? for best-effort semantics where transient failures fall through to unauthenticated requests.
The four Auth cases (.env, .unauthenticated, .token(:), .provider(:)) are mutually exclusive by construction – the type system prevents combining them, so there is no runtime "mutual exclusion" failure to handle.
Offline mode
Download calls take a networkAccess: NetworkAccess parameter (default .default) that controls whether the network is consulted on a cache miss:
.use– always hit the network on cache miss..bypass– cache-only; throwslocalEntryNotFoundif the file isn't cached..useIfAvailable(Apple-only) – consultsNetworkMonitor.shared.state.shouldUseOfflineMode()and falls back to.bypasswhen offline,.useotherwise.
.default resolves to .useIfAvailable on Apple platforms and .use on Linux (no NWPathMonitor equivalent).
// Auto-detect (Apple default): online → fetch; offline → cache.
let url = try await client
.model(owner: "openai-community", name: "gpt2")
.snapshotDownload()
// Force cache-only resolution.
let url = try await client
.model(owner: "openai-community", name: "gpt2")
.snapshotDownload(networkAccess: .bypass)
// Force network resolution (skip the offline check).
let url = try await client
.model(owner: "openai-community", name: "gpt2")
.snapshotDownload(networkAccess: .use)HFAPI.NetworkMonitor wraps NWPathMonitor and is Apple-only – the symbol does not exist in the Linux build. Linux consumers who need offline-aware behavior should pass .use or .bypass explicitly based on their own detection. Set CI_DISABLE_NETWORK_MONITOR=1 to disable the offline-mode signal in CI environments where the path monitor can produce false negatives.
Testing
swift testruns read-only tests against the live Hub; the mutation tests skip cleanly without an opt-in env var.HFAPI_RUN_HUB_MUTATION_TESTS=1 swift testopts into the mutation tests (create / upload / delete / branch / tag / commit). Each test creates an isolatedswift-hf-api-test-…-{uuid}repo under the authenticated user's namespace and tears it down afterward.- The Rust crate is built and shipped as a SwiftPM artifactbundle. Set
HFAPI_RUST_LOCAL_ARTIFACTBUNDLE_PATH=rust/target/artifactbundle/HFAPIRust.artifactbundleto point the package at a locally built bundle (seescripts/rust/for the assembly pipeline).
Platform support
macOS 14+, iOS 17+, Linux (excluding OAuth and HubAuth modules)
Package Metadata
Repository: depasqualeorg/swift-hf-api
Default branch: main
README: README.md