Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Structure of package that can also be run as command line script

I have written a package with the 'standard' minimal structure. It looks like this:

my_package/
    my_package/
        __init__.py
    setup.py

__init__.py contains a class and as such can simply be imported and used as one would expect.

However, the code really lends itself to be used in a command-line-way, e.g.

python my_package --arg1 "I like bananas."

At first, I just had an if __name__ == '__main__' check in __init__ which would then use argparse. This works but it isn't pretty, because it would mean that you'd call it from the command line like so:

python my_package/__init__.py --arg1 "I like bananas."

From what I read, this is where a __main__.py file comes in which would be executed as the default script inside a folder (similar to a index.html file on a website). The idea I have is to then simply import __init__.py, run argparse and feed the arguments to the class constructor. Like so:

import argparse
from __init__ import MyClass

parser = argparse.ArgumentParser()
parser.add_argument("--arg1", help="Some dummy value")

args = parser.parse_args()
my_class = MyClass(**vars(args))
my_class.do_stuff()

Is this how similar packages ought to be structured, or is there a better way?


The above works but PyCharm tells me that in __main__.py __init__ is an unresolved reference. Same for MyClass on that import line. When I use .__init__ instead (with a dot) the warning goes away but then the code doesn't work anymore, giving me a ImportError: attempted relative import with no known parent package.

like image 425
Bram Vanroy Avatar asked Dec 20 '18 09:12

Bram Vanroy


People also ask

How do I run a Python package from command line?

To run Python scripts with the python command, you need to open a command-line and type in the word python , or python3 if you have both versions, followed by the path to your script, just like this: $ python3 hello.py Hello World!

What is a command line application?

Command-line applications, also referred to as Console Applications, are computer programs designed to be used from a text interface, such as a shell.

How to build a package in a specific directory in Linux?

You can use any directory name, such as /home (in Linux), C:/folderName (in Windows), and so on. You can use the dot if you want the package to be in the same directory ( . ) The current working directory is represented by the dot "." operator. Look at my H: the above command built the mypackage (folder) successfully.

What is command line scripts in Python?

Command Line Scripts¶. Many Python packages include command line tools. This is useful for distributing support tools which are associated with a library, or just taking advantage of the setuptools / PyPI infrastructure to distribute a command line tool that happens to use Python. For funniest, we’ll add a funniest-joke command line tool.

How to compile and run Java programs from command line?

Run below steps to compile and run java programs from command line: “javac” is the java compiler available in bin folder of the jdk. “ -d ” stands for the “ directory “. it explains compiler that where the class files should be created.

How to run the program after it has been compiled?

After the program has been compiled, run it using the command. Recognize the following command. java:- It is the Java application launcher. mypackage:- It is our package name.


1 Answers

I want to suggest a different structure to you:

my_package/
    my_package/
        __init__.py
        cli_scripts.py
    setup.py

Let's assume your __init__.py looks like this (as a side note, I'd recommend moving the classes defined in there to a separate file, and then simply importing that file in the __init__):

class Test(object):

    def __init__(self, a)
        self.a = a

    def __call__(self):
        print(self.a)

Now there is an additional file inside the package that utilizes the stuff you implemented in the package, let's call it cli_scripts.py:

import argparse

from my_package import Test


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("a", type=int, help="Just an example argument")
    return parser.parse_args()

def demonstration():
    args = parse_args()
    t = Test(a=args.a)
    t()

My suggestion now is to utilize the console_scripts entry point inside setup.py, which could now look something like this:

from setuptools import setup

setup(
    name='testpy',
    version='0.1',
    description='Just an example',
    author='RunOrVeith',
    author_email='[email protected]',
    packages=["my_package"],
    entry_points={
        "console_scripts": ["mypkg=my_package.cli_scripts:demonstration"]},
    install_requires=[],
)

Now when you run pip install . inside the top-level my_package folder, your package will be installed. The entry_points automatically generate an executable for you, that you can now call with the name you gave it inside setup.py, in this example mypkg. This means you can now run

mypkg 5 and this will call demonstration().

This way:

  • you do not need to handle any __main__ files
  • you can give your script a meaningful name
  • you don't need to care whether your script is executable, or specify to run the script with python
  • you can have as many of these as you want inside the list entry_points
  • it forces you to write functions that you could also call from other modules, instead of the __main__

I think this is pretty clean. You can read more about entry_points in this blog, it has more functionalities! If you want to use code specified in __main__ instead of this cli_scripts approach, you can have a look at this question.

like image 83
RunOrVeith Avatar answered Oct 17 '22 03:10

RunOrVeith