huggingface-hub 0.29.0rc2__py3-none-any.whl → 1.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. huggingface_hub/__init__.py +160 -46
  2. huggingface_hub/_commit_api.py +277 -71
  3. huggingface_hub/_commit_scheduler.py +15 -15
  4. huggingface_hub/_inference_endpoints.py +33 -22
  5. huggingface_hub/_jobs_api.py +301 -0
  6. huggingface_hub/_local_folder.py +18 -3
  7. huggingface_hub/_login.py +31 -63
  8. huggingface_hub/_oauth.py +460 -0
  9. huggingface_hub/_snapshot_download.py +241 -81
  10. huggingface_hub/_space_api.py +18 -10
  11. huggingface_hub/_tensorboard_logger.py +15 -19
  12. huggingface_hub/_upload_large_folder.py +196 -76
  13. huggingface_hub/_webhooks_payload.py +3 -3
  14. huggingface_hub/_webhooks_server.py +15 -25
  15. huggingface_hub/{commands → cli}/__init__.py +1 -15
  16. huggingface_hub/cli/_cli_utils.py +173 -0
  17. huggingface_hub/cli/auth.py +147 -0
  18. huggingface_hub/cli/cache.py +841 -0
  19. huggingface_hub/cli/download.py +189 -0
  20. huggingface_hub/cli/hf.py +60 -0
  21. huggingface_hub/cli/inference_endpoints.py +377 -0
  22. huggingface_hub/cli/jobs.py +772 -0
  23. huggingface_hub/cli/lfs.py +175 -0
  24. huggingface_hub/cli/repo.py +315 -0
  25. huggingface_hub/cli/repo_files.py +94 -0
  26. huggingface_hub/{commands/env.py → cli/system.py} +10 -13
  27. huggingface_hub/cli/upload.py +294 -0
  28. huggingface_hub/cli/upload_large_folder.py +117 -0
  29. huggingface_hub/community.py +20 -12
  30. huggingface_hub/constants.py +83 -59
  31. huggingface_hub/dataclasses.py +609 -0
  32. huggingface_hub/errors.py +99 -30
  33. huggingface_hub/fastai_utils.py +30 -41
  34. huggingface_hub/file_download.py +606 -346
  35. huggingface_hub/hf_api.py +2445 -1132
  36. huggingface_hub/hf_file_system.py +269 -152
  37. huggingface_hub/hub_mixin.py +61 -66
  38. huggingface_hub/inference/_client.py +501 -630
  39. huggingface_hub/inference/_common.py +133 -121
  40. huggingface_hub/inference/_generated/_async_client.py +536 -722
  41. huggingface_hub/inference/_generated/types/__init__.py +6 -1
  42. huggingface_hub/inference/_generated/types/automatic_speech_recognition.py +5 -6
  43. huggingface_hub/inference/_generated/types/base.py +10 -7
  44. huggingface_hub/inference/_generated/types/chat_completion.py +77 -31
  45. huggingface_hub/inference/_generated/types/depth_estimation.py +2 -2
  46. huggingface_hub/inference/_generated/types/document_question_answering.py +2 -2
  47. huggingface_hub/inference/_generated/types/feature_extraction.py +2 -2
  48. huggingface_hub/inference/_generated/types/fill_mask.py +2 -2
  49. huggingface_hub/inference/_generated/types/image_to_image.py +8 -2
  50. huggingface_hub/inference/_generated/types/image_to_text.py +2 -3
  51. huggingface_hub/inference/_generated/types/image_to_video.py +60 -0
  52. huggingface_hub/inference/_generated/types/sentence_similarity.py +3 -3
  53. huggingface_hub/inference/_generated/types/summarization.py +2 -2
  54. huggingface_hub/inference/_generated/types/table_question_answering.py +5 -5
  55. huggingface_hub/inference/_generated/types/text2text_generation.py +2 -2
  56. huggingface_hub/inference/_generated/types/text_generation.py +11 -11
  57. huggingface_hub/inference/_generated/types/text_to_audio.py +1 -2
  58. huggingface_hub/inference/_generated/types/text_to_speech.py +1 -2
  59. huggingface_hub/inference/_generated/types/text_to_video.py +2 -2
  60. huggingface_hub/inference/_generated/types/token_classification.py +2 -2
  61. huggingface_hub/inference/_generated/types/translation.py +2 -2
  62. huggingface_hub/inference/_generated/types/zero_shot_classification.py +2 -2
  63. huggingface_hub/inference/_generated/types/zero_shot_image_classification.py +2 -2
  64. huggingface_hub/inference/_generated/types/zero_shot_object_detection.py +1 -3
  65. huggingface_hub/inference/_mcp/__init__.py +0 -0
  66. huggingface_hub/inference/_mcp/_cli_hacks.py +88 -0
  67. huggingface_hub/inference/_mcp/agent.py +100 -0
  68. huggingface_hub/inference/_mcp/cli.py +247 -0
  69. huggingface_hub/inference/_mcp/constants.py +81 -0
  70. huggingface_hub/inference/_mcp/mcp_client.py +395 -0
  71. huggingface_hub/inference/_mcp/types.py +45 -0
  72. huggingface_hub/inference/_mcp/utils.py +128 -0
  73. huggingface_hub/inference/_providers/__init__.py +149 -20
  74. huggingface_hub/inference/_providers/_common.py +160 -37
  75. huggingface_hub/inference/_providers/black_forest_labs.py +12 -9
  76. huggingface_hub/inference/_providers/cerebras.py +6 -0
  77. huggingface_hub/inference/_providers/clarifai.py +13 -0
  78. huggingface_hub/inference/_providers/cohere.py +32 -0
  79. huggingface_hub/inference/_providers/fal_ai.py +231 -22
  80. huggingface_hub/inference/_providers/featherless_ai.py +38 -0
  81. huggingface_hub/inference/_providers/fireworks_ai.py +22 -1
  82. huggingface_hub/inference/_providers/groq.py +9 -0
  83. huggingface_hub/inference/_providers/hf_inference.py +143 -33
  84. huggingface_hub/inference/_providers/hyperbolic.py +9 -5
  85. huggingface_hub/inference/_providers/nebius.py +47 -5
  86. huggingface_hub/inference/_providers/novita.py +48 -5
  87. huggingface_hub/inference/_providers/nscale.py +44 -0
  88. huggingface_hub/inference/_providers/openai.py +25 -0
  89. huggingface_hub/inference/_providers/publicai.py +6 -0
  90. huggingface_hub/inference/_providers/replicate.py +46 -9
  91. huggingface_hub/inference/_providers/sambanova.py +37 -1
  92. huggingface_hub/inference/_providers/scaleway.py +28 -0
  93. huggingface_hub/inference/_providers/together.py +34 -5
  94. huggingface_hub/inference/_providers/wavespeed.py +138 -0
  95. huggingface_hub/inference/_providers/zai_org.py +17 -0
  96. huggingface_hub/lfs.py +33 -100
  97. huggingface_hub/repocard.py +34 -38
  98. huggingface_hub/repocard_data.py +79 -59
  99. huggingface_hub/serialization/__init__.py +0 -1
  100. huggingface_hub/serialization/_base.py +12 -15
  101. huggingface_hub/serialization/_dduf.py +8 -8
  102. huggingface_hub/serialization/_torch.py +69 -69
  103. huggingface_hub/utils/__init__.py +27 -8
  104. huggingface_hub/utils/_auth.py +7 -7
  105. huggingface_hub/utils/_cache_manager.py +92 -147
  106. huggingface_hub/utils/_chunk_utils.py +2 -3
  107. huggingface_hub/utils/_deprecation.py +1 -1
  108. huggingface_hub/utils/_dotenv.py +55 -0
  109. huggingface_hub/utils/_experimental.py +7 -5
  110. huggingface_hub/utils/_fixes.py +0 -10
  111. huggingface_hub/utils/_git_credential.py +5 -5
  112. huggingface_hub/utils/_headers.py +8 -30
  113. huggingface_hub/utils/_http.py +399 -237
  114. huggingface_hub/utils/_pagination.py +6 -6
  115. huggingface_hub/utils/_parsing.py +98 -0
  116. huggingface_hub/utils/_paths.py +5 -5
  117. huggingface_hub/utils/_runtime.py +74 -22
  118. huggingface_hub/utils/_safetensors.py +21 -21
  119. huggingface_hub/utils/_subprocess.py +13 -11
  120. huggingface_hub/utils/_telemetry.py +4 -4
  121. huggingface_hub/{commands/_cli_utils.py → utils/_terminal.py} +4 -4
  122. huggingface_hub/utils/_typing.py +25 -5
  123. huggingface_hub/utils/_validators.py +55 -74
  124. huggingface_hub/utils/_verification.py +167 -0
  125. huggingface_hub/utils/_xet.py +235 -0
  126. huggingface_hub/utils/_xet_progress_reporting.py +162 -0
  127. huggingface_hub/utils/insecure_hashlib.py +3 -5
  128. huggingface_hub/utils/logging.py +8 -11
  129. huggingface_hub/utils/tqdm.py +33 -4
  130. {huggingface_hub-0.29.0rc2.dist-info → huggingface_hub-1.1.3.dist-info}/METADATA +94 -82
  131. huggingface_hub-1.1.3.dist-info/RECORD +155 -0
  132. {huggingface_hub-0.29.0rc2.dist-info → huggingface_hub-1.1.3.dist-info}/WHEEL +1 -1
  133. huggingface_hub-1.1.3.dist-info/entry_points.txt +6 -0
  134. huggingface_hub/commands/delete_cache.py +0 -428
  135. huggingface_hub/commands/download.py +0 -200
  136. huggingface_hub/commands/huggingface_cli.py +0 -61
  137. huggingface_hub/commands/lfs.py +0 -200
  138. huggingface_hub/commands/repo_files.py +0 -128
  139. huggingface_hub/commands/scan_cache.py +0 -181
  140. huggingface_hub/commands/tag.py +0 -159
  141. huggingface_hub/commands/upload.py +0 -299
  142. huggingface_hub/commands/upload_large_folder.py +0 -129
  143. huggingface_hub/commands/user.py +0 -304
  144. huggingface_hub/commands/version.py +0 -37
  145. huggingface_hub/inference_api.py +0 -217
  146. huggingface_hub/keras_mixin.py +0 -500
  147. huggingface_hub/repository.py +0 -1477
  148. huggingface_hub/serialization/_tensorflow.py +0 -95
  149. huggingface_hub/utils/_hf_folder.py +0 -68
  150. huggingface_hub-0.29.0rc2.dist-info/RECORD +0 -131
  151. huggingface_hub-0.29.0rc2.dist-info/entry_points.txt +0 -6
  152. {huggingface_hub-0.29.0rc2.dist-info → huggingface_hub-1.1.3.dist-info/licenses}/LICENSE +0 -0
  153. {huggingface_hub-0.29.0rc2.dist-info → huggingface_hub-1.1.3.dist-info}/top_level.txt +0 -0
@@ -2,18 +2,17 @@ import time
2
2
  from dataclasses import dataclass, field
3
3
  from datetime import datetime
4
4
  from enum import Enum
5
- from typing import TYPE_CHECKING, Dict, Optional, Union
5
+ from typing import TYPE_CHECKING, Optional, Union
6
6
 
7
7
  from huggingface_hub.errors import InferenceEndpointError, InferenceEndpointTimeoutError
8
8
 
9
- from .inference._client import InferenceClient
10
- from .inference._generated._async_client import AsyncInferenceClient
11
9
  from .utils import get_session, logging, parse_datetime
12
10
 
13
11
 
14
12
  if TYPE_CHECKING:
15
13
  from .hf_api import HfApi
16
-
14
+ from .inference._client import InferenceClient
15
+ from .inference._generated._async_client import AsyncInferenceClient
17
16
 
18
17
  logger = logging.get_logger(__name__)
19
18
 
@@ -63,7 +62,7 @@ class InferenceEndpoint:
63
62
  The timestamp of the last update of the Inference Endpoint.
64
63
  type ([`InferenceEndpointType`]):
65
64
  The type of the Inference Endpoint (public, protected, private).
66
- raw (`Dict`):
65
+ raw (`dict`):
67
66
  The raw dictionary data returned from the API.
68
67
  token (`str` or `bool`, *optional*):
69
68
  Authentication token for the Inference Endpoint, if set when requesting the API. Will default to the
@@ -101,6 +100,7 @@ class InferenceEndpoint:
101
100
  namespace: str
102
101
  repository: str = field(init=False)
103
102
  status: InferenceEndpointStatus = field(init=False)
103
+ health_route: str = field(init=False)
104
104
  url: Optional[str] = field(init=False)
105
105
 
106
106
  # Other fields
@@ -112,7 +112,7 @@ class InferenceEndpoint:
112
112
  type: InferenceEndpointType = field(repr=False, init=False)
113
113
 
114
114
  # Raw dict from the API
115
- raw: Dict = field(repr=False)
115
+ raw: dict = field(repr=False)
116
116
 
117
117
  # Internal fields
118
118
  _token: Union[str, bool, None] = field(repr=False, compare=False)
@@ -120,7 +120,7 @@ class InferenceEndpoint:
120
120
 
121
121
  @classmethod
122
122
  def from_raw(
123
- cls, raw: Dict, namespace: str, token: Union[str, bool, None] = None, api: Optional["HfApi"] = None
123
+ cls, raw: dict, namespace: str, token: Union[str, bool, None] = None, api: Optional["HfApi"] = None
124
124
  ) -> "InferenceEndpoint":
125
125
  """Initialize object from raw dictionary."""
126
126
  if api is None:
@@ -138,7 +138,7 @@ class InferenceEndpoint:
138
138
  self._populate_from_raw()
139
139
 
140
140
  @property
141
- def client(self) -> InferenceClient:
141
+ def client(self) -> "InferenceClient":
142
142
  """Returns a client to make predictions on this Inference Endpoint.
143
143
 
144
144
  Returns:
@@ -152,13 +152,15 @@ class InferenceEndpoint:
152
152
  "Cannot create a client for this Inference Endpoint as it is not yet deployed. "
153
153
  "Please wait for the Inference Endpoint to be deployed using `endpoint.wait()` and try again."
154
154
  )
155
+ from .inference._client import InferenceClient
156
+
155
157
  return InferenceClient(
156
158
  model=self.url,
157
159
  token=self._token, # type: ignore[arg-type] # boolean token shouldn't be possible. In practice it's ok.
158
160
  )
159
161
 
160
162
  @property
161
- def async_client(self) -> AsyncInferenceClient:
163
+ def async_client(self) -> "AsyncInferenceClient":
162
164
  """Returns a client to make predictions on this Inference Endpoint.
163
165
 
164
166
  Returns:
@@ -172,6 +174,8 @@ class InferenceEndpoint:
172
174
  "Cannot create a client for this Inference Endpoint as it is not yet deployed. "
173
175
  "Please wait for the Inference Endpoint to be deployed using `endpoint.wait()` and try again."
174
176
  )
177
+ from .inference._generated._async_client import AsyncInferenceClient
178
+
175
179
  return AsyncInferenceClient(
176
180
  model=self.url,
177
181
  token=self._token, # type: ignore[arg-type] # boolean token shouldn't be possible. In practice it's ok.
@@ -207,16 +211,22 @@ class InferenceEndpoint:
207
211
 
208
212
  start = time.time()
209
213
  while True:
210
- if self.url is not None:
211
- # Means the URL is provisioned => check if the endpoint is reachable
212
- response = get_session().get(self.url, headers=self._api._build_hf_headers(token=self._token))
213
- if response.status_code == 200:
214
- logger.info("Inference Endpoint is ready to be used.")
215
- return self
216
214
  if self.status == InferenceEndpointStatus.FAILED:
217
215
  raise InferenceEndpointError(
218
216
  f"Inference Endpoint {self.name} failed to deploy. Please check the logs for more information."
219
217
  )
218
+ if self.status == InferenceEndpointStatus.UPDATE_FAILED:
219
+ raise InferenceEndpointError(
220
+ f"Inference Endpoint {self.name} failed to update. Please check the logs for more information."
221
+ )
222
+ if self.status == InferenceEndpointStatus.RUNNING and self.url is not None:
223
+ # Verify the endpoint is actually reachable
224
+ _health_url = f"{self.url.rstrip('/')}/{self.health_route.lstrip('/')}"
225
+ response = get_session().get(_health_url, headers=self._api._build_hf_headers(token=self._token))
226
+ if response.status_code == 200:
227
+ logger.info("Inference Endpoint is ready to be used.")
228
+ return self
229
+
220
230
  if timeout is not None:
221
231
  if time.time() - start > timeout:
222
232
  raise InferenceEndpointTimeoutError("Timeout while waiting for Inference Endpoint to be deployed.")
@@ -250,8 +260,8 @@ class InferenceEndpoint:
250
260
  framework: Optional[str] = None,
251
261
  revision: Optional[str] = None,
252
262
  task: Optional[str] = None,
253
- custom_image: Optional[Dict] = None,
254
- secrets: Optional[Dict[str, str]] = None,
263
+ custom_image: Optional[dict] = None,
264
+ secrets: Optional[dict[str, str]] = None,
255
265
  ) -> "InferenceEndpoint":
256
266
  """Update the Inference Endpoint.
257
267
 
@@ -283,10 +293,10 @@ class InferenceEndpoint:
283
293
  The specific model revision to deploy on the Inference Endpoint (e.g. `"6c0e6080953db56375760c0471a8c5f2929baf11"`).
284
294
  task (`str`, *optional*):
285
295
  The task on which to deploy the model (e.g. `"text-classification"`).
286
- custom_image (`Dict`, *optional*):
296
+ custom_image (`dict`, *optional*):
287
297
  A custom Docker image to use for the Inference Endpoint. This is useful if you want to deploy an
288
298
  Inference Endpoint running on the `text-generation-inference` (TGI) framework (see examples).
289
- secrets (`Dict[str, str]`, *optional*):
299
+ secrets (`dict[str, str]`, *optional*):
290
300
  Secret values to inject in the container environment.
291
301
  Returns:
292
302
  [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
@@ -319,7 +329,7 @@ class InferenceEndpoint:
319
329
  """Pause the Inference Endpoint.
320
330
 
321
331
  A paused Inference Endpoint will not be charged. It can be resumed at any time using [`InferenceEndpoint.resume`].
322
- This is different than scaling the Inference Endpoint to zero with [`InferenceEndpoint.scale_to_zero`], which
332
+ This is different from scaling the Inference Endpoint to zero with [`InferenceEndpoint.scale_to_zero`], which
323
333
  would be automatically restarted when a request is made to it.
324
334
 
325
335
  This is an alias for [`HfApi.pause_inference_endpoint`]. The current object is mutated in place with the
@@ -357,8 +367,8 @@ class InferenceEndpoint:
357
367
  def scale_to_zero(self) -> "InferenceEndpoint":
358
368
  """Scale Inference Endpoint to zero.
359
369
 
360
- An Inference Endpoint scaled to zero will not be charged. It will be resume on the next request to it, with a
361
- cold start delay. This is different than pausing the Inference Endpoint with [`InferenceEndpoint.pause`], which
370
+ An Inference Endpoint scaled to zero will not be charged. It will be resumed on the next request to it, with a
371
+ cold start delay. This is different from pausing the Inference Endpoint with [`InferenceEndpoint.pause`], which
362
372
  would require a manual resume with [`InferenceEndpoint.resume`].
363
373
 
364
374
  This is an alias for [`HfApi.scale_to_zero_inference_endpoint`]. The current object is mutated in place with the
@@ -392,6 +402,7 @@ class InferenceEndpoint:
392
402
  self.repository = self.raw["model"]["repository"]
393
403
  self.status = self.raw["status"]["state"]
394
404
  self.url = self.raw["status"].get("url")
405
+ self.health_route = self.raw["healthRoute"]
395
406
 
396
407
  # Other fields
397
408
  self.framework = self.raw["model"]["framework"]
@@ -0,0 +1,301 @@
1
+ # coding=utf-8
2
+ # Copyright 2025-present, the HuggingFace Inc. team.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ from dataclasses import dataclass
16
+ from datetime import datetime
17
+ from enum import Enum
18
+ from typing import Any, Optional, Union
19
+
20
+ from huggingface_hub import constants
21
+ from huggingface_hub._space_api import SpaceHardware
22
+ from huggingface_hub.utils._datetime import parse_datetime
23
+
24
+
25
+ class JobStage(str, Enum):
26
+ """
27
+ Enumeration of possible stage of a Job on the Hub.
28
+
29
+ Value can be compared to a string:
30
+ ```py
31
+ assert JobStage.COMPLETED == "COMPLETED"
32
+ ```
33
+ Possible values are: `COMPLETED`, `CANCELED`, `ERROR`, `DELETED`, `RUNNING`.
34
+ Taken from https://github.com/huggingface/moon-landing/blob/main/server/job_types/JobInfo.ts#L61 (private url).
35
+ """
36
+
37
+ # Copied from moon-landing > server > lib > Job.ts
38
+ COMPLETED = "COMPLETED"
39
+ CANCELED = "CANCELED"
40
+ ERROR = "ERROR"
41
+ DELETED = "DELETED"
42
+ RUNNING = "RUNNING"
43
+
44
+
45
+ @dataclass
46
+ class JobStatus:
47
+ stage: JobStage
48
+ message: Optional[str]
49
+
50
+
51
+ @dataclass
52
+ class JobOwner:
53
+ id: str
54
+ name: str
55
+ type: str
56
+
57
+
58
+ @dataclass
59
+ class JobInfo:
60
+ """
61
+ Contains information about a Job.
62
+
63
+ Args:
64
+ id (`str`):
65
+ Job ID.
66
+ created_at (`datetime` or `None`):
67
+ When the Job was created.
68
+ docker_image (`str` or `None`):
69
+ The Docker image from Docker Hub used for the Job.
70
+ Can be None if space_id is present instead.
71
+ space_id (`str` or `None`):
72
+ The Docker image from Hugging Face Spaces used for the Job.
73
+ Can be None if docker_image is present instead.
74
+ command (`list[str]` or `None`):
75
+ Command of the Job, e.g. `["python", "-c", "print('hello world')"]`
76
+ arguments (`list[str]` or `None`):
77
+ Arguments passed to the command
78
+ environment (`dict[str]` or `None`):
79
+ Environment variables of the Job as a dictionary.
80
+ secrets (`dict[str]` or `None`):
81
+ Secret environment variables of the Job (encrypted).
82
+ flavor (`str` or `None`):
83
+ Flavor for the hardware, as in Hugging Face Spaces. See [`SpaceHardware`] for possible values.
84
+ E.g. `"cpu-basic"`.
85
+ status: (`JobStatus` or `None`):
86
+ Status of the Job, e.g. `JobStatus(stage="RUNNING", message=None)`
87
+ See [`JobStage`] for possible stage values.
88
+ owner: (`JobOwner` or `None`):
89
+ Owner of the Job, e.g. `JobOwner(id="5e9ecfc04957053f60648a3e", name="lhoestq", type="user")`
90
+
91
+ Example:
92
+
93
+ ```python
94
+ >>> from huggingface_hub import run_job
95
+ >>> job = run_job(
96
+ ... image="python:3.12",
97
+ ... command=["python", "-c", "print('Hello from the cloud!')"]
98
+ ... )
99
+ >>> job
100
+ JobInfo(id='687fb701029421ae5549d998', created_at=datetime.datetime(2025, 7, 22, 16, 6, 25, 79000, tzinfo=datetime.timezone.utc), docker_image='python:3.12', space_id=None, command=['python', '-c', "print('Hello from the cloud!')"], arguments=[], environment={}, secrets={}, flavor='cpu-basic', status=JobStatus(stage='RUNNING', message=None), owner=JobOwner(id='5e9ecfc04957053f60648a3e', name='lhoestq', type='user'), endpoint='https://huggingface.co', url='https://huggingface.co/jobs/lhoestq/687fb701029421ae5549d998')
101
+ >>> job.id
102
+ '687fb701029421ae5549d998'
103
+ >>> job.url
104
+ 'https://huggingface.co/jobs/lhoestq/687fb701029421ae5549d998'
105
+ >>> job.status.stage
106
+ 'RUNNING'
107
+ ```
108
+ """
109
+
110
+ id: str
111
+ created_at: Optional[datetime]
112
+ docker_image: Optional[str]
113
+ space_id: Optional[str]
114
+ command: Optional[list[str]]
115
+ arguments: Optional[list[str]]
116
+ environment: Optional[dict[str, Any]]
117
+ secrets: Optional[dict[str, Any]]
118
+ flavor: Optional[SpaceHardware]
119
+ status: JobStatus
120
+ owner: JobOwner
121
+
122
+ # Inferred fields
123
+ endpoint: str
124
+ url: str
125
+
126
+ def __init__(self, **kwargs) -> None:
127
+ self.id = kwargs["id"]
128
+ created_at = kwargs.get("createdAt") or kwargs.get("created_at")
129
+ self.created_at = parse_datetime(created_at) if created_at else None
130
+ self.docker_image = kwargs.get("dockerImage") or kwargs.get("docker_image")
131
+ self.space_id = kwargs.get("spaceId") or kwargs.get("space_id")
132
+ owner = kwargs.get("owner", {})
133
+ self.owner = JobOwner(id=owner["id"], name=owner["name"], type=owner["type"])
134
+ self.command = kwargs.get("command")
135
+ self.arguments = kwargs.get("arguments")
136
+ self.environment = kwargs.get("environment")
137
+ self.secrets = kwargs.get("secrets")
138
+ self.flavor = kwargs.get("flavor")
139
+ status = kwargs.get("status", {})
140
+ self.status = JobStatus(stage=status["stage"], message=status.get("message"))
141
+
142
+ # Inferred fields
143
+ self.endpoint = kwargs.get("endpoint", constants.ENDPOINT)
144
+ self.url = f"{self.endpoint}/jobs/{self.owner.name}/{self.id}"
145
+
146
+
147
+ @dataclass
148
+ class JobSpec:
149
+ docker_image: Optional[str]
150
+ space_id: Optional[str]
151
+ command: Optional[list[str]]
152
+ arguments: Optional[list[str]]
153
+ environment: Optional[dict[str, Any]]
154
+ secrets: Optional[dict[str, Any]]
155
+ flavor: Optional[SpaceHardware]
156
+ timeout: Optional[int]
157
+ tags: Optional[list[str]]
158
+ arch: Optional[str]
159
+
160
+ def __init__(self, **kwargs) -> None:
161
+ self.docker_image = kwargs.get("dockerImage") or kwargs.get("docker_image")
162
+ self.space_id = kwargs.get("spaceId") or kwargs.get("space_id")
163
+ self.command = kwargs.get("command")
164
+ self.arguments = kwargs.get("arguments")
165
+ self.environment = kwargs.get("environment")
166
+ self.secrets = kwargs.get("secrets")
167
+ self.flavor = kwargs.get("flavor")
168
+ self.timeout = kwargs.get("timeout")
169
+ self.tags = kwargs.get("tags")
170
+ self.arch = kwargs.get("arch")
171
+
172
+
173
+ @dataclass
174
+ class LastJobInfo:
175
+ id: str
176
+ at: datetime
177
+
178
+ def __init__(self, **kwargs) -> None:
179
+ self.id = kwargs["id"]
180
+ self.at = parse_datetime(kwargs["at"])
181
+
182
+
183
+ @dataclass
184
+ class ScheduledJobStatus:
185
+ last_job: Optional[LastJobInfo]
186
+ next_job_run_at: Optional[datetime]
187
+
188
+ def __init__(self, **kwargs) -> None:
189
+ last_job = kwargs.get("lastJob") or kwargs.get("last_job")
190
+ self.last_job = LastJobInfo(**last_job) if last_job else None
191
+ next_job_run_at = kwargs.get("nextJobRunAt") or kwargs.get("next_job_run_at")
192
+ self.next_job_run_at = parse_datetime(str(next_job_run_at)) if next_job_run_at else None
193
+
194
+
195
+ @dataclass
196
+ class ScheduledJobInfo:
197
+ """
198
+ Contains information about a Job.
199
+
200
+ Args:
201
+ id (`str`):
202
+ Scheduled Job ID.
203
+ created_at (`datetime` or `None`):
204
+ When the scheduled Job was created.
205
+ tags (`list[str]` or `None`):
206
+ The tags of the scheduled Job.
207
+ schedule (`str` or `None`):
208
+ One of "@annually", "@yearly", "@monthly", "@weekly", "@daily", "@hourly", or a
209
+ CRON schedule expression (e.g., '0 9 * * 1' for 9 AM every Monday).
210
+ suspend (`bool` or `None`):
211
+ Whether the scheduled job is suspended (paused).
212
+ concurrency (`bool` or `None`):
213
+ Whether multiple instances of this Job can run concurrently.
214
+ status (`ScheduledJobStatus` or `None`):
215
+ Status of the scheduled Job.
216
+ owner: (`JobOwner` or `None`):
217
+ Owner of the scheduled Job, e.g. `JobOwner(id="5e9ecfc04957053f60648a3e", name="lhoestq", type="user")`
218
+ job_spec: (`JobSpec` or `None`):
219
+ Specifications of the Job.
220
+
221
+ Example:
222
+
223
+ ```python
224
+ >>> from huggingface_hub import run_job
225
+ >>> scheduled_job = create_scheduled_job(
226
+ ... image="python:3.12",
227
+ ... command=["python", "-c", "print('Hello from the cloud!')"],
228
+ ... schedule="@hourly",
229
+ ... )
230
+ >>> scheduled_job.id
231
+ '687fb701029421ae5549d999'
232
+ >>> scheduled_job.status.next_job_run_at
233
+ datetime.datetime(2025, 7, 22, 17, 6, 25, 79000, tzinfo=datetime.timezone.utc)
234
+ ```
235
+ """
236
+
237
+ id: str
238
+ created_at: Optional[datetime]
239
+ job_spec: JobSpec
240
+ schedule: Optional[str]
241
+ suspend: Optional[bool]
242
+ concurrency: Optional[bool]
243
+ status: ScheduledJobStatus
244
+ owner: JobOwner
245
+
246
+ def __init__(self, **kwargs) -> None:
247
+ self.id = kwargs["id"]
248
+ created_at = kwargs.get("createdAt") or kwargs.get("created_at")
249
+ self.created_at = parse_datetime(created_at) if created_at else None
250
+ self.job_spec = JobSpec(**(kwargs.get("job_spec") or kwargs.get("jobSpec", {})))
251
+ self.schedule = kwargs.get("schedule")
252
+ self.suspend = kwargs.get("suspend")
253
+ self.concurrency = kwargs.get("concurrency")
254
+ status = kwargs.get("status", {})
255
+ self.status = ScheduledJobStatus(
256
+ last_job=status.get("last_job") or status.get("lastJob"),
257
+ next_job_run_at=status.get("next_job_run_at") or status.get("nextJobRunAt"),
258
+ )
259
+ owner = kwargs.get("owner", {})
260
+ self.owner = JobOwner(id=owner["id"], name=owner["name"], type=owner["type"])
261
+
262
+
263
+ def _create_job_spec(
264
+ *,
265
+ image: str,
266
+ command: list[str],
267
+ env: Optional[dict[str, Any]],
268
+ secrets: Optional[dict[str, Any]],
269
+ flavor: Optional[SpaceHardware],
270
+ timeout: Optional[Union[int, float, str]],
271
+ ) -> dict[str, Any]:
272
+ # prepare job spec to send to HF Jobs API
273
+ job_spec: dict[str, Any] = {
274
+ "command": command,
275
+ "arguments": [],
276
+ "environment": env or {},
277
+ "flavor": flavor or SpaceHardware.CPU_BASIC,
278
+ }
279
+ # secrets are optional
280
+ if secrets:
281
+ job_spec["secrets"] = secrets
282
+ # timeout is optional
283
+ if timeout:
284
+ time_units_factors = {"s": 1, "m": 60, "h": 3600, "d": 3600 * 24}
285
+ if isinstance(timeout, str) and timeout[-1] in time_units_factors:
286
+ job_spec["timeoutSeconds"] = int(float(timeout[:-1]) * time_units_factors[timeout[-1]])
287
+ else:
288
+ job_spec["timeoutSeconds"] = int(timeout)
289
+ # input is either from docker hub or from HF spaces
290
+ for prefix in (
291
+ "https://huggingface.co/spaces/",
292
+ "https://hf.co/spaces/",
293
+ "huggingface.co/spaces/",
294
+ "hf.co/spaces/",
295
+ ):
296
+ if image.startswith(prefix):
297
+ job_spec["spaceId"] = image[len(prefix) :]
298
+ break
299
+ else:
300
+ job_spec["dockerImage"] = image
301
+ return job_spec
@@ -86,7 +86,13 @@ class LocalDownloadFilePaths:
86
86
 
87
87
  def incomplete_path(self, etag: str) -> Path:
88
88
  """Return the path where a file will be temporarily downloaded before being moved to `file_path`."""
89
- return self.metadata_path.parent / f"{_short_hash(self.metadata_path.name)}.{etag}.incomplete"
89
+ path = self.metadata_path.parent / f"{_short_hash(self.metadata_path.name)}.{etag}.incomplete"
90
+ resolved_path = str(path.resolve())
91
+ # Some Windows versions do not allow for paths longer than 255 characters.
92
+ # In this case, we must specify it as an extended path by using the "\\?\" prefix.
93
+ if os.name == "nt" and len(resolved_path) > 255 and not resolved_path.startswith("\\\\?\\"):
94
+ path = Path("\\\\?\\" + resolved_path)
95
+ return path
90
96
 
91
97
 
92
98
  @dataclass(frozen=True)
@@ -149,6 +155,7 @@ class LocalUploadFileMetadata:
149
155
  should_ignore: Optional[bool] = None
150
156
  sha256: Optional[str] = None
151
157
  upload_mode: Optional[str] = None
158
+ remote_oid: Optional[str] = None
152
159
  is_uploaded: bool = False
153
160
  is_committed: bool = False
154
161
 
@@ -174,6 +181,10 @@ class LocalUploadFileMetadata:
174
181
  f.write(self.upload_mode)
175
182
  f.write("\n")
176
183
 
184
+ if self.remote_oid is not None:
185
+ f.write(self.remote_oid)
186
+ f.write("\n")
187
+
177
188
  f.write(str(int(self.is_uploaded)) + "\n")
178
189
  f.write(str(int(self.is_committed)) + "\n")
179
190
 
@@ -195,7 +206,7 @@ def get_local_download_paths(local_dir: Path, filename: str) -> LocalDownloadFil
195
206
  [`LocalDownloadFilePaths`]: the paths to the files (file_path, lock_path, metadata_path, incomplete_path).
196
207
  """
197
208
  # filename is the path in the Hub repository (separated by '/')
198
- # make sure to have a cross platform transcription
209
+ # make sure to have a cross-platform transcription
199
210
  sanitized_filename = os.path.join(*filename.split("/"))
200
211
  if os.name == "nt":
201
212
  if sanitized_filename.startswith("..\\") or "\\..\\" in sanitized_filename:
@@ -235,7 +246,7 @@ def get_local_upload_paths(local_dir: Path, filename: str) -> LocalUploadFilePat
235
246
  [`LocalUploadFilePaths`]: the paths to the files (file_path, lock_path, metadata_path).
236
247
  """
237
248
  # filename is the path in the Hub repository (separated by '/')
238
- # make sure to have a cross platform transcription
249
+ # make sure to have a cross-platform transcription
239
250
  sanitized_filename = os.path.join(*filename.split("/"))
240
251
  if os.name == "nt":
241
252
  if sanitized_filename.startswith("..\\") or "\\..\\" in sanitized_filename:
@@ -346,6 +357,9 @@ def read_upload_metadata(local_dir: Path, filename: str) -> LocalUploadFileMetad
346
357
  if upload_mode not in (None, "regular", "lfs"):
347
358
  raise ValueError(f"Invalid upload mode in metadata {paths.path_in_repo}: {upload_mode}")
348
359
 
360
+ _remote_oid = f.readline().strip()
361
+ remote_oid = None if _remote_oid == "" else _remote_oid
362
+
349
363
  is_uploaded = bool(int(f.readline().strip()))
350
364
  is_committed = bool(int(f.readline().strip()))
351
365
 
@@ -355,6 +369,7 @@ def read_upload_metadata(local_dir: Path, filename: str) -> LocalUploadFileMetad
355
369
  should_ignore=should_ignore,
356
370
  sha256=sha256,
357
371
  upload_mode=upload_mode,
372
+ remote_oid=remote_oid,
358
373
  is_uploaded=is_uploaded,
359
374
  is_committed=is_committed,
360
375
  )