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.
 | |
| 
 |