Skip to content

Commit 8d4cabb

Browse files
authored
Install dbt (#731)
* Install dbt * Review remarks
1 parent d5d1421 commit 8d4cabb

File tree

9 files changed

+212
-33
lines changed

9 files changed

+212
-33
lines changed

package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"walkthrough.initialSetup.steps.selectInterpreter.title": "Select Python Interpreter",
1313
"walkthrough.initialSetup.steps.selectInterpreter.description": "Choose the Python Interpreter where you have installed dbt.\n[Select Python Interpreter](command:python.setInterpreter)",
1414
"walkthrough.initialSetup.steps.installDbt.title": "Install dbt",
15-
"walkthrough.initialSetup.steps.installDbt.description": "Please install dbt in the selected Python Environment and validate the dbt installation.\n[Validate dbt installation](command:dbtPowerUser.checkIfDbtIsInstalled)",
15+
"walkthrough.initialSetup.steps.installDbt.description": "Please install dbt in the selected Python Environment.\n[Install dbt](command:dbtPowerUser.installDbt)",
1616
"walkthrough.initialSetup.steps.updateExtension.title": "Update dbt Power User Extension",
1717
"walkthrough.initialSetup.steps.updateExtension.description": "To get the latest features, update the dbt Power User extension.\n[Update Extension](command:workbench.extensions.action.extensionUpdates)",
1818
"walkthrough.initialSetup.steps.associateFileExts.title": "Associate File Types",

src/commands/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,15 @@ export class VSCodeCommands implements Disposable {
2929
private walkthroughCommands: WalkthroughCommands,
3030
) {
3131
this.disposables.push(
32-
commands.registerCommand("dbtPowerUser.checkIfDbtIsInstalled", () =>
33-
this.dbtProjectContainer.detectDBT(),
32+
commands.registerCommand(
33+
"dbtPowerUser.checkIfDbtIsInstalled",
34+
async () => {
35+
await this.dbtProjectContainer.detectDBT();
36+
this.dbtProjectContainer.initializePythonBridges();
37+
},
38+
),
39+
commands.registerCommand("dbtPowerUser.installDbt", () =>
40+
this.walkthroughCommands.installDbt(),
3441
),
3542
commands.registerCommand("dbtPowerUser.runCurrentModel", () =>
3643
this.runModel.runModelOnActiveWindow(),

src/commands/walkthroughCommands.ts

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { window, version, commands } from "vscode";
1+
import { window, QuickPickItem, ProgressLocation, commands } from "vscode";
22
import { provideSingleton } from "../utils";
33
import { DBTProjectContainer } from "../manifest/dbtProjectContainer";
44
import { CommandProcessExecutionFactory } from "../commandProcessExecution";
@@ -12,12 +12,14 @@ enum PromptAnswer {
1212
NO = "No",
1313
}
1414

15+
enum DbtInstallationPromptAnswer {
16+
INSTALL = "Install dbt",
17+
}
18+
1519
@provideSingleton(WalkthroughCommands)
1620
export class WalkthroughCommands {
1721
constructor(
1822
private dbtProjectContainer: DBTProjectContainer,
19-
private commandProcessExecutionFactory: CommandProcessExecutionFactory,
20-
private terminal: DBTTerminal,
2123
private telemetry: TelemetryService,
2224
private dbtCommandFactory: DBTCommandFactory,
2325
) {}
@@ -138,4 +140,106 @@ export class WalkthroughCommands {
138140
const latestVersion = extLatest.tag_name.toString();
139141
return currentVersion < latestVersion;
140142
}
143+
144+
async installDbt(): Promise<void> {
145+
const dbtVersion: QuickPickItem | undefined = await window.showQuickPick(
146+
["1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7"].map((value) => ({
147+
label: value,
148+
})),
149+
{
150+
title: "Select your dbt version",
151+
canPickMany: false,
152+
},
153+
);
154+
if (dbtVersion) {
155+
const adapter: QuickPickItem | undefined = await window.showQuickPick(
156+
[
157+
"snowflake",
158+
"bigquery",
159+
"redshift",
160+
"postgres",
161+
"databricks",
162+
"sqlserver",
163+
"duckdb",
164+
"athena",
165+
"spark",
166+
"clickhouse",
167+
"trino",
168+
"synapse",
169+
].map((value) => ({
170+
label: value,
171+
})),
172+
{
173+
title: "Select your adapter",
174+
canPickMany: false,
175+
},
176+
);
177+
if (adapter && adapter.label) {
178+
const packageVersion = dbtVersion.label;
179+
const packageName = this.mapToAdapterPackage(adapter.label);
180+
let error = undefined;
181+
await window.withProgress(
182+
{
183+
title: `Installing ${packageName} ${packageVersion}...`,
184+
location: ProgressLocation.Notification,
185+
cancellable: false,
186+
},
187+
async () => {
188+
try {
189+
await this.dbtProjectContainer.runCommandAndReturnResults(
190+
this.dbtCommandFactory.createDbtInstallCommand(
191+
packageName,
192+
packageVersion,
193+
),
194+
);
195+
await this.dbtProjectContainer.detectDBT();
196+
this.dbtProjectContainer.initializePythonBridges();
197+
} catch (err) {
198+
console.log(err);
199+
error = err;
200+
}
201+
},
202+
);
203+
if (error) {
204+
const answer = await window.showErrorMessage(
205+
"Could not install dbt: " + error,
206+
DbtInstallationPromptAnswer.INSTALL,
207+
);
208+
if (answer === DbtInstallationPromptAnswer.INSTALL) {
209+
commands.executeCommand("dbtPowerUser.installDbt");
210+
}
211+
}
212+
}
213+
}
214+
}
215+
216+
private mapToAdapterPackage(adapter: string): string {
217+
switch (adapter) {
218+
case "snowflake":
219+
return "dbt-snowflake";
220+
case "bigquery":
221+
return "dbt-bigquery";
222+
case "redshift":
223+
return "dbt-redshift";
224+
case "postgres":
225+
return "dbt-postgres";
226+
case "databricks":
227+
return "dbt-databricks";
228+
case "sqlserver":
229+
return "dbt-sqlserver";
230+
case "duckdb":
231+
return "dbt-duckdb";
232+
case "athena":
233+
return "dbt-athena-community";
234+
case "spark":
235+
return "dbt-spark";
236+
case "clickhouse":
237+
return "dbt-clickhouse";
238+
case "trino":
239+
return "dbt-trino";
240+
case "synapse":
241+
return "dbt-synapse";
242+
}
243+
throw new Error("Adapter is not supported" + adapter);
244+
}
141245
}

src/dbt_client/dbtCommandFactory.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,17 @@ export class DBTCommandFactory {
231231
};
232232
}
233233

234+
createDbtInstallCommand(adapter: string, version: string): DBTCommand {
235+
return {
236+
commandAsString: `pip install ${adapter}==${version}`,
237+
statusMessage: `Installing ${adapter} ${version}...`,
238+
processExecutionParams: {
239+
args: ["-m", "pip", "install", `${adapter}==${version}`],
240+
},
241+
focus: true,
242+
};
243+
}
244+
234245
createInstallDepsCommand(projectRoot: Uri, profilesDir: string): DBTCommand {
235246
const profilesDirParams = this.profilesDirParams(profilesDir);
236247
return {

src/dbt_client/dbtVersionEvent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export interface DBTInstallationVerificationEvent {
22
inProgress: boolean;
3+
pythonInstalled: boolean;
34
dbtInstallationFound?: {
45
installed: boolean;
56
latestVersion?: string;

src/dbt_client/index.ts

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ import { DBTCommandQueue } from "./dbtCommandQueue";
1717
import { DBTTerminal } from "./dbtTerminal";
1818
import { DBTInstallationVerificationEvent } from "./dbtVersionEvent";
1919
import { TelemetryService } from "../telemetry";
20+
import { existsSync } from "fs";
21+
22+
enum DbtInstallationPromptAnswer {
23+
INSTALL = "Install dbt",
24+
}
25+
26+
enum PythonInterpreterPromptAnswer {
27+
SELECT = "Select Python interpreter",
28+
}
2029

2130
@provideSingleton(DBTClient)
2231
export class DBTClient implements Disposable {
@@ -31,6 +40,7 @@ export class DBTClient implements Disposable {
3140
/latest.*:\s*(\d{1,2}\.\d{1,2}\.\d{1,2})/g;
3241
private static readonly IS_INSTALLED = /installed/g;
3342
private dbtInstalled?: boolean;
43+
private pythonInstalled?: boolean;
3444
private disposables: Disposable[] = [
3545
this._onDBTInstallationVerificationEvent,
3646
];
@@ -66,6 +76,7 @@ export class DBTClient implements Disposable {
6676
private async checkAllInstalled(): Promise<void> {
6777
this._onDBTInstallationVerificationEvent.fire({
6878
inProgress: true,
79+
pythonInstalled: this.pythonPathExists(),
6980
});
7081
this.dbtInstalled = undefined;
7182
try {
@@ -74,12 +85,6 @@ export class DBTClient implements Disposable {
7485
this.dbtCommandFactory.createVerifyDbtInstalledCommand(),
7586
);
7687
await checkDBTInstalledProcess.complete();
77-
this.dbtInstalled = true;
78-
commands.executeCommand(
79-
"setContext",
80-
"dbtPowerUser.dbtInstalled",
81-
this.dbtInstalled,
82-
);
8388
} catch (error) {
8489
this.telemetry.sendTelemetryError("dbtInstalledCheckError", error);
8590
this.dbtInstalled = false;
@@ -118,14 +123,35 @@ export class DBTClient implements Disposable {
118123
this.raiseDBTVersionCouldNotBeDeterminedEvent();
119124
}
120125

121-
addCommandToQueue(command: DBTCommand) {
126+
async showDbtNotInstalledErrorMessageIfDbtIsNotInstalled() {
127+
if (!this.pythonInstalled) {
128+
const answer = await window.showErrorMessage(
129+
"No Python interpreter is selected or Python is not installed",
130+
PythonInterpreterPromptAnswer.SELECT,
131+
);
132+
if (answer === PythonInterpreterPromptAnswer.SELECT) {
133+
commands.executeCommand("python.setInterpreter");
134+
}
135+
return true;
136+
}
122137
if (!this.dbtInstalled) {
123-
if (command.focus) {
124-
window.showErrorMessage(
125-
"Please ensure dbt is installed in your selected Python environment.",
126-
);
138+
const answer = await window.showErrorMessage(
139+
"Please ensure dbt is installed in your selected Python environment.",
140+
DbtInstallationPromptAnswer.INSTALL,
141+
);
142+
if (answer === DbtInstallationPromptAnswer.INSTALL) {
143+
commands.executeCommand("dbtPowerUser.installDbt");
144+
}
145+
return true;
146+
}
147+
return false;
148+
}
149+
150+
async addCommandToQueue(command: DBTCommand) {
151+
if (command.focus) {
152+
if (await this.showDbtNotInstalledErrorMessageIfDbtIsNotInstalled()) {
153+
return;
127154
}
128-
return;
129155
}
130156

131157
this.queue.addToQueue({
@@ -195,6 +221,7 @@ export class DBTClient implements Disposable {
195221
message: string | undefined = undefined,
196222
): void {
197223
this.dbtInstalled = dbtInstalled;
224+
this.pythonInstalled = this.pythonPathExists();
198225
const upToDate =
199226
installedVersion !== undefined &&
200227
latestVersion !== undefined &&
@@ -219,13 +246,27 @@ export class DBTClient implements Disposable {
219246
});
220247
this._onDBTInstallationVerificationEvent.fire({
221248
inProgress: false,
249+
pythonInstalled: this.pythonInstalled,
222250
dbtInstallationFound: {
223251
installed: this.dbtInstalled,
224252
installedVersion,
225253
latestVersion,
226254
upToDate,
227255
},
228256
});
257+
commands.executeCommand(
258+
"setContext",
259+
"dbtPowerUser.dbtInstalled",
260+
this.dbtInstalled,
261+
);
262+
this.showDbtNotInstalledErrorMessageIfDbtIsNotInstalled();
263+
}
264+
265+
private pythonPathExists() {
266+
return (
267+
this.pythonEnvironment.pythonPath !== undefined &&
268+
existsSync(this.pythonEnvironment.pythonPath)
269+
);
229270
}
230271

231272
private checkIfDBTIsUpToDate(message: string): void {

src/manifest/dbtProject.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -195,20 +195,17 @@ export class DBTProject implements Disposable {
195195
this.pythonBridgeDiagnostics,
196196
this.projectConfigDiagnostics,
197197
);
198-
this.initializePythonBridge(
199-
this.PythonEnvironment.pythonPath,
200-
this.PythonEnvironment.environmentVariables,
201-
);
198+
this.initializePythonBridge();
202199
}
203200

204201
public getProjectName() {
205202
return this.projectName;
206203
}
207204

208-
private async initializePythonBridge(
209-
pythonPath: string,
210-
envVars: EnvironmentVariables,
211-
) {
205+
async initializePythonBridge() {
206+
let pythonPath = this.PythonEnvironment.pythonPath;
207+
const envVars = this.PythonEnvironment.environmentVariables;
208+
212209
if (this.python !== undefined) {
213210
// Python env has changed
214211
this.pythonBridgeInitialized = false;
@@ -269,10 +266,7 @@ export class DBTProject implements Disposable {
269266
}
270267

271268
private async onPythonEnvironmentChanged() {
272-
this.initializePythonBridge(
273-
this.PythonEnvironment.pythonPath,
274-
this.PythonEnvironment.environmentVariables,
275-
);
269+
this.initializePythonBridge();
276270
}
277271

278272
private async tryRefresh() {
@@ -459,7 +453,11 @@ export class DBTProject implements Disposable {
459453

460454
async compileNode(modelName: string): Promise<string | undefined> {
461455
await this.blockUntilPythonBridgeIsInitalized();
462-
456+
if (
457+
await this.dbtProjectContainer.showDbtNotInstalledErrorMessageIfDbtIsNotInstalled()
458+
) {
459+
return;
460+
}
463461
if (!this.pythonBridgeInitialized) {
464462
window.showErrorMessage(
465463
extendErrorWithSupportLinks(
@@ -512,7 +510,11 @@ export class DBTProject implements Disposable {
512510

513511
async compileQuery(query: string): Promise<string | undefined> {
514512
await this.blockUntilPythonBridgeIsInitalized();
515-
513+
if (
514+
await this.dbtProjectContainer.showDbtNotInstalledErrorMessageIfDbtIsNotInstalled()
515+
) {
516+
return;
517+
}
516518
if (!this.pythonBridgeInitialized) {
517519
window.showErrorMessage(
518520
extendErrorWithSupportLinks(
@@ -861,6 +863,11 @@ select * from renamed
861863

862864
async executeSQL(query: string) {
863865
await this.blockUntilPythonBridgeIsInitalized();
866+
if (
867+
await this.dbtProjectContainer.showDbtNotInstalledErrorMessageIfDbtIsNotInstalled()
868+
) {
869+
return;
870+
}
864871
if (!this.pythonBridgeInitialized) {
865872
window.showErrorMessage(
866873
extendErrorWithSupportLinks(

0 commit comments

Comments
 (0)