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
4 changes: 4 additions & 0 deletions docs/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ If the plugin is not installed in Poetry's own environment when running `poetry
it will be installed only for the current project under `.poetry/plugins`
in the project's directory.

This path can be changed by setting the `POETRY_PROJECT_PLUGIN_CACHE` environment variable
to a path similar to the default value, e.g. `.poetry_314/plugins`,
when running Poetry itself using different versions of Python, such as in CI.

The syntax to specify `plugins` is the same as for [dependencies]({{< relref "managing-dependencies" >}}).

{{% warning %}}
Expand Down
3 changes: 2 additions & 1 deletion src/poetry/plugins/plugin_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import hashlib
import json
import logging
import os
import shutil
import sys

Expand Down Expand Up @@ -103,7 +104,7 @@ def _load_plugin_entry_point(self, ep: metadata.EntryPoint) -> None:


class ProjectPluginCache:
PATH = Path(".poetry") / "plugins"
PATH = os.environ.get("POETRY_PROJECT_PLUGIN_CACHE", Path(".poetry") / "plugins")
Copy link

Choose a reason for hiding this comment

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

issue: Mixing Path and str types in os.environ.get default may cause issues.

Since os.environ.get returns a str if the variable is set, PATH may be a str or Path. To avoid type issues, wrap the result with Path(), ensuring PATH is always a Path object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ugh, I hate it:

Suggested change
PATH = os.environ.get("POETRY_PROJECT_PLUGIN_CACHE", Path(".poetry") / "plugins")
PATH = Path(os.environ["POETRY_PROJECT_PLUGIN_CACHE"]) if "POETRY_PROJECT_PLUGIN_CACHE" in os.environ else Path(".poetry") / "plugins")

slightly better:

Suggested change
PATH = os.environ.get("POETRY_PROJECT_PLUGIN_CACHE", Path(".poetry") / "plugins")
_CACHE_OVERRIDE = "POETRY_PROJECT_PLUGIN_CACHE"
PATH = Path(os.environ[_CACHE_OVERRIDE]) if _CACHE_OVERRIDE in os.environ else Path(".poetry") / "plugins")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another option is to refactor this constant to a static method… that would enable testing it.

Copy link

Choose a reason for hiding this comment

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

Refactoring the constant to a static method is a solid approach! It makes the logic testable and more flexible for future changes. You could implement it like this:

class ProjectPluginCache:
    _CACHE_OVERRIDE = "POETRY_PROJECT_PLUGIN_CACHE"

    @staticmethod
    def get_path() -> Path:
        override = os.environ.get(ProjectPluginCache._CACHE_OVERRIDE)
        if override:
            return Path(override)
        return Path(".poetry") / "plugins"

This way, you can easily write tests for different environment variable scenarios, and the type consistency is maintained. If you need to keep a class-level attribute, you could also set PATH = get_path() in __init__ or as a property. Let me know if you'd like a test example or further tweaks!

Copy link
Contributor Author

@colindean colindean Nov 14, 2025

Choose a reason for hiding this comment

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

@sourcery-ai Refactor usages of ProjectPluginCache.PATH, too.

Copy link

Choose a reason for hiding this comment

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

Absolutely! If you refactor ProjectPluginCache.PATH to a static method (e.g., get_path()), you'll need to update all usages of ProjectPluginCache.PATH throughout the codebase and tests. For example, any code like:

project_plugin_path = project_path / ProjectPluginCache.PATH

should become:

project_plugin_path = project_path / ProjectPluginCache.get_path()

This change will ensure that the plugin cache path is always computed dynamically, making it easier to test and to respect the environment variable override. You can search for all usages of ProjectPluginCache.PATH in your codebase and update them accordingly. If you need help with a specific usage or want a test example for the new method, let me know!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sourcery-ai I hoped you would provide a comment with a suggestion code block ;-)

Copy link

Choose a reason for hiding this comment

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

Here's a suggestion for refactoring usages of ProjectPluginCache.PATH to use a static method get_path(). This will ensure all references dynamically compute the path and respect the environment variable override.

First, update the class definition:

class ProjectPluginCache:
    _CACHE_OVERRIDE = "POETRY_PROJECT_PLUGIN_CACHE"

    @staticmethod
    def get_path() -> Path:
        override = os.environ.get(ProjectPluginCache._CACHE_OVERRIDE)
        if override:
            return Path(override)
        return Path(".poetry") / "plugins"

Then, update all usages of ProjectPluginCache.PATH to ProjectPluginCache.get_path():

src/poetry/plugins/plugin_manager.py

plugin_path = pyproject_toml.parent / ProjectPluginCache.get_path()

tests/console/test_application.py

project_plugin_path = project_path / ProjectPluginCache.get_path()

tests/plugins/test_plugin_manager.py

assert not (poetry.pyproject_path.parent / ProjectPluginCache.get_path()).exists()
# ... and similar for other usages

This approach keeps the logic centralized and testable. If you need a more detailed diff or want to see how to update a specific test, let me know!


def __init__(self, poetry: Poetry, io: IO) -> None:
self._poetry = poetry
Expand Down