Skip to content

Conversation

@veeceey
Copy link

@veeceey veeceey commented Feb 8, 2026

Summary

Fixes #10395

This PR fixes the poetry env activate command to work correctly with Bash (and similar shells) on Windows.

Problem

The command eval $(poetry env activate) didn't work in Bash running on Windows (e.g., Git Bash, WSL) because the output was missing the source command prefix. The code incorrectly assumed that all shells on Windows should skip the command prefix, but this is only true for PowerShell and cmd.

Changes

  • Changed the logic to set an empty command prefix ("") only for PowerShell and cmd shells
  • Modified the output logic to check if not command: instead of if WINDOWS:
  • All POSIX-style shells (bash, zsh, etc.) now correctly get the "source" prefix regardless of OS

Testing

  • All existing tests pass
  • The fix ensures that bash/zsh on Windows will output: source /path/to/activate
  • PowerShell/cmd on Windows will continue to output just: /path/to/activate (or with appropriate quoting)

Summary by Sourcery

Validate package names for poetry update, ensure deterministic dependency constraint ordering in the lockfile, and fix environment activation output for POSIX shells on Windows.

New Features:

  • Reject poetry update invocations that specify packages not declared as project dependencies.
  • Support parsing package arguments with version constraints when validating update targets.

Bug Fixes:

  • Ensure poetry env activate prints a source prefix for POSIX-style shells on Windows while keeping bare script paths for PowerShell and cmd.
  • Stabilize the ordering of dependency constraint entries in the lockfile for consistent output.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 8, 2026

Reviewer's Guide

Adjusts virtualenv activation behavior so POSIX-style shells on Windows receive a proper 'source' prefix, introduces validation for 'poetry update' package arguments, and makes lockfile dependency constraints output deterministic via sorting, with accompanying tests.

Sequence diagram for the updated poetry env activate behavior on different shells

sequenceDiagram
    actor User
    participant Shell
    participant PoetryEnvActivate as PoetryEnvActivateCommand
    participant Env

    rect rgb(235, 245, 255)
      User->>Shell: eval $(poetry env activate)
      Shell->>PoetryEnvActivate: poetry env activate --shell bash
      PoetryEnvActivate->>Env: resolve_activation_script()
      Env-->>PoetryEnvActivate: bin_dir/activate exists
      PoetryEnvActivate-->>Shell: source /path/to/activate
      Shell->>Env: source /path/to/activate
    end

    rect rgb(245, 235, 255)
      User->>Shell: poetry env activate --shell powershell
      Shell->>PoetryEnvActivate: poetry env activate --shell powershell
      PoetryEnvActivate->>Env: resolve_activation_script()
      Env-->>PoetryEnvActivate: bin_dir/activate.ps1 exists
      PoetryEnvActivate-->>Shell: /path/to/activate.ps1
      Shell->>Env: . /path/to/activate.ps1
    end

    rect rgb(235, 255, 235)
      User->>Shell: poetry env activate --shell cmd
      Shell->>PoetryEnvActivate: poetry env activate --shell cmd
      PoetryEnvActivate->>Env: resolve_activation_script()
      Env-->>PoetryEnvActivate: bin_dir/activate.bat exists
      PoetryEnvActivate-->>Shell: C:\\path\\to\\activate.bat
      Shell->>Env: C:\\path\\to\\activate.bat
    end
Loading

Updated class diagram for commands and locker behavior

classDiagram
    class UpdateCommand {
      +handle() int
    }

    class EnvActivateCommand {
      +_get_activate_command(env Env, shell str) str
    }

    class Locker {
      +_dump_package(package Package, category str) dict
    }

    class Env {
      +bin_dir Path
    }

    class Package {
      +all_requires list
      +extras dict
    }

    UpdateCommand --> Package : validates_update_packages_against
    Locker --> Package : serializes_dependencies_of
    EnvActivateCommand --> Env : reads_activation_script_from
    UpdateCommand --> EnvActivateCommand : indirectly_uses_env_activation

    class UpdateCommand {
      +handle() int
      +validate_packages(packages list) list
    }

    class EnvActivateCommand {
      +_get_activate_command(env Env, shell str) str
      +_quote(path str, shell str) str
    }

    class Locker {
      +_dump_package(package Package, category str) dict
      +sort_dependency_constraints(constraints list) list
    }
Loading

File-Level Changes

Change Details Files
Fix env activation command so Bash-like shells on Windows receive the correct prefix while PowerShell/cmd omit it.
  • Set the activation command prefix to an empty string for PowerShell and cmd while keeping file names unchanged.
  • Change the activation output logic to omit the command prefix only when it is empty rather than when running on Windows.
  • Preserve 'source' as the command prefix for POSIX-style shells (bash, zsh, etc.) regardless of operating system.
src/poetry/console/commands/env/activate.py
Validate that packages passed to 'poetry update' are declared dependencies, including when version constraints are present.
  • Collect canonicalized names of all declared dependencies from the project metadata.
  • Parse each requested package argument to strip common version constraint operators before canonicalization.
  • Accumulate package arguments not present in declared dependencies and, if any, print an error listing them and exit with status 1 before whitelisting.
  • Leave the existing installer whitelisting logic intact for valid package names.
src/poetry/console/commands/update.py
Make lockfile dependency constraint dumping deterministic by sorting constraints before writing.
  • Introduce a sort over dependency constraint entries prior to appending them to the 'dependencies' array in the lock data.
  • Sort constraints using a key that orders by markers (empty first), optional flag (False first), then version string.
  • Keep the existing multiline array structure for multiple constraints while ensuring stable ordering.
src/poetry/packages/locker.py
Add regression tests to cover invalid 'poetry update' package arguments and error messaging.
  • Add a test asserting that providing a single non-existent package name causes the command to exit with status 1 and emit a clear error message.
  • Add a test asserting that multiple invalid package names are all listed in the error output while still returning status 1.
tests/console/commands/test_update.py

Assessment against linked issues

Issue Objective Addressed Explanation
#10395 Make poetry env activate behave consistently across operating systems so that in POSIX-style shells (e.g., Bash) on Windows, eval $(poetry env activate) outputs a command prefixed with source (or equivalent), rather than just the quoted path intended for PowerShell/cmd.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues, and left some high level feedback:

  • The package-name parsing in UpdateCommand.handle is quite brittle (manual split on >, <, =, !); consider using packaging.requirements.Requirement (or a similar existing parser in the codebase) so that extras, spaces, and more complex version specifiers are handled correctly.
  • When validating packages in UpdateCommand.handle, you might want to strip extras ([extra]) before canonicalization, otherwise valid dependency names with extras may be incorrectly treated as invalid.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The package-name parsing in `UpdateCommand.handle` is quite brittle (manual `split` on `>`, `<`, `=`, `!`); consider using `packaging.requirements.Requirement` (or a similar existing parser in the codebase) so that extras, spaces, and more complex version specifiers are handled correctly.
- When validating packages in `UpdateCommand.handle`, you might want to strip extras (`[extra]`) before canonicalization, otherwise valid dependency names with extras may be incorrectly treated as invalid.

## Individual Comments

### Comment 1
<location> `src/poetry/console/commands/update.py:55-58` </location>
<code_context>
+            }
+
+            invalid_packages = []
+            for package in packages:
+                # Check if package name contains version constraint
+                # (e.g., "package==1.0.0" or "package>=1.0")
+                package_name = package.split(">")[0].split("<")[0].split("=")[0].split("!")[0].strip()
+                canonical_name = canonicalize_name(package_name)
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Manual splitting of version constraints is brittle and will mis-handle more complex specifiers.

This chained `split` approach will break on common valid forms like multiple constraints (`pkg>=1,<2`), markers (`pkg; python_version<'3.11'`), extras (`pkg[extra]==1.0`), or varied spacing. Prefer using an existing parser such as Poetry’s requirement/specifier utilities or `packaging.requirements.Requirement` to derive the canonical package name so all valid syntaxes remain supported without custom parsing updates.
</issue_to_address>

### Comment 2
<location> `src/poetry/console/commands/update.py:58-59` </location>
<code_context>
+            for package in packages:
+                # Check if package name contains version constraint
+                # (e.g., "package==1.0.0" or "package>=1.0")
+                package_name = package.split(">")[0].split("<")[0].split("=")[0].split("!")[0].strip()
+                canonical_name = canonicalize_name(package_name)
+
+                if canonical_name not in all_dependencies:
</code_context>

<issue_to_address>
**issue (bug_risk):** Extras in package specs will be treated as invalid dependencies.

For inputs like `poetry update foo[bar]==1.0`, `package_name` becomes `foo[bar]`. `canonicalize_name` will preserve the brackets, but `all_dependencies` is keyed by `dep.name` (typically just `foo`), so dependencies with extras will be incorrectly flagged as invalid. When deriving `package_name`, strip any extras segment (e.g. split on `[` before canonicalization) or use a requirement parser that gives you `.name` separately from extras.
</issue_to_address>

### Comment 3
<location> `src/poetry/console/commands/env/activate.py:60-61` </location>
<code_context>

         if (activation_script := env.bin_dir / filename).exists():
-            if WINDOWS:
+            # Only omit the command prefix for PowerShell and cmd on Windows
+            if not command:
                 return f"{self._quote(str(activation_script), shell)}"
             return f"{command} {self._quote(str(activation_script), shell)}"
</code_context>

<issue_to_address>
**issue (bug_risk):** Condition now omits the command prefix for PowerShell/cmd on non-Windows as well.

With this change, any shell mapped to an empty `command` (PowerShell/pwsh or cmd) now omits the prefix on all platforms. On non-Windows systems where `powershell`/`pwsh` is used (PowerShell Core), running a `.ps1` without `pwsh -File`, `.` or `source` will typically fail. To keep the previous cross-platform behavior and match the comment, this condition likely needs to remain `if WINDOWS and not command:` instead of `if not command:`.
</issue_to_address>

### Comment 4
<location> `tests/console/commands/test_update.py:105-114` </location>
<code_context>
     assert tester.command.installer._requires_synchronization
+
+
+def test_update_with_invalid_package_name_shows_error(
+    poetry_with_outdated_lockfile: Poetry,
+    command_tester_factory: CommandTesterFactory,
+) -> None:
+    """
+    Providing non-existent package names should raise an error.
+    """
+    tester = command_tester_factory("update", poetry=poetry_with_outdated_lockfile)
+
+    status = tester.execute("nonexistent-package")
+
+    assert status == 1
+    assert (
+        "The following packages are not dependencies of this project: nonexistent-package"
+        in tester.io.fetch_error()
</code_context>

<issue_to_address>
**suggestion (testing):** Add tests for invalid package names that include version specifiers and comparison operators

Since the implementation now strips version/comparison parts from user input (e.g., `foo==1.0`, `foo>=1.0`, `foo<2`, `foo!=1.0`), please add tests that pass invalid package names with such constraints (e.g., `nonexistent==1.0`, `nonexistent>=2`, `nonexistent!=1.0`) and assert they’re correctly rejected. This will lock the tests to the new parsing behavior and guard against regressions.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 55 to 58
for package in packages:
# Check if package name contains version constraint
# (e.g., "package==1.0.0" or "package>=1.0")
package_name = package.split(">")[0].split("<")[0].split("=")[0].split("!")[0].strip()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Manual splitting of version constraints is brittle and will mis-handle more complex specifiers.

This chained split approach will break on common valid forms like multiple constraints (pkg>=1,<2), markers (pkg; python_version<'3.11'), extras (pkg[extra]==1.0), or varied spacing. Prefer using an existing parser such as Poetry’s requirement/specifier utilities or packaging.requirements.Requirement to derive the canonical package name so all valid syntaxes remain supported without custom parsing updates.

Comment on lines 58 to 59
package_name = package.split(">")[0].split("<")[0].split("=")[0].split("!")[0].strip()
canonical_name = canonicalize_name(package_name)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Extras in package specs will be treated as invalid dependencies.

For inputs like poetry update foo[bar]==1.0, package_name becomes foo[bar]. canonicalize_name will preserve the brackets, but all_dependencies is keyed by dep.name (typically just foo), so dependencies with extras will be incorrectly flagged as invalid. When deriving package_name, strip any extras segment (e.g. split on [ before canonicalization) or use a requirement parser that gives you .name separately from extras.

Comment on lines 105 to 114
def test_update_with_invalid_package_name_shows_error(
poetry_with_outdated_lockfile: Poetry,
command_tester_factory: CommandTesterFactory,
) -> None:
"""
Providing non-existent package names should raise an error.
"""
tester = command_tester_factory("update", poetry=poetry_with_outdated_lockfile)

status = tester.execute("nonexistent-package")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add tests for invalid package names that include version specifiers and comparison operators

Since the implementation now strips version/comparison parts from user input (e.g., foo==1.0, foo>=1.0, foo<2, foo!=1.0), please add tests that pass invalid package names with such constraints (e.g., nonexistent==1.0, nonexistent>=2, nonexistent!=1.0) and assert they’re correctly rejected. This will lock the tests to the new parsing behavior and guard against regressions.

@dimbleby
Copy link
Contributor

dimbleby commented Feb 8, 2026

you have made a mess of your branches, this pull request is mixed up with changes from another

@veeceey
Copy link
Author

veeceey commented Feb 8, 2026 via email

The `poetry env activate` command now works correctly with Bash (and
similar shells) on Windows by including the source command prefix.

Previously, the code checked if the OS was Windows and skipped the
command prefix for all shells, but this was incorrect for Bash/Zsh/etc.
running on Windows (e.g., Git Bash, WSL).

This fix changes the logic to only skip the command prefix for
PowerShell and cmd shells, which are the only Windows-specific shells
that don't need it. All other shells (bash, zsh, etc.) now correctly
get the "source" prefix regardless of the OS.

Fixes python-poetry#10395
@veeceey veeceey force-pushed the fix/issue-10395-env-activate-bash-windows branch from cd6b269 to d3540e2 Compare February 8, 2026 13:39
@veeceey
Copy link
Author

veeceey commented Feb 8, 2026

Fixed - rebased the branch so it only contains the changes for this PR. Sorry about the branch mix-up.

Restrict the no-prefix condition to PowerShell/cmd on Windows only,
rather than using an empty command string check. This ensures
PowerShell Core on non-Windows platforms still receives the '.'
prefix needed to source activation scripts correctly.

Changes:
- Restore '.' as the command for PowerShell/pwsh and cmd (matching
  upstream) instead of empty string
- Replace 'if not command' with explicit
  'if WINDOWS and shell in ("powershell", "pwsh", "cmd")' check
- Add clarifying comment explaining why the prefix is omitted only
  for these specific shells on Windows
@veeceey
Copy link
Author

veeceey commented Feb 8, 2026

Verified the condition now correctly limits the prefix omission to Windows PowerShell/cmd/pwsh shells only. Bash/zsh on Windows retain their 'source' prefix as intended. All 11 activate tests pass.

@dimbleby
Copy link
Contributor

dimbleby commented Feb 8, 2026

cf #10716

@veeceey
Copy link
Author

veeceey commented Feb 9, 2026

Closing in favor of #10716 which addresses the same issue (#10395) with a cleaner approach. Thanks for pointing this out @dimbleby!

@veeceey veeceey closed this Feb 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

poetry env activate does not work in Bash for Windows

2 participants