mirror of
https://github.com/kennethreitz/heroku-buildpack-python.git
synced 2026-06-05 23:10:16 +00:00
715ab8b96a
So seamless.
155 lines
5.6 KiB
Plaintext
155 lines
5.6 KiB
Plaintext
Python Script Wrapper for Windows
|
|
=================================
|
|
|
|
setuptools includes wrappers for Python scripts that allows them to be
|
|
executed like regular windows programs. There are 2 wrappers, once
|
|
for command-line programs, cli.exe, and one for graphica programs,
|
|
gui.exe. These programs are almost identical, function pretty much
|
|
the same way, and are generated from the same source file. The
|
|
wrapper programs are used by copying them to the directory containing
|
|
the script they are to wrap and with the same name as the script they
|
|
are to wrap. In the rest of this document, we'll give an example that
|
|
will illustrate this.
|
|
|
|
Let's create a simple script, foo-script.py:
|
|
|
|
>>> import os, sys, tempfile
|
|
>>> from setuptools.command.easy_install import nt_quote_arg
|
|
>>> sample_directory = tempfile.mkdtemp()
|
|
>>> f = open(os.path.join(sample_directory, 'foo-script.py'), 'w')
|
|
>>> bytes_written = f.write(
|
|
... """#!%(python_exe)s
|
|
... import sys
|
|
... input = repr(sys.stdin.read())
|
|
... print(sys.argv[0][-14:])
|
|
... print(sys.argv[1:])
|
|
... print(input)
|
|
... if __debug__:
|
|
... print('non-optimized')
|
|
... """ % dict(python_exe=nt_quote_arg(sys.executable)))
|
|
>>> f.close()
|
|
|
|
Note that the script starts with a Unix-style '#!' line saying which
|
|
Python executable to run. The wrapper will use this to find the
|
|
correct Python executable.
|
|
|
|
We'll also copy cli.exe to the sample-directory with the name foo.exe:
|
|
|
|
>>> import pkg_resources
|
|
>>> f = open(os.path.join(sample_directory, 'foo.exe'), 'wb')
|
|
>>> bytes_written = f.write(
|
|
... pkg_resources.resource_string('setuptools', 'cli-32.exe')
|
|
... )
|
|
>>> f.close()
|
|
|
|
When the copy of cli.exe, foo.exe in this example, runs, it examines
|
|
the path name it was run with and computes a Python script path name
|
|
by removing the '.exe' suffic and adding the '-script.py' suffix. (For
|
|
GUI programs, the suffix '-script-pyw' is added.) This is why we
|
|
named out script the way we did. Now we can run out script by running
|
|
the wrapper:
|
|
|
|
>>> import subprocess
|
|
>>> cmd = [os.path.join(sample_directory, 'foo.exe'), 'arg1', 'arg 2',
|
|
... 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b']
|
|
>>> proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
|
|
>>> stdout, stderr = proc.communicate('hello\nworld\n'.encode('ascii'))
|
|
>>> bytes = sys.stdout.write(stdout.decode('ascii').replace('\r\n', '\n'))
|
|
\foo-script.py
|
|
['arg1', 'arg 2', 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b']
|
|
'hello\nworld\n'
|
|
non-optimized
|
|
|
|
This example was a little pathological in that it exercised windows
|
|
(MS C runtime) quoting rules:
|
|
|
|
- Strings containing spaces are surrounded by double quotes.
|
|
|
|
- Double quotes in strings need to be escaped by preceding them with
|
|
back slashes.
|
|
|
|
- One or more backslashes preceding double quotes quotes need to be
|
|
escaped by preceding each of them them with back slashes.
|
|
|
|
|
|
Specifying Python Command-line Options
|
|
--------------------------------------
|
|
|
|
You can specify a single argument on the '#!' line. This can be used
|
|
to specify Python options like -O, to run in optimized mode or -i
|
|
to start the interactive interpreter. You can combine multiple
|
|
options as usual. For example, to run in optimized mode and
|
|
enter the interpreter after running the script, you could use -Oi:
|
|
|
|
>>> f = open(os.path.join(sample_directory, 'foo-script.py'), 'w')
|
|
>>> bytes_written = f.write(
|
|
... """#!%(python_exe)s -Oi
|
|
... import sys
|
|
... input = repr(sys.stdin.read())
|
|
... print(sys.argv[0][-14:])
|
|
... print(sys.argv[1:])
|
|
... print(input)
|
|
... if __debug__:
|
|
... print('non-optimized')
|
|
... sys.ps1 = '---'
|
|
... """ % dict(python_exe=nt_quote_arg(sys.executable)))
|
|
>>> f.close()
|
|
>>> cmd = [os.path.join(sample_directory, 'foo.exe')]
|
|
>>> proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
>>> stdout, stderr = proc.communicate()
|
|
>>> bytes = sys.stdout.write(stdout.decode('ascii').replace('\r\n', '\n'))
|
|
\foo-script.py
|
|
[]
|
|
''
|
|
---
|
|
|
|
Testing the GUI Version
|
|
-----------------------
|
|
|
|
Now let's test the GUI version with the simple scipt, bar-script.py:
|
|
|
|
>>> import os, sys, tempfile
|
|
>>> from setuptools.command.easy_install import nt_quote_arg
|
|
>>> sample_directory = tempfile.mkdtemp()
|
|
>>> f = open(os.path.join(sample_directory, 'bar-script.pyw'), 'w')
|
|
>>> bytes_written = f.write(
|
|
... """#!%(python_exe)s
|
|
... import sys
|
|
... f = open(sys.argv[1], 'wb')
|
|
... bytes_written = f.write(repr(sys.argv[2]).encode('utf-8'))
|
|
... f.close()
|
|
... """ % dict(python_exe=nt_quote_arg(sys.executable)))
|
|
>>> f.close()
|
|
|
|
We'll also copy gui.exe to the sample-directory with the name bar.exe:
|
|
|
|
>>> import pkg_resources
|
|
>>> f = open(os.path.join(sample_directory, 'bar.exe'), 'wb')
|
|
>>> bytes_written = f.write(
|
|
... pkg_resources.resource_string('setuptools', 'gui-32.exe')
|
|
... )
|
|
>>> f.close()
|
|
|
|
Finally, we'll run the script and check the result:
|
|
|
|
>>> cmd = [
|
|
... os.path.join(sample_directory, 'bar.exe'),
|
|
... os.path.join(sample_directory, 'test_output.txt'),
|
|
... 'Test Argument',
|
|
... ]
|
|
>>> proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
>>> stdout, stderr = proc.communicate()
|
|
>>> print(stdout.decode('ascii'))
|
|
<BLANKLINE>
|
|
>>> f_out = open(os.path.join(sample_directory, 'test_output.txt'), 'rb')
|
|
>>> print(f_out.read().decode('ascii'))
|
|
'Test Argument'
|
|
>>> f_out.close()
|
|
|
|
|
|
We're done with the sample_directory:
|
|
|
|
>>> import shutil
|
|
>>> shutil.rmtree(sample_directory)
|
|
|