background

I have used PyInstaller and Py2exe before when packaging automation tools, and mainly encountered the following problems:

  1. Excessive volume due to dependencies (I used numpy, Opencv-Python, Pyside2, etc. Third-party libraries)

  2. Missing dependencies (probably because I didn’t configure hidden-import properly, some third-party packages implicitly call other packages, fixing this is still a pain)

  3. The path is out of place, so my own module can’t find the location of the package, and the relative path is also prone to error

  4. How do I include non-code class resource files in a package

    In a typical scenario, my program generates some data in the data directory. Before packaging, the data directory had a lot of cached data (which I could still use and couldn’t delete), but I struggled to include the data directory and its subdirectories as “empty folders” in the packaging tool.

  5. How to do incremental updates. If you’ve already installed version 1.0.0, I’ve released version 1.0.1 with the same third party dependencies, and if you pack them in, it’s a little bit too big

Each problem is not easy to solve, and the pyInstaller parameters are worth a lot of research (there are many in-depth explanations on the web)… Is there a one-click packing tool for lazy people? This is the Python library introduced in this article: Pyportable – Installer.

PyPortable Installer

Pyportable – Installer is a Python project packaging tool inspired by Poetry and intended to emerge as an alternative to PyInstaller.

Pyportable – Installer manages packaging through an all-in-one configuration file that packages your Python project as “install-free” software without requiring users to install Python programs or third-party dependencies (note: This feature requires you to enable the virtual environment option in the configuration), truly “out of the box, double-click to start “.

features

Pyportable – Installer has the following features:

  1. The volume after packaging is very small. The same magnitude as your source code (which is usually only a few hundred KILobytes) without the virtual environment attached

  2. Easy to use. You only need to maintain a PyProject.json configuration file. In a fast-iterating environment, you can even change the version number to produce new packaging results immediately

  3. Fast packaging speed. A small to medium size project can generate packaging results in seconds

  4. Source code encryption. Use the PyArmor library to obfuscate source code for code security

  5. Out of the box. The directory structure is very clear after the package is packaged, as shown in the following example:

    My_project | = dist. | = hello_world_0 | = checkups 1.0: Some of the attached inspection tool (optional) | - doctor. Pyc | - update. Pyc | - mainifest. Json | = SRC: your source code will be compiled and placed in this directory | -... | = lib: some custom third-party libraries will be under this directory | = pytransform: used to run the encrypted source code, to ensure the safety of code | - set p y | - _pytransform. DLL | = venv: Own virtual environment (optional) | -... | - README. HTML: README document | - Hello World. Exe: double-click to start!Copy the code
  6. Do not break the relative path. In the packaged ~/ SRC directory, all folders retain the original project directory structure. When the program starts, it switches the working directory to the same directory as the startup script, which means that the relative path that you started with in the original project remains the same when packaged

  7. Painless update (this feature will be available in a later release). Double click in the software directorycheckup/update.pycTo obtain the latest version of the software

  8. Activation and authorization (this feature will be available in a later release). This feature is provided by PyArmor,pyportable-installerIntegrate it into the All-in-One profile as well

The working process

Its flow can be summarized as follows:

  1. Prepare the items you want to pack
  2. Create a new all-in-one configuration file in the root directory of your project: ‘Pyproject. json’
    1. The file name is optional
    2. There is a template file available. There is also a manual for viewing the format and function of each option
  3. throughpyportable-installerProcess this configuration file to complete packaging:
from pyportable_installer import full_build
full_build('pyproject.json')
Copy the code

Pyportable – Installer generates the following for your project:

  1. Encrypted source code file

    1. The encrypted file suffix is still ‘.py’
    2. Encrypted file by~/lib/pytransformPackages are decoded at run time
    3. Use a text editor to open the encrypted file with the ciphertext shown below:
  2. An initiator in exe format

  3. Custom initiator icon (note: the default icon is python.ico)

  4. A clean virtual environment (this is optional)

  5. The entire packaged result exists as a folder

You can then make this folder into a zipped file and distribute it as a “install-free version” of the software.

Installation and use

Install Pyportable – Installer via PIP:

pip install pyportable-installer
Copy the code

The following uses a “Hello World” project as an example to describe the specific packaging work:

Suppose the project structure of “Hello World” is as follows:

hello_world |= data |- names.txt :: | Elena | Lorez | Mei |= hello_world |- main.py :: | def say_hello(file): | with open(file, 'r') as f: | for name in f: | print(f'Hello {name}! ') | | if __name__ == '__main__': | say_hello('.. /data/names.txt') |- README.mdCopy the code

Create a new ‘PyProject. json’ in the project root directory (a template file is available here) and fill in the following:

{
    "app_name": "Hello World"."app_version": "0.1.0 from"."description": "Say hello to everyone."."author": "Likianta <[email protected]>"."build": {
        "proj_dir": "hello_world"."dist_dir": "dist/{app_name_lower}_{app_version}"."icon": ""."target": {
            "file": "hello_world/main.py"."function": "say_hello"."args": [".. /data/names.txt"]."kwargs": {}},"readme": "README.md"."module_paths": []."attachments": {
            "data": "assets"
        },
        "required": {
            "python_version": "3.6"."enable_venv": true."venv": ""
        },
        "enable_console": true
    },
    "note": ""
}
Copy the code

Note: Please refer to more usagePyproject Template.

To generate the installation package, run the following code:

from pyportable_installer import full_build
full_build('pyproject.json')

When incremental updates are made, run the following:
# from pyportable_installer import min_build
# min_build('pyproject.json')

If you don't need to encrypt the source code, run the following (for debugging only!) :
# from pyportable_installer import debug_build
# debug_build('pyproject.json')
Copy the code

The generated installation package is located at hello_world/dist/hello_world_0.1.0:

The hello_world | = dist. | = hello_world_0 1.0 | = checkups | - doctor. Pyc | - update. Pyc | - manifest. Json | = SRC | = data | - names.txt |= hello_world |- main.py: This is the encrypted script, With the source file name | - bootloader. Py | = lib | = pytransform | - set p y | - _pytransform. DLL | = venv | = site - packages | - python. Exe | -... | | - README. Md - Hello World. Exe: double click start | -...Copy the code

Matters needing attention

  1. If you have the virtual environment option enabled, the installation path must not contain Chinese, otherwise it will cause startup failure (this issue may be related to the Embed Python interpreter)
  2. pyportable-installerA Python 3.9 interpreter is required

FAQ

Runtime error: Tkinter library not found

This is because the virtual environment in your published project uses embedded Python, which does not come with the Tkinter library.

Solution: See this article: How to add Tk suites to Embedded Python.

Pywin32, Win32, Win32ClipBoard, etc

Short answer

Copy the two DLL files in venv/lib/site-packages/ pyWin32_system32 to venv/lib/site-packages/win32/lib.

Detailed operation

Suppose your project is structured as follows:

my_project |= assets |= src |- main.py |= venv |= lib |= site-packages: If your project is useful to pywin32 library, will have the following directory | = win32 | = win32com | = win32comext | = pywin32_system32 | =... |- pyproject.jsonCopy the code
  1. copyvenv/lib/site-packages/win32assets/win32
  2. copyvenv/lib/site-packages/pywin32_system32The next two DLL files toassets/win32/libdirectory
  3. inpyproject.jsonAdd the following (see highlights)In piecesSymbol) :
{ "app_name": "My Project", "app_version": "..." , "description": "..." , "author": "..." , "build": { "proj_dir": "src", "dist_dir": "dist/{app_name_lower}_{app_version}", "icon": "", "target": { "file": "src/main.py", "function": "main", "args": [], "kwargs": {} }, "readme": "", "attachments": "Assets /win32": "assets,dist_lib"}, "module_paths": Path "{dist_lib}/win32", "{dist_lib}/win32/lib"], "required": {"python_version": "3.9", "enable_venv": true, // ◆ Enter the venv directory of your project "venv": "./venv"}, "enable_console": true }, "note": "" }Copy the code

Reference: stackoverflow.com/questions/2…