Pyinstaller always fails to package projects containing celery tasks. Solution

When we use pyinstaller to package and release a project containing celery tasks, if the project packaging is successful, but when running, as long as the celery process is opened, an error message is reported: exe: maximum recursion depth exceeded while calling a Python object…, even if it is set through sys.setrecursionlimit When the system’s maximum recursion depth cannot solve the problem, don’t panic, I will teach you how to solve it.

ps: In order to prevent some smart people from copying and compiling the code below, I will first explain the precautions. It is best not to store code files in the dist folder in the project root directory, because the following solution In this method, all files in this folder will be automatically overwritten when building the compiled file. If there are these two folders in the root directory of your project, please change them to other names! ! !

OK, we officially enter the topic. First, create a new build.py (The file name can be freely named) file in the root directory of your project (the same directory as the entry), as follows:

Enter the following code into the build.py file:

#!/usr/bin/env python
# cython: language_level=3
# -*- coding: utf-8 -*-
# @Author : LuBowen
# @Number : 20210509
# @FileName :build
# @Time :2023/9/7 13:37
# @Software: PyCharm Community Edition
# @Version: Python3
# ====================================
from subprocess import call
from distutils.core import setup
from Cython.Build import cythonize
import os
import shutil
from tqdm import tqdm

# Absolute path to the project root directory used for compilation (replace here with your project root directory - the absolute path to the directory where the entry file is located)
project_dir = "D:/projectsFiles/vision"
# Absolute path of non-compiled files - the included files will not be compiled and will be copied directly to the compiled project directory after the project is compiled (if there are python files or other files in your project that do not need to be compiled, please Put their absolute paths into the list below, these files will not be compiled and will be automatically copied to the compiled project package/folder after the project is compiled)
include_files = [
        "D:/projectsFiles/vision/.env",
        "D:/projectsFiles/vision/DockerFile",
        "D:/projectsFiles/vision/r.txt",
]


# noinspection PyMissingOrEmptyDocstring
def collect_file(project_path, direct_name=None, file_end=('pyd', 'o'), include=()):
    project_path_direct_name = os.path.basename(project_path)
    if not direct_name:
        direct_name = project_path_direct_name
    base_path = f'{<!-- -->project_path}/dist/{<!-- -->direct_name}'.replace('', '/')
    for root, dirs, files in os.walk(project_path):
        root_format = str(root).replace('', '/')
        if base_path in f'{<!-- -->root_format}/{<!-- -->direct_name}':
            continue
        if os.path.abspath('./build').replace('', '/') in root_format:
            continue
        if os.path.abspath('./dist').replace('', '/') in root_format:
            continue
        if '__pycache__' in root_format:
            continue
        files = tqdm(files)
        for file in files:
            files.set_description(f'Collecting compiled file:{<!-- -->root_format}')
            if str(file).split('.')[-1] in file_end or f'{<!-- -->root_format}/{<!-- -->file}' in include:
                current_file = f'{<!-- -->root_format}/{<!-- -->file}'
                target_direct = root_format.replace(str(project_path), base_path, 1)
                if not os.path.exists(target_direct):
                    os.makedirs(target_direct)
                shutil.copy(current_file, target_direct)


# noinspection PyMissingOrEmptyDocstring
def clear_file(project_path, file_end=('pyd', 'o', 'c', 'pyc'), direct_name=None, exclude=()):
    project_path_direct_name = os.path.basename(project_path)
    if not direct_name:
        direct_name = project_path_direct_name
    base_path = f'{<!-- -->project_path}/dist/{<!-- -->direct_name}'.replace('', '/')
    for root, dirs, files in os.walk(project_path):
        root_format = str(root).replace('', '/')
        if base_path in f'{<!-- -->root_format}/{<!-- -->direct_name}':
            continue
        if os.path.abspath('./build').replace('', '/') in root_format:
            continue
        if os.path.abspath('./dist').replace('', '/') in root_format:
            continue
        files = tqdm(files)
        for file in files:
            files.set_description(f'Clear:{<!-- -->root_format}')
            if f'{<!-- -->root_format}/{<!-- -->file}' in exclude:
                continue
            if str(file).split('.')[-1] in file_end:
                current_file = f'{<!-- -->root_format}/{<!-- -->file}'
                os.remove(current_file)


# noinspection PyMissingOrEmptyDocstring
def compiling(project_path, direct_name=None, exclude=()):
    compile_file_set = set()
    project_path_direct_name = os.path.basename(project_path)
    if not direct_name:
        direct_name = project_path_direct_name
    base_path = f'{<!-- -->project_path}/dist/{<!-- -->direct_name}'.replace('', '/')
    for root, dirs, files in os.walk(project_path):
        root_format = str(root).replace('', '/')
        if base_path in f'{<!-- -->root_format}/{<!-- -->direct_name}':
            continue
        if os.path.abspath('./build').replace('', '/') in root_format:
            continue
        if os.path.abspath('./dist').replace('', '/') in root_format:
            continue
        if '__pycache__' in root_format:
            continue
        target_path = root_format.replace(str(project_path), base_path, 1)
        if not os.path.exists(target_path):
            os.makedirs(target_path)
        files = tqdm(files)
        for file in files:
            files.set_description(f'Collecting python file:{<!-- -->root_format}')
            if str(file).endswith('.py') and f'{<!-- -->root_format}/{<!-- -->file}' not in exclude:
                compile_file_set.add(f'{<!-- -->root_format}/{<!-- -->file}')
    print(f'Collected python file number:{<!-- -->len(compile_file_set)}')
    setup(ext_modules=cythonize(compile_file_set))


# noinspection PyMissingOrEmptyDocstring
def pyinstaller_cmd_para(server_file, app_name):
#"""
# If compiled and run, an error will be reported: ModuleNotFoundError: No module named 'module name'
# First import in the project environment: pip install module name
\t#\tThen
# '--hidden-import', 'module name',
# Add to cmd, pay attention to the example!
#"""
    cmd = ['pyinstaller',
           '-y',
           f'{<!-- -->server_file}.py',
           '--name', f'{<!-- -->app_name}',
           '--hidden-import', 'celery.app.events',
           '--hidden-import', 'celery.backends.redis',
           '--hidden-import', 'celery.backends',
           '--hidden-import', 'celery.fixups',
           '--hidden-import', 'celery.app.amqp',
           '--hidden-import', 'kombu.transport.redis',
           '--hidden-import', 'celery.fixups.django',
           '--hidden-import', 'celery.concurrency.gevent',
           '--hidden-import', 'celery.apps.worker',
           '--hidden-import', 'engineio.async_gevent',
           '--hidden-import', 'gevent',
           '--hidden-import', 'psycopg2',
           '--hidden-import', 'pymysql',
           '--hidden-import', 'celery.app.log',
           '--hidden-import', 'celery.worker.components',
           '--hidden-import', 'celery.worker.autoscale',
           '--hidden-import', 'celery.worker.consumer',
           '--hidden-import', 'celery.app.control',
           '--hidden-import', 'celery.events.state',
           '--hidden-import', 'celery.worker.strategy',
           '--hidden-import', 'skimage',
           ]
    call(cmd)


# noinspection PyMissingOrEmptyDocstring
def build(entry_file, app_name):
    pyinstaller_cmd_para(entry_file, app_name)
    compiling(project_path=project_dir, direct_name=app_name)
    collect_file(project_path=project_dir, direct_name=app_name, include=include_files)
    clear_file(project_path=project_dir, direct_name=app_name)
    print('completed!')


if __name__ == '__main__':
#The two parameters here are: entry file name, compiled project name
    build('startup', 'startup')
    #Terminal input command: python build.py build_ext --inplace to start compilation

Save the above code file, open the terminal, enter the project root directory, enter python build.py (please replace this python file name with your own file name) build_ext –inplace and press Enter to start Compile like:

Wait for the compilation to complete, as follows:

The following display indicates completion:

There will be a build/ folder and a dist/ folder in the root directory. The dist folder contains the packaged project, as follows:


At this point, the project packaging is complete!