Source code for simstack.models.resource_definition
from typing import Optional, List
import urllib.parse
import socket
import re
from pathlib import Path
from odmantic import Model, Field, EmbeddedModel
from pydantic import field_validator, model_serializer
from simstack.util.transform_file_name import transform_file_name
[docs]
class GitRepo(Model):
"""
Represents a Git repository with relevant attributes such as its URL, branch,
and whether it is a submodule. Ensures that the URL provided is valid.
This class is used to model information about a Git repository, including
its URL, the branch being used, and whether it is included as a submodule
within another repository. It provides validation for the URL to ensure that
it is in the correct format.
In the user database there is a list of Git repositories for the user
:ivar url: The URL of the Git repository.
:type url: str
:ivar branch: The branch of the Git repository. Optional.
:type branch: Optional[str]
:ivar is_submodule: Indicates whether the repository is a submodule. Defaults to False.
:type is_submodule: bool
"""
url: str
branch: Optional[str]
is_submodule: bool = Field(default=False)
[docs]
@field_validator("url", mode="after")
@classmethod
def validate_url(cls, v):
try:
result = urllib.parse.urlparse(v)
if not all([result.scheme, result.netloc]):
raise ValueError("Invalid URL format")
except Exception:
raise ValueError("Invalid URL format")
return v
[docs]
class ResourceDefinition(Model):
resource_str: str = Field(unique=True)
workdir: str # Change Path to str
hostname: str
python_paths: List[str] = Field(default_factory=list) # Change List[Path] to List[str]
environment_start: Optional[str] = None
ssh_key: Optional[str] = None # Change Optional[Path] to Optional[str]
routes: Optional[List[str]] = []
queue: str = "default"
is_default: bool = False
git_branch: str = "main"
@staticmethod
def _convert_backslashes(path_str: str) -> str:
return re.sub(r'\\+', '/', path_str)
[docs]
@field_validator("workdir", mode="before")
@classmethod
def convert_workdir(cls, v):
if isinstance(v, (Path, str)):
return str(cls._convert_backslashes(str(v)))
return v
[docs]
@field_validator("python_paths", mode="before")
@classmethod
def convert_python_paths(cls, v):
if isinstance(v, list):
return [str(cls._convert_backslashes(str(p))) for p in v]
return v
[docs]
@field_validator("ssh_key", mode="before")
@classmethod
def convert_ssh_key(cls, v):
if v is None:
return None
return str(cls._convert_backslashes(str(v)))
[docs]
@field_validator("queue", mode="before")
@classmethod
def normalize_queue(cls, v):
if v is None:
return "default"
normalized = str(v).strip()
return normalized or "default"
[docs]
def validate_hostname(self):
current_hostname = socket.gethostname()
if self.hostname != current_hostname:
raise ValueError(f"Hostname must match current host. Expected: {current_hostname}, got: {self.hostname}")
[docs]
def validate_ssh_key(self):
if self.ssh_key is not None:
file_path = transform_file_name(Path(self.ssh_key)) # Convert to Path for utility
if not file_path:
raise ValueError(f"SSH key path does not exist: {self.ssh_key}")
[docs]
def get_ssh_key_path(self):
if self.ssh_key is not None:
return transform_file_name(Path(self.ssh_key))
return None
[docs]
def validate_python_path(self):
for path_str in self.python_paths:
path = Path(path_str)
real_path = transform_file_name(path)
if not path.exists():
raise ValueError(f"Python path does not exist: {path}")
if not path.is_dir():
raise ValueError(f"Python path is not a directory: {path}")
[docs]
def get_python_path(self):
if self.python_paths:
return [transform_file_name(Path(p)) for p in self.python_paths]
return None
def __repr__(self):
return f"ResourceDefinition(name={self.resource_str})"
[docs]
@classmethod
def from_resource_definition(cls, resource_definition: dict):
return cls(**resource_definition)