Reputation: 2274
My request seems unorthodox, but I would like to quickly package an old repository, consisting mostly of python executable scripts.
The problem is that those scripts were not designed as modules, so some of them execute code directly at the module top-level, and some other have the if __name__=='__main__'
part.
How would you distribute those scripts using setuptools, without too much rewrite?
scripts
option of setup()
, but it's not advised, and also it doesn't allow me to rename them.main()
function in all those scripts, also because some scripts call weird recursive functions with side effects on global variables, so I'm a bit afraid of breaking stuff."myscript=mypkg.myscript"
instead of "myscript=mypkg.myscript:main"
), it logically complains after installation that a module is not callable.Is there a way to create scripts from modules? At least when they have a if __name__=='__main__'
?
Upvotes: 9
Views: 2013
Reputation: 1686
There's some design considerations, but I would recommend using a __main__.py for this.
__main__.py
from pathlib import Path
from runpy import run_path
pkg_dir = Path(__file__).resolve().parent
def execute_script():
script_pth = pkg_dir / "local path to script"
run_path(str(script_pth), run_name="__main__")
Then, you can set your_package.__main__:execute_script
as a console script in setup.py/pyproject.toml. You can obviously have as many scripts as you like this way.
Upvotes: 1
Reputation: 3559
i had the same problem when packaging chromium depot_tools
where i used some bash code
to transform the python scripts to python modules
you cant use scripts
because imports would fail
so you must use console_scripts
so you need a main
function in every script
replace
if __name__ == '__main__':
# main body
with
def main():
# main body
if __name__ == '__main__':
main()
and add the script as some_script=some_module.some_script:main
note: some main
functions require arguments, usually argv
replace
def main(argv):
options = parse_argv(argv)
# ...
with
def main_argv(argv):
options = parse_argv(argv)
# ...
def main():
import sys
return main_argv(sys.argv)
add empty __init__.py
files to every folder
replace file-imports with relative imports
-import utils
+from . import utils
-from utils import some_tool
+from .utils import some_tool
module files cannot have kebab-case.py
names
you rename (or symlink) them to snake_case.py
names
some scripts will try to write tempfiles to their basedir
which can cause [Errno 30] Read-only file system
errors
so you will have to patch the tempfile path
all in all, this is a solvable problem
and it would be nice to have a migration tool
similar to the 2to3 tool in python3.11
(${python311}/bin/2to3 -w -n --no-diffs .
)
to automate at least the simple transforms
Upvotes: 0
Reputation: 778
You can use the following codes.
def main():
pass # or do something
if __name__ == "__main__":
main()
Upvotes: -2
Reputation: 2274
I just realised part of the answer:
in the case where the module executes everything at the top-level, i.e. on import, it's therefore ok to define a dummy "no-op" main
function, like so:
# Content of mypkg/myscript.py
print("myscript being executed!")
def main():
pass # Do nothing!
This solution will still force me to add this line to the existing scripts, but I think it's a quick but cautious solution.
No solution if the code is under a if __name__=='__main__'
though...
Upvotes: 3