diff --git a/Cargo.lock b/Cargo.lock index f815bb73..2f2b6268 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -480,7 +480,7 @@ dependencies = [ [[package]] name = "config" -version = "0.3.40" +version = "0.3.41" dependencies = [ "base64", "chrono", @@ -587,7 +587,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto" -version = "0.3.40" +version = "0.3.41" dependencies = [ "aes-gcm", "base64", @@ -3215,7 +3215,7 @@ dependencies = [ [[package]] name = "testutils" -version = "0.3.40" +version = "0.3.41" dependencies = [ "pem", "rsa", @@ -3485,7 +3485,7 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tower" -version = "0.3.40" +version = "0.3.41" dependencies = [ "tokio", "tower-api", @@ -3510,7 +3510,7 @@ dependencies = [ [[package]] name = "tower-api" -version = "0.3.40" +version = "0.3.41" dependencies = [ "reqwest", "serde", @@ -3522,7 +3522,7 @@ dependencies = [ [[package]] name = "tower-cmd" -version = "0.3.40" +version = "0.3.41" dependencies = [ "axum", "bytes", @@ -3592,7 +3592,7 @@ checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-package" -version = "0.3.40" +version = "0.3.41" dependencies = [ "async-compression", "config", @@ -3611,7 +3611,7 @@ dependencies = [ [[package]] name = "tower-runtime" -version = "0.3.40" +version = "0.3.41" dependencies = [ "chrono", "config", @@ -3632,7 +3632,7 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tower-telemetry" -version = "0.3.40" +version = "0.3.41" dependencies = [ "tracing", "tracing-appender", @@ -3641,7 +3641,7 @@ dependencies = [ [[package]] name = "tower-uv" -version = "0.3.40" +version = "0.3.41" dependencies = [ "async-compression", "async_zip", @@ -3655,7 +3655,7 @@ dependencies = [ [[package]] name = "tower-version" -version = "0.3.40" +version = "0.3.41" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 00342e04..2a69b74e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" [workspace.package] edition = "2021" -version = "0.3.40" +version = "0.3.41" description = "Tower is the best way to host Python data apps in production" rust-version = "1.81" authors = ["Brad Heller "] diff --git a/crates/tower-cmd/src/apps.rs b/crates/tower-cmd/src/apps.rs index 668d5f79..db86e857 100644 --- a/crates/tower-cmd/src/apps.rs +++ b/crates/tower-cmd/src/apps.rs @@ -9,15 +9,19 @@ pub fn apps_cmd() -> Command { Command::new("apps") .about("Manage the apps in your current Tower account") .arg_required_else_help(true) - .subcommand(Command::new("list").about("List all of your apps`")) + .subcommand(Command::new("list").about("List all of your apps")) .subcommand( Command::new("show") .allow_external_subcommands(true) + .override_usage("tower apps show [OPTIONS] ") + .after_help("Example: tower apps show hello-world") .about("Show the details about an app in Tower"), ) .subcommand( Command::new("logs") .allow_external_subcommands(true) + .override_usage("tower apps logs [OPTIONS] #") + .after_help("Example: tower apps logs hello-world#11") .about("Get the logs from a previous Tower app run"), ) .subcommand( @@ -42,6 +46,8 @@ pub fn apps_cmd() -> Command { .subcommand( Command::new("delete") .allow_external_subcommands(true) + .override_usage("tower apps delete [OPTIONS] ") + .after_help("Example: tower apps delete hello-world") .about("Delete an app in Tower"), ) } diff --git a/crates/tower-cmd/src/schedules.rs b/crates/tower-cmd/src/schedules.rs index 68c8cb8e..aec97162 100644 --- a/crates/tower-cmd/src/schedules.rs +++ b/crates/tower-cmd/src/schedules.rs @@ -72,6 +72,8 @@ pub fn schedules_cmd() -> Command { .subcommand( Command::new("delete") .allow_external_subcommands(true) + .override_usage("tower schedules delete [OPTIONS] ") + .after_help("Example: tower schedules delete 123") .about("Delete a schedule"), ) .subcommand( @@ -92,6 +94,8 @@ pub fn schedules_cmd() -> Command { .action(clap::ArgAction::Append), ) .allow_external_subcommands(true) + .override_usage("tower schedules update [OPTIONS] ") + .after_help("Example: tower schedules update 123 --cron \"*/15 * * * *\"") .about("Update an existing schedule"), ) } diff --git a/crates/tower-cmd/src/secrets.rs b/crates/tower-cmd/src/secrets.rs index 5e878c13..ecd0b94a 100644 --- a/crates/tower-cmd/src/secrets.rs +++ b/crates/tower-cmd/src/secrets.rs @@ -19,6 +19,7 @@ pub fn secrets_cmd() -> Command { Arg::new("show") .short('s') .long("show") + .help("Show secrets in plain text") .action(clap::ArgAction::SetTrue), ) .arg( @@ -27,12 +28,14 @@ pub fn secrets_cmd() -> Command { .long("environment") .default_value("default") .value_parser(value_parser!(String)) + .help("List secrets in this environment") .action(clap::ArgAction::Set), ) .arg( Arg::new("all") .short('a') .long("all") + .help("List secrets across all environments") .action(clap::ArgAction::SetTrue), ) .about("List all of your secrets"), @@ -45,6 +48,7 @@ pub fn secrets_cmd() -> Command { .long("name") .value_parser(value_parser!(String)) .required(true) + .help("Secret name to create") .action(clap::ArgAction::Set), ) .arg( @@ -53,6 +57,7 @@ pub fn secrets_cmd() -> Command { .long("environment") .default_value("default") .value_parser(value_parser!(String)) + .help("Environment to store the secret in") .action(clap::ArgAction::Set), ) .arg( @@ -61,6 +66,7 @@ pub fn secrets_cmd() -> Command { .long("value") .value_parser(value_parser!(String)) .required(true) + .help("Secret value to store") .action(clap::ArgAction::Set), ) .about("Create a new secret in your Tower account"), @@ -68,6 +74,8 @@ pub fn secrets_cmd() -> Command { .subcommand( Command::new("delete") .allow_external_subcommands(true) + .override_usage("tower secrets delete [OPTIONS] ") + .after_help("Example: tower secrets delete MY_API_KEY") .about("Delete a secret in Tower"), ) } diff --git a/pyproject.toml b/pyproject.toml index 645e9d25..100c4643 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "tower" -version = "0.3.40" +version = "0.3.41" description = "Tower CLI and runtime environment for Tower." authors = [{ name = "Tower Computing Inc.", email = "brad@tower.dev" }] readme = "README.md" diff --git a/src/tower/__init__.py b/src/tower/__init__.py index 44d6ebad..9fa4c0fd 100644 --- a/src/tower/__init__.py +++ b/src/tower/__init__.py @@ -19,6 +19,13 @@ wait_for_runs, ) +from ._utils import ( + param, + parameter, + secret, +) + + from ._features import override_get_attr, get_available_features, is_feature_enabled if TYPE_CHECKING: @@ -26,6 +33,11 @@ from ._llms import llms as llms from ._dbt import dbt as dbt +# +# Sub-packages to expose +# +from . import info + def __getattr__(name: str): """ diff --git a/src/tower/_utils.py b/src/tower/_utils.py new file mode 100644 index 00000000..6a93aaf2 --- /dev/null +++ b/src/tower/_utils.py @@ -0,0 +1,45 @@ +def secret(name: str, default: str = ""): + """ + Retrieve a secret value from environment variables. + + Args: + name (str): The name of the secret (environment variable). + default (str, optional): The default value to return if the secret is not found. Defaults to "". + + Returns: + str: The value of the secret or the default value. + """ + import os + + return os.getenv(name, default) + + +def param(name: str, default: str = ""): + """ + Retrieve a parameter value from this invocation. In this implementation, + it fetches the value from environment variables. + + Args: + name (str): The name of the parameter (environment variable). + default (str, optional): The default value to return if the parameter is not found. Defaults to "". + + Returns: + str: The value of the parameter or the default value. + """ + import os + + return os.getenv(name, default) + + +def parameter(name: str, default: str = ""): + """ + Alias for param function to retrieve a parameter value from environment variables. + + Args: + name (str): The name of the parameter (environment variable). + default (str, optional): The default value to return if the parameter is not found. Defaults to "". + + Returns: + str: The value of the parameter or the default value. + """ + return param(name, default) diff --git a/src/tower/info/__init__.py b/src/tower/info/__init__.py new file mode 100644 index 00000000..8aede614 --- /dev/null +++ b/src/tower/info/__init__.py @@ -0,0 +1,187 @@ +from typing import Optional + + +def schedule_name() -> Optional[str]: + """ + Retrieve the name of the schedule that invoked this run from the runtime + environment. + + Returns: + Optional[str]: The name of the schedule if set, otherwise None. + """ + return _get_runtime_env_variable("SCHEDULE_NAME", None) + + +def is_scheduled_run() -> bool: + """ + Check if the current run is a scheduled run based on environment variables. + Returns: + bool: True if it is a scheduled run, otherwise False. + """ + return schedule_name() is not None + + +def schedule_id() -> Optional[str]: + """ + Retrieve the ID of the schedule that invoked this run from the runtime + environment. + + Returns: + Optional[str]: The ID of the schedule if set, otherwise None. + """ + return _get_runtime_env_variable("SCHEDULE_ID", None) + + +def run_id() -> Optional[str]: + """ + Retrieve the ID of the current run from the runtime environment. + + Returns: + Optional[str]: The ID of the run if set, otherwise None. + """ + return _get_runtime_env_variable("RUN_ID", None) + + +def run_number() -> Optional[int]: + """ + Retrieve the number of the current run from the runtime environment. + + Returns: + Optional[int]: The run number if set, otherwise None. + """ + run_number_str = _get_runtime_env_variable("RUN_NUMBER", None) + return int(run_number_str) if run_number_str is not None else None + + +def hostname() -> Optional[str]: + """ + Retrieve the hostname of the current run assigned by the runtime + environment. + + Returns: + Optional[str]: The hostname if set, otherwise None. + """ + return _get_env_variable("TOWER__HOST", None) + + +def port() -> Optional[int]: + """ + Retrieve the port number assigned to this run by the current runtime + environment. + + Returns: + Optional[int]: The port number if set, otherwise None. + """ + port_str = _get_env_variable("TOWER__PORT", None) + return int(port_str) if port_str is not None else None + + +def is_cloud_run() -> bool: + """ + Check if the current run is executing in the Tower cloud environment. + + Returns: + bool: True if running in Tower cloud, otherwise False. + """ + val = _get_runtime_env_variable("IS_TOWER_MANAGED", "") + return _strtobool(val) + + +def runner_name() -> str: + """Retrieve the name of the runner executing this run from the runtime. If the + name is unknown, an empty string is returned. + + Returns: + str: The name of the runner or an empty string if unknown. + """ + return _get_runtime_env_variable("RUNNER_NAME", "") + + +def runner_id() -> str: + """ + Retrieve the ID of the runner executing this run from the runtime. If the + ID is unknown, an empty string is returned. + + Returns: + str: The ID of the runner or an empty string if unknown. + """ + return _get_runtime_env_variable("RUNNER_ID", "") + + +def app_name() -> str: + """ + Retrieve the name of the app being executed in this run from the runtime. + + Returns: + str: The name of the app or an empty string if unknown. + """ + return _get_runtime_env_variable("APP_NAME") + + +def team_name() -> str: + """ + Retrieve the name of the team associated with this run from the runtime. + + Returns: + str: The name of the team or an empty string if unknown. + """ + return _get_runtime_env_variable("TEAM_NAME", "") + + +def environment() -> str: + """ + Retrieve the name of the environment this run is running in. + + Returns: + str: The name of the environment or an empty string if unknown. + """ + return _get_runtime_env_variable("ENVIRONMENT_NAME", "") + + +def is_manual_run() -> bool: + """ + Check if the current run was manually triggered. + + Returns: + bool: True if the run was manually triggered, otherwise False. + """ + val = _get_runtime_env_variable("IS_MANUAL_RUN", "false") + return _strtobool(val) + + +def _get_runtime_env_variable(name: str, default: Optional[str] = "") -> Optional[str]: + """ + Helper function to retrieve a runtime environment variable. + + Args: + name (str): The name of the runtime environment variable. + + Returns: + Optional[str]: The value of the runtime environment variable if set, otherwise None. + """ + return _get_env_variable(f"TOWER__RUNTIME__{name}", default) + + +def _get_env_variable(var_name: str, default: Optional[str] = "") -> Optional[str]: + """ + Helper function to retrieve an environment variable. + + Args: + var_name (str): The name of the environment variable. + + Returns: + Optional[str]: The value of the environment variable if set, otherwise None. + """ + import os + + return os.getenv(var_name, default) + + +def _strtobool(val: str) -> bool: + val = val.lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return True + elif val in ("n", "no", "f", "false", "off", "0"): + return False + else: + raise ValueError(f"invalid truth value {val!r}") diff --git a/uv.lock b/uv.lock index 0c9bb048..d984e3c4 100644 --- a/uv.lock +++ b/uv.lock @@ -2744,7 +2744,7 @@ wheels = [ [[package]] name = "tower" -version = "0.3.40" +version = "0.3.41" source = { editable = "." } dependencies = [ { name = "attrs" },