diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 9a00195942..f1c026826b 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -26,8 +26,8 @@ jobs: [ "Repository only", "Everything else", - "Inference only" - + "Inference only", + "Xet only" ] include: - python-version: "3.13" # LFS not ran on 3.8 @@ -95,6 +95,10 @@ jobs: uv pip install "huggingface_hub[tensorflow-testing] @ ." ;; + "Xet only") + uv pip install "huggingface_hub[hf_xet] @ ." + ;; + esac # Run tests @@ -121,7 +125,7 @@ jobs: ;; "Everything else") - PYTEST="$PYTEST ../tests -k 'not TestRepository and not test_inference' -n 4" + PYTEST="$PYTEST ../tests -k 'not TestRepository and not test_inference and not test_xet' -n 4" echo $PYTEST eval $PYTEST ;; @@ -130,7 +134,6 @@ jobs: eval "RUN_GIT_LFS_TESTS=1 $PYTEST ../tests -k 'HfLargefilesTest'" ;; - fastai) eval "$PYTEST ../tests/test_fastai*" ;; @@ -147,6 +150,12 @@ jobs: eval "$PYTEST ../tests/test_serialization.py" ;; + "Xet only") + PYTEST="$PYTEST ../tests -k 'test_xet' -n 4" + echo $PYTEST + eval $PYTEST + ;; + esac # Upload code coverage @@ -169,6 +178,7 @@ jobs: fail-fast: false matrix: python-version: ["3.8"] + test_name: ["Everything else", "Xet only"] steps: - uses: actions/checkout@v2 @@ -186,14 +196,37 @@ jobs: # Install dependencies - name: Install dependencies - run: uv pip install "huggingface_hub[testing] @ ." + run: | + uv pip install "huggingface_hub[testing] @ ." + if ("${{ matrix.test_name }}" -eq "Xet only") { + uv pip install "huggingface_hub[hf_xet] @ ." + } # Run tests - name: Run tests working-directory: ./src # For code coverage to work run: | ..\.venv\Scripts\activate - python -m pytest -n 4 --cov=./huggingface_hub --cov-report=xml:../coverage.xml --vcr-record=none --reruns 8 --reruns-delay 2 --only-rerun '(OSError|Timeout|HTTPError.*502|HTTPError.*504|not less than or equal to 0.01)' ../tests + $PYTEST_ARGS = @( + "-m", "pytest", + "-n", "4", + "--cov=./huggingface_hub", + "--cov-report=xml:../coverage.xml", + "--vcr-record=none", + "--reruns", "8", + "--reruns-delay", "2", + "--only-rerun", "(OSError|Timeout|HTTPError.*502|HTTPError.*504|not less than or equal to 0.01)", + "../tests" + ) + + switch ("${{ matrix.test_name }}") { + "Xet only" { + python $PYTEST_ARGS -k "test_xet" + } + "Everything else" { + python $PYTEST_ARGS -k "not test_xet" + } + } # Upload code coverage - name: Upload coverage reports to Codecov with GitHub Action diff --git a/docs/source/en/guides/download.md b/docs/source/en/guides/download.md index 254c72d165..8143286956 100644 --- a/docs/source/en/guides/download.md +++ b/docs/source/en/guides/download.md @@ -166,6 +166,30 @@ For more details about the CLI download command, please refer to the [CLI guide] ## Faster downloads +There are two options to speed up downloads. Both involve installing a Python package written in Rust. + +* `hf_xet` is newer and uses the Xet storage backend for upload/download. It is available in production, but is in the process of being rolled out to all users, so join the [waitlist](https://huggingface.co/join/xet) to get onboarded soon! +* `hf_transfer` is a power-tool to download and upload to our LFS storage backend (note: this is less future-proof than Xet). It is thoroughly tested and has been in production for a long time, but it has some limitations. + +### hf_xet + +Take advantage of faster downloads through `hf_xet`, the Python binding to the [`xet-core`](https://github.com/huggingface/xet-core) library that enables +chunk-based deduplication for faster downloads and uploads. `hf_xet` integrates seamlessly with `huggingface_hub`, but uses the Rust `xet-core` library and Xet storage instead of LFS. + +`hf_xet` uses the Xet storage system, which breaks files down into immutable chunks, storing collections of these chunks (called blocks or xorbs) remotely and retrieving them to reassemble the file when requested. When downloading, after confirming the user is authorized to access the files, `hf_xet` will query the Xet content-addressable service (CAS) with the LFS SHA256 hash for this file to receive the reconstruction metadata (ranges within xorbs) to assemble these files, along with presigned URLs to download the xorbs directly. Then `hf_xet` will efficiently download the xorb ranges necessary and will write out the files on disk. `hf_xet` uses a local disk cache to only download chunks once, learn more in the [Chunk-based caching(Xet)](./manage-cache.md#chunk-based-caching-xet) section. + +To enable it, specify the `hf_xet` package when installing `huggingface_hub`: + +```bash +pip install -U huggingface_hub[hf_xet] +``` + +Note: `hf_xet` will only be utilized when the files being downloaded are being stored with Xet Storage. + +All other `huggingface_hub` APIs will continue to work without any modification. To learn more about the benefits of Xet storage and `hf_xet`, refer to this [section](https://huggingface.co/docs/hub/storage-backends). + +### hf_transfer + If you are running on a machine with high bandwidth, you can increase your download speed with [`hf_transfer`](https://github.com/huggingface/hf_transfer), a Rust-based library developed to speed up file transfers with the Hub. diff --git a/docs/source/en/guides/manage-cache.md b/docs/source/en/guides/manage-cache.md index 521a50b21f..b2ef0cf6c9 100644 --- a/docs/source/en/guides/manage-cache.md +++ b/docs/source/en/guides/manage-cache.md @@ -2,9 +2,11 @@ rendered properly in your Markdown viewer. --> -# Manage `huggingface_hub` cache-system +# Understand caching -## Understand caching +`huggingface_hub` utilizes the local disk as two caches, which avoid re-downloading items again. The first cache is a file-based cache, which caches individual files downloaded from the Hub and ensures that the same file is not downloaded again when a repo gets updated. The second cache is a chunk cache, where each chunk represents a byte range from a file and ensures that chunks that are shared across files are only downloaded once. + +## File-based caching The Hugging Face Hub cache-system is designed to be the central cache shared across libraries that depend on the Hub. It has been updated in v0.8.0 to prevent re-downloading same files @@ -170,6 +172,95 @@ When symlinks are not supported, a warning message is displayed to the user to a them they are using a degraded version of the cache-system. This warning can be disabled by setting the `HF_HUB_DISABLE_SYMLINKS_WARNING` environment variable to true. +## Chunk-based caching (Xet) + +To provide more efficient file transfers, `hf_xet` adds a `xet` directory to the existing `huggingface_hub` cache, creating additional caching layer to enable chunk-based deduplication. This cache holds chunks, which are immutable byte ranges from files (up to 64KB) that are created using content-defined chunking. For more information on the Xet Storage system, see this [section](https://huggingface.co/docs/hub/storage-backends). + +The `xet` directory, located at `~/.cache/huggingface/xet` by default, contains two caches, utilized for uploads and downloads with the following structure + +```bash + +├─ chunk_cache +├─ shard_cache +``` + +The `xet` cache, like the rest of `hf_xet` is fully integrated with `huggingface_hub`. If you use the existing APIs for interacting with cached assets, there is no need to update your workflow. The `xet` cache is built as an optimization layer on top of the existing `hf_xet` chunk-based deduplication and `huggingface_hub` cache system. + +The `chunk-cache` directory contains cached data chunks that are used to speed up downloads while the `shard-cache` directory contains cached shards that are utilized on the upload path. + +### `chunk_cache` + +This cache is used on the download path. The cache directory structure is based on a base-64 encoded hash from the content-addressed store (CAS) that backs each Xet-enabled repository. A CAS hash serves as the key to lookup the offsets of where the data is stored. + +At the topmost level, the first two letters of the base 64 encoded CAS hash are used to create a subdirectory in the `chunk_cache` (keys that share these first two letters are grouped here). The inner levels are comprised of subdirectories with the full key as the directory name. At the base are the cache items which are ranges of blocks that contain the cached chunks. + +```bash + +├─ xet +│ ├─ chunk_cache +│ │ ├─ A1 +│ │ │ ├─ A1GerURLUcISVivdseeoY1PnYifYkOaCCJ7V5Q9fjgxkZWZhdWx0 +│ │ │ │ ├─ AAAAAAEAAAA5DQAAAAAAAIhRLjDI3SS5jYs4ysNKZiJy9XFI8CN7Ww0UyEA9KPD9 +│ │ │ │ ├─ AQAAAAIAAABzngAAAAAAAPNqPjd5Zby5aBvabF7Z1itCx0ryMwoCnuQcDwq79jlB + +``` + +When requesting a file, the first thing `hf_xet` does is communicate with Xet storage’s content addressed store (CAS) for reconstruction information. The reconstruction information contains information about the CAS keys required to download the file in its entirety. + +Before executing the requests for the CAS keys, the `chunk_cache` is consulted. If a key in the cache matches a CAS key, then there is no reason to issue a request for that content. `hf_xet` uses the chunks stored in the directory instead. + +As the `chunk_cache` is purely an optimization, not a guarantee, `hf_xet` utilizes a computationally efficient eviction policy. When the `chunk_cache` is full (see `Limits and Limitations` below), `hf_xet` implements a random eviction policy when selecting an eviction candidate. This significantly reduces the overhead of managing a robust caching system (e.g., LRU) while still providing most of the benefits of caching chunks. + +### `shard_cache` + +This cache is used when uploading content to the Hub. The directory is flat, comprising only of shard files, each using an ID for the shard name. + +```sh + +├─ xet +│ ├─ shard_cache +│ │ ├─ 1fe4ffd5cf0c3375f1ef9aec5016cf773ccc5ca294293d3f92d92771dacfc15d.mdb +│ │ ├─ 906ee184dc1cd0615164a89ed64e8147b3fdccd1163d80d794c66814b3b09992.mdb +│ │ ├─ ceeeb7ea4cf6c0a8d395a2cf9c08871211fbbd17b9b5dc1005811845307e6b8f.mdb +│ │ ├─ e8535155b1b11ebd894c908e91a1e14e3461dddd1392695ddc90ae54a548d8b2.mdb +``` + +The `shard_cache` contains shards that are: + +- Locally generated and successfully uploaded to the CAS +- Downloaded from CAS as part of the global deduplication algorithm + +Shards provide a mapping between files and chunks. During uploads, each file is chunked and the hash of the chunk is saved. Every shard in the cache is then consulted. If a shard contains a chunk hash that is present in the local file being uploaded, then that chunk can be discarded as it is already stored in CAS. + +All shards have an expiration date of 3-4 weeks from when they are downloaded. Shards that are expired are not loaded during upload and are deleted one week after expiration. + +### Limits and Limitations + +The `chunk_cache` is limited to 10GB in size while the `shard_cache` is technically without limits (in practice, the size and use of shards are such that limiting the cache is unnecessary). + +By design, both caches are without high-level APIs. These caches are used primarily to facilitate the reconstruction (download) or upload of a file. To interact with the assets themselves, it’s recommended that you use the [`huggingface_hub` cache system APIs](https://huggingface.co/docs/huggingface_hub/guides/manage-cache). + +If you need to reclaim the space utilized by either cache or need to debug any potential cache-related issues, simply remove the `xet` cache entirely by running `rm -rf ~//xet` where `` is the location of your Hugging Face cache, typically `~/.cache/huggingface` + +Example full `xet`cache directory tree: + +```sh + +├─ xet +│ ├─ chunk_cache +│ │ ├─ L1 +│ │ │ ├─ L1GerURLUcISVivdseeoY1PnYifYkOaCCJ7V5Q9fjgxkZWZhdWx0 +│ │ │ │ ├─ AAAAAAEAAAA5DQAAAAAAAIhRLjDI3SS5jYs4ysNKZiJy9XFI8CN7Ww0UyEA9KPD9 +│ │ │ │ ├─ AQAAAAIAAABzngAAAAAAAPNqPjd5Zby5aBvabF7Z1itCx0ryMwoCnuQcDwq79jlB +│ ├─ shard_cache +│ │ ├─ 1fe4ffd5cf0c3375f1ef9aec5016cf773ccc5ca294293d3f92d92771dacfc15d.mdb +│ │ ├─ 906ee184dc1cd0615164a89ed64e8147b3fdccd1163d80d794c66814b3b09992.mdb +│ │ ├─ ceeeb7ea4cf6c0a8d395a2cf9c08871211fbbd17b9b5dc1005811845307e6b8f.mdb +│ │ ├─ e8535155b1b11ebd894c908e91a1e14e3461dddd1392695ddc90ae54a548d8b2.mdb +``` + +To learn more about Xet Storage, see this [section](https://huggingface.co/docs/hub/storage-backends). + ## Caching assets In addition to caching files from the Hub, downstream libraries often requires to cache @@ -232,7 +323,9 @@ In practice, your assets cache should look like the following tree: └── (...) ``` -## Scan your cache +## Manage your file-based cache + +### Scan your cache At the moment, cached files are never deleted from your local directory: when you download a new revision of a branch, previous files are kept in case you need them again. @@ -240,7 +333,7 @@ Therefore it can be useful to scan your cache directory in order to know which r and revisions are taking the most disk space. `huggingface_hub` provides an helper to do so that can be used via `huggingface-cli` or in a python script. -### Scan cache from the terminal +**Scan cache from the terminal** The easiest way to scan your HF cache-system is to use the `scan-cache` command from `huggingface-cli` tool. This command scans the cache and prints a report with information @@ -291,7 +384,7 @@ Done in 0.0s. Scanned 6 repo(s) for a total of 3.4G. Got 1 warning(s) while scanning. Use -vvv to print details. ``` -#### Grep example +**Grep example** Since the output is in tabular format, you can combine it with any `grep`-like tools to filter the entries. Here is an example to filter only revisions from the "t5-small" @@ -304,7 +397,7 @@ t5-small model d0a119eedb3718e34c648e594394474cf95e0617 t5-small model d78aea13fa7ecd06c29e3e46195d6341255065d5 970.7M 9 1 week ago main /home/wauplin/.cache/huggingface/hub/models--t5-small/snapshots/d78aea13fa7ecd06c29e3e46195d6341255065d5 ``` -### Scan cache from Python +**Scan cache from Python** For a more advanced usage, use [`scan_cache_dir`] which is the python utility called by the CLI tool. @@ -368,7 +461,7 @@ HFCacheInfo( ) ``` -## Clean your cache +### Clean your cache Scanning your cache is interesting but what you really want to do next is usually to delete some portions to free up some space on your drive. This is possible using the @@ -376,7 +469,7 @@ delete some portions to free up some space on your drive. This is possible using [`~HFCacheInfo.delete_revisions`] helper from [`HFCacheInfo`] object returned when scanning the cache. -### Delete strategy +**Delete strategy** To delete some cache, you need to pass a list of revisions to delete. The tool will define a strategy to free up the space based on this list. It returns a @@ -408,7 +501,7 @@ error is thrown. The deletion continues for other paths contained in the -### Clean cache from the terminal +**Clean cache from the terminal** The easiest way to delete some revisions from your HF cache-system is to use the `delete-cache` command from `huggingface-cli` tool. The command has two modes. By @@ -417,7 +510,7 @@ revisions to delete. This TUI is currently in beta as it has not been tested on platforms. If the TUI doesn't work on your machine, you can disable it using the `--disable-tui` flag. -#### Using the TUI +**Using the TUI** This is the default mode. To use it, you first need to install extra dependencies by running the following command: @@ -461,7 +554,7 @@ Start deletion. Done. Deleted 1 repo(s) and 0 revision(s) for a total of 3.1G. ``` -#### Without TUI +**Without TUI** As mentioned above, the TUI mode is currently in beta and is optional. It may be the case that it doesn't work on your machine or that you don't find it convenient. @@ -522,7 +615,7 @@ Example of command file: # 9cfa5647b32c0a30d0adfca06bf198d82192a0d1 # Refs: main # modified 5 days ago ``` -### Clean cache from Python +**Clean cache from Python** For more flexibility, you can also use the [`~HFCacheInfo.delete_revisions`] method programmatically. Here is a simple example. See reference for details. diff --git a/docs/source/en/guides/upload.md b/docs/source/en/guides/upload.md index b7d254a4c1..434b8e284d 100644 --- a/docs/source/en/guides/upload.md +++ b/docs/source/en/guides/upload.md @@ -166,14 +166,17 @@ Check out our [Repository limitations and recommendations](https://huggingface.c - **Start small**: We recommend starting with a small amount of data to test your upload script. It's easier to iterate on a script when failing takes only a little time. - **Expect failures**: Streaming large amounts of data is challenging. You don't know what can happen, but it's always best to consider that something will fail at least once -no matter if it's due to your machine, your connection, or our servers. For example, if you plan to upload a large number of files, it's best to keep track locally of which files you already uploaded before uploading the next batch. You are ensured that an LFS file that is already committed will never be re-uploaded twice but checking it client-side can still save some time. This is what [`upload_large_folder`] does for you. -- **Use `hf_transfer`**: this is a Rust-based [library](https://github.com/huggingface/hf_transfer) meant to speed up uploads on machines with very high bandwidth. To use `hf_transfer`: +- **Use `hf_xet`**: this leverages the new storage backend for Hub, is written in Rust, and is being rolled out to users right now. In order to upload using `hf_xet` your repo must be enabled to use the Xet storage backend. It is being rolled out now, so join the [waitlist](https://huggingface.co/join/xet) to get onboarded soon! +- **Use `hf_transfer`**: this is a Rust-based [library](https://github.com/huggingface/hf_transfer) meant to speed up uploads on machines with very high bandwidth (uploads LFS files). To use `hf_transfer`: 1. Specify the `hf_transfer` extra when installing `huggingface_hub` (i.e., `pip install huggingface_hub[hf_transfer]`). 2. Set `HF_HUB_ENABLE_HF_TRANSFER=1` as an environment variable. -`hf_transfer` is a power user tool! It is tested and production-ready, but it lacks user-friendly features like advanced error handling or proxies. For more details, please take a look at this [section](https://huggingface.co/docs/huggingface_hub/hf_transfer). +`hf_transfer` is a power user tool for uploading LFS files! It is tested and production-ready, but it is less future-proof and lacks user-friendly features like advanced error handling or proxies. For more details, please take a look at this [section](https://huggingface.co/docs/huggingface_hub/hf_transfer). + +Note that `hf_xet` and `hf_transfer` tools are mutually exclusive. The former is used to upload files to Xet-enabled repos while the later uploads LFS files to regular repos. @@ -182,6 +185,25 @@ Check out our [Repository limitations and recommendations](https://huggingface.c In most cases, you won't need more than [`upload_file`] and [`upload_folder`] to upload your files to the Hub. However, `huggingface_hub` has more advanced features to make things easier. Let's have a look at them! +### Faster Uploads + +Take advantage of faster uploads through `hf_xet`, the Python binding to the [`xet-core`](https://github.com/huggingface/xet-core) library that enables chunk-based deduplication for faster uploads and downloads. `hf_xet` integrates seamlessly with `huggingface_hub`, but uses the Rust `xet-core` library and Xet storage instead of LFS. + + + +Xet storage is being rolled out to Hugging Face Hub users at this time, so xet uploads may need to be enabled for your repo for `hf_xet` to actually upload to the Xet backend. Join the [waitlist](https://huggingface.co/join/xet) to get onboarded soon! Also, `hf_xet` today only works with files on the file system, so cannot be used with file-like objects (byte-arrays, buffers). + + + +`hf_xet` uses the Xet storage system, which breaks files down into immutable chunks, storing collections of these chunks (called blocks or xorbs) remotely and retrieving them to reassemble the file when requested. When uploading, after confirming the user is authorized to write to this repo, `hf_xet` will scan the files, breaking them down into their chunks and collecting those chunks into xorbs (and deduplicating across known chunks), and then will be upload these xorbs to the Xet content-addressable service (CAS), which will verify the integrity of the xorbs, register the xorb metadata along with the LFS SHA256 hash (to support lookup/download), and write the xorbs to remote storage. + +To enable it, specify the `hf_xet` extra when installing `huggingface_hub`: + +```bash +pip install -U huggingface_hub[hf_xet] +``` + +All other `huggingface_hub` APIs will continue to work without any modification. To learn more about the benefits of Xet storage and `hf_xet`, refer to this [section](https://huggingface.co/docs/hub/storage-backends). ### Non-blocking uploads diff --git a/docs/source/en/package_reference/environment_variables.md b/docs/source/en/package_reference/environment_variables.md index 4ab3cc3cd6..4776ffcb06 100644 --- a/docs/source/en/package_reference/environment_variables.md +++ b/docs/source/en/package_reference/environment_variables.md @@ -36,6 +36,12 @@ spaces). Defaults to `"$HF_HOME/hub"` (e.g. `"~/.cache/huggingface/hub"` by default). +### HF_XET_CACHE + +To configure where Xet chunks (byte ranges from files managed by Xet backend) are cached locally. + +Defaults to `"$HF_HOME/xet"` (e.g. `"~/.cache/huggingface/xet"` by default). + ### HF_ASSETS_CACHE To configure where [assets](../guides/manage-cache#caching-assets) created by downstream libraries @@ -158,6 +164,14 @@ To use `hf_transfer`: Please note that using `hf_transfer` comes with certain limitations. Since it is not purely Python-based, debugging errors may be challenging. Additionally, `hf_transfer` lacks several user-friendly features such as resumable downloads and proxies. These omissions are intentional to maintain the simplicity and speed of the Rust logic. Consequently, `hf_transfer` is not enabled by default in `huggingface_hub`. + + +`hf_xet` is an alternative to `hf_transfer`. It provides efficient file transfers through a chunk-based deduplication strategy, custom Xet storage (replacing Git LFS), and a seamless integration with `huggingface_hub`. + +[Read more about the package](https://huggingface.co/docs/hub/storage-backends) and enable with `pip install huggingface_hub[hf_xet]`. + + + ## Deprecated environment variables In order to standardize all environment variables within the Hugging Face ecosystem, some variables have been marked as deprecated. Although they remain functional, they no longer take precedence over their replacements. The following table outlines the deprecated variables and their corresponding alternatives: diff --git a/setup.py b/setup.py index 425facb3d8..b1533fd001 100644 --- a/setup.py +++ b/setup.py @@ -55,6 +55,7 @@ def get_version() -> str: "keras<3.0", ] +extras["hf_xet"] = ["hf_xet>=0.1.4"] extras["testing"] = ( extras["cli"] diff --git a/src/huggingface_hub/_commit_api.py b/src/huggingface_hub/_commit_api.py index 783a3d2e3f..b19ddedbcb 100644 --- a/src/huggingface_hub/_commit_api.py +++ b/src/huggingface_hub/_commit_api.py @@ -4,6 +4,7 @@ import base64 import io +import math import os import warnings from collections import defaultdict @@ -16,12 +17,14 @@ from tqdm.contrib.concurrent import thread_map from . import constants -from .errors import EntryNotFoundError +from .errors import EntryNotFoundError, HfHubHTTPError, XetAuthorizationError, XetRefreshTokenError from .file_download import hf_hub_url from .lfs import UploadInfo, lfs_upload, post_lfs_batch_info from .utils import ( FORBIDDEN_FOLDERS, + XetTokenType, chunk_iterable, + fetch_xet_connection_info_from_repo_info, get_session, hf_raise_for_status, logging, @@ -30,6 +33,7 @@ validate_hf_hub_args, ) from .utils import tqdm as hf_tqdm +from .utils.tqdm import _get_progress_bar_context if TYPE_CHECKING: @@ -47,6 +51,8 @@ # See https://github.com/huggingface/huggingface_hub/issues/1503 FETCH_LFS_BATCH_SIZE = 500 +UPLOAD_BATCH_MAX_NUM_FILES = 256 + @dataclass class CommitOperationDelete: @@ -391,7 +397,7 @@ def _upload_lfs_files( # Upload instructions are retrieved by chunk of 256 files to avoid reaching # the payload limit. batch_actions: List[Dict] = [] - for chunk in chunk_iterable(additions, chunk_size=256): + for chunk in chunk_iterable(additions, chunk_size=UPLOAD_BATCH_MAX_NUM_FILES): batch_actions_chunk, batch_errors_chunk = post_lfs_batch_info( upload_infos=[op.upload_info for op in chunk], repo_id=repo_id, @@ -458,6 +464,138 @@ def _wrapped_lfs_upload(batch_action) -> None: ) +@validate_hf_hub_args +def _upload_xet_files( + *, + additions: List[CommitOperationAdd], + repo_type: str, + repo_id: str, + headers: Dict[str, str], + endpoint: Optional[str] = None, + revision: Optional[str] = None, + create_pr: Optional[bool] = None, +): + """ + Uploads the content of `additions` to the Hub using the xet storage protocol. + This chunks the files and deduplicates the chunks before uploading them to xetcas storage. + + Args: + additions (`List` of `CommitOperationAdd`): + The files to be uploaded. + repo_type (`str`): + Type of the repo to upload to: `"model"`, `"dataset"` or `"space"`. + repo_id (`str`): + A namespace (user or an organization) and a repo name separated + by a `/`. + headers (`Dict[str, str]`): + Headers to use for the request, including authorization headers and user agent. + endpoint: (`str`, *optional*): + The endpoint to use for the xetcas service. Defaults to `constants.ENDPOINT`. + revision (`str`, *optional*): + The git revision to upload to. + create_pr (`bool`, *optional*): + Whether or not to create a Pull Request with that commit. + + Raises: + [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError) + If an upload failed for any reason. + [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) + If the server returns malformed responses or if the user is unauthorized to upload to xet storage. + [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) + If the LFS batch endpoint returned an HTTP error. + + **How it works:** + The file download system uses Xet storage, which is a content-addressable storage system that breaks files into chunks + for efficient storage and transfer. + + `hf_xet.upload_files` manages uploading files by: + - Taking a list of file paths to upload + - Breaking files into smaller chunks for efficient storage + - Avoiding duplicate storage by recognizing identical chunks across files + - Connecting to a storage server (CAS server) that manages these chunks + + The upload process works like this: + 1. Create a local folder at ~/.cache/huggingface/xet/chunk-cache to store file chunks for reuse. + 2. Process files in parallel (up to 8 files at once): + 2.1. Read the file content. + 2.2. Split the file content into smaller chunks based on content patterns: each chunk gets a unique ID based on what's in it. + 2.3. For each chunk: + - Check if it already exists in storage. + - Skip uploading chunks that already exist. + 2.4. Group chunks into larger blocks for efficient transfer. + 2.5. Upload these blocks to the storage server. + 2.6. Create and upload information about how the file is structured. + 3. Return reference files that contain information about the uploaded files, which can be used later to download them. + """ + if len(additions) == 0: + return + # at this point, we know that hf_xet is installed + from hf_xet import upload_files + + try: + xet_connection_info = fetch_xet_connection_info_from_repo_info( + token_type=XetTokenType.WRITE, + repo_id=repo_id, + repo_type=repo_type, + revision=revision, + headers=headers, + endpoint=endpoint, + params={"create_pr": "1"} if create_pr else None, + ) + except HfHubHTTPError as e: + if e.response.status_code == 401: + raise XetAuthorizationError( + f"You are unauthorized to upload to xet storage for {repo_type}/{repo_id}. " + f"Please check that you have configured your access token with write access to the repo." + ) from e + raise + + xet_endpoint = xet_connection_info.endpoint + access_token_info = (xet_connection_info.access_token, xet_connection_info.expiration_unix_epoch) + + def token_refresher() -> Tuple[str, int]: + new_xet_connection = fetch_xet_connection_info_from_repo_info( + token_type=XetTokenType.WRITE, + repo_id=repo_id, + repo_type=repo_type, + revision=revision, + headers=headers, + endpoint=endpoint, + params={"create_pr": "1"} if create_pr else None, + ) + if new_xet_connection is None: + raise XetRefreshTokenError("Failed to refresh xet token") + return new_xet_connection.access_token, new_xet_connection.expiration_unix_epoch + + num_chunks = math.ceil(len(additions) / UPLOAD_BATCH_MAX_NUM_FILES) + num_chunks_num_digits = int(math.log10(num_chunks)) + 1 + for i, chunk in enumerate(chunk_iterable(additions, chunk_size=UPLOAD_BATCH_MAX_NUM_FILES)): + _chunk = [op for op in chunk] + paths = [str(op.path_or_fileobj) for op in _chunk] + expected_size = sum([os.path.getsize(path) for path in paths]) + + if num_chunks > 1: + description = f"Uploading Batch [{str(i + 1).zfill(num_chunks_num_digits)}/{num_chunks}]..." + else: + description = "Uploading..." + progress_cm = _get_progress_bar_context( + desc=description, + total=expected_size, + initial=0, + unit="B", + unit_scale=True, + name="huggingface_hub.xet_put", + log_level=logger.getEffectiveLevel(), + ) + with progress_cm as progress: + + def update_progress(increment: int): + progress.update(increment) + + upload_files(paths, xet_endpoint, access_token_info, token_refresher, update_progress, repo_type) + return + + def _validate_preupload_info(preupload_info: dict): files = preupload_info.get("files") if not isinstance(files, list): @@ -485,8 +623,8 @@ def _fetch_upload_modes( gitignore_content: Optional[str] = None, ) -> None: """ - Requests the Hub "preupload" endpoint to determine whether each input file should be uploaded as a regular git blob - or as git LFS blob. Input `additions` are mutated in-place with the upload mode. + Requests the Hub "preupload" endpoint to determine whether each input file should be uploaded as a regular git blob, + as a git LFS blob, or as a XET file. Input `additions` are mutated in-place with the upload mode. Args: additions (`Iterable` of :class:`CommitOperationAdd`): diff --git a/src/huggingface_hub/constants.py b/src/huggingface_hub/constants.py index 1f0ef4c3d2..758a1fa7ca 100644 --- a/src/huggingface_hub/constants.py +++ b/src/huggingface_hub/constants.py @@ -257,3 +257,16 @@ def _as_int(value: Optional[str]) -> Optional[int]: "stanza", "timm", ] + +# Xet constants + + +HUGGINGFACE_HEADER_X_XET_ENDPOINT = "X-Xet-Cas-Url" +HUGGINGFACE_HEADER_X_XET_ACCESS_TOKEN = "X-Xet-Access-Token" +HUGGINGFACE_HEADER_X_XET_EXPIRATION = "X-Xet-Token-Expiration" +HUGGINGFACE_HEADER_X_XET_HASH = "X-Xet-Hash" +HUGGINGFACE_HEADER_X_XET_REFRESH_ROUTE = "X-Xet-Refresh-Route" +HUGGINGFACE_HEADER_LINK_XET_AUTH_KEY = "xet-auth" + +default_xet_cache_path = os.path.join(HF_HOME, "xet") +HF_XET_CACHE = os.getenv("HF_XET_CACHE", default_xet_cache_path) diff --git a/src/huggingface_hub/errors.py b/src/huggingface_hub/errors.py index 226c8bb400..7b09e180bf 100644 --- a/src/huggingface_hub/errors.py +++ b/src/huggingface_hub/errors.py @@ -327,3 +327,22 @@ class DDUFExportError(DDUFError): class DDUFInvalidEntryNameError(DDUFExportError): """Exception thrown when the entry name is invalid.""" + + +# XET ERRORS + + +class XetError(Exception): + """Base exception for errors related to Xet Storage.""" + + +class XetAuthorizationError(XetError): + """Exception thrown when the user does not have the right authorization to use Xet Storage.""" + + +class XetRefreshTokenError(XetError): + """Exception thrown when the refresh token is invalid.""" + + +class XetDownloadError(Exception): + """Exception thrown when the download from Xet Storage fails.""" diff --git a/src/huggingface_hub/file_download.py b/src/huggingface_hub/file_download.py index e9f3d9fba7..e8d66bb8be 100644 --- a/src/huggingface_hub/file_download.py +++ b/src/huggingface_hub/file_download.py @@ -1,4 +1,3 @@ -import contextlib import copy import errno import inspect @@ -38,6 +37,7 @@ OfflineModeIsEnabled, SoftTemporaryDirectory, WeakFileLock, + XetFileData, build_hf_headers, get_fastai_version, # noqa: F401 # for backward compatibility get_fastcore_version, # noqa: F401 # for backward compatibility @@ -56,15 +56,17 @@ is_tf_available, # noqa: F401 # for backward compatibility is_torch_available, # noqa: F401 # for backward compatibility logging, + parse_xet_file_data_from_response, + refresh_xet_connection_info, reset_sessions, tqdm, validate_hf_hub_args, ) from .utils._http import _adjust_range_header -from .utils._runtime import _PY_VERSION # noqa: F401 # for backward compatibility +from .utils._runtime import _PY_VERSION, is_xet_available # noqa: F401 # for backward compatibility from .utils._typing import HTTP_METHOD_T from .utils.sha import sha_fileobj -from .utils.tqdm import is_tqdm_disabled +from .utils.tqdm import _get_progress_bar_context logger = logging.get_logger(__name__) @@ -160,12 +162,15 @@ class HfFileMetadata: size (`size`): Size of the file. In case of an LFS file, contains the size of the actual LFS file, not the pointer. + xet_file_data (`XetFileData`, *optional*): + Xet information for the file. This is only set if the file is stored using Xet storage. """ commit_hash: Optional[str] etag: Optional[str] location: str size: Optional[int] + xet_file_data: Optional[XetFileData] @validate_hf_hub_args @@ -396,23 +401,13 @@ def http_get( f" {{actual_size}} ({displayed_filename}).\nThis is usually due to network issues while downloading the file." " Please retry with `force_download=True`." ) - - # Stream file to buffer - progress_cm: tqdm = ( - tqdm( # type: ignore[assignment] - unit="B", - unit_scale=True, - total=total, - initial=resume_size, - desc=displayed_filename, - disable=is_tqdm_disabled(logger.getEffectiveLevel()), - name="huggingface_hub.http_get", - ) - if _tqdm_bar is None - else contextlib.nullcontext(_tqdm_bar) - # ^ `contextlib.nullcontext` mimics a context manager that does nothing - # Makes it easier to use the same code path for both cases but in the later - # case, the progress bar is not closed when exiting the context manager. + progress_cm = _get_progress_bar_context( + desc=displayed_filename, + log_level=logger.getEffectiveLevel(), + total=total, + initial=resume_size, + name="huggingface_hub.http_get", + _tqdm_bar=_tqdm_bar, ) with progress_cm as progress: @@ -487,6 +482,110 @@ def http_get( ) +def xet_get( + *, + incomplete_path: Path, + xet_file_data: XetFileData, + headers: Dict[str, str], + expected_size: Optional[int] = None, + displayed_filename: Optional[str] = None, + _tqdm_bar: Optional[tqdm] = None, +) -> None: + """ + Download a file using Xet storage service. + + Args: + incomplete_path (`Path`): + The path to the file to download. + xet_file_data (`XetFileData`): + The file metadata needed to make the request to the xet storage service. + headers (`Dict[str, str]`): + The headers to send to the xet storage service. + expected_size (`int`, *optional*): + The expected size of the file to download. If set, the download will raise an error if the size of the + received content is different from the expected one. + displayed_filename (`str`, *optional*): + The filename of the file that is being downloaded. Value is used only to display a nice progress bar. If + not set, the filename is guessed from the URL or the `Content-Disposition` header. + + **How it works:** + The file download system uses Xet storage, which is a content-addressable storage system that breaks files into chunks + for efficient storage and transfer. + + `hf_xet.download_files` manages downloading files by: + - Taking a list of files to download (each with its unique content hash) + - Connecting to a storage server (CAS server) that knows how files are chunked + - Using authentication to ensure secure access + - Providing progress updates during download + + Authentication works by regularly refreshing access tokens through `refresh_xet_connection_info` to maintain a valid + connection to the storage server. + + The download process works like this: + 1. Create a local cache folder at `~/.cache/huggingface/xet/chunk-cache` to store reusable file chunks + 2. Download files in parallel: + 2.1. Prepare to write the file to disk + 2.2. Ask the server "how is this file split into chunks?" using the file's unique hash + The server responds with: + - Which chunks make up the complete file + - Where each chunk can be downloaded from + 2.3. For each needed chunk: + - Checks if we already have it in our local cache + - If not, download it from cloud storage (S3) + - Save it to cache for future use + - Assemble the chunks in order to recreate the original file + + """ + try: + from hf_xet import PyPointerFile, download_files # type: ignore[no-redef] + except ImportError: + raise ValueError( + "To use optimized download using Xet storage, you need to install the hf_xet package. " + "Try `pip install huggingface_hub[hf_xet]` or `pip install hf_xet`." + ) + + connection_info = refresh_xet_connection_info(file_data=xet_file_data, headers=headers) + + def token_refresher() -> Tuple[str, int]: + connection_info = refresh_xet_connection_info(file_data=xet_file_data, headers=headers) + if connection_info is None: + raise ValueError("Failed to refresh token using xet metadata.") + return connection_info.access_token, connection_info.expiration_unix_epoch + + pointer_files = [ + PyPointerFile(path=str(incomplete_path.absolute()), hash=xet_file_data.file_hash, filesize=expected_size) + ] + + if not displayed_filename: + displayed_filename = incomplete_path.name + + # Truncate filename if too long to display + if len(displayed_filename) > 40: + displayed_filename = f"{displayed_filename[:40]}(…)" + + progress_cm = _get_progress_bar_context( + desc=displayed_filename, + log_level=logger.getEffectiveLevel(), + total=expected_size, + initial=0, + name="huggingface_hub.xet_get", + _tqdm_bar=_tqdm_bar, + ) + + with progress_cm as progress: + + def progress_updater(progress_bytes: float): + progress.update(progress_bytes) + + download_files( + pointer_files, + endpoint=connection_info.endpoint, + token_info=(connection_info.access_token, connection_info.expiration_unix_epoch), + token_refresher=token_refresher, + progress_updater=[progress_updater], + ) + + def _normalize_etag(etag: Optional[str]) -> Optional[str]: """Normalize ETag HTTP header, so it can be used to create nice filepaths. @@ -922,7 +1021,7 @@ def _hf_hub_download_to_cache_dir( # Try to get metadata (etag, commit_hash, url, size) from the server. # If we can't, a HEAD request error is returned. - (url_to_download, etag, commit_hash, expected_size, head_call_error) = _get_metadata_or_catch_error( + (url_to_download, etag, commit_hash, expected_size, xet_file_data, head_call_error) = _get_metadata_or_catch_error( repo_id=repo_id, filename=filename, repo_type=repo_type, @@ -1006,6 +1105,8 @@ def _hf_hub_download_to_cache_dir( if os.name == "nt" and len(os.path.abspath(blob_path)) > 255: blob_path = "\\\\?\\" + os.path.abspath(blob_path) + # Local file doesn't exist or etag isn't a match => retrieve file from remote (or cache) + Path(lock_path).parent.mkdir(parents=True, exist_ok=True) with WeakFileLock(lock_path): _download_to_tmp_and_move( @@ -1017,6 +1118,8 @@ def _hf_hub_download_to_cache_dir( expected_size=expected_size, filename=filename, force_download=force_download, + etag=etag, + xet_file_data=xet_file_data, ) if not os.path.exists(pointer_path): _create_symlink(blob_path, pointer_path, new_blob=True) @@ -1067,7 +1170,7 @@ def _hf_hub_download_to_local_dir( return str(paths.file_path) # Local file doesn't exist or commit_hash doesn't match => we need the etag - (url_to_download, etag, commit_hash, expected_size, head_call_error) = _get_metadata_or_catch_error( + (url_to_download, etag, commit_hash, expected_size, xet_file_data, head_call_error) = _get_metadata_or_catch_error( repo_id=repo_id, filename=filename, repo_type=repo_type, @@ -1144,6 +1247,8 @@ def _hf_hub_download_to_local_dir( expected_size=expected_size, filename=filename, force_download=force_download, + etag=etag, + xet_file_data=xet_file_data, ) write_download_metadata(local_dir=local_dir, filename=filename, commit_hash=commit_hash, etag=etag) @@ -1317,6 +1422,7 @@ def get_hf_file_metadata( size=_int_or_none( r.headers.get(constants.HUGGINGFACE_HEADER_X_LINKED_SIZE) or r.headers.get("Content-Length") ), + xet_file_data=parse_xet_file_data_from_response(r), # type: ignore ) @@ -1336,10 +1442,10 @@ def _get_metadata_or_catch_error( storage_folder: Optional[str] = None, # only used to store `.no_exists` in cache ) -> Union[ # Either an exception is caught and returned - Tuple[None, None, None, None, Exception], + Tuple[None, None, None, None, None, Exception], # Or the metadata is returned as - # `(url_to_download, etag, commit_hash, expected_size, None)` - Tuple[str, str, str, int, None], + # `(url_to_download, etag, commit_hash, expected_size, xet_file_data, None)` + Tuple[str, str, str, int, Optional[XetFileData], None], ]: """Get metadata for a file on the Hub, safely handling network issues. @@ -1356,6 +1462,7 @@ def _get_metadata_or_catch_error( None, None, None, + None, OfflineModeIsEnabled( f"Cannot access file since 'local_files_only=True' as been set. (repo_id: {repo_id}, repo_type: {repo_type}, revision: {revision}, filename: {filename})" ), @@ -1367,6 +1474,7 @@ def _get_metadata_or_catch_error( commit_hash: Optional[str] = None expected_size: Optional[int] = None head_error_call: Optional[Exception] = None + xet_file_data: Optional[XetFileData] = None # Try to get metadata from the server. # Do not raise yet if the file is not found or not accessible. @@ -1414,13 +1522,15 @@ def _get_metadata_or_catch_error( if expected_size is None: raise FileMetadataError("Distant resource does not have a Content-Length.") + xet_file_data = metadata.xet_file_data + # In case of a redirect, save an extra redirect on the request.get call, # and ensure we download the exact atomic version even if it changed # between the HEAD and the GET (unlikely, but hey). # # If url domain is different => we are downloading from a CDN => url is signed => don't send auth # If url domain is the same => redirect due to repo rename AND downloading a regular file => keep auth - if url != metadata.location: + if xet_file_data is None and url != metadata.location: url_to_download = metadata.location if urlparse(url).netloc != urlparse(metadata.location).netloc: # Remove authorization header when downloading a LFS blob @@ -1458,7 +1568,7 @@ def _get_metadata_or_catch_error( if not (local_files_only or etag is not None or head_error_call is not None): raise RuntimeError("etag is empty due to uncovered problems") - return (url_to_download, etag, commit_hash, expected_size, head_error_call) # type: ignore [return-value] + return (url_to_download, etag, commit_hash, expected_size, xet_file_data, head_error_call) # type: ignore [return-value] def _raise_on_head_call_error(head_call_error: Exception, force_download: bool, local_files_only: bool) -> NoReturn: @@ -1502,6 +1612,8 @@ def _download_to_tmp_and_move( expected_size: Optional[int], filename: str, force_download: bool, + etag: Optional[str], + xet_file_data: Optional[XetFileData], ) -> None: """Download content from a URL to a destination path. @@ -1544,14 +1656,30 @@ def _download_to_tmp_and_move( _check_disk_space(expected_size, incomplete_path.parent) _check_disk_space(expected_size, destination_path.parent) - http_get( - url_to_download, - f, - proxies=proxies, - resume_size=resume_size, - headers=headers, - expected_size=expected_size, - ) + if xet_file_data is not None and is_xet_available(): + logger.info("Xet Storage is enabled for this repo. Downloading file from Xet Storage..") + xet_get( + incomplete_path=incomplete_path, + xet_file_data=xet_file_data, + headers=headers, + expected_size=expected_size, + displayed_filename=filename, + ) + else: + if xet_file_data is not None: + logger.warning( + "Xet Storage is enabled for this repo, but the 'hf_xet' package is not installed. " + "Falling back to regular HTTP download. " + "For better performance, install the package with: `pip install huggingface_hub[hf_xet]` or `pip install hf_xet`" + ) + http_get( + url_to_download, + f, + proxies=proxies, + resume_size=resume_size, + headers=headers, + expected_size=expected_size, + ) logger.info(f"Download complete. Moving file to {destination_path}") _chmod_and_move(incomplete_path, destination_path) diff --git a/src/huggingface_hub/hf_api.py b/src/huggingface_hub/hf_api.py index fdbaf96fae..a994915244 100644 --- a/src/huggingface_hub/hf_api.py +++ b/src/huggingface_hub/hf_api.py @@ -15,6 +15,7 @@ from __future__ import annotations import inspect +import io import json import re import struct @@ -41,7 +42,7 @@ Union, overload, ) -from urllib.parse import quote +from urllib.parse import quote, unquote import requests from requests.exceptions import HTTPError @@ -58,6 +59,7 @@ _fetch_upload_modes, _prepare_commit_payload, _upload_lfs_files, + _upload_xet_files, _warn_on_overwriting_operations, ) from ._inference_endpoints import InferenceEndpoint, InferenceEndpointType @@ -127,6 +129,7 @@ from .utils import tqdm as hf_tqdm from .utils._auth import _get_token_from_environment, _get_token_from_file, _get_token_from_google_colab from .utils._deprecation import _deprecate_method +from .utils._runtime import is_xet_available from .utils._typing import CallableT from .utils.endpoint_helpers import _is_emission_within_threshold @@ -3837,6 +3840,7 @@ def update_repo_settings( private: Optional[bool] = None, token: Union[str, bool, None] = None, repo_type: Optional[str] = None, + xet_enabled: Optional[bool] = None, ) -> None: """ Update the settings of a repository, including gated access and visibility. @@ -3862,7 +3866,8 @@ def update_repo_settings( repo_type (`str`, *optional*): The type of the repository to update settings from (`"model"`, `"dataset"` or `"space"`). Defaults to `"model"`. - + xet_enabled (`bool`, *optional*): + Whether the repository should be enabled for Xet Storage. Raises: [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) If gated is not one of "auto", "manual", or False. @@ -3880,13 +3885,6 @@ def update_repo_settings( if repo_type is None: repo_type = constants.REPO_TYPE_MODEL # default repo type - # Check if both gated and private are None - if gated is None and private is None: - raise ValueError("At least one of 'gated' or 'private' must be provided.") - - # Build headers - headers = self._build_hf_headers(token=token) - # Prepare the JSON payload for the PUT request payload: Dict = {} @@ -3898,6 +3896,15 @@ def update_repo_settings( if private is not None: payload["private"] = private + if xet_enabled is not None: + payload["xetEnabled"] = xet_enabled + + if len(payload) == 0: + raise ValueError("At least one setting must be updated.") + + # Build headers + headers = self._build_hf_headers(token=token) + r = get_session().put( url=f"{self.endpoint}/api/{repo_type}s/{repo_id}/settings", headers=headers, @@ -4435,20 +4442,45 @@ def preupload_lfs_files( f"Skipped upload for {len(new_lfs_additions) - len(new_lfs_additions_to_upload)} LFS file(s) " "(ignored by gitignore file)." ) - - # Upload new LFS files - _upload_lfs_files( - additions=new_lfs_additions_to_upload, - repo_type=repo_type, - repo_id=repo_id, - headers=headers, - endpoint=self.endpoint, - num_threads=num_threads, + # Prepare upload parameters + upload_kwargs = { + "additions": new_lfs_additions_to_upload, + "repo_type": repo_type, + "repo_id": repo_id, + "headers": headers, + "endpoint": self.endpoint, # If `create_pr`, we don't want to check user permission on the revision as users with read permission # should still be able to create PRs even if they don't have write permission on the target branch of the # PR (i.e. `revision`). - revision=revision if not create_pr else None, + "revision": revision if not create_pr else None, + } + # Upload files using Xet protocol if all of the following are true: + # - xet is enabled for the repo, + # - the files are provided as str or paths objects, + # - the library is installed. + # Otherwise, default back to LFS. + xet_enabled = self.repo_info( + repo_id=repo_id, + repo_type=repo_type, + revision=unquote(revision) if revision is not None else revision, + expand="xetEnabled", + token=token, + ).xet_enabled + has_binary_data = any( + isinstance(addition.path_or_fileobj, (bytes, io.BufferedIOBase)) + for addition in new_lfs_additions_to_upload ) + if xet_enabled and not has_binary_data and is_xet_available(): + logger.info("Uploading files using Xet Storage..") + _upload_xet_files(**upload_kwargs, create_pr=create_pr) # type: ignore [arg-type] + else: + if xet_enabled and is_xet_available(): + if has_binary_data: + logger.warning( + "Uploading files as bytes or binary IO objects is not supported by Xet Storage. " + "Falling back to HTTP upload." + ) + _upload_lfs_files(**upload_kwargs, num_threads=num_threads) # type: ignore [arg-type] for addition in new_lfs_additions_to_upload: addition._is_uploaded = True if free_memory: diff --git a/src/huggingface_hub/utils/__init__.py b/src/huggingface_hub/utils/__init__.py index b9715dc0ad..ab6f90b157 100644 --- a/src/huggingface_hub/utils/__init__.py +++ b/src/huggingface_hub/utils/__init__.py @@ -107,4 +107,12 @@ from ._telemetry import send_telemetry from ._typing import is_jsonable, is_simple_optional_type, unwrap_simple_optional_type from ._validators import smoothly_deprecate_use_auth_token, validate_hf_hub_args, validate_repo_id +from ._xet import ( + XetConnectionInfo, + XetFileData, + XetTokenType, + fetch_xet_connection_info_from_repo_info, + parse_xet_file_data_from_response, + refresh_xet_connection_info, +) from .tqdm import are_progress_bars_disabled, disable_progress_bars, enable_progress_bars, tqdm, tqdm_stream_file diff --git a/src/huggingface_hub/utils/_runtime.py b/src/huggingface_hub/utils/_runtime.py index c8d82d4129..6ff4913f86 100644 --- a/src/huggingface_hub/utils/_runtime.py +++ b/src/huggingface_hub/utils/_runtime.py @@ -36,6 +36,7 @@ "gradio": {"gradio"}, "graphviz": {"graphviz"}, "hf_transfer": {"hf_transfer"}, + "hf_xet": {"hf_xet"}, "jinja": {"Jinja2"}, "keras": {"keras"}, "numpy": {"numpy"}, @@ -151,6 +152,15 @@ def get_hf_transfer_version() -> str: return _get_version("hf_transfer") +# xet +def is_xet_available() -> bool: + return is_package_available("hf_xet") + + +def get_xet_version() -> str: + return _get_version("hf_xet") + + # keras def is_keras_available() -> bool: return is_package_available("keras") @@ -357,6 +367,7 @@ def dump_environment_info() -> Dict[str, Any]: info["numpy"] = get_numpy_version() info["pydantic"] = get_pydantic_version() info["aiohttp"] = get_aiohttp_version() + info["hf_xet"] = get_xet_version() # Environment variables info["ENDPOINT"] = constants.ENDPOINT diff --git a/src/huggingface_hub/utils/_xet.py b/src/huggingface_hub/utils/_xet.py new file mode 100644 index 0000000000..d6bcea1dad --- /dev/null +++ b/src/huggingface_hub/utils/_xet.py @@ -0,0 +1,199 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Dict, Optional + +import requests + +from .. import constants +from . import get_session, hf_raise_for_status, validate_hf_hub_args + + +class XetTokenType(str, Enum): + READ = "read" + WRITE = "write" + + +@dataclass(frozen=True) +class XetFileData: + file_hash: str + refresh_route: str + + +@dataclass(frozen=True) +class XetConnectionInfo: + access_token: str + expiration_unix_epoch: int + endpoint: str + + +def parse_xet_file_data_from_response(response: requests.Response) -> Optional[XetFileData]: + """ + Parse XET file metadata from an HTTP response. + + This function extracts XET file metadata from the HTTP headers or HTTP links + of a given response object. If the required metadata is not found, it returns `None`. + + Args: + response (`requests.Response`): + The HTTP response object containing headers dict and links dict to extract the XET metadata from. + Returns: + `Optional[XetFileData]`: + An instance of `XetFileData` containing the file hash and refresh route if the metadata + is found. Returns `None` if the required metadata is missing. + """ + if response is None: + return None + try: + file_hash = response.headers[constants.HUGGINGFACE_HEADER_X_XET_HASH] + + if constants.HUGGINGFACE_HEADER_LINK_XET_AUTH_KEY in response.links: + refresh_route = response.links[constants.HUGGINGFACE_HEADER_LINK_XET_AUTH_KEY]["url"] + else: + refresh_route = response.headers[constants.HUGGINGFACE_HEADER_X_XET_REFRESH_ROUTE] + except KeyError: + return None + + return XetFileData( + file_hash=file_hash, + refresh_route=refresh_route, + ) + + +def parse_xet_connection_info_from_headers(headers: Dict[str, str]) -> Optional[XetConnectionInfo]: + """ + Parse XET connection info from the HTTP headers or return None if not found. + Args: + headers (`Dict`): + HTTP headers to extract the XET metadata from. + Returns: + `XetConnectionInfo` or `None`: + The information needed to connect to the XET storage service. + Returns `None` if the headers do not contain the XET connection info. + """ + try: + endpoint = headers[constants.HUGGINGFACE_HEADER_X_XET_ENDPOINT] + access_token = headers[constants.HUGGINGFACE_HEADER_X_XET_ACCESS_TOKEN] + expiration_unix_epoch = int(headers[constants.HUGGINGFACE_HEADER_X_XET_EXPIRATION]) + except (KeyError, ValueError, TypeError): + return None + + return XetConnectionInfo( + endpoint=endpoint, + access_token=access_token, + expiration_unix_epoch=expiration_unix_epoch, + ) + + +@validate_hf_hub_args +def refresh_xet_connection_info( + *, + file_data: XetFileData, + headers: Dict[str, str], + endpoint: Optional[str] = None, +) -> XetConnectionInfo: + """ + Utilizes the information in the parsed metadata to request the Hub xet connection information. + This includes the access token, expiration, and XET service URL. + Args: + file_data: (`XetFileData`): + The file data needed to refresh the xet connection information. + headers (`Dict[str, str]`): + Headers to use for the request, including authorization headers and user agent. + endpoint (`str`, `optional`): + The endpoint to use for the request. Defaults to the Hub endpoint. + Returns: + `XetConnectionInfo`: + The connection information needed to make the request to the xet storage service. + Raises: + [`~utils.HfHubHTTPError`] + If the Hub API returned an error. + [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) + If the Hub API response is improperly formatted. + """ + if file_data.refresh_route is None: + raise ValueError("The provided xet metadata does not contain a refresh endpoint.") + endpoint = endpoint if endpoint is not None else constants.ENDPOINT + + # TODO: An upcoming version of hub will prepend the endpoint to the refresh route in + # the headers. Once that's deployed we can call fetch on the refresh route directly. + url = file_data.refresh_route + if url.startswith("/"): + url = f"{endpoint}{url}" + + return _fetch_xet_connection_info_with_url(url, headers) + + +@validate_hf_hub_args +def fetch_xet_connection_info_from_repo_info( + *, + token_type: XetTokenType, + repo_id: str, + repo_type: str, + revision: Optional[str] = None, + headers: Dict[str, str], + endpoint: Optional[str] = None, + params: Optional[Dict[str, str]] = None, +) -> XetConnectionInfo: + """ + Uses the repo info to request a xet access token from Hub. + Args: + token_type (`XetTokenType`): + Type of the token to request: `"read"` or `"write"`. + repo_id (`str`): + A namespace (user or an organization) and a repo name separated by a `/`. + repo_type (`str`): + Type of the repo to upload to: `"model"`, `"dataset"` or `"space"`. + revision (`str`, `optional`): + The revision of the repo to get the token for. + headers (`Dict[str, str]`): + Headers to use for the request, including authorization headers and user agent. + endpoint (`str`, `optional`): + The endpoint to use for the request. Defaults to the Hub endpoint. + params (`Dict[str, str]`, `optional`): + Additional parameters to pass with the request. + Returns: + `XetConnectionInfo`: + The connection information needed to make the request to the xet storage service. + Raises: + [`~utils.HfHubHTTPError`] + If the Hub API returned an error. + [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) + If the Hub API response is improperly formatted. + """ + endpoint = endpoint if endpoint is not None else constants.ENDPOINT + url = f"{endpoint}/api/{repo_type}s/{repo_id}/xet-{token_type.value}-token/{revision}" + return _fetch_xet_connection_info_with_url(url, headers, params) + + +@validate_hf_hub_args +def _fetch_xet_connection_info_with_url( + url: str, + headers: Dict[str, str], + params: Optional[Dict[str, str]] = None, +) -> XetConnectionInfo: + """ + Requests the xet connection info from the supplied URL. This includes the + access token, expiration time, and endpoint to use for the xet storage service. + Args: + url: (`str`): + The access token endpoint URL. + headers (`Dict[str, str]`): + Headers to use for the request, including authorization headers and user agent. + params (`Dict[str, str]`, `optional`): + Additional parameters to pass with the request. + Returns: + `XetConnectionInfo`: + The connection information needed to make the request to the xet storage service. + Raises: + [`~utils.HfHubHTTPError`] + If the Hub API returned an error. + [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) + If the Hub API response is improperly formatted. + """ + resp = get_session().get(headers=headers, url=url, params=params) + hf_raise_for_status(resp) + + metadata = parse_xet_connection_info_from_headers(resp.headers) # type: ignore + if metadata is None: + raise ValueError("Xet headers have not been correctly set by the server.") + return metadata diff --git a/src/huggingface_hub/utils/tqdm.py b/src/huggingface_hub/utils/tqdm.py index b22b797c32..4c1fcef4be 100644 --- a/src/huggingface_hub/utils/tqdm.py +++ b/src/huggingface_hub/utils/tqdm.py @@ -84,9 +84,9 @@ import logging import os import warnings -from contextlib import contextmanager +from contextlib import contextmanager, nullcontext from pathlib import Path -from typing import Dict, Iterator, Optional, Union +from typing import ContextManager, Dict, Iterator, Optional, Union from tqdm.auto import tqdm as old_tqdm @@ -277,3 +277,31 @@ def _inner_read(size: Optional[int] = -1) -> bytes: yield f pbar.close() + + +def _get_progress_bar_context( + *, + desc: str, + log_level: int, + total: Optional[int] = None, + initial: int = 0, + unit: str = "B", + unit_scale: bool = True, + name: Optional[str] = None, + _tqdm_bar: Optional[tqdm] = None, +) -> ContextManager[tqdm]: + if _tqdm_bar is not None: + return nullcontext(_tqdm_bar) + # ^ `contextlib.nullcontext` mimics a context manager that does nothing + # Makes it easier to use the same code path for both cases but in the later + # case, the progress bar is not closed when exiting the context manager. + + return tqdm( + unit=unit, + unit_scale=unit_scale, + total=total, + initial=initial, + desc=desc, + disable=is_tqdm_disabled(log_level=log_level), + name=name, + ) diff --git a/tests/cassettes/TestSpaceAPIProduction.test_manage_secrets.yaml b/tests/cassettes/TestSpaceAPIProduction.test_manage_secrets.yaml index cd8ffa0f33..f25149abf1 100644 --- a/tests/cassettes/TestSpaceAPIProduction.test_manage_secrets.yaml +++ b/tests/cassettes/TestSpaceAPIProduction.test_manage_secrets.yaml @@ -1,117 +1,167 @@ interactions: - request: - body: '{"name": "tmp_test_space", "organization": "user", - "private": true, "type": "space", "sdk": "gradio"}' + body: '{"name": "tmp_test_space", "organization": "user", "private": true, + "type": "space", "sdk": "gradio"}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '126' + - '104' Content-Type: - application/json X-Amzn-Trace-Id: - - e66b0377-bc64-4006-9739-ef25917703b5 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - c9be3bc3-c902-4af2-98ce-49f29dd89efd method: POST uri: https://huggingface.co/api/repos/create response: body: - string: '{"url":"https://huggingface.co/spaces/user/tmp_test_space","name":"user/tmp_test_space"}' + string: '{"error":"You already created this space repo","url":"https://huggingface.co/spaces/user/tmp_test_space"}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '138' + - '108' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:08 GMT + - Fri, 07 Mar 2025 12:24:11 GMT ETag: - - W/"8a-WEP7JYyATKrcvT8FcoqNgk44G0c" + - W/"6c-cLXSdVZqrgsnfZ2Xm7BqF4YqbPo" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - NOVPRYqzLUCTU8KJvktNzEti3tkVe_MgyxCqErKDtvvHjI8pUmyxTw== + - aH7hYCC_MTDl-sikHXAQZwwl8H7ZkAHuU1Raj65PT1M9ZqDdlUMHtg== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - - Miss from cloudfront + - Error from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e38-12d10c3374cb321f7d288e89;e66b0377-bc64-4006-9739-ef25917703b5 + - Root=1-67cae56b-7044bad340f6f3b2760f4124;c9be3bc3-c902-4af2-98ce-49f29dd89efd + cross-origin-opener-policy: + - same-origin status: - code: 200 - message: OK + code: 409 + message: Conflict - request: body: '{"files": [{"path": "app.py", "sample": "CmltcG9ydCBncmFkaW8gYXMgZ3IKCgpkZWYgZ3JlZXQobmFtZSk6CiAgICByZXR1cm4gIkhlbGxvICIgKyBuYW1lICsgIiEhIgoKaWZhY2UgPSBnci5JbnRlcmZhY2UoZm49Z3JlZXQsIGlucHV0cz0idGV4dCIsIG91dHB1dHM9InRleHQiKQppZmFjZS5sYXVuY2goKQo=", - "size": 152, "sha": "cf0d1d937f3c0dbe0d626e10da89ddd32c89c13cbbec8e5a665bb13c8abf19d4"}]}' + "size": 152}]}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '337' + - '262' Content-Type: - application/json X-Amzn-Trace-Id: - - 5cd55563-d5f7-4b82-9ef0-2644ecd149a2 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 96319374-fbbe-42ed-b621-7552a91114f5 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/preupload/main response: body: - string: '{"files":[{"path":"app.py","uploadMode":"regular","shouldIgnore":false}]}' + string: '{"files":[{"path":"app.py","uploadMode":"regular","shouldIgnore":false}],"commitOid":"8aa4cd78db279c7d673ade7a798dd4adff562cbb"}' + headers: + Access-Control-Allow-Origin: + - https://huggingface.co + Access-Control-Expose-Headers: + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash + Connection: + - keep-alive + Content-Length: + - '128' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 07 Mar 2025 12:24:11 GMT + ETag: + - W/"80-9/S6gfzEBsCFgJIVX0Zf1/sEtmg" + Referrer-Policy: + - strict-origin-when-cross-origin + Vary: + - Origin + Via: + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - 4SXTzf9atTyWrsSHYbbYLKULBj-VoshO6iwrAen7eSOqozorgP99IQ== + X-Amz-Cf-Pop: + - CDG52-P4 + X-Cache: + - Miss from cloudfront + X-Powered-By: + - huggingface-moon + X-Request-Id: + - Root=1-67cae56b-49212cca2d70b5a51205c99a;96319374-fbbe-42ed-b621-7552a91114f5 + cross-origin-opener-policy: + - same-origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + X-Amzn-Trace-Id: + - 86a2f7da-ff0f-41f2-9025-a87e405216b4 + method: GET + uri: https://huggingface.co/api/spaces/user/tmp_test_space/revision/main?expand=xetEnabled + response: + body: + string: '{"_id":"67cae5358ccdbd832011c081","id":"user/tmp_test_space","xetEnabled":false}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '73' + - '83' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:08 GMT + - Fri, 07 Mar 2025 12:24:11 GMT ETag: - - W/"49-nCeVdVg3Vd8tCoIYVmhKsYvl0Vs" + - W/"53-a2Em1NBwfk1o0EIzZGOT899ueUk" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - pZ5ltWojeacjtXqVxiL8e4Vj8DOuzN3FMYGKQayPMgQ1Y5Yq15WrXg== + - hATAPZaLcQEsftKG9LsDN0rOcZxEGFk4ndyGXoxsVLxzSaOziI9HLA== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e38-214ac59413362cac4ec2ff37;5cd55563-d5f7-4b82-9ef0-2644ecd149a2 + - Root=1-67cae56b-4ccabd6c4a7d63f0790a5d8e;86a2f7da-ff0f-41f2-9025-a87e405216b4 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -127,7 +177,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -135,46 +185,45 @@ interactions: Content-Type: - application/x-ndjson X-Amzn-Trace-Id: - - 1594e403-6e69-4e0f-b5fe-96eaa0c1cf2f - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 03a62a5f-cd11-48d4-aad2-4b47cbecedec method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/commit/main response: body: - string: '{"success":true,"commitOid":"bbc3b307a356c99b0b2901e150b980e55ba5093b","commitUrl":"https://huggingface.co/spaces/user/tmp_test_space/commit/bbc3b307a356c99b0b2901e150b980e55ba5093b","hookOutput":""}' + string: '{"success":true,"commitOid":"855efb9561990d02233a49204227f93ba5cfaa81","commitUrl":"https://huggingface.co/spaces/user/tmp_test_space/commit/855efb9561990d02233a49204227f93ba5cfaa81","hookOutput":""}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '224' + - '202' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:09 GMT + - Fri, 07 Mar 2025 12:24:12 GMT ETag: - - W/"e0-rybzVXm4FehyjfvwKj66LUUzuxA" + - W/"ca-iQts/ibleiMnAtiU0ILntNCQMhM" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - QuePsrsqum26l7qBr-y5YqgngKnAs-AT9MGEa2liAW8pbjxqs8iDXw== + - Pe-v9zhEQlLPUtsJdofOm6Sx2Y4cHG64XD0txMcnixqoCvWQsYUDbw== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e39-5abda3f92ceb373546c77521;1594e403-6e69-4e0f-b5fe-96eaa0c1cf2f + - Root=1-67cae56c-45d0c68012abaf6223afe331;03a62a5f-cd11-48d4-aad2-4b47cbecedec + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -184,7 +233,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -192,12 +241,7 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - 0794318e-2c47-4e56-b684-47a12939b00c - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - d44a034d-b2e2-43e7-8504-7c4a7b6a6f15 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/secrets response: @@ -211,21 +255,25 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:09 GMT + - Fri, 07 Mar 2025 12:24:12 GMT ETag: - W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8" + Referrer-Policy: + - strict-origin-when-cross-origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - IP-jFwlRuY90UE67bZYJpjd198zjz_KSCCF8ae92BpZBdayuKsjycA== + - -Z2SKEQXC4d3RJ9p-LV4slzus6Qd-MNY97DSl1EL2-6lsTYHQ3a8hQ== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e39-02e160dc25f8969a23ffcc6d;0794318e-2c47-4e56-b684-47a12939b00c + - Root=1-67cae56c-77406d6558571d27093d9032;d44a034d-b2e2-43e7-8504-7c4a7b6a6f15 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -235,7 +283,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -243,12 +291,7 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - 876691c3-8429-4db4-8aba-2763cb351c1c - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - bf7a9f6b-a4a2-471a-9a0e-8c0d9f28e5f8 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/secrets response: @@ -262,21 +305,25 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:09 GMT + - Fri, 07 Mar 2025 12:24:13 GMT ETag: - W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8" + Referrer-Policy: + - strict-origin-when-cross-origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 9jdxxQkjiKiAO4nbtl9TQmZXi6Ht5D009pNjaMq6vJxIlIxC3pCAtg== + - PfnZ8uwcAcpYTlIsO5Wv6-EK3kZBPdPDjy8uf_RcDB0-Yyj5K6ic5w== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e39-3900d5750a6792ac2e34464a;876691c3-8429-4db4-8aba-2763cb351c1c + - Root=1-67cae56d-7423833c6ca909b27c519585;bf7a9f6b-a4a2-471a-9a0e-8c0d9f28e5f8 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -286,7 +333,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -294,12 +341,7 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - 5c7bfe2c-a905-4dbd-88c9-9e95dc15ddbf - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 754a6946-e9c9-4f00-b5d8-00a0409265e9 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/secrets response: @@ -313,21 +355,25 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:09 GMT + - Fri, 07 Mar 2025 12:24:13 GMT ETag: - W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8" + Referrer-Policy: + - strict-origin-when-cross-origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - ybIMsWFOtj3rc4Xaq5s0YbGfeNKZm3Q9K1KCWGMEVigKyobNkGz_0g== + - vZ_aoXKkuQAEkCLmr8wT9bEETyAwg7SyUcsugmFzfyjMi2p7fJm0Lw== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e39-486eb42553feb8d256ab8c29;5c7bfe2c-a905-4dbd-88c9-9e95dc15ddbf + - Root=1-67cae56d-630a8f1c1fd4e3755c433323;754a6946-e9c9-4f00-b5d8-00a0409265e9 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -337,7 +383,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -345,12 +391,7 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - f3da584b-9af8-4f32-9c82-571d19944adf - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 6dd3747b-f44c-4b95-a9d4-0c7c0ccb55a4 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/secrets response: @@ -364,21 +405,25 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:09 GMT + - Fri, 07 Mar 2025 12:24:13 GMT ETag: - W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8" + Referrer-Policy: + - strict-origin-when-cross-origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - -dUBTWoOEFxITdM3fyNlVHPcDisisu1iFk4avqVRcXYk8_S0SfbHww== + - ZJxk2O9KM1gKhOrg9CEKUTCBFyP-vqQBQGyIDz4FyzINYz50A8H8Zg== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e39-44c34ffd0e58ece67040e164;f3da584b-9af8-4f32-9c82-571d19944adf + - Root=1-67cae56d-0d991be52616ccd52f81462b;6dd3747b-f44c-4b95-a9d4-0c7c0ccb55a4 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -388,7 +433,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -396,12 +441,7 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - ee7a0e7a-6fe9-494c-99a7-a2fdcfa4713d - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 23a9e1fa-b4b7-402e-81ea-5ffee1e1fe40 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/secrets response: @@ -415,21 +455,25 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:10 GMT + - Fri, 07 Mar 2025 12:24:13 GMT ETag: - W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8" + Referrer-Policy: + - strict-origin-when-cross-origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - EFGNLe_3yJnCXNfUdTDDyQ1sCX3CnYdnHna0dlMSLRVidEzBl5n3vw== + - ff4tYN8pDZCRzOyHxiX4htBPouIMsQgaOI8Tu6CXb1ZCv66tOQp40Q== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e39-550c439578f387fe7cfe149e;ee7a0e7a-6fe9-494c-99a7-a2fdcfa4713d + - Root=1-67cae56d-203ef959504ba4523d8d2080;23a9e1fa-b4b7-402e-81ea-5ffee1e1fe40 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -439,7 +483,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -447,12 +491,7 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - 28eaf3ba-060c-490a-abb5-9021b0f1e8b6 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - a658ade9-d7b5-4348-a55a-7cc63920ac12 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/secrets response: @@ -466,21 +505,25 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:10 GMT + - Fri, 07 Mar 2025 12:24:13 GMT ETag: - W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8" + Referrer-Policy: + - strict-origin-when-cross-origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - rSdCXpn4gdXEg1SapFI8hOz3FXxKqJqgUZXYyImPqtiMp44AeQhIjA== + - wvPMPDDGhrYi1XzKNk6W1yKMnywEDoO7GUutB158Hb6hoA9bDoE2EQ== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3a-78efd20f31816e237250105a;28eaf3ba-060c-490a-abb5-9021b0f1e8b6 + - Root=1-67cae56d-7f7ec498356cf25d7891fdb2;a658ade9-d7b5-4348-a55a-7cc63920ac12 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -490,7 +533,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -498,12 +541,7 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - 468881f1-d952-4f13-8886-771df62ad785 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 280fc309-a810-485a-875e-ef801cef4a06 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/secrets response: @@ -517,21 +555,25 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:10 GMT + - Fri, 07 Mar 2025 12:24:14 GMT ETag: - W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8" + Referrer-Policy: + - strict-origin-when-cross-origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - OwLxa0n4Lh0YNMB5HJq9zx8QU7fSdpvp9XpHKAoOy67YQkrdVdCBvQ== + - MtyENUIdc-DDlS2r4LecZeN7xdY687cS99iDyk_AljAcMdDxfkvESQ== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3a-53ca4bdf1af99d7c0a2f0474;468881f1-d952-4f13-8886-771df62ad785 + - Root=1-67cae56e-6c1831633adba4df35d727bf;280fc309-a810-485a-875e-ef801cef4a06 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -541,7 +583,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -549,12 +591,7 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - 377022dc-bca1-45c1-89f6-3590f28e693f - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 6a5ccc7d-dad7-4260-9db5-432d85667736 method: DELETE uri: https://huggingface.co/api/spaces/user/tmp_test_space/secrets response: @@ -568,21 +605,25 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:10 GMT + - Fri, 07 Mar 2025 12:24:14 GMT ETag: - W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8" + Referrer-Policy: + - strict-origin-when-cross-origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 2QEFaTfyxUznbs9-zreXCj4IkSM-G3GNA4bkVtwIpvII0h5n5Mx8xg== + - FPgiwymoD902XfsaHSJ5PeBg0cTa0wzCclmMCk4W2oMIlowB5ATzxw== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3a-1265a5d15339f3363893e2e3;377022dc-bca1-45c1-89f6-3590f28e693f + - Root=1-67cae56e-2b3eb53a6d6ada9c42857e7d;6a5ccc7d-dad7-4260-9db5-432d85667736 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -592,7 +633,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -600,12 +641,7 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - 539cbd61-e68b-4d0b-89b5-72b1cd9a6039 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 0c4e1e88-7136-4776-b5e3-20553214d278 method: DELETE uri: https://huggingface.co/api/spaces/user/tmp_test_space/secrets response: @@ -619,45 +655,43 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:10 GMT + - Fri, 07 Mar 2025 12:24:14 GMT ETag: - W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8" + Referrer-Policy: + - strict-origin-when-cross-origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - IbQ4czJrC-TguMo2PXUIVYL-FGOtAhLQAO5c8hiB3x8maQu4ido_uQ== + - Dc-eyr_Pot418vnzFWwJBlOYn_cGX6tglUd7tpXgr4v7bLwkjR42_A== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3a-57447a4971791c4b6c721515;539cbd61-e68b-4d0b-89b5-72b1cd9a6039 + - Root=1-67cae56e-519f0665135ac4485aae313e;0c4e1e88-7136-4776-b5e3-20553214d278 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK - request: - body: '{"name": "tmp_test_space", "organization": "user", - "type": "space"}' + body: '{"name": "tmp_test_space", "organization": "user", "type": "space"}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '92' + - '70' Content-Type: - application/json X-Amzn-Trace-Id: - - d6f98ff9-6ca0-46f1-8abe-527ce3fc9cdf - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - e9b0afaa-3fbf-46f9-98e8-e4fe67829ca6 method: DELETE uri: https://huggingface.co/api/repos/delete response: @@ -667,7 +701,7 @@ interactions: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -675,23 +709,27 @@ interactions: Content-Type: - text/plain; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:11 GMT + - Fri, 07 Mar 2025 12:24:15 GMT ETag: - W/"2-nOO9QiTIwXgNtWtBJezz8kv3SLc" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 a799a2015a613685dbaf214eb2f38aa8.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - NaAwsKoKWUYZYSlBUsRb0sxdCwdwVmEZhWtgQFLW7CdmhndEzV03Vg== + - 1YKXYUszejurZEoZbaR8bzf4gBLOutzPs95GXjhlwfC0w_lZGNpnmA== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3b-525f29b64d2be09e68f1d236;d6f98ff9-6ca0-46f1-8abe-527ce3fc9cdf + - Root=1-67cae56e-159b91f5249e2e672237e7a9;e9b0afaa-3fbf-46f9-98e8-e4fe67829ca6 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK diff --git a/tests/cassettes/TestSpaceAPIProduction.test_manage_variables.yaml b/tests/cassettes/TestSpaceAPIProduction.test_manage_variables.yaml index 3a78c08716..e6f3e15e44 100644 --- a/tests/cassettes/TestSpaceAPIProduction.test_manage_variables.yaml +++ b/tests/cassettes/TestSpaceAPIProduction.test_manage_variables.yaml @@ -1,117 +1,167 @@ interactions: - request: - body: '{"name": "tmp_test_space", "organization": "user", - "private": true, "type": "space", "sdk": "gradio"}' + body: '{"name": "tmp_test_space", "organization": "user", "private": true, + "type": "space", "sdk": "gradio"}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '126' + - '104' Content-Type: - application/json X-Amzn-Trace-Id: - - e263f1bc-b926-41b1-a10a-1e187a1b96b0 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 47a4cc5f-9a1b-44b2-9cef-901111fd2498 method: POST uri: https://huggingface.co/api/repos/create response: body: - string: '{"url":"https://huggingface.co/spaces/user/tmp_test_space","name":"user/tmp_test_space"}' + string: '{"url":"https://huggingface.co/spaces/user/tmp_test_space","name":"user/tmp_test_space","id":"67cae56f9b4f4ee4713c83e7"}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '138' + - '126' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:12 GMT + - Fri, 07 Mar 2025 12:24:16 GMT ETag: - - W/"8a-yeooLdmcHdMQlRR0P3uy5ToctjY" + - W/"7e-IIZfkz6oRU0wTdbGC2eUKrWYXDw" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - o04mAFqWbRWcCtCNZAYa_VJ_yePomft9skCuEZrbW3FqdADsHi-62g== + - eqpp6gXTuHTKzoytrR-zThBLi_XqvA0R-mC6pfbbkhalO8mlbt6Low== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3c-4d3eb62915ab70197e341895;e263f1bc-b926-41b1-a10a-1e187a1b96b0 + - Root=1-67cae56f-2255614a05efec98258484db;47a4cc5f-9a1b-44b2-9cef-901111fd2498 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK - request: body: '{"files": [{"path": "app.py", "sample": "CmltcG9ydCBncmFkaW8gYXMgZ3IKCgpkZWYgZ3JlZXQobmFtZSk6CiAgICByZXR1cm4gIkhlbGxvICIgKyBuYW1lICsgIiEhIgoKaWZhY2UgPSBnci5JbnRlcmZhY2UoZm49Z3JlZXQsIGlucHV0cz0idGV4dCIsIG91dHB1dHM9InRleHQiKQppZmFjZS5sYXVuY2goKQo=", - "size": 152, "sha": "cf0d1d937f3c0dbe0d626e10da89ddd32c89c13cbbec8e5a665bb13c8abf19d4"}]}' + "size": 152}]}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '337' + - '262' Content-Type: - application/json X-Amzn-Trace-Id: - - a7926e1d-25d2-4c7c-8729-3eb036c1096a - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - a674d7b9-a3ce-4402-a8f4-ae05e73a8578 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/preupload/main response: body: - string: '{"files":[{"path":"app.py","uploadMode":"regular","shouldIgnore":false}]}' + string: '{"files":[{"path":"app.py","uploadMode":"regular","shouldIgnore":false}],"commitOid":"c28c3378a2354b5aa3b65c9652ed8ed8941ba3d5"}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '73' + - '128' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 07 Mar 2025 12:24:16 GMT + ETag: + - W/"80-kRxMLDjU5j+SSNqA7hsLAnQGOHQ" + Referrer-Policy: + - strict-origin-when-cross-origin + Vary: + - Origin + Via: + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - J3WLQ_RzQt5sPpotjNWKtNlmPzid0SzaB-uhWA8Wwjwx3WiAf0WU0w== + X-Amz-Cf-Pop: + - CDG52-P4 + X-Cache: + - Miss from cloudfront + X-Powered-By: + - huggingface-moon + X-Request-Id: + - Root=1-67cae570-790fb2dd21f9cad57389b7fd;a674d7b9-a3ce-4402-a8f4-ae05e73a8578 + cross-origin-opener-policy: + - same-origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + X-Amzn-Trace-Id: + - 02a4c7dc-c8b8-4b16-8021-83edf7b5ecae + method: GET + uri: https://huggingface.co/api/spaces/user/tmp_test_space/revision/main?expand=xetEnabled + response: + body: + string: '{"_id":"67cae56f9b4f4ee4713c83e7","id":"user/tmp_test_space","xetEnabled":false}' + headers: + Access-Control-Allow-Origin: + - https://huggingface.co + Access-Control-Expose-Headers: + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash + Connection: + - keep-alive + Content-Length: + - '83' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:12 GMT + - Fri, 07 Mar 2025 12:24:17 GMT ETag: - - W/"49-nCeVdVg3Vd8tCoIYVmhKsYvl0Vs" + - W/"53-KcEoMEXQ1qRAHs8f1K9d+H6xV7w" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 0MN4imDQcJBrkrL_4lDI_zAZ7QApmZkbfLmBzfwxO4WtFvFx0mUY9A== + - mFd5RLMJhi2obWQ-QkhDBmc75IiBBox5ZQvIvc2T8Cecz-G3vJQynA== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3c-2a4abb84674dc7cd7ff15d7f;a7926e1d-25d2-4c7c-8729-3eb036c1096a + - Root=1-67cae571-344d912007f9a51620a0fca7;02a4c7dc-c8b8-4b16-8021-83edf7b5ecae + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -127,7 +177,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -135,46 +185,45 @@ interactions: Content-Type: - application/x-ndjson X-Amzn-Trace-Id: - - 13ebbfcb-6c8a-4533-8911-eb05cf4fb202 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - b0f4a049-91f2-4a65-9eec-9f75b5a07872 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/commit/main response: body: - string: '{"success":true,"commitOid":"918c918d44286d17e278947c4156bc9eb572818f","commitUrl":"https://huggingface.co/spaces/user/tmp_test_space/commit/918c918d44286d17e278947c4156bc9eb572818f","hookOutput":""}' + string: '{"success":true,"commitOid":"eff8e0eb38c673ede5f5d4c4d106255900fa39e2","commitUrl":"https://huggingface.co/spaces/user/tmp_test_space/commit/eff8e0eb38c673ede5f5d4c4d106255900fa39e2","hookOutput":""}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '224' + - '202' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:13 GMT + - Fri, 07 Mar 2025 12:24:18 GMT ETag: - - W/"e0-oCi/rVVyj2vxQIYnO3haPQq31I0" + - W/"ca-iLOqNyjYlzhNCQkFbLydVgeRbf4" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 6jXvo6JfrsWRfWYYwnhhBHA-BprgPMLllMH6934C8qr3CPzR4tfnxQ== + - xuZonJ3zlvIeaBbYm2gx4mROKU5nvjpWgraQpLXjoAyhRA1XuRnzHw== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3d-562f580743de14ef2eeb0141;13ebbfcb-6c8a-4533-8911-eb05cf4fb202 + - Root=1-67cae571-573543ba4c3e13ec17141d90;b0f4a049-91f2-4a65-9eec-9f75b5a07872 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -184,16 +233,11 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive X-Amzn-Trace-Id: - - f2d60c50-ceeb-43bd-9990-9ce30dc90c5a - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - bf9d247f-1b2e-4454-acf7-aa972cd07b55 method: GET uri: https://huggingface.co/api/spaces/user/tmp_test_space/variables response: @@ -203,7 +247,7 @@ interactions: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -211,23 +255,27 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:13 GMT + - Fri, 07 Mar 2025 12:24:18 GMT ETag: - W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - dIFAfXDtDY8gZEtMAvzCrS_WjECBl5kDBteBICvzcO8LHHCCPx2p1g== + - a2QqDwiAwKKod4_2b-xu79UmL_Kc271A63ScnYMkR9s9yUhdPNikcg== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3d-3d2f306e0651867647f39b3d;f2d60c50-ceeb-43bd-9990-9ce30dc90c5a + - Root=1-67cae572-6ae7936b53e3166a22040d10;bf9d247f-1b2e-4454-acf7-aa972cd07b55 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -237,7 +285,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -245,22 +293,17 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - 565727c9-b0f4-4a58-a185-ef9283781161 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - c08ae5a9-0683-4d1d-ac9c-8ce685520f3d method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/variables response: body: - string: '{"foo":{"value":"123","updatedAt":"2023-12-21T15:48:13.527Z"}}' + string: '{"foo":{"value":"123","updatedAt":"2025-03-07T12:24:18.657Z"}}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -268,23 +311,27 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:13 GMT + - Fri, 07 Mar 2025 12:24:18 GMT ETag: - - W/"3e-u9dSYwYuGnQSUupsXnXHKy5CS3Y" + - W/"3e-q4uUCpjRuU299XB4L0PIX635r20" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - GOULsBKNg-LHnKPjSmf8ZKhu7gCiJdqUsmJz0xTuvu_qqMzJgEaeiA== + - 8YXx2Dhq-vkxGSoArPsfuHdW5Fajil-0986X799w2pVg3ZtDfMTBkg== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3d-03d4b3175291565d3572913a;565727c9-b0f4-4a58-a185-ef9283781161 + - Root=1-67cae572-62408b0012d48183413b41e6;c08ae5a9-0683-4d1d-ac9c-8ce685520f3d + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -294,7 +341,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -302,22 +349,17 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - 239c6622-afc2-4b56-a424-37d135c17397 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 6727a16f-2d47-45f3-843b-e54989ad0580 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/variables response: body: - string: '{"foo":{"value":"123","updatedAt":"2023-12-21T15:48:13.527Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2023-12-21T15:48:13.741Z"}}' + string: '{"foo":{"value":"123","updatedAt":"2025-03-07T12:24:18.657Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2025-03-07T12:24:19.140Z"}}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -325,23 +367,27 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:13 GMT + - Fri, 07 Mar 2025 12:24:19 GMT ETag: - - W/"8b-Erh57N2QrJTXScPDn0usd2+hKVA" + - W/"8b-0w1ouMDC0O3knwFRG8mKuf2z1gU" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - G9vPRHS501FGbVnt-jX-YZhP3qTBnBcdqCpkbvBReoYrnA7d2w83aA== + - x9C51EPVQRKMu0X9269jE9kRV1TI3E1WxIAvgpzZX4UoGBIQnSmUjg== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3d-78dfdace542398e000b597cb;239c6622-afc2-4b56-a424-37d135c17397 + - Root=1-67cae572-0e816e0d22d9de186c24f5f5;6727a16f-2d47-45f3-843b-e54989ad0580 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -351,7 +397,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -359,23 +405,18 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - b07050bc-e41a-4907-b392-d313241fca70 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - ac16a5ea-5d18-47b7-98c3-dc4129acfcf5 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/variables response: body: - string: '{"foo":{"value":"123","updatedAt":"2023-12-21T15:48:13.527Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2023-12-21T15:48:13.741Z"},"MODEL_PAPER":{"value":"arXiv","description":"found - it there","updatedAt":"2023-12-21T15:48:13.939Z"}}' + string: '{"foo":{"value":"123","updatedAt":"2025-03-07T12:24:18.657Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2025-03-07T12:24:19.140Z"},"MODEL_PAPER":{"value":"arXiv","description":"found + it there","updatedAt":"2025-03-07T12:24:19.337Z"}}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -383,23 +424,27 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:14 GMT + - Fri, 07 Mar 2025 12:24:19 GMT ETag: - - W/"f1-fknyIuU+4blGP8iV5xMN0U4cKmQ" + - W/"f1-bEfkBM1h+HtLdktXu58IvCW0uyE" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - rkSfQSSxvZIoyX-LaUdxbNYblxxVycUXuvICTWt4fVPI7BEO6ohBSQ== + - 8bg9Y0pAFQBq9RtFl3WHSGkvOXjwi35BJRm5xLn6s4wzyhg0mhMnKg== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3d-756ee9f82037502912405d25;b07050bc-e41a-4907-b392-d313241fca70 + - Root=1-67cae573-2205bd7c46758c396c705b7a;ac16a5ea-5d18-47b7-98c3-dc4129acfcf5 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -409,7 +454,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -417,23 +462,18 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - 737103b9-a707-429f-a916-fb4f7bb71d4e - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 699803d2-ae72-49ae-9712-e576db0751ea method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/variables response: body: - string: '{"foo":{"value":"456","updatedAt":"2023-12-21T15:48:14.247Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2023-12-21T15:48:13.741Z"},"MODEL_PAPER":{"value":"arXiv","description":"found - it there","updatedAt":"2023-12-21T15:48:13.939Z"}}' + string: '{"foo":{"value":"456","updatedAt":"2025-03-07T12:24:19.651Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2025-03-07T12:24:19.140Z"},"MODEL_PAPER":{"value":"arXiv","description":"found + it there","updatedAt":"2025-03-07T12:24:19.337Z"}}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -441,23 +481,27 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:14 GMT + - Fri, 07 Mar 2025 12:24:19 GMT ETag: - - W/"f1-KcLZie0mbYdNRJqVBPTJAHWnJ0Q" + - W/"f1-PE5/Di6GQPKkXc5Dc7tQBbWdbSE" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - HBj75X7PY822iCia0I2sANWYc9GBE0izd_5tpZ8BY2AjbRpL7Ml5iA== + - ajAUhuKkNRQmjXeSetA9dc0TwaMHDX7zIx4nkz8O80cuxYrvqtW5Bg== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3e-3f0d85cd42d5004e577bb5d3;737103b9-a707-429f-a916-fb4f7bb71d4e + - Root=1-67cae573-613368fc40b0a0e05c3a03ea;699803d2-ae72-49ae-9712-e576db0751ea + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -467,7 +511,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -475,23 +519,18 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - c7e4e72e-3cff-4ba0-9864-66dbbae3466f - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - a934728a-ddd4-4369-a0e4-6cdffa21758c method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/variables response: body: - string: '{"foo":{"value":"456","description":"updated description","updatedAt":"2023-12-21T15:48:14.457Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2023-12-21T15:48:13.741Z"},"MODEL_PAPER":{"value":"arXiv","description":"found - it there","updatedAt":"2023-12-21T15:48:13.939Z"}}' + string: '{"foo":{"value":"456","description":"updated description","updatedAt":"2025-03-07T12:24:19.875Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2025-03-07T12:24:19.140Z"},"MODEL_PAPER":{"value":"arXiv","description":"found + it there","updatedAt":"2025-03-07T12:24:19.337Z"}}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -499,23 +538,27 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:14 GMT + - Fri, 07 Mar 2025 12:24:19 GMT ETag: - - W/"115-HEcATrIdAN3+CPVS5n8Xm62NNl8" + - W/"115-eatWlFhuJXDKdFCN6DDHH8ZrEa0" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - fiSge1PfRo2RDaAbTUFF--jO4urM2ve8cC7HoasExmSjoc0xZRsLHA== + - -eXNt-VkjkjAhxHxFRgmHOEgWq0AHNxJ5A7v3ejo1tj6PfHfGs6WRg== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3e-4bf8ebf71ff75cf732eba175;c7e4e72e-3cff-4ba0-9864-66dbbae3466f + - Root=1-67cae573-36676ba6315a70c6651b6ecb;a934728a-ddd4-4369-a0e4-6cdffa21758c + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -525,7 +568,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -533,23 +576,18 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - d2717516-02c5-40e2-b6e1-38e73e858231 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 41cc0e9b-386c-4960-9b0c-fefffe97ea62 method: DELETE uri: https://huggingface.co/api/spaces/user/tmp_test_space/variables response: body: - string: '{"foo":{"value":"456","description":"updated description","updatedAt":"2023-12-21T15:48:14.457Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2023-12-21T15:48:13.741Z"},"MODEL_PAPER":{"value":"arXiv","description":"found - it there","updatedAt":"2023-12-21T15:48:13.939Z"}}' + string: '{"foo":{"value":"456","description":"updated description","updatedAt":"2025-03-07T12:24:19.875Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2025-03-07T12:24:19.140Z"},"MODEL_PAPER":{"value":"arXiv","description":"found + it there","updatedAt":"2025-03-07T12:24:19.337Z"}}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -557,23 +595,27 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:14 GMT + - Fri, 07 Mar 2025 12:24:20 GMT ETag: - - W/"115-HEcATrIdAN3+CPVS5n8Xm62NNl8" + - W/"115-eatWlFhuJXDKdFCN6DDHH8ZrEa0" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - Xny6r_ROKdWejuoemFqEntf9eynrcJi2en2HLLwmlCm4isNS4ScCzA== + - 3V7eqdB1eXIzDFvSAo4lEw42b3cS46NwFu3TFiTzGa17Pi3g7gbLtA== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3e-7fc17ef02bf2580a68b08d28;d2717516-02c5-40e2-b6e1-38e73e858231 + - Root=1-67cae574-643f6a7e075723b3013b1364;41cc0e9b-386c-4960-9b0c-fefffe97ea62 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -583,7 +625,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -591,23 +633,18 @@ interactions: Content-Type: - application/json X-Amzn-Trace-Id: - - b585f008-e682-40c1-93e0-6fdb53518492 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - ec63b32a-6bcc-4a86-8a9c-6f7beda90418 method: DELETE uri: https://huggingface.co/api/spaces/user/tmp_test_space/variables response: body: - string: '{"foo":{"value":"456","description":"updated description","updatedAt":"2023-12-21T15:48:14.457Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2023-12-21T15:48:13.741Z"},"MODEL_PAPER":{"value":"arXiv","description":"found - it there","updatedAt":"2023-12-21T15:48:13.939Z"}}' + string: '{"foo":{"value":"456","description":"updated description","updatedAt":"2025-03-07T12:24:19.875Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2025-03-07T12:24:19.140Z"},"MODEL_PAPER":{"value":"arXiv","description":"found + it there","updatedAt":"2025-03-07T12:24:19.337Z"}}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -615,23 +652,27 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:14 GMT + - Fri, 07 Mar 2025 12:24:20 GMT ETag: - - W/"115-HEcATrIdAN3+CPVS5n8Xm62NNl8" + - W/"115-eatWlFhuJXDKdFCN6DDHH8ZrEa0" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - -Ljx6qdQSnOsOmVPvTp0Oz-_dzueF-4Y3D_8KiupU3RbeQ8jNlehKQ== + - nvIM3SyyDIDQyq6II7z0MYhkPao7GWEuvtbeJGArLb96ivflvXPHQw== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3e-35dfc3973fbd6af053c23a0c;b585f008-e682-40c1-93e0-6fdb53518492 + - Root=1-67cae574-3f86dee741149c407e6df30c;ec63b32a-6bcc-4a86-8a9c-6f7beda90418 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -641,27 +682,22 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive X-Amzn-Trace-Id: - - 1d9a2c03-4686-4375-b245-05945c68897f - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 1b8a4629-6629-48df-a512-dcff8d947cb4 method: GET uri: https://huggingface.co/api/spaces/user/tmp_test_space/variables response: body: - string: '{"foo":{"value":"456","description":"updated description","updatedAt":"2023-12-21T15:48:14.457Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2023-12-21T15:48:13.741Z"},"MODEL_PAPER":{"value":"arXiv","description":"found - it there","updatedAt":"2023-12-21T15:48:13.939Z"}}' + string: '{"foo":{"value":"456","description":"updated description","updatedAt":"2025-03-07T12:24:19.875Z"},"MODEL_REPO_ID":{"value":"user/repo","updatedAt":"2025-03-07T12:24:19.140Z"},"MODEL_PAPER":{"value":"arXiv","description":"found + it there","updatedAt":"2025-03-07T12:24:19.337Z"}}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -669,47 +705,45 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:15 GMT + - Fri, 07 Mar 2025 12:24:20 GMT ETag: - - W/"115-HEcATrIdAN3+CPVS5n8Xm62NNl8" + - W/"115-eatWlFhuJXDKdFCN6DDHH8ZrEa0" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 08oJqBu3NeLxqexVhmKKUaXS1ryTZfCXUF0qDv3BAf0HU1BWXG4IQg== + - gp59kY8IyTOe7uOCAPRyk7aqNXqXzjX7YF374voAc3cHzsm4jJzJRA== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3f-4075a801327f9e41296fa682;1d9a2c03-4686-4375-b245-05945c68897f + - Root=1-67cae574-7b50faed54abeb620f129366;1b8a4629-6629-48df-a512-dcff8d947cb4 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK - request: - body: '{"name": "tmp_test_space", "organization": "user", - "type": "space"}' + body: '{"name": "tmp_test_space", "organization": "user", "type": "space"}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '92' + - '70' Content-Type: - application/json X-Amzn-Trace-Id: - - f5e9b3b3-e0e4-4890-bb0f-b33b7d190abb - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 09422d1d-6beb-47cd-96e9-334e1d23e667 method: DELETE uri: https://huggingface.co/api/repos/delete response: @@ -719,7 +753,7 @@ interactions: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -727,23 +761,27 @@ interactions: Content-Type: - text/plain; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:15 GMT + - Fri, 07 Mar 2025 12:24:20 GMT ETag: - W/"2-nOO9QiTIwXgNtWtBJezz8kv3SLc" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 5cb605e8100138acccc04f094724133e.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 9me6HlhyQ8oHK08FPgJJddO0f9rLrkULF4X0lJhU1rj9tG5gLk9JoA== + - k3vFxzVrf5jBRs4IIh2ZTzOLk4fpLYBov63R9icnaBQaOtgQJuwwLw== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e3f-7dcf897f7dad29340e7ec577;f5e9b3b3-e0e4-4890-bb0f-b33b7d190abb + - Root=1-67cae574-21fe270c44ec0a8c51b83eb2;09422d1d-6beb-47cd-96e9-334e1d23e667 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK diff --git a/tests/cassettes/TestSpaceAPIProduction.test_pause_and_restart_space.yaml b/tests/cassettes/TestSpaceAPIProduction.test_pause_and_restart_space.yaml index d400e4e3e2..997f9590d2 100644 --- a/tests/cassettes/TestSpaceAPIProduction.test_pause_and_restart_space.yaml +++ b/tests/cassettes/TestSpaceAPIProduction.test_pause_and_restart_space.yaml @@ -1,117 +1,167 @@ interactions: - request: - body: '{"name": "tmp_test_space", "organization": "user", - "private": true, "type": "space", "sdk": "gradio"}' + body: '{"name": "tmp_test_space", "organization": "user", "private": true, + "type": "space", "sdk": "gradio"}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '126' + - '104' Content-Type: - application/json X-Amzn-Trace-Id: - - c8665da6-190c-44f5-97a9-657aa2970e7f - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - a86bc51b-b47e-426f-a35f-240a57a69ba0 method: POST uri: https://huggingface.co/api/repos/create response: body: - string: '{"url":"https://huggingface.co/spaces/user/tmp_test_space","name":"user/tmp_test_space"}' + string: '{"url":"https://huggingface.co/spaces/user/tmp_test_space","name":"user/tmp_test_space","id":"67cae57522fde9195aaf95c5"}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '138' + - '126' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:16 GMT + - Fri, 07 Mar 2025 12:24:22 GMT ETag: - - W/"8a-cX1tp8KZ9AbSjjAv7Ea81tUl/o0" + - W/"7e-cANDOJXnR7JqsqX4FY/+yV6Z80g" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 c0ec38d1888eee986b8ba3b8834111c6.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 9WC2pIXZRijNtgbWWJWNyUTnZooErgMTYSYana_9oBTi4_Y375JyHg== + - ltslbWIDbeto-y9731xrW2NKtvntvbrk0DkxHcPJqcMdWG1iTY-v4w== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e40-0971def90d16c23148e749c0;c8665da6-190c-44f5-97a9-657aa2970e7f + - Root=1-67cae575-057288fc692431ca313fa387;a86bc51b-b47e-426f-a35f-240a57a69ba0 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK - request: body: '{"files": [{"path": "app.py", "sample": "CmltcG9ydCBncmFkaW8gYXMgZ3IKCgpkZWYgZ3JlZXQobmFtZSk6CiAgICByZXR1cm4gIkhlbGxvICIgKyBuYW1lICsgIiEhIgoKaWZhY2UgPSBnci5JbnRlcmZhY2UoZm49Z3JlZXQsIGlucHV0cz0idGV4dCIsIG91dHB1dHM9InRleHQiKQppZmFjZS5sYXVuY2goKQo=", - "size": 152, "sha": "cf0d1d937f3c0dbe0d626e10da89ddd32c89c13cbbec8e5a665bb13c8abf19d4"}]}' + "size": 152}]}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '337' + - '262' Content-Type: - application/json X-Amzn-Trace-Id: - - 55b87731-52c0-4d26-b3f9-74de40980261 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 06cce17b-d0eb-4018-9104-e57f84a6b1c7 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/preupload/main response: body: - string: '{"files":[{"path":"app.py","uploadMode":"regular","shouldIgnore":false}]}' + string: '{"files":[{"path":"app.py","uploadMode":"regular","shouldIgnore":false}],"commitOid":"d052d85daa4e615c3554cecaa7dbdc2e4d179929"}' + headers: + Access-Control-Allow-Origin: + - https://huggingface.co + Access-Control-Expose-Headers: + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash + Connection: + - keep-alive + Content-Length: + - '128' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 07 Mar 2025 12:24:22 GMT + ETag: + - W/"80-o37TMCJion9tXEpXCGSfmyWO+H4" + Referrer-Policy: + - strict-origin-when-cross-origin + Vary: + - Origin + Via: + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - 6QbhLOXmcZfCV6aRrJ1wdoLR0nSreMTPdHza4E50w-VY0L08kovFKA== + X-Amz-Cf-Pop: + - CDG52-P4 + X-Cache: + - Miss from cloudfront + X-Powered-By: + - huggingface-moon + X-Request-Id: + - Root=1-67cae576-298749c90215b9e265f7a281;06cce17b-d0eb-4018-9104-e57f84a6b1c7 + cross-origin-opener-policy: + - same-origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + X-Amzn-Trace-Id: + - 59b8bb46-367a-4e3d-b796-4511a701a469 + method: GET + uri: https://huggingface.co/api/spaces/user/tmp_test_space/revision/main?expand=xetEnabled + response: + body: + string: '{"_id":"67cae57522fde9195aaf95c5","id":"user/tmp_test_space","xetEnabled":false}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '73' + - '83' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:17 GMT + - Fri, 07 Mar 2025 12:24:22 GMT ETag: - - W/"49-nCeVdVg3Vd8tCoIYVmhKsYvl0Vs" + - W/"53-FIrrE87MlsfOWdXe5c0aZBcYc5M" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 c0ec38d1888eee986b8ba3b8834111c6.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - ItAaH1jzx1zN7PL5qBoV0bnV_TWkMAw-Yg8ySTlDFlGtSaRwbCHAmg== + - YZbiyM_Rm3gBuDSprem8uIdDugxCCJzil9NXuGXdSmjYcJzDjSbEpA== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e40-4d9d511a5f9386bc4b3fe3f0;55b87731-52c0-4d26-b3f9-74de40980261 + - Root=1-67cae576-58adf5e47599e8a908829681;59b8bb46-367a-4e3d-b796-4511a701a469 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -127,7 +177,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -135,103 +185,153 @@ interactions: Content-Type: - application/x-ndjson X-Amzn-Trace-Id: - - eddaadbb-269b-47de-b30e-dbabe129b5fa - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - ad37bd5d-59ad-4aba-bb5b-ee2a5c004855 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/commit/main response: body: - string: '{"success":true,"commitOid":"2f2dcda7439e7451aedac474325b0f37afbeccaf","commitUrl":"https://huggingface.co/spaces/user/tmp_test_space/commit/2f2dcda7439e7451aedac474325b0f37afbeccaf","hookOutput":""}' + string: '{"success":true,"commitOid":"c52ce5e45984b9378ef6247fe7a45ed72b096b24","commitUrl":"https://huggingface.co/spaces/user/tmp_test_space/commit/c52ce5e45984b9378ef6247fe7a45ed72b096b24","hookOutput":""}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '224' + - '202' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:17 GMT + - Fri, 07 Mar 2025 12:24:23 GMT ETag: - - W/"e0-HUwkMh4OiEaF2bP3Aj9ujLvPgOo" + - W/"ca-wapu7c2+EVB7QHYtNqK8Q/f985Y" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 c0ec38d1888eee986b8ba3b8834111c6.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - pEi67vjgaYXn0LghY4hL9FeKBH4Fd5yJOql1MkoDhGz5Weax1JAofw== + - dFoaB0Y-sS8CnESrd49aWe7jj8pVe0NLvV4M07gn21isBrsUjwrwag== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e41-1393b1cf1b2d840634cec973;eddaadbb-269b-47de-b30e-dbabe129b5fa + - Root=1-67cae576-1430fb4e73a8e5c63ded648d;ad37bd5d-59ad-4aba-bb5b-ee2a5c004855 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK - request: - body: '{"files": [{"path": "app.py", "sample": "", "size": 0, "sha": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}]}' + body: '{"files": [{"path": "app.py", "sample": "", "size": 0}]}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '131' + - '56' Content-Type: - application/json X-Amzn-Trace-Id: - - 31afebbb-3722-4d9b-aeff-eb3c3fd914a9 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 48734f70-485c-4e46-a6ba-7c5dddac5545 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/preupload/main response: body: - string: '{"files":[{"path":"app.py","uploadMode":"regular","shouldIgnore":false}]}' + string: '{"files":[{"path":"app.py","uploadMode":"regular","shouldIgnore":false,"oid":"1a42034850e2f5213e1a89a887747b667dc9d125"}],"commitOid":"c52ce5e45984b9378ef6247fe7a45ed72b096b24"}' + headers: + Access-Control-Allow-Origin: + - https://huggingface.co + Access-Control-Expose-Headers: + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash + Connection: + - keep-alive + Content-Length: + - '177' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 07 Mar 2025 12:24:23 GMT + ETag: + - W/"b1-IARwrXroBnMWqawtpuKn+D8vBgU" + Referrer-Policy: + - strict-origin-when-cross-origin + Vary: + - Origin + Via: + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - Zgq9WfyFTJthPNuYLd2ok-oGQ1Laviez3l6g-hKX_D1MGMdoeZbr6A== + X-Amz-Cf-Pop: + - CDG52-P4 + X-Cache: + - Miss from cloudfront + X-Powered-By: + - huggingface-moon + X-Request-Id: + - Root=1-67cae577-599e5f4d1d0a6c486864d32e;48734f70-485c-4e46-a6ba-7c5dddac5545 + cross-origin-opener-policy: + - same-origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + X-Amzn-Trace-Id: + - c53b6463-dce9-45db-a4f1-347c5f447ae1 + method: GET + uri: https://huggingface.co/api/spaces/user/tmp_test_space/revision/main?expand=xetEnabled + response: + body: + string: '{"_id":"67cae57522fde9195aaf95c5","id":"user/tmp_test_space","xetEnabled":false}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '73' + - '83' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:17 GMT + - Fri, 07 Mar 2025 12:24:23 GMT ETag: - - W/"49-nCeVdVg3Vd8tCoIYVmhKsYvl0Vs" + - W/"53-FIrrE87MlsfOWdXe5c0aZBcYc5M" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 c0ec38d1888eee986b8ba3b8834111c6.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - gn0KT92R8SMOy8A_iQjflvCyHBybj2kwKZTHutkOGY_6jb7-9NDzfw== + - uILqgA1fn8pYLBT269WjhgA-frxwjbbJ6AE5Baad1jLu-uP7ye_PEg== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e41-267edc4170245e015a43e12b;31afebbb-3722-4d9b-aeff-eb3c3fd914a9 + - Root=1-67cae577-350598925a39e6d302576c0c;c53b6463-dce9-45db-a4f1-347c5f447ae1 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -246,7 +346,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -254,46 +354,45 @@ interactions: Content-Type: - application/x-ndjson X-Amzn-Trace-Id: - - 069fb2f6-4b9a-474a-81f5-312d591b39bb - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 39fc2f2d-ae6a-41c4-8fd3-1ad093f7fb6d method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/commit/main response: body: - string: '{"success":true,"commitOid":"3bc6e8cd08f81f9052b34d39022e397731acbe14","commitUrl":"https://huggingface.co/spaces/user/tmp_test_space/commit/3bc6e8cd08f81f9052b34d39022e397731acbe14","hookOutput":""}' + string: '{"success":true,"commitOid":"d37235f0eaf17cd7e86cc944e5487a3570056b04","commitUrl":"https://huggingface.co/spaces/user/tmp_test_space/commit/d37235f0eaf17cd7e86cc944e5487a3570056b04","hookOutput":""}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '224' + - '202' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:17 GMT + - Fri, 07 Mar 2025 12:24:24 GMT ETag: - - W/"e0-nW44QSoFe6DhLvkkH0JnsFTrCB8" + - W/"ca-KkEZAwv8/Q7fV9Kl05Z7vhmmTgU" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 c0ec38d1888eee986b8ba3b8834111c6.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - zSgGac00h4Ym_xw2qj65C7ptvNLt_LuqfbuL91BSY9c6blX-gMVyPw== + - qmQPq_yQ9Gzs-in0oFdpoByZVn5vMfmVzupHM_5weN2b2TBqi9pNjw== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e41-255e6ea827c256b93689ec06;069fb2f6-4b9a-474a-81f5-312d591b39bb + - Root=1-67cae577-61fe6831284a00c21e5f093f;39fc2f2d-ae6a-41c4-8fd3-1ad093f7fb6d + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -303,50 +402,49 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive X-Amzn-Trace-Id: - - 11a94de5-d46a-45c0-87bb-058605bd035e - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 1b845141-f4a8-40de-acbe-7c9c08993cc8 method: GET uri: https://huggingface.co/api/spaces/user/tmp_test_space/runtime response: body: - string: '{"stage":"BUILDING","hardware":{"current":null,"requested":"cpu-basic"},"storage":null,"gcTimeout":172800,"replicas":{"current":1,"requested":1}}' + string: '{"stage":"BUILDING","hardware":{"current":null,"requested":"cpu-basic"},"storage":null,"gcTimeout":172800,"replicas":{"requested":1},"devMode":false,"domains":[{"domain":"user-tmp-test-space.hf.space","stage":"READY"}]}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '145' + - '222' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:17 GMT + - Fri, 07 Mar 2025 12:24:25 GMT ETag: - - W/"91-j1NQfsXE5fy1h/qeo4p+DhhJGr8" + - W/"de-+RBXGi1JeTfRdHwxH3iLUmZUjr0" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 c0ec38d1888eee986b8ba3b8834111c6.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 5LXoJ0wtvQapkrpniR6YpWBcnlQWW2sfZ3n7AHfnFypDEla9hc23yg== + - mxNUus7YVqDX2BgB59vLKLzFV-uZiaPCUEHB6OGVERj0JeczSbf9DQ== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e41-0ef1595e4f0538bc008107a8;11a94de5-d46a-45c0-87bb-058605bd035e + - Root=1-67cae578-23edd72a19764b8a21eccc05;1b845141-f4a8-40de-acbe-7c9c08993cc8 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -356,52 +454,51 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - '0' X-Amzn-Trace-Id: - - bfa7f96a-b56c-4cba-85b3-240973e1fe23 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - c89896dc-5bdb-4968-abcf-6b6dd93fd1c6 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/pause response: body: - string: '{"stage":"PAUSED","hardware":{"current":null,"requested":"cpu-basic"},"storage":null,"gcTimeout":172800,"replicas":{"current":1,"requested":1}}' + string: '{"stage":"PAUSED","hardware":{"current":null,"requested":"cpu-basic"},"storage":null,"gcTimeout":172800,"replicas":{"requested":1},"devMode":false,"domains":[{"domain":"user-tmp-test-space.hf.space","stage":"READY"}]}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '143' + - '220' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:18 GMT + - Fri, 07 Mar 2025 12:24:25 GMT ETag: - - W/"8f-EUVoDox+oD2Y+qurpiOKauzrpms" + - W/"dc-0cOOI26eshHyYgeIJy7pudLBiCs" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 c0ec38d1888eee986b8ba3b8834111c6.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - QK4J1JFKiaHiRjtsFAK0NqeYEFU2ndhZ8Er-qd-AP8tTdvR1-9RzkA== + - Vaf1rVJi7OQ-3L2J6Ue_ufnOpqa73Qcif2uQzfjYFAdQJrj3OG2XJw== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e42-6094295a7dfcef531bb3dadb;bfa7f96a-b56c-4cba-85b3-240973e1fe23 + - Root=1-67cae579-3cfda27777a71d5b604ff856;c89896dc-5bdb-4968-abcf-6b6dd93fd1c6 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -411,52 +508,51 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - '0' X-Amzn-Trace-Id: - - 68a13ffb-0e0a-4e83-8cfa-d14e781d57df - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 2bb06c3c-52f8-41ed-8e00-6c13f2d0c22e method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/restart response: body: - string: '{"stage":"BUILDING","hardware":{"current":null,"requested":"cpu-basic"},"storage":null,"gcTimeout":172800,"replicas":{"current":1,"requested":1}}' + string: '{"stage":"BUILDING","hardware":{"current":null,"requested":"cpu-basic"},"storage":null,"gcTimeout":172800,"replicas":{"requested":1},"devMode":false,"domains":[{"domain":"user-tmp-test-space.hf.space","stage":"READY"}]}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '145' + - '222' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:18 GMT + - Fri, 07 Mar 2025 12:24:25 GMT ETag: - - W/"91-j1NQfsXE5fy1h/qeo4p+DhhJGr8" + - W/"de-+RBXGi1JeTfRdHwxH3iLUmZUjr0" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 c0ec38d1888eee986b8ba3b8834111c6.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - XuscfanQbg3kgh7t8ZYD0ICI-PmVIhEy6THfHCcFnukRRTAPQ6ruSA== + - 40udxw9EAJeiZntMDziLw_FMqrjTQXhtcz5ufqgj45BAOkub3-DTMA== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e42-25554a84326ba39e4b250d36;68a13ffb-0e0a-4e83-8cfa-d14e781d57df + - Root=1-67cae579-40acb3ca5d7781964f1a2181;2bb06c3c-52f8-41ed-8e00-6c13f2d0c22e + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -466,74 +562,67 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive X-Amzn-Trace-Id: - - 473e46a8-d98c-4f45-893e-61a6ff42a6ea - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 5589740f-cde4-4bc2-a85c-ca33b9a5fd2d method: GET uri: https://huggingface.co/api/spaces/user/tmp_test_space/runtime response: body: - string: '{"stage":"BUILDING","hardware":{"current":null,"requested":"cpu-basic"},"storage":null,"gcTimeout":172800,"replicas":{"current":1,"requested":1}}' + string: '{"stage":"BUILDING","hardware":{"current":null,"requested":"cpu-basic"},"storage":null,"gcTimeout":172800,"replicas":{"requested":1},"devMode":false,"domains":[{"domain":"user-tmp-test-space.hf.space","stage":"READY"}]}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '145' + - '222' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:23 GMT + - Fri, 07 Mar 2025 12:24:26 GMT ETag: - - W/"91-j1NQfsXE5fy1h/qeo4p+DhhJGr8" + - W/"de-+RBXGi1JeTfRdHwxH3iLUmZUjr0" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 c0ec38d1888eee986b8ba3b8834111c6.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 8tK5pIK1ktOM5wdNboopObtl2TkzcpjBKdD9DRWvigbdSQpeqI0wbA== + - 8fxvdFL7hd6ZCU3oMeW-vxlAC9e3Z3g85NHrfcFlnhb3GLVTR7SMsA== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e47-326f4c40440c694f37cccf70;473e46a8-d98c-4f45-893e-61a6ff42a6ea + - Root=1-67cae57a-254a6c2d154805431ec92977;5589740f-cde4-4bc2-a85c-ca33b9a5fd2d + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK - request: - body: '{"name": "tmp_test_space", "organization": "user", - "type": "space"}' + body: '{"name": "tmp_test_space", "organization": "user", "type": "space"}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '92' + - '70' Content-Type: - application/json X-Amzn-Trace-Id: - - 2ec5ed59-87c7-4eb1-bd89-b6fe1ff05d80 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - d783a055-0dc6-458a-a4d7-73a3d551c741 method: DELETE uri: https://huggingface.co/api/repos/delete response: @@ -543,7 +632,7 @@ interactions: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -551,23 +640,27 @@ interactions: Content-Type: - text/plain; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:24 GMT + - Fri, 07 Mar 2025 12:24:26 GMT ETag: - W/"2-nOO9QiTIwXgNtWtBJezz8kv3SLc" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 c0ec38d1888eee986b8ba3b8834111c6.cloudfront.net (CloudFront) + - 1.1 aad5d23429e63574c684a22d6a0313f0.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - DRxwCPR7JUsMnEE2QOvIU5NM9dQgBUgYqwQRiTUVAj-hVf9nWKwBpA== + - mMN-FE-9HxpXFjuPoT6HMJxJPbpHPwYpCpGkpfy_8fs4oUpbTB3n4g== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e47-4ea4c30647bb7a412e4bc76d;2ec5ed59-87c7-4eb1-bd89-b6fe1ff05d80 + - Root=1-67cae57a-2ef2666e084d6e0a2a395796;d783a055-0dc6-458a-a4d7-73a3d551c741 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK diff --git a/tests/cassettes/TestSpaceAPIProduction.test_space_runtime.yaml b/tests/cassettes/TestSpaceAPIProduction.test_space_runtime.yaml index 0c43f7f13e..7079d27f9b 100644 --- a/tests/cassettes/TestSpaceAPIProduction.test_space_runtime.yaml +++ b/tests/cassettes/TestSpaceAPIProduction.test_space_runtime.yaml @@ -1,117 +1,167 @@ interactions: - request: - body: '{"name": "tmp_test_space", "organization": "user", - "private": true, "type": "space", "sdk": "gradio"}' + body: '{"name": "tmp_test_space", "organization": "user", "private": true, + "type": "space", "sdk": "gradio"}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '126' + - '104' Content-Type: - application/json X-Amzn-Trace-Id: - - b9f6dc11-3021-4a3b-9656-7e1fe8db22a5 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - dce0b1eb-2033-4828-bde7-0c350982d090 method: POST uri: https://huggingface.co/api/repos/create response: body: - string: '{"url":"https://huggingface.co/spaces/user/tmp_test_space","name":"user/tmp_test_space"}' + string: '{"url":"https://huggingface.co/spaces/user/tmp_test_space","name":"user/tmp_test_space","id":"67cae57bc3f95553ede73be7"}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '138' + - '126' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:25 GMT + - Fri, 07 Mar 2025 12:24:27 GMT ETag: - - W/"8a-VViltcPMYxf/HQ1ZzLQ3ALxA9NQ" + - W/"7e-gtR2sRmrCIGWw3Q/NRHwpzFc1y0" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 12462511bf75e25d997040c6b0156390.cloudfront.net (CloudFront) + - 1.1 e47c282d2c53705a367f9e376a2eab28.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - XCLyREqfkctChMi5tt7_KAUqB2-LVZL6RGFZee2GV-ibgppNsk2btg== + - Y90ILOyKIRNKrjO7hdzXuPn0hIe2EW5yuEeoG46wLCHRXC7TkKv-wQ== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e48-5d44a2300f982a3a64cc147a;b9f6dc11-3021-4a3b-9656-7e1fe8db22a5 + - Root=1-67cae57a-4b4e5c5b432e5b3c0e788d01;dce0b1eb-2033-4828-bde7-0c350982d090 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK - request: body: '{"files": [{"path": "app.py", "sample": "CmltcG9ydCBncmFkaW8gYXMgZ3IKCgpkZWYgZ3JlZXQobmFtZSk6CiAgICByZXR1cm4gIkhlbGxvICIgKyBuYW1lICsgIiEhIgoKaWZhY2UgPSBnci5JbnRlcmZhY2UoZm49Z3JlZXQsIGlucHV0cz0idGV4dCIsIG91dHB1dHM9InRleHQiKQppZmFjZS5sYXVuY2goKQo=", - "size": 152, "sha": "cf0d1d937f3c0dbe0d626e10da89ddd32c89c13cbbec8e5a665bb13c8abf19d4"}]}' + "size": 152}]}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '337' + - '262' Content-Type: - application/json X-Amzn-Trace-Id: - - 62521671-ac25-46ca-a453-f28ab57dd0b7 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 9885712b-69e9-48d8-8010-2eab63ee50b8 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/preupload/main response: body: - string: '{"files":[{"path":"app.py","uploadMode":"regular","shouldIgnore":false}]}' + string: '{"files":[{"path":"app.py","uploadMode":"regular","shouldIgnore":false}],"commitOid":"d02801a18c74e2875b33127a993615a36eb7448b"}' + headers: + Access-Control-Allow-Origin: + - https://huggingface.co + Access-Control-Expose-Headers: + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash + Connection: + - keep-alive + Content-Length: + - '128' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 07 Mar 2025 12:24:28 GMT + ETag: + - W/"80-wh4U6aPRbJd/LbtLDKJsYOQ33R8" + Referrer-Policy: + - strict-origin-when-cross-origin + Vary: + - Origin + Via: + - 1.1 e47c282d2c53705a367f9e376a2eab28.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - 58BAAB6MNc-tQjWz9QNWKUfQEaj8ENSBz7RJyVHldTFtLMsvpimi-g== + X-Amz-Cf-Pop: + - CDG52-P4 + X-Cache: + - Miss from cloudfront + X-Powered-By: + - huggingface-moon + X-Request-Id: + - Root=1-67cae57c-013d82071f6e1128724ce17a;9885712b-69e9-48d8-8010-2eab63ee50b8 + cross-origin-opener-policy: + - same-origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + X-Amzn-Trace-Id: + - 616ebcf7-e610-496c-b4f0-40a438b22494 + method: GET + uri: https://huggingface.co/api/spaces/user/tmp_test_space/revision/main?expand=xetEnabled + response: + body: + string: '{"_id":"67cae57bc3f95553ede73be7","id":"user/tmp_test_space","xetEnabled":false}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '73' + - '83' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:25 GMT + - Fri, 07 Mar 2025 12:24:28 GMT ETag: - - W/"49-nCeVdVg3Vd8tCoIYVmhKsYvl0Vs" + - W/"53-r0lG4ObhJFKVnhyKtQ899fj4eDw" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 12462511bf75e25d997040c6b0156390.cloudfront.net (CloudFront) + - 1.1 e47c282d2c53705a367f9e376a2eab28.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 4qCRcqViywkR9HsiUza6qa3n409VOEqhrSzoTa-Vp7q9SRy1IbKKRA== + - 2P2xhj59xnkLbs4kavlFidr_5x8EVhXMu_ieGtpyK2XZPSARzPHN4w== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e49-18cabb494fc3868a7ecbba0a;62521671-ac25-46ca-a453-f28ab57dd0b7 + - Root=1-67cae57c-230f25335970111108e17684;616ebcf7-e610-496c-b4f0-40a438b22494 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -127,7 +177,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -135,46 +185,45 @@ interactions: Content-Type: - application/x-ndjson X-Amzn-Trace-Id: - - 176b59ef-c65c-49e6-8f17-485d6c3c5f5d - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - e6008894-c8c9-46f8-b312-fe122fb2a865 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/commit/main response: body: - string: '{"success":true,"commitOid":"b85ff0939b7b063079febb7718a33b196a093640","commitUrl":"https://huggingface.co/spaces/user/tmp_test_space/commit/b85ff0939b7b063079febb7718a33b196a093640","hookOutput":""}' + string: '{"success":true,"commitOid":"403adf5c8b48aecc6b4e5d3bfc402587153a707a","commitUrl":"https://huggingface.co/spaces/user/tmp_test_space/commit/403adf5c8b48aecc6b4e5d3bfc402587153a707a","hookOutput":""}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '224' + - '202' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:25 GMT + - Fri, 07 Mar 2025 12:24:28 GMT ETag: - - W/"e0-tCspvOpu7Kpet4dJF3/VUMp0yJo" + - W/"ca-CjZ13AP//EebkDr6mTGQvoAfv9k" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 12462511bf75e25d997040c6b0156390.cloudfront.net (CloudFront) + - 1.1 e47c282d2c53705a367f9e376a2eab28.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - s46f7yN6Wg3LpQt-BtELoQvAsK4s2CyHGfqvp5gyJ79qZax3K5JAdA== + - qYf8mYLedX8_1Ad_I0WOrQKks4fy_QE06sR0XLUJyrqshZJS-Y25WQ== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e49-7357e41a03cb529504a77478;176b59ef-c65c-49e6-8f17-485d6c3c5f5d + - Root=1-67cae57c-6da554ae1ba9088c56d60ab1;e6008894-c8c9-46f8-b312-fe122fb2a865 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -184,74 +233,67 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive X-Amzn-Trace-Id: - - 98fe3aa8-af57-4a84-bfe1-242ae8fa777a - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 3fca2004-7797-4321-9981-8d2b491cf804 method: GET uri: https://huggingface.co/api/spaces/user/tmp_test_space/runtime response: body: - string: '{"stage":"NO_APP_FILE","hardware":{"current":null,"requested":"cpu-basic"},"storage":null,"gcTimeout":172800,"replicas":{"current":1,"requested":1}}' + string: '{"stage":"BUILDING","hardware":{"current":null,"requested":"cpu-basic"},"storage":null,"gcTimeout":172800,"replicas":{"requested":1},"devMode":false,"domains":[{"domain":"user-tmp-test-space.hf.space","stage":"READY"}]}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '148' + - '222' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:25 GMT + - Fri, 07 Mar 2025 12:24:28 GMT ETag: - - W/"94-92DORcTD91w77YbtjBk7RAgry5I" + - W/"de-+RBXGi1JeTfRdHwxH3iLUmZUjr0" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 12462511bf75e25d997040c6b0156390.cloudfront.net (CloudFront) + - 1.1 e47c282d2c53705a367f9e376a2eab28.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 2Z0ZunWAb_tIqURyQSuF5GzPJbC-yG1GyoRqRNBCabzsiZ3hos4c6g== + - Bv6K4IAykvk23sjvTLP_Hw7THKNjVElwAGrLR1rO6wd-PvBAFPHAQw== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e49-61de2f940c7f3c29471409d5;98fe3aa8-af57-4a84-bfe1-242ae8fa777a + - Root=1-67cae57c-57dd5e08323750a80cc4d371;3fca2004-7797-4321-9981-8d2b491cf804 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK - request: - body: '{"name": "tmp_test_space", "organization": "user", - "type": "space"}' + body: '{"name": "tmp_test_space", "organization": "user", "type": "space"}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '92' + - '70' Content-Type: - application/json X-Amzn-Trace-Id: - - 15a04646-60a2-42df-a3ff-3e17990903d2 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 6c21467f-21f0-4540-901b-8ccffdc38aba method: DELETE uri: https://huggingface.co/api/repos/delete response: @@ -261,7 +303,7 @@ interactions: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -269,23 +311,27 @@ interactions: Content-Type: - text/plain; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:26 GMT + - Fri, 07 Mar 2025 12:24:29 GMT ETag: - W/"2-nOO9QiTIwXgNtWtBJezz8kv3SLc" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 12462511bf75e25d997040c6b0156390.cloudfront.net (CloudFront) + - 1.1 e47c282d2c53705a367f9e376a2eab28.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 6vZojaY85KCqmK1ANwPkPgcVjo_1c8xc2qQQo5qQYgME_f3c3Xt9uw== + - a7cMrRuhNNV4QjDn2npcfEr4tCp9tO698-5gAGH44hYpOnfo0pleNA== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e4a-7e064f31264770a26808abc3;15a04646-60a2-42df-a3ff-3e17990903d2 + - Root=1-67cae57d-6203d6c110ca8209526862d6;6c21467f-21f0-4540-901b-8ccffdc38aba + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK diff --git a/tests/cassettes/TestSpaceAPIProduction.test_static_space_runtime.yaml b/tests/cassettes/TestSpaceAPIProduction.test_static_space_runtime.yaml index 067d427681..9ecc8555ea 100644 --- a/tests/cassettes/TestSpaceAPIProduction.test_static_space_runtime.yaml +++ b/tests/cassettes/TestSpaceAPIProduction.test_static_space_runtime.yaml @@ -1,117 +1,167 @@ interactions: - request: - body: '{"name": "tmp_test_space", "organization": "user", - "private": true, "type": "space", "sdk": "gradio"}' + body: '{"name": "tmp_test_space", "organization": "user", "private": true, + "type": "space", "sdk": "gradio"}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '126' + - '104' Content-Type: - application/json X-Amzn-Trace-Id: - - 12990993-69d3-4062-8f0d-c830400832e8 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 4549789f-7f26-4688-9868-9f1bc01c974d method: POST uri: https://huggingface.co/api/repos/create response: body: - string: '{"url":"https://huggingface.co/spaces/user/tmp_test_space","name":"user/tmp_test_space"}' + string: '{"url":"https://huggingface.co/spaces/user/tmp_test_space","name":"user/tmp_test_space","id":"67cae57dfb88e1c3d1fec3f5"}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '138' + - '126' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:28 GMT + - Fri, 07 Mar 2025 12:24:30 GMT ETag: - - W/"8a-e1HOQQY6+79xs9xeAyCIvmdd5pY" + - W/"7e-Za5bJCkGRDGiUaqxqd5bgQlWb0s" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 0a58752d78fb248f2488304f0f93599a.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - 3eFE9LqicIiIuJwFjEmiUAx3KE6KvFVgs7BlIjS1nMAubIZMasvG0A== + - 4n7fqC7W63JdXvQadMSji8v52mrgQrUJsxb-xGjFDs1z8p32gkbADg== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e4b-3376a5f55c117b9f3e2507af;12990993-69d3-4062-8f0d-c830400832e8 + - Root=1-67cae57d-32f50faf78820a041d5de551;4549789f-7f26-4688-9868-9f1bc01c974d + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK - request: body: '{"files": [{"path": "app.py", "sample": "CmltcG9ydCBncmFkaW8gYXMgZ3IKCgpkZWYgZ3JlZXQobmFtZSk6CiAgICByZXR1cm4gIkhlbGxvICIgKyBuYW1lICsgIiEhIgoKaWZhY2UgPSBnci5JbnRlcmZhY2UoZm49Z3JlZXQsIGlucHV0cz0idGV4dCIsIG91dHB1dHM9InRleHQiKQppZmFjZS5sYXVuY2goKQo=", - "size": 152, "sha": "cf0d1d937f3c0dbe0d626e10da89ddd32c89c13cbbec8e5a665bb13c8abf19d4"}]}' + "size": 152}]}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '337' + - '262' Content-Type: - application/json X-Amzn-Trace-Id: - - b559bc5b-5c75-4673-92da-cc368eb78d79 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 77fe438e-3344-4de8-9a38-40309aef3c92 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/preupload/main response: body: - string: '{"files":[{"path":"app.py","uploadMode":"regular","shouldIgnore":false}]}' + string: '{"files":[{"path":"app.py","uploadMode":"regular","shouldIgnore":false}],"commitOid":"47ad63ebd52a0886c638a6445aa05d6fe5cb0836"}' + headers: + Access-Control-Allow-Origin: + - https://huggingface.co + Access-Control-Expose-Headers: + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash + Connection: + - keep-alive + Content-Length: + - '128' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 07 Mar 2025 12:24:30 GMT + ETag: + - W/"80-s4MwkC8gi+pv72Az3k1g5l7bhpQ" + Referrer-Policy: + - strict-origin-when-cross-origin + Vary: + - Origin + Via: + - 1.1 0a58752d78fb248f2488304f0f93599a.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - imIuYRyXozoYv6n3ga8exJUEUUPskH4CXHda1C8pG_oa9Vgfi90pzQ== + X-Amz-Cf-Pop: + - CDG52-P4 + X-Cache: + - Miss from cloudfront + X-Powered-By: + - huggingface-moon + X-Request-Id: + - Root=1-67cae57e-732a1ad93e26bfa548f5013e;77fe438e-3344-4de8-9a38-40309aef3c92 + cross-origin-opener-policy: + - same-origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + X-Amzn-Trace-Id: + - 798c63f1-e894-4be3-844b-cdd4f782f19e + method: GET + uri: https://huggingface.co/api/spaces/user/tmp_test_space/revision/main?expand=xetEnabled + response: + body: + string: '{"_id":"67cae57dfb88e1c3d1fec3f5","id":"user/tmp_test_space","xetEnabled":false}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '73' + - '83' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:28 GMT + - Fri, 07 Mar 2025 12:24:30 GMT ETag: - - W/"49-nCeVdVg3Vd8tCoIYVmhKsYvl0Vs" + - W/"53-mxGtYvhlZ5YLSyPSosOnT6ZX0iI" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 0a58752d78fb248f2488304f0f93599a.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - P1dzYc8fO82sIlZKM_lN-BQEFnm5Q4OnxZ8Mh80lxGmZGnwFQvyMQA== + - sGYTzm-Xp-C72Q4k6RuLfZGzOR0cyAWxAhi1P9DzSbgFSh4zWGZXYg== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e4c-53fccd270fffacd5162826de;b559bc5b-5c75-4673-92da-cc368eb78d79 + - Root=1-67cae57e-2772093b1fff549c1db58234;798c63f1-e894-4be3-844b-cdd4f782f19e + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -127,7 +177,7 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: @@ -135,46 +185,45 @@ interactions: Content-Type: - application/x-ndjson X-Amzn-Trace-Id: - - ce7bba7a-ee75-4e1b-ae82-9b3a74b486c1 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - ff93d09b-63d6-4527-90a8-fb753acf0862 method: POST uri: https://huggingface.co/api/spaces/user/tmp_test_space/commit/main response: body: - string: '{"success":true,"commitOid":"f3b8983b4d51ad1cf54254c99ab89d5d76098af8","commitUrl":"https://huggingface.co/spaces/user/tmp_test_space/commit/f3b8983b4d51ad1cf54254c99ab89d5d76098af8","hookOutput":""}' + string: '{"success":true,"commitOid":"18404cdff7a1dbb7890b52aa94d6f6db6e7b580a","commitUrl":"https://huggingface.co/spaces/user/tmp_test_space/commit/18404cdff7a1dbb7890b52aa94d6f6db6e7b580a","hookOutput":""}' headers: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: - - '224' + - '202' Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:28 GMT + - Fri, 07 Mar 2025 12:24:31 GMT ETag: - - W/"e0-jdLcTIOo/3SI1GrlFN0Hu/1ZGFs" + - W/"ca-oCTIzMc2Cy1da0xNjSczfJ7lxFo" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 0a58752d78fb248f2488304f0f93599a.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - ULXd5h388BjvEuySgGqjUXkfz7muM3SK5uyf0eRdcZNo5rp_EEnSYQ== + - HCFbwV029QrXZm6V5Nyfhd-CsxzWGal5HMlsHG0sEQEeeGZRY2cufQ== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e4c-638878385fcbe9b502750b13;ce7bba7a-ee75-4e1b-ae82-9b3a74b486c1 + - Root=1-67cae57f-3d688bc439de5a007a6b0774;ff93d09b-63d6-4527-90a8-fb753acf0862 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK @@ -184,16 +233,11 @@ interactions: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive X-Amzn-Trace-Id: - - b2b8b3a5-599c-487b-a4a5-0a95ae9ab424 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 667eaa7d-1339-46c0-82ce-c0bd167b6fe1 method: GET uri: https://huggingface.co/api/spaces/victor/static-space/runtime response: @@ -203,7 +247,7 @@ interactions: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -211,47 +255,45 @@ interactions: Content-Type: - application/json; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:29 GMT + - Fri, 07 Mar 2025 12:24:31 GMT ETag: - W/"76-P0FPsIJ0y4/N4wjDHf89CLxBVFg" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 0a58752d78fb248f2488304f0f93599a.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - xe8wqd-q6c_Ok74vOaYja8dW--qOD-dXiPnci-06IR7xEN1u-jElRw== + - FX2xaJMG8GeIQbEev3sNjve3z__q3GoBVSs_-WM2rxqQAoda0aSDww== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e4c-7dfdcd292597ff90047c30b4;b2b8b3a5-599c-487b-a4a5-0a95ae9ab424 + - Root=1-67cae57f-381dd26e43c8857376b052e8;667eaa7d-1339-46c0-82ce-c0bd167b6fe1 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK - request: - body: '{"name": "tmp_test_space", "organization": "user", - "type": "space"}' + body: '{"name": "tmp_test_space", "organization": "user", "type": "space"}' headers: Accept: - '*/*' Accept-Encoding: - - gzip, deflate + - gzip, deflate, br Connection: - keep-alive Content-Length: - - '92' + - '70' Content-Type: - application/json X-Amzn-Trace-Id: - - d5d8bddc-a78c-4682-a454-b24d9749a683 - authorization: - - Bearer hf_fake_token - user-agent: - - unknown/None; hf_hub/0.21.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0; - fastcore/1.5.23 + - 19e2c3c5-6fad-4f35-8791-f2d124301c44 method: DELETE uri: https://huggingface.co/api/repos/delete response: @@ -261,7 +303,7 @@ interactions: Access-Control-Allow-Origin: - https://huggingface.co Access-Control-Expose-Headers: - - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,ETag,Link,Accept-Ranges,Content-Range + - X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash Connection: - keep-alive Content-Length: @@ -269,23 +311,27 @@ interactions: Content-Type: - text/plain; charset=utf-8 Date: - - Thu, 21 Dec 2023 15:48:29 GMT + - Fri, 07 Mar 2025 12:24:32 GMT ETag: - W/"2-nOO9QiTIwXgNtWtBJezz8kv3SLc" + Referrer-Policy: + - strict-origin-when-cross-origin Vary: - Origin Via: - - 1.1 05320c9e938aca851d2d618f965d8882.cloudfront.net (CloudFront) + - 1.1 0a58752d78fb248f2488304f0f93599a.cloudfront.net (CloudFront) X-Amz-Cf-Id: - - IHulcaS-HJSPubMYQEA15Hv1bypEYs7eKuI599_7qduxeu0p_vlW2A== + - KAeQud5wozopUAy_MGd2FCVGhJvdnBLTNTSKri5T-uOFkKzLIVHc6g== X-Amz-Cf-Pop: - - MRS52-P4 + - CDG52-P4 X-Cache: - Miss from cloudfront X-Powered-By: - huggingface-moon X-Request-Id: - - Root=1-65845e4d-2c6f965215a1d20873500f4d;d5d8bddc-a78c-4682-a454-b24d9749a683 + - Root=1-67cae57f-305cba7f65a0591268a9898e;19e2c3c5-6fad-4f35-8791-f2d124301c44 + cross-origin-opener-policy: + - same-origin status: code: 200 message: OK diff --git a/tests/conftest.py b/tests/conftest.py index 81ba124145..017c5751f2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,6 +17,7 @@ def patch_constants(mocker): with SoftTemporaryDirectory() as cache_dir: mocker.patch.object(constants, "HF_HOME", cache_dir) mocker.patch.object(constants, "HF_HUB_CACHE", os.path.join(cache_dir, "hub")) + mocker.patch.object(constants, "HF_XET_CACHE", os.path.join(cache_dir, "xet")) mocker.patch.object(constants, "HUGGINGFACE_HUB_CACHE", os.path.join(cache_dir, "hub")) mocker.patch.object(constants, "HF_ASSETS_CACHE", os.path.join(cache_dir, "assets")) mocker.patch.object(constants, "HF_TOKEN_PATH", os.path.join(cache_dir, "token")) diff --git a/tests/test_file_download.py b/tests/test_file_download.py index f20794e241..c493543c22 100644 --- a/tests/test_file_download.py +++ b/tests/test_file_download.py @@ -523,6 +523,7 @@ def _mocked_hf_file_metadata(*args, **kwargs): etag=metadata.etag, location=metadata.location, size=450, # will expect 450 bytes but will download 496 bytes + xet_file_data=None, ) with patch("huggingface_hub.file_download.get_hf_file_metadata", _mocked_hf_file_metadata): @@ -544,6 +545,7 @@ def _mocked_hf_file_metadata(*args, **kwargs): etag=metadata.etag, location=metadata.location, size=65000, # will expect 65000 bytes but will download 65074 bytes + xet_file_data=None, ) with patch("huggingface_hub.file_download.get_hf_file_metadata", _mocked_hf_file_metadata): diff --git a/tests/test_hf_api.py b/tests/test_hf_api.py index 35abc5e224..4a2f5f3a1c 100644 --- a/tests/test_hf_api.py +++ b/tests/test_hf_api.py @@ -274,6 +274,13 @@ def test_update_dataset_repo_settings(self, repo_url: RepoUrl): assert info.gated == gated_value assert info.private == private_value + @use_tmp_repo(repo_type="model") + def test_update_repo_settings_xet_enabled(self, repo_url: RepoUrl): + repo_id = repo_url.repo_id + self._api.update_repo_settings(repo_id=repo_id, xet_enabled=True) + info = self._api.model_info(repo_id, expand="xetEnabled") + assert info.xet_enabled + @expect_deprecation("get_token_permission") def test_get_token_permission_on_oauth_token(self): whoami = { @@ -3195,7 +3202,7 @@ def setUp(self): self.api = HfApi(token="hf_fake_token", endpoint=ENDPOINT_PRODUCTION) # Create a Space - self.api.create_repo(repo_id=self.repo_id, repo_type="space", space_sdk="gradio", private=True) + self.api.create_repo(repo_id=self.repo_id, repo_type="space", space_sdk="gradio", private=True, exist_ok=True) self.api.upload_file( path_or_fileobj=self._BASIC_APP_PY_TEMPLATE, repo_id=self.repo_id, diff --git a/tests/test_xet_download.py b/tests/test_xet_download.py new file mode 100644 index 0000000000..385d8c2a5f --- /dev/null +++ b/tests/test_xet_download.py @@ -0,0 +1,295 @@ +import os +from contextlib import contextmanager +from pathlib import Path +from unittest.mock import DEFAULT, Mock, patch + +from huggingface_hub import snapshot_download +from huggingface_hub.file_download import ( + HfFileMetadata, + get_hf_file_metadata, + hf_hub_download, + hf_hub_url, + try_to_load_from_cache, + xet_get, +) +from huggingface_hub.utils import ( + XetConnectionInfo, + XetFileData, + refresh_xet_connection_info, +) + +from .testing_utils import ( + DUMMY_XET_FILE, + DUMMY_XET_MODEL_ID, + requires, + with_production_testing, +) + + +@requires("hf_xet") +@with_production_testing +class TestXetFileDownload: + @contextmanager + def _patch_xet_file_metadata(self, with_xet_data: bool): + patcher = patch("huggingface_hub.file_download.get_hf_file_metadata") + mock_metadata = patcher.start() + mock_metadata.return_value = HfFileMetadata( + commit_hash="mock_commit", + etag="mock_etag", + location="mock_location", + size=1024, + xet_file_data=XetFileData(file_hash="mock_hash", refresh_route="mock/route") if with_xet_data else None, + ) + try: + yield mock_metadata + finally: + patcher.stop() + + @contextmanager + def _patch_get_refresh_xet_connection_info(self): + patcher = patch("huggingface_hub.utils.refresh_xet_connection_info") + connection_info = ( + XetConnectionInfo( + endpoint="mock_endpoint", + access_token="mock_token", + expiration_unix_epoch=9999999999, + ), + ) + + mock_xet_connection = patcher.start() + mock_xet_connection.return_value = connection_info + try: + yield mock_xet_connection + finally: + patcher.stop() + + def test_xet_get_called_when_xet_metadata_present(self, tmp_path): + """Test that xet_get is called when xet metadata is present.""" + with self._patch_xet_file_metadata(with_xet_data=True) as mock_file_metadata: + with self._patch_get_refresh_xet_connection_info(): + with patch("huggingface_hub.file_download.xet_get") as mock_xet_get: + with patch("huggingface_hub.file_download._create_symlink"): + hf_hub_download( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + cache_dir=tmp_path, + force_download=True, + ) + + # Verify xet_get was called with correct parameters + mock_xet_get.assert_called_once() + _, kwargs = mock_xet_get.call_args + assert "xet_file_data" in kwargs + assert kwargs["xet_file_data"] == mock_file_metadata.return_value.xet_file_data + + def test_backward_compatibility_no_xet_metadata(self, tmp_path): + """Test backward compatibility when response has no xet metadata.""" + with self._patch_xet_file_metadata(with_xet_data=False): + with patch("huggingface_hub.file_download.http_get") as mock_http_get: + with patch("huggingface_hub.file_download._create_symlink"): + hf_hub_download( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + cache_dir=tmp_path, + force_download=True, + ) + + # Verify http_get was called + mock_http_get.assert_called_once() + + def test_get_xet_file_metadata_basic(self) -> None: + """Test getting metadata from a file on the Hub.""" + url = hf_hub_url( + repo_id=DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + ) + metadata = get_hf_file_metadata(url) + assert metadata.xet_file_data is not None + assert metadata.xet_file_data.file_hash is not None + + connection_info = refresh_xet_connection_info(file_data=metadata.xet_file_data, headers={}) + assert connection_info is not None + assert connection_info.endpoint is not None + assert connection_info.access_token is not None + assert isinstance(connection_info.expiration_unix_epoch, int) + + def test_basic_download(self, tmp_path): + # Make sure that xet_get is called + with patch("huggingface_hub.file_download.xet_get", wraps=xet_get) as _xet_get: + filepath = hf_hub_download( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + cache_dir=tmp_path, + ) + + assert os.path.exists(filepath) + assert os.path.getsize(filepath) > 0 + + _xet_get.assert_called_once() + + def test_try_to_load_from_cache(self, tmp_path): + cached_path = try_to_load_from_cache( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + cache_dir=tmp_path, + ) + assert cached_path is None + + downloaded_path = hf_hub_download( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + cache_dir=tmp_path, + ) + + # Now should find it in cache + cached_path = try_to_load_from_cache( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + cache_dir=tmp_path, + ) + assert cached_path == downloaded_path + + def test_cache_reuse(self, tmp_path): + path1 = hf_hub_download( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + cache_dir=tmp_path, + ) + + assert os.path.exists(path1) + + with patch("huggingface_hub.file_download._download_to_tmp_and_move") as mock: + # Second download should use cache + path2 = hf_hub_download( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + cache_dir=tmp_path, + ) + + assert path1 == path2 + mock.assert_not_called() + + def test_download_to_local_dir(self, tmp_path): + local_dir = tmp_path / "local_dir" + local_dir.mkdir(exist_ok=True, parents=True) + + cache_dir = tmp_path / "cache" + cache_dir.mkdir(exist_ok=True, parents=True) + # Download to local dir + returned_path = hf_hub_download( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + local_dir=local_dir, + cache_dir=cache_dir, + ) + assert local_dir in Path(returned_path).parents + + for path in cache_dir.glob("**/blobs/**"): + assert not path.is_file() + for path in cache_dir.glob("**/snapshots/**"): + assert not path.is_file() + + def test_force_download(self, tmp_path): + # First download + path1 = hf_hub_download( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + cache_dir=tmp_path, + ) + + # Force download should re-download even if in cache + with patch("huggingface_hub.file_download.xet_get") as mock_xet_get: + path2 = hf_hub_download( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + cache_dir=tmp_path, + force_download=True, + ) + + assert path1 == path2 + mock_xet_get.assert_called_once() + + def test_fallback_to_http_when_xet_not_available(self, tmp_path): + """Test that http_get is used when hf_xet is not available.""" + with self._patch_xet_file_metadata(with_xet_data=True): + with self._patch_get_refresh_xet_connection_info(): + # Mock is_xet_available to return False + with patch.multiple( + "huggingface_hub.file_download", + is_xet_available=Mock(return_value=False), + http_get=DEFAULT, + xet_get=DEFAULT, + _create_symlink=DEFAULT, + ) as mocks: + hf_hub_download( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + cache_dir=tmp_path, + force_download=True, + ) + + # Verify http_get was called and xet_get was not + mocks["http_get"].assert_called_once() + mocks["xet_get"].assert_not_called() + + def test_use_xet_when_available(self, tmp_path): + """Test that xet_get is used when hf_xet is available.""" + with self._patch_xet_file_metadata(with_xet_data=True): + with self._patch_get_refresh_xet_connection_info(): + with patch.multiple( + "huggingface_hub.file_download", + is_xet_available=Mock(return_value=True), + http_get=DEFAULT, + xet_get=DEFAULT, + _create_symlink=DEFAULT, + ) as mocks: + hf_hub_download( + DUMMY_XET_MODEL_ID, + filename=DUMMY_XET_FILE, + cache_dir=tmp_path, + force_download=True, + ) + + # Verify xet_get was called and http_get was not + mocks["xet_get"].assert_called_once() + mocks["http_get"].assert_not_called() + + +@requires("hf_xet") +@with_production_testing +class TestXetSnapshotDownload: + def test_download_model(self, tmp_path): + """Test that snapshot_download works with Xet storage.""" + storage_folder = snapshot_download( + DUMMY_XET_MODEL_ID, + cache_dir=tmp_path, + ) + + assert os.path.exists(storage_folder) + assert os.path.isdir(storage_folder) + assert os.path.exists(os.path.join(storage_folder, DUMMY_XET_FILE)) + + with open(os.path.join(storage_folder, DUMMY_XET_FILE), "rb") as f: + content = f.read() + assert len(content) > 0 + + def test_snapshot_download_cache_reuse(self, tmp_path): + """Test that snapshot_download reuses cached files.""" + # First download + storage_folder1 = snapshot_download( + DUMMY_XET_MODEL_ID, + cache_dir=tmp_path, + ) + + with patch("huggingface_hub.file_download.xet_get") as mock_xet_get: + # Second download should use cache + storage_folder2 = snapshot_download( + DUMMY_XET_MODEL_ID, + cache_dir=tmp_path, + ) + + # Verify same folder is returned + assert storage_folder1 == storage_folder2 + + # Verify xet_get was not called (files were cached) + mock_xet_get.assert_not_called() diff --git a/tests/test_xet_upload.py b/tests/test_xet_upload.py new file mode 100644 index 0000000000..b47dc134ac --- /dev/null +++ b/tests/test_xet_upload.py @@ -0,0 +1,349 @@ +# Copyright 2025 The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from contextlib import contextmanager +from io import BytesIO +from pathlib import Path +from typing import Tuple +from unittest.mock import MagicMock, patch + +import pytest + +from huggingface_hub import HfApi, RepoUrl +from huggingface_hub._commit_api import _upload_lfs_files, _upload_xet_files +from huggingface_hub.file_download import ( + _get_metadata_or_catch_error, + get_hf_file_metadata, + hf_hub_download, + hf_hub_url, +) +from huggingface_hub.utils import build_hf_headers, refresh_xet_connection_info + +from .testing_constants import ENDPOINT_STAGING, TOKEN +from .testing_utils import repo_name, requires + + +@contextmanager +def assert_upload_mode(mode: str): + if mode not in ("xet", "lfs"): + raise ValueError("Mode must be either 'xet' or 'lfs'") + + with patch("huggingface_hub.hf_api._upload_xet_files", wraps=_upload_xet_files) as mock_xet: + with patch("huggingface_hub.hf_api._upload_lfs_files", wraps=_upload_lfs_files) as mock_lfs: + yield + assert mock_xet.called == (mode == "xet"), ( + f"Expected {'XET' if mode == 'xet' else 'LFS'} upload to be used" + ) + assert mock_lfs.called == (mode == "lfs"), ( + f"Expected {'LFS' if mode == 'lfs' else 'XET'} upload to be used" + ) + + +@pytest.fixture(scope="module") +def api(): + return HfApi(endpoint=ENDPOINT_STAGING, token=TOKEN) + + +@pytest.fixture +def repo_url(api, repo_type: str = "model"): + repo_url = api.create_repo(repo_id=repo_name(prefix=repo_type), repo_type=repo_type) + api.update_repo_settings(repo_id=repo_url.repo_id, xet_enabled=True) + + yield repo_url + + api.delete_repo(repo_id=repo_url.repo_id, repo_type=repo_type) + + +@requires("hf_xet") +class TestXetUpload: + @pytest.fixture(autouse=True) + def setup(self, tmp_path): + self.folder_path = tmp_path + # Create a regular text file + text_file = self.folder_path / "text_file.txt" + self.text_content = "This is a regular text file" + text_file.write_text(self.text_content) + + # Create a binary file + self.bin_file = self.folder_path / "binary_file.bin" + self.bin_content = b"0" * (1 * 1024 * 1024) + self.bin_file.write_bytes(self.bin_content) + + # Create nested directory structure + nested_dir = self.folder_path / "nested" + nested_dir.mkdir() + + # Create a nested text file + nested_text_file = nested_dir / "nested_text.txt" + self.nested_text_content = "This is a nested text file" + nested_text_file.write_text(self.nested_text_content) + + # Create a nested binary file + nested_bin_file = nested_dir / "nested_binary.safetensors" + self.nested_bin_content = b"1" * (1 * 1024 * 1024) + nested_bin_file.write_bytes(self.nested_bin_content) + + def test_upload_file(self, api, tmp_path, repo_url): + filename_in_repo = "binary_file.bin" + repo_id = repo_url.repo_id + with assert_upload_mode("xet"): + return_val = api.upload_file( + path_or_fileobj=self.bin_file, + path_in_repo=filename_in_repo, + repo_id=repo_id, + ) + + assert return_val == f"{api.endpoint}/{repo_id}/blob/main/{filename_in_repo}" + # Download and verify content + downloaded_file = hf_hub_download(repo_id=repo_id, filename=filename_in_repo, cache_dir=tmp_path) + with open(downloaded_file, "rb") as f: + downloaded_content = f.read() + assert downloaded_content == self.bin_content + + # Check xet metadata + url = hf_hub_url( + repo_id=repo_id, + filename=filename_in_repo, + ) + metadata = get_hf_file_metadata(url) + assert metadata.xet_file_data is not None + xet_connection = refresh_xet_connection_info(file_data=metadata.xet_file_data, headers={}) + assert xet_connection is not None + + def test_upload_file_with_bytesio(self, api, tmp_path, repo_url): + repo_id = repo_url.repo_id + content = BytesIO(self.bin_content) + with assert_upload_mode("lfs"): + api.upload_file( + path_or_fileobj=content, + path_in_repo="bytesio_file.bin", + repo_id=repo_id, + ) + # Download and verify content + downloaded_file = hf_hub_download(repo_id=repo_id, filename="bytesio_file.bin", cache_dir=tmp_path) + with open(downloaded_file, "rb") as f: + downloaded_content = f.read() + assert downloaded_content == self.bin_content + + def test_fallback_to_lfs_when_xet_not_available(self, api, repo_url): + repo_id = repo_url.repo_id + with patch("huggingface_hub.hf_api.is_xet_available", return_value=False): + with assert_upload_mode("lfs"): + api.upload_file( + path_or_fileobj=self.bin_file, + path_in_repo="fallback_file.bin", + repo_id=repo_id, + ) + + def test_upload_based_on_xet_enabled_setting(self, api, repo_url): + repo_id = repo_url.repo_id + + # Test when xet is enabled -> use Xet upload + with patch("huggingface_hub.hf_api.HfApi.repo_info") as mock_repo_info: + mock_repo_info.return_value.xet_enabled = True + with assert_upload_mode("xet"): + api.upload_file( + path_or_fileobj=self.bin_file, + path_in_repo="xet_enabled.bin", + repo_id=repo_id, + ) + + # Test when xet is disabled -> use LFS upload + with patch("huggingface_hub.hf_api.HfApi.repo_info") as mock_repo_info: + mock_repo_info.return_value.xet_enabled = False + with assert_upload_mode("lfs"): + api.upload_file( + path_or_fileobj=self.bin_file, + path_in_repo="xet_disabled.bin", + repo_id=repo_id, + ) + + def test_upload_folder(self, api, repo_url): + repo_id = repo_url.repo_id + folder_in_repo = "temp" + with assert_upload_mode("xet"): + return_val = api.upload_folder( + folder_path=self.folder_path, + path_in_repo=folder_in_repo, + repo_id=repo_id, + ) + + assert return_val == f"{api.endpoint}/{repo_id}/tree/main/{folder_in_repo}" + files_in_repo = set(api.list_repo_files(repo_id=repo_id)) + files = { + f"{folder_in_repo}/text_file.txt", + f"{folder_in_repo}/binary_file.bin", + f"{folder_in_repo}/nested/nested_text.txt", + f"{folder_in_repo}/nested/nested_binary.safetensors", + } + assert all(file in files_in_repo for file in files) + + for rpath in files: + local_file = Path(rpath).relative_to(folder_in_repo) + local_path = self.folder_path / local_file + filepath = hf_hub_download(repo_id=repo_id, filename=rpath) + assert Path(local_path).read_bytes() == Path(filepath).read_bytes() + + def test_upload_folder_create_pr(self, api, repo_url) -> None: + repo_id = repo_url.repo_id + folder_in_repo = "temp_create_pr" + with assert_upload_mode("xet"): + return_val = api.upload_folder( + folder_path=self.folder_path, + path_in_repo=folder_in_repo, + repo_id=repo_id, + create_pr=True, + ) + + assert return_val == f"{api.endpoint}/{repo_id}/tree/refs%2Fpr%2F1/{folder_in_repo}" + + for rpath in ["text_file.txt", "nested/nested_binary.safetensors"]: + local_path = self.folder_path / rpath + filepath = hf_hub_download( + repo_id=repo_id, filename=f"{folder_in_repo}/{rpath}", revision=return_val.pr_revision + ) + assert Path(local_path).read_bytes() == Path(filepath).read_bytes() + + +@requires("hf_xet") +class TestXetLargeUpload: + def test_upload_large_folder(self, api, tmp_path, repo_url: RepoUrl) -> None: + N_FILES_PER_FOLDER = 4 + repo_id = repo_url.repo_id + + folder = Path(tmp_path) / "large_folder" + for i in range(N_FILES_PER_FOLDER): + subfolder = folder / f"subfolder_{i}" + subfolder.mkdir(parents=True, exist_ok=True) + for j in range(N_FILES_PER_FOLDER): + (subfolder / f"file_xet_{i}_{j}.bin").write_bytes(f"content_lfs_{i}_{j}".encode()) + (subfolder / f"file_regular_{i}_{j}.txt").write_bytes(f"content_regular_{i}_{j}".encode()) + + with assert_upload_mode("xet"): + api.upload_large_folder(repo_id=repo_id, repo_type="model", folder_path=folder, num_workers=4) + + # Check all files have been uploaded + uploaded_files = api.list_repo_files(repo_id=repo_id) + + # Download and verify content + local_dir = Path(tmp_path) / "snapshot" + local_dir.mkdir() + api.snapshot_download(repo_id=repo_id, local_dir=local_dir, cache_dir=None) + + for i in range(N_FILES_PER_FOLDER): + for j in range(N_FILES_PER_FOLDER): + assert f"subfolder_{i}/file_xet_{i}_{j}.bin" in uploaded_files + assert f"subfolder_{i}/file_regular_{i}_{j}.txt" in uploaded_files + + # Check xet metadata + url = hf_hub_url( + repo_id=repo_id, + filename=f"subfolder_{i}/file_xet_{i}_{j}.bin", + ) + + metadata = get_hf_file_metadata(url) + xet_filedata = metadata.xet_file_data + assert xet_filedata is not None + + # Verify xet files + xet_file = local_dir / f"subfolder_{i}/file_xet_{i}_{j}.bin" + assert xet_file.read_bytes() == f"content_lfs_{i}_{j}".encode() + + # Verify regular files + regular_file = local_dir / f"subfolder_{i}/file_regular_{i}_{j}.txt" + assert regular_file.read_bytes() == f"content_regular_{i}_{j}".encode() + + +@requires("hf_xet") +class TestXetE2E(TestXetUpload): + def test_hf_xet_with_token_refresher(self, api, tmp_path, repo_url): + """ + Test the hf_xet.download_files function with a token refresher. + + This test manually calls the hf_xet.download_files function with a token refresher + function to verify that the token refresh mechanism works as expected. It aims to + identify regressions in the hf_xet.download_files function. + + * Define a token refresher function that issues a token refresh by returning a new + access token and expiration time. + * Mock the token refresher function. + * Construct the necessary headers and metadata for the file to be downloaded. + * Call the download_files function with the token refresher, forcing a token refresh. + * Assert that the token refresher function was called as expected. + + This test ensures that the downloaded file is the same as the uploaded file. + """ + from hf_xet import PyPointerFile, download_files + + filename_in_repo = "binary_file.bin" + repo_id = repo_url.repo_id + + # Upload a file + api.upload_file( + path_or_fileobj=self.bin_file, + path_in_repo=filename_in_repo, + repo_id=repo_id, + ) + + # headers + headers = build_hf_headers(token=TOKEN) + + # metadata for url + (url_to_download, etag, commit_hash, expected_size, xet_filedata, head_call_error) = ( + _get_metadata_or_catch_error( + repo_id=repo_id, + filename=filename_in_repo, + revision="main", + repo_type="model", + headers=headers, + endpoint=api.endpoint, + token=TOKEN, + proxies=None, + etag_timeout=None, + local_files_only=False, + ) + ) + + xet_connection_info = refresh_xet_connection_info(file_data=xet_filedata, headers=headers) + + # manually construct parameters to hf_xet.download_files and use a locally defined token_refresher function + # to verify that token refresh works as expected. + def token_refresher() -> Tuple[str, int]: + # Issue a token refresh by returning a new access token and expiration time + new_connection = refresh_xet_connection_info(file_data=xet_filedata, headers=headers) + return new_connection.access_token, new_connection.expiration_unix_epoch + + mock_token_refresher = MagicMock(side_effect=token_refresher) + + incomplete_path = Path(tmp_path) / "file.bin.incomplete" + py_file = [ + PyPointerFile(path=str(incomplete_path.absolute()), hash=xet_filedata.file_hash, filesize=expected_size) + ] + + # Call the download_files function with the token refresher, set expiration to 0 forcing a refresh + download_files( + py_file, + endpoint=xet_connection_info.endpoint, + token_info=(xet_connection_info.access_token, 0), + token_refresher=mock_token_refresher, + progress_updater=None, + ) + + # assert that our local token_refresher function was called by hfxet as expected. + mock_token_refresher.assert_called_once() + + # Check that the downloaded file is the same as the uploaded file + with open(incomplete_path, "rb") as f: + downloaded_content = f.read() + assert downloaded_content == self.bin_content diff --git a/tests/test_xet_utils.py b/tests/test_xet_utils.py new file mode 100644 index 0000000000..4ad2f8f9f4 --- /dev/null +++ b/tests/test_xet_utils.py @@ -0,0 +1,255 @@ +from unittest.mock import MagicMock + +import pytest + +from huggingface_hub import constants +from huggingface_hub.utils._xet import ( + XetFileData, + _fetch_xet_connection_info_with_url, + parse_xet_connection_info_from_headers, + parse_xet_file_data_from_response, + refresh_xet_connection_info, +) + + +def test_parse_valid_headers_file_info() -> None: + mock_response = MagicMock() + mock_response.headers = { + "X-Xet-Hash": "sha256:abcdef", + "X-Xet-Refresh-Route": "/api/refresh", + } + mock_response.links = {} + + file_data = parse_xet_file_data_from_response(mock_response) + + assert file_data is not None + assert file_data.refresh_route == "/api/refresh" + assert file_data.file_hash == "sha256:abcdef" + + +def test_parse_valid_headers_file_info_with_link() -> None: + mock_response = MagicMock() + mock_response.headers = { + "X-Xet-Hash": "sha256:abcdef", + } + mock_response.links = { + "xet-auth": {"url": "/api/refresh"}, + } + + file_data = parse_xet_file_data_from_response(mock_response) + + assert file_data is not None + assert file_data.refresh_route == "/api/refresh" + assert file_data.file_hash == "sha256:abcdef" + + +def test_parse_invalid_headers_file_info() -> None: + mock_response = MagicMock() + mock_response.headers = {"X-foo": "bar"} + mock_response.links = {} + assert parse_xet_file_data_from_response(mock_response) is None + + +def test_parse_valid_headers_connection_info() -> None: + headers = { + "X-Xet-Cas-Url": "https://xet.example.com", + "X-Xet-Access-Token": "xet_token_abc", + "X-Xet-Token-Expiration": "1234567890", + } + + connection_info = parse_xet_connection_info_from_headers(headers) + + assert connection_info is not None + assert connection_info.endpoint == "https://xet.example.com" + assert connection_info.access_token == "xet_token_abc" + assert connection_info.expiration_unix_epoch == 1234567890 + + +def test_parse_valid_headers_full() -> None: + mock_response = MagicMock() + mock_response.headers = { + "X-Xet-Cas-Url": "https://xet.example.com", + "X-Xet-Access-Token": "xet_token_abc", + "X-Xet-Token-Expiration": "1234567890", + "X-Xet-Refresh-Route": "/api/refresh", + "X-Xet-Hash": "sha256:abcdef", + } + mock_response.links = {} + + file_metadata = parse_xet_file_data_from_response(mock_response) + connection_info = parse_xet_connection_info_from_headers(mock_response.headers) + + assert file_metadata is not None + assert file_metadata.refresh_route == "/api/refresh" + assert file_metadata.file_hash == "sha256:abcdef" + + assert connection_info is not None + assert connection_info.endpoint == "https://xet.example.com" + assert connection_info.access_token == "xet_token_abc" + assert connection_info.expiration_unix_epoch == 1234567890 + + +@pytest.mark.parametrize( + "missing_key", + [ + "X-Xet-Cas-Url", + "X-Xet-Access-Token", + "X-Xet-Token-Expiration", + ], +) +def test_parse_missing_required_header(missing_key: str) -> None: + headers = { + "X-Xet-Cas-Url": "https://xet.example.com", + "X-Xet-Access-Token": "xet_token_abc", + "X-Xet-Token-Expiration": "1234567890", + } + + # Remove the key to test + headers.pop(missing_key) + + connection_info = parse_xet_connection_info_from_headers(headers) + assert connection_info is None + + +def test_parse_invalid_expiration() -> None: + """Test parsing headers with invalid expiration format returns None.""" + headers = { + "X-Xet-Cas-Url": "https://xet.example.com", + "X-Xet-Access-Token": "xet_token_abc", + "X-Xet-Token-Expiration": "not-a-number", + } + + connection_info = parse_xet_connection_info_from_headers(headers) + assert connection_info is None + + +def test_refresh_metadata_success(mocker) -> None: + # Mock headers for the refreshed response + mock_response = MagicMock() + mock_response.headers = { + "X-Xet-Cas-Url": "https://example.xethub.hf.co", + "X-Xet-Access-Token": "new_token", + "X-Xet-Token-Expiration": "1234599999", + "X-Xet-Refresh-Route": "/api/models/username/repo_name/xet-read-token/token", + } + + mock_session = MagicMock() + mock_session.get.return_value = mock_response + mocker.patch("huggingface_hub.utils._xet.get_session", return_value=mock_session) + + headers = {"user-agent": "user-agent-example"} + refreshed_connection = refresh_xet_connection_info( + file_data=XetFileData( + refresh_route="/api/models/username/repo_name/xet-read-token/token", + file_hash="sha256:abcdef", + ), + headers=headers, + ) + + # Verify the request + expected_url = f"{constants.ENDPOINT}/api/models/username/repo_name/xet-read-token/token" + mock_session.get.assert_called_once_with( + headers=headers, + url=expected_url, + params=None, + ) + + assert refreshed_connection.endpoint == "https://example.xethub.hf.co" + assert refreshed_connection.access_token == "new_token" + assert refreshed_connection.expiration_unix_epoch == 1234599999 + + +def test_refresh_metadata_custom_endpoint(mocker) -> None: + custom_endpoint = "https://custom.xethub.hf.co" + + # Mock headers for the refreshed response + mock_response = MagicMock() + mock_response.headers = { + "X-Xet-Cas-Url": "https://custom.xethub.hf.co", + "X-Xet-Access-Token": "new_token", + "X-Xet-Token-Expiration": "1234599999", + } + + mock_session = MagicMock() + mock_session.get.return_value = mock_response + mocker.patch("huggingface_hub.utils._xet.get_session", return_value=mock_session) + + headers = {"user-agent": "user-agent-example"} + refresh_xet_connection_info( + file_data=XetFileData( + refresh_route="/api/models/username/repo_name/xet-read-token/token", + file_hash="sha256:abcdef", + ), + headers=headers, + endpoint=custom_endpoint, + ) + + # Verify the request used the custom endpoint + expected_url = f"{custom_endpoint}/api/models/username/repo_name/xet-read-token/token" + mock_session.get.assert_called_once_with( + headers=headers, + url=expected_url, + params=None, + ) + + +def test_refresh_metadata_missing_refresh_route() -> None: + # Create metadata without refresh_route + headers = {"user-agent": "user-agent-example"} + + # Verify it raises ValueError + with pytest.raises(ValueError, match="The provided xet metadata does not contain a refresh endpoint."): + refresh_xet_connection_info( + file_data=XetFileData( + refresh_route=None, + file_hash="sha256:abcdef", + ), + headers=headers, + ) + + +def test_fetch_xet_metadata_with_url(mocker) -> None: + mock_response = MagicMock() + mock_response.headers = { + "X-Xet-Cas-Url": "https://example.xethub.hf.co", + "X-Xet-Access-Token": "xet_token123", + "X-Xet-Token-Expiration": "1234567890", + } + + # Mock the session.get method + mock_session = MagicMock() + mock_session.get.return_value = mock_response + mocker.patch("huggingface_hub.utils._xet.get_session", return_value=mock_session) + + # Call the function + url = "https://example.xethub.hf.co/api/models/username/repo_name/xet-read-token/token" + headers = {"user-agent": "user-agent-example"} + metadata = _fetch_xet_connection_info_with_url(url=url, headers=headers) + + # Verify the request + mock_session.get.assert_called_once_with( + headers=headers, + url=url, + params=None, + ) + + # Verify returned metadata + assert metadata.endpoint == "https://example.xethub.hf.co" + assert metadata.access_token == "xet_token123" + assert metadata.expiration_unix_epoch == 1234567890 + + +def test_fetch_xet_metadata_with_url_invalid_response(mocker) -> None: + mock_response = MagicMock() + mock_response.headers = {"Content-Type": "application/json"} # No XET headers + + # Mock the session.get method + mock_session = MagicMock() + mock_session.get.return_value = mock_response + mocker.patch("huggingface_hub.utils._xet.get_session", return_value=mock_session) + + url = "https://example.xethub.hf.co/api/models/username/repo_name/xet-read-token/token" + headers = {"user-agent": "user-agent-example"} + + with pytest.raises(ValueError, match="Xet headers have not been correctly set by the server."): + _fetch_xet_connection_info_with_url(url=url, headers=headers) diff --git a/tests/testing_utils.py b/tests/testing_utils.py index e66d932fd0..bfd1eb9e74 100644 --- a/tests/testing_utils.py +++ b/tests/testing_utils.py @@ -50,6 +50,12 @@ NO = ("n", "no", "f", "false", "off", "0") +# Xet testing +DUMMY_XET_MODEL_ID = "celinah/dummy-xet-testing" +DUMMY_XET_FILE = "dummy.safetensors" +DUMMY_XET_REGULAR_FILE = "dummy.txt" + + def repo_name(id: Optional[str] = None, prefix: str = "repo") -> str: """ Return a readable pseudo-unique repository name for tests.