Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- Keep primary/public functions before local helper functions in a module section.
- Keep private classes, exceptions, and similar implementation-specific types close to the private functions that use them unless they are shared more broadly in the module.
- Prefer pydantic-style models in `core/models`.
- Document attributes when the note adds behavior, compatibility, or semantic context that is not obvious from the name and type. Use attribute docstrings without leading newline.
- Tests use `test_*.py` modules and `test_*` functions; fixtures live near usage.

## Testing Guidelines
Expand Down
5 changes: 3 additions & 2 deletions src/dstack/_internal/core/models/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,16 @@ class BackendType(str, enum.Enum):
CLOUDRIFT = "cloudrift"
CRUSOE = "crusoe"
CUDO = "cudo"
DATACRUNCH = "datacrunch" # BackendType for backward compatibility
DATACRUNCH = "datacrunch"
"""`DATACRUNCH` is kept as a `BackendType` for backward compatibility."""
DIGITALOCEAN = "digitalocean"
DSTACK = "dstack"
GCP = "gcp"
HOTAISLE = "hotaisle"
KUBERNETES = "kubernetes"
LAMBDA = "lambda"
LOCAL = "local"
REMOTE = "remote" # TODO: replace for LOCAL
REMOTE = "remote"
NEBIUS = "nebius"
OCI = "oci"
RUNPOD = "runpod"
Expand Down
6 changes: 4 additions & 2 deletions src/dstack/_internal/core/models/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,10 @@ class RegistryAuth(FrozenCoreModel):


class ApplyAction(str, Enum):
CREATE = "create" # resource is to be created or overridden
UPDATE = "update" # resource is to be updated in-place
CREATE = "create"
"""`CREATE` means the resource is to be created or overridden."""
UPDATE = "update"
"""`UPDATE` means the resource is to be updated in-place."""


class NetworkMode(str, Enum):
Expand Down
8 changes: 5 additions & 3 deletions src/dstack/_internal/core/models/compute_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ class ComputeGroupProvisioningData(CoreModel):
compute_group_id: str
compute_group_name: str
backend: BackendType
# In case backend provisions instance in another backend,
# it may set that backend as base_backend.
base_backend: Optional[BackendType] = None
"""`base_backend` may be set when a backend provisions an instance in another backend and needs
to record that backend as `base_backend`.
"""
region: str
job_provisioning_datas: List[JobProvisioningData]
backend_data: Optional[str] = None # backend-specific data in json
backend_data: Optional[str] = None
"""`backend_data` stores backend-specific data in JSON."""


class ComputeGroup(CoreModel):
Expand Down
5 changes: 3 additions & 2 deletions src/dstack/_internal/core/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class RepoConfig(CoreModel):

class GlobalConfig(CoreModel):
projects: Annotated[List[ProjectConfig], Field(description="The list of projects")] = []
# Not used since 0.20.0. Can be removed when most users update their `config.yml` (it's updated
# each time a project is added)
repos: Annotated[list[RepoConfig], Field(exclude=True)] = []
"""`repos` is not used since 0.20.0. It can be removed when most users update their `config.yml`
because it is updated each time a project is added.
"""
11 changes: 7 additions & 4 deletions src/dstack/_internal/core/models/configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ def parse(cls, v: str) -> "PortMapping":


class RepoExistsAction(str, Enum):
# Don't try to check out, terminate the run with an error (the default action since 0.20.0)
ERROR = "error"
# Don't try to check out, skip the repo (the logic hardcoded in the pre-0.20.0 runner)
"""`ERROR` means do not try to check out and terminate the run with an error. This is the default action since 0.20.0."""
SKIP = "skip"
"""`SKIP` means do not try to check out and skip the repo. This is the logic hardcoded in the pre-0.20.0 runner."""


class RepoSpec(CoreModel):
Expand Down Expand Up @@ -469,8 +469,8 @@ class BaseRunConfiguration(CoreModel):
),
),
] = None
# deprecated since 0.18.31; has no effect
home_dir: str = "/root"
"""`home_dir` is deprecated since 0.18.31 and has no effect."""
registry_auth: Annotated[
Optional[RegistryAuth], Field(description="Credentials for pulling a private Docker image")
] = None
Expand Down Expand Up @@ -540,8 +540,11 @@ class BaseRunConfiguration(CoreModel):
list[FilePathMapping],
Field(description="The local to container file path mappings"),
] = []
# deprecated since 0.18.31; task, service -- no effect; dev-environment -- executed right before `init`
setup: CommandsList = []
"""
setup: Deprecated since 0.18.31. It has no effect for tasks and services; for
dev environments it runs right before `init`.
"""

@validator("python", pre=True, always=True)
def convert_python(cls, v, values) -> Optional[PythonVersion]:
Expand Down
14 changes: 8 additions & 6 deletions src/dstack/_internal/core/models/fleets.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@


class FleetStatus(str, Enum):
# Currently all fleets are ACTIVE/TERMINATING/TERMINATED
# SUBMITTED/FAILED may be used if fleets require async processing
# Currently all fleets are ACTIVE, TERMINATING, or TERMINATED.
# SUBMITTED and FAILED may be used if fleets require async processing.
SUBMITTED = "submitted"
ACTIVE = "active"
TERMINATING = "terminating"
Expand Down Expand Up @@ -372,10 +372,11 @@ class FleetSpec(generate_dual_core_model(FleetSpecConfig)):
configuration_path: Optional[str] = None
profile: Profile
autocreated: bool = False
# merged_profile stores profile parameters merged from profile and configuration.
# Read profile parameters from merged_profile instead of profile directly.
# TODO: make merged_profile a computed field after migrating to pydanticV2
# TODO: make `merged_profile` a computed field after migrating to Pydantic v2.
merged_profile: Annotated[Profile, Field(exclude=True)] = None
"""`merged_profile` stores profile parameters merged from `profile` and `configuration`.
Read profile parameters from `merged_profile` instead of `profile` directly.
"""

@root_validator
def _merged_profile(cls, values) -> Dict:
Expand Down Expand Up @@ -416,7 +417,8 @@ class FleetPlan(CoreModel):
offers: List[InstanceOfferWithAvailability]
total_offers: int
max_offer_price: Optional[float] = None
action: Optional[ApplyAction] = None # default value for backward compatibility
action: Optional[ApplyAction] = None
"""`action` uses a default value for backward compatibility."""

def get_effective_spec(self) -> FleetSpec:
if self.effective_spec is not None:
Expand Down
28 changes: 18 additions & 10 deletions src/dstack/_internal/core/models/gateways.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,27 +102,33 @@ class GatewaySpec(CoreModel):


class Gateway(CoreModel):
# ID is only optional on the client side for compatibility with pre-0.20.7 servers.
# TODO(0.21): Make required.
# TODO(0.21): Make `id` required.
id: Optional[uuid.UUID] = None
"""`id` is only optional on the client side for compatibility with pre-0.20.7 servers."""
name: str
configuration: GatewayConfiguration
created_at: datetime.datetime
status: GatewayStatus
status_message: Optional[str]
# The ip address / hostname the user should set up the domain for.
# Could be the same as ip_address but also different, e.g. gateway behind ALB.
hostname: Optional[str]
# The ip address of the gateway instance
"""`hostname` is the IP address or hostname the user should set up the domain for.
Could be the same as `ip_address` but also different, for example a gateway behind ALB.
"""
ip_address: Optional[str]
"""`ip_address` is the IP address of the gateway instance."""
instance_id: Optional[str]
wildcard_domain: Optional[str]
default: bool
# TODO: Deprecated configuration fields duplicated on top-level
# for backward compatibility with 0.19.x clients that expect them required.
# Remove after 0.21
backend: Optional[BackendType] = None
"""`backend` duplicates a configuration field on the top level for backward compatibility
with 0.19.x clients that expect it to be required.
Remove after 0.21.
"""
region: Optional[str] = None
"""`region` duplicates a configuration field on the top level for backward compatibility
with 0.19.x clients that expect it to be required.
Remove after 0.21.
"""


class GatewayPlan(CoreModel):
Expand All @@ -147,8 +153,10 @@ class GatewayComputeConfiguration(CoreModel):

class GatewayProvisioningData(CoreModel):
instance_id: str
ip_address: str # TODO: rename, Kubernetes uses domain names
# TODO: rename `ip_address`; Kubernetes uses domain names here.
ip_address: str
region: str
availability_zone: Optional[str] = None
hostname: Optional[str] = None
backend_data: Optional[str] = None # backend-specific data in json
backend_data: Optional[str] = None
"""`backend_data` stores backend-specific data in JSON."""
28 changes: 18 additions & 10 deletions src/dstack/_internal/core/models/instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
class Gpu(CoreModel):
name: str
memory_mib: int
# Although it's declared as Optional, in fact it always has a value set by the root validator,
# that is, `assert gpu.vendor is not None` should be a safe type narrowing.
vendor: Optional[gpuhunt.AcceleratorVendor] = None
"""`vendor` is declared as optional, but the root validator always sets a value.
`assert gpu.vendor is not None` should be a safe type narrowing.
"""

@root_validator(pre=True)
def validate_name_and_vendor(cls, values):
Expand Down Expand Up @@ -54,13 +55,15 @@ class Resources(CoreModel):
memory_mib: int
gpus: List[Gpu]
spot: bool
disk: Disk = Disk(size_mib=102400) # the default value (100GB) for backward compatibility
disk: Disk = Disk(size_mib=102400)
"""`disk` defaults to 100GB for backward compatibility."""
cpu_arch: Optional[gpuhunt.CPUArchitecture] = None
# Deprecated: description is now generated client-side. TODO: remove in 0.21.
# TODO: remove `description` in 0.21.
description: Annotated[
str,
Field(description="Deprecated: generated client-side. Will be removed in 0.21."),
] = ""
"""`description` is deprecated because it is now generated client-side."""

@root_validator
def _description(cls, values) -> Dict:
Expand Down Expand Up @@ -187,7 +190,8 @@ class RemoteConnectionInfo(CoreModel):
class InstanceConfiguration(CoreModel):
project_name: str
instance_name: str
user: str # dstack user name
user: str
"""`user` stores the dstack user name."""
ssh_keys: List[SSHKey]
instance_id: Optional[str] = None
reservation: Optional[str] = None
Expand All @@ -208,7 +212,8 @@ class InstanceAvailability(Enum):
AVAILABLE = "available"
NOT_AVAILABLE = "not_available"
NO_QUOTA = "no_quota"
NO_BALANCE = "no_balance" # For dstack Sky
NO_BALANCE = "no_balance"
"""`NO_BALANCE` is used for dstack Sky."""
IDLE = "idle"
BUSY = "busy"

Expand Down Expand Up @@ -268,7 +273,8 @@ class InstanceTerminationReason(str, Enum):
NO_OFFERS = "no_offers"
MASTER_FAILED = "master_failed"
MAX_INSTANCES_LIMIT = "max_instances_limit"
NO_BALANCE = "no_balance" # used in dstack Sky
NO_BALANCE = "no_balance"
"""`NO_BALANCE` is used in dstack Sky."""

@classmethod
def from_legacy_str(cls, v: str) -> "InstanceTerminationReason":
Expand Down Expand Up @@ -332,14 +338,16 @@ class Instance(CoreModel):
fleet_id: Optional[UUID] = None
fleet_name: Optional[str] = None
instance_num: int
job_name: Optional[str] = None # deprecated, always None (instance can have more than one job)
job_name: Optional[str] = None
"""`job_name` is deprecated and always `None` because an instance can have more than one job."""
hostname: Optional[str] = None
status: InstanceStatus
unreachable: bool = False
health_status: HealthStatus = HealthStatus.HEALTHY
# termination_reason stores InstanceTerminationReason.
# str allows adding new enum members without breaking compatibility with old clients.
termination_reason: Optional[str] = None
"""`termination_reason` stores `InstanceTerminationReason`.
`str` allows adding new enum members without breaking compatibility with old clients.
"""
termination_reason_message: Optional[str] = None
created: datetime.datetime
finished_at: Optional[datetime.datetime] = None
Expand Down
3 changes: 2 additions & 1 deletion src/dstack/_internal/core/models/placement.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class PlacementGroupConfiguration(CoreModel):


class PlacementGroupProvisioningData(CoreModel):
backend: BackendType # can be different from configuration backend
backend: BackendType
"""`backend` can be different from the backend in `configuration`."""
backend_data: Optional[str] = None


Expand Down
3 changes: 2 additions & 1 deletion src/dstack/_internal/core/models/repos/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ class GitRepoURL:
ssh_port: Optional[str]
path: str

original_host: str # before SSH config lookup
original_host: str
"""`original_host` stores the host value before SSH config lookup."""

@staticmethod
def parse(
Expand Down
4 changes: 2 additions & 2 deletions src/dstack/_internal/core/models/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ def schema_extra(schema: Dict[str, Any]):


class ResourcesSpec(generate_dual_core_model(ResourcesSpecConfig)):
# TODO: Remove Range[int] in 0.20. Range[int] for backward compatibility only.
# TODO: remove `Range[int]` in 0.20. It is kept only for backward compatibility.
cpu: Annotated[Union[CPUSpec, Range[int]], Field(description="The CPU requirements")] = (
CPUSpec()
)
Expand All @@ -390,8 +390,8 @@ class ResourcesSpec(generate_dual_core_model(ResourcesSpecConfig)):
"you may need to configure this"
),
] = None
# Optional for backward compatibility
gpu: Annotated[Optional[GPUSpec], Field(description="The GPU requirements")] = DEFAULT_GPU_SPEC
"""`gpu` is optional for backward compatibility."""
disk: Annotated[Optional[DiskSpec], Field(description="The disk resources")] = DEFAULT_DISK

def pretty_format(self) -> str:
Expand Down
Loading