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
19 changes: 12 additions & 7 deletions httpfs/src/xetfs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> splitPath(std::string_view path) {
Expand Down Expand Up @@ -129,32 +130,36 @@ void XetFileSystem::evictMemoizedResolveTarget(const std::string& url) {
std::unique_ptr<common::FileInfo> 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);
}

std::vector<std::string> 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) {
return HTTPFileSystem::fileOrPathExists(toHuggingFaceURL(path), context);
}

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()));
Expand Down
25 changes: 25 additions & 0 deletions httpfs/test/xetfs_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string>{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<std::string>{path}, fs.glob(nullptr, path));
}
Loading