Skip to content

Commit 9f92376

Browse files
committed
Address code review feedback for HuggingFace deployer
Implements 8 improvements from code review: 1. Add organization support for deploying to HF organizations - Added 'organization' config parameter to HuggingFaceDeployerConfig - Updated _get_space_id() to check organization before falling back to username 2. Add space name length validation - Added HF_SPACE_NAME_MAX_LENGTH constant (96 chars) - Validate space name length and raise DeployerError if exceeded 3. Handle space visibility updates - Check if space_info.private != settings.private when updating - Call api.update_repo_visibility() to apply visibility changes 4. Fail on invalid hardware/storage instead of warning - Changed hardware/storage errors to raise DeploymentProvisionError - Added clear error messages with documentation links 5. Use proper error types for 404 detection - Import HfHubHTTPError from huggingface_hub.utils - Check e.response.status_code == 404 instead of string matching 6. Document timeout parameter in deprovision - Added docstring note that timeout is unused (deletion is immediate) 7. Remove unused space_exists variable - Removed space_exists assignments to fix lint error 8. Update documentation terminology - Changed "Docker applications" to "Docker Spaces" in deployers README All changes maintain backward compatibility and improve code quality.
1 parent 3d3d317 commit 9f92376

File tree

3 files changed

+76
-21
lines changed

3 files changed

+76
-21
lines changed

docs/book/component-guide/deployers/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Out of the box, ZenML comes with a `local` deployer already part of the default
3333
| [Docker](docker.md) | `docker` | Built-in | Deploys pipelines as locally running Docker containers |
3434
| [GCP Cloud Run](gcp-cloud-run.md) | `gcp` | `gcp` | Deploys pipelines to Google Cloud Run for serverless execution |
3535
| [AWS App Runner](aws-app-runner.md) | `aws` | `aws` | Deploys pipelines to AWS App Runner for serverless execution |
36-
| [Hugging Face](huggingface.md) | `huggingface` | `huggingface` | Deploys pipelines to Hugging Face Spaces as Docker applications |
36+
| [Hugging Face](huggingface.md) | `huggingface` | `huggingface` | Deploys pipelines to Hugging Face Spaces as Docker Spaces |
3737

3838
If you would like to see the available flavors of deployers, you can use the command:
3939

src/zenml/integrations/huggingface/deployers/huggingface_deployer.py

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454

5555
from zenml.stack import Stack
5656

57+
# HF Space name max length (repo name limit)
58+
HF_SPACE_NAME_MAX_LENGTH = 96
59+
5760
logger = get_logger(__name__)
5861

5962

@@ -172,21 +175,39 @@ def _get_hf_api(self) -> "HfApi":
172175
def _get_space_id(self, deployment: DeploymentResponse) -> str:
173176
"""Get the Space ID for a deployment.
174177
178+
Supports deploying to either a user account or organization.
179+
175180
Args:
176181
deployment: The deployment.
177182
178183
Returns:
179-
The Space ID in format 'username/space-name'.
180-
"""
181-
api = self._get_hf_api()
182-
username = api.whoami()["name"]
184+
The Space ID in format 'owner/space-name' where owner is either
185+
the username or organization name.
183186
184-
# Simple sanitization: alphanumeric, hyphens, underscores only
187+
Raises:
188+
DeployerError: If the space name exceeds HF's maximum length.
189+
"""
190+
# Get owner (organization or username)
191+
if self.config.organization:
192+
owner = self.config.organization
193+
else:
194+
api = self._get_hf_api()
195+
owner = api.whoami()["name"]
196+
197+
# Sanitize deployment name: alphanumeric, hyphens, underscores only
185198
sanitized = re.sub(r"[^a-zA-Z0-9\-_]", "-", deployment.name).lower()
186199
sanitized = sanitized.strip("-") or "deployment"
187200
space_name = f"{self.config.space_prefix}-{sanitized}"
188201

189-
return f"{username}/{space_name}"
202+
# Validate length (HF has 96 char limit for repo names)
203+
if len(space_name) > HF_SPACE_NAME_MAX_LENGTH:
204+
raise DeployerError(
205+
f"Space name '{space_name}' exceeds Hugging Face's "
206+
f"maximum length of {HF_SPACE_NAME_MAX_LENGTH} characters. "
207+
f"Please use a shorter deployment name or space_prefix."
208+
)
209+
210+
return f"{owner}/{space_name}"
190211

191212
def _get_entrypoint_and_command(
192213
self, deployment: DeploymentResponse
@@ -286,10 +307,22 @@ def do_provision_deployment(
286307
)
287308

288309
try:
289-
# Create Space if it doesn't exist
310+
# Create Space if it doesn't exist, or update visibility if needed
290311
try:
291-
api.space_info(space_id)
312+
space_info = api.space_info(space_id)
292313
logger.info(f"Updating existing Space: {space_id}")
314+
315+
# Update visibility if changed
316+
if space_info.private != settings.private:
317+
logger.info(
318+
f"Updating Space visibility: "
319+
f"{'private' if settings.private else 'public'}"
320+
)
321+
api.update_repo_visibility(
322+
repo_id=space_id,
323+
private=settings.private,
324+
repo_type="space",
325+
)
293326
except Exception:
294327
logger.info(f"Creating new Space: {space_id}")
295328
api.create_repo(
@@ -362,32 +395,49 @@ def do_provision_deployment(
362395
except Exception as e:
363396
logger.warning(f"Failed to set secret {key}: {e}")
364397

365-
# Set hardware and storage if specified
398+
# Set hardware if specified (fail if this doesn't work)
366399
hardware = settings.space_hardware or self.config.space_hardware
367400
if hardware:
368-
try:
369-
from huggingface_hub import SpaceHardware
401+
from huggingface_hub import SpaceHardware
370402

403+
try:
371404
api.request_space_hardware(
372405
repo_id=space_id,
373406
hardware=getattr(
374407
SpaceHardware, hardware.upper().replace("-", "_")
375408
),
376409
)
410+
logger.info(f"Requested hardware: {hardware}")
411+
except AttributeError:
412+
raise DeploymentProvisionError(
413+
f"Invalid hardware tier '{hardware}'. "
414+
f"See https://huggingface.co/docs/hub/spaces-gpus"
415+
)
377416
except Exception as e:
378-
logger.warning(f"Failed to set hardware {hardware}: {e}")
417+
raise DeploymentProvisionError(
418+
f"Failed to set hardware {hardware}: {e}"
419+
) from e
379420

421+
# Set storage if specified (fail if this doesn't work)
380422
storage = settings.space_storage or self.config.space_storage
381423
if storage:
382-
try:
383-
from huggingface_hub import SpaceStorage
424+
from huggingface_hub import SpaceStorage
384425

426+
try:
385427
api.request_space_storage(
386428
repo_id=space_id,
387429
storage=getattr(SpaceStorage, storage.upper()),
388430
)
431+
logger.info(f"Requested storage: {storage}")
432+
except AttributeError:
433+
raise DeploymentProvisionError(
434+
f"Invalid storage tier '{storage}'. "
435+
f"Valid options: small, medium, large"
436+
)
389437
except Exception as e:
390-
logger.warning(f"Failed to set storage {storage}: {e}")
438+
raise DeploymentProvisionError(
439+
f"Failed to set storage {storage}: {e}"
440+
) from e
391441

392442
space_url = f"https://huggingface.co/spaces/{space_id}"
393443
return DeploymentOperationalState(
@@ -473,14 +523,13 @@ def do_deprovision_deployment(
473523
474524
Args:
475525
deployment: The deployment to stop.
476-
timeout: Maximum time to wait for deprovision.
526+
timeout: Maximum time to wait for deprovision (unused - deletion is immediate).
477527
478528
Returns:
479529
None, indicating immediate deletion completed.
480530
481531
Raises:
482532
DeploymentNotFoundError: If the Space is not found.
483-
Exception: If deletion fails for reasons other than 404.
484533
"""
485534
space_id = deployment.deployment_metadata.get("space_id")
486535
if not space_id:
@@ -489,11 +538,15 @@ def do_deprovision_deployment(
489538
api = self._get_hf_api()
490539

491540
try:
541+
from huggingface_hub.utils import HfHubHTTPError
542+
492543
api.delete_repo(repo_id=space_id, repo_type="space")
493544
logger.info(f"Deleted Space: {space_id}")
494545
return None
495-
except Exception as e:
496-
if "404" in str(e):
546+
except HfHubHTTPError as e:
547+
if e.response.status_code == 404:
497548
logger.info(f"Space {space_id} already deleted")
498549
return None
499-
raise
550+
raise DeploymentNotFoundError(
551+
f"Failed to delete Space {space_id}: {e}"
552+
) from e

src/zenml/integrations/huggingface/flavors/huggingface_deployer_flavor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@ class HuggingFaceDeployerConfig(BaseDeployerConfig):
3333
Attributes:
3434
token: Hugging Face API token for authentication
3535
secret_name: Name of ZenML secret containing the token
36+
organization: HF organization to deploy to (uses username if not set)
3637
space_hardware: Hardware tier (e.g., 'cpu-basic', 't4-small')
3738
space_storage: Persistent storage tier (e.g., 'small', 'medium', 'large')
3839
space_prefix: Prefix for Space names to organize deployments
3940
"""
4041

4142
token: Optional[str] = SecretField(default=None)
4243
secret_name: Optional[str] = None
44+
organization: Optional[str] = None
4345
space_hardware: Optional[str] = None
4446
space_storage: Optional[str] = None
4547
space_prefix: str = "zenml"

0 commit comments

Comments
 (0)