Repository: huggingface/huggingface_hub

Issue:
Add Secrets via API on hf_api
Since now it's possible to add Secrets via API it would be nice to have this added to the library.

```
curl 'https://huggingface.co/api/spaces/$namespace/$repo/secrets' -X POST -H 'Authorization: Bearer $access_token' -H 'Content-Type: application/json' --data-raw '{"key":"$key","value":"$value"}'

delete (-X DELETE)
```



Discussion:
Since they're similar, maybe we could also add setting the hardware flavor into the api

```
curl 'https://huggingface.co/api/spaces/$namespace/$repo/hardware' -X POST -H 'Authorization: Bearer $access_token' -H 'Content-Type: application/json' --data-raw '{"flavor": "t4-medium"}'
```

These are two ending points that we're using on this Space Space creator https://huggingface.co/spaces/anzorq/sd-space-creator

maybe it could args inside the `create_repot` method? 

```
create_repo(
    repo_id=model_id,
    exist_ok=update,
    repo_type="space",
    space_sdk="gradio",
    flavor="t4-medium"
    secrets={"SECRETA":"asd", "SECRETB":"asd"}
)
```
Thanks @radames for the suggestions ! This makes indeed a lot of sense. I played a bit with the Spaces API to see what we can include in `huggingface_hub`. The HTTP endpoints seem quite complete already as we can:
- get current state (`GET /runtime`): RUNNING, BUILDING,...
- get current and requested hardware (`GET /runtime`)
- get current secrets (`GET /secrets`)
- set new secret (`POST /secrets`)
- delete secret (`DELETE /secrets`)
- request new hardware (`POST /hardware`)


### 1. What do you think about a few new helpers in `HfApi` ?

**EDIT:** `current_state` shouldn't be part of `SpaceConfig`.

```py
class SpaceConfig:
    secrets: Dict[str, str]
    # current_state: Literal or Enum # <- EDIT: this is not really a "config", could be retrieve in a separate method.
    current_hardware: Literal or Enum
    requested_hardware: Literal or Enum

class HfApi:
    (...)

    def get_space_config(self, repo_id: str) -> SpaceConfig:
        (...)

    def request_space_hardware(self, repo_id: str, hardware: Literal or Enum) -> None:
        (...)

    def set_space_secrets(self, repo_id: str, secrets: Dict[str, str]) -> None:
        # Handles both additions and deletions
        # Each addition/deletion is atomic so this method would not be transactional
        # (e.g.: might end up in a weird state if it fails)

    def add_space_secret(self, repo_id: str, key: str, value: str) -> None:
        # Atomic update

    def delete_space_secret(self, repo_id: str, key: str) -> None:
        # Atomic update
```

### 2. Additional suggestion: a less verbose `SpaceApi`

Otherwise I'm starting to consider the idea of having a `SpaceApi` object. Something like this (really just an suggestion):
```py
api = HfApi(token="hf_***").space(repo_id="...")

api.get_config()
api.request_hardware("t4-medium")
api.set_secrets({"foo": "bar", "hello": "world"})
api.add_secret("foo": "bar2")
api.delete_secret("foo")
```

We wouldn't have to repeat the `repo_id` and `space` all the time. This suggestion is a bit orthogonal and could be done later (it is more a design decision at this point since we could do the same for models and datasets as well).


WDTY ? (also cc @LysandreJik @osanseviero)

Resolving patch:
diff --git a/src/huggingface_hub/__init__.py b/src/huggingface_hub/__init__.py
--- a/src/huggingface_hub/__init__.py
+++ b/src/huggingface_hub/__init__.py
@@ -61,6 +61,11 @@
     "_snapshot_download": [
         "snapshot_download",
     ],
+    "_space_api": [
+        "SpaceHardware",
+        "SpaceRuntime",
+        "SpaceStage",
+    ],
     "community": [
         "Discussion",
         "DiscussionComment",
@@ -103,6 +108,7 @@
         "DatasetSearchArguments",
         "HfApi",
         "ModelSearchArguments",
+        "add_space_secret",
         "change_discussion_status",
         "comment_discussion",
         "create_branch",
@@ -116,6 +122,7 @@
         "delete_file",
         "delete_folder",
         "delete_repo",
+        "delete_space_secret",
         "delete_tag",
         "edit_discussion_comment",
         "get_dataset_tags",
@@ -123,6 +130,8 @@
         "get_full_repo_name",
         "get_model_tags",
         "get_repo_discussions",
+        "get_space_runtime",
+        "get_space_secrets",
         "list_datasets",
         "list_metrics",
         "list_models",
@@ -133,6 +142,7 @@
         "move_repo",
         "rename_discussion",
         "repo_type_and_id_from_hf_id",
+        "request_space_hardware",
         "set_access_token",
         "space_info",
         "unset_access_token",
@@ -296,6 +306,9 @@ def __dir__():
     from ._login import logout  # noqa: F401
     from ._login import notebook_login  # noqa: F401
     from ._snapshot_download import snapshot_download  # noqa: F401
+    from ._space_api import SpaceHardware  # noqa: F401
+    from ._space_api import SpaceRuntime  # noqa: F401
+    from ._space_api import SpaceStage  # noqa: F401
     from .community import Discussion  # noqa: F401
     from .community import DiscussionComment  # noqa: F401
     from .community import DiscussionCommit  # noqa: F401
@@ -329,6 +342,7 @@ def __dir__():
     from .hf_api import DatasetSearchArguments  # noqa: F401
     from .hf_api import HfApi  # noqa: F401
     from .hf_api import ModelSearchArguments  # noqa: F401
+    from .hf_api import add_space_secret  # noqa: F401
     from .hf_api import change_discussion_status  # noqa: F401
     from .hf_api import comment_discussion  # noqa: F401
     from .hf_api import create_branch  # noqa: F401
@@ -342,6 +356,7 @@ def __dir__():
     from .hf_api import delete_file  # noqa: F401
     from .hf_api import delete_folder  # noqa: F401
     from .hf_api import delete_repo  # noqa: F401
+    from .hf_api import delete_space_secret  # noqa: F401
     from .hf_api import delete_tag  # noqa: F401
     from .hf_api import edit_discussion_comment  # noqa: F401
     from .hf_api import get_dataset_tags  # noqa: F401
@@ -349,6 +364,8 @@ def __dir__():
     from .hf_api import get_full_repo_name  # noqa: F401
     from .hf_api import get_model_tags  # noqa: F401
     from .hf_api import get_repo_discussions  # noqa: F401
+    from .hf_api import get_space_runtime  # noqa: F401
+    from .hf_api import get_space_secrets  # noqa: F401
     from .hf_api import list_datasets  # noqa: F401
     from .hf_api import list_metrics  # noqa: F401
     from .hf_api import list_models  # noqa: F401
@@ -359,6 +376,7 @@ def __dir__():
     from .hf_api import move_repo  # noqa: F401
     from .hf_api import rename_discussion  # noqa: F401
     from .hf_api import repo_type_and_id_from_hf_id  # noqa: F401
+    from .hf_api import request_space_hardware  # noqa: F401
     from .hf_api import set_access_token  # noqa: F401
     from .hf_api import space_info  # noqa: F401
     from .hf_api import unset_access_token  # noqa: F401
diff --git a/src/huggingface_hub/_space_api.py b/src/huggingface_hub/_space_api.py
new file mode 100644
--- /dev/null
+++ b/src/huggingface_hub/_space_api.py
@@ -0,0 +1,88 @@
+# coding=utf-8
+# Copyright 2019-present, the HuggingFace Inc. team.
+#
+# 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 dataclasses import dataclass
+from enum import Enum
+from typing import Dict, Optional
+
+
+class SpaceStage(str, Enum):
+    """
+    Enumeration of possible stage of a Space on the Hub.
+
+    Value can be compared to a string:
+    ```py
+    assert SpaceStage.BUILDING == "BUILDING"
+    ```
+
+    Taken from https://github.com/huggingface/moon-landing/blob/main/server/repo_types/SpaceInfo.ts#L61 (private url).
+    """
+
+    # Copied from moon-landing > server > repo_types > SpaceInfo.ts (private repo)
+    NO_APP_FILE = "NO_APP_FILE"
+    CONFIG_ERROR = "CONFIG_ERROR"
+    BUILDING = "BUILDING"
+    BUILD_ERROR = "BUILD_ERROR"
+    RUNNING = "RUNNING"
+    RUNNING_BUILDING = "RUNNING_BUILDING"
+    RUNTIME_ERROR = "RUNTIME_ERROR"
+    DELETING = "DELETING"
+    STOPPED = "STOPPED"
+
+
+class SpaceHardware(str, Enum):
+    """
+    Enumeration of hardwares available to run your Space on the Hub.
+
+    Value can be compared to a string:
+    ```py
+    assert SpaceHardware.CPU_BASIC == "cpu-basic"
+    ```
+
+    Taken from https://github.com/huggingface/moon-landing/blob/main/server/repo_types/SpaceInfo.ts#L73 (private url).
+    """
+
+    CPU_BASIC = "cpu-basic"
+    CPU_UPGRADE = "cpu-upgrade"
+    T4_SMALL = "t4-small"
+    T4_MEDIUM = "t4-medium"
+    A10G_SMALL = "a10g-small"
+    A10G_LARGE = "a10g-large"
+    A100_LARGE = "a100-large"
+
+
+@dataclass
+class SpaceRuntime:
+    """
+    Contains information about the current runtime of a Space.
+
+    Args:
+        stage (`str`):
+            Current stage of the space. Example: RUNNING.
+        hardware (`str` or `None`):
+            Current hardware of the space. Example: "cpu-basic". Can be `None` if Space
+            is `BUILDING` for the first time.
+        requested_hardware (`str` or `None`):
+            Requested hardware. Can be different than `hardware` especially if the request
+            has just been made. Example: "t4-medium". Can be `None` if no hardware has
+            been requested yet.
+        raw (`dict`):
+            Raw response from the server. Contains more information about the Space
+            runtime like number of replicas, number of cpu, memory size,...
+    """
+
+    stage: SpaceStage
+    hardware: Optional[SpaceHardware]
+    requested_hardware: Optional[SpaceHardware]
+    raw: Dict
diff --git a/src/huggingface_hub/hf_api.py b/src/huggingface_hub/hf_api.py
--- a/src/huggingface_hub/hf_api.py
+++ b/src/huggingface_hub/hf_api.py
@@ -35,6 +35,7 @@
     upload_lfs_files,
     warn_on_overwriting_operations,
 )
+from ._space_api import SpaceHardware, SpaceRuntime
 from .community import (
     Discussion,
     DiscussionComment,
@@ -1518,6 +1519,7 @@ def create_repo(
         repo_type: Optional[str] = None,
         exist_ok: bool = False,
         space_sdk: Optional[str] = None,
+        space_hardware: Optional[str] = None,
     ) -> str:
         """Create an empty repo on the HuggingFace Hub.
 
@@ -1536,8 +1538,9 @@ def create_repo(
             exist_ok (`bool`, *optional*, defaults to `False`):
                 If `True`, do not raise an error if repo already exists.
             space_sdk (`str`, *optional*):
-                Choice of SDK to use if repo_type is "space". Can be
-                "streamlit", "gradio", or "static".
+                Choice of SDK to use if repo_type is "space". Can be "streamlit", "gradio", or "static".
+            space_hardware (`SpaceHardware` or `str`, *optional*):
+                Choice of Hardware if repo_type is "space". See [`SpaceHardware`] for a complete list.
 
         Returns:
             `str`: URL to the newly created repo.
@@ -1563,11 +1566,20 @@ def create_repo(
                     f"Invalid space_sdk. Please choose one of {SPACES_SDK_TYPES}."
                 )
             json["sdk"] = space_sdk
+
         if space_sdk is not None and repo_type != "space":
             warnings.warn(
                 "Ignoring provided space_sdk because repo_type is not 'space'."
             )
 
+        if space_hardware is not None:
+            if repo_type == "space":
+                json["hardware"] = space_hardware
+            else:
+                warnings.warn(
+                    "Ignoring provided space_hardware because repo_type is not 'space'."
+                )
+
         if getattr(self, "_lfsmultipartthresh", None):
             # Testing purposes only.
             # See https://github.com/huggingface/huggingface_hub/pull/733/files#r820604472
@@ -3433,6 +3445,132 @@ def hide_discussion_comment(
         )
         return deserialize_event(resp.json()["updatedComment"])  # type: ignore
 
+    def add_space_secret(
+        self, repo_id: str, key: str, value: str, *, token: Optional[str] = None
+    ) -> None:
+        """Adds or updates a secret in a Space.
+
+        Secrets allow to set secret keys or tokens to a Space without hardcoding them.
+        For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
+
+        Args:
+            repo_id (`str`):
+                ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+            key (`str`):
+                Secret key. Example: `"GITHUB_API_KEY"`
+            value (`str`):
+                Secret value. Example: `"your_github_api_key"`.
+            token (`str`, *optional*):
+                Hugging Face token. Will default to the locally saved token if not provided.
+        """
+        r = requests.post(
+            f"{self.endpoint}/api/spaces/{repo_id}/secrets",
+            headers=self._build_hf_headers(token=token),
+            json={"key": key, "value": value},
+        )
+        hf_raise_for_status(r)
+
+    def delete_space_secret(
+        self, repo_id: str, key: str, *, token: Optional[str] = None
+    ) -> None:
+        """Deletes a secret from a Space.
+
+        Secrets allow to set secret keys or tokens to a Space without hardcoding them.
+        For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
+
+        Args:
+            repo_id (`str`):
+                ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+            key (`str`):
+                Secret key. Example: `"GITHUB_API_KEY"`.
+            token (`str`, *optional*):
+                Hugging Face token. Will default to the locally saved token if not provided.
+        """
+        r = requests.delete(
+            f"{self.endpoint}/api/spaces/{repo_id}/secrets",
+            headers=self._build_hf_headers(token=token),
+            json={"key": key},
+        )
+        hf_raise_for_status(r)
+
+    def get_space_secrets(
+        self, repo_id: str, *, token: Optional[str] = None
+    ) -> Dict[str, str]:
+        """Gets all secrets from a Space.
+
+        Secrets allow to set secret keys or tokens to a Space without hardcoding them.
+        More for details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
+
+        Args:
+            repo_id (`str`):
+                ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+            token (`str`, *optional*):
+                Hugging Face token. Will default to the locally saved token if not provided.
+        Returns:
+            `Dict[str, str]`: dictionary containing all key/value pairs.
+        """
+        r = requests.get(
+            f"{self.endpoint}/api/spaces/{repo_id}/secrets",
+            headers=self._build_hf_headers(token=token),
+        )
+        hf_raise_for_status(r)
+        return r.json()
+
+    def get_space_runtime(
+        self, repo_id: str, *, token: Optional[str] = None
+    ) -> SpaceRuntime:
+        """Gets runtime information about a Space.
+
+        Args:
+            repo_id (`str`):
+                ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+            token (`str`, *optional*):
+                Hugging Face token. Will default to the locally saved token if
+                not provided.
+        Returns:
+            `SpaceRuntime`: dataclass containing runtime information about a Space
+             including Space stage and hardware.
+        """
+        r = requests.get(
+            f"{self.endpoint}/api/spaces/{repo_id}/runtime",
+            headers=self._build_hf_headers(token=token),
+        )
+        hf_raise_for_status(r)
+        data = r.json()
+        return SpaceRuntime(
+            stage=data["stage"],
+            hardware=data["hardware"]["current"],
+            requested_hardware=data["hardware"]["requested"],
+            raw=data,
+        )
+
+    def request_space_hardware(
+        self, repo_id: str, hardware: SpaceHardware, *, token: Optional[str] = None
+    ) -> None:
+        """Request new hardware for a Space.
+
+        Args:
+            repo_id (`str`):
+                ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+            hardware (`str` or [`SpaceHardware`]):
+                Hardware on which to run the Space. Example: `"t4-medium"`.
+            token (`str`, *optional*):
+                Hugging Face token. Will default to the locally saved token if not provided.
+
+        <Tip>
+
+        It is also possible to request hardware directly when creating the Space repo!
+        See [`create_repo`] for details.
+
+        </Tip>
+        """
+        r = requests.post(
+            f"{self.endpoint}/api/spaces/{repo_id}/hardware",
+            headers=self._build_hf_headers(token=token),
+            json={"flavor": hardware},
+        )
+        hf_raise_for_status(r)
+
     def _build_hf_headers(
         self,
         token: Optional[Union[bool, str]] = None,
@@ -3562,3 +3700,10 @@ def _parse_revision_from_pr_url(pr_url: str) -> str:
 edit_discussion_comment = api.edit_discussion_comment
 rename_discussion = api.rename_discussion
 merge_pull_request = api.merge_pull_request
+
+# Space API
+add_space_secret = api.add_space_secret
+delete_space_secret = api.delete_space_secret
+get_space_secrets = api.get_space_secrets
+get_space_runtime = api.get_space_runtime
+request_space_hardware = api.request_space_hardware


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -5,7 +5,10 @@
 import pytest
 
 from _pytest.fixtures import SubRequest
-from huggingface_hub import HfFolder
+from huggingface_hub import HfApi, HfFolder
+
+from .testing_constants import ENDPOINT_PRODUCTION, PRODUCTION_TOKEN
+from .testing_utils import repo_name
 
 
 @pytest.fixture
@@ -42,3 +45,62 @@ def clean_hf_folder_token_for_tests() -> Generator:
     # Set back token once all tests have passed
     if token is not None:
         HfFolder().save_token(token)
+
+
+@pytest.fixture
+def fx_production_space(request: SubRequest) -> Generator[None, None, None]:
+    """Add a `repo_id` attribute referencing a Space repo on the production Hub.
+
+    Fully testing Spaces is not possible on staging so we need to use the production
+    environment for it. Tests are skipped if we can't find a `HUGGINGFACE_PRODUCTION_USER_TOKEN`
+    environment variable.
+
+    Example:
+    ```py
+    @pytest.mark.usefixtures("fx_production_space")
+    class TestSpaceAPI(unittest.TestCase):
+        repo_id: str
+        api: HfApi
+
+        def test_space(self) -> None:
+            api.repo_info(repo_id, repo_type="space")
+    ```
+    """
+    # Check if production token exists
+    if PRODUCTION_TOKEN is None:
+        pytest.skip(
+            "Skip Space tests. `HUGGINGFACE_PRODUCTION_USER_TOKEN` environment variable"
+            " is not set."
+        )
+
+    # Generate repo id from prod token
+    api = HfApi(token=PRODUCTION_TOKEN, endpoint=ENDPOINT_PRODUCTION)
+    user = api.whoami()["name"]
+    repo_id = f"{user}/{repo_name(prefix='tmp_test_space')}"
+    request.cls.api = api
+    request.cls.repo_id = repo_id
+
+    # Create and clean space repo
+    api.create_repo(
+        repo_id=repo_id, repo_type="space", space_sdk="gradio", private=True
+    )
+    api.upload_file(
+        path_or_fileobj=_BASIC_APP_PY_TEMPLATE,
+        repo_id=repo_id,
+        repo_type="space",
+        path_in_repo="app.py",
+    )
+    yield
+    api.delete_repo(repo_id=repo_id, repo_type="space")
+
+
+_BASIC_APP_PY_TEMPLATE = """
+import gradio as gr
+
+
+def greet(name):
+    return "Hello " + name + "!!"
+
+iface = gr.Interface(fn=greet, inputs="text", outputs="text")
+iface.launch()
+""".encode()
diff --git a/tests/test_hf_api.py b/tests/test_hf_api.py
--- a/tests/test_hf_api.py
+++ b/tests/test_hf_api.py
@@ -30,7 +30,7 @@
 import pytest
 
 import requests
-from huggingface_hub import Repository
+from huggingface_hub import Repository, SpaceHardware, SpaceStage
 from huggingface_hub._commit_api import (
     CommitOperationAdd,
     CommitOperationDelete,
@@ -2276,6 +2276,83 @@ def test_merge_pull_request(self):
         self.assertIsNotNone(retrieved.merge_commit_oid)
 
 
+@pytest.mark.usefixtures("fx_production_space")
+class TestSpaceAPI(unittest.TestCase):
+    """
+    Testing Space API is not possible on staging. Tests are run against production
+    server using a token stored under `HUGGINGFACE_PRODUCTION_USER_TOKEN` environment
+    variable.
+    """
+
+    repo_id: str
+    api: HfApi
+
+    def test_manage_secrets(self) -> None:
+        # Add 3 secrets
+        self.api.add_space_secret(self.repo_id, "foo", "123")
+        self.api.add_space_secret(self.repo_id, "token", "hf_api_123456")
+        self.api.add_space_secret(self.repo_id, "gh_api_key", "******")
+
+        # Update secret
+        self.api.add_space_secret(self.repo_id, "foo", "456")
+
+        # Delete secret
+        self.api.delete_space_secret(self.repo_id, "gh_api_key")
+
+        # Doesn't fail on missing key
+        self.api.delete_space_secret(self.repo_id, "missing_key")
+
+        # Get all
+        self.assertEqual(
+            self.api.get_space_secrets(repo_id=self.repo_id),
+            {"foo": "456", "token": "hf_api_123456"},
+        )
+
+    def test_create_space_with_hardware(self) -> None:
+        # Clunky but OK: delete repo from fixture
+        self.api.delete_repo(self.repo_id, repo_type="space")
+
+        # Bad request if hardware not supported
+        with self.assertRaises(BadRequestError):
+            self.api.create_repo(
+                self.repo_id,
+                private=True,
+                repo_type="space",
+                space_sdk="gradio",
+                space_hardware="atari-2600",
+            )
+
+        # OK if valid hardware
+        self.api.create_repo(
+            self.repo_id,
+            private=True,
+            repo_type="space",
+            space_sdk="gradio",
+            space_hardware=SpaceHardware.T4_MEDIUM,
+        )
+
+    def test_space_runtime(self) -> None:
+        runtime = self.api.get_space_runtime(self.repo_id)
+
+        # Space has just been created: hardware might not be set yet.
+        self.assertIn(runtime.hardware, (None, SpaceHardware.CPU_BASIC))
+        self.assertIn(runtime.requested_hardware, (None, SpaceHardware.CPU_BASIC))
+
+        # Other fields are fine
+        self.assertEqual(runtime.stage, SpaceStage.BUILDING)
+        self.assertEqual(runtime.stage, "BUILDING")  # Can compare to a string as well
+        self.assertIsInstance(runtime.raw, dict)  # Raw response from Hub
+
+    def test_request_space_hardware(self) -> None:
+        self.api.request_space_hardware(self.repo_id, SpaceHardware.T4_MEDIUM)
+        runtime = self.api.get_space_runtime(self.repo_id)
+        self.assertEqual(runtime.requested_hardware, SpaceHardware.T4_MEDIUM)
+
+    def test_unexistent_space_hardware(self) -> None:
+        with self.assertRaises(BadRequestError):
+            self.api.request_space_hardware(self.repo_id, "atari-2600")
+
+
 @patch("huggingface_hub.hf_api.build_hf_headers")
 class HfApiTokenAttributeTest(unittest.TestCase):
     def test_token_passed(self, mock_build_hf_headers: Mock) -> None:
diff --git a/tests/testing_constants.py b/tests/testing_constants.py
--- a/tests/testing_constants.py
+++ b/tests/testing_constants.py
@@ -1,3 +1,6 @@
+import os
+
+
 USER = "__DUMMY_TRANSFORMERS_USER__"
 FULL_NAME = "Dummy User"
 PASS = "__DUMMY_TRANSFORMERS_PASS__"
@@ -12,3 +15,15 @@
 ENDPOINT_PRODUCTION_URL_SCHEME = (
     ENDPOINT_PRODUCTION + "/{repo_id}/resolve/{revision}/{filename}"
 )
+
+# Token to be set as environment variable.
+# Almost all features are tested on staging environment. However, Spaces are not supported
+# there which makes it impossible to test secrets/hardware requests.
+#
+# Value is set as a secret in Github actions. To make this test work locally, set
+# `HUGGINGFACE_HUB_PRODUCTION_TEST` environment variable on your machine with a personal
+# token (see https://huggingface.co/settings/tokens). The test pipeline will only create
+# private spaces (and delete them afterwards).
+#
+# See `fx_production_space` fixture. Goal is to limit its usage as much as possible.
+PRODUCTION_TOKEN = os.environ.get("HUGGINGFACE_PRODUCTION_USER_TOKEN")