-
Notifications
You must be signed in to change notification settings - Fork 181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: Allow macros in python model properties #3740
base: main
Are you sure you want to change the base?
Conversation
sqlmesh/core/model/decorator.py
Outdated
) | ||
) | ||
|
||
renderer_kwargs = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How was this set compiled? Can we have this set shared somehow with the one in load_sql_based_model
? I don't want to update one place and then forget about the other one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right I will see how to either share or refactor to have a function with the same logic in both load_sql_based_model
and here
sqlmesh/core/model/decorator.py
Outdated
"dialect": dialect, | ||
"default_catalog": default_catalog, | ||
} | ||
meta_renderer = _meta_renderer( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps, instead of kwargs, the _meta_renderer
function should take all the arguments above by name explicitly (no default values).
sqlmesh/core/model/decorator.py
Outdated
@@ -119,21 +121,49 @@ def model( | |||
|
|||
build_env(self.func, env=env, name=entrypoint, path=module_path) | |||
|
|||
# Properties to be rendered at model creation time | |||
expressions = [exp.Property(this="name", value=exp.to_table(self.name, dialect=dialect))] | |||
for field_name in {"enabled", "start", "end"}: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why only these properties? Shouldn't we instead take all fields from the ModelMeta
schema with few exceptions like signals
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok I will add all fields of ModelMeta
instead, aside from these that shouldn't be rendered at creation time. it will require specific handling for each property, but it can be done having something similar to META_FIELD_CONVERTER
like in render_definition
sqlmesh/core/model/decorator.py
Outdated
from sqlmesh.core.model.definition import ( | ||
Model, | ||
_meta_renderer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we rename this to get rid of the leading _
? It's not a private helper anymore if we reference it here.
sqlmesh/core/model/decorator.py
Outdated
and isinstance(field_value, exp.Expression) | ||
or (isinstance(field_value, str) and field_value.startswith(SQLMESH_MACRO_PREFIX)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's include parentheses so that operator precedence can be easily understood.
sqlmesh/core/model/decorator.py
Outdated
@@ -119,21 +121,49 @@ def model( | |||
|
|||
build_env(self.func, env=env, name=entrypoint, path=module_path) | |||
|
|||
# Properties to be rendered at model creation time | |||
expressions = [exp.Property(this="name", value=exp.to_table(self.name, dialect=dialect))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"name" should be a var, otherwise the ast is invalid
8065b51
to
7cf4024
Compare
Thanks for the review @izeigerman , @georgesittas , @tobymao addressed comments and revised logic to handle all model properties, except for those that need to be rendered at evaluation time |
fields: t.Dict[str, t.Any], | ||
module_path: Path, | ||
path: Path, | ||
jinja_macros: t.Optional[JinjaMacroRegistry] = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest we don't have default values for any fields here, so that we know to explicitly provide them everywhere we call this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is they flow in as optional from model's arguments:
sqlmesh/sqlmesh/core/model/decorator.py
Lines 87 to 90 in dc613d0
macros: t.Optional[MacroRegistry] = None, | |
jinja_macros: t.Optional[JinjaMacroRegistry] = None, | |
audit_definitions: t.Optional[t.Dict[str, ModelAudit]] = None, | |
dialect: t.Optional[str] = None, |
sqlmesh/core/model/definition.py
Outdated
default_catalog=default_catalog, | ||
) | ||
assert rendered_expr and len(rendered_expr) == 1 | ||
return rendered_expr[0].sql(dialect=dialect) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look right. If the input is expression, we return a string instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we always return an expression?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes revised it, this was needed for the model's name, but adjusted it for this field only
sqlmesh/core/model/definition.py
Outdated
assert rendered_expr and len(rendered_expr) == 1 | ||
return rendered_expr[0].sql(dialect=dialect) | ||
except Exception: | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why ignore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was for cron that it was for example trying to render and failing to find @daily
macro, but revised it and simply added cron in the RUNTIME_RENDERED_MODEL_FIELDS
as well.
sqlmesh/core/model/definition.py
Outdated
return value | ||
|
||
for field_name, _ in ModelMeta.all_field_infos().items(): | ||
field_name = field_name[:-1] if field_name.endswith("_") else field_name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to do this. You can get the alias from the FieldInfo
object in the value of the dict that you're iterating over.
sqlmesh/core/model/definition.py
Outdated
for field_name, _ in ModelMeta.all_field_infos().items(): | ||
field_name = field_name[:-1] if field_name.endswith("_") else field_name | ||
if (field_value := fields.get(field_name)) and field_name not in { | ||
"audits", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we set these in a named constant like RUNTIME_RENDERED_MODEL_FIELDS
sqlmesh/core/model/definition.py
Outdated
"virtual_properties", | ||
"session_properties", | ||
}: | ||
if isinstance(field_value, t.Dict): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, not sure whether it's a good practice to check for instance of t.Dict
. I believe we just check for dict
in other places.
sqlmesh/core/model/definition.py
Outdated
}: | ||
if isinstance(field_value, t.Dict): | ||
for key, value in field_value.items(): | ||
if key != "merge_filter": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be a part of the same RUNTIME_RENDERED_MODEL_FIELDS
sqlmesh/core/model/definition.py
Outdated
for key, value in field_value.items(): | ||
if key != "merge_filter": | ||
fields[field_name][key] = render_field_value(value) | ||
elif isinstance(field_value, t.List): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto: isinstance(..., list)
sqlmesh/core/model/definition.py
Outdated
dialect=dialect, | ||
default_catalog=default_catalog, | ||
) | ||
assert rendered_expr and len(rendered_expr) == 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we inform the user about the problem here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right adapted it to follow instead the same pattern we have in the rest of the places
7cf4024
to
4432204
Compare
4432204
to
254fe8b
Compare
This update enables macros to be passed in the properties for Python models, aside from
audits
,signals
,merge filter
andphysical_properties
which are rendered at evaluation time (the latter will be addressed on a subsequent pr). This allows to dynamically control for example theenabled
flag based on the gateway: