Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/pymsi/package.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import copy
import io
import mmap
from pathlib import Path
from typing import Iterator, Optional, Union

Expand All @@ -18,13 +19,14 @@


class Package:
def __init__(self, path_or_bytesio: Union[Path, io.BytesIO]):
if isinstance(path_or_bytesio, io.BytesIO):
self.path = None
self.file = path_or_bytesio
else:
# TODO: consider typing.BinaryIO
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did typing.BinaryIO work?

Copy link
Owner

@nightlark nightlark Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting discussion typing.*IO: python/typing#829

Leaning towards the current list of type options being fine for now, with the option to change it later if needed for e.g. integration with unblob.

def __init__(self, path_or_bytesio: Union[Path, io.BytesIO, mmap.mmap]):
if isinstance(path_or_bytesio, Path):
self.path = path_or_bytesio.resolve(True)
self.file = self.path.open("rb")
else:
self.path = None
self.file = path_or_bytesio
self.tables = {}
self.ole = None
self.summary = None
Expand Down
49 changes: 49 additions & 0 deletions tests/io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import mmap
from pathlib import Path

import pytest

import pymsi


def read_package(path_or_bytesio):
with pymsi.Package(path_or_bytesio) as package:
msi = pymsi.Msi(package)
return msi


# Function to read a package using Path
def read_package_path(file_path):
path = Path(file_path)
return read_package(path)


# Function to read a package using with open
def read_package_with_open(file_path):
with open(file_path, "rb") as f:
return read_package(f)


# Function to read a package using mmap
def read_package_mmap(file_path):
with open(file_path, "r+b") as f:
mm = mmap.mmap(f.fileno(), 0)
return read_package(mm)


# Test cases
@pytest.mark.parametrize(
"read_package_func", [read_package_path, read_package_with_open, read_package_mmap]
)
def test_read_package(read_package_func):
test_file = "powertoys.msi"

msi = read_package_func(test_file)

size = (msi.package.ole.nb_sect * msi.package.ole.sector_size) + 512

print(size)
Copy link
Owner

@nightlark nightlark Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it would probably be good to pick a specific version of powertoys.msi eventually so that we know what size to expect here. (Doesn't need to be part of this PR though)



if __name__ == "__main__":
pytest.main()
Loading