Skip to content

Commit c60d6e5

Browse files
authored
Bump version: 0.5.3 → 0.5.4 (#214)
dbt 0.5.4 release
1 parent 40948fb commit c60d6e5

File tree

89 files changed

+4285
-37
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+4285
-37
lines changed

.bumpversion.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.5.3
2+
current_version = 0.5.4
33
commit = True
44
tag = True
55

.coveragerc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[report]
2+
include =
3+
dbt/*

Dockerfile

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM python
2+
3+
RUN apt-get update
4+
5+
RUN apt-get install -y python-pip netcat
6+
RUN apt-get install -y python-dev python3-dev
7+
8+
RUN pip install pip --upgrade
9+
RUN pip install virtualenv
10+
RUN pip install virtualenvwrapper
11+
12+
COPY . /usr/src/app
13+
14+
WORKDIR /usr/src/app
15+
RUN cd /usr/src/app
16+
RUN ./test/setup.sh
17+
18+

Makefile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.PHONY: test
2+
3+
changed_tests := `git status --porcelain | grep '^\( M\|A\)' | awk '{ print $$2 }' | grep '\/test_[a-zA-Z_\-\.]\+.py'`
4+
5+
test:
6+
@echo "Test run starting..."
7+
@docker-compose run test /usr/src/app/test/runner.sh
8+
9+
test-new:
10+
@echo "Test run starting..."
11+
@echo "Changed test files:"
12+
@echo "${changed_tests}"
13+
@docker-compose run test /usr/src/app/test/runner.sh ${changed_tests}

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# dbt
22
Tests: [![CircleCI](https://circleci.com/gh/analyst-collective/dbt/tree/master.svg?style=svg)](https://circleci.com/gh/analyst-collective/dbt/tree/master)
3+
4+
[![AppVeyor](https://ci.appveyor.com/api/projects/status/v01rwd3q91jnwp9m/branch/development?svg=true)](https://ci.appveyor.com/project/DrewBanin/dbt/branch/development)
5+
6+
[Coverage](https://circleci.com/api/v1/project/analyst-collective/dbt/latest/artifacts/0/$CIRCLE_ARTIFACTS/htmlcov/index.html?branch=development)
7+
8+
[Docs](http://dbt.readthedocs.io/en/master/about/overview/)
39

410
dbt (data build tool) helps analysts write reliable, modular code using a workflow that closely mirrors software development.
511

appveyor.yml

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
version: 1.0.{build}-{branch}
2+
3+
environment:
4+
# SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
5+
# /E:ON and /V:ON options are not enabled in the batch script intepreter
6+
# See: http://stackoverflow.com/a/13751649/163740
7+
CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd"
8+
TOX_ENV: "pywin"
9+
10+
matrix:
11+
- PYTHON: "C:\\Python27"
12+
PYTHON_VERSION: "2.7.8"
13+
PYTHON_ARCH: "32"
14+
15+
#- PYTHON: "C:\\Python35"
16+
# PYTHON_VERSION: "3.5.2"
17+
# PYTHON_ARCH: "32"
18+
19+
PGUSER: postgres
20+
PGPASSWORD: Password12!
21+
22+
services:
23+
- postgresql94
24+
25+
hosts:
26+
database: 127.0.0.1
27+
28+
init:
29+
- PATH=C:\Program Files\PostgreSQL\9.4\bin\;%PATH%
30+
- ps: Set-Content "c:\program files\postgresql\9.4\data\pg_hba.conf" "host all all ::1/128 trust"
31+
- ps: Add-Content "c:\program files\postgresql\9.4\data\pg_hba.conf" "host all all 127.0.0.1/32 trust"
32+
33+
install:
34+
# Download setup scripts and unzip
35+
- ps: "wget https://github.com/cloudify-cosmo/appveyor-utils/archive/master.zip -OutFile ./master.zip"
36+
- "7z e master.zip */appveyor/* -oappveyor"
37+
38+
# Install Python (from the official .msi of http://python.org) and pip when
39+
# not already installed.
40+
- "powershell ./appveyor/install.ps1"
41+
42+
# Prepend newly installed Python to the PATH of this build (this cannot be
43+
# done from inside the powershell script as it would require to restart
44+
# the parent CMD process).
45+
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
46+
47+
# Check that we have the expected version and architecture for Python
48+
- "python --version"
49+
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
50+
51+
build: false # Not a C# project, build stuff at the test step instead.
52+
53+
before_test:
54+
- "%CMD_IN_ENV% pip install tox"
55+
56+
test_script:
57+
# set up psql db
58+
- createdb dbt
59+
- psql -c "CREATE ROLE root WITH UNENCRYPTED PASSWORD 'password';" -U postgres
60+
- psql -c "ALTER ROLE root WITH LOGIN;" -U postgres
61+
- psql -c "GRANT CREATE, CONNECT ON DATABASE dbt TO root;" -U postgres
62+
63+
# this is generally a bad idea TODO
64+
- git config --system http.sslverify false
65+
66+
- "%CMD_IN_ENV% tox -e %TOX_ENV%"

circle.yml

+26-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
1+
machine:
2+
post:
3+
- pyenv global 2.7.9 3.5.0
4+
hosts:
5+
database: 127.0.0.1
6+
7+
database:
8+
override:
9+
- createdb dbt
10+
- echo "CREATE ROLE root WITH UNENCRYPTED PASSWORD 'password';" | psql -U postgres
11+
- echo "ALTER ROLE root WITH LOGIN;" | psql -U postgres
12+
- echo "GRANT SELECT, UPDATE, INSERT ON ALL TABLES IN SCHEMA dbt.* TO root;" | psql -U postgres
13+
- echo "GRANT CREATE, CONNECT ON DATABASE dbt TO root;" | psql -U postgres
14+
15+
116
dependencies:
217
pre:
3-
- sudo add-apt-repository -y ppa:fkrull/deadsnakes
4-
- sudo apt-get update
5-
- sudo apt-get install python3.5 python3.5-dev
18+
- pip install --upgrade pip setuptools || true
19+
- pip install --upgrade tox tox-pyenv
20+
override:
21+
- pyenv local 2.7.9 3.5.0
22+
23+
test:
24+
override:
25+
- sudo chown -R ubuntu:ubuntu /root/
26+
- /bin/bash -c 'cd /home/ubuntu/dbt && tox'
27+
post:
28+
- mv htmlcov $CIRCLE_ARTIFACTS/

dbt/compilation.py

+40-10
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import time
1313
import sqlparse
1414

15-
CompilableEntities = ["models", "tests", "archives", "analyses"]
15+
CompilableEntities = ["models", "data tests", "schema tests", "archives", "analyses"]
1616

1717
class Compiler(object):
1818
def __init__(self, project, create_template_class):
@@ -60,6 +60,10 @@ def project_schemas(self):
6060
source_paths = self.project.get('source-paths', [])
6161
return Source(self.project).get_schemas(source_paths)
6262

63+
def project_tests(self):
64+
source_paths = self.project.get('test-paths', [])
65+
return Source(self.project).get_tests(source_paths)
66+
6367
def analysis_sources(self, project):
6468
paths = project.get('analysis-paths', [])
6569
return Source(project).get_analyses(paths)
@@ -109,7 +113,7 @@ def model_can_reference(self, src_model, other_model):
109113
return other_model.own_project['name'] == src_model.own_project['name'] \
110114
or src_model.own_project['name'] == src_model.project['name']
111115

112-
def __ref(self, linker, ctx, model, all_models):
116+
def __ref(self, linker, ctx, model, all_models, add_dependency=True):
113117
schema = ctx['env']['schema']
114118

115119
source_model = tuple(model.fqn)
@@ -138,7 +142,7 @@ def do_ref(*args):
138142

139143
# this creates a trivial cycle -- should this be a compiler error?
140144
# we can still interpolate the name w/o making a self-cycle
141-
if source_model == other_model_fqn:
145+
if source_model == other_model_fqn or not add_dependency:
142146
pass
143147
else:
144148
linker.dependency(source_model, other_model_fqn)
@@ -163,14 +167,15 @@ def wrapped_do_ref(*args):
163167

164168
return wrapped_do_ref
165169

166-
def get_context(self, linker, model, models):
170+
def get_context(self, linker, model, models, add_dependency=False):
167171
context = self.project.context()
168172

169173
# built-ins
170-
context['ref'] = self.__ref(linker, context, model, models)
174+
context['ref'] = self.__ref(linker, context, model, models, add_dependency)
171175
context['config'] = self.__model_config(model, linker)
172176
context['this'] = This(context['env']['schema'], model.immediate_name, model.name)
173177
context['var'] = Var(model, context=context)
178+
context['target'] = self.project.get('run-target')
174179

175180
# these get re-interpolated at runtime!
176181
context['run_started_at'] = '{{ run_started_at }}'
@@ -185,15 +190,15 @@ def get_context(self, linker, model, models):
185190

186191
return context
187192

188-
def compile_model(self, linker, model, models):
193+
def compile_model(self, linker, model, models, add_dependency=True):
189194
try:
190195
fs_loader = jinja2.FileSystemLoader(searchpath=model.root_dir)
191196
jinja = jinja2.Environment(loader=fs_loader)
192197

193198
# this is a dumb jinja2 bug -- on windows, forward slashes are EXPECTED
194199
posix_filepath = '/'.join(split_path(model.rel_filepath))
195200
template = jinja.get_template(posix_filepath)
196-
context = self.get_context(linker, model, models)
201+
context = self.get_context(linker, model, models, add_dependency=add_dependency)
197202

198203
rendered = template.render(context)
199204
except jinja2.exceptions.TemplateSyntaxError as e:
@@ -329,6 +334,23 @@ def compile_schema_tests(self, linker):
329334

330335
return written_tests
331336

337+
def compile_data_tests(self, linker):
338+
tests = self.project_tests()
339+
340+
all_models = self.get_models()
341+
enabled_models = [model for model in all_models if model.is_enabled]
342+
343+
written_tests = []
344+
for data_test in tests:
345+
serialized = data_test.serialize()
346+
linker.update_node_data(tuple(data_test.fqn), serialized)
347+
query = self.compile_model(linker, data_test, enabled_models, add_dependency=False)
348+
wrapped = data_test.render(query)
349+
self.__write(data_test.build_path(), wrapped)
350+
written_tests.append(data_test)
351+
352+
return written_tests
353+
332354
def generate_macros(self, all_macros):
333355
def do_gen(ctx):
334356
macros = []
@@ -351,14 +373,20 @@ def compile_archives(self):
351373
self.write_graph_file(linker, 'archive')
352374
return all_archives
353375

376+
def get_models(self):
377+
all_models = self.model_sources(this_project=self.project)
378+
for project in dependency_projects(self.project):
379+
all_models.extend(self.model_sources(this_project=self.project, own_project=project))
380+
381+
return all_models
382+
354383
def compile(self, dry=False):
355384
linker = Linker()
356385

357-
all_models = self.model_sources(this_project=self.project)
386+
all_models = self.get_models()
358387
all_macros = self.get_macros(this_project=self.project)
359388

360389
for project in dependency_projects(self.project):
361-
all_models.extend(self.model_sources(this_project=self.project, own_project=project))
362390
all_macros.extend(self.get_macros(this_project=self.project, own_project=project))
363391

364392
self.macro_generator = self.generate_macros(all_macros)
@@ -369,6 +397,7 @@ def compile(self, dry=False):
369397

370398
# TODO : only compile schema tests for enabled models
371399
written_schema_tests = self.compile_schema_tests(linker)
400+
written_data_tests = self.compile_data_tests(linker)
372401

373402
self.validate_models_unique(compiled_models)
374403
self.validate_models_unique(written_schema_tests)
@@ -384,7 +413,8 @@ def compile(self, dry=False):
384413

385414
return {
386415
"models": len(written_models),
387-
"tests" : len(written_schema_tests),
416+
"schema tests" : len(written_schema_tests),
417+
"data tests" : len(written_data_tests),
388418
"archives": len(compiled_archives),
389419
"analyses" : len(written_analyses)
390420
}

dbt/compiled_model.py

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ def should_skip(self):
3939
def is_type(self, run_type):
4040
return self.data['dbt_run_type'] == run_type
4141

42+
def is_test_type(self, test_type):
43+
return self.data.get('dbt_test_type') == test_type
44+
4245
@property
4346
def contents(self):
4447
if self._contents is None:

dbt/main.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ def handle(args):
8787
sub.set_defaults(cls=seed_task.SeedTask, which='seed')
8888

8989
sub = subs.add_parser('test', parents=[base_subparser])
90-
sub.add_argument('--skip-test-creates', action='store_true', help="Don't create temporary views to validate model SQL")
91-
sub.add_argument('--validate', action='store_true', help='Run constraint validations from schema.yml files')
90+
sub.add_argument('--data', action='store_true', help='Run data tests defined in "tests" directory')
91+
sub.add_argument('--schema', action='store_true', help='Run constraint validations from schema.yml files')
9292
sub.add_argument('--threads', type=int, required=False, help="Specify number of threads to use while executing tests. Overrides settings in profiles.yml")
9393
sub.set_defaults(cls=test_task.TestTask, which='test')
9494

dbt/model.py

+37
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ def __repr__(self):
415415
class SchemaTest(DBTSource):
416416
test_type = "base"
417417
dbt_run_type = 'test'
418+
dbt_test_type = 'schema'
418419

419420
def __init__(self, project, target_dir, rel_filepath, model_name, options):
420421
self.schema = project.context()['env']['schema']
@@ -430,6 +431,12 @@ def fqn(self):
430431
name, _ = os.path.splitext(parts[-1])
431432
return [self.project['name']] + parts[1:-1] + ['schema', self.get_filename()]
432433

434+
def serialize(self):
435+
serialized = DBTSource.serialize(self).copy()
436+
serialized['dbt_test_type'] = self.dbt_test_type
437+
438+
return serialized
439+
433440
def get_params(self, options):
434441
return {
435442
"schema": self.schema,
@@ -644,3 +651,33 @@ def build_path(self):
644651

645652
def __repr__(self):
646653
return "<ArchiveModel {} --> {} unique:{} updated_at:{}>".format(self.source_table, self.target_table, self.unique_key, self.updated_at)
654+
655+
class DataTest(DBTSource):
656+
dbt_run_type = 'test'
657+
dbt_test_type = 'data'
658+
659+
def __init__(self, project, target_dir, rel_filepath, own_project):
660+
super(DataTest, self).__init__(project, target_dir, rel_filepath, own_project)
661+
662+
def build_path(self):
663+
build_dir = "test"
664+
filename = "{}.sql".format(self.name)
665+
fqn_parts = self.fqn[0:1] + ['data'] + self.fqn[1:-1]
666+
path_parts = [build_dir] + fqn_parts + [filename]
667+
return os.path.join(*path_parts)
668+
669+
def serialize(self):
670+
serialized = DBTSource.serialize(self).copy()
671+
serialized['dbt_test_type'] = self.dbt_test_type
672+
673+
return serialized
674+
675+
def render(self, query):
676+
return "select count(*) from (\n{}\n) sbq".format(query)
677+
678+
@property
679+
def immediate_name(self):
680+
return self.name
681+
682+
def __repr__(self):
683+
return "<DataTest {}.{}: {}>".format(self.project['name'], self.name, self.filepath)

0 commit comments

Comments
 (0)