Reputation: 257
I wrote a project in Python which uses multiple scripts (modules). The main script calls on the other modules and reads some files from a folder in order to perform its function.
I am trying to package it for distribution. I wish to make it a runable program from the command line, meaning, once the user downloads it and installs it, they can call the program like this:
$ python my_program -i arg1 -o arg2
or similar.
The tutorials I find online wrap the project in the form of a package that you can import.
>> import my_program
>> my_program.run_stuff(arg1, arg2)
That's not what I want.
Upvotes: 1
Views: 2276
Reputation: 4856
Since you wrote "or similar", let's say you want to call the program like so:
$ my_program -i arg1 -o arg2
This is even shorter. It's also how we call ubiquitous Python tools like pip
. And there is an established procedure to define an "entry point" (as it's known) for any Python package.
All Python packaging tools allow that. We could use the classic Setuptools if we wanted to — that's what Pip does. Or use Poetry, a more modern alternative. But it's often easiest to set up with Flit.
In the simplest case, your package my_program
only contains an __init__.py
file that defines a function:
def main():
print('Running my program...')
That function would typically act on the command-line arguments in sys.argv
. The function doesn't have to be called main
, it could be any name, and it could also be in any other module of the package.
We can then define the entry point for the console script in the project's meta data. Flit reads that from a configuration file named pyproject.toml
in the root folder. So the repository now looks like this:
.
├── my_program
│ └── __init__.py
└── pyproject.toml
Using the most recent standard for the meta data, PEP 621, pyproject.toml
would contain:
[project]
name = 'my_program'
version = '1.0.0'
description = 'Can be run in the console from anywhere.'
[project.scripts]
my_program = 'my_program:main'
[build-system]
requires = ['flit_core>=3.2,<4']
build-backend = 'flit_core.buildapi'
In the [project.scripts
] section we have mapped the console command my_program
to the main
function in the top-level name space of the my_program
package. Again, it could be any other function elsewhere in the package too.
Now we package the project:
$ flit build --format wheel
Copying package file(s) from my_program I-flit_core.wheel
Writing metadata files I-flit_core.wheel
Writing the record of files I-flit_core.wheel
Built wheel: dist\my_program-1.0.0-py2.py3-none-any.whl I-flit_core.wheel
This puts the packaged "wheel" in a folder named dist
. We could upload that .whl
file to PyPI for distribution, or install it with Pip right away:
$ pip install dist/my_program-1.0.0-py2.py3-none-any.whl
Processing .\dist\my_program-1.0.0-py2.py3-none-any.whl
Installing collected packages: my-program
Successfully installed my-program-1.0.0
Now we can run the program like any other console application:
$ my_program
Running my program...
What Pip did there for us is, it created a small launcher for our package right next to its very own launcher. Just like it did for Flit too. On Windows, for example, there is now a my_program.exe
inside Python's Scripts
folder, right next to pip.exe
and flit.exe
.
Upvotes: 6