huggingface-hub 0.33.5__py3-none-any.whl → 0.35.0rc0__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.

Potentially problematic release.


This version of huggingface-hub might be problematic. Click here for more details.

Files changed (68) hide show
  1. huggingface_hub/__init__.py +487 -525
  2. huggingface_hub/_commit_api.py +21 -28
  3. huggingface_hub/_jobs_api.py +145 -0
  4. huggingface_hub/_local_folder.py +7 -1
  5. huggingface_hub/_login.py +5 -5
  6. huggingface_hub/_oauth.py +1 -1
  7. huggingface_hub/_snapshot_download.py +11 -6
  8. huggingface_hub/_upload_large_folder.py +46 -23
  9. huggingface_hub/cli/__init__.py +27 -0
  10. huggingface_hub/cli/_cli_utils.py +69 -0
  11. huggingface_hub/cli/auth.py +210 -0
  12. huggingface_hub/cli/cache.py +405 -0
  13. huggingface_hub/cli/download.py +181 -0
  14. huggingface_hub/cli/hf.py +66 -0
  15. huggingface_hub/cli/jobs.py +522 -0
  16. huggingface_hub/cli/lfs.py +198 -0
  17. huggingface_hub/cli/repo.py +243 -0
  18. huggingface_hub/cli/repo_files.py +128 -0
  19. huggingface_hub/cli/system.py +52 -0
  20. huggingface_hub/cli/upload.py +316 -0
  21. huggingface_hub/cli/upload_large_folder.py +132 -0
  22. huggingface_hub/commands/_cli_utils.py +5 -0
  23. huggingface_hub/commands/delete_cache.py +3 -1
  24. huggingface_hub/commands/download.py +4 -0
  25. huggingface_hub/commands/env.py +3 -0
  26. huggingface_hub/commands/huggingface_cli.py +2 -0
  27. huggingface_hub/commands/repo.py +4 -0
  28. huggingface_hub/commands/repo_files.py +4 -0
  29. huggingface_hub/commands/scan_cache.py +3 -1
  30. huggingface_hub/commands/tag.py +3 -1
  31. huggingface_hub/commands/upload.py +4 -0
  32. huggingface_hub/commands/upload_large_folder.py +3 -1
  33. huggingface_hub/commands/user.py +11 -1
  34. huggingface_hub/commands/version.py +3 -0
  35. huggingface_hub/constants.py +1 -0
  36. huggingface_hub/file_download.py +16 -5
  37. huggingface_hub/hf_api.py +519 -7
  38. huggingface_hub/hf_file_system.py +8 -16
  39. huggingface_hub/hub_mixin.py +3 -3
  40. huggingface_hub/inference/_client.py +38 -39
  41. huggingface_hub/inference/_common.py +38 -11
  42. huggingface_hub/inference/_generated/_async_client.py +50 -51
  43. huggingface_hub/inference/_generated/types/__init__.py +1 -0
  44. huggingface_hub/inference/_generated/types/image_to_video.py +60 -0
  45. huggingface_hub/inference/_mcp/cli.py +36 -18
  46. huggingface_hub/inference/_mcp/constants.py +8 -0
  47. huggingface_hub/inference/_mcp/types.py +3 -0
  48. huggingface_hub/inference/_providers/__init__.py +4 -1
  49. huggingface_hub/inference/_providers/_common.py +3 -6
  50. huggingface_hub/inference/_providers/fal_ai.py +85 -42
  51. huggingface_hub/inference/_providers/hf_inference.py +17 -9
  52. huggingface_hub/inference/_providers/replicate.py +19 -1
  53. huggingface_hub/keras_mixin.py +2 -2
  54. huggingface_hub/repocard.py +1 -1
  55. huggingface_hub/repository.py +2 -2
  56. huggingface_hub/utils/_auth.py +1 -1
  57. huggingface_hub/utils/_cache_manager.py +2 -2
  58. huggingface_hub/utils/_dotenv.py +51 -0
  59. huggingface_hub/utils/_headers.py +1 -1
  60. huggingface_hub/utils/_runtime.py +1 -1
  61. huggingface_hub/utils/_xet.py +6 -2
  62. huggingface_hub/utils/_xet_progress_reporting.py +141 -0
  63. {huggingface_hub-0.33.5.dist-info → huggingface_hub-0.35.0rc0.dist-info}/METADATA +7 -8
  64. {huggingface_hub-0.33.5.dist-info → huggingface_hub-0.35.0rc0.dist-info}/RECORD +68 -51
  65. {huggingface_hub-0.33.5.dist-info → huggingface_hub-0.35.0rc0.dist-info}/entry_points.txt +1 -0
  66. {huggingface_hub-0.33.5.dist-info → huggingface_hub-0.35.0rc0.dist-info}/LICENSE +0 -0
  67. {huggingface_hub-0.33.5.dist-info → huggingface_hub-0.35.0rc0.dist-info}/WHEEL +0 -0
  68. {huggingface_hub-0.33.5.dist-info → huggingface_hub-0.35.0rc0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,522 @@
1
+ # Copyright 2025 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 jobs on the Hugging Face Hub.
15
+
16
+ Usage:
17
+ # run a job
18
+ hf jobs run <image> <command>
19
+
20
+ # List running or completed jobs
21
+ hf jobs ps [-a] [-f key=value] [--format TEMPLATE]
22
+
23
+ # Stream logs from a job
24
+ hf jobs logs <job-id>
25
+
26
+ # Inspect detailed information about a job
27
+ hf jobs inspect <job-id>
28
+
29
+ # Cancel a running job
30
+ hf jobs cancel <job-id>
31
+ """
32
+
33
+ import json
34
+ import os
35
+ import re
36
+ from argparse import Namespace, _SubParsersAction
37
+ from dataclasses import asdict
38
+ from pathlib import Path
39
+ from typing import Dict, List, Optional, Union
40
+
41
+ import requests
42
+
43
+ from huggingface_hub import HfApi, SpaceHardware
44
+ from huggingface_hub.utils import logging
45
+ from huggingface_hub.utils._dotenv import load_dotenv
46
+
47
+ from . import BaseHuggingfaceCLICommand
48
+
49
+
50
+ logger = logging.get_logger(__name__)
51
+
52
+ SUGGESTED_FLAVORS = [item.value for item in SpaceHardware if item.value != "zero-a10g"]
53
+
54
+
55
+ class JobsCommands(BaseHuggingfaceCLICommand):
56
+ @staticmethod
57
+ def register_subcommand(parser: _SubParsersAction):
58
+ jobs_parser = parser.add_parser("jobs", help="Run and manage Jobs on the Hub.")
59
+ jobs_subparsers = jobs_parser.add_subparsers(help="huggingface.co jobs related commands")
60
+
61
+ # Register commands
62
+ InspectCommand.register_subcommand(jobs_subparsers)
63
+ LogsCommand.register_subcommand(jobs_subparsers)
64
+ PsCommand.register_subcommand(jobs_subparsers)
65
+ RunCommand.register_subcommand(jobs_subparsers)
66
+ CancelCommand.register_subcommand(jobs_subparsers)
67
+ UvCommand.register_subcommand(jobs_subparsers)
68
+
69
+
70
+ class RunCommand(BaseHuggingfaceCLICommand):
71
+ @staticmethod
72
+ def register_subcommand(parser: _SubParsersAction) -> None:
73
+ run_parser = parser.add_parser("run", help="Run a Job")
74
+ run_parser.add_argument("image", type=str, help="The Docker image to use.")
75
+ run_parser.add_argument("-e", "--env", action="append", help="Set environment variables.")
76
+ run_parser.add_argument("-s", "--secrets", action="append", help="Set secret environment variables.")
77
+ run_parser.add_argument("--env-file", type=str, help="Read in a file of environment variables.")
78
+ run_parser.add_argument("--secrets-file", type=str, help="Read in a file of secret environment variables.")
79
+ run_parser.add_argument(
80
+ "--flavor",
81
+ type=str,
82
+ help=f"Flavor for the hardware, as in HF Spaces. Defaults to `cpu-basic`. Possible values: {', '.join(SUGGESTED_FLAVORS)}.",
83
+ )
84
+ run_parser.add_argument(
85
+ "--timeout",
86
+ type=str,
87
+ help="Max duration: int/float with s (seconds, default), m (minutes), h (hours) or d (days).",
88
+ )
89
+ run_parser.add_argument(
90
+ "-d",
91
+ "--detach",
92
+ action="store_true",
93
+ help="Run the Job in the background and print the Job ID.",
94
+ )
95
+ run_parser.add_argument(
96
+ "--namespace",
97
+ type=str,
98
+ help="The namespace where the Job will be created. Defaults to the current user's namespace.",
99
+ )
100
+ run_parser.add_argument(
101
+ "--token",
102
+ type=str,
103
+ help="A User Access Token generated from https://huggingface.co/settings/tokens",
104
+ )
105
+ run_parser.add_argument("command", nargs="...", help="The command to run.")
106
+ run_parser.set_defaults(func=RunCommand)
107
+
108
+ def __init__(self, args: Namespace) -> None:
109
+ self.image: str = args.image
110
+ self.command: List[str] = args.command
111
+ self.env: dict[str, Optional[str]] = {}
112
+ if args.env_file:
113
+ self.env.update(load_dotenv(Path(args.env_file).read_text()))
114
+ for env_value in args.env or []:
115
+ self.env.update(load_dotenv(env_value))
116
+ self.secrets: dict[str, Optional[str]] = {}
117
+ if args.secrets_file:
118
+ self.secrets.update(load_dotenv(Path(args.secrets_file).read_text()))
119
+ for secret in args.secrets or []:
120
+ self.secrets.update(load_dotenv(secret))
121
+ self.flavor: Optional[SpaceHardware] = args.flavor
122
+ self.timeout: Optional[str] = args.timeout
123
+ self.detach: bool = args.detach
124
+ self.namespace: Optional[str] = args.namespace
125
+ self.token: Optional[str] = args.token
126
+
127
+ def run(self) -> None:
128
+ api = HfApi(token=self.token)
129
+ job = api.run_job(
130
+ image=self.image,
131
+ command=self.command,
132
+ env=self.env,
133
+ secrets=self.secrets,
134
+ flavor=self.flavor,
135
+ timeout=self.timeout,
136
+ namespace=self.namespace,
137
+ )
138
+ # Always print the job ID to the user
139
+ print(f"Job started with ID: {job.id}")
140
+ print(f"View at: {job.url}")
141
+
142
+ if self.detach:
143
+ return
144
+
145
+ # Now let's stream the logs
146
+ for log in api.fetch_job_logs(job_id=job.id):
147
+ print(log)
148
+
149
+
150
+ class LogsCommand(BaseHuggingfaceCLICommand):
151
+ @staticmethod
152
+ def register_subcommand(parser: _SubParsersAction) -> None:
153
+ run_parser = parser.add_parser("logs", help="Fetch the logs of a Job")
154
+ run_parser.add_argument("job_id", type=str, help="Job ID")
155
+ run_parser.add_argument(
156
+ "--namespace",
157
+ type=str,
158
+ help="The namespace where the job is running. Defaults to the current user's namespace.",
159
+ )
160
+ run_parser.add_argument(
161
+ "--token", type=str, help="A User Access Token generated from https://huggingface.co/settings/tokens"
162
+ )
163
+ run_parser.set_defaults(func=LogsCommand)
164
+
165
+ def __init__(self, args: Namespace) -> None:
166
+ self.job_id: str = args.job_id
167
+ self.namespace: Optional[str] = args.namespace
168
+ self.token: Optional[str] = args.token
169
+
170
+ def run(self) -> None:
171
+ api = HfApi(token=self.token)
172
+ for log in api.fetch_job_logs(job_id=self.job_id, namespace=self.namespace):
173
+ print(log)
174
+
175
+
176
+ def _tabulate(rows: List[List[Union[str, int]]], headers: List[str]) -> str:
177
+ """
178
+ Inspired by:
179
+
180
+ - stackoverflow.com/a/8356620/593036
181
+ - stackoverflow.com/questions/9535954/printing-lists-as-tabular-data
182
+ """
183
+ col_widths = [max(len(str(x)) for x in col) for col in zip(*rows, headers)]
184
+ terminal_width = max(os.get_terminal_size().columns, len(headers) * 12)
185
+ while len(headers) + sum(col_widths) > terminal_width:
186
+ col_to_minimize = col_widths.index(max(col_widths))
187
+ col_widths[col_to_minimize] //= 2
188
+ if len(headers) + sum(col_widths) <= terminal_width:
189
+ col_widths[col_to_minimize] = terminal_width - sum(col_widths) - len(headers) + col_widths[col_to_minimize]
190
+ row_format = ("{{:{}}} " * len(headers)).format(*col_widths)
191
+ lines = []
192
+ lines.append(row_format.format(*headers))
193
+ lines.append(row_format.format(*["-" * w for w in col_widths]))
194
+ for row in rows:
195
+ row_format_args = [
196
+ str(x)[: col_width - 3] + "..." if len(str(x)) > col_width else str(x)
197
+ for x, col_width in zip(row, col_widths)
198
+ ]
199
+ lines.append(row_format.format(*row_format_args))
200
+ return "\n".join(lines)
201
+
202
+
203
+ class PsCommand(BaseHuggingfaceCLICommand):
204
+ @staticmethod
205
+ def register_subcommand(parser: _SubParsersAction) -> None:
206
+ run_parser = parser.add_parser("ps", help="List Jobs")
207
+ run_parser.add_argument(
208
+ "-a",
209
+ "--all",
210
+ action="store_true",
211
+ help="Show all Jobs (default shows just running)",
212
+ )
213
+ run_parser.add_argument(
214
+ "--namespace",
215
+ type=str,
216
+ help="The namespace from where it lists the jobs. Defaults to the current user's namespace.",
217
+ )
218
+ run_parser.add_argument(
219
+ "--token",
220
+ type=str,
221
+ help="A User Access Token generated from https://huggingface.co/settings/tokens",
222
+ )
223
+ # Add Docker-style filtering argument
224
+ run_parser.add_argument(
225
+ "-f",
226
+ "--filter",
227
+ action="append",
228
+ default=[],
229
+ help="Filter output based on conditions provided (format: key=value)",
230
+ )
231
+ # Add option to format output
232
+ run_parser.add_argument(
233
+ "--format",
234
+ type=str,
235
+ help="Format output using a custom template",
236
+ )
237
+ run_parser.set_defaults(func=PsCommand)
238
+
239
+ def __init__(self, args: Namespace) -> None:
240
+ self.all: bool = args.all
241
+ self.namespace: Optional[str] = args.namespace
242
+ self.token: Optional[str] = args.token
243
+ self.format: Optional[str] = args.format
244
+ self.filters: Dict[str, str] = {}
245
+
246
+ # Parse filter arguments (key=value pairs)
247
+ for f in args.filter:
248
+ if "=" in f:
249
+ key, value = f.split("=", 1)
250
+ self.filters[key.lower()] = value
251
+ else:
252
+ print(f"Warning: Ignoring invalid filter format '{f}'. Use key=value format.")
253
+
254
+ def run(self) -> None:
255
+ """
256
+ Fetch and display job information for the current user.
257
+ Uses Docker-style filtering with -f/--filter flag and key=value pairs.
258
+ """
259
+ try:
260
+ api = HfApi(token=self.token)
261
+
262
+ # Fetch jobs data
263
+ jobs = api.list_jobs(namespace=self.namespace)
264
+
265
+ # Define table headers
266
+ table_headers = ["JOB ID", "IMAGE/SPACE", "COMMAND", "CREATED", "STATUS"]
267
+
268
+ # Process jobs data
269
+ rows = []
270
+
271
+ for job in jobs:
272
+ # Extract job data for filtering
273
+ status = job.status.stage if job.status else "UNKNOWN"
274
+
275
+ # Skip job if not all jobs should be shown and status doesn't match criteria
276
+ if not self.all and status not in ("RUNNING", "UPDATING"):
277
+ continue
278
+
279
+ # Extract job ID
280
+ job_id = job.id
281
+
282
+ # Extract image or space information
283
+ image_or_space = job.docker_image or "N/A"
284
+
285
+ # Extract and format command
286
+ command = job.command or []
287
+ command_str = " ".join(command) if command else "N/A"
288
+
289
+ # Extract creation time
290
+ created_at = job.created_at or "N/A"
291
+
292
+ # Create a dict with all job properties for filtering
293
+ job_properties = {
294
+ "id": job_id,
295
+ "image": image_or_space,
296
+ "status": status.lower(),
297
+ "command": command_str,
298
+ }
299
+
300
+ # Check if job matches all filters
301
+ if not self._matches_filters(job_properties):
302
+ continue
303
+
304
+ # Create row
305
+ rows.append([job_id, image_or_space, command_str, created_at, status])
306
+
307
+ # Handle empty results
308
+ if not rows:
309
+ filters_msg = ""
310
+ if self.filters:
311
+ filters_msg = f" matching filters: {', '.join([f'{k}={v}' for k, v in self.filters.items()])}"
312
+
313
+ print(f"No jobs found{filters_msg}")
314
+ return
315
+
316
+ # Apply custom format if provided or use default tabular format
317
+ self._print_output(rows, table_headers)
318
+
319
+ except requests.RequestException as e:
320
+ print(f"Error fetching jobs data: {e}")
321
+ except (KeyError, ValueError, TypeError) as e:
322
+ print(f"Error processing jobs data: {e}")
323
+ except Exception as e:
324
+ print(f"Unexpected error - {type(e).__name__}: {e}")
325
+
326
+ def _matches_filters(self, job_properties: Dict[str, str]) -> bool:
327
+ """Check if job matches all specified filters."""
328
+ for key, pattern in self.filters.items():
329
+ # Check if property exists
330
+ if key not in job_properties:
331
+ return False
332
+
333
+ # Support pattern matching with wildcards
334
+ if "*" in pattern or "?" in pattern:
335
+ # Convert glob pattern to regex
336
+ regex_pattern = pattern.replace("*", ".*").replace("?", ".")
337
+ if not re.search(f"^{regex_pattern}$", job_properties[key], re.IGNORECASE):
338
+ return False
339
+ # Simple substring matching
340
+ elif pattern.lower() not in job_properties[key].lower():
341
+ return False
342
+
343
+ return True
344
+
345
+ def _print_output(self, rows, headers):
346
+ """Print output according to the chosen format."""
347
+ if self.format:
348
+ # Custom template formatting (simplified)
349
+ template = self.format
350
+ for row in rows:
351
+ line = template
352
+ for i, field in enumerate(["id", "image", "command", "created", "status"]):
353
+ placeholder = f"{{{{.{field}}}}}"
354
+ if placeholder in line:
355
+ line = line.replace(placeholder, str(row[i]))
356
+ print(line)
357
+ else:
358
+ # Default tabular format
359
+ print(
360
+ _tabulate(
361
+ rows,
362
+ headers=headers,
363
+ )
364
+ )
365
+
366
+
367
+ class InspectCommand(BaseHuggingfaceCLICommand):
368
+ @staticmethod
369
+ def register_subcommand(parser: _SubParsersAction) -> None:
370
+ run_parser = parser.add_parser("inspect", help="Display detailed information on one or more Jobs")
371
+ run_parser.add_argument(
372
+ "--namespace",
373
+ type=str,
374
+ help="The namespace where the job is running. Defaults to the current user's namespace.",
375
+ )
376
+ run_parser.add_argument(
377
+ "--token", type=str, help="A User Access Token generated from https://huggingface.co/settings/tokens"
378
+ )
379
+ run_parser.add_argument("job_ids", nargs="...", help="The jobs to inspect")
380
+ run_parser.set_defaults(func=InspectCommand)
381
+
382
+ def __init__(self, args: Namespace) -> None:
383
+ self.namespace: Optional[str] = args.namespace
384
+ self.token: Optional[str] = args.token
385
+ self.job_ids: List[str] = args.job_ids
386
+
387
+ def run(self) -> None:
388
+ api = HfApi(token=self.token)
389
+ jobs = [api.inspect_job(job_id=job_id, namespace=self.namespace) for job_id in self.job_ids]
390
+ print(json.dumps([asdict(job) for job in jobs], indent=4, default=str))
391
+
392
+
393
+ class CancelCommand(BaseHuggingfaceCLICommand):
394
+ @staticmethod
395
+ def register_subcommand(parser: _SubParsersAction) -> None:
396
+ run_parser = parser.add_parser("cancel", help="Cancel a Job")
397
+ run_parser.add_argument("job_id", type=str, help="Job ID")
398
+ run_parser.add_argument(
399
+ "--namespace",
400
+ type=str,
401
+ help="The namespace where the job is running. Defaults to the current user's namespace.",
402
+ )
403
+ run_parser.add_argument(
404
+ "--token", type=str, help="A User Access Token generated from https://huggingface.co/settings/tokens"
405
+ )
406
+ run_parser.set_defaults(func=CancelCommand)
407
+
408
+ def __init__(self, args: Namespace) -> None:
409
+ self.job_id: str = args.job_id
410
+ self.namespace = args.namespace
411
+ self.token: Optional[str] = args.token
412
+
413
+ def run(self) -> None:
414
+ api = HfApi(token=self.token)
415
+ api.cancel_job(job_id=self.job_id, namespace=self.namespace)
416
+
417
+
418
+ class UvCommand(BaseHuggingfaceCLICommand):
419
+ """Run UV scripts on Hugging Face infrastructure."""
420
+
421
+ @staticmethod
422
+ def register_subcommand(parser):
423
+ """Register UV run subcommand."""
424
+ uv_parser = parser.add_parser(
425
+ "uv",
426
+ help="Run UV scripts (Python with inline dependencies) on HF infrastructure",
427
+ )
428
+
429
+ subparsers = uv_parser.add_subparsers(dest="uv_command", help="UV commands", required=True)
430
+
431
+ # Run command only
432
+ run_parser = subparsers.add_parser(
433
+ "run",
434
+ help="Run a UV script (local file or URL) on HF infrastructure",
435
+ )
436
+ run_parser.add_argument("script", help="UV script to run (local file or URL)")
437
+ run_parser.add_argument("script_args", nargs="...", help="Arguments for the script", default=[])
438
+ run_parser.add_argument("--image", type=str, help="Use a custom Docker image with `uv` installed.")
439
+ run_parser.add_argument(
440
+ "--repo",
441
+ help="Repository name for the script (creates ephemeral if not specified)",
442
+ )
443
+ run_parser.add_argument(
444
+ "--flavor",
445
+ type=str,
446
+ help=f"Flavor for the hardware, as in HF Spaces. Defaults to `cpu-basic`. Possible values: {', '.join(SUGGESTED_FLAVORS)}.",
447
+ )
448
+ run_parser.add_argument("-e", "--env", action="append", help="Environment variables")
449
+ run_parser.add_argument("-s", "--secrets", action="append", help="Secret environment variables")
450
+ run_parser.add_argument("--env-file", type=str, help="Read in a file of environment variables.")
451
+ run_parser.add_argument(
452
+ "--secrets-file",
453
+ type=str,
454
+ help="Read in a file of secret environment variables.",
455
+ )
456
+ run_parser.add_argument("--timeout", type=str, help="Max duration (e.g., 30s, 5m, 1h)")
457
+ run_parser.add_argument("-d", "--detach", action="store_true", help="Run in background")
458
+ run_parser.add_argument(
459
+ "--namespace",
460
+ type=str,
461
+ help="The namespace where the Job will be created. Defaults to the current user's namespace.",
462
+ )
463
+ run_parser.add_argument("--token", type=str, help="HF token")
464
+ # UV options
465
+ run_parser.add_argument("--with", action="append", help="Run with the given packages installed", dest="with_")
466
+ run_parser.add_argument(
467
+ "-p", "--python", type=str, help="The Python interpreter to use for the run environment"
468
+ )
469
+ run_parser.set_defaults(func=UvCommand)
470
+
471
+ def __init__(self, args: Namespace) -> None:
472
+ """Initialize the command with parsed arguments."""
473
+ self.script = args.script
474
+ self.script_args = args.script_args
475
+ self.dependencies = args.with_
476
+ self.python = args.python
477
+ self.image = args.image
478
+ self.env: dict[str, Optional[str]] = {}
479
+ if args.env_file:
480
+ self.env.update(load_dotenv(Path(args.env_file).read_text()))
481
+ for env_value in args.env or []:
482
+ self.env.update(load_dotenv(env_value))
483
+ self.secrets: dict[str, Optional[str]] = {}
484
+ if args.secrets_file:
485
+ self.secrets.update(load_dotenv(Path(args.secrets_file).read_text()))
486
+ for secret in args.secrets or []:
487
+ self.secrets.update(load_dotenv(secret))
488
+ self.flavor: Optional[SpaceHardware] = args.flavor
489
+ self.timeout: Optional[str] = args.timeout
490
+ self.detach: bool = args.detach
491
+ self.namespace: Optional[str] = args.namespace
492
+ self.token: Optional[str] = args.token
493
+ self._repo = args.repo
494
+
495
+ def run(self) -> None:
496
+ """Execute UV command."""
497
+ logging.set_verbosity(logging.INFO)
498
+ api = HfApi(token=self.token)
499
+ job = api.run_uv_job(
500
+ script=self.script,
501
+ script_args=self.script_args,
502
+ dependencies=self.dependencies,
503
+ python=self.python,
504
+ image=self.image,
505
+ env=self.env,
506
+ secrets=self.secrets,
507
+ flavor=self.flavor,
508
+ timeout=self.timeout,
509
+ namespace=self.namespace,
510
+ _repo=self._repo,
511
+ )
512
+
513
+ # Always print the job ID to the user
514
+ print(f"Job started with ID: {job.id}")
515
+ print(f"View at: {job.url}")
516
+
517
+ if self.detach:
518
+ return
519
+
520
+ # Now let's stream the logs
521
+ for log in api.fetch_job_logs(job_id=job.id):
522
+ print(log)