88import string
99import tempfile
1010import warnings
11+ from functools import cache
1112from pathlib import Path
1213from typing import List , Optional , Set , Tuple
1314from uuid import uuid4
1718from google .cloud import bigquery
1819from jinja2 import Environment , FileSystemLoader
1920
20- from bigquery_etl .config import ConfigLoader
21+ from bigquery_etl .config import BQETL_PROJECT_CONFIG , ConfigLoader
2122from bigquery_etl .format_sql .formatter import reformat
2223from bigquery_etl .metrics import MetricHub
2324
@@ -70,6 +71,18 @@ def random_str(length: int = 12) -> str:
7071 return "" .join (random .choice (string .ascii_lowercase ) for i in range (length ))
7172
7273
74+ @cache
75+ def get_bqetl_project_root () -> Path | None :
76+ """Return the root path of the bqetl project the user is currently in."""
77+ cwd = Path .cwd ()
78+ search_paths = [cwd ]
79+ search_paths .extend (cwd .parents )
80+ for possible_project_root in search_paths :
81+ if (possible_project_root / BQETL_PROJECT_CONFIG ).exists ():
82+ return possible_project_root
83+ return None
84+
85+
7386def render (
7487 sql_filename ,
7588 template_folder = "." ,
@@ -78,7 +91,8 @@ def render(
7891 ** kwargs ,
7992) -> str :
8093 """Render a given template query using Jinja."""
81- path = Path (template_folder ) / sql_filename
94+ template_folder_path = Path (template_folder )
95+ path = template_folder_path / sql_filename
8296 skip = {
8397 file
8498 for skip in ConfigLoader .get ("render" , "skip" , fallback = [])
@@ -122,14 +136,20 @@ def render(
122136 checks_template .write_text (
123137 macro_imports
124138 + "\n "
125- + (Path ( template_folder ) / sql_filename ).read_text ()
139+ + (template_folder_path / sql_filename ).read_text ()
126140 )
127141
128142 file_loader = FileSystemLoader (f"{ str (checks_template .parent )} " )
129143 env = Environment (loader = file_loader )
130144 main_sql = env .get_template (checks_template .name )
131145 else :
132- file_loader = FileSystemLoader (f"{ template_folder } " )
146+ # Add the bigquery-etl project root to the search path to support Jinja imports/includes.
147+ file_loader_search_paths = [template_folder_path , ROOT ]
148+ # Also dynamically detect the project root so imports/includes in the private-bigquery-etl repo work.
149+ if bqetl_project_root := get_bqetl_project_root ():
150+ if bqetl_project_root not in file_loader_search_paths :
151+ file_loader_search_paths .append (bqetl_project_root )
152+ file_loader = FileSystemLoader (file_loader_search_paths )
133153 env = Environment (loader = file_loader )
134154 main_sql = env .get_template (sql_filename )
135155
0 commit comments