collections.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. # Copyright 2026 The HuggingFace Team. All rights reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Contains commands to interact with collections on the Hugging Face Hub.
  15. Usage:
  16. # list collections on the Hub
  17. hf collections ls
  18. # list collections for a specific user
  19. hf collections ls --owner username
  20. # get info about a collection
  21. hf collections info username/collection-slug
  22. # create a new collection
  23. hf collections create "My Collection" --description "A collection of models"
  24. # add an item to a collection
  25. hf collections add-item username/collection-slug username/model-name model
  26. # delete a collection
  27. hf collections delete username/collection-slug
  28. """
  29. import enum
  30. from typing import Annotated, get_args
  31. import typer
  32. from huggingface_hub.hf_api import CollectionItemType_T, CollectionSort_T
  33. from ._cli_utils import FormatWithAutoOpt, LimitOpt, TokenOpt, api_object_to_dict, get_hf_api, typer_factory
  34. from ._output import OutputFormatWithAuto, out
  35. # Build enums dynamically from Literal types to avoid duplication
  36. _COLLECTION_ITEM_TYPES = get_args(CollectionItemType_T)
  37. CollectionItemType = enum.Enum("CollectionItemType", {t: t for t in _COLLECTION_ITEM_TYPES}, type=str) # type: ignore[misc]
  38. _COLLECTION_SORT_OPTIONS = get_args(CollectionSort_T)
  39. CollectionSort = enum.Enum("CollectionSort", {s: s for s in _COLLECTION_SORT_OPTIONS}, type=str) # type: ignore[misc]
  40. collections_cli = typer_factory(help="Interact with collections on the Hub.")
  41. @collections_cli.command(
  42. "list | ls",
  43. examples=[
  44. "hf collections ls",
  45. "hf collections ls --owner nvidia",
  46. "hf collections ls --item models/teknium/OpenHermes-2.5-Mistral-7B --limit 10",
  47. ],
  48. )
  49. def collections_ls(
  50. owner: Annotated[
  51. str | None,
  52. typer.Option(help="Filter by owner username or organization."),
  53. ] = None,
  54. item: Annotated[
  55. str | None,
  56. typer.Option(
  57. help='Filter collections containing a specific item (e.g., "models/gpt2", "datasets/squad", "papers/2311.12983").'
  58. ),
  59. ] = None,
  60. sort: Annotated[
  61. CollectionSort | None,
  62. typer.Option(help="Sort results by last modified, trending, or upvotes."),
  63. ] = None,
  64. limit: LimitOpt = 10,
  65. format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
  66. token: TokenOpt = None,
  67. ) -> None:
  68. """List collections on the Hub."""
  69. api = get_hf_api(token=token)
  70. sort_key = sort.value if sort else None
  71. results = [
  72. api_object_to_dict(collection)
  73. for collection in api.list_collections(
  74. owner=owner,
  75. item=item,
  76. sort=sort_key, # type: ignore[arg-type]
  77. limit=limit,
  78. )
  79. ]
  80. out.table(results)
  81. @collections_cli.command(
  82. "info",
  83. examples=[
  84. "hf collections info username/my-collection-slug",
  85. ],
  86. )
  87. def collections_info(
  88. collection_slug: Annotated[str, typer.Argument(help="The collection slug (e.g., 'username/collection-slug').")],
  89. format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
  90. token: TokenOpt = None,
  91. ) -> None:
  92. """Get info about a collection on the Hub."""
  93. api = get_hf_api(token=token)
  94. collection = api.get_collection(collection_slug)
  95. out.dict(collection)
  96. @collections_cli.command(
  97. "create",
  98. examples=[
  99. 'hf collections create "My Models"',
  100. 'hf collections create "My Models" --description "A collection of my favorite models" --private',
  101. 'hf collections create "Org Collection" --namespace my-org',
  102. ],
  103. )
  104. def collections_create(
  105. title: Annotated[str, typer.Argument(help="The title of the collection.")],
  106. namespace: Annotated[
  107. str | None,
  108. typer.Option(help="The namespace (username or organization). Defaults to the authenticated user."),
  109. ] = None,
  110. description: Annotated[
  111. str | None,
  112. typer.Option(help="A description for the collection."),
  113. ] = None,
  114. private: Annotated[
  115. bool,
  116. typer.Option(help="Create a private collection."),
  117. ] = False,
  118. exists_ok: Annotated[
  119. bool,
  120. typer.Option(help="Do not raise an error if the collection already exists."),
  121. ] = False,
  122. format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
  123. token: TokenOpt = None,
  124. ) -> None:
  125. """Create a new collection on the Hub."""
  126. api = get_hf_api(token=token)
  127. collection = api.create_collection(
  128. title=title,
  129. namespace=namespace,
  130. description=description,
  131. private=private,
  132. exists_ok=exists_ok,
  133. )
  134. out.result("Collection created", slug=collection.slug, url=collection.url)
  135. @collections_cli.command(
  136. "update",
  137. examples=[
  138. 'hf collections update username/my-collection --title "New Title"',
  139. 'hf collections update username/my-collection --description "Updated description"',
  140. "hf collections update username/my-collection --private --theme green",
  141. ],
  142. )
  143. def collections_update(
  144. collection_slug: Annotated[str, typer.Argument(help="The collection slug (e.g., 'username/collection-slug').")],
  145. title: Annotated[
  146. str | None,
  147. typer.Option(help="The new title for the collection."),
  148. ] = None,
  149. description: Annotated[
  150. str | None,
  151. typer.Option(help="The new description for the collection."),
  152. ] = None,
  153. position: Annotated[
  154. int | None,
  155. typer.Option(help="The new position of the collection in the owner's list."),
  156. ] = None,
  157. private: Annotated[
  158. bool | None,
  159. typer.Option(help="Whether the collection should be private."),
  160. ] = None,
  161. theme: Annotated[
  162. str | None,
  163. typer.Option(help="The theme color for the collection (e.g., 'green', 'blue')."),
  164. ] = None,
  165. format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
  166. token: TokenOpt = None,
  167. ) -> None:
  168. """Update a collection's metadata on the Hub."""
  169. api = get_hf_api(token=token)
  170. collection = api.update_collection_metadata(
  171. collection_slug=collection_slug,
  172. title=title,
  173. description=description,
  174. position=position,
  175. private=private,
  176. theme=theme,
  177. )
  178. out.result("Collection updated", slug=collection.slug, url=collection.url)
  179. @collections_cli.command(
  180. "delete",
  181. examples=[
  182. "hf collections delete username/my-collection",
  183. "hf collections delete username/my-collection --missing-ok",
  184. ],
  185. )
  186. def collections_delete(
  187. collection_slug: Annotated[str, typer.Argument(help="The collection slug (e.g., 'username/collection-slug').")],
  188. missing_ok: Annotated[
  189. bool,
  190. typer.Option(help="Do not raise an error if the collection doesn't exist."),
  191. ] = False,
  192. format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
  193. token: TokenOpt = None,
  194. ) -> None:
  195. """Delete a collection from the Hub."""
  196. api = get_hf_api(token=token)
  197. api.delete_collection(collection_slug, missing_ok=missing_ok)
  198. out.result("Collection deleted", slug=collection_slug)
  199. @collections_cli.command(
  200. "add-item",
  201. examples=[
  202. "hf collections add-item username/my-collection moonshotai/kimi-k2 model",
  203. 'hf collections add-item username/my-collection Qwen/DeepPlanning dataset --note "Useful dataset"',
  204. "hf collections add-item username/my-collection Tongyi-MAI/Z-Image space",
  205. ],
  206. )
  207. def collections_add_item(
  208. collection_slug: Annotated[str, typer.Argument(help="The collection slug (e.g., 'username/collection-slug').")],
  209. item_id: Annotated[
  210. str, typer.Argument(help="The ID of the item to add (repo_id for repos, paper ID for papers).")
  211. ],
  212. item_type: Annotated[
  213. CollectionItemType,
  214. typer.Argument(help="The type of item (model, dataset, space, paper, collection, or bucket)."),
  215. ],
  216. note: Annotated[
  217. str | None,
  218. typer.Option(help="A note to attach to the item (max 500 characters)."),
  219. ] = None,
  220. exists_ok: Annotated[
  221. bool,
  222. typer.Option(help="Do not raise an error if the item is already in the collection."),
  223. ] = False,
  224. format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
  225. token: TokenOpt = None,
  226. ) -> None:
  227. """Add an item to a collection."""
  228. api = get_hf_api(token=token)
  229. collection = api.add_collection_item(
  230. collection_slug=collection_slug,
  231. item_id=item_id,
  232. item_type=item_type.value, # type: ignore[arg-type]
  233. note=note,
  234. exists_ok=exists_ok,
  235. )
  236. out.result("Item added to collection", slug=collection_slug, url=collection.url)
  237. @collections_cli.command(
  238. "update-item",
  239. examples=[
  240. 'hf collections update-item username/my-collection ITEM_OBJECT_ID --note "Updated note"',
  241. "hf collections update-item username/my-collection ITEM_OBJECT_ID --position 0",
  242. ],
  243. )
  244. def collections_update_item(
  245. collection_slug: Annotated[str, typer.Argument(help="The collection slug (e.g., 'username/collection-slug').")],
  246. item_object_id: Annotated[
  247. str,
  248. typer.Argument(help="The ID of the item in the collection (from 'item_object_id' field, not the repo_id)."),
  249. ],
  250. note: Annotated[
  251. str | None,
  252. typer.Option(help="A new note for the item (max 500 characters)."),
  253. ] = None,
  254. position: Annotated[
  255. int | None,
  256. typer.Option(help="The new position of the item in the collection."),
  257. ] = None,
  258. format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
  259. token: TokenOpt = None,
  260. ) -> None:
  261. """Update an item in a collection."""
  262. api = get_hf_api(token=token)
  263. api.update_collection_item(
  264. collection_slug=collection_slug,
  265. item_object_id=item_object_id,
  266. note=note,
  267. position=position,
  268. )
  269. out.result("Item updated in collection", slug=collection_slug)
  270. @collections_cli.command("delete-item")
  271. def collections_delete_item(
  272. collection_slug: Annotated[str, typer.Argument(help="The collection slug (e.g., 'username/collection-slug').")],
  273. item_object_id: Annotated[
  274. str,
  275. typer.Argument(
  276. help="The ID of the item in the collection (retrieved from `item_object_id` field returned by 'hf collections info'."
  277. ),
  278. ],
  279. missing_ok: Annotated[
  280. bool,
  281. typer.Option(help="Do not raise an error if the item doesn't exist."),
  282. ] = False,
  283. format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
  284. token: TokenOpt = None,
  285. ) -> None:
  286. """Delete an item from a collection."""
  287. api = get_hf_api(token=token)
  288. api.delete_collection_item(
  289. collection_slug=collection_slug,
  290. item_object_id=item_object_id,
  291. missing_ok=missing_ok,
  292. )
  293. out.result("Item deleted from collection", slug=collection_slug)