tl;dr: To make this project runnable as a command, do these changes:
- my_tool/__init__.py
from my_tool.the_tool import main as main_tool
- pyproject.toml
[tool.poetry.scripts] my_tool = "my_tool:main_tool"
- my_tool/the_tool.py
def main(): pass
To build this project, install poetry and run:
poetry install poetry run my_tool
The command "my_tool" is defined in the pyproject.toml file (note the "scripts" section).
my_tool/ folder is the module. A module is a folder with an __init__.py file inside the project folder.
pyproject.toml defines everything poetry needs to know about the project and its packaging.
Inside the module my_tool/, __init__.py is the first point of entry.
The code block shown to be inserted into pyproject.toml is supposed to be additional to all the other existing stuff - the autogenerated details and additional dependencies described by the user and/or the poetry new initialization command. For an existing project, you can use poetry init to create a pyproject.toml file, but you'll have to do the mapping yourself.
As the __init__.py file is the first point of entry, it is loaded when the module is refered to in pyproject.toml: from it, the object "main_tool" is mapped as the command "my_tool".
The "main_tool" object has been aliased in __init__.py to map to "my_tool.the_tool.main". Here the_tool refers to the .py file in the package i.e. my_tool/the_tool.py. And the mapped "main_tool" refers to its function named main().
In other words, the sequence goes like this:
- poetry looks for scripts in pyproject.toml
- it finds my_tool which it maps to my_tool:main_tool -> which is mapped to my_tool/the_tool.py:main()
- when installed, my_tool will run my_tool/the_tool.py:main()
The cli object defined in the_tool.py is wrapped when the file is imported into __init__.py - or rather - to the module i.e. the main is refered to just as a placeholder. This leverages the fact, that importing happens in compile phase - which in python is actually a running phase i.e. python code gets executed. Thus the Cli wrapping and parsing of arguments etc. happens for the definitions in the_tool.py file already, when the file is imported.