simplest way to make a python module usable as a cli-tool

Viewed 807

I'd like to make a cli tool and found this as a reference: https://python-packaging.readthedocs.io/en/latest/command-line-scripts.html

So I created a directory with the following structure

$ tree modules_and_packages
modules_and_packages
├── bin
│   ├── cli_script
│   └── cli_script~
├── __init__.py
├── setup.py
├── setup.py~
├── some_module.py
└── some_module.py~

I can install the module through pip without any problems:

pip3 install ./modules_and_packages
Processing ./modules_and_packages
Installing collected packages: some-module
  Running setup.py install for some-module ... done
Successfully installed some-module-0.0.0

But when I run cli_script on the command line, I get the following error:

Traceback (most recent call last):
  File "/home/david/.local/bin/cli_script", line 3, in <module>
    import modules_and_packages
ModuleNotFoundError: No module named 'modules_and_packages'

Here's what's in the files:

setup.py

from setuptools import setup

setup(
    name='some_module',
    py_modules=['some_module'],
    scripts=['bin/cli_script']
)

some_module.py

def​ some_func():
    return​ 42

bin/cli_script

#!/usr/bin/env python3

import modules_and_packages
print('hello')

As you can see, I'm just trying to test the simplest case for making a command line tool using a module. What am I doing wrong here?

EDIT:

Error message I get when trying to import some_module after trying hoefling's suggestion

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/david/.local/lib/python3.6/site-packages/some_module.py", line 1
    ​def​ some_func():
        ^
SyntaxError: invalid character in identifier
2 Answers

You are not including modules_and_packages in the distribution. This is because your project structure is wrong - compare it with the one in the tutorial you referenced. It should look like the following:

myproject
├── modules_and_packages
│   ├── __init__.py
│   └── some_module.py
├── setup.py
└── bin
    └── cli_script

Now include the package modules_and_packages to the dist in the setup script:

# setup.py

setup(
    name='some_module',
    packages=['modules_and_packages'],
    scripts=['bin/cli_script']
)

It gets even easier if you use the setuptools.find_packages() function for package collection:

from setuptools import find_packages, setup

setup(
    name='some_module',
    packages=find_packages(),
    scripts=['bin/cli_script']
)

The guide you are following is 5 years old and a little vague. You could setup your cli-tool like so:

~ tree modules_and_packages
├── bin
│   └── cli_script
├── setup.py
└── some_module
    ├── __init__.py
    └── some_module.py

setup.py

from setuptools import setup

setup(
    name='some_module',
    py_modules=['some_module'],
    scripts=['bin/cli_script']
)

some_module/__init__.py

from .some_module import *

some_module/some_module.py

def some_func():
    return 42

bin/cli_script

#!/usr/bin/env python3

import some_module

print('hello')
print(some_module.some_func())

Then to install, in the modules_and_packages directory, run:

~ python setup.py develop

Then running:

~ cli_script
hello
42

Alternatively, if you are familiar with cookiecutter, I would recommend using this template for a python package, as it has a built in cli interface.

Related