Repository navigation
fix(run): execute file scripts instead of looking for a callable #11113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -58,6 +58,9 @@ def run_script(self, script: str | dict[str, str], args: list[str]) -> int: | |||||||||||||
| Otherwise (when an entry point script does not exist), ``sys.argv[0]`` is the | ||||||||||||||
| script name only, i.e. ``poetry run foo`` has ``sys.argv == ['foo']``. | ||||||||||||||
| """ | ||||||||||||||
| if isinstance(script, dict) and script.get("type") == "file": | ||||||||||||||
| return self.run_file_script(script, args) | ||||||||||||||
|
|
||||||||||||||
| for script_dir in self.env.script_dirs: | ||||||||||||||
| script_path = script_dir / args[0] | ||||||||||||||
| if WINDOWS: | ||||||||||||||
|
|
@@ -70,7 +73,10 @@ def run_script(self, script: str | dict[str, str], args: list[str]) -> int: | |||||||||||||
| self._warning_not_installed_script(args[0]) | ||||||||||||||
|
|
||||||||||||||
| if isinstance(script, dict): | ||||||||||||||
| script = script["callable"] | ||||||||||||||
| # A script can also be specified as a table, either as a legacy | ||||||||||||||
| # ``{callable = "module:callable"}`` entry or as a | ||||||||||||||
| # ``{reference = "module:callable", type = "console"}`` entry. | ||||||||||||||
| script = script.get("callable") or script["reference"] | ||||||||||||||
|
|
||||||||||||||
| module, callable_ = script.split(":") | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -89,6 +95,35 @@ def run_script(self, script: str | dict[str, str], args: list[str]) -> int: | |||||||||||||
|
|
||||||||||||||
| return self.env.execute(*cmd) | ||||||||||||||
|
|
||||||||||||||
| def run_file_script(self, script: dict[str, str], args: list[str]) -> int: | ||||||||||||||
| """Runs a file script defined in the section ``[tool.poetry.scripts]``. | ||||||||||||||
|
|
||||||||||||||
| Unlike entry points, file scripts (``type = "file"``) are copied to the | ||||||||||||||
| environment's script directory as-is when the project is installed, so they | ||||||||||||||
| are executed directly instead of being resolved to a ``module:callable`` pair. | ||||||||||||||
| """ | ||||||||||||||
| script_name = args[0] | ||||||||||||||
|
|
||||||||||||||
| for script_dir in self.env.script_dirs: | ||||||||||||||
| candidates = [script_dir / script_name] | ||||||||||||||
| if WINDOWS: | ||||||||||||||
| candidates.append(script_dir / f"{script_name}.cmd") | ||||||||||||||
|
Comment on lines
+108
to
+110
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): On Windows, an installed extensionless file at Triggers: On Windows when both the extensionless installed file and its Suggested fix: Prefer the
Suggested change
|
||||||||||||||
| for script_path in candidates: | ||||||||||||||
| if script_path.exists(): | ||||||||||||||
| return self.env.execute(str(script_path), *args[1:]) | ||||||||||||||
|
|
||||||||||||||
| # If we reach this point, the script is not installed, so we fall back to | ||||||||||||||
| # the script file referenced in the project. | ||||||||||||||
| reference = script["reference"] | ||||||||||||||
| script_path = self.poetry.file.path.parent / reference | ||||||||||||||
|
Comment on lines
+117
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): A file-script table without a Triggers: When a Suggested fix: Read the reference with |
||||||||||||||
|
|
||||||||||||||
| if not script_path.exists(): | ||||||||||||||
| self.line_error(f"<error>Command not found: <c1>{script_name}</c1></error>") | ||||||||||||||
| return 1 | ||||||||||||||
|
|
||||||||||||||
| self._warning_not_installed_script(script_name) | ||||||||||||||
| return self.env.execute(str(script_path), *args[1:]) | ||||||||||||||
|
|
||||||||||||||
| def _warning_not_installed_script(self, script: str) -> None: | ||||||||||||||
| message = f"""\ | ||||||||||||||
| Warning: '{script}' is an entry point defined in pyproject.toml, but it's not \ | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: The
run_scriptdocstring now describes file-script fallback behavior incorrectly: it claims that an uninstalled script runs withsys.argv[0]set to only the script name, butrun_file_scriptexecutes the referenced path directly, sosys.argv[0]is the full reference path.Triggers: When a file script is run from its project reference because it is not installed.
Suggested fix: Document the distinct
sys.argv[0]behavior for file scripts, or execute the fallback in a way that preserves the documented value.