dvt-core 1.11.0b4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dvt-core might be problematic. Click here for more details.

Files changed (261) hide show
  1. dvt/__init__.py +7 -0
  2. dvt/_pydantic_shim.py +26 -0
  3. dvt/adapters/__init__.py +16 -0
  4. dvt/adapters/multi_adapter_manager.py +268 -0
  5. dvt/artifacts/__init__.py +0 -0
  6. dvt/artifacts/exceptions/__init__.py +1 -0
  7. dvt/artifacts/exceptions/schemas.py +31 -0
  8. dvt/artifacts/resources/__init__.py +116 -0
  9. dvt/artifacts/resources/base.py +68 -0
  10. dvt/artifacts/resources/types.py +93 -0
  11. dvt/artifacts/resources/v1/analysis.py +10 -0
  12. dvt/artifacts/resources/v1/catalog.py +23 -0
  13. dvt/artifacts/resources/v1/components.py +275 -0
  14. dvt/artifacts/resources/v1/config.py +282 -0
  15. dvt/artifacts/resources/v1/documentation.py +11 -0
  16. dvt/artifacts/resources/v1/exposure.py +52 -0
  17. dvt/artifacts/resources/v1/function.py +53 -0
  18. dvt/artifacts/resources/v1/generic_test.py +32 -0
  19. dvt/artifacts/resources/v1/group.py +22 -0
  20. dvt/artifacts/resources/v1/hook.py +11 -0
  21. dvt/artifacts/resources/v1/macro.py +30 -0
  22. dvt/artifacts/resources/v1/metric.py +173 -0
  23. dvt/artifacts/resources/v1/model.py +146 -0
  24. dvt/artifacts/resources/v1/owner.py +10 -0
  25. dvt/artifacts/resources/v1/saved_query.py +112 -0
  26. dvt/artifacts/resources/v1/seed.py +42 -0
  27. dvt/artifacts/resources/v1/semantic_layer_components.py +72 -0
  28. dvt/artifacts/resources/v1/semantic_model.py +315 -0
  29. dvt/artifacts/resources/v1/singular_test.py +14 -0
  30. dvt/artifacts/resources/v1/snapshot.py +92 -0
  31. dvt/artifacts/resources/v1/source_definition.py +85 -0
  32. dvt/artifacts/resources/v1/sql_operation.py +10 -0
  33. dvt/artifacts/resources/v1/unit_test_definition.py +78 -0
  34. dvt/artifacts/schemas/__init__.py +0 -0
  35. dvt/artifacts/schemas/base.py +191 -0
  36. dvt/artifacts/schemas/batch_results.py +24 -0
  37. dvt/artifacts/schemas/catalog/__init__.py +12 -0
  38. dvt/artifacts/schemas/catalog/v1/__init__.py +0 -0
  39. dvt/artifacts/schemas/catalog/v1/catalog.py +60 -0
  40. dvt/artifacts/schemas/freshness/__init__.py +1 -0
  41. dvt/artifacts/schemas/freshness/v3/__init__.py +0 -0
  42. dvt/artifacts/schemas/freshness/v3/freshness.py +159 -0
  43. dvt/artifacts/schemas/manifest/__init__.py +2 -0
  44. dvt/artifacts/schemas/manifest/v12/__init__.py +0 -0
  45. dvt/artifacts/schemas/manifest/v12/manifest.py +212 -0
  46. dvt/artifacts/schemas/results.py +148 -0
  47. dvt/artifacts/schemas/run/__init__.py +2 -0
  48. dvt/artifacts/schemas/run/v5/__init__.py +0 -0
  49. dvt/artifacts/schemas/run/v5/run.py +184 -0
  50. dvt/artifacts/schemas/upgrades/__init__.py +4 -0
  51. dvt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
  52. dvt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
  53. dvt/artifacts/utils/validation.py +153 -0
  54. dvt/cli/__init__.py +1 -0
  55. dvt/cli/context.py +16 -0
  56. dvt/cli/exceptions.py +56 -0
  57. dvt/cli/flags.py +558 -0
  58. dvt/cli/main.py +971 -0
  59. dvt/cli/option_types.py +121 -0
  60. dvt/cli/options.py +79 -0
  61. dvt/cli/params.py +803 -0
  62. dvt/cli/requires.py +478 -0
  63. dvt/cli/resolvers.py +32 -0
  64. dvt/cli/types.py +40 -0
  65. dvt/clients/__init__.py +0 -0
  66. dvt/clients/checked_load.py +82 -0
  67. dvt/clients/git.py +164 -0
  68. dvt/clients/jinja.py +206 -0
  69. dvt/clients/jinja_static.py +245 -0
  70. dvt/clients/registry.py +192 -0
  71. dvt/clients/yaml_helper.py +68 -0
  72. dvt/compilation.py +833 -0
  73. dvt/compute/__init__.py +26 -0
  74. dvt/compute/base.py +288 -0
  75. dvt/compute/engines/__init__.py +13 -0
  76. dvt/compute/engines/duckdb_engine.py +368 -0
  77. dvt/compute/engines/spark_engine.py +273 -0
  78. dvt/compute/query_analyzer.py +212 -0
  79. dvt/compute/router.py +483 -0
  80. dvt/config/__init__.py +4 -0
  81. dvt/config/catalogs.py +95 -0
  82. dvt/config/compute_config.py +406 -0
  83. dvt/config/profile.py +411 -0
  84. dvt/config/profiles_v2.py +464 -0
  85. dvt/config/project.py +893 -0
  86. dvt/config/renderer.py +232 -0
  87. dvt/config/runtime.py +491 -0
  88. dvt/config/selectors.py +209 -0
  89. dvt/config/utils.py +78 -0
  90. dvt/connectors/.gitignore +6 -0
  91. dvt/connectors/README.md +306 -0
  92. dvt/connectors/catalog.yml +217 -0
  93. dvt/connectors/download_connectors.py +300 -0
  94. dvt/constants.py +29 -0
  95. dvt/context/__init__.py +0 -0
  96. dvt/context/base.py +746 -0
  97. dvt/context/configured.py +136 -0
  98. dvt/context/context_config.py +350 -0
  99. dvt/context/docs.py +82 -0
  100. dvt/context/exceptions_jinja.py +179 -0
  101. dvt/context/macro_resolver.py +195 -0
  102. dvt/context/macros.py +171 -0
  103. dvt/context/manifest.py +73 -0
  104. dvt/context/providers.py +2198 -0
  105. dvt/context/query_header.py +14 -0
  106. dvt/context/secret.py +59 -0
  107. dvt/context/target.py +74 -0
  108. dvt/contracts/__init__.py +0 -0
  109. dvt/contracts/files.py +413 -0
  110. dvt/contracts/graph/__init__.py +0 -0
  111. dvt/contracts/graph/manifest.py +1904 -0
  112. dvt/contracts/graph/metrics.py +98 -0
  113. dvt/contracts/graph/model_config.py +71 -0
  114. dvt/contracts/graph/node_args.py +42 -0
  115. dvt/contracts/graph/nodes.py +1806 -0
  116. dvt/contracts/graph/semantic_manifest.py +233 -0
  117. dvt/contracts/graph/unparsed.py +812 -0
  118. dvt/contracts/project.py +417 -0
  119. dvt/contracts/results.py +53 -0
  120. dvt/contracts/selection.py +23 -0
  121. dvt/contracts/sql.py +86 -0
  122. dvt/contracts/state.py +69 -0
  123. dvt/contracts/util.py +46 -0
  124. dvt/deprecations.py +347 -0
  125. dvt/deps/__init__.py +0 -0
  126. dvt/deps/base.py +153 -0
  127. dvt/deps/git.py +196 -0
  128. dvt/deps/local.py +80 -0
  129. dvt/deps/registry.py +131 -0
  130. dvt/deps/resolver.py +149 -0
  131. dvt/deps/tarball.py +121 -0
  132. dvt/docs/source/_ext/dbt_click.py +118 -0
  133. dvt/docs/source/conf.py +32 -0
  134. dvt/env_vars.py +64 -0
  135. dvt/event_time/event_time.py +40 -0
  136. dvt/event_time/sample_window.py +60 -0
  137. dvt/events/__init__.py +16 -0
  138. dvt/events/base_types.py +37 -0
  139. dvt/events/core_types_pb2.py +2 -0
  140. dvt/events/logging.py +109 -0
  141. dvt/events/types.py +2534 -0
  142. dvt/exceptions.py +1487 -0
  143. dvt/flags.py +89 -0
  144. dvt/graph/__init__.py +11 -0
  145. dvt/graph/cli.py +248 -0
  146. dvt/graph/graph.py +172 -0
  147. dvt/graph/queue.py +213 -0
  148. dvt/graph/selector.py +375 -0
  149. dvt/graph/selector_methods.py +976 -0
  150. dvt/graph/selector_spec.py +223 -0
  151. dvt/graph/thread_pool.py +18 -0
  152. dvt/hooks.py +21 -0
  153. dvt/include/README.md +49 -0
  154. dvt/include/__init__.py +3 -0
  155. dvt/include/global_project.py +4 -0
  156. dvt/include/starter_project/.gitignore +4 -0
  157. dvt/include/starter_project/README.md +15 -0
  158. dvt/include/starter_project/__init__.py +3 -0
  159. dvt/include/starter_project/analyses/.gitkeep +0 -0
  160. dvt/include/starter_project/dvt_project.yml +36 -0
  161. dvt/include/starter_project/macros/.gitkeep +0 -0
  162. dvt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  163. dvt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  164. dvt/include/starter_project/models/example/schema.yml +21 -0
  165. dvt/include/starter_project/seeds/.gitkeep +0 -0
  166. dvt/include/starter_project/snapshots/.gitkeep +0 -0
  167. dvt/include/starter_project/tests/.gitkeep +0 -0
  168. dvt/internal_deprecations.py +27 -0
  169. dvt/jsonschemas/__init__.py +3 -0
  170. dvt/jsonschemas/jsonschemas.py +309 -0
  171. dvt/jsonschemas/project/0.0.110.json +4717 -0
  172. dvt/jsonschemas/project/0.0.85.json +2015 -0
  173. dvt/jsonschemas/resources/0.0.110.json +2636 -0
  174. dvt/jsonschemas/resources/0.0.85.json +2536 -0
  175. dvt/jsonschemas/resources/latest.json +6773 -0
  176. dvt/links.py +4 -0
  177. dvt/materializations/__init__.py +0 -0
  178. dvt/materializations/incremental/__init__.py +0 -0
  179. dvt/materializations/incremental/microbatch.py +235 -0
  180. dvt/mp_context.py +8 -0
  181. dvt/node_types.py +37 -0
  182. dvt/parser/__init__.py +23 -0
  183. dvt/parser/analysis.py +21 -0
  184. dvt/parser/base.py +549 -0
  185. dvt/parser/common.py +267 -0
  186. dvt/parser/docs.py +52 -0
  187. dvt/parser/fixtures.py +51 -0
  188. dvt/parser/functions.py +30 -0
  189. dvt/parser/generic_test.py +100 -0
  190. dvt/parser/generic_test_builders.py +334 -0
  191. dvt/parser/hooks.py +119 -0
  192. dvt/parser/macros.py +137 -0
  193. dvt/parser/manifest.py +2204 -0
  194. dvt/parser/models.py +574 -0
  195. dvt/parser/partial.py +1179 -0
  196. dvt/parser/read_files.py +445 -0
  197. dvt/parser/schema_generic_tests.py +423 -0
  198. dvt/parser/schema_renderer.py +111 -0
  199. dvt/parser/schema_yaml_readers.py +936 -0
  200. dvt/parser/schemas.py +1467 -0
  201. dvt/parser/search.py +149 -0
  202. dvt/parser/seeds.py +28 -0
  203. dvt/parser/singular_test.py +20 -0
  204. dvt/parser/snapshots.py +44 -0
  205. dvt/parser/sources.py +557 -0
  206. dvt/parser/sql.py +63 -0
  207. dvt/parser/unit_tests.py +622 -0
  208. dvt/plugins/__init__.py +20 -0
  209. dvt/plugins/contracts.py +10 -0
  210. dvt/plugins/exceptions.py +2 -0
  211. dvt/plugins/manager.py +164 -0
  212. dvt/plugins/manifest.py +21 -0
  213. dvt/profiler.py +20 -0
  214. dvt/py.typed +1 -0
  215. dvt/runners/__init__.py +2 -0
  216. dvt/runners/exposure_runner.py +7 -0
  217. dvt/runners/no_op_runner.py +46 -0
  218. dvt/runners/saved_query_runner.py +7 -0
  219. dvt/selected_resources.py +8 -0
  220. dvt/task/__init__.py +0 -0
  221. dvt/task/base.py +504 -0
  222. dvt/task/build.py +197 -0
  223. dvt/task/clean.py +57 -0
  224. dvt/task/clone.py +162 -0
  225. dvt/task/compile.py +151 -0
  226. dvt/task/compute.py +366 -0
  227. dvt/task/debug.py +650 -0
  228. dvt/task/deps.py +280 -0
  229. dvt/task/docs/__init__.py +3 -0
  230. dvt/task/docs/generate.py +408 -0
  231. dvt/task/docs/index.html +250 -0
  232. dvt/task/docs/serve.py +28 -0
  233. dvt/task/freshness.py +323 -0
  234. dvt/task/function.py +122 -0
  235. dvt/task/group_lookup.py +46 -0
  236. dvt/task/init.py +374 -0
  237. dvt/task/list.py +237 -0
  238. dvt/task/printer.py +176 -0
  239. dvt/task/profiles.py +256 -0
  240. dvt/task/retry.py +175 -0
  241. dvt/task/run.py +1146 -0
  242. dvt/task/run_operation.py +142 -0
  243. dvt/task/runnable.py +802 -0
  244. dvt/task/seed.py +104 -0
  245. dvt/task/show.py +150 -0
  246. dvt/task/snapshot.py +57 -0
  247. dvt/task/sql.py +111 -0
  248. dvt/task/test.py +464 -0
  249. dvt/tests/fixtures/__init__.py +1 -0
  250. dvt/tests/fixtures/project.py +620 -0
  251. dvt/tests/util.py +651 -0
  252. dvt/tracking.py +529 -0
  253. dvt/utils/__init__.py +3 -0
  254. dvt/utils/artifact_upload.py +151 -0
  255. dvt/utils/utils.py +408 -0
  256. dvt/version.py +249 -0
  257. dvt_core-1.11.0b4.dist-info/METADATA +252 -0
  258. dvt_core-1.11.0b4.dist-info/RECORD +261 -0
  259. dvt_core-1.11.0b4.dist-info/WHEEL +5 -0
  260. dvt_core-1.11.0b4.dist-info/entry_points.txt +2 -0
  261. dvt_core-1.11.0b4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,620 @@
1
+ import os
2
+ import random
3
+ from argparse import Namespace
4
+ from datetime import datetime, timezone
5
+ from pathlib import Path
6
+ from typing import Dict, Mapping
7
+
8
+ import dvt.flags as flags
9
+ import pytest # type: ignore
10
+ import yaml
11
+ from dvt.config.runtime import RuntimeConfig
12
+ from dvt.context.providers import generate_runtime_macro_context
13
+ from dvt.deprecations import reset_deprecations
14
+ from dvt.events.logging import setup_event_logger
15
+ from dvt.mp_context import get_mp_context
16
+ from dvt.parser.manifest import ManifestLoader
17
+ from dvt.tests.util import (
18
+ TestProcessingException,
19
+ get_connection,
20
+ run_sql_with_adapter,
21
+ write_file,
22
+ )
23
+
24
+ from dbt.adapters.factory import (
25
+ get_adapter,
26
+ get_adapter_by_type,
27
+ register_adapter,
28
+ reset_adapters,
29
+ )
30
+ from dbt_common.context import set_invocation_context
31
+ from dbt_common.events.event_manager_client import cleanup_event_logger
32
+ from dbt_common.exceptions import CompilationError, DbtDatabaseError
33
+ from dbt_common.tests import enable_test_caching
34
+
35
+ # These are the fixtures that are used in dbt core functional tests
36
+ #
37
+ # The main functional test fixture is the 'project' fixture, which combines
38
+ # other fixtures, writes out a dbt project in a temporary directory, creates a temp
39
+ # schema in the testing database, and returns a `TestProjInfo` object that
40
+ # contains information from the other fixtures for convenience.
41
+ #
42
+ # The models, macros, seeds, snapshots, tests, and analyses fixtures all
43
+ # represent directories in a dbt project, and are all dictionaries with
44
+ # file name keys and file contents values.
45
+ #
46
+ # The other commonly used fixture is 'project_config_update'. Other
47
+ # occasionally used fixtures are 'profiles_config_update', 'packages',
48
+ # and 'selectors'.
49
+ #
50
+ # Most test cases have fairly small files which are best included in
51
+ # the test case file itself as string variables, to make it easy to
52
+ # understand what is happening in the test. Files which are used
53
+ # in multiple test case files can be included in a common file, such as
54
+ # files.py or fixtures.py. Large files, such as seed files, which would
55
+ # just clutter the test file can be pulled in from 'data' subdirectories
56
+ # in the test directory.
57
+ #
58
+ # Test logs are written in the 'logs' directory in the root of the repo.
59
+ # Every test case writes to a log directory with the same 'prefix' as the
60
+ # test's unique schema.
61
+ #
62
+ # These fixture have "class" scope. Class scope fixtures can be used both
63
+ # in classes and in single test functions (which act as classes for this
64
+ # purpose). Pytest will collect all classes starting with 'Test', so if
65
+ # you have a class that you want to be subclassed, it's generally best to
66
+ # not start the class name with 'Test'. All standalone functions starting with
67
+ # 'test_' and methods in classes starting with 'test_' (in classes starting
68
+ # with 'Test') will be collected.
69
+ #
70
+ # Please see the pytest docs for further information:
71
+ # https://docs.pytest.org
72
+
73
+
74
+ # Used in constructing the unique_schema and logs_dir
75
+ @pytest.fixture(scope="class")
76
+ def prefix():
77
+ # create a directory name that will be unique per test session
78
+ _randint = random.randint(0, 9999)
79
+ _runtime_timedelta = datetime.now(timezone.utc).replace(tzinfo=None) - datetime(
80
+ 1970, 1, 1, 0, 0, 0
81
+ )
82
+ _runtime = (int(_runtime_timedelta.total_seconds() * 1e6)) + _runtime_timedelta.microseconds
83
+ prefix = f"test{_runtime}{_randint:04}"
84
+ return prefix
85
+
86
+
87
+ # Every test has a unique schema
88
+ @pytest.fixture(scope="class")
89
+ def unique_schema(request, prefix) -> str:
90
+ test_file = request.module.__name__
91
+ # We only want the last part of the name
92
+ test_file = test_file.split(".")[-1]
93
+ unique_schema = f"{prefix}_{test_file}"
94
+ return unique_schema
95
+
96
+
97
+ # Create a directory for the profile using tmpdir fixture
98
+ @pytest.fixture(scope="class")
99
+ def profiles_root(tmpdir_factory):
100
+ return tmpdir_factory.mktemp("profile")
101
+
102
+
103
+ # Create a directory for the project using tmpdir fixture
104
+ @pytest.fixture(scope="class")
105
+ def project_root(tmpdir_factory):
106
+ # tmpdir docs - https://docs.pytest.org/en/6.2.x/tmpdir.html
107
+ project_root = tmpdir_factory.mktemp("project")
108
+ print(f"\n=== Test project_root: {project_root}")
109
+ return project_root
110
+
111
+
112
+ # This is for data used by multiple tests, in the 'tests/data' directory
113
+ @pytest.fixture(scope="session")
114
+ def shared_data_dir(request):
115
+ return os.path.join(request.config.rootdir, "tests", "data")
116
+
117
+
118
+ # This is for data for a specific test directory, i.e. tests/basic/data
119
+ @pytest.fixture(scope="module")
120
+ def test_data_dir(request):
121
+ return os.path.join(request.fspath.dirname, "data")
122
+
123
+
124
+ # This contains the profile target information, for simplicity in setting
125
+ # up different profiles, particularly in the adapter repos.
126
+ # Note: because we load the profile to create the adapter, this
127
+ # fixture can't be used to test vars and env_vars or errors. The
128
+ # profile must be written out after the test starts.
129
+ @pytest.fixture(scope="class")
130
+ def dbt_profile_target():
131
+ return {
132
+ "type": "postgres",
133
+ "threads": 4,
134
+ "host": "localhost",
135
+ "port": int(os.getenv("POSTGRES_TEST_PORT", 5432)),
136
+ "user": os.getenv("POSTGRES_TEST_USER", "root"),
137
+ "pass": os.getenv("POSTGRES_TEST_PASS", "password"),
138
+ "dbname": os.getenv("POSTGRES_TEST_DATABASE", "dbt"),
139
+ }
140
+
141
+
142
+ @pytest.fixture(scope="class")
143
+ def profile_user(dbt_profile_target):
144
+ return dbt_profile_target["user"]
145
+
146
+
147
+ # This fixture can be overridden in a project. The data provided in this
148
+ # fixture will be merged into the default project dictionary via a python 'update'.
149
+ @pytest.fixture(scope="class")
150
+ def profiles_config_update():
151
+ return {}
152
+
153
+
154
+ # The profile dictionary, used to write out profiles.yml. It will pull in updates
155
+ # from two separate sources, the 'profile_target' and 'profiles_config_update'.
156
+ # The second one is useful when using alternative targets, etc.
157
+ @pytest.fixture(scope="class")
158
+ def dbt_profile_data(unique_schema, dbt_profile_target, profiles_config_update):
159
+ profile = {
160
+ "test": {
161
+ "outputs": {
162
+ "default": {},
163
+ },
164
+ "target": "default",
165
+ },
166
+ }
167
+ target = dbt_profile_target
168
+ target["schema"] = unique_schema
169
+ profile["test"]["outputs"]["default"] = target
170
+
171
+ if profiles_config_update:
172
+ profile.update(profiles_config_update)
173
+ return profile
174
+
175
+
176
+ # Write out the profile data as a yaml file
177
+ @pytest.fixture(scope="class")
178
+ def profiles_yml(profiles_root, dbt_profile_data):
179
+ os.environ["DBT_PROFILES_DIR"] = str(profiles_root)
180
+ write_file(yaml.safe_dump(dbt_profile_data), profiles_root, "profiles.yml")
181
+ yield dbt_profile_data
182
+ del os.environ["DBT_PROFILES_DIR"]
183
+
184
+
185
+ # Data used to update the dbt_project config data.
186
+ @pytest.fixture(scope="class")
187
+ def project_config_update():
188
+ return {}
189
+
190
+
191
+ # Combines the project_config_update dictionary with project_config defaults to
192
+ # produce a project_yml config and write it out as dbt_project.yml
193
+ @pytest.fixture(scope="class")
194
+ def dbt_project_yml(project_root, project_config_update):
195
+ project_config = {
196
+ "name": "test",
197
+ "profile": "test",
198
+ "flags": {"send_anonymous_usage_stats": False},
199
+ }
200
+ if project_config_update:
201
+ if isinstance(project_config_update, dict):
202
+ project_config.update(project_config_update)
203
+ elif isinstance(project_config_update, str):
204
+ updates = yaml.safe_load(project_config_update)
205
+ project_config.update(updates)
206
+ write_file(yaml.safe_dump(project_config), project_root, "dbt_project.yml")
207
+ return project_config
208
+
209
+
210
+ # Fixture to provide dependencies
211
+ @pytest.fixture(scope="class")
212
+ def dependencies():
213
+ return {}
214
+
215
+
216
+ # Write out the dependencies.yml file
217
+ # Write out the packages.yml file
218
+ @pytest.fixture(scope="class")
219
+ def dependencies_yml(project_root, dependencies):
220
+ if dependencies:
221
+ if isinstance(dependencies, str):
222
+ data = dependencies
223
+ else:
224
+ data = yaml.safe_dump(dependencies)
225
+ write_file(data, project_root, "dependencies.yml")
226
+
227
+
228
+ # Fixture to provide packages as either yaml or dictionary
229
+ @pytest.fixture(scope="class")
230
+ def packages():
231
+ return {}
232
+
233
+
234
+ # Write out the packages.yml file
235
+ @pytest.fixture(scope="class")
236
+ def packages_yml(project_root, packages):
237
+ if packages:
238
+ if isinstance(packages, str):
239
+ data = packages
240
+ else:
241
+ data = yaml.safe_dump(packages)
242
+ write_file(data, project_root, "packages.yml")
243
+
244
+
245
+ # Fixture to provide selectors as either yaml or dictionary
246
+ @pytest.fixture(scope="class")
247
+ def selectors():
248
+ return {}
249
+
250
+
251
+ # Write out the selectors.yml file
252
+ @pytest.fixture(scope="class")
253
+ def selectors_yml(project_root, selectors):
254
+ if selectors:
255
+ if isinstance(selectors, str):
256
+ data = selectors
257
+ else:
258
+ data = yaml.safe_dump(selectors)
259
+ write_file(data, project_root, "selectors.yml")
260
+
261
+
262
+ # This fixture ensures that the logging infrastructure does not accidentally
263
+ # reuse streams configured on previous test runs, which might now be closed.
264
+ # It should be run before (and so included as a parameter by) any other fixture
265
+ # which runs dbt-core functions that might fire events.
266
+ @pytest.fixture(scope="class")
267
+ def clean_up_logging():
268
+ cleanup_event_logger()
269
+
270
+
271
+ # This creates an adapter that is used for running test setup, such as creating
272
+ # the test schema, and sql commands that are run in tests prior to the first
273
+ # dbt command. After a dbt command is run, the project.adapter property will
274
+ # return the current adapter (for this adapter type) from the adapter factory.
275
+ # The adapter produced by this fixture will contain the "base" macros (not including
276
+ # macros from dependencies).
277
+ #
278
+ # Anything used here must be actually working (dbt_project, profile, project and internal macros),
279
+ # otherwise this will fail. So to test errors in those areas, you need to copy the files
280
+ # into the project in the tests instead of putting them in the fixtures.
281
+ @pytest.fixture(scope="class")
282
+ def adapter(
283
+ logs_dir,
284
+ unique_schema,
285
+ project_root,
286
+ profiles_root,
287
+ profiles_yml,
288
+ clean_up_logging,
289
+ dbt_project_yml,
290
+ ):
291
+ # The profiles.yml and dbt_project.yml should already be written out
292
+ args = Namespace(
293
+ profiles_dir=str(profiles_root),
294
+ project_dir=str(project_root),
295
+ target=None,
296
+ profile=None,
297
+ threads=None,
298
+ )
299
+ flags.set_from_args(args, {})
300
+ runtime_config = RuntimeConfig.from_args(args)
301
+ register_adapter(runtime_config, get_mp_context())
302
+ adapter = get_adapter(runtime_config)
303
+ # We only need the base macros, not macros from dependencies, and don't want
304
+ # to run 'dbt deps' here.
305
+ manifest = ManifestLoader.load_macros(
306
+ runtime_config,
307
+ adapter.connections.set_query_header,
308
+ base_macros_only=True,
309
+ )
310
+
311
+ adapter.set_macro_resolver(manifest)
312
+ adapter.set_macro_context_generator(generate_runtime_macro_context)
313
+ yield adapter
314
+ adapter.cleanup_connections()
315
+ reset_adapters()
316
+
317
+
318
+ # Start at directory level.
319
+ def write_project_files(project_root, dir_name, file_dict):
320
+ path = project_root.mkdir(dir_name)
321
+ if file_dict:
322
+ write_project_files_recursively(path, file_dict)
323
+
324
+
325
+ # Write files out from file_dict. Can be nested directories...
326
+ def write_project_files_recursively(path, file_dict):
327
+ if type(file_dict) is not dict:
328
+ raise TestProcessingException(f"File dict is not a dict: '{file_dict}' for path '{path}'")
329
+ suffix_list = [".sql", ".csv", ".md", ".txt", ".py"]
330
+ for name, value in file_dict.items():
331
+ if name.endswith(".yml") or name.endswith(".yaml"):
332
+ if isinstance(value, str):
333
+ data = value
334
+ else:
335
+ data = yaml.safe_dump(value)
336
+ write_file(data, path, name)
337
+ elif name.endswith(tuple(suffix_list)):
338
+ write_file(value, path, name)
339
+ else:
340
+ write_project_files_recursively(path.mkdir(name), value)
341
+
342
+
343
+ # models, macros, seeds, snapshots, tests, analyses
344
+ # Provide a dictionary of file names to contents. Nested directories
345
+ # are handle by nested dictionaries.
346
+
347
+
348
+ # models directory
349
+ @pytest.fixture(scope="class")
350
+ def models():
351
+ return {}
352
+
353
+
354
+ # macros directory
355
+ @pytest.fixture(scope="class")
356
+ def macros():
357
+ return {}
358
+
359
+
360
+ # properties directory
361
+ @pytest.fixture(scope="class")
362
+ def properties():
363
+ return {}
364
+
365
+
366
+ # seeds directory
367
+ @pytest.fixture(scope="class")
368
+ def seeds():
369
+ return {}
370
+
371
+
372
+ # snapshots directory
373
+ @pytest.fixture(scope="class")
374
+ def snapshots():
375
+ return {}
376
+
377
+
378
+ # tests directory
379
+ @pytest.fixture(scope="class")
380
+ def tests():
381
+ return {}
382
+
383
+
384
+ # analyses directory
385
+ @pytest.fixture(scope="class")
386
+ def analyses():
387
+ return {}
388
+
389
+
390
+ @pytest.fixture(scope="class")
391
+ def functions() -> Dict[str, str]:
392
+ return {}
393
+
394
+
395
+ # Write out the files provided by models, macros, properties, snapshots, seeds, tests, analyses
396
+ @pytest.fixture(scope="class")
397
+ def project_files(
398
+ project_root,
399
+ models,
400
+ macros,
401
+ snapshots,
402
+ properties,
403
+ seeds,
404
+ tests,
405
+ analyses,
406
+ functions,
407
+ selectors_yml,
408
+ dependencies_yml,
409
+ packages_yml,
410
+ dbt_project_yml,
411
+ ):
412
+ write_project_files(project_root, "models", {**models, **properties})
413
+ write_project_files(project_root, "macros", macros)
414
+ write_project_files(project_root, "snapshots", snapshots)
415
+ write_project_files(project_root, "seeds", seeds)
416
+ write_project_files(project_root, "tests", tests)
417
+ write_project_files(project_root, "analyses", analyses)
418
+ write_project_files(project_root, "functions", functions)
419
+
420
+
421
+ # We have a separate logs dir for every test
422
+ @pytest.fixture(scope="class")
423
+ def logs_dir(request, prefix):
424
+ dbt_log_dir = os.path.join(request.config.rootdir, "logs", prefix)
425
+ os.environ["DBT_LOG_PATH"] = str(dbt_log_dir)
426
+ yield str(Path(dbt_log_dir))
427
+ del os.environ["DBT_LOG_PATH"]
428
+
429
+
430
+ # This fixture is for customizing tests that need overrides in adapter
431
+ # repos. Example in tests.functional.adapter.basic.test_base.
432
+ @pytest.fixture(scope="class")
433
+ def test_config():
434
+ return {}
435
+
436
+
437
+ # This class is returned from the 'project' fixture, and contains information
438
+ # from the pytest fixtures that may be needed in the test functions, including
439
+ # a 'run_sql' method.
440
+ class TestProjInfo:
441
+ __test__ = False
442
+
443
+ def __init__(
444
+ self,
445
+ project_root,
446
+ profiles_dir,
447
+ adapter_type,
448
+ test_dir,
449
+ shared_data_dir,
450
+ test_data_dir,
451
+ test_schema,
452
+ database,
453
+ test_config,
454
+ ):
455
+ self.project_root = project_root
456
+ self.profiles_dir = profiles_dir
457
+ self.adapter_type = adapter_type
458
+ self.test_dir = test_dir
459
+ self.shared_data_dir = shared_data_dir
460
+ self.test_data_dir = test_data_dir
461
+ self.test_schema = test_schema
462
+ self.database = database
463
+ self.test_config = test_config
464
+ self.created_schemas = []
465
+
466
+ @property
467
+ def adapter(self):
468
+ # This returns the last created "adapter" from the adapter factory. Each
469
+ # dbt command will create a new one. This allows us to avoid patching the
470
+ # providers 'get_adapter' function.
471
+ return get_adapter_by_type(self.adapter_type)
472
+
473
+ # Run sql from a path
474
+ def run_sql_file(self, sql_path, fetch=None):
475
+ with open(sql_path, "r") as f:
476
+ statements = f.read().split(";")
477
+ for statement in statements:
478
+ self.run_sql(statement, fetch)
479
+
480
+ # Run sql from a string, using adapter saved at test startup
481
+ def run_sql(self, sql, fetch=None):
482
+ return run_sql_with_adapter(self.adapter, sql, fetch=fetch)
483
+
484
+ # Create the unique test schema. Used in test setup, so that we're
485
+ # ready for initial sql prior to a run_dbt command.
486
+ def create_test_schema(self, schema_name=None):
487
+ if schema_name is None:
488
+ schema_name = self.test_schema
489
+ with get_connection(self.adapter):
490
+ relation = self.adapter.Relation.create(database=self.database, schema=schema_name)
491
+ self.adapter.create_schema(relation)
492
+ self.created_schemas.append(schema_name)
493
+
494
+ # Drop the unique test schema, usually called in test cleanup
495
+ def drop_test_schema(self):
496
+ if self.adapter.get_macro_resolver() is None:
497
+ manifest = ManifestLoader.load_macros(
498
+ self.adapter.config,
499
+ self.adapter.connections.set_query_header,
500
+ base_macros_only=True,
501
+ )
502
+ self.adapter.set_macro_resolver(manifest)
503
+
504
+ with get_connection(self.adapter):
505
+ for schema_name in self.created_schemas:
506
+ relation = self.adapter.Relation.create(database=self.database, schema=schema_name)
507
+ self.adapter.drop_schema(relation)
508
+ self.created_schemas = []
509
+
510
+ # This return a dictionary of table names to 'view' or 'table' values.
511
+ def get_tables_in_schema(self):
512
+ sql = """
513
+ select table_name,
514
+ case when table_type = 'BASE TABLE' then 'table'
515
+ when table_type = 'VIEW' then 'view'
516
+ else table_type
517
+ end as materialization
518
+ from information_schema.tables
519
+ where {}
520
+ order by table_name
521
+ """
522
+ sql = sql.format("{} ilike '{}'".format("table_schema", self.test_schema))
523
+ result = self.run_sql(sql, fetch="all")
524
+ return {model_name: materialization for (model_name, materialization) in result}
525
+
526
+
527
+ @pytest.fixture(scope="class")
528
+ def environment() -> Mapping[str, str]:
529
+ # By default, fixture initialization is done with the following environment
530
+ # from the os, but this fixture provides a way to customize the environment.
531
+ return os.environ
532
+
533
+
534
+ # Housekeeping that needs to be done before we start setting up any test fixtures.
535
+ @pytest.fixture(scope="class")
536
+ def initialization(environment) -> None:
537
+ # Create an "invocation context," which dbt application code relies on.
538
+ set_invocation_context(environment)
539
+
540
+ # Enable caches used between test runs, for better testing performance.
541
+ enable_test_caching()
542
+
543
+
544
+ @pytest.fixture(scope="class")
545
+ def project_setup(
546
+ initialization,
547
+ clean_up_logging,
548
+ project_root,
549
+ profiles_root,
550
+ request,
551
+ unique_schema,
552
+ profiles_yml,
553
+ adapter,
554
+ shared_data_dir,
555
+ test_data_dir,
556
+ logs_dir,
557
+ test_config,
558
+ ):
559
+ log_flags = Namespace(
560
+ LOG_PATH=logs_dir,
561
+ LOG_FORMAT="json",
562
+ LOG_FORMAT_FILE="json",
563
+ USE_COLORS=False,
564
+ USE_COLORS_FILE=False,
565
+ LOG_LEVEL="info",
566
+ LOG_LEVEL_FILE="debug",
567
+ DEBUG=False,
568
+ LOG_CACHE_EVENTS=False,
569
+ QUIET=False,
570
+ LOG_FILE_MAX_BYTES=1000000,
571
+ )
572
+ setup_event_logger(log_flags)
573
+ orig_cwd = os.getcwd()
574
+ os.chdir(project_root)
575
+ # Return whatever is needed later in tests but can only come from fixtures, so we can keep
576
+ # the signatures in the test signature to a minimum.
577
+ project = TestProjInfo(
578
+ project_root=project_root,
579
+ profiles_dir=profiles_root,
580
+ adapter_type=adapter.type(),
581
+ test_dir=request.fspath.dirname,
582
+ shared_data_dir=shared_data_dir,
583
+ test_data_dir=test_data_dir,
584
+ test_schema=unique_schema,
585
+ database=adapter.config.credentials.database,
586
+ test_config=test_config,
587
+ )
588
+ project.drop_test_schema()
589
+ project.create_test_schema()
590
+
591
+ yield project
592
+
593
+ # deps, debug and clean commands will not have an installed adapter when running and will raise
594
+ # a KeyError here. Just pass for now.
595
+ # See https://github.com/dbt-labs/dbt-core/issues/5041
596
+ # The debug command also results in an AttributeError since `Profile` doesn't have
597
+ # a `load_dependencies` method.
598
+ # Macros gets executed as part of drop_scheme in core/dbt/adapters/sql/impl.py. When
599
+ # the macros have errors (which is what we're actually testing for...) they end up
600
+ # throwing CompilationErrorss or DatabaseErrors
601
+ try:
602
+ project.drop_test_schema()
603
+ except (KeyError, AttributeError, CompilationError, DbtDatabaseError):
604
+ pass
605
+ os.chdir(orig_cwd)
606
+ cleanup_event_logger()
607
+ reset_deprecations()
608
+
609
+
610
+ # This is the main fixture that is used in all functional tests. It pulls in the other
611
+ # fixtures that are necessary to set up a dbt project, and saves some of the information
612
+ # in a TestProjInfo class, which it returns, so that individual test cases do not have
613
+ # to pull in the other fixtures individually to access their information.
614
+ # The order of arguments here determine which steps runs first.
615
+ @pytest.fixture(scope="class")
616
+ def project(
617
+ project_setup: TestProjInfo,
618
+ project_files,
619
+ ):
620
+ return project_setup