450 lines
18 KiB
ReStructuredText
450 lines
18 KiB
ReStructuredText
:mod:`zipapp` --- Manage executable Python zip archives
|
|
=======================================================
|
|
|
|
.. module:: zipapp
|
|
:synopsis: Manage executable Python zip archives
|
|
|
|
.. versionadded:: 3.5
|
|
|
|
**Source code:** :source:`Lib/zipapp.py`
|
|
|
|
.. index::
|
|
single: Executable Zip Files
|
|
|
|
--------------
|
|
|
|
This module provides tools to manage the creation of zip files containing
|
|
Python code, which can be :ref:`executed directly by the Python interpreter
|
|
<using-on-interface-options>`. The module provides both a
|
|
:ref:`zipapp-command-line-interface` and a :ref:`zipapp-python-api`.
|
|
|
|
|
|
Basic Example
|
|
-------------
|
|
|
|
The following example shows how the :ref:`zipapp-command-line-interface`
|
|
can be used to create an executable archive from a directory containing
|
|
Python code. When run, the archive will execute the ``main`` function from
|
|
the module ``myapp`` in the archive.
|
|
|
|
.. code-block:: shell-session
|
|
|
|
$ python -m zipapp myapp -m "myapp:main"
|
|
$ python myapp.pyz
|
|
<output from myapp>
|
|
|
|
|
|
.. _zipapp-command-line-interface:
|
|
|
|
Command-Line Interface
|
|
----------------------
|
|
|
|
When called as a program from the command line, the following form is used:
|
|
|
|
.. code-block:: shell-session
|
|
|
|
$ python -m zipapp source [options]
|
|
|
|
If *source* is a directory, this will create an archive from the contents of
|
|
*source*. If *source* is a file, it should be an archive, and it will be
|
|
copied to the target archive (or the contents of its shebang line will be
|
|
displayed if the --info option is specified).
|
|
|
|
The following options are understood:
|
|
|
|
.. program:: zipapp
|
|
|
|
.. cmdoption:: -o <output>, --output=<output>
|
|
|
|
Write the output to a file named *output*. If this option is not specified,
|
|
the output filename will be the same as the input *source*, with the
|
|
extension ``.pyz`` added. If an explicit filename is given, it is used as
|
|
is (so a ``.pyz`` extension should be included if required).
|
|
|
|
An output filename must be specified if the *source* is an archive (and in
|
|
that case, *output* must not be the same as *source*).
|
|
|
|
.. cmdoption:: -p <interpreter>, --python=<interpreter>
|
|
|
|
Add a ``#!`` line to the archive specifying *interpreter* as the command
|
|
to run. Also, on POSIX, make the archive executable. The default is to
|
|
write no ``#!`` line, and not make the file executable.
|
|
|
|
.. cmdoption:: -m <mainfn>, --main=<mainfn>
|
|
|
|
Write a ``__main__.py`` file to the archive that executes *mainfn*. The
|
|
*mainfn* argument should have the form "pkg.mod:fn", where "pkg.mod" is a
|
|
package/module in the archive, and "fn" is a callable in the given module.
|
|
The ``__main__.py`` file will execute that callable.
|
|
|
|
:option:`--main` cannot be specified when copying an archive.
|
|
|
|
.. cmdoption:: -c, --compress
|
|
|
|
Compress files with the deflate method, reducing the size of the output
|
|
file. By default, files are stored uncompressed in the archive.
|
|
|
|
:option:`--compress` has no effect when copying an archive.
|
|
|
|
.. versionadded:: 3.7
|
|
|
|
.. cmdoption:: --info
|
|
|
|
Display the interpreter embedded in the archive, for diagnostic purposes. In
|
|
this case, any other options are ignored and SOURCE must be an archive, not a
|
|
directory.
|
|
|
|
.. cmdoption:: -h, --help
|
|
|
|
Print a short usage message and exit.
|
|
|
|
|
|
.. _zipapp-python-api:
|
|
|
|
Python API
|
|
----------
|
|
|
|
The module defines two convenience functions:
|
|
|
|
|
|
.. function:: create_archive(source, target=None, interpreter=None, main=None, filter=None, compressed=False)
|
|
|
|
Create an application archive from *source*. The source can be any
|
|
of the following:
|
|
|
|
* The name of a directory, or a :term:`path-like object` referring
|
|
to a directory, in which case a new application archive will be
|
|
created from the content of that directory.
|
|
* The name of an existing application archive file, or a :term:`path-like object`
|
|
referring to such a file, in which case the file is copied to
|
|
the target (modifying it to reflect the value given for the *interpreter*
|
|
argument). The file name should include the ``.pyz`` extension, if required.
|
|
* A file object open for reading in bytes mode. The content of the
|
|
file should be an application archive, and the file object is
|
|
assumed to be positioned at the start of the archive.
|
|
|
|
The *target* argument determines where the resulting archive will be
|
|
written:
|
|
|
|
* If it is the name of a file, or a :term:`path-like object`,
|
|
the archive will be written to that file.
|
|
* If it is an open file object, the archive will be written to that
|
|
file object, which must be open for writing in bytes mode.
|
|
* If the target is omitted (or ``None``), the source must be a directory
|
|
and the target will be a file with the same name as the source, with
|
|
a ``.pyz`` extension added.
|
|
|
|
The *interpreter* argument specifies the name of the Python
|
|
interpreter with which the archive will be executed. It is written as
|
|
a "shebang" line at the start of the archive. On POSIX, this will be
|
|
interpreted by the OS, and on Windows it will be handled by the Python
|
|
launcher. Omitting the *interpreter* results in no shebang line being
|
|
written. If an interpreter is specified, and the target is a
|
|
filename, the executable bit of the target file will be set.
|
|
|
|
The *main* argument specifies the name of a callable which will be
|
|
used as the main program for the archive. It can only be specified if
|
|
the source is a directory, and the source does not already contain a
|
|
``__main__.py`` file. The *main* argument should take the form
|
|
"pkg.module:callable" and the archive will be run by importing
|
|
"pkg.module" and executing the given callable with no arguments. It
|
|
is an error to omit *main* if the source is a directory and does not
|
|
contain a ``__main__.py`` file, as otherwise the resulting archive
|
|
would not be executable.
|
|
|
|
The optional *filter* argument specifies a callback function that
|
|
is passed a Path object representing the path to the file being added
|
|
(relative to the source directory). It should return ``True`` if the
|
|
file is to be added.
|
|
|
|
The optional *compressed* argument determines whether files are
|
|
compressed. If set to ``True``, files in the archive are compressed
|
|
with the deflate method; otherwise, files are stored uncompressed.
|
|
This argument has no effect when copying an existing archive.
|
|
|
|
If a file object is specified for *source* or *target*, it is the
|
|
caller's responsibility to close it after calling create_archive.
|
|
|
|
When copying an existing archive, file objects supplied only need
|
|
``read`` and ``readline``, or ``write`` methods. When creating an
|
|
archive from a directory, if the target is a file object it will be
|
|
passed to the ``zipfile.ZipFile`` class, and must supply the methods
|
|
needed by that class.
|
|
|
|
.. versionadded:: 3.7
|
|
Added the *filter* and *compressed* arguments.
|
|
|
|
.. function:: get_interpreter(archive)
|
|
|
|
Return the interpreter specified in the ``#!`` line at the start of the
|
|
archive. If there is no ``#!`` line, return :const:`None`.
|
|
The *archive* argument can be a filename or a file-like object open
|
|
for reading in bytes mode. It is assumed to be at the start of the archive.
|
|
|
|
|
|
.. _zipapp-examples:
|
|
|
|
Examples
|
|
--------
|
|
|
|
Pack up a directory into an archive, and run it.
|
|
|
|
.. code-block:: shell-session
|
|
|
|
$ python -m zipapp myapp
|
|
$ python myapp.pyz
|
|
<output from myapp>
|
|
|
|
The same can be done using the :func:`create_archive` function::
|
|
|
|
>>> import zipapp
|
|
>>> zipapp.create_archive('myapp', 'myapp.pyz')
|
|
|
|
To make the application directly executable on POSIX, specify an interpreter
|
|
to use.
|
|
|
|
.. code-block:: shell-session
|
|
|
|
$ python -m zipapp myapp -p "/usr/bin/env python"
|
|
$ ./myapp.pyz
|
|
<output from myapp>
|
|
|
|
To replace the shebang line on an existing archive, create a modified archive
|
|
using the :func:`create_archive` function::
|
|
|
|
>>> import zipapp
|
|
>>> zipapp.create_archive('old_archive.pyz', 'new_archive.pyz', '/usr/bin/python3')
|
|
|
|
To update the file in place, do the replacement in memory using a :class:`BytesIO`
|
|
object, and then overwrite the source afterwards. Note that there is a risk
|
|
when overwriting a file in place that an error will result in the loss of
|
|
the original file. This code does not protect against such errors, but
|
|
production code should do so. Also, this method will only work if the archive
|
|
fits in memory::
|
|
|
|
>>> import zipapp
|
|
>>> import io
|
|
>>> temp = io.BytesIO()
|
|
>>> zipapp.create_archive('myapp.pyz', temp, '/usr/bin/python2')
|
|
>>> with open('myapp.pyz', 'wb') as f:
|
|
>>> f.write(temp.getvalue())
|
|
|
|
|
|
.. _zipapp-specifying-the-interpreter:
|
|
|
|
Specifying the Interpreter
|
|
--------------------------
|
|
|
|
Note that if you specify an interpreter and then distribute your application
|
|
archive, you need to ensure that the interpreter used is portable. The Python
|
|
launcher for Windows supports most common forms of POSIX ``#!`` line, but there
|
|
are other issues to consider:
|
|
|
|
* If you use "/usr/bin/env python" (or other forms of the "python" command,
|
|
such as "/usr/bin/python"), you need to consider that your users may have
|
|
either Python 2 or Python 3 as their default, and write your code to work
|
|
under both versions.
|
|
* If you use an explicit version, for example "/usr/bin/env python3" your
|
|
application will not work for users who do not have that version. (This
|
|
may be what you want if you have not made your code Python 2 compatible).
|
|
* There is no way to say "python X.Y or later", so be careful of using an
|
|
exact version like "/usr/bin/env python3.4" as you will need to change your
|
|
shebang line for users of Python 3.5, for example.
|
|
|
|
Typically, you should use an "/usr/bin/env python2" or "/usr/bin/env python3",
|
|
depending on whether your code is written for Python 2 or 3.
|
|
|
|
|
|
Creating Standalone Applications with zipapp
|
|
--------------------------------------------
|
|
|
|
Using the :mod:`zipapp` module, it is possible to create self-contained Python
|
|
programs, which can be distributed to end users who only need to have a
|
|
suitable version of Python installed on their system. The key to doing this
|
|
is to bundle all of the application's dependencies into the archive, along
|
|
with the application code.
|
|
|
|
The steps to create a standalone archive are as follows:
|
|
|
|
1. Create your application in a directory as normal, so you have a ``myapp``
|
|
directory containing a ``__main__.py`` file, and any supporting application
|
|
code.
|
|
|
|
2. Install all of your application's dependencies into the ``myapp`` directory,
|
|
using pip:
|
|
|
|
.. code-block:: shell-session
|
|
|
|
$ python -m pip install -r requirements.txt --target myapp
|
|
|
|
(this assumes you have your project requirements in a ``requirements.txt``
|
|
file - if not, you can just list the dependencies manually on the pip command
|
|
line).
|
|
|
|
3. Optionally, delete the ``.dist-info`` directories created by pip in the
|
|
``myapp`` directory. These hold metadata for pip to manage the packages, and
|
|
as you won't be making any further use of pip they aren't required -
|
|
although it won't do any harm if you leave them.
|
|
|
|
4. Package the application using:
|
|
|
|
.. code-block:: shell-session
|
|
|
|
$ python -m zipapp -p "interpreter" myapp
|
|
|
|
This will produce a standalone executable, which can be run on any machine with
|
|
the appropriate interpreter available. See :ref:`zipapp-specifying-the-interpreter`
|
|
for details. It can be shipped to users as a single file.
|
|
|
|
On Unix, the ``myapp.pyz`` file is executable as it stands. You can rename the
|
|
file to remove the ``.pyz`` extension if you prefer a "plain" command name. On
|
|
Windows, the ``myapp.pyz[w]`` file is executable by virtue of the fact that
|
|
the Python interpreter registers the ``.pyz`` and ``.pyzw`` file extensions
|
|
when installed.
|
|
|
|
|
|
Making a Windows executable
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
On Windows, registration of the ``.pyz`` extension is optional, and
|
|
furthermore, there are certain places that don't recognise registered
|
|
extensions "transparently" (the simplest example is that
|
|
``subprocess.run(['myapp'])`` won't find your application - you need to
|
|
explicitly specify the extension).
|
|
|
|
On Windows, therefore, it is often preferable to create an executable from the
|
|
zipapp. This is relatively easy, although it does require a C compiler. The
|
|
basic approach relies on the fact that zipfiles can have arbitrary data
|
|
prepended, and Windows exe files can have arbitrary data appended. So by
|
|
creating a suitable launcher and tacking the ``.pyz`` file onto the end of it,
|
|
you end up with a single-file executable that runs your application.
|
|
|
|
A suitable launcher can be as simple as the following::
|
|
|
|
#define Py_LIMITED_API 1
|
|
#include "Python.h"
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
|
|
#ifdef WINDOWS
|
|
int WINAPI wWinMain(
|
|
HINSTANCE hInstance, /* handle to current instance */
|
|
HINSTANCE hPrevInstance, /* handle to previous instance */
|
|
LPWSTR lpCmdLine, /* pointer to command line */
|
|
int nCmdShow /* show state of window */
|
|
)
|
|
#else
|
|
int wmain()
|
|
#endif
|
|
{
|
|
wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
|
|
myargv[0] = __wargv[0];
|
|
memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
|
|
return Py_Main(__argc+1, myargv);
|
|
}
|
|
|
|
If you define the ``WINDOWS`` preprocessor symbol, this will generate a
|
|
GUI executable, and without it, a console executable.
|
|
|
|
To compile the executable, you can either just use the standard MSVC
|
|
command line tools, or you can take advantage of the fact that distutils
|
|
knows how to compile Python source::
|
|
|
|
>>> from distutils.ccompiler import new_compiler
|
|
>>> import distutils.sysconfig
|
|
>>> import sys
|
|
>>> import os
|
|
>>> from pathlib import Path
|
|
|
|
>>> def compile(src):
|
|
>>> src = Path(src)
|
|
>>> cc = new_compiler()
|
|
>>> exe = src.stem
|
|
>>> cc.add_include_dir(distutils.sysconfig.get_python_inc())
|
|
>>> cc.add_library_dir(os.path.join(sys.base_exec_prefix, 'libs'))
|
|
>>> # First the CLI executable
|
|
>>> objs = cc.compile([str(src)])
|
|
>>> cc.link_executable(objs, exe)
|
|
>>> # Now the GUI executable
|
|
>>> cc.define_macro('WINDOWS')
|
|
>>> objs = cc.compile([str(src)])
|
|
>>> cc.link_executable(objs, exe + 'w')
|
|
|
|
>>> if __name__ == "__main__":
|
|
>>> compile("zastub.c")
|
|
|
|
The resulting launcher uses the "Limited ABI", so it will run unchanged with
|
|
any version of Python 3.x. All it needs is for Python (``python3.dll``) to be
|
|
on the user's ``PATH``.
|
|
|
|
For a fully standalone distribution, you can distribute the launcher with your
|
|
application appended, bundled with the Python "embedded" distribution. This
|
|
will run on any PC with the appropriate architecture (32 bit or 64 bit).
|
|
|
|
|
|
Caveats
|
|
~~~~~~~
|
|
|
|
There are some limitations to the process of bundling your application into
|
|
a single file. In most, if not all, cases they can be addressed without
|
|
needing major changes to your application.
|
|
|
|
1. If your application depends on a package that includes a C extension, that
|
|
package cannot be run from a zip file (this is an OS limitation, as executable
|
|
code must be present in the filesystem for the OS loader to load it). In this
|
|
case, you can exclude that dependency from the zipfile, and either require
|
|
your users to have it installed, or ship it alongside your zipfile and add code
|
|
to your ``__main__.py`` to include the directory containing the unzipped
|
|
module in ``sys.path``. In this case, you will need to make sure to ship
|
|
appropriate binaries for your target architecture(s) (and potentially pick the
|
|
correct version to add to ``sys.path`` at runtime, based on the user's machine).
|
|
|
|
2. If you are shipping a Windows executable as described above, you either need to
|
|
ensure that your users have ``python3.dll`` on their PATH (which is not the
|
|
default behaviour of the installer) or you should bundle your application with
|
|
the embedded distribution.
|
|
|
|
3. The suggested launcher above uses the Python embedding API. This means that in
|
|
your application, ``sys.executable`` will be your application, and *not* a
|
|
conventional Python interpreter. Your code and its dependencies need to be
|
|
prepared for this possibility. For example, if your application uses the
|
|
:mod:`multiprocessing` module, it will need to call
|
|
:func:`multiprocessing.set_executable` to let the module know where to find the
|
|
standard Python interpreter.
|
|
|
|
|
|
The Python Zip Application Archive Format
|
|
-----------------------------------------
|
|
|
|
Python has been able to execute zip files which contain a ``__main__.py`` file
|
|
since version 2.6. In order to be executed by Python, an application archive
|
|
simply has to be a standard zip file containing a ``__main__.py`` file which
|
|
will be run as the entry point for the application. As usual for any Python
|
|
script, the parent of the script (in this case the zip file) will be placed on
|
|
:data:`sys.path` and thus further modules can be imported from the zip file.
|
|
|
|
The zip file format allows arbitrary data to be prepended to a zip file. The
|
|
zip application format uses this ability to prepend a standard POSIX "shebang"
|
|
line to the file (``#!/path/to/interpreter``).
|
|
|
|
Formally, the Python zip application format is therefore:
|
|
|
|
1. An optional shebang line, containing the characters ``b'#!'`` followed by an
|
|
interpreter name, and then a newline (``b'\n'``) character. The interpreter
|
|
name can be anything acceptable to the OS "shebang" processing, or the Python
|
|
launcher on Windows. The interpreter should be encoded in UTF-8 on Windows,
|
|
and in :func:`sys.getfilesystemencoding()` on POSIX.
|
|
2. Standard zipfile data, as generated by the :mod:`zipfile` module. The
|
|
zipfile content *must* include a file called ``__main__.py`` (which must be
|
|
in the "root" of the zipfile - i.e., it cannot be in a subdirectory). The
|
|
zipfile data can be compressed or uncompressed.
|
|
|
|
If an application archive has a shebang line, it may have the executable bit set
|
|
on POSIX systems, to allow it to be executed directly.
|
|
|
|
There is no requirement that the tools in this module are used to create
|
|
application archives - the module is a convenience, but archives in the above
|
|
format created by any means are acceptable to Python.
|
|
|