2017-09-28 88 views
12

Problem

Próbuję użyć PyInstaller do utworzenia aplikacji do użytku wewnętrznego w mojej firmie. Skrypt działa świetnie z działającego środowiska Pythona, ale traci coś po przetłumaczeniu na pakiet.PyInstaller, jak dołączyć pliki danych z zewnętrznego pakietu, który został zainstalowany przez pip?

W jaki sposób dołączać i odwoływać pliki danych, które sam potrzebuję w moim pakiecie, ale mam problemy z włączaniem lub odwoływaniem się do plików, które powinny wejść po zaimportowaniu.

Używam instalowanego na paczce pakietu o nazwie tk-tools, który zawiera kilka ładnych obrazów na panelowe wyświetlacze (wygląda jak diody LED). Problem polega na tym, że kiedy utworzyć skrypt pyinstaller, za każdym razem, jeden z tych obrazów jest odwołanie, otrzymuję błąd:

DEBUG:aspen_comm.display:COM23 19200 
INFO:aspen_comm.display:adding pump 1 to the pump list: [1] 
DEBUG:aspen_comm.display:updating interrogation list: [1] 
Exception in Tkinter callback 
Traceback (most recent call last): 
    File "tkinter\__init__.py", line 1550, in __call__ 
    File "aspen_comm\display.py", line 206, in add 
    File "aspen_comm\display.py", line 121, in add 
    File "aspen_comm\display.py", line 271, in __init__ 
    File "aspen_comm\display.py", line 311, in __init__ 
    File "lib\site-packages\tk_tools\visual.py", line 277, in __init__ 
    File "lib\site-packages\tk_tools\visual.py", line 289, in to_grey 
    File "lib\site-packages\tk_tools\visual.py", line 284, in _load_new 
    File "tkinter\__init__.py", line 3394, in __init__ 
    File "tkinter\__init__.py", line 3350, in __init__ 
_tkinter.TclError: couldn't open "C:\_code\tools\python\aspen_comm\dist\aspen_comm\tk_tools\img/led-grey.png": no such file or directory 

I wyglądał w tym katalogu w ostatniej linii - czyli tam, gdzie moja dystrybucja znajduje się - i okazało się, że nie ma katalogu tk_tools.

Pytanie

Jak uzyskać pyinstaller zebrać pliki danych importowanych pakietów?

Spec pliku

Obecnie mój datas jest pusty. Spec plik, stworzony z pyinstaller -n aspen_comm aspen_comm/__main__.py:

# -*- mode: python -*- 

block_cipher = None 


a = Analysis(['aspen_comm\\__main__.py'], 
      pathex=['C:\\_code\\tools\\python\\aspen_comm'], 
      binaries=[], 
      datas=[], 
      hiddenimports=[], 
      hookspath=[], 
      runtime_hooks=[], 
      excludes=[], 
      win_no_prefer_redirects=False, 
      win_private_assemblies=False, 
      cipher=block_cipher) 

pyz = PYZ(a.pure, a.zipped_data, 
      cipher=block_cipher) 

exe = EXE(pyz, 
      a.scripts, 
      exclude_binaries=True, 
      name='aspen_comm', 
      debug=False, 
      strip=False, 
      upx=True, 
      console=True) 

coll = COLLECT(exe, 
       a.binaries, 
       a.zipfiles, 
       a.datas, 
       strip=False, 
       upx=True, 
       name='aspen_comm') 

Kiedy patrzę w ciągu /build/aspen_comm/out00-Analysis.toc i /build/aspen_comm/out00-PYZ.toc, znaleźć wpis, który wygląda jak stwierdzono pakiet tk_tools. Dodatkowo istnieją funkcje pakietu tk_tools, które działają idealnie przed dotarciem do punktu znajdowania plików danych, więc wiem, że jest on importowany gdzieś, po prostu nie wiem gdzie. Kiedy robię wyszukiwania dla tk_tools, nie mogę znaleźć odniesienia do niego w strukturze pliku.

Próbowałem również opcji --hidden-imports z takimi samymi wynikami.

częściowe rozwiązanie

Gdybym „ręcznie” dodać ścieżkę do pliku spec użyciu datas = [('C:\\_virtualenv\\aspen\\Lib\\site-packages\\tk_tools\\img\\', 'tk_tools\\img\\')] i datas=datas w Analysis, to wszystkie prace zgodnie z oczekiwaniami. To zadziała, ale wolałbym, aby PyInstaller znalazł dane pakietu, ponieważ jest on wyraźnie zainstalowany. Będę szukał rozwiązania, ale - na razie - prawdopodobnie wykorzystam to nieidealne rozwiązanie.

Jeśli masz kontrolę nad pakietem ...

Następnie można użyć stringify na podpakiecie, ale to działa tylko jeśli jest to twój własny pakiet.

+0

Jeśli dobrze rozumiem, to, co chcesz oprócz tego rozwiązania https://stackoverflow.com/a/31966932/4724196 to metoda automatycznego wykrywania statycznych plików 'tk_tools', czy to prawda? – HassenPy

+0

@HassenPy Tak, ale podejrzewam, że automatyczne wykrywanie jest już częścią pakietu PyInstaller, po prostu nie znalazłem poprawnej składni. Łączone rozwiązanie nadal ma bezwzględną ścieżkę dla plików statycznych zakodowanych w pliku '.spec'. Miałem wrażenie, że pakiety 'pip installed' powinny zostać wciągnięte do PyInstaller przy niewielkim wysiłku, ale to najwyraźniej tak nie jest. Dziękuję za uwagę! – slightlynybbled

Odpowiedz

1

edytowane Dodaj

Aby rozwiązać ten problem bardziej trwale, stworzyłem paczkę PIP-instalowanego nazwie stringify który odbędzie plik lub katalog i przekształcić go w ciąg Pythona tak, że pakiety takie jak pyinstaller je rozpoznać jako rodzime pliki Pythona.

Zapoznaj się z project page, opinia jest mile widziany!


Original Odpowiedź

Odpowiedź jest rondo nieco i dotyczy sposobu, w jaki tk_tools pakuje zamiast pyinstaller.

Ktoś niedawno uświadomił mi technika, w której dane binarne - takich jak dane obrazu - może być przechowywany jako base64 ciąg:

with open(img_path, 'rb') as f: 
    encoded_string = base64.encode(f.read()) 

Zakodowany ciąg faktycznie przechowuje dane. Jeśli pakiet początkowy po prostu przechowuje pliki pakietów jako ciągi, a nie jako pliki obrazów i tworzy plik python z danymi dostępnymi jako zmienna łańcuchowa, wówczas możliwe jest po prostu uwzględnienie danych binarnych w pakiecie w formie, którą znajdzie pyinstaller i wykrywaj bez interwencji.

Rozważmy poniższe funkcje:

def create_image_string(img_path): 
    """ 
    creates the base64 encoded string from the image path 
    and returns the (filename, data) as a tuple 
    """ 

    with open(img_path, 'rb') as f: 
     encoded_string = base64.b64encode(f.read()) 

    file_name = os.path.basename(img_path).split('.')[0] 
    file_name = file_name.replace('-', '_') 

    return file_name, encoded_string 


def archive_image_files(): 
    """ 
    Reads all files in 'images' directory and saves them as 
    encoded strings accessible as python variables. The image 
    at images/my_image.png can now be found in tk_tools/images.py 
    with a variable name of my_image 
    """ 

    destination_path = "tk_tools" 
    py_file = '' 

    for root, dirs, files in os.walk("images"): 
     for name in files: 
      img_path = os.path.join(root, name) 
      file_name, file_string = create_image_string(img_path) 

      py_file += '{} = {}\n'.format(file_name, file_string) 

    py_file += '\n' 

    with open(os.path.join(destination_path, 'images.py'), 'w') as f: 
     f.write(py_file) 

Jeśli archive_image_files() jest umieszczony w pliku konfiguracyjnym, wówczas <package_name>/images.py jest tworzony automatycznie w każdej chwili skrypt instalacyjny jest uruchamiany (podczas tworzenia kół i montaż).

Mogę poprawić tę technikę w niedalekiej przyszłości. Dziękuję wszystkim za pomoc,

j

1

Poniższy kod położy wszystkich plików PNG w katalogu do folderu na najwyższym poziomie dołączonemu aplikacji o nazwie imgs:

datas=[("C:\\_code\\tools\\python\\aspen_comm\\dist\\aspen_comm\\tk_tools\\img\\*.png", "imgs")], 

Następnie można odwoływać się do nich z os.path.join("imgs", "your_image.png") w kodzie.

+0

Ścieżka 'C: \ _ code \ tools \ python \ aspen_comm \ dist \ aspen_comm' istnieje, ale nie ma w niej żadnego katalogu o nazwie' tk_tools', więc to rozwiązanie nie jest obecnie możliwe. Dziękuję za obejrzenie! – slightlynybbled

+0

Musiałem źle zrozumieć. Gdzie są twoje obrazy? – Steampunkery

+0

Obrazy są instalowane, gdy 'tk_tools' jest instalowany z pipem, więc znajdują się one w wirtualnym środowisku Pythona.Miałem nadzieję, że funkcja automatycznego wykrywania odkryje, że pliki znajdowały się w 'tk_tools', a następnie je ściągały. – slightlynybbled

0

I rozwiązać ten, wykorzystując fakt, że plik spec jest kod Pythona, który zostanie wykonany. Możesz uzyskać katalog główny pakietu dynamicznie podczas fazy budowania PyInstaller i użyć tej wartości na liście datas. W moim przypadku mam coś takiego w moim pliku .spec:

import os 
import importlib 

package_imports = [['package_name', ['file0', 'file1']] 

datas = [] 
for package, files in package_imports: 
    proot = os.path.dirname(importlib.import_module(package).__file__) 
    datas.extend((os.path.join(proot, f), package) for f in files) 

i korzystać z wynikowej liście datas jako parametry do Analysis.