0

I've been struggling on and off for months trying to create a wheel for my project 'rpn' (https://github.com/ConceptJunkie/rpn). I've finally gotten to the point where everything seems right, except for the console commands I'm trying to create in setup.py.

My project has multiple modules, and my setup.py, which I will post below, creates a wheel that seems to work correctly. I haven't done extensive testing with virtualenv because I'd like to get the basics right. When I install the wheel with pip, I can run python and import my modules and run the functions just fine. However, I use the project (and a few auxilliary utilities) as a command-line app, so I want the wheel to create some commands for me.

I'm on Windows, so when I install the wheel, it creates a small EXE file for each of the commands I have defined, but when I try to run the commands, I get this output:

c:\app\python36\scripts>makeRPNUnits.exe
Traceback (most recent call last):
  File "c:\app\python36\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "c:\app\python36\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\app\python36\Scripts\makeUnits.exe\__main__.py", line 5, in <module>
ImportError: cannot import name '__main__'

This is one of the little utilities I need to run to prepare the data files the app uses, but the other commands all do the same thing. If I go into Python I can run it just fine:

c:\app\python36\scripts>python
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit 
(AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

>>> import makeUnits
>>> makeUnits.main( )
makeUnits 6.999.13 (7.0b13) - RPN command-line calculator unit conversion data generator 
copyright (c) 2018 (1988), Rick Gutleber (rickg@his.com)

[and so on...]

I can also use the actual application through Python:

>>> rpn.handleOutput( rpn.rpn( [ '2', '2', '+' ] ) )
4

I have spent hours searching for help... the documentation for setup tools is very sparse and opaque, and I can only find tiny snippets of information elsewhere and it's often conflicting. There's never enough to understand the details, but just enough to make educated guesses.

My directory structure looks like this:

rpn/
├── rpndata
│   ├── various_text_files
└── setup.py
    __init__.py
    rpn.py
    makeUnits.py
    makeHelp.py
    MANIFEST.in
    rpn*.py

Each of the commands I wish to create is represented in a separate module that handles main. For example:

if __name__ == '__main__':
    main( )

and the rest of the files implement all the functionality used by the program and the various utilities. Again, most of this stuff works from within Python, but the commands in /python36/scripts fail with the error above.

The whole setup.py is rather lengthy, but the relevant part is this:

entry_points = {
    'console_scripts': [
        'rpn = rpn.rpn:__main__',
        'makeRPNHelp = rpn.makeHelp:__main__',
        'makeRPNUnits = rpn.makeUnits:__main__',
        'prepareRPNPrimeData = rpn.preparePrimeData:__main__',
        'testRPN = rpn.testRPN:__main__',
    ],
}

The experts may require more details, which I will be happy to provide, and of course, the entire project is available from GitHub. The project is fairly large, and somewhat convoluted... it's outgrown my ability to manage a Python project, because it's only a hobby for me, I do C++ at my day job.

Your help is appreciated.

Rick Gutleber
  • 63
  • 1
  • 6
  • Could you trim down... (preferably to a [\[SO\]: How to create a Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) for more asking related details)? – CristiFati Jan 28 '18 at 03:44
  • That's a great idea, CristiFati, that hadn't occurred to me. I shall do so, although I'm not feeling well today, so it may be a while until I can get around to it. – Rick Gutleber Jan 28 '18 at 23:27

1 Answers1

1

For your project's particular case, you have defined a main function in every module, so for setuptool entrypoints to work you can simply reference them directly, as if how you imported them (import makeUnits), i.e.:

entry_points = {
    'console_scripts': [
        'rpn = rpn:main',
        'makeRPNHelp = makeHelp:main',
        'makeRPNUnits = makeUnits:main',
        ...
    ]
}

Why you may have gotten confuse with needing __main__ is that this value is assigned to a module __name__ if that is the executable script, or naming a module as __main__.py is a way to allow a module to be executed directly using python -m example.module on the command line (pip for instance does this for python -m pip to work). However, for a standard entry point, just reference the module:attr directly and it should do what you expect.


Also, please consider going through the documentation about packaging on the Python site, as it will address how to properly package a project (which setuptools documentation don't actually teach, as you noted). Also consider creating a directory called rpn and putting your rpn*.py files into there to better organize your program code into its own namespace (so to make the entry point as you originally defined it (e.g. makeHelp = rpn.makeHelp:main work; naturally this comes with a change in how the main function is imported (from rpn.makeHelp import main)), though I note that on PyPI (the official python package index) that there is already a rpn package.

metatoaster
  • 17,419
  • 5
  • 55
  • 66
  • Okay, I made the assumption when I originally created this answer that you got code that you had moved everything into the `rpn` directory, so what I had here probably didn't work. It's corrected now. – metatoaster Jan 28 '18 at 04:17
  • I had previously had be "buildRPN" batch file copy everything to an 'rpn' subdirectory, as you described, for the reasons you described, but ended up taking it out. I was afraid that just moving everything would break the Git history. I had subsequently taken it out as part of the trial-and-error process to try to just get it to work. – Rick Gutleber Jan 28 '18 at 23:25
  • You can always try this out in a separate branch as a test - git actually track by content, not by path, so if you need to `git blame` down the road it will show the commit id, and from that the original path of the file can be derived. – metatoaster Jan 29 '18 at 00:29
  • You're right. Git is extremely intelligent software, and I've found I can usually give it the benefit of the doubt. – Rick Gutleber Jan 29 '18 at 15:20
  • This answer worked for me, but not until I moved all the files into a subdirectory named "rpn" and adjusted the imports accordingly. – Rick Gutleber Feb 03 '18 at 06:09
  • Yeah, that will be required as that changes the import locations. Just keep the packaging guide in mind the next time you find yourself building a different python package. – metatoaster Feb 03 '18 at 06:10
  • Thanks for your help and feedback. – Rick Gutleber Feb 05 '18 at 16:25