Fixing KeyError: ‘Data’ errors with PyInstaller 5.8

Fixing KeyError: ‘Data’ errors with PyInstaller 5.8

Let’s say you use PyInstaller to build your Python app into an easily distributable bundle. You make the spec file for a single executable version and one for separate assets, libs, and executable.

Separate version:

# -*- mode: python ; coding: utf-8 -*-
import PyInstaller.config
import os
PyInstaller.config.CONF['distpath'] = './dict_release'

block_cipher = None
path = os.getcwd()


a = Analysis(['..\\main.py'],
             pathex=[path + '\\..\\MF_run_counter'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
a.datas += [('d2icon.png', path+'\\..\\MF_run_counter\\media\\d2icon.png', 'Data')]
a.datas += [('run_sound.wav', path+'\\..\\MF_run_counter\\media\\run_sound.wav', 'Data')]
a.datas += [('icon.ico', path+'\\..\\MF_run_counter\\media\\icon.ico', 'Data')]
a.datas += [('item_library.csv', path+'\\..\\MF_run_counter\\media\\item_library.csv', 'Data')]
a.datas += [('stat_map.csv', path+'\\..\\MF_run_counter\\media\\stat_map.csv', 'Data')]
a.datas += [('caret-down.png', path+'\\..\\MF_run_counter\\media\\caret-down.png', 'Data')]
a.datas += [('caret-up.png', path+'\\..\\MF_run_counter\\media\\caret-up.png', 'Data')]
a.datas += [('about_icon.png', path+'\\..\\MF_run_counter\\media\\about_icon.png', 'Data')]
a.datas += [('default_eth_grail_data.json', path+'\\..\\MF_run_counter\\utils\\default_eth_grail_data.json', 'Data')]
a.datas += [('default_grail_data.json', path+'\\..\\MF_run_counter\\utils\\default_grail_data.json', 'Data')]
pyz = PYZ(a.pure, a.zipped_data,
          cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='mf_timer',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=False , icon=path+'\\media\\icon.ico')
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               upx_exclude=[],
               name='mf_timer')

And the single file one:

# -*- mode: python ; coding: utf-8 -*-
import PyInstaller.config
import os
PyInstaller.config.CONF['distpath'] = './release'

block_cipher = None
path = os.getcwd()

a = Analysis(['..\\main.py'],
             pathex=[path + '\\..\\MF_run_counter'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
a.datas += [('d2icon.png', path+'\\..\\MF_run_counter\\media\\d2icon.png', 'Data')]
a.datas += [('run_sound.wav', path+'\\..\\MF_run_counter\\media\\run_sound.wav', 'Data')]
a.datas += [('icon.ico', path+'\\..\\MF_run_counter\\media\\icon.ico', 'Data')]
a.datas += [('item_library.csv', path+'\\..\\MF_run_counter\\media\\item_library.csv', 'Data')]
a.datas += [('stat_map.csv', path+'\\..\\MF_run_counter\\media\\stat_map.csv', 'Data')]
a.datas += [('caret-down.png', path+'\\..\\MF_run_counter\\media\\caret-down.png', 'Data')]
a.datas += [('caret-up.png', path+'\\..\\MF_run_counter\\media\\caret-up.png', 'Data')]
a.datas += [('about_icon.png', path+'\\..\\MF_run_counter\\media\\about_icon.png', 'Data')]
a.datas += [('default_eth_grail_data.json', path+'\\..\\MF_run_counter\\utils\\default_eth_grail_data.json', 'Data')]
a.datas += [('default_grail_data.json', path+'\\..\\MF_run_counter\\utils\\default_grail_data.json', 'Data')]
pyz = PYZ(a.pure, a.zipped_data,
          cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='mf_timer',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=False,
          icon=path+'\\media\\icon.ico')

They’re almost identical, the differences boil down to how the binaries are moved around, as you’d expect. And both build fine with PyInstaller 5.7. For the record, and for debugging purposes, the last release that still supports this version and has a Windows installer is Python 3.10.11.

Then you decide it’s time to upgrade, and change your requirement to PyInstaller 5.8. The separate version still builds fine, but… the single file build errors out:

14619 INFO: Building PKG (CArchive) mf_timer.pkg
Traceback (most recent call last):
  File "C:\Users\noobient\AppData\Local\Programs\Python\Python310\lib\runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Users\noobient\AppData\Local\Programs\Python\Python310\lib\runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "C:\Users\noobient\AppData\Local\Programs\Python\Python310\Scripts\pyinstaller.exe\__main__.py", line 7, in <module>
  File "C:\Users\noobient\AppData\Local\Programs\Python\Python310\lib\site-packages\PyInstaller\__main__.py", line 194, in _console_script_run
    run()
  File "C:\Users\noobient\AppData\Local\Programs\Python\Python310\lib\site-packages\PyInstaller\__main__.py", line 180, in run
    run_build(pyi_config, spec_file, **vars(args))
  File "C:\Users\noobient\AppData\Local\Programs\Python\Python310\lib\site-packages\PyInstaller\__main__.py", line 61, in run_build
    PyInstaller.building.build_main.main(pyi_config, spec_file, **kwargs)
  File "C:\Users\noobient\AppData\Local\Programs\Python\Python310\lib\site-packages\PyInstaller\building\build_main.py", line 977, in main
    build(specfile, distpath, workpath, clean_build)
  File "C:\Users\noobient\AppData\Local\Programs\Python\Python310\lib\site-packages\PyInstaller\building\build_main.py", line 899, in build
    exec(code, spec_namespace)
  File "build_exe\onefile_exe.spec", line 33, in <module>
    exe = EXE(pyz,
  File "C:\Users\noobient\AppData\Local\Programs\Python\Python310\lib\site-packages\PyInstaller\building\api.py", line 576, in __init__
    self.pkg = PKG(
  File "C:\Users\noobient\AppData\Local\Programs\Python\Python310\lib\site-packages\PyInstaller\building\api.py", line 233, in __init__
    self.__postinit__()
  File "C:\Users\noobient\AppData\Local\Programs\Python\Python310\lib\site-packages\PyInstaller\building\datastruct.py", line 173, in __postinit__
    self.assemble()
  File "C:\Users\noobient\AppData\Local\Programs\Python\Python310\lib\site-packages\PyInstaller\building\api.py", line 304, in assemble
    archive_toc.append((dest_name, src_name, self.cdict.get(typecode, False), self.xformdict[typecode]))
KeyError: 'Data'

Then comes several hours of googling with literally just one hit in Google for this specific error. And guess what their “solution” is:

I downgraded pyinstaller to a proven 5.1, and solved it when I started to start from generating the executable file.

Gee, that helps a lot 😀 Then I stumbled upon a lot of stuff, and finally it came down to a random StackOverflow question. Not because it actually resolves my issue, but because I realized an interesting tidbit. It’s about the syntax:

a.datas += [('Converter-GUI.ico', 'C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico', 'DATA')]

Notice that capitalized ‘DATA’? But… hang on a sec, both spec files have it like ‘Data’, and only one of them broke after the upgrade. So it can’t be it, right? Out of pure desperation I tried it anyway, changed it to all caps ‘DATA’. Guess what:

15718 INFO: Building EXE from EXE-00.toc completed successfully.

Are you fookin kidding me? So in a nutshell, if you upgrade PyInstaller to anything newer than 5.7.0 AND you use ‘Data’ instead of ‘DATA’ in your spec file, it’ll error out, BUT only if you use a single file build. Why, Python, why are you doing this to me. I guess they fixed their syntax enforcement, but only halfway through? I really don’t know. There’s no mention of this in the changelog, so I wouldn’t know. BTW this behavior remains so up until the latest version as of now, 6.16.0. Maybe I should file a ticket.

Anyhow, TLDR use ‘DATA’ in your spec file and you should be good.

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *