Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cc4dfc1

Browse files
committed
Issue #23491: Implement PEP 441: Improving Python Zip Application Support
Thanks to Paul Moore for the PEP and implementation.
1 parent ff2a661 commit cc4dfc1

7 files changed

Lines changed: 720 additions & 4 deletions

File tree

Doc/library/distribution.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ with a local index server, or without any index server at all.
1212
distutils.rst
1313
ensurepip.rst
1414
venv.rst
15+
zipapp.rst

Doc/library/zipapp.rst

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
:mod:`zipapp` --- Manage executable python zip archives
2+
=======================================================
3+
4+
.. module:: zipapp
5+
:synopsis: Manage executable python zip archives
6+
7+
8+
.. index::
9+
single: Executable Zip Files
10+
11+
.. versionadded:: 3.5
12+
13+
**Source code:** :source:`Lib/zipapp.py`
14+
15+
--------------
16+
17+
This module provides tools to manage the creation of zip files containing
18+
Python code, which can be :ref:`executed directly by the Python interpreter
19+
<using-on-interface-options>`. The module provides both a
20+
:ref:`zipapp-command-line-interface` and a :ref:`zipapp-python-api`.
21+
22+
23+
Basic Example
24+
-------------
25+
26+
The following example shows how the :ref:`command-line-interface`
27+
can be used to create an executable archive from a directory containing
28+
Python code. When run, the archive will execute the ``main`` function from
29+
the module ``myapp`` in the archive.
30+
31+
.. code-block:: sh
32+
33+
$ python -m zipapp myapp -m "myapp:main"
34+
$ python myapp.pyz
35+
<output from myapp>
36+
37+
38+
.. _zipapp-command-line-interface:
39+
40+
Command-Line Interface
41+
----------------------
42+
43+
When called as a program from the command line, the following form is used:
44+
45+
.. code-block:: sh
46+
47+
$ python -m zipapp source [options]
48+
49+
If *source* is a directory, this will create an archive from the contents of
50+
*source*. If *source* is a file, it should be an archive, and it will be
51+
copied to the target archive (or the contents of its shebang line will be
52+
displayed if the --info option is specified).
53+
54+
The following options are understood:
55+
56+
.. program:: zipapp
57+
58+
.. cmdoption:: -o <output>, --output=<output>
59+
60+
Write the output to a file named *output*. If this option is not specified,
61+
the output filename will be the same as the input *source*, with the
62+
extension ``.pyz`` added. If an explicit filename is given, it is used as
63+
is (so a ``.pyz`` extension should be included if required).
64+
65+
An output filename must be specified if the *source* is an archive (and in
66+
that case, *output* must not be the same as *source*).
67+
68+
.. cmdoption:: -p <interpreter>, --python=<interpreter>
69+
70+
Add a ``#!`` line to the archive specifying *interpreter* as the command
71+
to run. Also, on POSIX, make the archive executable. The default is to
72+
write no ``#!`` line, and not make the file executable.
73+
74+
.. cmdoption:: -m <mainfn>, --main=<mainfn>
75+
76+
Write a ``__main__.py`` file to the archive that executes *mainfn*. The
77+
*mainfn* argument should have the form "pkg.mod:fn", where "pkg.mod" is a
78+
package/module in the archive, and "fn" is a callable in the given module.
79+
The ``__main__.py`` file will execute that callable.
80+
81+
:option:`--main` cannot be specified when copying an archive.
82+
83+
.. cmdoption:: --info
84+
85+
Display the interpreter embedded in the archive, for diagnostic purposes. In
86+
this case, any other options are ignored and SOURCE must be an archive, not a
87+
directory.
88+
89+
.. cmdoption:: -h, --help
90+
91+
Print a short usage message and exit.
92+
93+
94+
.. _zipapp-python-api:
95+
96+
Python API
97+
----------
98+
99+
The module defines two convenience functions:
100+
101+
102+
.. function:: create_archive(source, target=None, interpreter=None, main=None)
103+
104+
Create an application archive from *source*. The source can be any
105+
of the following:
106+
107+
* The name of a directory, in which case a new application archive
108+
will be created from the content of that directory.
109+
* The name of an existing application archive file, in which case the file is
110+
copied to the target (modifying it to reflect the value given for the
111+
*interpreter* argument). The file name should include the ``.pyz``
112+
extension, if required.
113+
* A file object open for reading in bytes mode. The content of the
114+
file should be an application archive, and the file object is
115+
assumed to be positioned at the start of the archive.
116+
117+
The *target* argument determines where the resulting archive will be
118+
written:
119+
120+
* If it is the name of a file, the archive will be written to that
121+
file.
122+
* If it is an open file object, the archive will be written to that
123+
file object, which must be open for writing in bytes mode.
124+
* If the target is omitted (or None), the source must be a directory
125+
and the target will be a file with the same name as the source, with
126+
a ``.pyz`` extension added.
127+
128+
The *interpreter* argument specifies the name of the Python
129+
interpreter with which the archive will be executed. It is written as
130+
a "shebang" line at the start of the archive. On POSIX, this will be
131+
interpreted by the OS, and on Windows it will be handled by the Python
132+
launcher. Omitting the *interpreter* results in no shebang line being
133+
written. If an interpreter is specified, and the target is a
134+
filename, the executable bit of the target file will be set.
135+
136+
The *main* argument specifies the name of a callable which will be
137+
used as the main program for the archive. It can only be specified if
138+
the source is a directory, and the source does not already contain a
139+
``__main__.py`` file. The *main* argument should take the form
140+
"pkg.module:callable" and the archive will be run by importing
141+
"pkg.module" and executing the given callable with no arguments. It
142+
is an error to omit *main* if the source is a directory and does not
143+
contain a ``__main__.py`` file, as otherwise the resulting archive
144+
would not be executable.
145+
146+
If a file object is specified for *source* or *target*, it is the
147+
caller's responsibility to close it after calling create_archive.
148+
149+
When copying an existing archive, file objects supplied only need
150+
``read`` and ``readline``, or ``write`` methods. When creating an
151+
archive from a directory, if the target is a file object it will be
152+
passed to the ``zipfile.ZipFile`` class, and must supply the methods
153+
needed by that class.
154+
155+
.. function:: get_interpreter(archive)
156+
157+
Return the interpreter specified in the ``#!`` line at the start of the
158+
archive. If there is no ``#!`` line, return :const:`None`.
159+
The *archive* argument can be a filename or a file-like object open
160+
for reading in bytes mode. It is assumed to be at the start of the archive.
161+
162+
163+
.. _zipapp-examples:
164+
165+
Examples
166+
--------
167+
168+
Pack up a directory into an archive, and run it.
169+
170+
.. code-block:: sh
171+
172+
$ python -m zipapp myapp
173+
$ python myapp.pyz
174+
<output from myapp>
175+
176+
The same can be done using the :func:`create_archive` functon::
177+
178+
>>> import zipapp
179+
>>> zipapp.create_archive('myapp.pyz', 'myapp')
180+
181+
To make the application directly executable on POSIX, specify an interpreter
182+
to use.
183+
184+
.. code-block:: sh
185+
186+
$ python -m zipapp myapp -p "/usr/bin/env python"
187+
$ ./myapp.pyz
188+
<output from myapp>
189+
190+
To replace the shebang line on an existing archive, create a modified archive
191+
using the :func:`create_archive` function::
192+
193+
>>> import zipapp
194+
>>> zipapp.create_archive('old_archive.pyz', 'new_archive.pyz', '/usr/bin/python3')
195+
196+
To update the file in place, do the replacement in memory using a :class:`BytesIO`
197+
object, and then overwrite the source afterwards. Note that there is a risk
198+
when overwriting a file in place that an error will result in the loss of
199+
the original file. This code does not protect against such errors, but
200+
production code should do so. Also, this method will only work if the archive
201+
fits in memory::
202+
203+
>>> import zipapp
204+
>>> import io
205+
>>> temp = io.BytesIO()
206+
>>> zipapp.create_archive('myapp.pyz', temp, '/usr/bin/python2')
207+
>>> with open('myapp.pyz', 'wb') as f:
208+
>>> f.write(temp.getvalue())
209+
210+
Note that if you specify an interpreter and then distribute your application
211+
archive, you need to ensure that the interpreter used is portable. The Python
212+
launcher for Windows supports most common forms of POSIX ``#!`` line, but there
213+
are other issues to consider:
214+
215+
* If you use "/usr/bin/env python" (or other forms of the "python" command,
216+
such as "/usr/bin/python"), you need to consider that your users may have
217+
either Python 2 or Python 3 as their default, and write your code to work
218+
under both versions.
219+
* If you use an explicit version, for example "/usr/bin/env python3" your
220+
application will not work for users who do not have that version. (This
221+
may be what you want if you have not made your code Python 2 compatible).
222+
* There is no way to say "python X.Y or later", so be careful of using an
223+
exact version like "/usr/bin/env python3.4" as you will need to change your
224+
shebang line for users of Python 3.5, for example.
225+
226+
The Python Zip Application Archive Format
227+
-----------------------------------------
228+
229+
Python has been able to execute zip files which contain a ``__main__.py`` file
230+
since version 2.6. In order to be executed by Python, an application archive
231+
simply has to be a standard zip file containing a ``__main__.py`` file which
232+
will be run as the entry point for the application. As usual for any Python
233+
script, the parent of the script (in this case the zip file) will be placed on
234+
:data:`sys.path` and thus further modules can be imported from the zip file.
235+
236+
The zip file format allows arbitrary data to be prepended to a zip file. The
237+
zip application format uses this ability to prepend a standard POSIX "shebang"
238+
line to the file (``#!/path/to/interpreter``).
239+
240+
Formally, the Python zip application format is therefore:
241+
242+
1. An optional shebang line, containing the characters ``b'#!'`` followed by an
243+
interpreter name, and then a newline (``b'\n'``) character. The interpreter
244+
name can be anything acceptable to the OS "shebang" processing, or the Python
245+
launcher on Windows. The interpreter should be encoded in UTF-8 on Windows,
246+
and in :func:`sys.getfilesystemencoding()` on POSIX.
247+
2. Standard zipfile data, as generated by the :mod:`zipfile` module. The
248+
zipfile content *must* include a file called ``__main__.py`` (which must be
249+
in the "root" of the zipfile - i.e., it cannot be in a subdirectory). The
250+
zipfile data can be compressed or uncompressed.
251+
252+
If an application archive has a shebang line, it may have the executable bit set
253+
on POSIX systems, to allow it to be executed directly.
254+
255+
There is no requirement that the tools in this module are used to create
256+
application archives - the module is a convenience, but archives in the above
257+
format created by any means are acceptable to Python.

Doc/whatsnew/3.5.rst

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ New syntax features:
7171

7272
New library modules:
7373

74-
* None yet.
74+
* :mod:`zipapp`: :ref:`Improving Python ZIP Application Support
75+
<whatsnew-zipapp>` (:pep:`441`).
7576

7677
New built-in features:
7778

@@ -166,10 +167,22 @@ Some smaller changes made to the core Python language are:
166167
New Modules
167168
===========
168169

169-
.. module name
170-
.. -----------
170+
.. _whatsnew-zipapp:
171171

172-
* None yet.
172+
zipapp
173+
------
174+
175+
The new :mod:`zipapp` module (specified in :pep:`441`) provides an API and
176+
command line tool for creating executable Python Zip Applications, which
177+
were introduced in Python 2.6 in :issue:`1739468` but which were not well
178+
publicised, either at the time or since.
179+
180+
With the new module, bundling your application is as simple as putting all
181+
the files, including a ``__main__.py`` file, into a directory ``myapp``
182+
and running::
183+
184+
$ python -m zipapp myapp
185+
$ python myapp.pyz
173186

174187

175188
Improved Modules

0 commit comments

Comments
 (0)