sqlmesh.core.model.meta API documentation

Edit on GitHub

# sqlmesh.core.model.meta

View Source

      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): View Source

    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): View Source

     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: View Source

    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: View Source

    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>: View Source

    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]: View Source

    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]: View Source

    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]: View Source

    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