From 3dac8470d7aec069677f850014dcbb73913ccb8a Mon Sep 17 00:00:00 2001 From: Dmitry Meyer Date: Fri, 17 Jul 2026 12:46:46 +0000 Subject: [PATCH 1/2] CLI: add `--no-profile` option Disables default profile loading. Mutually exclusive with `--profile`. Supported by the following commands: * `dstack apply` (run configurations only) * `dstack preset create|apply` * `dstack offer` --- src/dstack/_internal/cli/commands/endpoint.py | 9 ++++++--- src/dstack/_internal/cli/commands/offer.py | 5 ++--- .../cli/services/configurators/run.py | 9 ++++++--- src/dstack/_internal/cli/services/profile.py | 18 +++++++++++++++++- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/dstack/_internal/cli/commands/endpoint.py b/src/dstack/_internal/cli/commands/endpoint.py index 0bf75b42b9..be609af5f8 100644 --- a/src/dstack/_internal/cli/commands/endpoint.py +++ b/src/dstack/_internal/cli/commands/endpoint.py @@ -15,13 +15,16 @@ EndpointPresetStore, load_endpoint_configuration, ) -from dstack._internal.cli.services.profile import apply_profile_args, register_profile_args +from dstack._internal.cli.services.profile import ( + apply_profile_args, + load_profile_from_args, + register_profile_args, +) from dstack._internal.cli.utils.common import confirm_ask, console from dstack._internal.core.errors import CLIError from dstack._internal.core.models.profiles import ProfileParams from dstack._internal.core.services import is_valid_dstack_resource_name from dstack.api import Client -from dstack.api.utils import load_profile class EndpointCommand(BaseCommand): @@ -255,7 +258,7 @@ def _get_effective_configuration( args: argparse.Namespace, ) -> EndpointConfiguration: _apply_name(configuration, args.name) - profile = load_profile(Path.cwd(), args.profile) + profile = load_profile_from_args(args=args, repo_dir=Path.cwd()) for field in ProfileParams.__fields__: if getattr(configuration, field) is None: setattr(configuration, field, getattr(profile, field)) diff --git a/src/dstack/_internal/cli/commands/offer.py b/src/dstack/_internal/cli/commands/offer.py index 92157ad9b1..9ca84ed718 100644 --- a/src/dstack/_internal/cli/commands/offer.py +++ b/src/dstack/_internal/cli/commands/offer.py @@ -6,7 +6,7 @@ from dstack._internal.cli.services.configurators.run import ( BaseRunConfigurator, ) -from dstack._internal.cli.services.profile import register_profile_args +from dstack._internal.cli.services.profile import load_profile_from_args, register_profile_args from dstack._internal.cli.services.resources import register_resources_args from dstack._internal.cli.utils.common import console from dstack._internal.cli.utils.gpu import print_gpu_json, print_gpu_table @@ -15,7 +15,6 @@ from dstack._internal.core.models.configurations import ApplyConfigurationType, TaskConfiguration from dstack._internal.core.models.gpus import GpuGroup from dstack._internal.core.models.runs import RunSpec -from dstack.api.utils import load_profile class OfferConfigurator(BaseRunConfigurator): @@ -81,7 +80,7 @@ def _command(self, args: argparse.Namespace): configurator = OfferConfigurator(api_client=self.api) configurator.apply_args(conf, args) - profile = load_profile(Path.cwd(), profile_name=args.profile) + profile = load_profile_from_args(args=args, repo_dir=Path.cwd()) run_spec = RunSpec( configuration=conf, diff --git a/src/dstack/_internal/cli/services/configurators/run.py b/src/dstack/_internal/cli/services/configurators/run.py index 3541bd19b7..f71193bd5a 100644 --- a/src/dstack/_internal/cli/services/configurators/run.py +++ b/src/dstack/_internal/cli/services/configurators/run.py @@ -17,7 +17,11 @@ ApplyEnvVarsConfiguratorMixin, BaseApplyConfigurator, ) -from dstack._internal.cli.services.profile import apply_profile_args, register_profile_args +from dstack._internal.cli.services.profile import ( + apply_profile_args, + load_profile_from_args, + register_profile_args, +) from dstack._internal.cli.services.repos import ( get_repo_from_dir, get_repo_from_url, @@ -68,7 +72,6 @@ from dstack._internal.utils.nested_list import NestedList, NestedListItem from dstack._internal.utils.path import is_absolute_posix_path from dstack.api._public.runs import Run -from dstack.api.utils import load_profile _KNOWN_AMD_GPUS = {gpu.name.lower() for gpu in gpuhunt.KNOWN_AMD_GPUS} _KNOWN_NVIDIA_GPUS = {gpu.name.lower() for gpu in gpuhunt.KNOWN_NVIDIA_GPUS} @@ -132,7 +135,7 @@ def get_plan( repo = self.get_repo(conf, configuration_path, configurator_args) if repo is None: repo = init_default_virtual_repo(api=self.api) - profile = load_profile(Path.cwd(), configurator_args.profile) + profile = load_profile_from_args(args=configurator_args, repo_dir=Path.cwd()) with console.status("Getting apply plan..."): run_plan = self.api.runs.get_run_plan( configuration=conf, diff --git a/src/dstack/_internal/cli/services/profile.py b/src/dstack/_internal/cli/services/profile.py index 9c00b55fea..ced7f04e82 100644 --- a/src/dstack/_internal/cli/services/profile.py +++ b/src/dstack/_internal/cli/services/profile.py @@ -3,12 +3,15 @@ from dstack._internal.core.models.profiles import ( CreationPolicy, + Profile, ProfileParams, ProfileRetry, SpotPolicy, parse_duration, parse_max_duration, ) +from dstack._internal.utils.path import PathLike +from dstack.api.utils import load_profile def register_profile_args(parser: argparse.ArgumentParser): @@ -17,13 +20,20 @@ def register_profile_args(parser: argparse.ArgumentParser): CLI arguments that override `profiles.yml` settings. """ profile_group = parser.add_argument_group("Profile") - profile_group.add_argument( + profile_exc = profile_group.add_mutually_exclusive_group() + profile_exc.add_argument( "--profile", metavar="NAME", help="The name of the profile. Defaults to [code]$DSTACK_PROFILE[/]", default=os.getenv("DSTACK_PROFILE"), dest="profile", ) + profile_exc.add_argument( + "--no-profile", + help="Don't load any profile", + action="store_true", + dest="no_profile", + ) profile_group.add_argument( "--max-price", metavar="PRICE", @@ -131,6 +141,12 @@ def register_profile_args(parser: argparse.ArgumentParser): ) +def load_profile_from_args(args: argparse.Namespace, repo_dir: PathLike) -> Profile: + if args.no_profile: + return Profile(name="no-profile") + return load_profile(repo_dir=repo_dir, profile_name=args.profile) + + def apply_profile_args( args: argparse.Namespace, profile_settings: ProfileParams, From ad9a3cf37dfff2a74177155b8890bc35994d01df Mon Sep 17 00:00:00 2001 From: Dmitry Meyer Date: Fri, 17 Jul 2026 14:48:31 +0000 Subject: [PATCH 2/2] Add `DSTACK_NO_PROFILE` --- src/dstack/_internal/cli/services/profile.py | 30 ++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/dstack/_internal/cli/services/profile.py b/src/dstack/_internal/cli/services/profile.py index ced7f04e82..0ef420d77e 100644 --- a/src/dstack/_internal/cli/services/profile.py +++ b/src/dstack/_internal/cli/services/profile.py @@ -1,6 +1,7 @@ import argparse import os +from dstack._internal.core.errors import CLIError from dstack._internal.core.models.profiles import ( CreationPolicy, Profile, @@ -10,9 +11,13 @@ parse_duration, parse_max_duration, ) +from dstack._internal.utils.env import environ from dstack._internal.utils.path import PathLike from dstack.api.utils import load_profile +_PROFILE_ENV_VAR = "DSTACK_PROFILE" +_NO_PROFILE_ENV_VAR = "DSTACK_NO_PROFILE" + def register_profile_args(parser: argparse.ArgumentParser): """ @@ -24,13 +29,15 @@ def register_profile_args(parser: argparse.ArgumentParser): profile_exc.add_argument( "--profile", metavar="NAME", - help="The name of the profile. Defaults to [code]$DSTACK_PROFILE[/]", - default=os.getenv("DSTACK_PROFILE"), + help=f"The name of the profile. Defaults to [code]${_PROFILE_ENV_VAR}[/]", dest="profile", ) profile_exc.add_argument( "--no-profile", - help="Don't load any profile", + help=( + "Don't load any profile." + f" Enabled by default if [code]${_NO_PROFILE_ENV_VAR}[/] is set and [code]--profile[/] is not specified" + ), action="store_true", dest="no_profile", ) @@ -142,9 +149,18 @@ def register_profile_args(parser: argparse.ArgumentParser): def load_profile_from_args(args: argparse.Namespace, repo_dir: PathLike) -> Profile: + # precedence: --no-profile > --profile=name > DSTACK_NO_PROFILE=1 > DSTACK_PROFILE=name if args.no_profile: - return Profile(name="no-profile") - return load_profile(repo_dir=repo_dir, profile_name=args.profile) + return _build_dummy_no_profile() + if args.profile is not None: + return load_profile(repo_dir=repo_dir, profile_name=args.profile) + try: + no_profile_from_env = environ.get_bool(_NO_PROFILE_ENV_VAR, default=False) + except ValueError as e: + raise CLIError(str(e)) from e + if no_profile_from_env: + return _build_dummy_no_profile() + return load_profile(repo_dir=repo_dir, profile_name=os.getenv(_PROFILE_ENV_VAR)) def apply_profile_args( @@ -194,3 +210,7 @@ def max_duration(v: str) -> int: def retry_duration(v: str) -> int: return parse_duration(v) + + +def _build_dummy_no_profile() -> Profile: + return Profile(name="no-profile")