Skip to content

Commit

Permalink
Validate --event-time-start is before --event-time-end (#10820)
Browse files Browse the repository at this point in the history
* Validate that `event_time_start` is before `event_time_end` when passed from CLI

Sometimes CLI options have restrictions based on other CLI options. This is the case
for `--event-time-start` and `--event-time-end`. Unfortunately, click doesn't provide
a good way for validating this, at least not that I found. Additionaly I'm not sure
if we have had anything like this previously. In any case, I couldn't find a
centralized validation area for such occurances. Thus I've gone and added one,
`validate_option_interactions`. Long term if more validations are added, we should
add this wrapper to each CLI command. For now I've only added it to the commands that
support `event_time_start` and `event_time_end`, specifically `build` and `run`.

* Add changie doc

* If `--event-time-end` is not specififed, ensure `--event-time-start` is less than the current time

* Fixup error message about event_time_start and event_time_end

* Move logic to validate `event_time` cli flags to `flags.py`

* Update validation of `--event-time-start` against `datetime.now` to use UTC
  • Loading branch information
QMalcolm authored Oct 7, 2024
1 parent fc83f5e commit fc8eb82
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/Features-20241003-170529.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Features
body: Ensure `--event-time-start` is before `--event-time-end`
time: 2024-10-03T17:05:29.984398-05:00
custom:
Author: QMalcolm
Issue: "10786"
26 changes: 26 additions & 0 deletions core/dbt/cli/flags.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import os
import sys
from dataclasses import dataclass
from datetime import datetime
from importlib import import_module
from pathlib import Path
from pprint import pformat as pf
from typing import Any, Callable, Dict, List, Optional, Set, Union

import pytz
from click import Context, Parameter, get_current_context
from click.core import Command as ClickCommand
from click.core import Group, ParameterSource
Expand Down Expand Up @@ -313,6 +315,9 @@ def _assign_params(
self._assert_mutually_exclusive(params_assigned_from_default, ["SELECT", "INLINE"])
self._assert_mutually_exclusive(params_assigned_from_default, ["SELECTOR", "INLINE"])

# Check event_time configs for validity
self._validate_event_time_configs()

# Support lower cased access for legacy code.
params = set(
x for x in dir(self) if not callable(getattr(self, x)) and not x.startswith("__")
Expand Down Expand Up @@ -349,6 +354,27 @@ def _assert_mutually_exclusive(
elif flag_set_by_user:
set_flag = flag

def _validate_event_time_configs(self) -> None:
event_time_start: datetime = (
getattr(self, "EVENT_TIME_START") if hasattr(self, "EVENT_TIME_START") else None
)
event_time_end: datetime = (
getattr(self, "EVENT_TIME_END") if hasattr(self, "EVENT_TIME_END") else None
)

if event_time_start is not None and event_time_end is not None:
if event_time_start >= event_time_end:
raise DbtUsageException(
"Value for `--event-time-start` must be less than `--event-time-end`"
)
elif event_time_start is not None:
utc_start = event_time_start.replace(tzinfo=pytz.UTC)
current_time = datetime.now(pytz.UTC)
if utc_start >= current_time:
raise DbtUsageException(
f"Value for `--event-time-start` ({utc_start}) must be less than the current time ({current_time}) if `--event-time-end` is not specififed"
)

def fire_deprecations(self):
"""Fires events for deprecated env_var usage."""
[dep_fn() for dep_fn in self.deprecated_env_var_warnings]
Expand Down
52 changes: 52 additions & 0 deletions tests/functional/cli/test_option_interaction_validations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from datetime import datetime

import pytest
from freezegun import freeze_time

from dbt.tests.util import run_dbt


class TestEventTimeEndEventTimeStart:
@pytest.mark.parametrize(
"event_time_start,event_time_end,expect_pass",
[
("2024-10-01", "2024-10-02", True),
("2024-10-02", "2024-10-01", False),
],
)
def test_option_combo(self, project, event_time_start, event_time_end, expect_pass):
try:
run_dbt(
[
"build",
"--event-time-start",
event_time_start,
"--event-time-end",
event_time_end,
]
)
assert expect_pass
except Exception as e:
assert (
"Value for `--event-time-start` must be less than `--event-time-end`"
in e.__str__()
)
assert not expect_pass


class TestEventTimeStartCurrent_time:
@pytest.mark.parametrize(
"event_time_start,current_time,expect_pass",
[
("2024-10-01", "2024-10-02", True),
("2024-10-02", "2024-10-01", False),
],
)
def test_option_combo(self, project, event_time_start, current_time, expect_pass):
with freeze_time(datetime.fromisoformat(current_time)):
try:
run_dbt(["build", "--event-time-start", event_time_start])
assert expect_pass
except Exception as e:
assert "must be less than the current time" in e.__str__()
assert not expect_pass

0 comments on commit fc8eb82

Please sign in to comment.