Skip to content
Draft
2 changes: 1 addition & 1 deletion truss-chains/truss_chains/deployment/deployment_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ def __init__(
dev_version = b10_core.get_dev_version(self._remote_provider.api, model_name)
if not dev_version:
raise b10_errors.RemoteError(
"No development model found. Run `truss push` then try again."
"No development model found. Run `truss push --watch` then try again."
)

def _patch(self) -> None:
Expand Down
39 changes: 33 additions & 6 deletions truss/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def push(
progress_bar: Optional[Type["progress.Progress"]] = None,
include_git_info: bool = False,
preserve_env_instance_type: bool = True,
watch: bool = False,
) -> definitions.ModelDeployment:
"""
Pushes a Truss to Baseten.
Expand All @@ -73,16 +74,17 @@ def push(
target_directory: Directory of Truss to push.
remote: Name of the remote in .trussrc to patch changes to.
model_name: The name of the model, if different from the one in the config.yaml.
publish: Push the truss as a published deployment. If no production deployment exists,
promote the truss to production after deploy completes.
promote: Push the truss as a published deployment. Even if a production deployment exists,
promote the truss to production after deploy completes.
publish: [DEPRECATED] Push the truss as a published deployment (default behavior).
Published deployments are now the default. Use 'watch=True' for development deployments.
This parameter will be removed in the following release.
promote: [DEPRECATED] Push the truss as a published deployment and promote to production.
Use 'environment="production"' instead. For other environments, use 'environment="{env_name}"'.
preserve_previous_production_deployment: Preserve the previous production deployment’s autoscaling
setting. When not specified, the previous production deployment will be updated to allow it to
scale to zero. Can only be use in combination with `promote` option.
scale to zero. Can only be used in combination with environment deployments.
trusted: [DEPRECATED]
deployment_name: Name of the deployment created by the push. Can only be
used in combination with `publish` or `promote`. Deployment name must
used in combination with environment deployments. Deployment name must
only contain alphanumeric, ’.’, ’-’ or ’_’ characters.
environment: Name of stable environment on baseten.
progress_bar: Optional `rich.progress.Progress` if output is desired.
Expand All @@ -92,10 +94,34 @@ def push(
preserve_env_instance_type: When pushing a truss to an environment, whether to use the resources
specified in the truss config to resolve the instance type or preserve the instance type
configured in the specified environment.
watch: Push the truss as a development deployment with hot reload support.
Development models allow you to iterate quickly during the deployment process.

Returns:
The newly created ModelDeployment.
"""
# Handle the new logic: --watch creates development deployment, default is published
if watch and publish:
raise ValueError(
"Cannot use both --watch and --publish flags. Use --watch for development deployments or --publish for published deployments."
)

if watch and promote:
raise ValueError(
"Cannot use both --watch and --promote flags. Use --watch for development deployments or --promote for production deployments."
)

# Determine the deployment type based on flags
if watch:
# --watch explicitly creates development deployment
publish = False
elif publish or promote:
# --publish or --promote explicitly creates published deployment
publish = True
else:
# Default behavior: create published deployment
publish = True

if trusted is not None:
warnings.warn(
"`trusted` is deprecated and will be ignored, all models are "
Expand Down Expand Up @@ -135,6 +161,7 @@ def push(
progress_bar=progress_bar,
include_git_info=include_git_info,
preserve_env_instance_type=preserve_env_instance_type,
watch=watch,
) # type: ignore

return definitions.ModelDeployment(cast(BasetenService, service))
63 changes: 56 additions & 7 deletions truss/cli/chains_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,13 @@ def _create_chains_table(service) -> Tuple[rich.table.Table, List[str]]:
)
@click.option(
"--publish/--no-publish",
default=False,
help="Create chainlets as published deployments.",
default=True,
help="[DEPRECATED] Create chainlets as published deployments (default behavior). Use --watch for development deployments instead. This flag will be removed in the following release.",
)
@click.option(
"--promote/--no-promote",
default=False,
help="Replace production chainlets with newly deployed chainlets.",
help="[DEPRECATED] Replace production chainlets with newly deployed chainlets. Use '--environment production' instead.",
)
@click.option(
"--environment",
Expand All @@ -178,7 +178,7 @@ def _create_chains_table(service) -> Tuple[rich.table.Table, List[str]]:
"Watches the chains source code and applies live patches. Using this option "
"will wait for the chain to be deployed (i.e. `--wait` flag is applied), "
"before starting to watch for changes. This option required the deployment "
"to be a development deployment (i.e. `--no-promote` and `--no-publish`."
"to be a development deployment (i.e. use `--watch` flag when pushing)."
),
)
@click.option(
Expand Down Expand Up @@ -244,11 +244,44 @@ def push_chain(
if experimental_watch_chainlet_names:
watch = True

# Handle the new logic: --watch creates development deployment, default is published
if watch and publish:
raise ValueError(
"Cannot use both --watch and --publish flags. Use --watch for development deployments (default is published)."
)

if watch and promote:
raise ValueError(
"Cannot use both --watch and --promote flags. Use --watch for development deployments or --environment {env_name} for production deployments."
)

# Determine the deployment type based on flags
if watch:
if publish or promote:
raise ValueError(
"When using `--watch`, the deployment cannot be published or promoted."
# --watch explicitly creates development deployment
publish = False
elif publish or promote:
# --publish or --promote explicitly creates published deployment
publish = True
# Show deprecation warning for --publish flag
if publish and not promote:
import warnings

warnings.warn(
"The '--publish' flag is deprecated. Published deployments are now the default behavior. "
"Use '--watch' for development deployments instead. This flag will be removed in the following release.",
DeprecationWarning,
stacklevel=2,
)
console.print(
"⚠️ The '--publish' flag is deprecated. Published deployments are now the default behavior. "
"Use '--watch' for development deployments instead. This flag will be removed in the following release.",
style="yellow",
)
else:
# Default behavior: create published deployment
publish = True

if watch:
if not wait:
console.print(
"'--watch' is used. Will wait for deployment before watching files."
Expand All @@ -262,6 +295,22 @@ def push_chain(
)
console.print(promote_warning, style="yellow")

if promote and not environment:
# Show deprecation warning for --promote flag
import warnings

warnings.warn(
"The '--promote' flag is deprecated. Use '--environment production' instead. "
"For other environments, use '--environment {env_name}'.",
DeprecationWarning,
stacklevel=2,
)
console.print(
"⚠️ The '--promote' flag is deprecated. Use '--environment production' instead. "
"For other environments, use '--environment {env_name}'.",
style="yellow",
)

if not remote:
if dryrun:
remote = ""
Expand Down
128 changes: 117 additions & 11 deletions truss/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,9 +406,8 @@ def run_python(script, target_directory):
required=False,
default=False,
help=(
"Push the truss as a published deployment. If no production "
"deployment exists, promote the truss to production "
"after deploy completes."
"[DEPRECATED] Push the truss as a published deployment (default behavior). "
"Use --watch for development deployments instead. This flag will be removed in the following release."
),
)
@click.option(
Expand All @@ -417,9 +416,8 @@ def run_python(script, target_directory):
required=False,
default=False,
help=(
"Push the truss as a published deployment. Even if a production "
"deployment exists, promote the truss to production "
"after deploy completes."
"[DEPRECATED] Push the truss as a published deployment and promote to production. "
"Use '--environment production' instead. For other environments, use '--environment {env_name}'."
),
)
@click.option(
Expand All @@ -439,7 +437,7 @@ def run_python(script, target_directory):
help=(
"Preserve the previous production deployment's autoscaling setting. When "
"not specified, the previous production deployment will be updated to allow "
"it to scale to zero. Can only be use in combination with --promote option."
"it to scale to zero. Can only be used in combination with environment deployments."
),
)
@click.option(
Expand All @@ -462,7 +460,7 @@ def run_python(script, target_directory):
required=False,
help=(
"Name of the deployment created by the push. Can only be "
"used in combination with '--publish' or '--promote'."
"used in combination with environment deployments."
),
)
@click.option(
Expand Down Expand Up @@ -501,6 +499,17 @@ def run_python(script, target_directory):
"Default is --preserve-env-instance-type."
),
)
@click.option(
"--watch",
is_flag=True,
required=False,
default=False,
help=(
"Push the truss as a development deployment with hot reload support. "
"Development models allow you to iterate quickly during the deployment process. "
"Automatically streams deployment logs and enables live patching."
),
)
@common.common_options()
def push(
target_directory: str,
Expand All @@ -518,21 +527,59 @@ def push(
include_git_info: bool = False,
tail: bool = False,
preserve_env_instance_type: bool = True,
watch: bool = False,
) -> None:
"""
Pushes a truss to a TrussRemote.

TARGET_DIRECTORY: A Truss directory. If none, use current directory.

"""
# Handle the new logic: --watch creates development deployment, default is published
if watch and publish:
raise click.UsageError(
"Cannot use both --watch and --publish flags. Use --watch for development deployments (default is published)."
)

if watch and promote:
raise click.UsageError(
"Cannot use both --watch and --promote flags. Use --watch for development deployments or --environment {env_name} for production deployments."
)

# Determine the deployment type based on flags
if watch:
# --watch explicitly creates development deployment
publish = False
elif publish or promote:
# --publish or --promote explicitly creates published deployment
publish = True
# Show deprecation warning for --publish flag
if publish and not promote:
import warnings

warnings.warn(
"The '--publish' flag is deprecated. Published deployments are now the default behavior. "
"Use '--watch' for development deployments instead. This flag will be removed in the following release.",
DeprecationWarning,
stacklevel=2,
)
console.print(
"⚠️ The '--publish' flag is deprecated. Published deployments are now the default behavior. "
"Use '--watch' for development deployments instead. This flag will be removed in the following release.",
style="yellow",
)
else:
# Default behavior: create published deployment
publish = True

tr = _get_truss_from_directory(target_directory=target_directory)
if (
tr.spec.config.runtime.transport.kind == TransportKind.GRPC
and not publish
and not promote
):
raise click.UsageError(
"Truss with gRPC transport cannot be used as a development deployment. Please rerun the command with --publish or --promote."
"Truss with gRPC transport cannot be used as a development deployment. Please rerun the command without --watch, or with --environment {env_name}."
)

if not remote:
Expand All @@ -551,6 +598,20 @@ def push(
promote_warning = "'promote' flag and 'environment' flag were both specified. Ignoring the value of 'promote'"
console.print(promote_warning, style="yellow")
if promote and not environment:
# Show deprecation warning for --promote flag
import warnings

warnings.warn(
"The '--promote' flag is deprecated. Use '--environment production' instead. "
"For other environments, use '--environment {env_name}'.",
DeprecationWarning,
stacklevel=2,
)
console.print(
"⚠️ The '--promote' flag is deprecated. Use '--environment production' instead. "
"For other environments, use '--environment {env_name}'.",
style="yellow",
)
environment = PRODUCTION_ENVIRONMENT_NAME

if preserve_env_instance_type is not None and not environment:
Expand Down Expand Up @@ -625,6 +686,7 @@ def push(
progress_bar=progress.Progress,
include_git_info=include_git_info,
preserve_env_instance_type=preserve_env_instance_type,
watch=watch,
) # type: ignore

click.echo(f"✨ Model {model_name} was successfully pushed ✨")
Expand All @@ -635,9 +697,9 @@ def push(
| Your model is deploying as a development model. Development models allow you to |
| iterate quickly during the deployment process. |
| |
| To monitor changes to your model and rapidly iterate, run the 'truss watch' command. |
| When you are ready to publish your deployed model as a new deployment, |
| pass '--publish' to the 'truss push' command. To monitor changes to your model and |
| rapidly iterate, run the 'truss watch' command. |
| run 'truss push' without the --watch flag. |
| |
|---------------------------------------------------------------------------------------|
"""
Expand Down Expand Up @@ -682,6 +744,50 @@ def push(
)
sys.exit(1)

elif watch and isinstance(service, BasetenService):
# For --watch, we want to stream logs and then enable live patching
bt_remote = cast(BasetenRemote, remote_provider)

# First, stream the logs like --tail does
console.print("πŸ“‘ Streaming deployment logs...", style="blue")
log_watcher = ModelDeploymentLogWatcher(
bt_remote.api, service.model_id, service.model_version_id
)
for log in log_watcher.watch():
cli_log_utils.output_log(log)

# After logs are streamed, start the watch/patch functionality
console.print("πŸ”„ Starting live patching mode...", style="green")
console.print(
"πŸ“ Edit your truss files and changes will be applied automatically.",
style="italic",
)
console.print("πŸ›‘ Press Ctrl+C to stop watching.", style="italic")

# Run the watch functionality using the existing watch command logic
try:
# Use the same logic as the watch command
if not os.path.isfile(target_directory):
bt_remote.sync_truss_to_dev_version_by_name(
model_name, target_directory, console, error_console
)
else:
# These imports are delayed, to handle pydantic v1 envs gracefully.
from truss_chains.deployment import deployment_client

deployment_client.watch_model(
source=Path(target_directory),
model_name=model_name,
remote_provider=bt_remote,
console=console,
error_console=error_console,
)
except KeyboardInterrupt:
console.print(
"\nπŸ‘‹ Stopped watching. Your development deployment is still running.",
style="yellow",
)

elif tail and isinstance(service, BasetenService):
bt_remote = cast(BasetenRemote, remote_provider)
log_watcher = ModelDeploymentLogWatcher(
Expand Down
Loading
Loading