diff --git a/CHANGELOG.md b/CHANGELOG.md index 061915c..1c4b369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index ae11535..0a1454e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/extension.js b/extension.js index 856f907..5fc7c1a 100644 --- a/extension.js +++ b/extension.js @@ -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 ` to resolve the Python interpreter + * for a PEP 723 script. + * @returns {Promise} 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 @@ -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; @@ -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); @@ -83,4 +165,6 @@ function deactivate() { } module.exports = { activate, deactivate, + hasPep723Metadata, + getUvScriptPythonPath, }; diff --git a/package.json b/package.json index 7b26332..453e940 100644 --- a/package.json +++ b/package.json @@ -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" }, @@ -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." } } }