Skip to content

Commit a87735f

Browse files
committed
feat: support linting or formatting whole project with multithreading and manage dbt deferral from server
1 parent 54776d2 commit a87735f

File tree

3 files changed

+135
-67
lines changed

3 files changed

+135
-67
lines changed

src/dbt_core_interface/client.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,13 @@ def lint_sql(
217217
if raw_sql is not None and sql_path is None:
218218
data = raw_sql
219219
headers = {"Content-Type": "text/plain"}
220-
resp = self._request("POST", "/api/v1/lint", params=params, data=data, headers=headers)
220+
resp = self._request(
221+
"POST" if data is not None else "GET",
222+
"/api/v1/lint",
223+
params=params,
224+
data=data,
225+
headers=headers,
226+
)
221227
return ServerLintResult.model_validate(resp.json())
222228

223229
def format_sql(
@@ -237,7 +243,13 @@ def format_sql(
237243
if raw_sql is not None and sql_path is None:
238244
data = raw_sql
239245
headers = {"Content-Type": "text/plain"}
240-
resp = self._request("POST", "/api/v1/format", params=params, data=data, headers=headers)
246+
resp = self._request(
247+
"POST" if data is not None else "GET",
248+
"/api/v1/format",
249+
params=params,
250+
data=data,
251+
headers=headers,
252+
)
241253
return ServerFormatResult.model_validate(resp.json())
242254

243255
def parse_project(
@@ -290,6 +302,24 @@ def command(
290302
source_freshness = functools.partialmethod(command, "source freshness")
291303
test = functools.partialmethod(command, "test")
292304

305+
def inject_state(self, directory: Path | str) -> dict[str, t.Any]:
306+
"""Inject manifest state for dbt deferral into the server."""
307+
resp = self._request(
308+
"GET",
309+
"/api/v1/state",
310+
json_payload={"directory": str(directory)},
311+
)
312+
if resp.ok:
313+
return {}
314+
return resp.json()
315+
316+
def clear_state(self) -> dict[str, t.Any]:
317+
"""Clear the deferral manifest state on the server."""
318+
resp = self._request("DELETE", "/api/v1/state")
319+
if resp.ok:
320+
return {}
321+
return resp.json()
322+
293323
def status(self) -> dict[str, t.Any]:
294324
"""Check server diagnostic status."""
295325
resp = self._request("GET", "/api/v1/status")

src/dbt_core_interface/project.py

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ def lint(
801801
extra_config_path: Path | str | None = None,
802802
ignore_local_config: bool = False,
803803
fluff_conf: FluffConfig | None = None,
804-
) -> LintingRecord | None:
804+
) -> list[LintingRecord]:
805805
"""Lint specified file or SQL string."""
806806
from sqlfluff.cli.commands import get_linter_and_formatter
807807

@@ -815,15 +815,34 @@ def lint(
815815
lint, _ = get_linter_and_formatter(fluff_conf)
816816

817817
if sql is None:
818-
# TODO: lint whole project
819-
return
818+
819+
def _lint(node: ManifestNode) -> list[LintingRecord]:
820+
records: list[LintingRecord] = []
821+
try:
822+
if node.resource_type == "model":
823+
records = self.lint(
824+
Path(node.original_file_path),
825+
extra_config_path=extra_config_path,
826+
ignore_local_config=ignore_local_config,
827+
fluff_conf=fluff_conf,
828+
)
829+
except Exception as e:
830+
logger.error(f"Error formatting node {node.name}: {e}")
831+
return records
832+
833+
all_records: list[LintingRecord] = []
834+
for records in self.pool.map(_lint, self.manifest.nodes.values()):
835+
all_records.extend(records)
836+
837+
return all_records
820838
elif isinstance(sql, str):
821839
result = lint.lint_string_wrapped(sql)
822840
else:
841+
if not sql.is_absolute() and not sql.exists():
842+
sql = self.project_root / sql
823843
result = lint.lint_paths((str(sql),), ignore_files=False)
824844

825-
records = result.as_records()
826-
return records[0] if records else None
845+
return result.as_records()
827846

828847
def format(
829848
self,
@@ -836,13 +855,6 @@ def format(
836855
from sqlfluff.cli.commands import get_linter_and_formatter
837856
from sqlfluff.core import SQLLintError
838857

839-
logger.info(f"""format_command(
840-
{self.project_root},
841-
{str(sql)[:100]},
842-
{extra_config_path},
843-
{ignore_local_config})
844-
""")
845-
846858
fluff_conf = fluff_conf or self.get_sqlfluff_configuration(
847859
sql if isinstance(sql, Path) else None,
848860
extra_config_path,
@@ -868,11 +880,27 @@ def format(
868880

869881
result_sql = None
870882
if sql is None:
871-
# TODO: format whole project
872-
return True, result_sql
883+
884+
def _format(node: ManifestNode) -> bool:
885+
success = True
886+
try:
887+
if node.resource_type == "model":
888+
success, _ = self.format(
889+
Path(node.original_file_path),
890+
extra_config_path=extra_config_path,
891+
ignore_local_config=ignore_local_config,
892+
fluff_conf=fluff_conf,
893+
)
894+
except Exception as e:
895+
logger.error(f"Error formatting node {node.name}: {e}")
896+
success = False
897+
return success
898+
899+
return all(res for res in self.pool.map(_format, self.manifest.nodes.values())), None
873900
if isinstance(sql, str):
874901
logger.info(f"Formatting SQL string: {sql[:100]}")
875902
result = lint.lint_string_wrapped(sql, fname="stdin", fix=True)
903+
876904
_, num_filtered_errors = result.count_tmp_prs_errors()
877905
result.discard_fixes_for_lint_errors_in_files_with_tmp_or_prs_errors()
878906
success = not num_filtered_errors
@@ -886,6 +914,8 @@ def format(
886914
logger.info("No fixable errors in SQL string")
887915
result_sql = sql
888916
else:
917+
if not sql.is_absolute() and not sql.exists():
918+
sql = self.project_root / sql
889919
logger.info(f"Formatting SQL file: {sql}")
890920
before_modified = datetime.fromtimestamp(sql.stat().st_mtime).strftime(
891921
"%Y-%m-%d %H:%M:%S"
@@ -898,6 +928,7 @@ def format(
898928
apply_fixes=True,
899929
fix_even_unparsable=False,
900930
)
931+
901932
_, num_filtered_errors = lint_result.count_tmp_prs_errors()
902933
lint_result.discard_fixes_for_lint_errors_in_files_with_tmp_or_prs_errors()
903934
success = not num_filtered_errors

0 commit comments

Comments
 (0)