问题
I have read several similar topics but haven't succeeded yet. I feel I miss or misunderstand some fundamental thing and this is the reason of my failure.
I have an 'application' written in a python which I want to deploy with help of standard setup.py. Due to complex functionality it consists of different python modules. But there is no sense in separate release of this modules as they are too specific.
Expected result is to have package installed in a system with help of pip install
and be available from OS command line with simple app
command.
Simplifying long story to reproducible example - I have following directory structure:
<root>
├─ app
| ├─ aaa
| | └── module_a.py
| ├─ bbb
| | └── module_b.py
| └── app.py
├─ docs
| └── .....
├─ tests
| └── .....
└─ setup.py
Below is code of modules:
app.py
#!/usr/bin/python
from aaa.module_a import method1
from bbb.module_b import method2
def main():
print("APP main executed")
method1()
method2()
if __name__ == '__main__':
main()
module_a.py
def method1():
print("A1 executed")
module_b.py
def method2():
print("B2 executed")
When I run app.py from console it works fine and gives expected output:
APP main executed
A1 executed
B2 executed
So, this simple 'application' works fine and I want to distribute it with help of following
setup.py
from setuptools import setup
setup(
name="app",
version="1.0",
packages=['app', 'app.aaa', 'app.bbb'],
package_dir={'app': 'app'},
entry_points={
'console_scripts': ['app=app.app:main', ]
}
)
Again, everything looks good and test installation looks good:
(venv) [user@test]$ pip install <root>
Processing /home/user/<root>
Using legacy 'setup.py install' for app, since package 'wheel' is not installed.
Installing collected packages: app
Running setup.py install for app ... done
Successfully installed app-1.0
(venv) [user@test]$
And now comes the problem. With aforementioned entry_points from setup.py I expect to be able execute my application with ./app command. Indeed it works. But application itself fails with error message:
File "/test/venv/lib/python3.9/site-packages/app/app.py", line 3, in <module>
from aaa.module_a import method1
ModuleNotFoundError: No module named 'aaa'
I understand the reason of the error - it is because pip install
put directories aaa
and bbb
together with app.py
in one directory app
. I.e. from this point of view app.py
should use import app.aaa
instead of import aaa
. But if I do so then my app during development runs with error:
ModuleNotFoundError: No module named 'app.aaa'; 'app' is not a package
that is also logical as there are no app
package available at that time... (it is under development and isn't installed in the system...)
Finally. The question is - what is a correct way to create directory structure and setup.py for standalone python application that consist of several own modules?
UPD
The most promising result (but proved to be wrong after discussion in coments) that I have now came after following changes:
- moved app.py from
<root>/app
into<root>
itself - I referenced it in
setup.py
bypy_modules=['app']
- I changed imports from
import aaa.method1
toimport app.aaa.method1
etc.
This way package works both in my development environment and after installation.
But I got a problem with entry_points
- I see no way how to configure entry point to use main()
from app.py
that is not a part of app
package but is a separate module....
I.e. new structure is
<root>
├─ app
| ├─ aaa
| | └── module_a.py
| ├─ bbb
| | └── module_b.py
| └──__init__.py
├─ docs
| └── .....
├─ tests
| └── .....
├─ app.py
└─ setup.py
I.e. the logic here - to have 2 separate entities:
- An empty package
app
(consists of init.py only) with subpackagesaaa
,bbb
etc. - A script
app.py
that uses functions from subpackagesapp.aaa
,app.bbb
But as I wrote - I see no way how to define entry point for app.py
to allow it's run from OS command line directly.
回答1:
With that directory (package) structure, in your app.py
you should import as one of the following:
from app.aaa.module_a import method1
from .aaa.module_a import method1
Then make sure to call you application like one of the following:
app
(this should work thanks to the console entry point)
python -m app.app
(this should work even without the console entry point)
I try to recreate the complete project here
Directory structure:
.
├── app
│ ├── aaa
│ │ └── module_a.py
│ ├── app.py
│ └── bbb
│ └── module_b.py
└── setup.py
setup.py
import setuptools
setuptools.setup(
name="app",
version="1.0",
packages=['app', 'app.aaa', 'app.bbb'],
entry_points={
'console_scripts': ['app=app.app:main', ]
},
)
app/app.py
#!/usr/bin/python
from .aaa.module_a import method1
from .bbb.module_b import method2
def main():
print("APP main executed")
method1()
method2()
if __name__ == '__main__':
main()
app/aaa/module_a.py
def method1():
print("A1 executed")
app/bbb/module_b.py
def method2():
print("B2 executed")
Then I run following commands:
$ python3 -V
Python 3.6.9
$ python3 -m venv .venv
$ .venv/bin/python -m pip install -U pip setuptools wheel
# [...]
$ .venv/bin/python -m pip list
Package Version
------------- -------------------
pip 20.3.3
pkg-resources 0.0.0
setuptools 51.1.0.post20201221
wheel 0.36.2
$ .venv/bin/python -m pip install .
# [...]
$ .venv/bin/python -m app.app
APP main executed
A1 executed
B2 executed
$ .venv/bin/app
APP main executed
A1 executed
B2 executed
$ .venv/bin/python -m pip uninstall app
# [...]
$ .venv/bin/python -m pip install --editable .
# [...]
$ .venv/bin/python -m app.app
APP main executed
A1 executed
B2 executed
$ .venv/bin/app
APP main executed
A1 executed
B2 executed
回答2:
The answer from sinoroc is mostly right - he executed a correct example that highlights all options. It shows a correct way to structure python package. But before any run this package should be first installed into venv. Then pip install --editable
option is required in order to continue develop/debug the package inside IDE (it installs package in venv but keeps source files in original place).
After long discussion in comments I came to the page An Overview of Packaging for Python that explains all options and highlights that Python’s native packaging is mostly built for distributing reusable code, called libraries. I assume this is a reason of my misunderstanding and initial question.
As an alternative solution I plan to play with PEP 441 -- Improving Python ZIP Application Support that looks like a correct approach for my case.
来源:https://stackoverflow.com/questions/65407999/how-to-make-setup-py-for-standalone-python-application-in-a-right-way