"""https://stackoverflow.com/a/58241294/6235116"""

import pefile  # type: ignore
from datetime import datetime
import logging


def get_portable_executable_info(file_path: str) -> dict:
    """
    Return a dictionary with ProductVersion and other params of portable executable.

    The Portable Executable (PE) format is a file format for executables, object code, DLLs and others used in 32-bit
    and 64-bit versions of Windows operating systems. For non PE files this error is raised:
    pefile.PEFormatError: 'DOS Header magic not found.'
    We can use it to provide the info about the tested build to the pytest report.
    """
    decoded_dict: dict = {}  # To avoid "Local variable 'pe' might be referenced before assignment" alert.
    try:
        pe = pefile.PE(file_path)  # type - class 'pefile.PE'
    except FileNotFoundError as error:
        logging.error(f"Check the provided file path: {error}. An empty dict is returned.")
        return decoded_dict
    except pefile.PEFormatError as error:
        logging.error(f"The provided file is not Portable Executable: {error}. An empty dict is returned.")
        return decoded_dict

    file_info: list = pe.FileInfo  # In my case: List of lists. len==1.
    # The nested list contains 2 elements of class 'pefile.Structure': VarFileInfo and StringFileInfo

    for structure in file_info[0]:
        if not hasattr(structure, 'StringTable'):
            logging.debug("This entry doesn't contain attribute StringTable.")
            continue

        string_table: list = structure.StringTable  # In my case:len==1. Contains 1 element of class 'pefile.Structure'
        logging.debug("This entry CONTAINS attribute StringTable.")
        encoded_dict: dict = string_table[0].entries
        decoded_dict = dict((key.decode(), value.decode()) for key, value in encoded_dict.items())

    # Add modification time from FILE_HEADER
    time_date_stamp = pe.FILE_HEADER.TimeDateStamp
    modification_time = str(datetime.fromtimestamp(time_date_stamp))
    decoded_dict["Modification_time"] = modification_time
    return decoded_dict


if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)

 Public
Share a link to this review

4.17% issue ratio

L12 Redundant code / overengineering

This code is not really needed or may be simplified

Suggested change:
decoded_dict = {key.decode(): value.decode() for key, value in encoded_dict.items()}
L9 Bad design

Seems like things could be organized in a better way.

This loop will be performed for all structures. I would rather exit as soon as we find StringTable. Of course unless there are multiple records with StringTable and you want decoded_dict contain the last one.

Suggested change:
decoded_dict = dict((key.decode(), value.decode()) for key, value in encoded_dict.items())
break  # <----

# or
from more_itertools import first, last
with suppress(ValueError):
    structure_with_string_table = first(struct for struct in file_info[0] if hasattr(struct, 'StringTable')))
    string_table = structure.StringTable
    encoded_dict = string_table[0].entries
    decoded_dict = dict((key.decode(), value.decode()) for key, value in encoded_dict.items())

Create new review request