{"id":"cc12624da9257bd0","repo":"python-poetry/poetry","slug":"bad-script-name-script-needs-to-specify-a-fun","errorCode":null,"errorMessage":"Bad script ({name}): script needs to specify a function within a module like: module(.submodule):function\nInstead got: {script_with_extras}","messagePattern":"Bad script \\((.+?)\\): script needs to specify a function within a module like: module\\(\\.submodule\\):function\nInstead got: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/poetry/masonry/builders/editable.py","lineNumber":179,"sourceCode":"            script_without_extras = script_with_extras.split(\"[\")[0]\n            try:\n                module, callable_ = script_without_extras.split(\":\")\n            except ValueError as exc:\n                msg = (\n                    f\"Bad script ({name}): script needs to specify a function within a\"\n                    \" module like: module(.submodule):function\\nInstead got:\"\n                    f\" {script_with_extras}\"\n                )\n                if \"not enough values\" in str(exc):\n                    msg += (\n                        \"\\nHint: If the script depends on module-level code, try\"\n                        \" wrapping it in a main() function and modifying your script\"\n                        f' like:\\n{name} = \"{script_with_extras}:main\"'\n                    )\n                elif \"too many values\" in str(exc):\n                    msg += '\\nToo many \":\" found!'\n\n                raise ValueError(msg)\n\n            callable_holder = callable_.split(\".\", 1)[0]\n\n            script_file = scripts_path.joinpath(name)\n            self._debug(\n                f\"  - Adding the <c2>{name}</c2> script to <b>{scripts_path}</b>\"\n            )\n            with script_file.open(\"w\", encoding=\"utf-8\") as f:\n                f.write(\n                    decode(\n                        SCRIPT_TEMPLATE.format(\n                            python=self._env.python,\n                            module=module,\n                            callable_holder=callable_holder,\n                            callable_=callable_,\n                        )\n                    )\n                )","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/python-poetry/poetry/blob/92b74dcfe348d0e01e14d40d6c1fa47a4ee04a54/src/poetry/masonry/builders/editable.py#L161-L197","documentation":"Raised by EditableBuilder._build_scripts at src/poetry/masonry/builders/editable.py:163-179 when a script entry under [tool.poetry.scripts] does not split into exactly 'module' and 'callable' on ':'. The split raising ValueError ('not enough' = no colon, 'too many' = multiple colons) is caught and re-raised with guidance. Format must be module(.submodule):function.","triggerScenarios":"Running `poetry install` (editable, on the current project) when [tool.poetry.scripts] has an entry like 'foo = \"mymain\"' (no ':function') or 'foo = \"a:b:c\"' (two colons). Build during editable install of the project.","commonSituations":"Copy-pasted a setuptools console_scripts entry that omits the function, pointed a script at a module rather than a callable, or accidentally used a Windows drive-letter colon.","solutions":["Rewrite the entry as module:func, e.g. 'foo = \"mypkg.cli:main\"'.","If the target is module-level code, wrap it in a main() function and point at ':main'.","Remove extra colons; submodule dotted form goes before the single colon (mypkg.sub:func)."],"exampleFix":"# before\n[tool.poetry.scripts]\nfoo = \"mypkg\"          # no :function\nbar = \"a:b:c\"          # too many colons\n\n# after\n[tool.poetry.scripts]\nfoo = \"mypkg:main\"\nbar = \"a.sub:run\"","handlingStrategy":"validation","validationCode":"import re\n\ndef validate_script_entry(script: str) -> None:\n    target = script.split('[')[0]\n    parts = target.split(':')\n    if len(parts) != 2 or not parts[0] or not parts[1]:\n        raise ValueError(\n            f'Bad script {script!r}; expected module(.submodule):function'\n        )","typeGuard":"def is_valid_script(script: str) -> bool:\n    target = script.split('[')[0]\n    parts = target.split(':')\n    return len(parts) == 2 and bool(parts[0]) and bool(parts[1])","tryCatchPattern":"try:\n    builder._build_scripts(scripts)\nexcept ValueError as e:\n    if 'Bad script' in str(e):\n        raise SystemExit(f'Fix [tool.poetry.scripts]: {e}') from e\n    raise","preventionTips":["Always write scripts as module:func (use dotted submodule before the colon).","Lint the [tool.poetry.scripts] table in CI with a regex check."],"tags":["scripts","editable-build","pyproject","masonry","validation"],"analyzedSha":"92b74dcfe348d0e01e14d40d6c1fa47a4ee04a54","analyzedAt":"2026-08-04T20:33:34.072Z","schemaVersion":2}