Edit on GitHub

sqlmesh.core.model.meta

  1from __future__ import annotations
  2
  3import typing as t
  4from enum import Enum
  5
  6from croniter import croniter
  7from pydantic import Field, root_validator, validator
  8from sqlglot import exp, maybe_parse
  9
 10import sqlmesh.core.dialect as d
 11from sqlmesh.core.model.kind import (
 12    IncrementalByTimeRangeKind,
 13    IncrementalByUniqueKeyKind,
 14    ModelKind,
 15    ModelKindName,
 16    TimeColumn,
 17    model_kind_validator,
 18)
 19from sqlmesh.utils import unique
 20from sqlmesh.utils.date import TimeLike, preserve_time_like_kind, to_datetime
 21from sqlmesh.utils.errors import ConfigError
 22from sqlmesh.utils.pydantic import PydanticModel
 23
 24
 25class IntervalUnit(str, Enum):
 26    """IntervalUnit is the inferred granularity of an incremental model.
 27
 28    IntervalUnit can be one of 4 types, DAY, HOUR, MINUTE. The unit is inferred
 29    based on the cron schedule of a model. The minimum time delta between a sample set of dates
 30    is used to determine which unit a model's schedule is.
 31    """
 32
 33    DAY = "day"
 34    HOUR = "hour"
 35    MINUTE = "minute"
 36
 37
 38HookCall = t.Union[exp.Expression, t.Tuple[str, t.Dict[str, exp.Expression]]]
 39AuditReference = t.Tuple[str, t.Dict[str, exp.Expression]]
 40
 41
 42class ModelMeta(PydanticModel):
 43    """Metadata for models which can be defined in SQL."""
 44
 45    name: str
 46    kind: ModelKind = ModelKind(name=ModelKindName.VIEW)
 47    dialect: str = ""
 48    cron: str = "@daily"
 49    owner: t.Optional[str]
 50    description: t.Optional[str]
 51    stamp: t.Optional[str]
 52    start: t.Optional[TimeLike]
 53    batch_size: t.Optional[int]
 54    storage_format: t.Optional[str]
 55    partitioned_by_: t.List[str] = Field(default=[], alias="partitioned_by")
 56    pre: t.List[HookCall] = []
 57    post: t.List[HookCall] = []
 58    depends_on_: t.Optional[t.Set[str]] = Field(default=None, alias="depends_on")
 59    columns_to_types_: t.Optional[t.Dict[str, exp.DataType]] = Field(default=None, alias="columns")
 60    column_descriptions_: t.Optional[t.Dict[str, str]]
 61    audits: t.List[AuditReference] = []
 62
 63    _croniter: t.Optional[croniter] = None
 64
 65    _model_kind_validator = model_kind_validator
 66
 67    @validator("audits", pre=True)
 68    def _audits_validator(cls, v: t.Any) -> t.Any:
 69        def extract(v: exp.Expression) -> t.Tuple[str, t.Dict[str, str]]:
 70            kwargs = {}
 71
 72            if isinstance(v, exp.Anonymous):
 73                func = v.name
 74                args = v.expressions
 75            elif isinstance(v, exp.Func):
 76                func = v.sql_name()
 77                args = list(v.args.values())
 78            else:
 79                return v.name.lower(), {}
 80
 81            for arg in args:
 82                if not isinstance(arg, exp.EQ):
 83                    raise ConfigError(
 84                        f"Function '{func}' must be called with key-value arguments like {func}(arg=value)."
 85                    )
 86                kwargs[arg.left.name] = arg.right
 87            return (func.lower(), kwargs)
 88
 89        if isinstance(v, (exp.Tuple, exp.Array)):
 90            return [extract(i) for i in v.expressions]
 91        if isinstance(v, exp.Paren):
 92            return [extract(v.this)]
 93        if isinstance(v, exp.Expression):
 94            return [extract(v)]
 95        if isinstance(v, list):
 96            return [
 97                (
 98                    entry[0].lower(),
 99                    {
100                        key: d.parse(value)[0] if isinstance(value, str) else value
101                        for key, value in entry[1].items()
102                    },
103                )
104                for entry in v
105            ]
106        return v
107
108    @validator("pre", "post", pre=True)
109    def _value_or_tuple_with_args_validator(cls, v: t.Any) -> t.Any:
110        def extract(v: exp.Expression) -> t.Union[exp.Expression, t.Tuple[str, t.Dict[str, str]]]:
111            kwargs = {}
112
113            if isinstance(v, exp.Anonymous):
114                func = v.name
115                args = v.expressions
116            elif not isinstance(v, d.Jinja) and isinstance(v, exp.Func):
117                func = v.sql_name()
118                args = list(v.args.values())
119            elif isinstance(v, exp.Column):
120                return v.name.lower(), {}
121            else:
122                return v
123
124            for arg in args:
125                if not isinstance(arg, exp.EQ):
126                    raise ConfigError(
127                        f"Function '{func}' must be called with key-value arguments like {func}(arg=value)."
128                    )
129                kwargs[arg.left.name] = arg.right
130            return (func.lower(), kwargs)
131
132        if isinstance(v, (exp.Tuple, exp.Array)):
133            items: t.List[t.Any] = []
134            for item in v.expressions:
135                if isinstance(item, exp.Literal):
136                    items.append(d.parse(item.this)[0])
137                elif isinstance(item, exp.Column) and isinstance(item.this, exp.Identifier):
138                    items.append(d.parse(item.this.this)[0])
139                else:
140                    items.append(extract(item))
141            return items
142        if isinstance(v, exp.Paren):
143            return [extract(v.this)]
144        if isinstance(v, exp.Expression):
145            return [extract(v)]
146        if isinstance(v, list) and v:
147            transformed = []
148            for entry in v:
149                if isinstance(entry, exp.Expression):
150                    transformed.append(extract(entry))
151                elif isinstance(entry, str):
152                    transformed.append(d.parse(entry)[0])
153                else:
154                    transformed.append(
155                        (
156                            entry[0].lower(),
157                            {
158                                key: d.parse(value)[0] if isinstance(value, str) else value
159                                for key, value in entry[1].items()
160                            },
161                        )
162                    )
163            return transformed
164
165        return v
166
167    @validator("partitioned_by_", pre=True)
168    def _value_or_tuple_validator(cls, v: t.Any) -> t.Any:
169        if isinstance(v, (exp.Tuple, exp.Array)):
170            return [e.name for e in v.expressions]
171        if isinstance(v, exp.Expression):
172            return [v.name]
173        return v
174
175    @validator("dialect", "owner", "storage_format", "description", "stamp", pre=True)
176    def _string_validator(cls, v: t.Any) -> t.Optional[str]:
177        if isinstance(v, exp.Expression):
178            return v.name
179        return str(v) if v is not None else None
180
181    @validator("cron", pre=True)
182    def _cron_validator(cls, v: t.Any) -> t.Optional[str]:
183        cron = cls._string_validator(v)
184        if cron:
185            try:
186                croniter(cron)
187            except Exception:
188                raise ConfigError(f"Invalid cron expression '{cron}'")
189        return cron
190
191    @validator("columns_to_types_", pre=True)
192    def _columns_validator(cls, v: t.Any) -> t.Optional[t.Dict[str, exp.DataType]]:
193        if isinstance(v, exp.Schema):
194            return {column.name: column.args["kind"] for column in v.expressions}
195        if isinstance(v, dict):
196            return {
197                k: maybe_parse(data_type, into=exp.DataType)  # type: ignore
198                for k, data_type in v.items()
199            }
200        return v
201
202    @validator("depends_on_", pre=True)
203    def _depends_on_validator(cls, v: t.Any) -> t.Optional[t.Set[str]]:
204        if isinstance(v, (exp.Array, exp.Tuple)):
205            return {
206                exp.table_name(table.name if table.is_string else table.sql())
207                for table in v.expressions
208            }
209        if isinstance(v, exp.Expression):
210            return {exp.table_name(v.sql())}
211        return v
212
213    @validator("start", pre=True)
214    def _date_validator(cls, v: t.Any) -> t.Optional[TimeLike]:
215        if isinstance(v, exp.Expression):
216            v = v.name
217        if v and not to_datetime(v):
218            raise ConfigError(f"'{v}' not a valid date time")
219        return v
220
221    @validator("batch_size", pre=True)
222    def _int_validator(cls, v: t.Any) -> t.Optional[int]:
223        if not isinstance(v, exp.Expression):
224            batch_size = int(v) if v is not None else None
225        else:
226            batch_size = int(v.name)
227        if batch_size is not None and batch_size <= 0:
228            raise ConfigError(
229                f"Invalid batch size {batch_size}. The value should be greater than 0"
230            )
231        return batch_size
232
233    @root_validator
234    def _kind_validator(cls, values: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
235        kind = values.get("kind")
236        if kind and not kind.is_materialized:
237            if values.get("partitioned_by_"):
238                raise ValueError(f"partitioned_by field cannot be set for {kind} models")
239        return values
240
241    @property
242    def time_column(self) -> t.Optional[TimeColumn]:
243        if isinstance(self.kind, IncrementalByTimeRangeKind):
244            return self.kind.time_column
245        return None
246
247    @property
248    def unique_key(self) -> t.List[str]:
249        if isinstance(self.kind, IncrementalByUniqueKeyKind):
250            return self.kind.unique_key
251        return []
252
253    @property
254    def partitioned_by(self) -> t.List[str]:
255        time_column = [self.time_column.column] if self.time_column else []
256        return unique([*time_column, *self.partitioned_by_])
257
258    @property
259    def column_descriptions(self) -> t.Dict[str, str]:
260        """A dictionary of column names to annotation comments."""
261        return self.column_descriptions_ or {}
262
263    def interval_unit(self, sample_size: int = 10) -> IntervalUnit:
264        """Returns the IntervalUnit of the model
265
266        The interval unit is used to determine the lag applied to start_date and end_date for model rendering and intervals.
267
268        Args:
269            sample_size: The number of samples to take from the cron to infer the unit.
270
271        Returns:
272            The IntervalUnit enum.
273        """
274        schedule = croniter(self.cron)
275        samples = [schedule.get_next() for _ in range(sample_size)]
276        min_interval = min(b - a for a, b in zip(samples, samples[1:]))
277        if min_interval >= 86400:
278            return IntervalUnit.DAY
279        elif min_interval >= 3600:
280            return IntervalUnit.HOUR
281        return IntervalUnit.MINUTE
282
283    def normalized_cron(self) -> str:
284        """Returns the UTC normalized cron based on sampling heuristics.
285
286        SQLMesh supports 3 interval units, daily, hourly, and minutes. If a job is scheduled
287        daily at 1PM, the actual intervals are shifted back to midnight UTC.
288
289        Returns:
290            The cron string representing either daily, hourly, or minutes.
291        """
292        unit = self.interval_unit()
293        if unit == IntervalUnit.MINUTE:
294            return "* * * * *"
295        if unit == IntervalUnit.HOUR:
296            return "0 * * * *"
297        if unit == IntervalUnit.DAY:
298            return "0 0 * * *"
299        return ""
300
301    def croniter(self, value: TimeLike) -> croniter:
302        if self._croniter is None:
303            self._croniter = croniter(self.normalized_cron())
304        self._croniter.set_current(to_datetime(value))
305        return self._croniter
306
307    def cron_next(self, value: TimeLike) -> TimeLike:
308        """
309        Get the next timestamp given a time-like value and the model's cron.
310
311        Args:
312            value: A variety of date formats.
313
314        Returns:
315            The timestamp for the next run.
316        """
317        return preserve_time_like_kind(value, self.croniter(value).get_next())
318
319    def cron_prev(self, value: TimeLike) -> TimeLike:
320        """
321        Get the previous timestamp given a time-like value and the model's cron.
322
323        Args:
324            value: A variety of date formats.
325
326        Returns:
327            The timestamp for the previous run.
328        """
329        return preserve_time_like_kind(value, self.croniter(value).get_prev())
330
331    def cron_floor(self, value: TimeLike) -> TimeLike:
332        """
333        Get the floor timestamp given a time-like value and the model's cron.
334
335        Args:
336            value: A variety of date formats.
337
338        Returns:
339            The timestamp floor.
340        """
341        return preserve_time_like_kind(value, self.croniter(self.cron_next(value)).get_prev())
class IntervalUnit(builtins.str, enum.Enum):
26class IntervalUnit(str, Enum):
27    """IntervalUnit is the inferred granularity of an incremental model.
28
29    IntervalUnit can be one of 4 types, DAY, HOUR, MINUTE. The unit is inferred
30    based on the cron schedule of a model. The minimum time delta between a sample set of dates
31    is used to determine which unit a model's schedule is.
32    """
33
34    DAY = "day"
35    HOUR = "hour"
36    MINUTE = "minute"

IntervalUnit is the inferred granularity of an incremental model.

IntervalUnit can be one of 4 types, DAY, HOUR, MINUTE. The unit is inferred based on the cron schedule of a model. The minimum time delta between a sample set of dates is used to determine which unit a model's schedule is.

DAY = <IntervalUnit.DAY: 'day'>
HOUR = <IntervalUnit.HOUR: 'hour'>
MINUTE = <IntervalUnit.MINUTE: 'minute'>
Inherited Members
enum.Enum
name
value
builtins.str
encode
replace
split
rsplit
join
capitalize
casefold
title
center
count
expandtabs
find
partition
index
ljust
lower
lstrip
rfind
rindex
rjust
rstrip
rpartition
splitlines
strip
swapcase
translate
upper
startswith
endswith
removeprefix
removesuffix
isascii
islower
isupper
istitle
isspace
isdecimal
isdigit
isnumeric
isalpha
isalnum
isidentifier
isprintable
zfill
format
format_map
maketrans
class ModelMeta(sqlmesh.utils.pydantic.PydanticModel):
 43class ModelMeta(PydanticModel):
 44    """Metadata for models which can be defined in SQL."""
 45
 46    name: str
 47    kind: ModelKind = ModelKind(name=ModelKindName.VIEW)
 48    dialect: str = ""
 49    cron: str = "@daily"
 50    owner: t.Optional[str]
 51    description: t.Optional[str]
 52    stamp: t.Optional[str]
 53    start: t.Optional[TimeLike]
 54    batch_size: t.Optional[int]
 55    storage_format: t.Optional[str]
 56    partitioned_by_: t.List[str] = Field(default=[], alias="partitioned_by")
 57    pre: t.List[HookCall] = []
 58    post: t.List[HookCall] = []
 59    depends_on_: t.Optional[t.Set[str]] = Field(default=None, alias="depends_on")
 60    columns_to_types_: t.Optional[t.Dict[str, exp.DataType]] = Field(default=None, alias="columns")
 61    column_descriptions_: t.Optional[t.Dict[str, str]]
 62    audits: t.List[AuditReference] = []
 63
 64    _croniter: t.Optional[croniter] = None
 65
 66    _model_kind_validator = model_kind_validator
 67
 68    @validator("audits", pre=True)
 69    def _audits_validator(cls, v: t.Any) -> t.Any:
 70        def extract(v: exp.Expression) -> t.Tuple[str, t.Dict[str, str]]:
 71            kwargs = {}
 72
 73            if isinstance(v, exp.Anonymous):
 74                func = v.name
 75                args = v.expressions
 76            elif isinstance(v, exp.Func):
 77                func = v.sql_name()
 78                args = list(v.args.values())
 79            else:
 80                return v.name.lower(), {}
 81
 82            for arg in args:
 83                if not isinstance(arg, exp.EQ):
 84                    raise ConfigError(
 85                        f"Function '{func}' must be called with key-value arguments like {func}(arg=value)."
 86                    )
 87                kwargs[arg.left.name] = arg.right
 88            return (func.lower(), kwargs)
 89
 90        if isinstance(v, (exp.Tuple, exp.Array)):
 91            return [extract(i) for i in v.expressions]
 92        if isinstance(v, exp.Paren):
 93            return [extract(v.this)]
 94        if isinstance(v, exp.Expression):
 95            return [extract(v)]
 96        if isinstance(v, list):
 97            return [
 98                (
 99                    entry[0].lower(),
100                    {
101                        key: d.parse(value)[0] if isinstance(value, str) else value
102                        for key, value in entry[1].items()
103                    },
104                )
105                for entry in v
106            ]
107        return v
108
109    @validator("pre", "post", pre=True)
110    def _value_or_tuple_with_args_validator(cls, v: t.Any) -> t.Any:
111        def extract(v: exp.Expression) -> t.Union[exp.Expression, t.Tuple[str, t.Dict[str, str]]]:
112            kwargs = {}
113
114            if isinstance(v, exp.Anonymous):
115                func = v.name
116                args = v.expressions
117            elif not isinstance(v, d.Jinja) and isinstance(v, exp.Func):
118                func = v.sql_name()
119                args = list(v.args.values())
120            elif isinstance(v, exp.Column):
121                return v.name.lower(), {}
122            else:
123                return v
124
125            for arg in args:
126                if not isinstance(arg, exp.EQ):
127                    raise ConfigError(
128                        f"Function '{func}' must be called with key-value arguments like {func}(arg=value)."
129                    )
130                kwargs[arg.left.name] = arg.right
131            return (func.lower(), kwargs)
132
133        if isinstance(v, (exp.Tuple, exp.Array)):
134            items: t.List[t.Any] = []
135            for item in v.expressions:
136                if isinstance(item, exp.Literal):
137                    items.append(d.parse(item.this)[0])
138                elif isinstance(item, exp.Column) and isinstance(item.this, exp.Identifier):
139                    items.append(d.parse(item.this.this)[0])
140                else:
141                    items.append(extract(item))
142            return items
143        if isinstance(v, exp.Paren):
144            return [extract(v.this)]
145        if isinstance(v, exp.Expression):
146            return [extract(v)]
147        if isinstance(v, list) and v:
148            transformed = []
149            for entry in v:
150                if isinstance(entry, exp.Expression):
151                    transformed.append(extract(entry))
152                elif isinstance(entry, str):
153                    transformed.append(d.parse(entry)[0])
154                else:
155                    transformed.append(
156                        (
157                            entry[0].lower(),
158                            {
159                                key: d.parse(value)[0] if isinstance(value, str) else value
160                                for key, value in entry[1].items()
161                            },
162                        )
163                    )
164            return transformed
165
166        return v
167
168    @validator("partitioned_by_", pre=True)
169    def _value_or_tuple_validator(cls, v: t.Any) -> t.Any:
170        if isinstance(v, (exp.Tuple, exp.Array)):
171            return [e.name for e in v.expressions]
172        if isinstance(v, exp.Expression):
173            return [v.name]
174        return v
175
176    @validator("dialect", "owner", "storage_format", "description", "stamp", pre=True)
177    def _string_validator(cls, v: t.Any) -> t.Optional[str]:
178        if isinstance(v, exp.Expression):
179            return v.name
180        return str(v) if v is not None else None
181
182    @validator("cron", pre=True)
183    def _cron_validator(cls, v: t.Any) -> t.Optional[str]:
184        cron = cls._string_validator(v)
185        if cron:
186            try:
187                croniter(cron)
188            except Exception:
189                raise ConfigError(f"Invalid cron expression '{cron}'")
190        return cron
191
192    @validator("columns_to_types_", pre=True)
193    def _columns_validator(cls, v: t.Any) -> t.Optional[t.Dict[str, exp.DataType]]:
194        if isinstance(v, exp.Schema):
195            return {column.name: column.args["kind"] for column in v.expressions}
196        if isinstance(v, dict):
197            return {
198                k: maybe_parse(data_type, into=exp.DataType)  # type: ignore
199                for k, data_type in v.items()
200            }
201        return v
202
203    @validator("depends_on_", pre=True)
204    def _depends_on_validator(cls, v: t.Any) -> t.Optional[t.Set[str]]:
205        if isinstance(v, (exp.Array, exp.Tuple)):
206            return {
207                exp.table_name(table.name if table.is_string else table.sql())
208                for table in v.expressions
209            }
210        if isinstance(v, exp.Expression):
211            return {exp.table_name(v.sql())}
212        return v
213
214    @validator("start", pre=True)
215    def _date_validator(cls, v: t.Any) -> t.Optional[TimeLike]:
216        if isinstance(v, exp.Expression):
217            v = v.name
218        if v and not to_datetime(v):
219            raise ConfigError(f"'{v}' not a valid date time")
220        return v
221
222    @validator("batch_size", pre=True)
223    def _int_validator(cls, v: t.Any) -> t.Optional[int]:
224        if not isinstance(v, exp.Expression):
225            batch_size = int(v) if v is not None else None
226        else:
227            batch_size = int(v.name)
228        if batch_size is not None and batch_size <= 0:
229            raise ConfigError(
230                f"Invalid batch size {batch_size}. The value should be greater than 0"
231            )
232        return batch_size
233
234    @root_validator
235    def _kind_validator(cls, values: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
236        kind = values.get("kind")
237        if kind and not kind.is_materialized:
238            if values.get("partitioned_by_"):
239                raise ValueError(f"partitioned_by field cannot be set for {kind} models")
240        return values
241
242    @property
243    def time_column(self) -> t.Optional[TimeColumn]:
244        if isinstance(self.kind, IncrementalByTimeRangeKind):
245            return self.kind.time_column
246        return None
247
248    @property
249    def unique_key(self) -> t.List[str]:
250        if isinstance(self.kind, IncrementalByUniqueKeyKind):
251            return self.kind.unique_key
252        return []
253
254    @property
255    def partitioned_by(self) -> t.List[str]:
256        time_column = [self.time_column.column] if self.time_column else []
257        return unique([*time_column, *self.partitioned_by_])
258
259    @property
260    def column_descriptions(self) -> t.Dict[str, str]:
261        """A dictionary of column names to annotation comments."""
262        return self.column_descriptions_ or {}
263
264    def interval_unit(self, sample_size: int = 10) -> IntervalUnit:
265        """Returns the IntervalUnit of the model
266
267        The interval unit is used to determine the lag applied to start_date and end_date for model rendering and intervals.
268
269        Args:
270            sample_size: The number of samples to take from the cron to infer the unit.
271
272        Returns:
273            The IntervalUnit enum.
274        """
275        schedule = croniter(self.cron)
276        samples = [schedule.get_next() for _ in range(sample_size)]
277        min_interval = min(b - a for a, b in zip(samples, samples[1:]))
278        if min_interval >= 86400:
279            return IntervalUnit.DAY
280        elif min_interval >= 3600:
281            return IntervalUnit.HOUR
282        return IntervalUnit.MINUTE
283
284    def normalized_cron(self) -> str:
285        """Returns the UTC normalized cron based on sampling heuristics.
286
287        SQLMesh supports 3 interval units, daily, hourly, and minutes. If a job is scheduled
288        daily at 1PM, the actual intervals are shifted back to midnight UTC.
289
290        Returns:
291            The cron string representing either daily, hourly, or minutes.
292        """
293        unit = self.interval_unit()
294        if unit == IntervalUnit.MINUTE:
295            return "* * * * *"
296        if unit == IntervalUnit.HOUR:
297            return "0 * * * *"
298        if unit == IntervalUnit.DAY:
299            return "0 0 * * *"
300        return ""
301
302    def croniter(self, value: TimeLike) -> croniter:
303        if self._croniter is None:
304            self._croniter = croniter(self.normalized_cron())
305        self._croniter.set_current(to_datetime(value))
306        return self._croniter
307
308    def cron_next(self, value: TimeLike) -> TimeLike:
309        """
310        Get the next timestamp given a time-like value and the model's cron.
311
312        Args:
313            value: A variety of date formats.
314
315        Returns:
316            The timestamp for the next run.
317        """
318        return preserve_time_like_kind(value, self.croniter(value).get_next())
319
320    def cron_prev(self, value: TimeLike) -> TimeLike:
321        """
322        Get the previous timestamp given a time-like value and the model's cron.
323
324        Args:
325            value: A variety of date formats.
326
327        Returns:
328            The timestamp for the previous run.
329        """
330        return preserve_time_like_kind(value, self.croniter(value).get_prev())
331
332    def cron_floor(self, value: TimeLike) -> TimeLike:
333        """
334        Get the floor timestamp given a time-like value and the model's cron.
335
336        Args:
337            value: A variety of date formats.
338
339        Returns:
340            The timestamp floor.
341        """
342        return preserve_time_like_kind(value, self.croniter(self.cron_next(value)).get_prev())

Metadata for models which can be defined in SQL.

column_descriptions: Dict[str, str]

A dictionary of column names to annotation comments.

def interval_unit(self, sample_size: int = 10) -> sqlmesh.core.model.meta.IntervalUnit:
264    def interval_unit(self, sample_size: int = 10) -> IntervalUnit:
265        """Returns the IntervalUnit of the model
266
267        The interval unit is used to determine the lag applied to start_date and end_date for model rendering and intervals.
268
269        Args:
270            sample_size: The number of samples to take from the cron to infer the unit.
271
272        Returns:
273            The IntervalUnit enum.
274        """
275        schedule = croniter(self.cron)
276        samples = [schedule.get_next() for _ in range(sample_size)]
277        min_interval = min(b - a for a, b in zip(samples, samples[1:]))
278        if min_interval >= 86400:
279            return IntervalUnit.DAY
280        elif min_interval >= 3600:
281            return IntervalUnit.HOUR
282        return IntervalUnit.MINUTE

Returns the IntervalUnit of the model

The interval unit is used to determine the lag applied to start_date and end_date for model rendering and intervals.

Arguments:
  • sample_size: The number of samples to take from the cron to infer the unit.
Returns:

The IntervalUnit enum.

def normalized_cron(self) -> str:
284    def normalized_cron(self) -> str:
285        """Returns the UTC normalized cron based on sampling heuristics.
286
287        SQLMesh supports 3 interval units, daily, hourly, and minutes. If a job is scheduled
288        daily at 1PM, the actual intervals are shifted back to midnight UTC.
289
290        Returns:
291            The cron string representing either daily, hourly, or minutes.
292        """
293        unit = self.interval_unit()
294        if unit == IntervalUnit.MINUTE:
295            return "* * * * *"
296        if unit == IntervalUnit.HOUR:
297            return "0 * * * *"
298        if unit == IntervalUnit.DAY:
299            return "0 0 * * *"
300        return ""

Returns the UTC normalized cron based on sampling heuristics.

SQLMesh supports 3 interval units, daily, hourly, and minutes. If a job is scheduled daily at 1PM, the actual intervals are shifted back to midnight UTC.

Returns:

The cron string representing either daily, hourly, or minutes.

def croniter( self, value: Union[datetime.date, datetime.datetime, str, int, float]) -> <function ModelMeta.croniter at 0x13c3ec940>:
302    def croniter(self, value: TimeLike) -> croniter:
303        if self._croniter is None:
304            self._croniter = croniter(self.normalized_cron())
305        self._croniter.set_current(to_datetime(value))
306        return self._croniter
def cron_next( self, value: Union[datetime.date, datetime.datetime, str, int, float]) -> Union[datetime.date, datetime.datetime, str, int, float]:
308    def cron_next(self, value: TimeLike) -> TimeLike:
309        """
310        Get the next timestamp given a time-like value and the model's cron.
311
312        Args:
313            value: A variety of date formats.
314
315        Returns:
316            The timestamp for the next run.
317        """
318        return preserve_time_like_kind(value, self.croniter(value).get_next())

Get the next timestamp given a time-like value and the model's cron.

Arguments:
  • value: A variety of date formats.
Returns:

The timestamp for the next run.

def cron_prev( self, value: Union[datetime.date, datetime.datetime, str, int, float]) -> Union[datetime.date, datetime.datetime, str, int, float]:
320    def cron_prev(self, value: TimeLike) -> TimeLike:
321        """
322        Get the previous timestamp given a time-like value and the model's cron.
323
324        Args:
325            value: A variety of date formats.
326
327        Returns:
328            The timestamp for the previous run.
329        """
330        return preserve_time_like_kind(value, self.croniter(value).get_prev())

Get the previous timestamp given a time-like value and the model's cron.

Arguments:
  • value: A variety of date formats.
Returns:

The timestamp for the previous run.

def cron_floor( self, value: Union[datetime.date, datetime.datetime, str, int, float]) -> Union[datetime.date, datetime.datetime, str, int, float]:
332    def cron_floor(self, value: TimeLike) -> TimeLike:
333        """
334        Get the floor timestamp given a time-like value and the model's cron.
335
336        Args:
337            value: A variety of date formats.
338
339        Returns:
340            The timestamp floor.
341        """
342        return preserve_time_like_kind(value, self.croniter(self.cron_next(value)).get_prev())

Get the floor timestamp given a time-like value and the model's cron.

Arguments:
  • value: A variety of date formats.
Returns:

The timestamp floor.

Inherited Members
pydantic.main.BaseModel
BaseModel
parse_obj
parse_raw
parse_file
from_orm
construct
copy
schema
schema_json
validate
update_forward_refs
sqlmesh.utils.pydantic.PydanticModel
Config
dict
json
missing_required_fields
extra_fields
all_fields
required_fields