Skip to content
Open
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
27 changes: 27 additions & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ Options:
-e, --end TEXT The end datetime of the interval for which this
command will be applied.
--execution-time TEXT The execution time (defaults to now).
--time-zone TEXT IANA timezone for day-sized relative dates such as
'1 week ago', 'today', and 'yesterday' (for example,
America/Los_Angeles). Those values are converted to
UTC timestamps. Defaults to UTC.
--help Show this message and exit.
```

Expand All @@ -83,6 +87,10 @@ Options:
command will be applied.
-e, --end TEXT The end datetime of the interval for which this command
will be applied.
--time-zone TEXT IANA timezone for day-sized relative dates such as '1
week ago', 'today', and 'yesterday' (for example,
America/Los_Angeles). Those values are converted to
UTC timestamps. Defaults to UTC.
--help Show this message and exit.
```

Expand Down Expand Up @@ -205,6 +213,10 @@ Options:
-e, --end TEXT The end datetime of the interval for which this
command will be applied.
--execution-time TEXT The execution time (defaults to now).
--time-zone TEXT IANA timezone for day-sized relative dates such as
'1 week ago', 'today', and 'yesterday' (for example,
America/Los_Angeles). Those values are converted to
UTC timestamps. Defaults to UTC.
--limit INTEGER The number of rows which the query should be limited
to.
--help Show this message and exit.
Expand Down Expand Up @@ -352,6 +364,12 @@ Options:
-e, --end TEXT The end datetime of the interval for which
this command will be applied.
--execution-time TEXT The execution time (defaults to now).
--time-zone TEXT IANA timezone for day-sized relative dates
such as '1 week ago', 'today', and
'yesterday' (for example,
America/Los_Angeles). Those values are
converted to UTC timestamps. Defaults to
UTC.
--create-from TEXT The environment to create the target
environment from if it doesn't exist.
Default: prod.
Expand Down Expand Up @@ -442,6 +460,10 @@ Options:
-e, --end TEXT The end datetime of the interval for which this
command will be applied.
--execution-time TEXT The execution time (defaults to now).
--time-zone TEXT IANA timezone for day-sized relative dates such
as '1 week ago', 'today', and 'yesterday' (for
example, America/Los_Angeles). Those values are
converted to UTC timestamps. Defaults to UTC.
--expand TEXT Whether or not to expand materialized models
(defaults to False). If True, all referenced
models are expanded as raw queries. Multiple
Expand Down Expand Up @@ -508,6 +530,11 @@ Options:
this command will be applied.
-e, --end TEXT The end datetime of the interval for which
this command will be applied.
--time-zone TEXT IANA timezone for day-sized relative dates
such as '1 week ago', 'today', and
'yesterday' (for example,
America/Los_Angeles). Those values are
converted to UTC timestamps. Defaults to UTC.
--skip-janitor Skip the janitor task.
--ignore-cron Run for all missing intervals, ignoring
individual cron schedules.
Expand Down
83 changes: 82 additions & 1 deletion sqlmesh/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import sys
import typing as t
from datetime import datetime, tzinfo
from pathlib import Path

import click
Expand All @@ -22,7 +23,12 @@
from sqlmesh.core.console import configure_console, get_console
from sqlmesh.core.context import Context
from sqlmesh.utils import Verbosity
from sqlmesh.utils.date import TimeLike
from sqlmesh.utils.date import (
TimeLike,
is_categorical_relative_expression,
parse_time_zone,
to_datetime,
)
from sqlmesh.utils.errors import MissingDependencyError, SQLMeshError

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -57,6 +63,57 @@ def parse_args(self, ctx: click.Context, args: t.List[str]) -> t.List[str]:
return rest


def _resolve_day_sized(
value: t.Optional[TimeLike],
relative_tz: tzinfo,
relative_base: t.Optional[datetime] = None,
) -> t.Optional[TimeLike]:
if isinstance(value, str) and is_categorical_relative_expression(value):
return to_datetime(value, relative_base=relative_base, relative_tz=relative_tz)
return value


def _resolve_time_zone_args(
start: t.Optional[TimeLike],
end: t.Optional[TimeLike],
execution_time: t.Optional[TimeLike],
time_zone: t.Optional[str],
) -> t.Tuple[t.Optional[TimeLike], t.Optional[TimeLike], t.Optional[TimeLike]]:
"""Turn day-sized relative bounds into UTC timestamps.

Phrases such as "1 week ago", "today", and "yesterday" are anchored to midnight in
`time_zone`. Sub-day phrases and absolute timestamps are left unchanged so downstream
plan, render, and macro handling keep using one UTC timestamp.
"""
if not time_zone:
return start, end, execution_time

relative_tz = parse_time_zone(time_zone)
if relative_tz is None:
return start, end, execution_time

execution_time = _resolve_day_sized(execution_time, relative_tz)
relative_base = to_datetime(execution_time) if execution_time is not None else None
start = _resolve_day_sized(start, relative_tz, relative_base)
end = _resolve_day_sized(end, relative_tz, relative_base)
return start, end, execution_time


def _apply_time_zone_kwargs(kwargs: t.Dict[str, t.Any]) -> None:
start, end, execution_time = _resolve_time_zone_args(
kwargs.get("start"),
kwargs.get("end"),
kwargs.get("execution_time"),
kwargs.pop("time_zone", None),
)
if "start" in kwargs:
kwargs["start"] = start
if "end" in kwargs:
kwargs["end"] = end
if "execution_time" in kwargs:
kwargs["execution_time"] = execution_time


def _sqlmesh_version() -> str:
try:
from sqlmesh import __version__
Expand Down Expand Up @@ -283,6 +340,7 @@ def init(
@opt.start_time
@opt.end_time
@opt.execution_time
@opt.time_zone
@opt.expand
@click.option(
"--dialect",
Expand All @@ -300,12 +358,16 @@ def render(
start: TimeLike,
end: TimeLike,
execution_time: t.Optional[TimeLike] = None,
time_zone: t.Optional[str] = None,
expand: t.Optional[t.Union[bool, t.Iterable[str]]] = None,
dialect: t.Optional[str] = None,
no_format: bool = False,
**format_kwargs: t.Any,
) -> None:
"""Render a model's query, optionally expanding referenced models."""
start, end, execution_time = _resolve_time_zone_args( # type: ignore[assignment]
start, end, execution_time, time_zone
)
model = ctx.obj.get_model(model, raise_if_missing=True)

rendered = ctx.obj.render(
Expand Down Expand Up @@ -338,6 +400,7 @@ def render(
@opt.start_time
@opt.end_time
@opt.execution_time
@opt.time_zone
@click.option(
"--limit",
type=int,
Expand All @@ -352,9 +415,13 @@ def evaluate(
start: TimeLike,
end: TimeLike,
execution_time: t.Optional[TimeLike] = None,
time_zone: t.Optional[str] = None,
limit: t.Optional[int] = None,
) -> None:
"""Evaluate a model and return a dataframe with a default limit of 1000."""
start, end, execution_time = _resolve_time_zone_args( # type: ignore[assignment]
start, end, execution_time, time_zone
)
df = ctx.obj.evaluate(
model,
start=start,
Expand Down Expand Up @@ -422,6 +489,7 @@ def diff(ctx: click.Context, environment: t.Optional[str] = None) -> None:
@opt.start_time
@opt.end_time
@opt.execution_time
@opt.time_zone
@click.option(
"--create-from",
type=str,
Expand Down Expand Up @@ -585,6 +653,7 @@ def plan(
) -> None:
"""Apply local changes to the target environment."""
context = ctx.obj
_apply_time_zone_kwargs(kwargs)
restate_models = kwargs.pop("restate_model") or None
select_models = kwargs.pop("select_model") or None
allow_destructive_models = kwargs.pop("allow_destructive_model") or None
Expand All @@ -609,6 +678,7 @@ def plan(
@click.argument("environment", required=False)
@opt.start_time
@opt.end_time
@opt.time_zone
@click.option("--skip-janitor", is_flag=True, help="Skip the janitor task.")
@click.option(
"--ignore-cron",
Expand Down Expand Up @@ -637,6 +707,7 @@ def plan(
def run(ctx: click.Context, environment: t.Optional[str] = None, **kwargs: t.Any) -> None:
"""Evaluate missing intervals for the target environment."""
context = ctx.obj
_apply_time_zone_kwargs(kwargs)
select_models = kwargs.pop("select_model") or None
completion_status = context.run(environment, select_models=select_models, **kwargs)
if completion_status.is_failure:
Expand Down Expand Up @@ -866,6 +937,7 @@ def test(
@opt.start_time
@opt.end_time
@opt.execution_time
@opt.time_zone
@click.pass_obj
@error_handler
@cli_analytics
Expand All @@ -875,8 +947,12 @@ def audit(
start: TimeLike,
end: TimeLike,
execution_time: t.Optional[TimeLike] = None,
time_zone: t.Optional[str] = None,
) -> None:
"""Run audits for the target model(s)."""
start, end, execution_time = _resolve_time_zone_args( # type: ignore[assignment]
start, end, execution_time, time_zone
)
if not obj.audit(models=models, start=start, end=end, execution_time=execution_time):
exit(1)

Expand All @@ -897,6 +973,7 @@ def audit(
)
@opt.start_time
@opt.end_time
@opt.time_zone
@click.pass_context
@error_handler
@cli_analytics
Expand All @@ -907,8 +984,12 @@ def check_intervals(
select_model: t.List[str],
start: TimeLike,
end: TimeLike,
time_zone: t.Optional[str] = None,
) -> None:
"""Show missing intervals in an environment, respecting signals."""
start, end, _ = _resolve_time_zone_args( # type: ignore[assignment]
start, end, None, time_zone
)
context = ctx.obj
context.console.show_intervals(
context.check_intervals(
Expand Down
9 changes: 9 additions & 0 deletions sqlmesh/cli/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@
help="The execution time (defaults to now).",
)

time_zone = click.option(
"--time-zone",
help=(
"IANA timezone for day-sized relative dates such as '1 week ago', 'today', and "
"'yesterday' (for example, America/Los_Angeles). Those values are converted to UTC "
"timestamps. Defaults to UTC."
),
)

expand = click.option(
"--expand",
multiple=True,
Expand Down
Loading