Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add settings for creating and duplicating spaces #1625

81 changes: 76 additions & 5 deletions docs/source/en/guides/manage-spaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ This can prove useful if you want to build up from an existing Space instead of
It is also useful is you want control over the configuration/settings of a public Space. See [`duplicate_space`] for more details.

```py
>>> duplicate_space("multimodalart/dreambooth-training")
>>> api.duplicate_space("multimodalart/dreambooth-training")
```

**2. Upload your code using your preferred solution.**
Expand All @@ -43,19 +43,21 @@ Here is an example to upload the local folder `src/` from your machine to your S
At this step, your app should already be running on the Hub for free !
However, you might want to configure it further with secrets and upgraded hardware.

**3. Configure secrets**
**3. Configure secrets and variables**

Your Space might require some secret keys or token to work.
Your Space might require some secret keys, token or variables to work.
See [docs](https://huggingface.co/docs/hub/spaces-overview#managing-secrets) for more details.
For example, an HF token to upload an image dataset to the Hub once generated from your Space.

```py
>>> api.add_space_secret(repo_id=repo_id, key="HF_TOKEN", value="hf_api_***")
>>> api.add_space_variable(repo_id=repo_id, key="MODEL_REPO_ID", value="user/repo")
```

Secrets can be deleted as well:
Secrets and variables can be deleted as well:
```py
>>> api.delete_space_secret(repo_id=repo_id, key="HF_TOKEN")
>>> api.delete_space_variable(repo_id=repo_id, key="MODEL_REPO_ID")
```

<Tip>
Expand All @@ -67,6 +69,28 @@ Streamlit Secrets Management if using Streamlit). No need to fetch them via the
Any change in your Space configuration (secrets or hardware) will trigger a restart of your app.
</Tip>

**Bonus: set secrets and variables when creating or duplicating the Space!**

Secrets and variables can be set when creating or duplicating a space:

```py
>>> api.create_repo(
... repo_id=repo_id,
... repo_type="space",
... space_sdk="gradio",
... space_secrets=[{"key"="HF_TOKEN", "value"="hf_api_***"}, ...],
... space_variables=[{"key"="MODEL_REPO_ID", "value"="user/repo"}, ...],
... )
```

```py
>>> api.duplicate_space(
... from_id=repo_id,
... secrets=[{"key"="HF_TOKEN", "value"="hf_api_***"}, ...],
... variables=[{"key"="MODEL_REPO_ID", "value"="user/repo"}, ...],
... )
```

**4. Configure the hardware**

By default, your Space will run on a CPU environment for free. You can upgrade the hardware
Expand Down Expand Up @@ -99,7 +123,7 @@ has been met.
You now have a Space fully configured. Make sure to downgrade your Space back to "cpu-classic"
when you are done using it.

**Bonus: request hardware when creating the Space!**
**Bonus: request hardware when creating or duplicating the Space!**

Upgraded hardware will be automatically assigned to your Space once it's built.

Expand All @@ -109,6 +133,16 @@ Upgraded hardware will be automatically assigned to your Space once it's built.
... repo_type="space",
... space_sdk="gradio"
... space_hardware="cpu-upgrade",
... space_storage="small",
... space_sleep_time="7200", # 2 hours in secs
... )
```
```py
>>> api.duplicate_space(
... from_id=repo_id,
... hardware="cpu-upgrade",
... storage="small",
... sleep_time="7200", # 2 hours in secs
... )
```

Expand Down Expand Up @@ -147,7 +181,27 @@ Upgraded hardware will be automatically assigned to your Space once it's built.
>>> api.request_space_hardware(repo_id=repo_id, hardware=SpaceHardware.T4_MEDIUM, sleep_time=3600)
```

**Bonus: set a sleep time when creating or duplicating the Space!**

```py
>>> api.create_repo(
... repo_id=repo_id,
... repo_type="space",
... space_sdk="gradio"
... space_hardware="t4-medium",
... space_sleep_time="3600",
... )
```
```py
>>> api.duplicate_space(
... from_id=repo_id,
... hardware="t4-medium",
... sleep_time="3600",
... )
```

**6. Add persistent storage to your Space**

You can choose the storage tier of your choice to access disk space that persists across restarts of your Space. This means you can read and write from disk like you would with a traditional hard drive. See [docs](https://huggingface.co/docs/hub/spaces-storage#persistent-storage) for more details.

```py
Expand All @@ -163,6 +217,23 @@ You can also delete your storage, losing all the data permanently.
Note: You cannot decrease the storage tier of your space once it's been granted. To do so,
you must delete the storage first then request the new desired tier.

**Bonus: request storage when creating or duplicating the Space!**

```py
>>> api.create_repo(
... repo_id=repo_id,
... repo_type="space",
... space_sdk="gradio"
... space_storage="large",
... )
```
```py
>>> api.duplicate_space(
... from_id=repo_id,
... storage="large",
... )
```

## More advanced: temporarily upgrade your Space !

Spaces allow for a lot of different use cases. Sometimes, you might want
Expand Down
2 changes: 1 addition & 1 deletion docs/source/en/guides/repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Specify the `repo_id` of the repository you want to delete:

In some cases, you want to copy someone else's repo to adapt it to your use case.
This is possible for Spaces using the [`duplicate_space`] method. It will duplicate the whole repository.
You will still need to configure your own settings (hardware and secrets). Check out our [Manage your Space](./manage-spaces) guide for more details.
You will still need to configure your own settings (hardware, sleep-time, storage, variables and secrets). Check out our [Manage your Space](./manage-spaces) guide for more details.

```py
>>> from huggingface_hub import duplicate_space
Expand Down
77 changes: 67 additions & 10 deletions src/huggingface_hub/hf_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2365,7 +2365,11 @@ def create_repo(
repo_type: Optional[str] = None,
exist_ok: bool = False,
space_sdk: Optional[str] = None,
space_hardware: Optional[str] = None,
space_hardware: Optional[SpaceHardware] = None,
space_storage: Optional[SpaceStorage] = None,
space_sleep_time: Optional[int] = None,
space_secrets: Optional[List[Dict[str, str]]] = None,
space_variables: Optional[List[Dict[str, str]]] = None,
) -> RepoUrl:
"""Create an empty repo on the HuggingFace Hub.

Expand All @@ -2387,6 +2391,19 @@ def create_repo(
Choice of SDK to use if repo_type is "space". Can be "streamlit", "gradio", "docker", or "static".
space_hardware (`SpaceHardware` or `str`, *optional*):
Choice of Hardware if repo_type is "space". See [`SpaceHardware`] for a complete list.
space_storage (`SpaceStorage` or `str`, *optional*):
Choice of persistent storage tier. Example: `"small"`. See [`SpaceStorage`] for a complete list.
space_sleep_time (`int`, *optional*):
Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don't want
your Space to sleep (default behavior for upgraded hardware). For free hardware, you can't configure
the sleep time (value is fixed to 48 hours of inactivity).
See https://huggingface.co/docs/hub/spaces-gpus#sleep-time for more details.
space_secrets (`List[Dict[str, str]]`, *optional*):
A list of secret keys to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional.
For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
space_variables (`List[Dict[str, str]]`, *optional*):
A list of public environment variables to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional.
For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables.

Returns:
[`RepoUrl`]: URL to the newly created repo. Value is a subclass of `str` containing
Expand All @@ -2399,7 +2416,7 @@ def create_repo(
if repo_type not in REPO_TYPES:
raise ValueError("Invalid repo type")

json = {"name": name, "organization": organization, "private": private}
json: Dict[str, Any] = {"name": name, "organization": organization, "private": private}
if repo_type is not None:
json["type"] = repo_type
if repo_type == "space":
Expand All @@ -2415,11 +2432,23 @@ def create_repo(
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'.")
function_args = [
"space_hardware",
"space_storage",
"space_sleep_time",
"space_secrets",
"space_variables",
]
json_keys = ["hardware", "storageTier", "sleepTimeSeconds", "secrets", "variables"]
values = [space_hardware, space_storage, space_sleep_time, space_secrets, space_variables]

if repo_type == "space":
json.update({k: v for k, v in zip(json_keys, values) if v is not None})
else:
provided_space_args = [key for key, value in zip(function_args, values) if value is not None]

if provided_space_args:
warnings.warn(f"Ignoring provided {', '.join(provided_space_args)} because repo_type is not 'space'.")
martinbrose marked this conversation as resolved.
Show resolved Hide resolved

if getattr(self, "_lfsmultipartthresh", None):
# Testing purposes only.
Expand Down Expand Up @@ -5339,6 +5368,11 @@ def duplicate_space(
private: Optional[bool] = None,
token: Optional[str] = None,
exist_ok: bool = False,
hardware: Optional[SpaceHardware] = None,
storage: Optional[SpaceStorage] = None,
sleep_time: Optional[int] = None,
secrets: Optional[List[Dict[str, str]]] = None,
variables: Optional[List[Dict[str, str]]] = None,
) -> RepoUrl:
"""Duplicate a Space.

Expand All @@ -5357,6 +5391,21 @@ def duplicate_space(
Hugging Face token. Will default to the locally saved token if not provided.
exist_ok (`bool`, *optional*, defaults to `False`):
If `True`, do not raise an error if repo already exists.
hardware (`SpaceHardware` or `str`, *optional*):
Choice of Hardware. Example: `"t4-medium"`. See [`SpaceHardware`] for a complete list.
storage (`SpaceStorage` or `str`, *optional*):
Choice of persistent storage tier. Example: `"small"`. See [`SpaceStorage`] for a complete list.
sleep_time (`int`, *optional*):
Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don't want
your Space to sleep (default behavior for upgraded hardware). For free hardware, you can't configure
the sleep time (value is fixed to 48 hours of inactivity).
See https://huggingface.co/docs/hub/spaces-gpus#sleep-time for more details.
secrets (`List[Dict[str, str]]`, *optional*):
A list of secret keys to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional.
For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
variables (`List[Dict[str, str]]`, *optional*):
A list of public environment variables to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional.
For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables.

Returns:
[`RepoUrl`]: URL to the newly created repo. Value is a subclass of `str` containing
Expand Down Expand Up @@ -5396,9 +5445,17 @@ def duplicate_space(
# repository must be a valid repo_id (namespace/repo_name).
payload: Dict[str, Any] = {"repository": f"{to_namespace}/{to_repo_name}"}

# private is optional with this endpoint, with None defaulting to the original space's privacy.
if private is not None:
payload["private"] = private
keys = ["private", "hardware", "storageTier", "sleepTimeSeconds", "secrets", "variables"]
values = [private, hardware, storage, sleep_time, secrets, variables]
payload.update({k: v for k, v in zip(keys, values) if v is not None})

if sleep_time is not None and hardware == SpaceHardware.CPU_BASIC:
warnings.warn(
"If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more"
" than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if"
" you want to set a custom sleep time, you need to upgrade to a paid Hardware.",
UserWarning,
)

r = get_session().post(
f"{self.endpoint}/api/spaces/{from_id}/duplicate",
Expand Down
76 changes: 76 additions & 0 deletions tests/test_hf_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2686,6 +2686,82 @@ def test_create_space_with_hardware(self) -> None:
},
)

def test_create_space_with_hardware_and_sleep_time(self) -> None:
self.api.create_repo(
self.repo_id,
repo_type="space",
space_sdk="gradio",
space_hardware=SpaceHardware.T4_MEDIUM,
space_sleep_time=123,
)
self.post_mock.assert_called_once_with(
f"{self.api.endpoint}/api/repos/create",
headers=self.api._build_hf_headers(),
json={
"name": self.repo_id,
"organization": None,
"private": False,
"type": "space",
"sdk": "gradio",
"hardware": "t4-medium",
"sleepTimeSeconds": 123,
},
)

def test_create_space_with_storage(self) -> None:
self.api.create_repo(
self.repo_id,
repo_type="space",
space_sdk="gradio",
space_storage=SpaceStorage.LARGE,
)
self.post_mock.assert_called_once_with(
f"{self.api.endpoint}/api/repos/create",
headers=self.api._build_hf_headers(),
json={
"name": self.repo_id,
"organization": None,
"private": False,
"type": "space",
"sdk": "gradio",
"storageTier": "large",
},
)

def test_create_space_with_secrets_and_variables(self) -> None:
self.api.create_repo(
self.repo_id,
repo_type="space",
space_sdk="gradio",
space_secrets=[
{"key": "Testsecret", "value": "Testvalue", "description": "Testdescription"},
{"key": "Testsecret2", "value": "Testvalue"},
],
space_variables=[
{"key": "Testvariable", "value": "Testvalue", "description": "Testdescription"},
{"key": "Testvariable2", "value": "Testvalue"},
],
)
self.post_mock.assert_called_once_with(
f"{self.api.endpoint}/api/repos/create",
headers=self.api._build_hf_headers(),
json={
"name": self.repo_id,
"organization": None,
"private": False,
"type": "space",
"sdk": "gradio",
"secrets": [
{"key": "Testsecret", "value": "Testvalue", "description": "Testdescription"},
{"key": "Testsecret2", "value": "Testvalue"},
],
"variables": [
{"key": "Testvariable", "value": "Testvalue", "description": "Testdescription"},
{"key": "Testvariable2", "value": "Testvalue"},
],
},
)

def test_request_space_hardware_no_sleep_time(self) -> None:
self.api.request_space_hardware(self.repo_id, SpaceHardware.T4_MEDIUM)
self.post_mock.assert_called_once_with(
Expand Down