4141from sqlmesh .core .loader import Loader , SqlMeshLoader
4242from sqlmesh .core .notification_target import NotificationTarget
4343from sqlmesh .core .user import User
44+ from sqlmesh .utils .date import to_timestamp , now , now_timestamp
4445from sqlmesh .utils .errors import ConfigError
4546from sqlmesh .utils .pydantic import field_validator , model_validator
4647
@@ -88,7 +89,7 @@ class Config(BaseConfig):
8889 after_all: SQL statements or macros to be executed at the end of the `sqlmesh plan` and `sqlmesh run` commands.
8990 """
9091
91- gateways : t . Dict [ str , GatewayConfig ] = {"" : GatewayConfig ()}
92+ gateways : GatewayDict = {"" : GatewayConfig ()}
9293 default_connection : SerializableConnectionConfig = DuckDBConnectionConfig ()
9394 default_test_connection_ : t .Optional [SerializableConnectionConfig ] = Field (
9495 default = None , alias = "default_test_connection"
@@ -97,8 +98,8 @@ class Config(BaseConfig):
9798 default_gateway : str = ""
9899 notification_targets : t .List [NotificationTarget ] = []
99100 project : str = ""
100- snapshot_ttl : str = c .DEFAULT_SNAPSHOT_TTL
101- environment_ttl : t .Optional [str ] = c .DEFAULT_ENVIRONMENT_TTL
101+ snapshot_ttl : NoPastTTLString = c .DEFAULT_SNAPSHOT_TTL
102+ environment_ttl : t .Optional [NoPastTTLString ] = c .DEFAULT_ENVIRONMENT_TTL
102103 ignore_patterns : t .List [str ] = c .IGNORE_PATTERNS
103104 time_column_format : str = c .DEFAULT_TIME_COLUMN_FORMAT
104105 users : t .List [User ] = []
@@ -108,12 +109,12 @@ class Config(BaseConfig):
108109 loader_kwargs : t .Dict [str , t .Any ] = {}
109110 env_vars : t .Dict [str , str ] = {}
110111 username : str = ""
111- physical_schema_mapping : t . Dict [ re . Pattern , str ] = {}
112+ physical_schema_mapping : RegexKeyDict = {}
112113 environment_suffix_target : EnvironmentSuffixTarget = Field (
113114 default = EnvironmentSuffixTarget .default
114115 )
115116 gateway_managed_virtual_layer : bool = False
116- environment_catalog_mapping : t . Dict [ re . Pattern , str ] = {}
117+ environment_catalog_mapping : RegexKeyDict = {}
117118 default_target_environment : str = c .PROD
118119 log_limit : int = c .DEFAULT_LOG_LIMIT
119120 cicd_bot : t .Optional [CICDBotConfig ] = None
@@ -154,23 +155,6 @@ class Config(BaseConfig):
154155 _scheduler_config_validator = scheduler_config_validator # type: ignore
155156 _variables_validator = variables_validator
156157
157- @field_validator ("gateways" , mode = "before" )
158- @classmethod
159- def _gateways_ensure_dict (cls , value : t .Dict [str , t .Any ]) -> t .Dict [str , t .Any ]:
160- try :
161- if not isinstance (value , GatewayConfig ):
162- GatewayConfig .parse_obj (value )
163- return {"" : value }
164- except Exception :
165- return value
166-
167- @field_validator ("environment_catalog_mapping" , "physical_schema_mapping" , mode = "before" )
168- @classmethod
169- def _validate_regex_keys (
170- cls , value : t .Dict [str | re .Pattern , t .Any ]
171- ) -> t .Dict [re .Pattern , t .Any ]:
172- return compile_regex_mapping (value )
173-
174158 @model_validator (mode = "before" )
175159 def _normalize_and_validate_fields (cls , data : t .Any ) -> t .Any :
176160 if not isinstance (data , dict ):
@@ -302,3 +286,37 @@ def dialect(self) -> t.Optional[str]:
302286 @property
303287 def fingerprint (self ) -> str :
304288 return str (zlib .crc32 (pickle .dumps (self .dict (exclude = {"loader" , "notification_targets" }))))
289+
290+
291+ def validate_no_past_ttl (v : str ) -> str :
292+ current_time = now ()
293+ if to_timestamp (v , relative_base = current_time ) < to_timestamp (current_time ):
294+ raise ValueError (
295+ f"TTL '{ v } ' is in the past. Please specify a relative time in the future. Ex: `in 1 week` instead of `1 week`."
296+ )
297+ return v
298+
299+
300+ def gateways_ensure_dict (value : t .Dict [str , t .Any ]) -> t .Dict [str , t .Any ]:
301+ try :
302+ if not isinstance (value , GatewayConfig ):
303+ GatewayConfig .parse_obj (value )
304+ return {"" : value }
305+ except Exception :
306+ return value
307+
308+
309+ def validate_regex_key_dict (value : t .Dict [str | re .Pattern , t .Any ]) -> t .Dict [re .Pattern , t .Any ]:
310+ return compile_regex_mapping (value )
311+
312+
313+ if t .TYPE_CHECKING :
314+ NoPastTTLString = str
315+ GatewayDict = t .Dict [str , GatewayConfig ]
316+ RegexKeyDict = t .Dict [re .Pattern , str ]
317+ else :
318+ from pydantic .functional_validators import BeforeValidator
319+
320+ NoPastTTLString = t .Annotated [str , BeforeValidator (validate_no_past_ttl )]
321+ GatewayDict = t .Annotated [t .Dict [str , GatewayConfig ], BeforeValidator (gateways_ensure_dict )]
322+ RegexKeyDict = t .Annotated [t .Dict [re .Pattern , str ], BeforeValidator (validate_regex_key_dict )]
0 commit comments