Добавить конфигурационный файл вне Pyinstaller --onefile exe в каталог dist
ситуация
Я использую Pyinstaller на Windows, чтобы сделать.exe-файл моего проекта.
Я хотел бы использовать --onefile
возможность получить чистый результат и легко распространять файл / программу.
Моя программа использует config.ini
файл для хранения настроек конфигурации. Этот файл может быть настроен пользователями.
проблема
С помощью --onefile
Опция Pyinstaller помещает все объявленные "данные-файлы" в единый .exe
файл файл.
Я видел этот запрос, но он дает возможность добавить файл пакета внутри одного файла, а не снаружи, на том же уровне .exe
и в том же dist
каталог.
В какой-то момент я решил использовать команду shutil.copy внутри.spec-файла, чтобы скопировать этот файл... но я думаю, что это неправильно.
Кто-нибудь может мне помочь? Я буду признателен за это:-)
7 ответов
Репозиторий на Github помог мне найти решение моего вопроса.
Я использовал shutil
модуль и .spec
файл для добавления дополнительных файлов данных (в моем случае config-sample.ini
файл) в папку dist с помощью Pyinstaller --onefile
вариант.
Создайте файл.spec для pyinstaller
Прежде всего я создаю файл makepec с нужными мне опциями:
$ pyi-makespec --onefile --windowed --name exefilename scriptname.py
Эта команда создает exefilename.spec
файл для использования с Pyinstaller
Измените exefilename.spec, добавив shutil.copyfile
Теперь я отредактировал exefilename.spec
добавив в конец файла следующий код.
import shutil
shutil.copyfile('config-sample.ini', '{0}/config-sample.ini'.format(DISTPATH))
shutil.copyfile('whateveryouwant.ext', '{0}/whateveryouwant.ext'.format(DISTPATH))
Этот код копирует файлы данных, необходимые в конце процесса компиляции. Вы можете использовать все методы, доступные в shutil
пакет.
Запустите PyInstaller
Последний шаг - запустить процесс компиляции.
pyinstaller --clean exefilename.spec
В результате в папке dist у вас должен быть скомпилированный файл.exe вместе с скопированными файлами данных.
рассмотрение
В официальной документации Pyinstaller я не нашел возможности получить этот результат. Я думаю, что это можно рассматривать как обходной путь... это работает.
Для тех, кто спотыкается об эту тему и ищет, как получить доступ к файлам, которые находятся на том же уровне, что и выходной файл. Хитрость в том, что sys.executable находится там, где находится однофайловый .exe. Так просто это делает трюк:
import sys
import os.path
CWD = os.path.abspath(os.path.dirname(sys.executable))
использовать его, например, с
with open(os.path.join(CWD, "config.ini")) as config_file:
print(config_file.read())
Причина, по которой /относительные пути не работают
Исполняемый файл — это просто исполняемый архив, который при выполнении извлекается во временный каталог, где выполняются файлы .pyc. Поэтому, когда вы звоните
Мое решение похоже на отличное решение @Stefano-Giraldi. Мне было отказано в разрешении при передаче каталогов вshutil.copyfile
.
В итоге я использовал shutil.copytree
:
import sys, os, shutil
site_packages = os.path.join(os.path.dirname(sys.executable), "Lib", "site-packages")
added_files = [
(os.path.join(site_packages, 'dash_html_components'), 'dash_html_components'),
(os.path.join(site_packages, 'dash_core_components'), 'dash_core_components'),
(os.path.join(site_packages, 'plotly'), 'plotly'),
(os.path.join(site_packages, 'scipy', '.libs', '*.dll'), '.')
]
working_dir_files = [
('assets', 'assets'),
('csv', 'csv'))
]
print('ADDED FILES: (will show up in sys._MEIPASS)')
print(added_files)
print('Copying files to the dist folder')
print(os.getcwd())
for tup in working_dir_files:
print(tup)
to_path = os.path.join(DISTPATH, tup[1])
if os.path.exists(to_path):
if os.path.isdir(to_path):
shutil.rmtree(to_path)
else:
os.remove(to_path)
if os.path.isdir(tup[0]):
shutil.copytree(tup[0], to_path )
else:
shutil.copyfile(tup[0], to_path )
#### ... Rest of spec file
a = Analysis(['myapp.py'],
pathex=['.', os.path.join(site_packages, 'scipy', '.libs')],
binaries=[],
datas=added_files,
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='myapp',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True )
Это позволяет избежать папки _MEI и не позволяет ей копировать файлы конфигурации, которые вы хотите, в папку dist, а не во временную папку.
Надеюсь, это поможет.
Если какой-либо из ваших сценариев Python использует внешние файлы (JSON, текст или любые файлы конфигурации) и вы хотите включить эти файлы в исполняемый файл, выполните следующие действия в системе Windows .
Учитывая, что есть сценарий Python, и он читает файл JSON, наша цель — добавить файл в какой-нибудь подходящий каталог, где к нему может получить доступ приложение во время его работы (как.exe
).
Этот ответ применим, даже если он не читается напрямую. может быть прочитан любым из используемых модулей, и ответ все равно поможет.
app.py
config.json
Шаг 1. Разрешение пути к файлу JSON во время работы приложения
Когда приложение запущено, файлы копируются во временное место в вашей системе Windows.C:\Users\<You>\AppData\Local\Temp\MEIxxx
. Итак, необходимо прочитать файл JSON из этого временного местоположения. Но как приложение узнает во время выполнения, в каком каталоге ему нужно искать файлы? Из этого отличного ответа в у нас будет функция,
def resolve_path(path):
if getattr(sys, "frozen", False):
# If the 'frozen' flag is set, we are in bundled-app mode!
resolved_path = os.path.abspath(os.path.join(sys._MEIPASS, path))
else:
# Normal development mode. Use os.getcwd() or __file__ as appropriate in your case...
resolved_path = os.path.abspath(os.path.join(os.getcwd(), path))
return resolved_path
# While reading the JSON file
with open(resolve_path("config.json"), "r") as jsonfile:
# Access the contents here
Теперь знает, где искать файл. Перед этим нам нужно указать PyInstaller скопировать в этот каталог временных файлов.
Обратите внимание, вам необходимо использовать
resolve_path
везде, где вы используете относительный путь в своих сценариях Python.
Шаг 2: Создайте и отредактируйте файл для копированияconfig.json
Поскольку мы хотим создать исполняемый файлapp.py
, мы сначала создадим для него файл (ссылаясь на ответ @Stefano Giraldi )
pyi-makespec --onefile --windowed --name appexe app.py
Откройте полученныйappexe.spec
файл, и вы заметите это содержимое,
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
...
datas=[],
...
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
...
)
Создать новый списокfiles
и передать его вdatas
аргумент,
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
files = [
( 'config.json' , '.' )
]
a = Analysis(
...
datas=files,
...
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
...
)
Кортеж( 'config.json' , '.' )
обозначает исходный и конечный пути файла. Путь назначения относится к каталогу временных файлов.
Шаг 3: Создайте исполняемый файл
Наконец, мы можем запустить.spec
файл для сборки установщика,
pyinstaller --clean appexe.spec
Полученный установщик теперь должен работать без каких-либоFileNotFoundError
с.
Пробовал много подходов, этот работает для меня:
Не используйте расширение .py для файла конфигурации. Вместо этого используйте json. Json не годится, потому что в нем нельзя писать комментарии, но если вы хотите сделать exe, вам придется его использовать, к сожалению.
Внутри вашего скрипта загрузите этот файл настроек таким образом
filename = "settings.json"
contents = open(filename).read()
config = eval(contents)
setting1 = config['setting1']
Запустите pyinstaller или auto-py-to-exe (пробовал оба, все работает)
Поместите файл settings.json в ту же папку, где находится ваш файл .exe.
Запустите его, и он возьмет настройки из этого файла.
Решение есть, но оно не самое лучшее:
config_str = """
some configuration code string
"""
with open('path_to_somewhere\\config.ini', 'w', encoding="gbk") as writer:
writer.write(config_str)
Я решил эту проблему, удалив файл конфигурации (для меня config.py) из папки проекта перед запуском pyinstaller.