Source code for simstack.models.file_instance
from __future__ import annotations
import logging
import shutil
import uuid
from datetime import datetime
from pathlib import Path
from typing import Any, Optional, Union
from odmantic import EmbeddedModel, Field, ObjectId
from pydantic import model_validator
from simstack.models import simstack_model
from simstack.models.parameters import Resource
from simstack.util.file_transfer_client import transfer_resource_name
from simstack.util.file_hashing import hash_file
logger = logging.getLogger("file_instance")
[docs]
@simstack_model
class FileInstance(EmbeddedModel):
"""
Represents an embedded model for a file instance.
The `FileInstance` class is used to encapsulate details about a file,
including its path, associated resource, and creation timestamp.
It provides a class method for initializing a `FileInstance` object
from a local file path, ensuring proper handling of file-related operations.
Attributes:
path (str): Path to the file relative to the host work directory.
resource (Resource): Name of the resource associated with the file.
created_at (datetime): Timestamp indicating when the file instance was created.
"""
id: Optional[str] = Field(default=None, description="File instance id")
path: str = Field(
description="Path to the file relative to the host work directory"
)
resource: Resource = Field(description="Resource name")
created_at: datetime = Field(description="Creation timestamp")
runner_id: Optional[str] = Field(
default=None, description="Runner identity that created the instance"
)
location_type: str = Field(default="local_path", description="File location type")
size_bytes: Optional[int] = Field(default=None, description="File size in bytes")
checksum_sha256: Optional[str] = Field(default=None, description="SHA256 checksum")
last_accessed_at: Optional[datetime] = Field(
default=None, description="Last access timestamp"
)
expires_at: Optional[datetime] = Field(
default=None, description="Expiration timestamp"
)
is_authoritative: bool = Field(
default=True, description="Whether this instance is authoritative"
)
is_cached: bool = Field(
default=False,
description="Whether this instance is a reusable local cache copy",
)
status: str = Field(default="available", description="Instance lifecycle status")
[docs]
@model_validator(mode="before")
def migration(cls, values: Any) -> Any:
if isinstance(values.get("resource"), str):
values["resource"] = Resource(value=values["resource"])
if "path" in values and isinstance(values["path"], Path):
values["path"] = str(values["path"])
if not values.get("id"):
values["id"] = str(uuid.uuid4())
if not values.get("location_type"):
values["location_type"] = "local_path"
if not values.get("status"):
values["status"] = "available"
if values.get("is_authoritative") is None:
values["is_authoritative"] = True
if values.get("is_cached") is None:
values["is_cached"] = False
return values
[docs]
@classmethod
def from_local_file(
cls,
path: Union[Path, str],
file_stack_id: ObjectId,
make_copy: bool = True,
tasks_id: str = "",
) -> FileInstance:
"""
Creates a FileInstance object from a local file path.
This class method is responsible for initializing a `FileInstance` based
on a local file's path. It supports options to hash the file, make a
user-specific copy, and tracks additional metadata. The method handles
local file operations such as copying files to a secure directory when
necessary and organizes resources under a configurable working directory.
:param file_stack_id: the id of the filestack where the file is in
:param path: The file path to the local file. Can be either a string
or `Path`.
:param make_copy: Indicates whether a secure local copy of the file should
be made within the application's working directory. Defaults to True.
:return: A `FileInstance` object initialized with file details.
:rtype: FileInstance
:raises ValueError: If there are issues during the creation of the
`FileInstance` from the specified local file.
"""
source_path = path if isinstance(path, Path) else Path(path)
# Prepare the content field if in_memory is True
resolved_path = Path(path).resolve()
# Find 'simstack' in the path and compute the relative path from its parent
from simstack.core.context import context
workdir = context.config.workdir
resolved_workdir = Path(workdir).resolve()
if tasks_id == "":
logger.debug(f"workdir is {resolved_workdir} path is {resolved_path}")
else:
logger.debug(
f"task_id: {tasks_id} workdir is {resolved_workdir} path is {resolved_path}"
)
try:
relative_path = resolved_path.relative_to(resolved_workdir)
except ValueError:
if tasks_id == "":
logger.error(
f"Path {resolved_path} is not under workdir {resolved_workdir}"
)
else:
logger.error(
f"Path {resolved_path} is not under workdir {resolved_workdir} for task_id: {tasks_id}"
)
import getpass
username = getpass.getuser()
relative_path = Path(username) / str(file_stack_id)
absolute_dir = Path(context.config.workdir) / relative_path
absolute_dir.mkdir(parents=True, exist_ok=True)
shutil.copy(source_path, absolute_dir)
file_instance = FileInstance(
path=str(relative_path),
resource=Resource(value=transfer_resource_name(context.config.resource)),
created_at=datetime.now(),
size_bytes=source_path.stat().st_size if source_path.exists() else None,
checksum_sha256=hash_file(source_path) if source_path.exists() else None,
)
return file_instance