Python 2.6 and beyond has the ability to directly execute a .zip file if the zip file contains a __main__.py
file at the top of the zip archive. I'm wanting to leverage this feature to provide preview releases of a tool I'm developing that won't require users to install anything beyond copying the .zip file to their disk. Is there a standard way to create such a zip file? I'm looking for a solution that works with python 2.6 and python 2.7.
Ideally I would like to use distutils, since I already have it working when I want to do a normal install. Is there a canonical way to use (or extend) distutils to create such a .zip file?
distutils provides an sdist
command which creates a source distribution that is almost right, but creates a structure that is a little too deep.
For example, my source tree looks like this:
my_package/
- setup.py
- src/
- __main__.py
- module1/
- module2/
- module3/
When I do python setup.py sdist
I end up with a .zip file with the following structure:
my_package-0.1.zip
- my_package-0.1/
- README.txt
- PKG_INFO
- src/
- __main__.py
- module1/
- module2/
- module3/
This isn't executable because __main__.py
is not at the top of the distribution. Effectively what I want is a src distribution that doesn't include src
, but only the files under src
. That, or exactly what sdist
gives me, but with an extra __main__.py
at the top of the archive.
Updated: Since the setup.cfg
is global, it affects the 'install-lib' setting for all commands, which is not what is desired. Unfortunately there is no way (to my knowledge) to pass a options to a subcommand via the command line, e.g. if you specify bdist --install-lib=/
it will raise an error instead of passing that down to the subcommands.
To customize the install-lib
for the install
subcommand only when bdist
is run, you can subclass the bdist_dumb
command and set the path manually after the install
subcommand is constructed / reinitialized:
setup.py
from distutils.core import setup
from distutils.command.bdist_dumb import bdist_dumb
class custom_bdist_dumb(bdist_dumb):
def reinitialize_command(self, name, **kw):
cmd = bdist_dumb.reinitialize_command(self, name, **kw)
if name == 'install':
cmd.install_lib = '/'
return cmd
if __name__ == '__main__':
setup(
# our custom class override
cmdclass = {'bdist_dumb': custom_bdist_dumb},
name='my_package',
py_modules = ['__main__'],
packages = ['module1', 'module2'],
package_dir = {'': 'src'}
)
Running:
% python setup.py bdist --format=zip
% unzip -l dist/my_package-0.0.0.linux-x86_64.zip
Archive: dist/my_package-0.0.0.linux-x86_64.zip
Length Date Time Name
--------- ---------- ----- ----
184 2011-05-31 20:34 my_package-0.0.0.egg-info
30 2011-05-31 20:34 __main__.py
128 2011-05-31 20:34 __main__.pyc
107 2011-05-31 20:34 module1/__init__.pyc
0 2011-05-31 20:27 module1/__init__.py
107 2011-05-31 20:34 module2/__init__.pyc
0 2011-05-31 20:27 module2/__init__.py
--------- -------
556 7 files
% python dist/my_package-0.0.0.linux-x86_64.zip
my_package working.
It is possible to make sdist
executable too by placing the following __main__.py
file to the root of your .zip:
import os
import sys
# add package .zip to python lookup path
__dir__ = os.path.dirname(__file__)
path = os.path.join(__dir__, 'my_package-0.1', 'src')
sys.path.insert(0, path)
import module1
module1.main()
This adds source subdir from archive into the sys.path
so that import from module1
becomes possible.
I can't tell right now how to patch distutils sdist
command to inject this __main__.py
into .zip
automatically, but that's definitely real.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With