Skip to content
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

Add test Jinja tag #3261

Merged
merged 3 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- Set application_name for Postgres connections ([#885](https://github.com/fishtown-analytics/dbt/issues/885), [#3182](https://github.com/fishtown-analytics/dbt/pull/3182))
- Support disabling schema tests, and configuring tests from `dbt_project.yml` ([#3252](https://github.com/fishtown-analytics/dbt/issues/3252),
[#3253](https://github.com/fishtown-analytics/dbt/issues/3253), [#3257](https://github.com/fishtown-analytics/dbt/pull/3257))
- Add Jinja tag for tests ([#1173](https://github.com/fishtown-analytics/dbt/issues/1173), [#3261](https://github.com/fishtown-analytics/dbt/pull/3261))

### Under the hood
- Add dependabot configuration for alerting maintainers about keeping dependencies up to date and secure. ([#3061](https://github.com/fishtown-analytics/dbt/issues/3061), [#3062](https://github.com/fishtown-analytics/dbt/pull/3062))
Expand Down
18 changes: 17 additions & 1 deletion core/dbt/clients/jinja.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from dbt.utils import (
get_dbt_macro_name, get_docs_macro_name, get_materialization_macro_name,
deep_map
get_test_macro_name, deep_map
)

from dbt.clients._jinja_blocks import BlockIterator, BlockData, BlockTag
Expand Down Expand Up @@ -408,6 +408,21 @@ def parse(self, parser):
return node


class TestExtension(jinja2.ext.Extension):
tags = ['test']

def parse(self, parser):
node = jinja2.nodes.Macro(lineno=next(parser.stream).lineno)
test_name = parser.parse_assign_target(name_only=True).name

parser.parse_signature(node)
node.defaults = []
node.name = get_test_macro_name(test_name)
node.body = parser.parse_statements(('name:endtest',),
drop_needle=True)
return node


def _is_dunder_name(name):
return name.startswith('__') and name.endswith('__')

Expand Down Expand Up @@ -479,6 +494,7 @@ def get_environment(

args['extensions'].append(MaterializationExtension)
args['extensions'].append(DocumentationExtension)
args['extensions'].append(TestExtension)

env_cls: Type[jinja2.Environment]
text_filter: Type
Expand Down
81 changes: 36 additions & 45 deletions core/dbt/compilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,6 @@ def _inject_ctes_into_sql(self, sql: str, ctes: List[InjectedCTE]) -> str:

return str(parsed)

def _get_dbt_test_name(self) -> str:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wee, since we don't prepend tests with count(*) when "compiling" this can go away!

return 'dbt__cte__internal_test'

# This method is called by the 'compile_node' method. Starting
# from the node that it is passed in, it will recursively call
# itself using the 'extra_ctes'. The 'ephemeral' models do
Expand Down Expand Up @@ -283,55 +280,49 @@ def _recursively_prepend_ctes(
# gathered and then "injected" into the model.
prepended_ctes: List[InjectedCTE] = []

dbt_test_name = self._get_dbt_test_name()

# extra_ctes are added to the model by
# RuntimeRefResolver.create_relation, which adds an
# extra_cte for every model relation which is an
# ephemeral model.
for cte in model.extra_ctes:
if cte.id == dbt_test_name:
sql = cte.sql
if cte.id not in manifest.nodes:
raise InternalException(
f'During compilation, found a cte reference that '
f'could not be resolved: {cte.id}'
)
cte_model = manifest.nodes[cte.id]

if not cte_model.is_ephemeral_model:
raise InternalException(f'{cte.id} is not ephemeral')

# This model has already been compiled, so it's been
# through here before
if getattr(cte_model, 'compiled', False):
assert isinstance(cte_model, tuple(COMPILED_TYPES.values()))
cte_model = cast(NonSourceCompiledNode, cte_model)
new_prepended_ctes = cte_model.extra_ctes

# if the cte_model isn't compiled, i.e. first time here
else:
if cte.id not in manifest.nodes:
raise InternalException(
f'During compilation, found a cte reference that '
f'could not be resolved: {cte.id}'
# This is an ephemeral parsed model that we can compile.
# Compile and update the node
cte_model = self._compile_node(
cte_model, manifest, extra_context)
# recursively call this method
cte_model, new_prepended_ctes = \
self._recursively_prepend_ctes(
cte_model, manifest, extra_context
)
cte_model = manifest.nodes[cte.id]

if not cte_model.is_ephemeral_model:
raise InternalException(f'{cte.id} is not ephemeral')

# This model has already been compiled, so it's been
# through here before
if getattr(cte_model, 'compiled', False):
assert isinstance(cte_model,
tuple(COMPILED_TYPES.values()))
cte_model = cast(NonSourceCompiledNode, cte_model)
new_prepended_ctes = cte_model.extra_ctes

# if the cte_model isn't compiled, i.e. first time here
else:
# This is an ephemeral parsed model that we can compile.
# Compile and update the node
cte_model = self._compile_node(
cte_model, manifest, extra_context)
# recursively call this method
cte_model, new_prepended_ctes = \
self._recursively_prepend_ctes(
cte_model, manifest, extra_context
)
# Save compiled SQL file and sync manifest
self._write_node(cte_model)
manifest.sync_update_node(cte_model)

_extend_prepended_ctes(prepended_ctes, new_prepended_ctes)

new_cte_name = self.add_ephemeral_prefix(cte_model.name)
sql = f' {new_cte_name} as (\n{cte_model.compiled_sql}\n)'

_add_prepended_cte(prepended_ctes, InjectedCTE(id=cte.id, sql=sql))
# Save compiled SQL file and sync manifest
self._write_node(cte_model)
manifest.sync_update_node(cte_model)

_extend_prepended_ctes(prepended_ctes, new_prepended_ctes)

new_cte_name = self.add_ephemeral_prefix(cte_model.name)
sql = f' {new_cte_name} as (\n{cte_model.compiled_sql}\n)'

_add_prepended_cte(prepended_ctes, InjectedCTE(id=cte.id, sql=sql))

# We don't save injected_sql into compiled sql for ephemeral models
# because it will cause problems with processing of subsequent models.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ from validation_errors

{% endmacro %}


{% macro test_accepted_values(model, values) %}
{% test accepted_values(model, values) %}
{% set macro = adapter.dispatch('test_accepted_values') %}
{{ macro(model, values, **kwargs) }}
{% endmacro %}
{% endtest %}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ where {{ column_name }} is null

{% endmacro %}



{% macro test_not_null(model) %}
{% test not_null(model) %}
{% set macro = adapter.dispatch('test_not_null') %}
{{ macro(model, **kwargs) }}
{% endmacro %}
{% endtest %}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ where child.id is not null



{% macro test_relationships(model, to, field) %}
{% test relationships(model, to, field) %}
{% set macro = adapter.dispatch('test_relationships') %}
{{ macro(model, to, field, **kwargs) }}
{% endmacro %}
{% endtest %}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ from (
{% endmacro %}


{% macro test_unique(model) %}
{% test unique(model) %}
{% set macro = adapter.dispatch('test_unique') %}
{{ macro(model, **kwargs) }}
{% endmacro %}
{% endtest %}
2 changes: 1 addition & 1 deletion core/dbt/parser/macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def parse_unparsed_macros(
t for t in
jinja.extract_toplevel_blocks(
base_node.raw_sql,
allowed_blocks={'macro', 'materialization'},
allowed_blocks={'macro', 'materialization', 'test'},
collect_raw_data=False,
)
if isinstance(t, jinja.BlockTag)
Expand Down
21 changes: 9 additions & 12 deletions core/dbt/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,33 +72,30 @@ def get_model_name_or_none(model):
def get_dbt_macro_name(name):
if name is None:
raise dbt.exceptions.InternalException('Got None for a macro name!')
return '{}{}'.format(MACRO_PREFIX, name)
return f'{MACRO_PREFIX}{name}'


def get_dbt_docs_name(name):
if name is None:
raise dbt.exceptions.InternalException('Got None for a doc name!')
return '{}{}'.format(DOCS_PREFIX, name)
return f'{DOCS_PREFIX}{name}'


def get_materialization_macro_name(materialization_name, adapter_type=None,
with_prefix=True):
if adapter_type is None:
adapter_type = 'default'
name = f'materialization_{materialization_name}_{adapter_type}'
return get_dbt_macro_name(name) if with_prefix else name

name = 'materialization_{}_{}'.format(materialization_name, adapter_type)

if with_prefix:
return get_dbt_macro_name(name)
else:
return name
def get_docs_macro_name(docs_name, with_prefix=True):
return get_dbt_docs_name(docs_name) if with_prefix else docs_name


def get_docs_macro_name(docs_name, with_prefix=True):
if with_prefix:
return get_dbt_docs_name(docs_name)
else:
return docs_name
def get_test_macro_name(test_name, with_prefix=True):
name = f'test_{test_name}'
return get_dbt_macro_name(name) if with_prefix else name


def split_path(path):
Expand Down