From a296b89c8f12451b7ad44b361cfc55b1f1cdf4fb Mon Sep 17 00:00:00 2001 From: Arun Sharma Date: Mon, 28 Sep 2026 13:47:54 -0700 Subject: [PATCH] Alias hf:// to xet:// in httpfs Other databases such as duckdb use hf:// URLs for Hugging Face content. Route hf:// through XetFileSystem so both schemes resolve identically. --- httpfs/src/xetfs.cpp | 19 ++++++++++++------- httpfs/test/xetfs_test.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/httpfs/src/xetfs.cpp b/httpfs/src/xetfs.cpp index b4ba6d0a..06ac1496 100644 --- a/httpfs/src/xetfs.cpp +++ b/httpfs/src/xetfs.cpp @@ -12,6 +12,7 @@ using namespace common; namespace { static constexpr std::string_view XET_PREFIX = "xet://"; +static constexpr std::string_view HF_PREFIX = "hf://"; static constexpr std::string_view HF_BASE_URL = "https://huggingface.co/"; std::vector splitPath(std::string_view path) { @@ -129,7 +130,7 @@ void XetFileSystem::evictMemoizedResolveTarget(const std::string& url) { std::unique_ptr XetFileSystem::openFile(const std::string& path, common::FileOpenFlags flags, main::ClientContext* context) { if (flags.flags & FileFlags::WRITE) { - throw IOException{"Writing to xet:// URLs is not supported."}; + throw IOException{"Writing to xet:// or hf:// URLs is not supported."}; } return HTTPFileSystem::openFileWithImmutability(toHuggingFaceURL(path), flags, context, /*immutableContent=*/true); @@ -137,12 +138,13 @@ std::unique_ptr XetFileSystem::openFile(const std::string& pat std::vector XetFileSystem::glob(main::ClientContext* /*context*/, const std::string& path) const { - // Keep xet:// paths routed to XetFileSystem after bind-time glob expansion. + // Keep xet:// and hf:// paths routed to XetFileSystem after bind-time glob + // expansion. return {path}; } bool XetFileSystem::canHandleFile(const std::string_view path) const { - return path.rfind(XET_PREFIX, 0) == 0; + return path.rfind(XET_PREFIX, 0) == 0 || path.rfind(HF_PREFIX, 0) == 0; } bool XetFileSystem::fileOrPathExists(const std::string& path, main::ClientContext* context) { @@ -150,11 +152,14 @@ bool XetFileSystem::fileOrPathExists(const std::string& path, main::ClientContex } std::string XetFileSystem::toHuggingFaceURL(const std::string& path) { - if (path.rfind(XET_PREFIX, 0) != 0) { - throw IOException{"Xet URL needs to start with xet://"}; + std::string_view suffix; + if (path.rfind(XET_PREFIX, 0) == 0) { + suffix = std::string_view{path}.substr(XET_PREFIX.size()); + } else if (path.rfind(HF_PREFIX, 0) == 0) { + suffix = std::string_view{path}.substr(HF_PREFIX.size()); + } else { + throw IOException{"Xet URL needs to start with xet:// or hf://"}; } - - auto suffix = std::string_view{path}.substr(XET_PREFIX.size()); if (suffix.rfind("huggingface.co/", 0) == 0) { return std::format("{}{}", HF_BASE_URL, suffix.substr(std::string_view{"huggingface.co/"}.size())); diff --git a/httpfs/test/xetfs_test.cpp b/httpfs/test/xetfs_test.cpp index 93516922..c940b1ec 100644 --- a/httpfs/test/xetfs_test.cpp +++ b/httpfs/test/xetfs_test.cpp @@ -26,3 +26,28 @@ TEST(XetFileSystemTest, GlobKeepsXetPath) { std::string{"xet://datasets/ladybugdb/small-kgs/main/kg_history/icebug-disk/schema.cypher"}; EXPECT_EQ(std::vector{path}, fs.glob(nullptr, path)); } + +TEST(XetFileSystemTest, HfSchemeAliasesXet) { + EXPECT_EQ("https://huggingface.co/Qwen/Qwen-Image-Edit/resolve/main/model.safetensors", + XetFileSystem::toHuggingFaceURL("hf://models/Qwen/Qwen-Image-Edit/main/" + "model.safetensors")); + EXPECT_EQ("https://huggingface.co/datasets/org/repo/resolve/main/data/train.parquet", + XetFileSystem::toHuggingFaceURL("hf://datasets/org/repo/resolve/main/" + "data/train.parquet")); + EXPECT_EQ("https://huggingface.co/org/repo/resolve/main/file.csv", + XetFileSystem::toHuggingFaceURL("hf://huggingface.co/org/repo/resolve/main/file.csv")); +} + +TEST(XetFileSystemTest, CanHandleBothSchemes) { + XetFileSystem fs; + EXPECT_TRUE(fs.canHandleFile("xet://models/org/repo/main/file.parquet")); + EXPECT_TRUE(fs.canHandleFile("hf://models/org/repo/main/file.parquet")); + EXPECT_FALSE(fs.canHandleFile("https://huggingface.co/org/repo/resolve/main/file.csv")); +} + +TEST(XetFileSystemTest, GlobKeepsHfPath) { + XetFileSystem fs; + const auto path = + std::string{"hf://datasets/ladybugdb/small-kgs/main/kg_history/icebug-disk/schema.cypher"}; + EXPECT_EQ(std::vector{path}, fs.glob(nullptr, path)); +}