Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to the "python-envy" extension will be documented in this file.

## [0.1.12]
- Add PEP 723 inline script metadata support. When a Python file contains `# /// script` metadata,
`uv python find --script` is used to resolve and activate the correct Python interpreter.
Requires [uv](https://docs.astral.sh/uv/) to be installed. Can be toggled with `pythonEnvy.enablePep723`.

## [0.1.11]
- Fix issue [#8](https://github.com/teticio/python-envy/issues/8) introduce option to disable notification
when the interpreter gets activated/switched.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This extension has the following settings:

* `pythonEnvy.venv`: Location of the virtual environments. Set to `.venv` by default.
* `pythonEnvy.showNotifications`: Show information messages when Python interpreter is automatically switched. Set to `true` by default.
* `pythonEnvy.enablePep723`: Detect [PEP 723](https://peps.python.org/pep-0723/) inline script metadata and use `uv python find --script` to activate the appropriate Python interpreter. Requires [uv](https://docs.astral.sh/uv/) to be installed. Set to `true` by default.

## Known Issues

Expand Down
86 changes: 85 additions & 1 deletion extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@ const { PythonExtension } = require("@vscode/python-extension");
const vscode = require("vscode");
const fs = require("fs");
const path = require("path");
const { execFile } = require("child_process");
const { promisify } = require("util");

const execFileAsync = promisify(execFile);

/**
* Check if a file contains PEP 723 inline script metadata.
* PEP 723 format:
* # /// script
* # ...TOML metadata...
* # ///
*/
function hasPep723Metadata(filePath) {
try {
const content = fs.readFileSync(filePath, "utf-8");
return /^# \/\/\/ script\s*$/m.test(content);
} catch (_err) {
return false;
}
}

/**
* Run `uv python find --script <filePath>` to resolve the Python interpreter
* for a PEP 723 script.
* @returns {Promise<string|null>} The Python interpreter path, or null on failure.
*/
async function getUvScriptPythonPath(filePath) {
try {
const { stdout } = await execFileAsync("uv", ["python", "find", "--script", filePath]);
return stdout.trim() || null;
} catch (_err) {
return null;
}
}

/**
* @param {vscode.ExtensionContext} context
Expand All @@ -24,7 +58,8 @@ async function activate(context) {
}

async function setupPythonEnvironment(editor, pythonApi) {
let currentDir = path.dirname(editor.document.uri.fsPath);
const filePath = editor.document.uri.fsPath;
let currentDir = path.dirname(filePath);
const root = path.parse(currentDir).root;
const currentWorkspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(editor.document.uri.path));
const currentWorkspaceFolderPath = currentWorkspaceFolder ? currentWorkspaceFolder.uri.path : null;
Expand All @@ -33,6 +68,53 @@ async function setupPythonEnvironment(editor, pythonApi) {
const config = vscode.workspace.getConfiguration('pythonEnvy');
const venvName = config.get('venvName');
const showNotifications = config.get('showNotifications', true); // Default to true for backward compatibility
const enablePep723 = config.get('enablePep723', true);

// Check for PEP 723 inline script metadata (requires uv)
if (enablePep723 && filePath.endsWith(".py") && hasPep723Metadata(filePath)) {
const pythonPath = await getUvScriptPythonPath(filePath);
if (pythonPath) {
if (!fs.existsSync(pythonPath)) {
if (showNotifications) {
vscode.window.showInformationMessage(
"Python Envy: PEP 723 script environment has not been created yet."
);
}
return;
}

const currentPythonPath =
pythonApi.environments.getActiveEnvironmentPath(
currentWorkspaceFolder ? currentWorkspaceFolder.uri : undefined
);

if (currentPythonPath.path !== pythonPath) {
try {
await pythonApi.environments.updateActiveEnvironmentPath(
pythonPath,
currentWorkspaceFolder ? currentWorkspaceFolder.uri : undefined
);

if (showNotifications) {
const displayPath = currentWorkspaceFolderPath
? path.relative(currentWorkspaceFolderPath, pythonPath)
: pythonPath;
const folderName = currentWorkspaceFolder
? " for " + currentWorkspaceFolder.name
: "";
vscode.window.showInformationMessage(
`Python Envy: PEP 723 script interpreter set to ${displayPath}${folderName}`
);
}
} catch (error) {
vscode.window.showErrorMessage(
`Python Envy: error setting Python interpreter for PEP 723 script: ${error.message}`
);
}
}
return;
}
}

while (currentDir !== root) {
const venvPath = path.join(currentDir, venvName);
Expand Down Expand Up @@ -83,4 +165,6 @@ function deactivate() { }
module.exports = {
activate,
deactivate,
hasPep723Metadata,
getUvScriptPythonPath,
};
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"type": "git",
"url": "https://github.com/teticio/python-envy.git"
},
"version": "0.1.11",
"version": "0.1.12",
"engines": {
"vscode": "^1.85.0"
},
Expand Down Expand Up @@ -51,6 +51,11 @@
"type": "boolean",
"default": true,
"description": "Show information messages when Python interpreter is automatically switched"
},
"pythonEnvy.enablePep723": {
"type": "boolean",
"default": true,
"description": "Detect PEP 723 inline script metadata and use `uv python find --script` to activate the appropriate Python interpreter. Requires uv to be installed."
}
}
}
Expand Down