Skip to content

Commit 52786d5

Browse files
committed
PR feedback
1 parent 5b1b04d commit 52786d5

6 files changed

Lines changed: 49 additions & 15 deletions

File tree

sqlmesh/core/context.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,14 @@
120120
from sqlmesh.utils import UniqueKeyDict, Verbosity
121121
from sqlmesh.utils.concurrency import concurrent_apply_to_values
122122
from sqlmesh.utils.dag import DAG
123-
from sqlmesh.utils.date import TimeLike, now_ds, to_timestamp, format_tz_datetime, now_timestamp
123+
from sqlmesh.utils.date import (
124+
TimeLike,
125+
now_ds,
126+
to_timestamp,
127+
format_tz_datetime,
128+
now_timestamp,
129+
now,
130+
)
124131
from sqlmesh.utils.errors import (
125132
CircuitBreakerError,
126133
ConfigError,
@@ -1517,7 +1524,7 @@ def plan_builder(
15171524
max_interval_end_per_model,
15181525
backfill_models,
15191526
modified_model_names,
1520-
execution_time,
1527+
execution_time or now(),
15211528
)
15221529

15231530
# Refresh snapshot intervals to ensure that they are up to date with values reflected in the max_interval_end_per_model.

sqlmesh/core/plan/builder.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
yesterday_ds,
4343
to_timestamp,
4444
time_like_to_str,
45+
is_relative,
4546
)
4647
from sqlmesh.utils.errors import NoChangesPlanError, PlanError
4748

@@ -139,7 +140,7 @@ def __init__(
139140
self._include_unmodified = include_unmodified
140141
self._restate_models = set(restate_models) if restate_models is not None else None
141142
self._effective_from = effective_from
142-
self._execution_time = execution_time
143+
self._execution_time = execution_time or now()
143144
self._backfill_models = backfill_models
144145
self._end = end or default_end
145146
self._apply = apply
@@ -176,20 +177,18 @@ def is_start_and_end_allowed(self) -> bool:
176177

177178
@property
178179
def start(self) -> t.Optional[TimeLike]:
179-
if self._start and self._execution_time:
180+
if self._start and is_relative(self._start):
181+
# only do this for relative expressions otherwise inclusive date strings like '2020-01-01' can be turned into exclusive timestamps eg '2020-01-01 00:00:00'
180182
return to_datetime(self._start, relative_base=to_datetime(self._execution_time))
181183
return self._start
182184

183185
@property
184186
def end(self) -> t.Optional[TimeLike]:
185-
if self._end and self._execution_time:
187+
if self._end and is_relative(self._end):
188+
# only do this for relative expressions otherwise inclusive date strings like '2020-01-01' can be turned into exclusive timestamps eg '2020-01-01 00:00:00'
186189
return to_datetime(self._end, relative_base=to_datetime(self._execution_time))
187190
return self._end
188191

189-
@property
190-
def execution_time(self) -> TimeLike:
191-
return self._execution_time or now()
192-
193192
def set_start(self, new_start: TimeLike) -> PlanBuilder:
194193
self._start = new_start
195194
self.override_start = True
@@ -274,7 +273,8 @@ def build(self) -> Plan:
274273
)
275274

276275
restatements = self._build_restatements(
277-
dag, earliest_interval_start(self._context_diff.snapshots.values(), self.execution_time)
276+
dag,
277+
earliest_interval_start(self._context_diff.snapshots.values(), self._execution_time),
278278
)
279279
models_to_backfill = self._build_models_to_backfill(dag, restatements)
280280

@@ -307,7 +307,7 @@ def build(self) -> Plan:
307307
selected_models_to_backfill=self._backfill_models,
308308
models_to_backfill=models_to_backfill,
309309
effective_from=self._effective_from,
310-
execution_time=self.execution_time,
310+
execution_time=self._execution_time,
311311
end_bounded=self._end_bounded,
312312
ensure_finalized_snapshots=self._ensure_finalized_snapshots,
313313
user_provided_flags=self._user_provided_flags,
@@ -764,9 +764,9 @@ def _ensure_valid_date_range(self) -> None:
764764
)
765765

766766
if end := self.end:
767-
if to_datetime(end) > to_datetime(self.execution_time):
767+
if to_datetime(end) > to_datetime(self._execution_time):
768768
raise PlanError(
769-
f"Plan end date: '{time_like_to_str(end)}' cannot be in the future (execution time: '{time_like_to_str(self.execution_time)}')"
769+
f"Plan end date: '{time_like_to_str(end)}' cannot be in the future (execution time: '{time_like_to_str(self._execution_time)}')"
770770
)
771771

772772
def _ensure_no_forward_only_revert(self) -> None:

sqlmesh/core/plan/definition.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ def start(self) -> TimeLike:
8383
def end(self) -> TimeLike:
8484
return self.provided_end or self.execution_time
8585

86-
@property
86+
@cached_property
8787
def execution_time(self) -> TimeLike:
88+
# note: property is cached so that it returns a consistent timestamp for now()
8889
return self.execution_time_ or now()
8990

9091
@property

sqlmesh/utils/date.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,16 @@ def is_categorical_relative_expression(expression: str) -> bool:
373373
return not any(k in TIME_UNITS for k in grain_kwargs)
374374

375375

376+
def is_relative(value: TimeLike) -> bool:
377+
"""
378+
Tests a TimeLike object to see if it is a relative expression, eg '1 week ago' as opposed to an absolute timestamp
379+
"""
380+
if isinstance(value, str):
381+
return is_categorical_relative_expression(value)
382+
383+
return False
384+
385+
376386
def to_time_column(
377387
time_column: t.Union[TimeLike, exp.Null],
378388
time_column_type: exp.DataType,

tests/integrations/github/cicd/test_github_controller.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
GithubCheckStatus,
2020
MergeStateStatus,
2121
)
22+
from sqlmesh.utils.date import to_datetime
2223
from tests.integrations.github.cicd.conftest import MockIssueComment
2324

2425
pytestmark = pytest.mark.github
@@ -249,7 +250,7 @@ def test_pr_plan_auto_categorization(github_client, make_controller):
249250
assert not controller._context.apply.called
250251
assert controller._context._run_plan_tests.call_args == call(skip_tests=True)
251252
assert controller._pr_plan_builder._categorizer_config == custom_categorizer_config
252-
assert controller.pr_plan.start == default_start
253+
assert controller.pr_plan.start == to_datetime(default_start)
253254

254255

255256
def test_prod_plan(github_client, make_controller):

tests/utils/test_date.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
date_dict,
1313
format_tz_datetime,
1414
is_categorical_relative_expression,
15+
is_relative,
1516
make_inclusive,
1617
to_datetime,
1718
to_time_column,
@@ -324,3 +325,17 @@ def test_format_tz_datetime():
324325
test_datetime = to_datetime("2020-01-01 00:00:00")
325326
assert format_tz_datetime(test_datetime) == "2020-01-01 12:00AM UTC"
326327
assert format_tz_datetime(test_datetime, format_string=None) == "2020-01-01 00:00:00+00:00"
328+
329+
330+
def test_is_relative():
331+
assert is_relative("1 week ago")
332+
assert is_relative("1 week")
333+
assert is_relative("1 day ago")
334+
assert is_relative("yesterday")
335+
336+
assert not is_relative("2024-01-01")
337+
assert not is_relative("2024-01-01 01:02:03")
338+
assert not is_relative(to_datetime("2024-01-01 01:02:03"))
339+
assert not is_relative(to_timestamp("2024-01-01 01:02:03"))
340+
assert not is_relative(to_datetime("1 week ago"))
341+
assert not is_relative(to_timestamp("1 day ago"))

0 commit comments

Comments
 (0)