acryl-datahub 0.15.0.5rc3__py3-none-any.whl → 0.15.0.5rc5__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 acryl-datahub might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  datahub/__init__.py,sha256=aq_i5lVREmoLfYIqcx_pEQicO855YlhD19tWc1eZZNI,59
2
2
  datahub/__main__.py,sha256=pegIvQ9hzK7IhqVeUi1MeADSZ2QlP-D3K0OQdEg55RU,106
3
- datahub/_version.py,sha256=y-G1yv1bSuxJLKtiCJEKjyAqf2KI3SupqeNUfVB1MUg,324
3
+ datahub/_version.py,sha256=k2KgFitDFziMKt2iil2MW_OYVP7lggtvH9A6OAJFD9c,324
4
4
  datahub/entrypoints.py,sha256=osv2ailvuW-HHlAE0fOtyblJI1X7HInZutd9DC66jqQ,8022
5
5
  datahub/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  datahub/_codegen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -219,7 +219,7 @@ datahub/ingestion/source/abs/report.py,sha256=fzkTdTewYlWrTk4f2Cyl-e8RV4qw9wEVtm
219
219
  datahub/ingestion/source/abs/source.py,sha256=cuMezUzr-Smp5tok2ceYor5I5jp52NDMjfeN8kfIbvg,24816
220
220
  datahub/ingestion/source/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
221
221
  datahub/ingestion/source/aws/aws_common.py,sha256=DfdQgkJ_s2isFx8WvqKTlAcBk4KE8SgfpmA5BgC3fgY,17716
222
- datahub/ingestion/source/aws/glue.py,sha256=qwkZMcbBlHIdhhuRj-gHNYMeuMADrvaHcN3gik0n_08,57919
222
+ datahub/ingestion/source/aws/glue.py,sha256=DwROr923M01QnvImUbMoHS6TTTT9kBz2tEmQ3Sv4EoY,58019
223
223
  datahub/ingestion/source/aws/s3_boto_utils.py,sha256=Y54jlLV5gLcuZ4Zs57kIW5dYHD89RSFfsVNlFbRnSkQ,3901
224
224
  datahub/ingestion/source/aws/s3_util.py,sha256=OFypcgmVC6jnZM90-gjcPpAMtTV1lbnreCaMhCzNlzs,2149
225
225
  datahub/ingestion/source/aws/sagemaker.py,sha256=Bl2tkBYnrindgx61VHYgNovUF_Kp_fXNcivQn28vC2w,5254
@@ -333,22 +333,22 @@ datahub/ingestion/source/kafka_connect/source_connectors.py,sha256=-rFNXKD8_EFoX
333
333
  datahub/ingestion/source/looker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
334
334
  datahub/ingestion/source/looker/lkml_patched.py,sha256=XShEU7Wbz0DubDhYMjKf9wjKZrBJa2XPg9MIjp8rPhk,733
335
335
  datahub/ingestion/source/looker/looker_common.py,sha256=squUUBHxsLeT5xbZOTO66irtOB8fL0V4Q8Tgd9EJMYU,62067
336
- datahub/ingestion/source/looker/looker_config.py,sha256=Kp-IoGcjfk5yPoqXFbsjo6aLA1j0H9LFEzZwhoGNvy4,13584
336
+ datahub/ingestion/source/looker/looker_config.py,sha256=eVKw1nn9D8hUFdRfNyT3MtzL8w-zWhFeokiwSnNKQuc,13607
337
337
  datahub/ingestion/source/looker/looker_connection.py,sha256=yDmC6lDsHmL2e_Pw8ULylwOIHPWPp_6gT1iyLvD0fTw,2075
338
338
  datahub/ingestion/source/looker/looker_constant.py,sha256=GMKYtNXlpojPxa9azridKfcGLSJwKdUCTesp7U8dIrQ,402
339
- datahub/ingestion/source/looker/looker_dataclasses.py,sha256=LjrP5m_A4HV-XeFlSNGVYNuyF0ulxp_qwB82Ss4Iycs,12200
340
- datahub/ingestion/source/looker/looker_file_loader.py,sha256=c1ewDrIb9VJg1o-asbwX9gL83kgL01vIETzzbmZIhmw,4267
339
+ datahub/ingestion/source/looker/looker_dataclasses.py,sha256=MrDeZ4Nd0wQnJbCoI1qePYlYeObnUw5dvpWcmhKuNgc,12346
340
+ datahub/ingestion/source/looker/looker_file_loader.py,sha256=PEyL9KWRaFcrvOkapU8wSNlFbmetmBy9tAyCgeVDOa4,4864
341
341
  datahub/ingestion/source/looker/looker_lib_wrapper.py,sha256=0gaYjBv4wkbbLWVgvaAV6JyWAFb0utTG6TCve2d9xss,11511
342
342
  datahub/ingestion/source/looker/looker_liquid_tag.py,sha256=mO4G4MNA4YZFvZaDBpdiJ2vP3irC82kY34RdaK4Pbfs,3100
343
343
  datahub/ingestion/source/looker/looker_query_model.py,sha256=N0jBbFruiCIIGT6sJn6tNeppeQ78KGTkOwTLirhxFNc,2144
344
344
  datahub/ingestion/source/looker/looker_source.py,sha256=S-g06Bm3sbyD0Qjra9hEhZmsVDb-BY_-bCPDwCjtEoQ,66427
345
- datahub/ingestion/source/looker/looker_template_language.py,sha256=mfbU27NYs0mkZHXdtvS38FC5WCJ4S_aGjC8t09yecKY,14330
345
+ datahub/ingestion/source/looker/looker_template_language.py,sha256=W-SMICKBfIuivrHywHRYchz9SJiXhoU8VOEKGQW_1v8,17825
346
346
  datahub/ingestion/source/looker/looker_usage.py,sha256=qFBX7OHtIcarYIqFe0jQMrDV8MMPV_nN4PZrZRUznTw,23029
347
347
  datahub/ingestion/source/looker/looker_view_id_cache.py,sha256=92gDy6NONhJYBp92z_IBzDVZvezmUIkaBCZY1bdk6mE,4392
348
348
  datahub/ingestion/source/looker/lookml_concept_context.py,sha256=eDaze9S7cgO5eFP7-0azUMEJyR3EfMjmfj5pMPjpm8c,18066
349
- datahub/ingestion/source/looker/lookml_config.py,sha256=Q0fMsu_Cvm8807R6VB14VJDLqjoLTyGF-WsiUD6xEk8,10519
349
+ datahub/ingestion/source/looker/lookml_config.py,sha256=RuZkH3DDmII21gEsUvPsJi5gxWngbYkqBP06H8_n_Hs,11353
350
350
  datahub/ingestion/source/looker/lookml_refinement.py,sha256=MkVreI0BylaCFyDHihDHaCcXyDSP84eF9p1h5d-ZHnM,9504
351
- datahub/ingestion/source/looker/lookml_source.py,sha256=jp58gSrWXITwxd-C5UfVoLJXpxBe5smFjdJyYza-Aek,40436
351
+ datahub/ingestion/source/looker/lookml_source.py,sha256=PJBUJgZfZyvmasDf_LJC39SggLCA6vSfAbf1PdzviZU,43889
352
352
  datahub/ingestion/source/looker/str_functions.py,sha256=zceEX2ka_4WaWwWgEdyknUSz7X3GrO951BkwSbF2afo,766
353
353
  datahub/ingestion/source/looker/urn_functions.py,sha256=4VvqEfGvIMq3rNHHps0-HlPurMPnpqdxNtDAOOHIZww,528
354
354
  datahub/ingestion/source/looker/view_upstream.py,sha256=4FCjZaU6p2G7npB2RJpP4Gv2yLjbvbsYWEbAg55IvjY,26110
@@ -446,7 +446,7 @@ datahub/ingestion/source/snowflake/snowflake_summary.py,sha256=kTmuCtRnvHqM8WBYh
446
446
  datahub/ingestion/source/snowflake/snowflake_tag.py,sha256=TN_cTF4a8V_tbeR2czm_hoMYfQMuqmBbFlAqyh7PJzQ,6551
447
447
  datahub/ingestion/source/snowflake/snowflake_usage_v2.py,sha256=ySFm7WDk8FW9KjCnX4HQfTqObIrlUS-V8WIHl3j0CTI,24848
448
448
  datahub/ingestion/source/snowflake/snowflake_utils.py,sha256=xq58c47zmaQPkTVqjKW25iViX8VJuHdQDTFY4jxzZ2o,12778
449
- datahub/ingestion/source/snowflake/snowflake_v2.py,sha256=8nnQ_XlbT4q6y6_JleSV67njUaPjF9x1yehKu5I1lgc,32072
449
+ datahub/ingestion/source/snowflake/snowflake_v2.py,sha256=vyTqC_C5Bf0AMRVyoxUfl1CdlgeQouX20msP2FsMqnk,33439
450
450
  datahub/ingestion/source/sql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
451
451
  datahub/ingestion/source/sql/athena.py,sha256=Uh9wGLOqAkcphffxOPIQNyXvjeRm74XIpaLb4rjqMjM,24045
452
452
  datahub/ingestion/source/sql/clickhouse.py,sha256=uSRy-HKAiGFTHVLoVtGoh23X0O1lwyYUaK8BaWkYhps,25555
@@ -993,9 +993,9 @@ datahub_provider/operators/datahub_assertion_operator.py,sha256=uvTQ-jk2F0sbqqxp
993
993
  datahub_provider/operators/datahub_assertion_sensor.py,sha256=lCBj_3x1cf5GMNpHdfkpHuyHfVxsm6ff5x2Z5iizcAo,140
994
994
  datahub_provider/operators/datahub_operation_operator.py,sha256=aevDp2FzX7FxGlXrR0khoHNbxbhKR2qPEX5e8O2Jyzw,174
995
995
  datahub_provider/operators/datahub_operation_sensor.py,sha256=8fcdVBCEPgqy1etTXgLoiHoJrRt_nzFZQMdSzHqSG7M,168
996
- acryl_datahub-0.15.0.5rc3.dist-info/LICENSE,sha256=9xNHpsD0uYF5ONzXsKDCuHHB-xbiCrSbueWXqrTNsxk,11365
997
- acryl_datahub-0.15.0.5rc3.dist-info/METADATA,sha256=iA_FeNAFTGBRzDr-rvVZXYUJtYerY57GdmWkuX4XCIg,173382
998
- acryl_datahub-0.15.0.5rc3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
999
- acryl_datahub-0.15.0.5rc3.dist-info/entry_points.txt,sha256=xnPSPLK3bJGADxe4TDS4wL4u0FT_PGlahDa-ENYdYCQ,9512
1000
- acryl_datahub-0.15.0.5rc3.dist-info/top_level.txt,sha256=iLjSrLK5ox1YVYcglRUkcvfZPvKlobBWx7CTUXx8_GI,25
1001
- acryl_datahub-0.15.0.5rc3.dist-info/RECORD,,
996
+ acryl_datahub-0.15.0.5rc5.dist-info/LICENSE,sha256=9xNHpsD0uYF5ONzXsKDCuHHB-xbiCrSbueWXqrTNsxk,11365
997
+ acryl_datahub-0.15.0.5rc5.dist-info/METADATA,sha256=iUCOkI7iz8GUQSvFsn9nLdzi1GxoElLbxrV96MnC9BM,173382
998
+ acryl_datahub-0.15.0.5rc5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
999
+ acryl_datahub-0.15.0.5rc5.dist-info/entry_points.txt,sha256=xnPSPLK3bJGADxe4TDS4wL4u0FT_PGlahDa-ENYdYCQ,9512
1000
+ acryl_datahub-0.15.0.5rc5.dist-info/top_level.txt,sha256=iLjSrLK5ox1YVYcglRUkcvfZPvKlobBWx7CTUXx8_GI,25
1001
+ acryl_datahub-0.15.0.5rc5.dist-info/RECORD,,
datahub/_version.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # Published at https://pypi.org/project/acryl-datahub/.
2
2
  __package_name__ = "acryl-datahub"
3
- __version__ = "0.15.0.5rc3"
3
+ __version__ = "0.15.0.5rc5"
4
4
 
5
5
 
6
6
  def is_dev_mode() -> bool:
@@ -218,6 +218,7 @@ class GlueSourceConfig(
218
218
 
219
219
  @dataclass
220
220
  class GlueSourceReport(StaleEntityRemovalSourceReport):
221
+ catalog_id: Optional[str] = None
221
222
  tables_scanned = 0
222
223
  filtered: List[str] = dataclass_field(default_factory=list)
223
224
  databases: EntityFilterReport = EntityFilterReport.field(type="database")
@@ -315,6 +316,7 @@ class GlueSource(StatefulIngestionSourceBase):
315
316
  self.extract_owners = config.extract_owners
316
317
  self.source_config = config
317
318
  self.report = GlueSourceReport()
319
+ self.report.catalog_id = self.source_config.catalog_id
318
320
  self.glue_client = config.glue_client
319
321
  self.s3_client = config.s3_client
320
322
  self.extract_transforms = config.extract_transforms
@@ -177,7 +177,9 @@ def _get_generic_definition(
177
177
  class LookerConnectionDefinition(ConfigModel):
178
178
  platform: str
179
179
  default_db: str
180
- default_schema: Optional[str] # Optional since some sources are two-level only
180
+ default_schema: Optional[str] = (
181
+ None # Optional since some sources are two-level only
182
+ )
181
183
  platform_instance: Optional[str] = None
182
184
  platform_env: Optional[str] = Field(
183
185
  default=None,
@@ -32,6 +32,12 @@ class LookerField:
32
32
  sql: Optional[str]
33
33
 
34
34
 
35
+ @dataclass
36
+ class LookerConstant:
37
+ name: str
38
+ value: str
39
+
40
+
35
41
  @dataclass
36
42
  class LookerModel:
37
43
  connection: str
@@ -75,6 +81,7 @@ class LookerModel:
75
81
  try:
76
82
  parsed = load_and_preprocess_file(
77
83
  path=included_file,
84
+ reporter=reporter,
78
85
  source_config=source_config,
79
86
  )
80
87
  included_explores = parsed.get("explores", [])
@@ -217,6 +224,7 @@ class LookerModel:
217
224
  try:
218
225
  parsed = load_and_preprocess_file(
219
226
  path=included_file,
227
+ reporter=reporter,
220
228
  source_config=source_config,
221
229
  )
222
230
  seen_so_far.add(included_file)
@@ -4,7 +4,10 @@ from dataclasses import replace
4
4
  from typing import Dict, Optional
5
5
 
6
6
  from datahub.ingestion.source.looker.looker_config import LookerConnectionDefinition
7
- from datahub.ingestion.source.looker.looker_dataclasses import LookerViewFile
7
+ from datahub.ingestion.source.looker.looker_dataclasses import (
8
+ LookerConstant,
9
+ LookerViewFile,
10
+ )
8
11
  from datahub.ingestion.source.looker.looker_template_language import (
9
12
  load_and_preprocess_file,
10
13
  )
@@ -30,12 +33,14 @@ class LookerViewFileLoader:
30
33
  base_projects_folder: Dict[str, pathlib.Path],
31
34
  reporter: LookMLSourceReport,
32
35
  source_config: LookMLSourceConfig,
36
+ manifest_constants: Dict[str, LookerConstant] = {},
33
37
  ) -> None:
34
38
  self.viewfile_cache: Dict[str, Optional[LookerViewFile]] = {}
35
39
  self._root_project_name = root_project_name
36
40
  self._base_projects_folder = base_projects_folder
37
41
  self.reporter = reporter
38
42
  self.source_config = source_config
43
+ self.manifest_constants = manifest_constants
39
44
 
40
45
  def _load_viewfile(
41
46
  self, project_name: str, path: str, reporter: LookMLSourceReport
@@ -60,7 +65,7 @@ class LookerViewFileLoader:
60
65
  with open(path) as file:
61
66
  raw_file_content = file.read()
62
67
  except Exception as e:
63
- self.reporter.failure(
68
+ self.reporter.report_warning(
64
69
  title="LKML File Loading Error",
65
70
  message="A lookml file is not present on local storage or GitHub",
66
71
  context=f"file path: {path}",
@@ -71,9 +76,15 @@ class LookerViewFileLoader:
71
76
  try:
72
77
  logger.debug(f"Loading viewfile {path}")
73
78
 
79
+ # load_and preprocess_file is called multiple times for loading view file from multiple flows.
80
+ # Flag resolve_constants is a hack to avoid passing around manifest_constants from all of the flows.
81
+ # This is fine as rest of flows do not need resolution of constants.
74
82
  parsed = load_and_preprocess_file(
75
83
  path=path,
84
+ reporter=self.reporter,
76
85
  source_config=self.source_config,
86
+ resolve_constants=True,
87
+ manifest_constants=self.manifest_constants,
77
88
  )
78
89
 
79
90
  looker_viewfile = LookerViewFile.from_looker_dict(
@@ -90,7 +101,7 @@ class LookerViewFileLoader:
90
101
  self.viewfile_cache[path] = looker_viewfile
91
102
  return looker_viewfile
92
103
  except Exception as e:
93
- self.reporter.failure(
104
+ self.reporter.report_warning(
94
105
  title="LKML File Parsing Error",
95
106
  message="The input file is not lookml file",
96
107
  context=f"file path: {path}",
@@ -2,7 +2,7 @@ import logging
2
2
  import pathlib
3
3
  import re
4
4
  from abc import ABC, abstractmethod
5
- from typing import Any, ClassVar, Dict, List, Optional, Set, Union
5
+ from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Set, Union
6
6
 
7
7
  from deepmerge import always_merger
8
8
  from liquid import Undefined
@@ -27,8 +27,12 @@ from datahub.ingestion.source.looker.looker_liquid_tag import (
27
27
  from datahub.ingestion.source.looker.lookml_config import (
28
28
  DERIVED_VIEW_PATTERN,
29
29
  LookMLSourceConfig,
30
+ LookMLSourceReport,
30
31
  )
31
32
 
33
+ if TYPE_CHECKING:
34
+ from datahub.ingestion.source.looker.looker_dataclasses import LookerConstant
35
+
32
36
  logger = logging.getLogger(__name__)
33
37
 
34
38
 
@@ -82,7 +86,12 @@ class SpecialVariable:
82
86
  return self._create_new_liquid_variables_with_default(variables=variables)
83
87
 
84
88
 
85
- def resolve_liquid_variable(text: str, liquid_variable: Dict[Any, Any]) -> str:
89
+ def resolve_liquid_variable(
90
+ text: str,
91
+ view_name: str,
92
+ liquid_variable: Dict[Any, Any],
93
+ report: LookMLSourceReport,
94
+ ) -> str:
86
95
  # Set variable value to NULL if not present in liquid_variable dictionary
87
96
  Undefined.__str__ = lambda instance: "NULL" # type: ignore
88
97
  try:
@@ -96,6 +105,7 @@ def resolve_liquid_variable(text: str, liquid_variable: Dict[Any, Any]) -> str:
96
105
  # Resolve liquid template
97
106
  return create_template(text).render(liquid_variable)
98
107
  except LiquidSyntaxError as e:
108
+ # TODO: Will add warning once we get rid of duplcate warning message for same view
99
109
  logger.warning(f"Unsupported liquid template encountered. error [{e.message}]")
100
110
  # TODO: There are some tag specific to looker and python-liquid library does not understand them. currently
101
111
  # we are not parsing such liquid template.
@@ -103,6 +113,7 @@ def resolve_liquid_variable(text: str, liquid_variable: Dict[Any, Any]) -> str:
103
113
  # See doc: https://cloud.google.com/looker/docs/templated-filters and look for { % condition region %}
104
114
  # order.region { % endcondition %}
105
115
  except CustomTagException as e:
116
+ # TODO: Will add warning once we get rid of duplcate warning message for same view
106
117
  logger.warning(e)
107
118
  logger.debug(e, exc_info=e)
108
119
 
@@ -192,15 +203,20 @@ class LookMLViewTransformer(ABC):
192
203
 
193
204
  source_config: LookMLSourceConfig
194
205
 
195
- def __init__(self, source_config: LookMLSourceConfig):
206
+ def __init__(
207
+ self,
208
+ source_config: LookMLSourceConfig,
209
+ reporter: LookMLSourceReport,
210
+ ):
196
211
  self.source_config = source_config
212
+ self.reporter = reporter
197
213
 
198
214
  def transform(self, view: dict) -> dict:
199
215
  value_to_transform: Optional[str] = None
200
216
 
201
- # is_attribute_supported check is required because not all transformer works on all attributes in current
202
- # case mostly all transformer works on sql_table_name and derived.sql attributes,
203
- # however IncompleteSqlTransformer only transform the derived.sql attribute
217
+ # is_attribute_supported check is required because not all transformers work on all attributes in the current
218
+ # case, mostly all transformers work on sql_table_name and derived.sql attributes;
219
+ # however, IncompleteSqlTransformer only transform the derived.sql attribute
204
220
  if SQL_TABLE_NAME in view and self.is_attribute_supported(SQL_TABLE_NAME):
205
221
  # Give precedence to already processed transformed view.sql_table_name to apply more transformation
206
222
  value_to_transform = view.get(
@@ -252,7 +268,9 @@ class LiquidVariableTransformer(LookMLViewTransformer):
252
268
  def _apply_transformation(self, value: str, view: dict) -> str:
253
269
  return resolve_liquid_variable(
254
270
  text=value,
255
- liquid_variable=self.source_config.liquid_variable,
271
+ liquid_variable=self.source_config.liquid_variables,
272
+ view_name=view["name"],
273
+ report=self.reporter,
256
274
  )
257
275
 
258
276
 
@@ -287,7 +305,7 @@ class IncompleteSqlTransformer(LookMLViewTransformer):
287
305
 
288
306
  class DropDerivedViewPatternTransformer(LookMLViewTransformer):
289
307
  """
290
- drop ${} from datahub_transformed_sql_table_name and view["derived_table"]["datahub_transformed_sql_table_name"] values.
308
+ drop ${} from datahub_transformed_sql_table_name and view["derived_table"]["datahub_transformed_sql_table_name"] values.
291
309
 
292
310
  Example: transform ${employee_income_source.SQL_TABLE_NAME} to employee_income_source.SQL_TABLE_NAME
293
311
  """
@@ -308,8 +326,8 @@ class LookMlIfCommentTransformer(LookMLViewTransformer):
308
326
  evaluate_to_true_regx: str
309
327
  remove_if_comment_line_regx: str
310
328
 
311
- def __init__(self, source_config: LookMLSourceConfig):
312
- super().__init__(source_config=source_config)
329
+ def __init__(self, source_config: LookMLSourceConfig, reporter: LookMLSourceReport):
330
+ super().__init__(source_config=source_config, reporter=reporter)
313
331
 
314
332
  # This regx will keep whatever after -- if looker_environment --
315
333
  self.evaluate_to_true_regx = r"-- if {} --".format(
@@ -335,6 +353,61 @@ class LookMlIfCommentTransformer(LookMLViewTransformer):
335
353
  return self._apply_regx(value)
336
354
 
337
355
 
356
+ class LookmlConstantTransformer(LookMLViewTransformer):
357
+ """
358
+ Replace LookML constants @{constant} from the manifest/configuration.
359
+ """
360
+
361
+ CONSTANT_PATTERN = r"@{(\w+)}" # Matches @{constant}
362
+
363
+ def __init__(
364
+ self,
365
+ source_config: LookMLSourceConfig,
366
+ reporter: LookMLSourceReport,
367
+ manifest_constants: Dict[str, "LookerConstant"],
368
+ ):
369
+ super().__init__(source_config=source_config, reporter=reporter)
370
+ self.manifest_constants = manifest_constants
371
+
372
+ def resolve_lookml_constant(self, text: str, view_name: Optional[str]) -> str:
373
+ """
374
+ Resolves LookML constants (@{ }) from manifest or config.
375
+ Logs warnings for misplaced or missing variables.
376
+ """
377
+
378
+ def replace_constants(match):
379
+ key = match.group(1)
380
+ # Resolve constant from config
381
+ if key in self.source_config.lookml_constants:
382
+ return str(self.source_config.lookml_constants.get(key))
383
+
384
+ # Resolve constant from manifest
385
+ if key in self.manifest_constants:
386
+ return self.manifest_constants[key].value
387
+
388
+ # Check if it's a misplaced lookml constant
389
+ if key in self.source_config.liquid_variables:
390
+ self.reporter.warning(
391
+ title="Misplaced lookml constant",
392
+ message="Use 'lookml_constants' instead of 'liquid_variables'.",
393
+ context=f"Key {key}",
394
+ )
395
+ return f"@{{{key}}}"
396
+
397
+ self.reporter.warning(
398
+ title="LookML constant not found",
399
+ message="The constant is missing. Either add it under 'lookml_constants' in the config or define it in `manifest.lkml`.",
400
+ context=f"view-name: {view_name}, constant: {key}",
401
+ )
402
+ return f"@{{{key}}}"
403
+
404
+ # Resolve @{} (constant)
405
+ return re.sub(self.CONSTANT_PATTERN, replace_constants, text)
406
+
407
+ def _apply_transformation(self, value: str, view: dict) -> str:
408
+ return self.resolve_lookml_constant(text=value, view_name=view.get("name"))
409
+
410
+
338
411
  class TransformedLookMlView:
339
412
  """
340
413
  TransformedLookMlView is collecting output of LookMLViewTransformer and creating a new transformed LookML view.
@@ -390,24 +463,35 @@ class TransformedLookMlView:
390
463
  def process_lookml_template_language(
391
464
  source_config: LookMLSourceConfig,
392
465
  view_lkml_file_dict: dict,
466
+ reporter: LookMLSourceReport,
467
+ manifest_constants: Dict[str, "LookerConstant"] = {},
468
+ resolve_constants: bool = False,
393
469
  ) -> None:
394
470
  if "views" not in view_lkml_file_dict:
395
471
  return
396
472
 
397
473
  transformers: List[LookMLViewTransformer] = [
398
474
  LookMlIfCommentTransformer(
399
- source_config=source_config
475
+ source_config=source_config, reporter=reporter
400
476
  ), # First evaluate the -- if -- comments. Looker does the same
401
477
  LiquidVariableTransformer(
402
- source_config=source_config
478
+ source_config=source_config, reporter=reporter
403
479
  ), # Now resolve liquid variables
404
480
  DropDerivedViewPatternTransformer(
405
- source_config=source_config
481
+ source_config=source_config, reporter=reporter
406
482
  ), # Remove any ${} symbol
407
483
  IncompleteSqlTransformer(
408
- source_config=source_config
484
+ source_config=source_config, reporter=reporter
409
485
  ), # complete any incomplete sql
410
486
  ]
487
+ if resolve_constants:
488
+ transformers.append(
489
+ LookmlConstantTransformer(
490
+ source_config=source_config,
491
+ manifest_constants=manifest_constants,
492
+ reporter=reporter,
493
+ ), # Resolve @{} constant with its corresponding value
494
+ )
411
495
 
412
496
  transformed_views: List[dict] = []
413
497
 
@@ -422,12 +506,18 @@ def process_lookml_template_language(
422
506
  def load_and_preprocess_file(
423
507
  path: Union[str, pathlib.Path],
424
508
  source_config: LookMLSourceConfig,
509
+ reporter: LookMLSourceReport,
510
+ manifest_constants: Dict[str, "LookerConstant"] = {},
511
+ resolve_constants: bool = False,
425
512
  ) -> dict:
426
513
  parsed = load_lkml(path)
427
514
 
428
515
  process_lookml_template_language(
429
516
  view_lkml_file_dict=parsed,
517
+ reporter=reporter,
430
518
  source_config=source_config,
519
+ manifest_constants=manifest_constants,
520
+ resolve_constants=resolve_constants,
431
521
  )
432
522
 
433
523
  return parsed
@@ -139,7 +139,10 @@ class LookMLSourceConfig(
139
139
  )
140
140
  emit_reachable_views_only: bool = Field(
141
141
  True,
142
- description="When enabled, only views that are reachable from explores defined in the model files are emitted",
142
+ description=(
143
+ "When enabled, only views that are reachable from explores defined in the model files are emitted. "
144
+ "If set to False, all views imported in model files are emitted. Views that are unreachable i.e. not explicitly defined in the model files are currently not emitted however reported as warning for debugging purposes."
145
+ ),
143
146
  )
144
147
  populate_sql_logic_for_missing_descriptions: bool = Field(
145
148
  False,
@@ -158,13 +161,27 @@ class LookMLSourceConfig(
158
161
  description="When enabled, looker refinement will be processed to adapt an existing view.",
159
162
  )
160
163
 
161
- liquid_variable: Dict[Any, Any] = Field(
164
+ liquid_variables: Dict[Any, Any] = Field(
162
165
  {},
163
- description="A dictionary containing Liquid variables and their corresponding values, utilized in SQL-defined "
166
+ description="A dictionary containing Liquid variables with their corresponding values, utilized in SQL-defined "
164
167
  "derived views. The Liquid template will be resolved in view.derived_table.sql and "
165
168
  "view.sql_table_name. Defaults to an empty dictionary.",
166
169
  )
167
170
 
171
+ _liquid_variable_deprecated = pydantic_renamed_field(
172
+ old_name="liquid_variable", new_name="liquid_variables", print_warning=True
173
+ )
174
+
175
+ lookml_constants: Dict[str, str] = Field(
176
+ {},
177
+ description=(
178
+ "A dictionary containing LookML constants (`@{constant_name}`) and their values. "
179
+ "If a constant is defined in the `manifest.lkml` file, its value will be used. "
180
+ "If not found in the manifest, the value from this config will be used instead. "
181
+ "Defaults to an empty dictionary."
182
+ ),
183
+ )
184
+
168
185
  looker_environment: Literal["prod", "dev"] = Field(
169
186
  "prod",
170
187
  description="A looker prod or dev environment. "