pyrunner / core /models /package.py
Perspicacious's picture
Deploy PyRunner on Hugging Face
2529305
Raw
History Blame Contribute Delete
3.32 kB
"""
Package operation model for tracking async pip operations.
"""
import uuid
from django.conf import settings
from django.db import models
class PackageOperation(models.Model):
"""
Tracks async package installation/uninstallation operations.
Used to provide progress feedback in the UI.
"""
class Operation(models.TextChoices):
INSTALL = "install", "Install"
UNINSTALL = "uninstall", "Uninstall"
BULK_INSTALL = "bulk_install", "Bulk Install"
class Status(models.TextChoices):
PENDING = "pending", "Pending"
RUNNING = "running", "Running"
SUCCESS = "success", "Success"
FAILED = "failed", "Failed"
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
environment = models.ForeignKey(
"core.Environment",
on_delete=models.CASCADE,
related_name="package_operations",
)
operation = models.CharField(
max_length=20,
choices=Operation.choices,
)
package_spec = models.CharField(
max_length=500,
help_text="Package specification (e.g., 'requests==2.31.0') or requirements content for bulk install",
)
status = models.CharField(
max_length=20,
choices=Status.choices,
default=Status.PENDING,
)
output = models.TextField(
blank=True,
help_text="pip stdout",
)
error = models.TextField(
blank=True,
help_text="pip stderr",
)
task_id = models.CharField(
max_length=100,
blank=True,
help_text="django-q2 task ID for tracking",
)
created_at = models.DateTimeField(auto_now_add=True)
started_at = models.DateTimeField(null=True, blank=True)
completed_at = models.DateTimeField(null=True, blank=True)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="package_operations",
)
class Meta:
db_table = "package_operations"
ordering = ["-created_at"]
verbose_name = "package operation"
verbose_name_plural = "package operations"
def __str__(self):
return f"{self.get_operation_display()} {self.package_spec[:50]} ({self.status})"
@property
def is_finished(self) -> bool:
"""Check if the operation has completed (success or failed)."""
return self.status in (self.Status.SUCCESS, self.Status.FAILED)
@property
def is_successful(self) -> bool:
"""Check if the operation completed successfully."""
return self.status == self.Status.SUCCESS
@property
def duration(self):
"""Return the duration of the operation if completed."""
if self.started_at and self.completed_at:
return self.completed_at - self.started_at
return None
@property
def duration_display(self) -> str:
"""Return a human-readable duration string."""
duration = self.duration
if not duration:
return "-"
total_seconds = int(duration.total_seconds())
if total_seconds < 60:
return f"{total_seconds}s"
minutes = total_seconds // 60
seconds = total_seconds % 60
return f"{minutes}m {seconds}s"