dvt-core 0.58.6__cp311-cp311-macosx_10_9_x86_64.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.
Files changed (324) hide show
  1. dbt/__init__.py +7 -0
  2. dbt/_pydantic_shim.py +26 -0
  3. dbt/artifacts/__init__.py +0 -0
  4. dbt/artifacts/exceptions/__init__.py +1 -0
  5. dbt/artifacts/exceptions/schemas.py +31 -0
  6. dbt/artifacts/resources/__init__.py +116 -0
  7. dbt/artifacts/resources/base.py +67 -0
  8. dbt/artifacts/resources/types.py +93 -0
  9. dbt/artifacts/resources/v1/analysis.py +10 -0
  10. dbt/artifacts/resources/v1/catalog.py +23 -0
  11. dbt/artifacts/resources/v1/components.py +274 -0
  12. dbt/artifacts/resources/v1/config.py +277 -0
  13. dbt/artifacts/resources/v1/documentation.py +11 -0
  14. dbt/artifacts/resources/v1/exposure.py +51 -0
  15. dbt/artifacts/resources/v1/function.py +52 -0
  16. dbt/artifacts/resources/v1/generic_test.py +31 -0
  17. dbt/artifacts/resources/v1/group.py +21 -0
  18. dbt/artifacts/resources/v1/hook.py +11 -0
  19. dbt/artifacts/resources/v1/macro.py +29 -0
  20. dbt/artifacts/resources/v1/metric.py +172 -0
  21. dbt/artifacts/resources/v1/model.py +145 -0
  22. dbt/artifacts/resources/v1/owner.py +10 -0
  23. dbt/artifacts/resources/v1/saved_query.py +111 -0
  24. dbt/artifacts/resources/v1/seed.py +41 -0
  25. dbt/artifacts/resources/v1/semantic_layer_components.py +72 -0
  26. dbt/artifacts/resources/v1/semantic_model.py +314 -0
  27. dbt/artifacts/resources/v1/singular_test.py +14 -0
  28. dbt/artifacts/resources/v1/snapshot.py +91 -0
  29. dbt/artifacts/resources/v1/source_definition.py +84 -0
  30. dbt/artifacts/resources/v1/sql_operation.py +10 -0
  31. dbt/artifacts/resources/v1/unit_test_definition.py +77 -0
  32. dbt/artifacts/schemas/__init__.py +0 -0
  33. dbt/artifacts/schemas/base.py +191 -0
  34. dbt/artifacts/schemas/batch_results.py +24 -0
  35. dbt/artifacts/schemas/catalog/__init__.py +11 -0
  36. dbt/artifacts/schemas/catalog/v1/__init__.py +0 -0
  37. dbt/artifacts/schemas/catalog/v1/catalog.py +59 -0
  38. dbt/artifacts/schemas/freshness/__init__.py +1 -0
  39. dbt/artifacts/schemas/freshness/v3/__init__.py +0 -0
  40. dbt/artifacts/schemas/freshness/v3/freshness.py +158 -0
  41. dbt/artifacts/schemas/manifest/__init__.py +2 -0
  42. dbt/artifacts/schemas/manifest/v12/__init__.py +0 -0
  43. dbt/artifacts/schemas/manifest/v12/manifest.py +211 -0
  44. dbt/artifacts/schemas/results.py +147 -0
  45. dbt/artifacts/schemas/run/__init__.py +2 -0
  46. dbt/artifacts/schemas/run/v5/__init__.py +0 -0
  47. dbt/artifacts/schemas/run/v5/run.py +184 -0
  48. dbt/artifacts/schemas/upgrades/__init__.py +4 -0
  49. dbt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
  50. dbt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
  51. dbt/artifacts/utils/validation.py +153 -0
  52. dbt/cli/__init__.py +1 -0
  53. dbt/cli/context.py +17 -0
  54. dbt/cli/exceptions.py +57 -0
  55. dbt/cli/flags.py +560 -0
  56. dbt/cli/main.py +2403 -0
  57. dbt/cli/option_types.py +121 -0
  58. dbt/cli/options.py +80 -0
  59. dbt/cli/params.py +844 -0
  60. dbt/cli/requires.py +490 -0
  61. dbt/cli/resolvers.py +50 -0
  62. dbt/cli/types.py +40 -0
  63. dbt/clients/__init__.py +0 -0
  64. dbt/clients/checked_load.py +83 -0
  65. dbt/clients/git.py +164 -0
  66. dbt/clients/jinja.py +206 -0
  67. dbt/clients/jinja_static.py +245 -0
  68. dbt/clients/registry.py +192 -0
  69. dbt/clients/yaml_helper.py +68 -0
  70. dbt/compilation.py +876 -0
  71. dbt/compute/__init__.py +14 -0
  72. dbt/compute/engines/__init__.py +12 -0
  73. dbt/compute/engines/spark_engine.cpython-311-darwin.so +0 -0
  74. dbt/compute/engines/spark_engine.py +642 -0
  75. dbt/compute/federated_executor.cpython-311-darwin.so +0 -0
  76. dbt/compute/federated_executor.py +1080 -0
  77. dbt/compute/filter_pushdown.cpython-311-darwin.so +0 -0
  78. dbt/compute/filter_pushdown.py +273 -0
  79. dbt/compute/jar_provisioning.cpython-311-darwin.so +0 -0
  80. dbt/compute/jar_provisioning.py +255 -0
  81. dbt/compute/java_compat.cpython-311-darwin.so +0 -0
  82. dbt/compute/java_compat.py +689 -0
  83. dbt/compute/jdbc_utils.cpython-311-darwin.so +0 -0
  84. dbt/compute/jdbc_utils.py +678 -0
  85. dbt/compute/metadata/__init__.py +40 -0
  86. dbt/compute/metadata/adapters_registry.cpython-311-darwin.so +0 -0
  87. dbt/compute/metadata/adapters_registry.py +370 -0
  88. dbt/compute/metadata/registry.cpython-311-darwin.so +0 -0
  89. dbt/compute/metadata/registry.py +674 -0
  90. dbt/compute/metadata/store.cpython-311-darwin.so +0 -0
  91. dbt/compute/metadata/store.py +1499 -0
  92. dbt/compute/smart_selector.cpython-311-darwin.so +0 -0
  93. dbt/compute/smart_selector.py +377 -0
  94. dbt/compute/strategies/__init__.py +55 -0
  95. dbt/compute/strategies/base.cpython-311-darwin.so +0 -0
  96. dbt/compute/strategies/base.py +165 -0
  97. dbt/compute/strategies/dataproc.cpython-311-darwin.so +0 -0
  98. dbt/compute/strategies/dataproc.py +207 -0
  99. dbt/compute/strategies/emr.cpython-311-darwin.so +0 -0
  100. dbt/compute/strategies/emr.py +203 -0
  101. dbt/compute/strategies/local.cpython-311-darwin.so +0 -0
  102. dbt/compute/strategies/local.py +443 -0
  103. dbt/compute/strategies/standalone.cpython-311-darwin.so +0 -0
  104. dbt/compute/strategies/standalone.py +262 -0
  105. dbt/config/__init__.py +4 -0
  106. dbt/config/catalogs.py +94 -0
  107. dbt/config/compute.cpython-311-darwin.so +0 -0
  108. dbt/config/compute.py +513 -0
  109. dbt/config/dvt_profile.cpython-311-darwin.so +0 -0
  110. dbt/config/dvt_profile.py +342 -0
  111. dbt/config/profile.py +422 -0
  112. dbt/config/project.py +873 -0
  113. dbt/config/project_utils.py +28 -0
  114. dbt/config/renderer.py +231 -0
  115. dbt/config/runtime.py +553 -0
  116. dbt/config/selectors.py +208 -0
  117. dbt/config/utils.py +77 -0
  118. dbt/constants.py +28 -0
  119. dbt/context/__init__.py +0 -0
  120. dbt/context/base.py +745 -0
  121. dbt/context/configured.py +135 -0
  122. dbt/context/context_config.py +382 -0
  123. dbt/context/docs.py +82 -0
  124. dbt/context/exceptions_jinja.py +178 -0
  125. dbt/context/macro_resolver.py +195 -0
  126. dbt/context/macros.py +171 -0
  127. dbt/context/manifest.py +72 -0
  128. dbt/context/providers.py +2249 -0
  129. dbt/context/query_header.py +13 -0
  130. dbt/context/secret.py +58 -0
  131. dbt/context/target.py +74 -0
  132. dbt/contracts/__init__.py +0 -0
  133. dbt/contracts/files.py +413 -0
  134. dbt/contracts/graph/__init__.py +0 -0
  135. dbt/contracts/graph/manifest.py +1904 -0
  136. dbt/contracts/graph/metrics.py +97 -0
  137. dbt/contracts/graph/model_config.py +70 -0
  138. dbt/contracts/graph/node_args.py +42 -0
  139. dbt/contracts/graph/nodes.py +1806 -0
  140. dbt/contracts/graph/semantic_manifest.py +232 -0
  141. dbt/contracts/graph/unparsed.py +811 -0
  142. dbt/contracts/project.py +417 -0
  143. dbt/contracts/results.py +53 -0
  144. dbt/contracts/selection.py +23 -0
  145. dbt/contracts/sql.py +85 -0
  146. dbt/contracts/state.py +68 -0
  147. dbt/contracts/util.py +46 -0
  148. dbt/deprecations.py +348 -0
  149. dbt/deps/__init__.py +0 -0
  150. dbt/deps/base.py +152 -0
  151. dbt/deps/git.py +195 -0
  152. dbt/deps/local.py +79 -0
  153. dbt/deps/registry.py +130 -0
  154. dbt/deps/resolver.py +149 -0
  155. dbt/deps/tarball.py +120 -0
  156. dbt/docs/source/_ext/dbt_click.py +119 -0
  157. dbt/docs/source/conf.py +32 -0
  158. dbt/env_vars.py +64 -0
  159. dbt/event_time/event_time.py +40 -0
  160. dbt/event_time/sample_window.py +60 -0
  161. dbt/events/__init__.py +15 -0
  162. dbt/events/base_types.py +36 -0
  163. dbt/events/core_types_pb2.py +2 -0
  164. dbt/events/logging.py +108 -0
  165. dbt/events/types.py +2516 -0
  166. dbt/exceptions.py +1486 -0
  167. dbt/flags.py +89 -0
  168. dbt/graph/__init__.py +11 -0
  169. dbt/graph/cli.py +249 -0
  170. dbt/graph/graph.py +172 -0
  171. dbt/graph/queue.py +214 -0
  172. dbt/graph/selector.py +374 -0
  173. dbt/graph/selector_methods.py +975 -0
  174. dbt/graph/selector_spec.py +222 -0
  175. dbt/graph/thread_pool.py +18 -0
  176. dbt/hooks.py +21 -0
  177. dbt/include/README.md +49 -0
  178. dbt/include/__init__.py +3 -0
  179. dbt/include/data/adapters_registry.duckdb +0 -0
  180. dbt/include/data/build_registry.py +242 -0
  181. dbt/include/data/csv/adapter_queries.csv +33 -0
  182. dbt/include/data/csv/syntax_rules.csv +9 -0
  183. dbt/include/data/csv/type_mappings_bigquery.csv +28 -0
  184. dbt/include/data/csv/type_mappings_databricks.csv +30 -0
  185. dbt/include/data/csv/type_mappings_mysql.csv +40 -0
  186. dbt/include/data/csv/type_mappings_oracle.csv +30 -0
  187. dbt/include/data/csv/type_mappings_postgres.csv +56 -0
  188. dbt/include/data/csv/type_mappings_redshift.csv +33 -0
  189. dbt/include/data/csv/type_mappings_snowflake.csv +38 -0
  190. dbt/include/data/csv/type_mappings_sqlserver.csv +35 -0
  191. dbt/include/starter_project/.gitignore +4 -0
  192. dbt/include/starter_project/README.md +15 -0
  193. dbt/include/starter_project/__init__.py +3 -0
  194. dbt/include/starter_project/analyses/.gitkeep +0 -0
  195. dbt/include/starter_project/dbt_project.yml +36 -0
  196. dbt/include/starter_project/macros/.gitkeep +0 -0
  197. dbt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  198. dbt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  199. dbt/include/starter_project/models/example/schema.yml +21 -0
  200. dbt/include/starter_project/seeds/.gitkeep +0 -0
  201. dbt/include/starter_project/snapshots/.gitkeep +0 -0
  202. dbt/include/starter_project/tests/.gitkeep +0 -0
  203. dbt/internal_deprecations.py +26 -0
  204. dbt/jsonschemas/__init__.py +3 -0
  205. dbt/jsonschemas/jsonschemas.py +309 -0
  206. dbt/jsonschemas/project/0.0.110.json +4717 -0
  207. dbt/jsonschemas/project/0.0.85.json +2015 -0
  208. dbt/jsonschemas/resources/0.0.110.json +2636 -0
  209. dbt/jsonschemas/resources/0.0.85.json +2536 -0
  210. dbt/jsonschemas/resources/latest.json +6773 -0
  211. dbt/links.py +4 -0
  212. dbt/materializations/__init__.py +0 -0
  213. dbt/materializations/incremental/__init__.py +0 -0
  214. dbt/materializations/incremental/microbatch.py +236 -0
  215. dbt/mp_context.py +8 -0
  216. dbt/node_types.py +37 -0
  217. dbt/parser/__init__.py +23 -0
  218. dbt/parser/analysis.py +21 -0
  219. dbt/parser/base.py +548 -0
  220. dbt/parser/common.py +266 -0
  221. dbt/parser/docs.py +52 -0
  222. dbt/parser/fixtures.py +51 -0
  223. dbt/parser/functions.py +30 -0
  224. dbt/parser/generic_test.py +100 -0
  225. dbt/parser/generic_test_builders.py +333 -0
  226. dbt/parser/hooks.py +118 -0
  227. dbt/parser/macros.py +137 -0
  228. dbt/parser/manifest.py +2204 -0
  229. dbt/parser/models.py +573 -0
  230. dbt/parser/partial.py +1178 -0
  231. dbt/parser/read_files.py +445 -0
  232. dbt/parser/schema_generic_tests.py +422 -0
  233. dbt/parser/schema_renderer.py +111 -0
  234. dbt/parser/schema_yaml_readers.py +935 -0
  235. dbt/parser/schemas.py +1466 -0
  236. dbt/parser/search.py +149 -0
  237. dbt/parser/seeds.py +28 -0
  238. dbt/parser/singular_test.py +20 -0
  239. dbt/parser/snapshots.py +44 -0
  240. dbt/parser/sources.py +558 -0
  241. dbt/parser/sql.py +62 -0
  242. dbt/parser/unit_tests.py +621 -0
  243. dbt/plugins/__init__.py +20 -0
  244. dbt/plugins/contracts.py +9 -0
  245. dbt/plugins/exceptions.py +2 -0
  246. dbt/plugins/manager.py +163 -0
  247. dbt/plugins/manifest.py +21 -0
  248. dbt/profiler.py +20 -0
  249. dbt/py.typed +1 -0
  250. dbt/query_analyzer.cpython-311-darwin.so +0 -0
  251. dbt/query_analyzer.py +410 -0
  252. dbt/runners/__init__.py +2 -0
  253. dbt/runners/exposure_runner.py +7 -0
  254. dbt/runners/no_op_runner.py +45 -0
  255. dbt/runners/saved_query_runner.py +7 -0
  256. dbt/selected_resources.py +8 -0
  257. dbt/task/__init__.py +0 -0
  258. dbt/task/base.py +503 -0
  259. dbt/task/build.py +197 -0
  260. dbt/task/clean.py +56 -0
  261. dbt/task/clone.py +161 -0
  262. dbt/task/compile.py +150 -0
  263. dbt/task/compute.cpython-311-darwin.so +0 -0
  264. dbt/task/compute.py +458 -0
  265. dbt/task/debug.py +505 -0
  266. dbt/task/deps.py +280 -0
  267. dbt/task/docs/__init__.py +3 -0
  268. dbt/task/docs/api/__init__.py +23 -0
  269. dbt/task/docs/api/catalog.cpython-311-darwin.so +0 -0
  270. dbt/task/docs/api/catalog.py +204 -0
  271. dbt/task/docs/api/lineage.cpython-311-darwin.so +0 -0
  272. dbt/task/docs/api/lineage.py +234 -0
  273. dbt/task/docs/api/profile.cpython-311-darwin.so +0 -0
  274. dbt/task/docs/api/profile.py +204 -0
  275. dbt/task/docs/api/spark.cpython-311-darwin.so +0 -0
  276. dbt/task/docs/api/spark.py +186 -0
  277. dbt/task/docs/generate.py +947 -0
  278. dbt/task/docs/index.html +250 -0
  279. dbt/task/docs/serve.cpython-311-darwin.so +0 -0
  280. dbt/task/docs/serve.py +174 -0
  281. dbt/task/dvt_output.py +362 -0
  282. dbt/task/dvt_run.py +204 -0
  283. dbt/task/freshness.py +322 -0
  284. dbt/task/function.py +121 -0
  285. dbt/task/group_lookup.py +46 -0
  286. dbt/task/init.cpython-311-darwin.so +0 -0
  287. dbt/task/init.py +604 -0
  288. dbt/task/java.cpython-311-darwin.so +0 -0
  289. dbt/task/java.py +316 -0
  290. dbt/task/list.py +236 -0
  291. dbt/task/metadata.cpython-311-darwin.so +0 -0
  292. dbt/task/metadata.py +804 -0
  293. dbt/task/printer.py +175 -0
  294. dbt/task/profile.cpython-311-darwin.so +0 -0
  295. dbt/task/profile.py +1307 -0
  296. dbt/task/profile_serve.py +615 -0
  297. dbt/task/retract.py +438 -0
  298. dbt/task/retry.py +175 -0
  299. dbt/task/run.py +1387 -0
  300. dbt/task/run_operation.py +141 -0
  301. dbt/task/runnable.py +758 -0
  302. dbt/task/seed.py +103 -0
  303. dbt/task/show.py +149 -0
  304. dbt/task/snapshot.py +56 -0
  305. dbt/task/spark.cpython-311-darwin.so +0 -0
  306. dbt/task/spark.py +414 -0
  307. dbt/task/sql.py +110 -0
  308. dbt/task/target_sync.cpython-311-darwin.so +0 -0
  309. dbt/task/target_sync.py +766 -0
  310. dbt/task/test.py +464 -0
  311. dbt/tests/fixtures/__init__.py +1 -0
  312. dbt/tests/fixtures/project.py +620 -0
  313. dbt/tests/util.py +651 -0
  314. dbt/tracking.py +529 -0
  315. dbt/utils/__init__.py +3 -0
  316. dbt/utils/artifact_upload.py +151 -0
  317. dbt/utils/utils.py +408 -0
  318. dbt/version.py +270 -0
  319. dvt_cli/__init__.py +72 -0
  320. dvt_core-0.58.6.dist-info/METADATA +288 -0
  321. dvt_core-0.58.6.dist-info/RECORD +324 -0
  322. dvt_core-0.58.6.dist-info/WHEEL +5 -0
  323. dvt_core-0.58.6.dist-info/entry_points.txt +2 -0
  324. dvt_core-0.58.6.dist-info/top_level.txt +2 -0
@@ -0,0 +1,935 @@
1
+ from collections.abc import Sequence
2
+ from typing import Any, Dict, List, Optional, Union
3
+
4
+ from dbt.artifacts.resources import (
5
+ ConversionTypeParams,
6
+ CumulativeTypeParams,
7
+ Dimension,
8
+ DimensionTypeParams,
9
+ Entity,
10
+ Export,
11
+ ExportConfig,
12
+ ExposureConfig,
13
+ Measure,
14
+ MetricConfig,
15
+ MetricInput,
16
+ MetricInputMeasure,
17
+ MetricTimeWindow,
18
+ MetricTypeParams,
19
+ NonAdditiveDimension,
20
+ QueryParams,
21
+ SavedQueryConfig,
22
+ SemanticLayerElementConfig,
23
+ WhereFilter,
24
+ WhereFilterIntersection,
25
+ )
26
+ from dbt.clients.jinja import get_rendered
27
+ from dbt.context.context_config import (
28
+ BaseContextConfigGenerator,
29
+ ContextConfigGenerator,
30
+ UnrenderedConfigGenerator,
31
+ )
32
+ from dbt.context.providers import (
33
+ generate_parse_exposure,
34
+ generate_parse_semantic_models,
35
+ )
36
+ from dbt.contracts.files import SchemaSourceFile
37
+ from dbt.contracts.graph.nodes import Exposure, Group, Metric, SavedQuery, SemanticModel
38
+ from dbt.contracts.graph.unparsed import (
39
+ UnparsedConversionTypeParams,
40
+ UnparsedCumulativeTypeParams,
41
+ UnparsedDimension,
42
+ UnparsedDimensionTypeParams,
43
+ UnparsedEntity,
44
+ UnparsedExport,
45
+ UnparsedExposure,
46
+ UnparsedGroup,
47
+ UnparsedMeasure,
48
+ UnparsedMetric,
49
+ UnparsedMetricInput,
50
+ UnparsedMetricInputMeasure,
51
+ UnparsedMetricTypeParams,
52
+ UnparsedNonAdditiveDimension,
53
+ UnparsedQueryParams,
54
+ UnparsedSavedQuery,
55
+ UnparsedSemanticModel,
56
+ )
57
+ from dbt.exceptions import JSONValidationError, YamlParseDictError
58
+ from dbt.node_types import NodeType
59
+ from dbt.parser.common import YamlBlock
60
+ from dbt.parser.schemas import ParseResult, SchemaParser, YamlReader
61
+ from dbt_common.dataclass_schema import ValidationError
62
+ from dbt_common.exceptions import DbtInternalError
63
+ from dbt_semantic_interfaces.type_enums import (
64
+ AggregationType,
65
+ ConversionCalculationType,
66
+ DimensionType,
67
+ EntityType,
68
+ MetricType,
69
+ PeriodAggregation,
70
+ TimeGranularity,
71
+ )
72
+
73
+
74
+ def parse_where_filter(
75
+ where: Optional[Union[List[str], str]]
76
+ ) -> Optional[WhereFilterIntersection]:
77
+ if where is None:
78
+ return None
79
+ elif isinstance(where, str):
80
+ return WhereFilterIntersection([WhereFilter(where)])
81
+ else:
82
+ return WhereFilterIntersection([WhereFilter(where_str) for where_str in where])
83
+
84
+
85
+ class ExposureParser(YamlReader):
86
+ def __init__(self, schema_parser: SchemaParser, yaml: YamlBlock) -> None:
87
+ super().__init__(schema_parser, yaml, NodeType.Exposure.pluralize())
88
+ self.schema_parser = schema_parser
89
+ self.yaml = yaml
90
+
91
+ def parse_exposure(self, unparsed: UnparsedExposure) -> None:
92
+ package_name = self.project.project_name
93
+ unique_id = f"{NodeType.Exposure}.{package_name}.{unparsed.name}"
94
+ path = self.yaml.path.relative_path
95
+
96
+ fqn = self.schema_parser.get_fqn_prefix(path)
97
+ fqn.append(unparsed.name)
98
+
99
+ config = self._generate_exposure_config(
100
+ target=unparsed,
101
+ fqn=fqn,
102
+ package_name=package_name,
103
+ rendered=True,
104
+ )
105
+
106
+ config = config.finalize_and_validate()
107
+
108
+ unrendered_config = self._generate_exposure_config(
109
+ target=unparsed,
110
+ fqn=fqn,
111
+ package_name=package_name,
112
+ rendered=False,
113
+ )
114
+
115
+ if not isinstance(config, ExposureConfig):
116
+ raise DbtInternalError(
117
+ f"Calculated a {type(config)} for an exposure, but expected an ExposureConfig"
118
+ )
119
+
120
+ tags = sorted(set(self.project.exposures.get("tags", []) + unparsed.tags + config.tags))
121
+ meta = {**self.project.exposures.get("meta", {}), **unparsed.meta, **config.meta}
122
+
123
+ config.tags = tags
124
+ config.meta = meta
125
+
126
+ parsed = Exposure(
127
+ resource_type=NodeType.Exposure,
128
+ package_name=package_name,
129
+ path=path,
130
+ original_file_path=self.yaml.path.original_file_path,
131
+ unique_id=unique_id,
132
+ fqn=fqn,
133
+ name=unparsed.name,
134
+ type=unparsed.type,
135
+ url=unparsed.url,
136
+ meta=meta,
137
+ tags=tags,
138
+ description=unparsed.description,
139
+ label=unparsed.label,
140
+ owner=unparsed.owner,
141
+ maturity=unparsed.maturity,
142
+ config=config,
143
+ unrendered_config=unrendered_config,
144
+ )
145
+ ctx = generate_parse_exposure(
146
+ parsed,
147
+ self.root_project,
148
+ self.schema_parser.manifest,
149
+ package_name,
150
+ )
151
+ depends_on_jinja = "\n".join("{{ " + line + "}}" for line in unparsed.depends_on)
152
+ get_rendered(depends_on_jinja, ctx, parsed, capture_macros=True)
153
+ # parsed now has a populated refs/sources/metrics
154
+
155
+ assert isinstance(self.yaml.file, SchemaSourceFile)
156
+ if parsed.config.enabled:
157
+ self.manifest.add_exposure(self.yaml.file, parsed)
158
+ else:
159
+ self.manifest.add_disabled(self.yaml.file, parsed)
160
+
161
+ def _generate_exposure_config(
162
+ self, target: UnparsedExposure, fqn: List[str], package_name: str, rendered: bool
163
+ ):
164
+ generator: BaseContextConfigGenerator
165
+ if rendered:
166
+ generator = ContextConfigGenerator(self.root_project)
167
+ else:
168
+ generator = UnrenderedConfigGenerator(self.root_project)
169
+
170
+ # configs with precendence set
171
+ precedence_configs = dict()
172
+ # apply exposure configs
173
+ precedence_configs.update(target.config)
174
+
175
+ return generator.calculate_node_config(
176
+ config_call_dict={},
177
+ fqn=fqn,
178
+ resource_type=NodeType.Exposure,
179
+ project_name=package_name,
180
+ base=False,
181
+ patch_config_dict=precedence_configs,
182
+ )
183
+
184
+ def parse(self) -> None:
185
+ for data in self.get_key_dicts():
186
+ try:
187
+ UnparsedExposure.validate(data)
188
+ unparsed = UnparsedExposure.from_dict(data)
189
+ except (ValidationError, JSONValidationError) as exc:
190
+ raise YamlParseDictError(self.yaml.path, self.key, data, exc)
191
+
192
+ self.parse_exposure(unparsed)
193
+
194
+
195
+ class MetricParser(YamlReader):
196
+ def __init__(self, schema_parser: SchemaParser, yaml: YamlBlock) -> None:
197
+ super().__init__(schema_parser, yaml, NodeType.Metric.pluralize())
198
+ self.schema_parser = schema_parser
199
+ self.yaml = yaml
200
+
201
+ def _get_input_measure(
202
+ self,
203
+ unparsed_input_measure: Union[UnparsedMetricInputMeasure, str],
204
+ ) -> MetricInputMeasure:
205
+ if isinstance(unparsed_input_measure, str):
206
+ return MetricInputMeasure(name=unparsed_input_measure)
207
+ else:
208
+ return MetricInputMeasure(
209
+ name=unparsed_input_measure.name,
210
+ filter=parse_where_filter(unparsed_input_measure.filter),
211
+ alias=unparsed_input_measure.alias,
212
+ join_to_timespine=unparsed_input_measure.join_to_timespine,
213
+ fill_nulls_with=unparsed_input_measure.fill_nulls_with,
214
+ )
215
+
216
+ def _get_optional_input_measure(
217
+ self,
218
+ unparsed_input_measure: Optional[Union[UnparsedMetricInputMeasure, str]],
219
+ ) -> Optional[MetricInputMeasure]:
220
+ if unparsed_input_measure is not None:
221
+ return self._get_input_measure(unparsed_input_measure)
222
+ else:
223
+ return None
224
+
225
+ def _get_input_measures(
226
+ self,
227
+ unparsed_input_measures: Optional[List[Union[UnparsedMetricInputMeasure, str]]],
228
+ ) -> List[MetricInputMeasure]:
229
+ input_measures: List[MetricInputMeasure] = []
230
+ if unparsed_input_measures is not None:
231
+ for unparsed_input_measure in unparsed_input_measures:
232
+ input_measures.append(self._get_input_measure(unparsed_input_measure))
233
+
234
+ return input_measures
235
+
236
+ def _get_period_agg(self, unparsed_period_agg: str) -> PeriodAggregation:
237
+ return PeriodAggregation(unparsed_period_agg)
238
+
239
+ def _get_optional_time_window(
240
+ self, unparsed_window: Optional[str]
241
+ ) -> Optional[MetricTimeWindow]:
242
+ if unparsed_window is not None:
243
+ parts = unparsed_window.lower().split(" ")
244
+ if len(parts) != 2:
245
+ raise YamlParseDictError(
246
+ self.yaml.path,
247
+ "window",
248
+ {"window": unparsed_window},
249
+ f"Invalid window ({unparsed_window}) in cumulative/conversion metric. Should be of the form `<count> <granularity>`, "
250
+ "e.g., `28 days`",
251
+ )
252
+
253
+ granularity = parts[1]
254
+ # once we drop python 3.8 this could just be `granularity = parts[0].removesuffix('s')
255
+ if granularity.endswith("s") and granularity[:-1] in [
256
+ item.value for item in TimeGranularity
257
+ ]:
258
+ # Can only remove the `s` if it's a standard grain, months -> month
259
+ granularity = granularity[:-1]
260
+
261
+ count = parts[0]
262
+ if not count.isdigit():
263
+ raise YamlParseDictError(
264
+ self.yaml.path,
265
+ "window",
266
+ {"window": unparsed_window},
267
+ f"Invalid count ({count}) in cumulative/conversion metric window string: ({unparsed_window})",
268
+ )
269
+
270
+ return MetricTimeWindow(
271
+ count=int(count),
272
+ granularity=granularity,
273
+ )
274
+ else:
275
+ return None
276
+
277
+ def _get_metric_input(self, unparsed: Union[UnparsedMetricInput, str]) -> MetricInput:
278
+ if isinstance(unparsed, str):
279
+ return MetricInput(name=unparsed)
280
+ else:
281
+ return MetricInput(
282
+ name=unparsed.name,
283
+ filter=parse_where_filter(unparsed.filter),
284
+ alias=unparsed.alias,
285
+ offset_window=self._get_optional_time_window(unparsed.offset_window),
286
+ offset_to_grain=unparsed.offset_to_grain,
287
+ )
288
+
289
+ def _get_optional_metric_input(
290
+ self,
291
+ unparsed: Optional[Union[UnparsedMetricInput, str]],
292
+ ) -> Optional[MetricInput]:
293
+ if unparsed is not None:
294
+ return self._get_metric_input(unparsed)
295
+ else:
296
+ return None
297
+
298
+ def _get_metric_inputs(
299
+ self,
300
+ unparsed_metric_inputs: Optional[List[Union[UnparsedMetricInput, str]]],
301
+ ) -> List[MetricInput]:
302
+ metric_inputs: List[MetricInput] = []
303
+ if unparsed_metric_inputs is not None:
304
+ for unparsed_metric_input in unparsed_metric_inputs:
305
+ metric_inputs.append(self._get_metric_input(unparsed=unparsed_metric_input))
306
+
307
+ return metric_inputs
308
+
309
+ def _get_optional_conversion_type_params(
310
+ self, unparsed: Optional[UnparsedConversionTypeParams]
311
+ ) -> Optional[ConversionTypeParams]:
312
+ if unparsed is None:
313
+ return None
314
+ return ConversionTypeParams(
315
+ base_measure=self._get_input_measure(unparsed.base_measure),
316
+ conversion_measure=self._get_input_measure(unparsed.conversion_measure),
317
+ entity=unparsed.entity,
318
+ calculation=ConversionCalculationType(unparsed.calculation),
319
+ window=self._get_optional_time_window(unparsed.window),
320
+ constant_properties=unparsed.constant_properties,
321
+ )
322
+
323
+ def _get_optional_cumulative_type_params(
324
+ self, unparsed_metric: UnparsedMetric
325
+ ) -> Optional[CumulativeTypeParams]:
326
+ unparsed_type_params = unparsed_metric.type_params
327
+ if unparsed_metric.type.lower() == MetricType.CUMULATIVE.value:
328
+ if not unparsed_type_params.cumulative_type_params:
329
+ unparsed_type_params.cumulative_type_params = UnparsedCumulativeTypeParams()
330
+
331
+ if (
332
+ unparsed_type_params.window
333
+ and not unparsed_type_params.cumulative_type_params.window
334
+ ):
335
+ unparsed_type_params.cumulative_type_params.window = unparsed_type_params.window
336
+ if (
337
+ unparsed_type_params.grain_to_date
338
+ and not unparsed_type_params.cumulative_type_params.grain_to_date
339
+ ):
340
+ unparsed_type_params.cumulative_type_params.grain_to_date = (
341
+ unparsed_type_params.grain_to_date
342
+ )
343
+
344
+ return CumulativeTypeParams(
345
+ window=self._get_optional_time_window(
346
+ unparsed_type_params.cumulative_type_params.window
347
+ ),
348
+ grain_to_date=unparsed_type_params.cumulative_type_params.grain_to_date,
349
+ period_agg=self._get_period_agg(
350
+ unparsed_type_params.cumulative_type_params.period_agg
351
+ ),
352
+ )
353
+
354
+ return None
355
+
356
+ def _get_metric_type_params(self, unparsed_metric: UnparsedMetric) -> MetricTypeParams:
357
+ type_params = unparsed_metric.type_params
358
+
359
+ grain_to_date: Optional[TimeGranularity] = None
360
+ if type_params.grain_to_date is not None:
361
+ # This should've been changed to a string (to support custom grain), but since this
362
+ # is a legacy field waiting to be deprecated, we will not support custom grain here
363
+ # in order to force customers off of using this field. The field to use should be
364
+ # `cumulative_type_params.grain_to_date`
365
+ grain_to_date = TimeGranularity(type_params.grain_to_date)
366
+
367
+ return MetricTypeParams(
368
+ measure=self._get_optional_input_measure(type_params.measure),
369
+ numerator=self._get_optional_metric_input(type_params.numerator),
370
+ denominator=self._get_optional_metric_input(type_params.denominator),
371
+ expr=str(type_params.expr) if type_params.expr is not None else None,
372
+ window=self._get_optional_time_window(type_params.window),
373
+ grain_to_date=grain_to_date,
374
+ metrics=self._get_metric_inputs(type_params.metrics),
375
+ conversion_type_params=self._get_optional_conversion_type_params(
376
+ type_params.conversion_type_params
377
+ ),
378
+ cumulative_type_params=self._get_optional_cumulative_type_params(
379
+ unparsed_metric=unparsed_metric,
380
+ ),
381
+ # input measures are calculated via metric processing post parsing
382
+ # input_measures=?,
383
+ )
384
+
385
+ def parse_metric(self, unparsed: UnparsedMetric, generated_from: Optional[str] = None) -> None:
386
+ package_name = self.project.project_name
387
+ unique_id = f"{NodeType.Metric}.{package_name}.{unparsed.name}"
388
+ path = self.yaml.path.relative_path
389
+
390
+ fqn = self.schema_parser.get_fqn_prefix(path)
391
+ fqn.append(unparsed.name)
392
+
393
+ config = self._generate_metric_config(
394
+ target=unparsed,
395
+ fqn=fqn,
396
+ package_name=package_name,
397
+ rendered=True,
398
+ )
399
+
400
+ config = config.finalize_and_validate()
401
+
402
+ unrendered_config = self._generate_metric_config(
403
+ target=unparsed,
404
+ fqn=fqn,
405
+ package_name=package_name,
406
+ rendered=False,
407
+ )
408
+
409
+ if not isinstance(config, MetricConfig):
410
+ raise DbtInternalError(
411
+ f"Calculated a {type(config)} for a metric, but expected a MetricConfig"
412
+ )
413
+
414
+ # If we have meta in the config, copy to node level, for backwards
415
+ # compatibility with earlier node-only config.
416
+ if "meta" in config and config["meta"]:
417
+ unparsed.meta = config["meta"]
418
+
419
+ parsed = Metric(
420
+ resource_type=NodeType.Metric,
421
+ package_name=package_name,
422
+ path=path,
423
+ original_file_path=self.yaml.path.original_file_path,
424
+ unique_id=unique_id,
425
+ fqn=fqn,
426
+ name=unparsed.name,
427
+ description=unparsed.description,
428
+ label=unparsed.label,
429
+ type=MetricType(unparsed.type),
430
+ type_params=self._get_metric_type_params(unparsed),
431
+ time_granularity=unparsed.time_granularity,
432
+ filter=parse_where_filter(unparsed.filter),
433
+ meta=unparsed.meta,
434
+ tags=unparsed.tags,
435
+ config=config,
436
+ unrendered_config=unrendered_config,
437
+ group=config.group,
438
+ )
439
+
440
+ # if the metric is disabled we do not want it included in the manifest, only in the disabled dict
441
+ assert isinstance(self.yaml.file, SchemaSourceFile)
442
+ if parsed.config.enabled:
443
+ self.manifest.add_metric(self.yaml.file, parsed, generated_from)
444
+ else:
445
+ self.manifest.add_disabled(self.yaml.file, parsed)
446
+
447
+ def _generate_metric_config(
448
+ self, target: UnparsedMetric, fqn: List[str], package_name: str, rendered: bool
449
+ ):
450
+ generator: BaseContextConfigGenerator
451
+ if rendered:
452
+ generator = ContextConfigGenerator(self.root_project)
453
+ else:
454
+ generator = UnrenderedConfigGenerator(self.root_project)
455
+
456
+ # configs with precendence set
457
+ precedence_configs = dict()
458
+ # first apply metric configs
459
+ precedence_configs.update(target.config)
460
+
461
+ config = generator.calculate_node_config(
462
+ config_call_dict={},
463
+ fqn=fqn,
464
+ resource_type=NodeType.Metric,
465
+ project_name=package_name,
466
+ base=False,
467
+ patch_config_dict=precedence_configs,
468
+ )
469
+ return config
470
+
471
+ def parse(self) -> None:
472
+ for data in self.get_key_dicts():
473
+ try:
474
+ UnparsedMetric.validate(data)
475
+ unparsed = UnparsedMetric.from_dict(data)
476
+
477
+ except (ValidationError, JSONValidationError) as exc:
478
+ raise YamlParseDictError(self.yaml.path, self.key, data, exc)
479
+ self.parse_metric(unparsed)
480
+
481
+
482
+ class GroupParser(YamlReader):
483
+ def __init__(self, schema_parser: SchemaParser, yaml: YamlBlock) -> None:
484
+ super().__init__(schema_parser, yaml, NodeType.Group.pluralize())
485
+ self.schema_parser = schema_parser
486
+ self.yaml = yaml
487
+
488
+ def parse_group(self, unparsed: UnparsedGroup) -> None:
489
+ package_name = self.project.project_name
490
+ unique_id = f"{NodeType.Group}.{package_name}.{unparsed.name}"
491
+ path = self.yaml.path.relative_path
492
+
493
+ fqn = self.schema_parser.get_fqn_prefix(path)
494
+ fqn.append(unparsed.name)
495
+ config = self._generate_group_config(unparsed, fqn, package_name, True)
496
+
497
+ parsed = Group(
498
+ resource_type=NodeType.Group,
499
+ package_name=package_name,
500
+ path=path,
501
+ original_file_path=self.yaml.path.original_file_path,
502
+ unique_id=unique_id,
503
+ name=unparsed.name,
504
+ owner=unparsed.owner,
505
+ description=unparsed.description,
506
+ config=config,
507
+ )
508
+
509
+ assert isinstance(self.yaml.file, SchemaSourceFile)
510
+ self.manifest.add_group(self.yaml.file, parsed)
511
+
512
+ def parse(self):
513
+ for data in self.get_key_dicts():
514
+ try:
515
+ UnparsedGroup.validate(data)
516
+ unparsed = UnparsedGroup.from_dict(data)
517
+ except (ValidationError, JSONValidationError) as exc:
518
+ raise YamlParseDictError(self.yaml.path, self.key, data, exc)
519
+
520
+ self.parse_group(unparsed)
521
+
522
+ def _generate_group_config(
523
+ self, target: UnparsedGroup, fqn: List[str], package_name: str, rendered: bool
524
+ ):
525
+ generator: BaseContextConfigGenerator
526
+ if rendered:
527
+ generator = ContextConfigGenerator(self.root_project)
528
+ else:
529
+ generator = UnrenderedConfigGenerator(self.root_project)
530
+
531
+ # configs with precendence set
532
+ precedence_configs = dict()
533
+ # first apply metric configs
534
+ precedence_configs.update(target.config)
535
+
536
+ config = generator.calculate_node_config(
537
+ config_call_dict={},
538
+ fqn=fqn,
539
+ resource_type=NodeType.Group,
540
+ project_name=package_name,
541
+ base=False,
542
+ patch_config_dict=precedence_configs,
543
+ )
544
+ return config
545
+
546
+
547
+ class SemanticModelParser(YamlReader):
548
+ def __init__(self, schema_parser: SchemaParser, yaml: YamlBlock) -> None:
549
+ super().__init__(schema_parser, yaml, "semantic_models")
550
+ self.schema_parser = schema_parser
551
+ self.yaml = yaml
552
+
553
+ def _get_dimension_type_params(
554
+ self, unparsed: Optional[UnparsedDimensionTypeParams]
555
+ ) -> Optional[DimensionTypeParams]:
556
+ if unparsed is not None:
557
+ return DimensionTypeParams(
558
+ time_granularity=TimeGranularity(unparsed.time_granularity),
559
+ validity_params=unparsed.validity_params,
560
+ )
561
+ else:
562
+ return None
563
+
564
+ def _get_dimensions(self, unparsed_dimensions: List[UnparsedDimension]) -> List[Dimension]:
565
+ dimensions: List[Dimension] = []
566
+ for unparsed in unparsed_dimensions:
567
+ dimensions.append(
568
+ Dimension(
569
+ name=unparsed.name,
570
+ type=DimensionType(unparsed.type),
571
+ description=unparsed.description,
572
+ label=unparsed.label,
573
+ is_partition=unparsed.is_partition,
574
+ type_params=self._get_dimension_type_params(unparsed=unparsed.type_params),
575
+ expr=unparsed.expr,
576
+ metadata=None, # TODO: requires a fair bit of parsing context
577
+ config=SemanticLayerElementConfig(meta=unparsed.config.get("meta", {})),
578
+ )
579
+ )
580
+ return dimensions
581
+
582
+ def _get_entities(self, unparsed_entities: List[UnparsedEntity]) -> List[Entity]:
583
+ entities: List[Entity] = []
584
+ for unparsed in unparsed_entities:
585
+ entities.append(
586
+ Entity(
587
+ name=unparsed.name,
588
+ type=EntityType(unparsed.type),
589
+ description=unparsed.description,
590
+ label=unparsed.label,
591
+ role=unparsed.role,
592
+ expr=unparsed.expr,
593
+ config=SemanticLayerElementConfig(meta=unparsed.config.get("meta", {})),
594
+ )
595
+ )
596
+
597
+ return entities
598
+
599
+ def _get_non_additive_dimension(
600
+ self, unparsed: Optional[UnparsedNonAdditiveDimension]
601
+ ) -> Optional[NonAdditiveDimension]:
602
+ if unparsed is not None:
603
+ return NonAdditiveDimension(
604
+ name=unparsed.name,
605
+ window_choice=AggregationType(unparsed.window_choice),
606
+ window_groupings=unparsed.window_groupings,
607
+ )
608
+ else:
609
+ return None
610
+
611
+ def _get_measures(self, unparsed_measures: List[UnparsedMeasure]) -> List[Measure]:
612
+ measures: List[Measure] = []
613
+ for unparsed in unparsed_measures:
614
+ measures.append(
615
+ Measure(
616
+ name=unparsed.name,
617
+ agg=AggregationType(unparsed.agg),
618
+ description=unparsed.description,
619
+ label=unparsed.label,
620
+ expr=str(unparsed.expr) if unparsed.expr is not None else None,
621
+ agg_params=unparsed.agg_params,
622
+ non_additive_dimension=self._get_non_additive_dimension(
623
+ unparsed.non_additive_dimension
624
+ ),
625
+ agg_time_dimension=unparsed.agg_time_dimension,
626
+ config=SemanticLayerElementConfig(meta=unparsed.config.get("meta", {})),
627
+ )
628
+ )
629
+ return measures
630
+
631
+ def _create_metric(
632
+ self,
633
+ measure: UnparsedMeasure,
634
+ enabled: bool,
635
+ semantic_model_name: str,
636
+ meta: Optional[Dict[str, Any]] = None,
637
+ ) -> None:
638
+ config: Dict[str, Any] = {"enabled": enabled}
639
+ if meta is not None:
640
+ # Need to propagate meta to metric from measure during create_metric: True
641
+ config["meta"] = meta
642
+ unparsed_metric = UnparsedMetric(
643
+ name=measure.name,
644
+ label=measure.label or measure.name,
645
+ type="simple",
646
+ type_params=UnparsedMetricTypeParams(
647
+ measure=measure.name, expr=measure.expr or measure.name # type: ignore
648
+ ),
649
+ description=measure.description or f"Metric created from measure {measure.name}",
650
+ config=config,
651
+ )
652
+
653
+ parser = MetricParser(self.schema_parser, yaml=self.yaml)
654
+ parser.parse_metric(unparsed=unparsed_metric, generated_from=semantic_model_name)
655
+
656
+ def _generate_semantic_model_config(
657
+ self, target: UnparsedSemanticModel, fqn: List[str], package_name: str, rendered: bool
658
+ ):
659
+ generator: BaseContextConfigGenerator
660
+ if rendered:
661
+ generator = ContextConfigGenerator(self.root_project)
662
+ else:
663
+ generator = UnrenderedConfigGenerator(self.root_project)
664
+
665
+ # configs with precendence set
666
+ precedence_configs = dict()
667
+ # first apply semantic model configs
668
+ precedence_configs.update(target.config)
669
+
670
+ config = generator.calculate_node_config(
671
+ config_call_dict={},
672
+ fqn=fqn,
673
+ resource_type=NodeType.SemanticModel,
674
+ project_name=package_name,
675
+ base=False,
676
+ patch_config_dict=precedence_configs,
677
+ )
678
+
679
+ return config
680
+
681
+ def parse_semantic_model(self, unparsed: UnparsedSemanticModel) -> None:
682
+ package_name = self.project.project_name
683
+ unique_id = f"{NodeType.SemanticModel}.{package_name}.{unparsed.name}"
684
+ path = self.yaml.path.relative_path
685
+
686
+ fqn = self.schema_parser.get_fqn_prefix(path)
687
+ fqn.append(unparsed.name)
688
+
689
+ entities = self._get_entities(unparsed.entities)
690
+ measures = self._get_measures(unparsed.measures)
691
+ dimensions = self._get_dimensions(unparsed.dimensions)
692
+
693
+ config = self._generate_semantic_model_config(
694
+ target=unparsed,
695
+ fqn=fqn,
696
+ package_name=package_name,
697
+ rendered=True,
698
+ )
699
+
700
+ # Combine configs according to the behavior documented here https://docs.getdbt.com/reference/configs-and-properties#combining-configs
701
+ elements: Sequence[Union[Dimension, Entity, Measure]] = [
702
+ *dimensions,
703
+ *entities,
704
+ *measures,
705
+ ]
706
+ for element in elements:
707
+ if config is not None:
708
+ if element.config is None:
709
+ element.config = SemanticLayerElementConfig(meta=config.meta)
710
+ else:
711
+ element.config.meta = {**config.get("meta", {}), **element.config.meta}
712
+
713
+ config = config.finalize_and_validate()
714
+
715
+ unrendered_config = self._generate_semantic_model_config(
716
+ target=unparsed,
717
+ fqn=fqn,
718
+ package_name=package_name,
719
+ rendered=False,
720
+ )
721
+
722
+ parsed = SemanticModel(
723
+ description=unparsed.description,
724
+ label=unparsed.label,
725
+ fqn=fqn,
726
+ model=unparsed.model,
727
+ name=unparsed.name,
728
+ node_relation=None, # Resolved from the value of "model" after parsing
729
+ original_file_path=self.yaml.path.original_file_path,
730
+ package_name=package_name,
731
+ path=path,
732
+ resource_type=NodeType.SemanticModel,
733
+ unique_id=unique_id,
734
+ entities=entities,
735
+ measures=measures,
736
+ dimensions=dimensions,
737
+ defaults=unparsed.defaults,
738
+ primary_entity=unparsed.primary_entity,
739
+ config=config,
740
+ unrendered_config=unrendered_config,
741
+ group=config.group,
742
+ )
743
+
744
+ ctx = generate_parse_semantic_models(
745
+ parsed,
746
+ self.root_project,
747
+ self.schema_parser.manifest,
748
+ package_name,
749
+ )
750
+
751
+ if parsed.model is not None:
752
+ model_ref = "{{ " + parsed.model + " }}"
753
+ # This sets the "refs" in the SemanticModel from the SemanticModelRefResolver in context/providers.py
754
+ get_rendered(model_ref, ctx, parsed)
755
+
756
+ # if the semantic model is disabled we do not want it included in the manifest,
757
+ # only in the disabled dict
758
+ assert isinstance(self.yaml.file, SchemaSourceFile)
759
+ if parsed.config.enabled:
760
+ self.manifest.add_semantic_model(self.yaml.file, parsed)
761
+ else:
762
+ self.manifest.add_disabled(self.yaml.file, parsed)
763
+
764
+ # Create a metric for each measure with `create_metric = True`
765
+ for measure in unparsed.measures:
766
+ if measure.create_metric is True:
767
+ self._create_metric(
768
+ measure=measure,
769
+ enabled=parsed.config.enabled,
770
+ semantic_model_name=parsed.name,
771
+ meta=config.meta if config is not None else None,
772
+ )
773
+
774
+ def parse(self) -> None:
775
+ for data in self.get_key_dicts():
776
+ try:
777
+ UnparsedSemanticModel.validate(data)
778
+ unparsed = UnparsedSemanticModel.from_dict(data)
779
+ except (ValidationError, JSONValidationError) as exc:
780
+ raise YamlParseDictError(self.yaml.path, self.key, data, exc)
781
+
782
+ self.parse_semantic_model(unparsed)
783
+
784
+
785
+ class SavedQueryParser(YamlReader):
786
+ def __init__(self, schema_parser: SchemaParser, yaml: YamlBlock) -> None:
787
+ super().__init__(schema_parser, yaml, "saved_queries")
788
+ self.schema_parser = schema_parser
789
+ self.yaml = yaml
790
+
791
+ def _generate_saved_query_config(
792
+ self, target: UnparsedSavedQuery, fqn: List[str], package_name: str, rendered: bool
793
+ ):
794
+ generator: BaseContextConfigGenerator
795
+ if rendered:
796
+ generator = ContextConfigGenerator(self.root_project)
797
+ else:
798
+ generator = UnrenderedConfigGenerator(self.root_project)
799
+
800
+ # configs with precendence set
801
+ precedence_configs = dict()
802
+ # first apply semantic model configs
803
+ precedence_configs.update(target.config)
804
+
805
+ config = generator.calculate_node_config(
806
+ config_call_dict={},
807
+ fqn=fqn,
808
+ resource_type=NodeType.SavedQuery,
809
+ project_name=package_name,
810
+ base=False,
811
+ patch_config_dict=precedence_configs,
812
+ )
813
+
814
+ return config
815
+
816
+ def _get_export_config(
817
+ self, unparsed_export_config: Dict[str, Any], saved_query_config: SavedQueryConfig
818
+ ) -> ExportConfig:
819
+ # Combine the two dictionaries using dictionary unpacking
820
+ # the second dictionary is the one whose keys take priority
821
+ combined = {**saved_query_config.__dict__, **unparsed_export_config}
822
+ # `schema` is the user facing attribute, but for DSI protocol purposes we track it as `schema_name`
823
+ if combined.get("schema") is not None and combined.get("schema_name") is None:
824
+ combined["schema_name"] = combined["schema"]
825
+
826
+ return ExportConfig.from_dict(combined)
827
+
828
+ def _get_export(
829
+ self, unparsed: UnparsedExport, saved_query_config: SavedQueryConfig
830
+ ) -> Export:
831
+ return Export(
832
+ name=unparsed.name,
833
+ config=self._get_export_config(unparsed.config, saved_query_config),
834
+ unrendered_config=unparsed.config,
835
+ )
836
+
837
+ def _get_query_params(self, unparsed: UnparsedQueryParams) -> QueryParams:
838
+ return QueryParams(
839
+ group_by=unparsed.group_by,
840
+ metrics=unparsed.metrics,
841
+ where=parse_where_filter(unparsed.where),
842
+ order_by=unparsed.order_by,
843
+ limit=unparsed.limit,
844
+ )
845
+
846
+ def parse_saved_query(self, unparsed: UnparsedSavedQuery) -> None:
847
+ package_name = self.project.project_name
848
+ unique_id = f"{NodeType.SavedQuery}.{package_name}.{unparsed.name}"
849
+ path = self.yaml.path.relative_path
850
+
851
+ fqn = self.schema_parser.get_fqn_prefix(path)
852
+ fqn.append(unparsed.name)
853
+
854
+ config = self._generate_saved_query_config(
855
+ target=unparsed,
856
+ fqn=fqn,
857
+ package_name=package_name,
858
+ rendered=True,
859
+ )
860
+
861
+ config = config.finalize_and_validate()
862
+
863
+ unrendered_config = self._generate_saved_query_config(
864
+ target=unparsed,
865
+ fqn=fqn,
866
+ package_name=package_name,
867
+ rendered=False,
868
+ )
869
+
870
+ # The parser handles plain strings just fine, but we need to be able
871
+ # to join two lists, remove duplicates, and sort, so we have to wrap things here.
872
+ def wrap_tags(s: Union[List[str], str]) -> List[str]:
873
+ if s is None:
874
+ return []
875
+ return [s] if isinstance(s, str) else s
876
+
877
+ config_tags = wrap_tags(config.get("tags"))
878
+ unparsed_tags = wrap_tags(unparsed.tags)
879
+ tags = list(set([*unparsed_tags, *config_tags]))
880
+ tags.sort()
881
+
882
+ parsed = SavedQuery(
883
+ description=unparsed.description,
884
+ label=unparsed.label,
885
+ fqn=fqn,
886
+ name=unparsed.name,
887
+ original_file_path=self.yaml.path.original_file_path,
888
+ package_name=package_name,
889
+ path=path,
890
+ resource_type=NodeType.SavedQuery,
891
+ unique_id=unique_id,
892
+ query_params=self._get_query_params(unparsed.query_params),
893
+ exports=[self._get_export(export, config) for export in unparsed.exports],
894
+ config=config,
895
+ unrendered_config=unrendered_config,
896
+ group=config.group,
897
+ tags=tags,
898
+ )
899
+
900
+ for export in parsed.exports:
901
+ self.schema_parser.update_parsed_node_relation_names(export, export.config.to_dict()) # type: ignore
902
+
903
+ if not export.config.schema_name:
904
+ export.config.schema_name = getattr(export, "schema", None)
905
+ delattr(export, "schema")
906
+
907
+ export.config.database = getattr(export, "database", None) or export.config.database
908
+ delattr(export, "database")
909
+
910
+ if not export.config.alias:
911
+ export.config.alias = getattr(export, "alias", None)
912
+ delattr(export, "alias")
913
+
914
+ delattr(export, "relation_name")
915
+
916
+ # Only add thes saved query if it's enabled, otherwise we track it with other diabled nodes
917
+ assert isinstance(self.yaml.file, SchemaSourceFile)
918
+ if parsed.config.enabled:
919
+ self.manifest.add_saved_query(self.yaml.file, parsed)
920
+ else:
921
+ self.manifest.add_disabled(self.yaml.file, parsed)
922
+
923
+ def parse(self) -> ParseResult:
924
+ for data in self.get_key_dicts():
925
+ try:
926
+ UnparsedSavedQuery.validate(data)
927
+ unparsed = UnparsedSavedQuery.from_dict(data)
928
+ except (ValidationError, JSONValidationError) as exc:
929
+ raise YamlParseDictError(self.yaml.path, self.key, data, exc)
930
+
931
+ self.parse_saved_query(unparsed)
932
+
933
+ # The supertype (YamlReader) requires `parse` to return a ParseResult, so
934
+ # we return an empty one because we don't have one to actually return.
935
+ return ParseResult()