Skip to content
Merged
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
11 changes: 7 additions & 4 deletions .vscode-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ const diagnosticMocha = diagnosticsDir
? { require: [path.resolve('out', 'test', 'diagnostics.js')] }
: {};

// Seed user settings.json so the extension actually activates: without
// `python.useEnvironmentsExtension=true` it short-circuits during activation
// and the smoke/e2e/integration tests see no registered managers.
// Seed explicit user values so tests are deterministic even when VS Code applies
// experimentation defaults. The environments extension must activate, while the
// inline-script preview stays off unless a dedicated test host opts in.
const userDir = path.join(userDataDir, 'User');
fs.mkdirSync(userDir, { recursive: true });
fs.writeFileSync(
path.join(userDir, 'settings.json'),
JSON.stringify({ 'python.useEnvironmentsExtension': true }) + '\n',
JSON.stringify({
'python.useEnvironmentsExtension': true,
'python-envs.inlineScripts.enabled': false,
}) + '\n',
);

export default defineConfig([
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ The "Python Projects" fold shows you all of the projects that are currently in y

The "Environment Managers" fold shows you all of the environment managers that are available on your machine with all related environments nested below. From this view, you can create new environments, delete old environments, and manage packages.

The PEP 723 inline-script preview can create and select cached environments from dependency metadata embedded in standalone Python files. The preview is controlled by `python-envs.inlineScripts.enabled`, defaults to `false`, and requires a window reload after the effective value changes.

<img src="https://raw.githubusercontent.com/microsoft/vscode-python-environments/main/images/python-envs-overview.gif" width=734 height=413>

### Environment Management
Expand Down Expand Up @@ -118,6 +120,8 @@ All commands can be accessed via the Command Palette (`ctrl/cmd + Shift + P`):
| Deactivate Environment in Current Terminal | Deactivates environment in currently opened terminal. |
| Run as Task | Runs Python module as a task. |
| Create New Project from Template | Creates scaffolded project with virtual environments based on a template. |
| Set Up Environments for Inline Script Files | Selects PEP 723 scripts and creates or reuses their declared environments when the inline-script preview is enabled. |
| Clear Inline Script Environment Cache | Deletes cached PEP 723 script environments and clears their known associations after confirmation. |

## Settings Reference

Expand All @@ -128,6 +132,7 @@ All commands can be accessed via the Command Palette (`ctrl/cmd + Shift + P`):
| defaultEnvManager | `"ms-python.python:venv"` | The default environment manager used for creating and managing environments. |
| defaultPackageManager | `"ms-python.python:pip"` | The default package manager to use for installing and managing packages. This is often dictated by the default environment manager but can be customized. |
| pythonProjects | `[]` | A list of Python workspaces, specified by the path, in which you can set particular environment and package managers. You can set information for a workspace as `[{"path": "/path/to/workspace", "envManager": "ms-python.python:venv", "packageManager": "ms-python.python:pip"]}`. |
| inlineScripts.enabled | `false` | (preview) Enables creating and selecting environments from PEP 723 inline script metadata. The effective default may be enabled through gradual rollout; an explicit user or workspace value takes precedence. Reload the window after changing this setting. |
| terminal.showActivateButton | `false` | (experimental) Show a button in the terminal to activate/deactivate the current environment for the terminal. This button is only shown if the active terminal is associated with a project that has an activatable environment. |
| terminal.autoActivationType | `"command"` | Specifies how the extension can activate an environment in a terminal. Accepted values: `command` (execute activation command in terminal), `shellStartup` (`terminal.integrated.shellIntegration.enabled` successfully enabled or we may modify shell startup scripts ), `off` (no auto-activation). Shell startup is only supported for: zsh, fish, pwsh, bash, cmd. **Takes precedence over** `python.terminal.activateEnvironment`. Restart terminals after changing this setting. To revert shell startup changes, run `Python Envs: Revert Shell Startup Script Changes`. |
| alwaysUseUv | `true` | When `true`, [uv](https://github.com/astral-sh/uv) will be used to manage all virtual environments if available. When `false`, uv will only manage virtual environments explicitly created by uv. The extension uses `uv` on its PATH, or the executable it installed during the current session. If unavailable in a trusted workspace, it also checks that workspace's `.pyprojectx/main/uv` (`uv.exe` on Windows) for environments inside that workspace. External environments require uv on PATH or a current-session installation. This does not alter the integrated terminal's PATH. |
Expand Down
2 changes: 2 additions & 0 deletions docs/managing-python-projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ my_package_project/

When you create a script, the extension generates a single `.py` file with PEP 723 inline script metadata, which allows you to specify dependencies directly in the file.

> **Preview:** Inline-script environment support is controlled by `python-envs.inlineScripts.enabled` and defaults to `false`. The effective default may be enabled for some users through gradual rollout. You can explicitly enable or disable the setting at user or workspace scope; reload the window after changing it.

An inline-script environment is built from the script's `# /// script` block and stored in the extension's cache, where it is shared by every script with the same dependencies and base interpreter. Because editing one would silently change the others, these environments are not user-managed: the Python Environments views do not offer install, uninstall, or version-change actions for them. Their package list remains visible.

A CodeLens stays above the `# /// script` block, including while its metadata is incomplete, malformed, or unsaved. It offers **Set up environment for this script** until the block matches a validated environment, then displays **Script environment ready (Python X.Y.Z)** as persistent, non-clickable text. The ready label also appears for environments restored after reopening VS Code.
Expand Down
11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@
}
}
},
"python-envs.inlineScripts.enabled": {
"type": "boolean",
"description": "%python-envs.inlineScripts.enabled.description%",
"default": false,
"scope": "window",
"requiresReload": true,
"tags": [
"onExP",
"preview"
]
},
"python-envs.terminal.showActivateButton": {
"type": "boolean",
"description": "%python-envs.terminal.showActivateButton.description%",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"python-envs.pythonProjects.path.description": "The path to a folder or file in the workspace to be treated as a Python project.",
"python-envs.pythonProjects.envManager.description": "The environment manager for creating and managing environments for this project.",
"python-envs.pythonProjects.packageManager.description": "The package manager for managing packages in environments for this project.",
"python-envs.inlineScripts.enabled.description": "Enables preview support for creating and selecting Python environments from PEP 723 inline script metadata. Reload the window after changing this setting.",
"python-envs.terminal.showActivateButton.description": "Whether to show the 'Activate' button in the terminal menu",
"python-envs.terminal.autoActivationType.description": "Specifies how the extension can activate an environment in a terminal.\n\nShell startup activates using [shell integration](https://code.visualstudio.com/docs/terminal/shell-integration) or may require changes to the shell initialization files and is only enabled for the following shells: zsh, fsh, pwsh, bash, cmd. When set to `command`, any shell can be activated.\n\n**Legacy Setting Support:** This setting takes precedence over the legacy `python.terminal.activateEnvironment` setting. If this setting is not explicitly set and `python.terminal.activateEnvironment` is set to false, this setting will automatically be set to `off` to preserve your preference.\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\nTo revert changes made during shellStartup, run `Python Envs: Revert Shell Startup Script Changes`.",
"python-envs.terminal.autoActivationType.command": "Activation by executing a command in the terminal.",
Expand Down
3 changes: 1 addition & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ import { TemporaryStateManager } from './features/views/temporaryStateManager';
import { PythonEnvTreeItem } from './features/views/treeViewItems';
import {
getEnvManagerAndPackageManagerConfigLevels,
isInlineScriptsFeatureEnabled,
runPetInTerminalImpl,
} from './helpers';
import type { ProjectCreators } from './features/creators/projectCreators';
Expand Down Expand Up @@ -402,7 +401,7 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
commands.registerCommand('python-envs.clearCache', async () => {
await clearEnvironmentCachesCommand(envManagers, shellStartupProviders, context.workspaceState);
}),
...(isInlineScriptsFeatureEnabled()
...(inlineScriptFeatureActivation.enabled
? [
commands.registerCommand('python-envs.clearScriptEnvCache', async () => {
await clearScriptEnvironmentCacheCommand(envManagers);
Expand Down
2 changes: 1 addition & 1 deletion src/features/inlineScript/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface InlineScriptFeatureActivation {
}

/**
* Latch the inline-script feature flag once during activation.
* Latch the inline-script preview setting once during activation.
* The setting requires a window reload, so later config changes
* in the same activation must not change the chosen mode.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/features/inlineScript/codeLens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export class InlineScriptCodeLensProvider implements CodeLensProvider, Disposabl

/**
* Register the inline-script CodeLens provider for local `.py` files. Only called when the PEP 723
* inline-script feature flag is enabled, so it is a no-op for everyone else.
* inline-script preview is enabled for this activation, so it is a no-op for everyone else.
*/
export function registerInlineScriptCodeLens(
routing: InlineScriptRoutingRegistry,
Expand Down
6 changes: 1 addition & 5 deletions src/features/inlineScript/setupCodeAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
import { readInlineScriptMetadata, sliceHeaderBytes } from '../../common/inlineScript/metadata';
import { getInlineScriptRoutingKey, InlineScriptRoutingRegistry } from '../../common/inlineScript/routingRegistry';
import { InlineScriptStrings } from '../../common/localize';
import { isInlineScriptsFeatureEnabled } from '../../helpers';

/**
* Diagnostic codes meaning "this import did not resolve", lowercased for comparison.
Expand Down Expand Up @@ -82,9 +81,6 @@ export class InlineScriptSetupCodeActionProvider implements CodeActionProvider {
context: CodeActionContext,
_token: CancellationToken,
): CodeAction[] {
if (!isInlineScriptsFeatureEnabled()) {
return [];
}
if (!context.diagnostics.some(isUnresolvedImportDiagnostic)) {
return [];
}
Expand All @@ -108,7 +104,7 @@ export class InlineScriptSetupCodeActionProvider implements CodeActionProvider {
}
}

/** Register the inline-script quick fix for local `.py` files. */
/** Register the inline-script quick fix for local `.py` files while the preview is active. */
export function registerInlineScriptSetupCodeAction(
routing: InlineScriptRoutingRegistry,
setupCommand: string,
Expand Down
8 changes: 4 additions & 4 deletions src/features/inlineScript/setupEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ import { registerInlineScriptSetupCodeAction } from './setupCodeAction';

/**
* Hidden command invoked by the inline-script CodeLens to set up the environment for one script.
* Intentionally not contributed in `package.json` while the feature is behind the internal flag.
* Intentionally not contributed in `package.json` because only the CodeLens and quick fix invoke it.
*/
export const SETUP_INLINE_SCRIPT_ENV_COMMAND = 'python-envs.setupInlineScriptEnv';

/**
* Command that scans the workspace and sets up environments for the selected inline-script files.
* Contributed in `package.json` but only shown in the Command Palette while the inline-scripts
* feature flag is enabled (gated by the `pythonEnvsInlineScriptsEnabled` context key).
* preview is active (gated by the activation-latched `pythonEnvsInlineScriptsEnabled` context key).
*/
export const SETUP_INLINE_SCRIPT_ENVS_COMMAND = 'python-envs.setupInlineScriptEnvs';

Expand Down Expand Up @@ -409,9 +409,9 @@ async function filterInlineScriptFiles(files: readonly Uri[]): Promise<Uri[]> {

/**
* Register the inline-script user-facing surfaces (the CodeLens, the quick fix, and their setup
* commands). Only called when the PEP 723 inline-script feature flag is enabled. The single-file
* commands). Only called when the PEP 723 inline-script preview is active. The single-file
* setup command is invoked by both surfaces and stays out of `package.json`; the bulk command is
* palette-gated behind the flag.
* palette-gated behind the activation-latched context key.
*/
export function registerInlineScriptUx(em: EnvironmentManagers, routing: InlineScriptRoutingRegistry): Disposable[] {
const codeLens = registerInlineScriptCodeLens(routing, SETUP_INLINE_SCRIPT_ENV_COMMAND);
Expand Down
7 changes: 4 additions & 3 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,10 @@ export function getUserConfiguredSetting<T>(section: string, key: string, scope?
}

/**
* Whether the PEP 723 inline-script env support is enabled. Internal
* undeclared flag (`python-envs.inlineScripts.enabled`); defaults to
* false. Window reload required to take effect.
* Whether the PEP 723 inline-script preview is enabled. Reads the effective
* `python-envs.inlineScripts.enabled` value, including an experimental default
* supplied by VS Code. The value is latched during activation and requires a
* window reload to change.
*/
export function isInlineScriptsFeatureEnabled(): boolean {
return getConfiguration('python-envs').get<boolean>('inlineScripts.enabled', false);
Expand Down
6 changes: 3 additions & 3 deletions src/managers/builtin/inlineScript/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { InlineScriptEnvManager } from './envManager';

/**
* Register the inline-script env manager when the activation-latched
* `python-envs.inlineScripts.enabled` flag is true.
* `python-envs.inlineScripts.enabled` preview setting is true.
*/
export async function registerInlineScriptFeatures(
nativeFinder: NativePythonFinder,
Expand All @@ -23,7 +23,7 @@ export async function registerInlineScriptFeatures(
workspaceState: Memento,
): Promise<void> {
if (!activation.enabled) {
traceVerbose('Inline-script env manager: skipping registration (internal flag is off)');
traceVerbose('Inline-script env manager: skipping registration (preview setting is off)');
return;
}
const { routingRegistry } = activation;
Expand All @@ -43,5 +43,5 @@ export async function registerInlineScriptFeatures(
);
disposables.push(mgr, api.registerEnvironmentManager(mgr));
setImmediate(() => mgr.startActivationDiscovery());
traceInfo('Inline-script env manager: registered (internal flag is on)');
traceInfo('Inline-script env manager: registered (preview setting is on)');
}
13 changes: 0 additions & 13 deletions src/test/features/inlineScript/setupCodeAction.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.

import assert from 'assert';
import * as sinon from 'sinon';
import { CodeActionContext, Diagnostic, Position, Range, TextDocument, Uri } from 'vscode';
import { InlineScriptMetadata, MAX_HEADER_BYTES } from '../../../common/inlineScript/metadata';
import { InlineScriptRoutingRegistry } from '../../../common/inlineScript/routingRegistry';
Expand All @@ -11,7 +10,6 @@ import {
InlineScriptSetupCodeActionProvider,
isUnresolvedImportDiagnostic,
} from '../../../features/inlineScript/setupCodeAction';
import * as helpers from '../../../helpers';

const SETUP_COMMAND = 'python-envs.setupInlineScriptEnv';

Expand Down Expand Up @@ -60,17 +58,14 @@ suite('Inline script setup code action', () => {
const scriptUri = Uri.file('/workspace/app.py');
let routing: InlineScriptRoutingRegistry;
let provider: InlineScriptSetupCodeActionProvider;
let featureEnabledStub: sinon.SinonStub;

setup(() => {
featureEnabledStub = sinon.stub(helpers, 'isInlineScriptsFeatureEnabled').returns(true);
routing = new InlineScriptRoutingRegistry();
provider = new InlineScriptSetupCodeActionProvider(routing, SETUP_COMMAND);
});

teardown(() => {
routing.dispose();
sinon.restore();
});

function provide(document: TextDocument, diagnostics: Diagnostic[]) {
Expand Down Expand Up @@ -206,14 +201,6 @@ suite('Inline script setup code action', () => {
assert.strictEqual(provide(makeDocument(scriptUri), [makeDiagnostic('reportMissingImports')]).length, 0);
});

test('offers nothing when the inline-scripts feature flag is off', () => {
featureEnabledStub.returns(false);

const actions = provide(makeDocument(scriptUri), [makeDiagnostic('reportMissingImports')]);

assert.strictEqual(actions.length, 0);
});

test('offers nothing for a file that cannot carry an inline-script environment', () => {
const notebookUri = Uri.parse('vscode-notebook-cell:/workspace/app.py#ch0');

Expand Down
4 changes: 2 additions & 2 deletions src/test/helpers.inlineScriptsFeature.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ suite('isInlineScriptsFeatureEnabled', () => {
sinon.restore();
});

test('returns false by default (no setting written)', () => {
test('returns false when the effective setting value is false', () => {
configGet.withArgs('inlineScripts.enabled', false).returns(false);
assert.strictEqual(isInlineScriptsFeatureEnabled(), false);
});

test('returns true when the user explicitly enables the setting', () => {
test('returns true when the effective setting value is true', () => {
configGet.withArgs('inlineScripts.enabled', false).returns(true);
assert.strictEqual(isInlineScriptsFeatureEnabled(), true);
});
Expand Down
Loading
Loading