Skip to content

Commit f984cdc

Browse files
WizKnightWauplin
andauthored
Feature: switch visibility with update_repo_settings #2537 (#2541)
* Enhance `update_repo_settings` to manage repo visibility * Enhance `update_repo_settings` to manage repo visibility * Enhance `update_repo_settings` to manage repo visibility * Enhance `update_repo_settings` to manage repo visibility * Enhance `update_repo_settings` to manage repo visibility * Apply suggestions from code review --------- Co-authored-by: Lucain <[email protected]>
1 parent 12eb785 commit f984cdc

File tree

5 files changed

+55
-31
lines changed

5 files changed

+55
-31
lines changed

docs/source/en/guides/repository.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ Some settings are specific to Spaces (hardware, environment variables,...). To c
151151
A repository can be public or private. A private repository is only visible to you or members of the organization in which the repository is located. Change a repository to private as shown in the following:
152152

153153
```py
154-
>>> from huggingface_hub import update_repo_visibility
155-
>>> update_repo_visibility(repo_id=repo_id, private=True)
154+
>>> from huggingface_hub import update_repo_settings
155+
>>> update_repo_settings(repo_id=repo_id, private=True)
156156
```
157157

158158
### Setup gated access

src/huggingface_hub/hf_api.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3524,6 +3524,7 @@ def delete_repo(
35243524
if not missing_ok:
35253525
raise
35263526

3527+
@_deprecate_method(version="0.29", message="Please use `update_repo_settings` instead.")
35273528
@validate_hf_hub_args
35283529
def update_repo_visibility(
35293530
self,
@@ -3535,6 +3536,8 @@ def update_repo_visibility(
35353536
) -> Dict[str, bool]:
35363537
"""Update the visibility setting of a repository.
35373538
3539+
Deprecated. Use `update_repo_settings` instead.
3540+
35383541
Args:
35393542
repo_id (`str`, *optional*):
35403543
A namespace (user or an organization) and a repo name separated by a `/`.
@@ -3581,29 +3584,34 @@ def update_repo_settings(
35813584
self,
35823585
repo_id: str,
35833586
*,
3584-
gated: Literal["auto", "manual", False] = False,
3587+
gated: Optional[Literal["auto", "manual", False]] = None,
3588+
private: Optional[bool] = None,
35853589
token: Union[str, bool, None] = None,
35863590
repo_type: Optional[str] = None,
35873591
) -> None:
35883592
"""
3589-
Update the gated settings of a repository.
3590-
To give more control over how repos are used, the Hub allows repo authors to enable **access requests** for their repos.
3593+
Update the settings of a repository, including gated access and visibility.
3594+
3595+
To give more control over how repos are used, the Hub allows repo authors to enable
3596+
access requests for their repos, and also to set the visibility of the repo to private.
35913597
35923598
Args:
35933599
repo_id (`str`):
35943600
A namespace (user or an organization) and a repo name separated by a /.
35953601
gated (`Literal["auto", "manual", False]`, *optional*):
3596-
The gated release status for the repository.
3602+
The gated status for the repository. If set to `None` (default), the `gated` setting of the repository won't be updated.
35973603
* "auto": The repository is gated, and access requests are automatically approved or denied based on predefined criteria.
35983604
* "manual": The repository is gated, and access requests require manual approval.
3599-
* False (default): The repository is not gated, and anyone can access it.
3605+
* False : The repository is not gated, and anyone can access it.
3606+
private (`bool`, *optional*):
3607+
Whether the model repo should be private.
36003608
token (`Union[str, bool, None]`, *optional*):
36013609
A valid user access token (string). Defaults to the locally saved token,
36023610
which is the recommended method for authentication (see
36033611
https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
36043612
To disable authentication, pass False.
36053613
repo_type (`str`, *optional*):
3606-
The type of the repository to update settings from (`"model"`, `"dataset"` or `"space"`.
3614+
The type of the repository to update settings from (`"model"`, `"dataset"` or `"space"`).
36073615
Defaults to `"model"`.
36083616
36093617
Raises:
@@ -3613,22 +3621,38 @@ def update_repo_settings(
36133621
If repo_type is not one of the values in constants.REPO_TYPES.
36143622
[`~utils.HfHubHTTPError`]:
36153623
If the request to the Hugging Face Hub API fails.
3624+
[`~utils.RepositoryNotFoundError`]
3625+
If the repository to download from cannot be found. This may be because it doesn't exist,
3626+
or because it is set to `private` and you do not have access.
36163627
"""
3617-
if gated not in ["auto", "manual", False]:
3618-
raise ValueError(f"Invalid gated status, must be one of 'auto', 'manual', or False. Got '{gated}'.")
36193628

36203629
if repo_type not in constants.REPO_TYPES:
36213630
raise ValueError(f"Invalid repo type, must be one of {constants.REPO_TYPES}")
36223631
if repo_type is None:
36233632
repo_type = constants.REPO_TYPE_MODEL # default repo type
36243633

3634+
# Check if both gated and private are None
3635+
if gated is None and private is None:
3636+
raise ValueError("At least one of 'gated' or 'private' must be provided.")
3637+
36253638
# Build headers
36263639
headers = self._build_hf_headers(token=token)
36273640

3641+
# Prepare the JSON payload for the PUT request
3642+
payload: Dict = {}
3643+
3644+
if gated is not None:
3645+
if gated not in ["auto", "manual", False]:
3646+
raise ValueError(f"Invalid gated status, must be one of 'auto', 'manual', or False. Got '{gated}'.")
3647+
payload["gated"] = gated
3648+
3649+
if private is not None:
3650+
payload["private"] = private
3651+
36283652
r = get_session().put(
36293653
url=f"{self.endpoint}/api/{repo_type}s/{repo_id}/settings",
36303654
headers=headers,
3631-
json={"gated": gated},
3655+
json=payload,
36323656
)
36333657
hf_raise_for_status(r)
36343658

tests/test_file_download.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def test_download_from_a_gated_repo_with_hf_hub_download(self, repo_url: RepoUrl
161161
repo_id=repo_url.repo_id, filename=".gitattributes", token=OTHER_TOKEN, cache_dir=tmpdir
162162
)
163163

164+
@expect_deprecation("update_repo_visibility")
164165
@use_tmp_repo()
165166
def test_download_regular_file_from_private_renamed_repo(self, repo_url: RepoUrl) -> None:
166167
"""Regression test for #1999.

tests/test_hf_api.py

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import types
2121
import unittest
2222
import uuid
23-
import warnings
2423
from collections.abc import Iterable
2524
from concurrent.futures import Future
2625
from dataclasses import fields
@@ -93,6 +92,7 @@
9392
DUMMY_MODEL_ID_REVISION_ONE_SPECIFIC_COMMIT,
9493
ENDPOINT_PRODUCTION,
9594
SAMPLE_DATASET_IDENTIFIER,
95+
expect_deprecation,
9696
repo_name,
9797
require_git_lfs,
9898
rmtree_with_retry,
@@ -124,18 +124,6 @@ def setUpClass(cls):
124124
cls._api = HfApi(endpoint=ENDPOINT_STAGING, token=TOKEN)
125125

126126

127-
def test_repo_id_no_warning():
128-
# tests that passing repo_id as positional arg doesn't raise any warnings
129-
# for {create, delete}_repo and update_repo_visibility
130-
api = HfApi(endpoint=ENDPOINT_STAGING, token=TOKEN)
131-
132-
with warnings.catch_warnings(record=True) as record:
133-
repo_id = api.create_repo(repo_name()).repo_id
134-
api.update_repo_visibility(repo_id, private=True)
135-
api.delete_repo(repo_id)
136-
assert not len(record)
137-
138-
139127
class HfApiRepoFileExistsTest(HfApiCommonTest):
140128
def setUp(self) -> None:
141129
super().setUp()
@@ -210,6 +198,7 @@ def test_delete_repo_error_message(self):
210198
def test_delete_repo_missing_ok(self) -> None:
211199
self._api.delete_repo("repo-that-does-not-exist", missing_ok=True)
212200

201+
@expect_deprecation("update_repo_visibility")
213202
def test_create_update_and_delete_repo(self):
214203
repo_id = self._api.create_repo(repo_id=repo_name()).repo_id
215204
res = self._api.update_repo_visibility(repo_id=repo_id, private=True)
@@ -218,6 +207,7 @@ def test_create_update_and_delete_repo(self):
218207
assert not res["private"]
219208
self._api.delete_repo(repo_id=repo_id)
220209

210+
@expect_deprecation("update_repo_visibility")
221211
def test_create_update_and_delete_model_repo(self):
222212
repo_id = self._api.create_repo(repo_id=repo_name(), repo_type=constants.REPO_TYPE_MODEL).repo_id
223213
res = self._api.update_repo_visibility(repo_id=repo_id, private=True, repo_type=constants.REPO_TYPE_MODEL)
@@ -226,6 +216,7 @@ def test_create_update_and_delete_model_repo(self):
226216
assert not res["private"]
227217
self._api.delete_repo(repo_id=repo_id, repo_type=constants.REPO_TYPE_MODEL)
228218

219+
@expect_deprecation("update_repo_visibility")
229220
def test_create_update_and_delete_dataset_repo(self):
230221
repo_id = self._api.create_repo(repo_id=repo_name(), repo_type=constants.REPO_TYPE_DATASET).repo_id
231222
res = self._api.update_repo_visibility(repo_id=repo_id, private=True, repo_type=constants.REPO_TYPE_DATASET)
@@ -234,6 +225,7 @@ def test_create_update_and_delete_dataset_repo(self):
234225
assert not res["private"]
235226
self._api.delete_repo(repo_id=repo_id, repo_type=constants.REPO_TYPE_DATASET)
236227

228+
@expect_deprecation("update_repo_visibility")
237229
def test_create_update_and_delete_space_repo(self):
238230
with pytest.raises(ValueError, match=r"No space_sdk provided.*"):
239231
self._api.create_repo(repo_id=repo_name(), repo_type=constants.REPO_TYPE_SPACE, space_sdk=None)
@@ -286,19 +278,25 @@ def test_update_repo_settings(self, repo_url: RepoUrl):
286278
repo_id = repo_url.repo_id
287279

288280
for gated_value in ["auto", "manual", False]:
289-
self._api.update_repo_settings(repo_id=repo_id, gated=gated_value)
290-
info = self._api.model_info(repo_id, expand="gated")
291-
assert info.gated == gated_value
281+
for private_value in [True, False]: # Test both private and public settings
282+
self._api.update_repo_settings(repo_id=repo_id, gated=gated_value, private=private_value)
283+
info = self._api.model_info(repo_id)
284+
assert info.gated == gated_value
285+
assert info.private == private_value # Verify the private setting
292286

293287
@use_tmp_repo(repo_type="dataset")
294288
def test_update_dataset_repo_settings(self, repo_url: RepoUrl):
295289
repo_id = repo_url.repo_id
296290
repo_type = repo_url.repo_type
297291

298292
for gated_value in ["auto", "manual", False]:
299-
self._api.update_repo_settings(repo_id=repo_id, repo_type=repo_type, gated=gated_value)
300-
info = self._api.dataset_info(repo_id, expand="gated")
301-
assert info.gated == gated_value
293+
for private_value in [True, False]:
294+
self._api.update_repo_settings(
295+
repo_id=repo_id, repo_type=repo_type, gated=gated_value, private=private_value
296+
)
297+
info = self._api.dataset_info(repo_id)
298+
assert info.gated == gated_value
299+
assert info.private == private_value
302300

303301

304302
class CommitApiTest(HfApiCommonTest):

tests/test_snapshot_download.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from huggingface_hub.utils import SoftTemporaryDirectory
99

1010
from .testing_constants import TOKEN
11-
from .testing_utils import OfflineSimulationMode, offline, repo_name
11+
from .testing_utils import OfflineSimulationMode, expect_deprecation, offline, repo_name
1212

1313

1414
class SnapshotDownloadTests(unittest.TestCase):
@@ -95,6 +95,7 @@ def test_download_model(self):
9595
# folder name contains the revision's commit sha.
9696
self.assertTrue(self.first_commit_hash in storage_folder)
9797

98+
@expect_deprecation("update_repo_visibility")
9899
def test_download_private_model(self):
99100
self.api.update_repo_visibility(repo_id=self.repo_id, private=True)
100101

0 commit comments

Comments
 (0)